[ivy] 01/06: Imported Upstream version 2.4.0

Markus Koschany apo-guest at moszumanska.debian.org
Wed Dec 16 12:51:02 UTC 2015


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

apo-guest pushed a commit to annotated tag debian/2.4.0-3
in repository ivy.

commit 1bd8bb1dc55ffa5a2bf06e9843584a12ea418780
Author: Markus Koschany <apo at debian.org>
Date:   Wed Dec 16 00:38:44 2015 +0100

    Imported Upstream version 2.4.0
---
 LICENSE                                            |  258 ++++
 META-INF/MANIFEST.MF                               |  112 ++
 NOTICE                                             |   16 +
 README                                             |   95 ++
 ant.patterns                                       |   24 +
 build.properties                                   |   56 +
 build.xml                                          |  690 +++++++++
 ivy.xml                                            |   70 +
 ivy.xsd                                            |  291 ++++
 optional.patterns                                  |   37 +
 src/etc/checkstyle/RequiredHeader.txt              |   17 +
 src/etc/checkstyle/checkstyle-config               |  152 ++
 src/etc/checkstyle/checkstyle-frames.xsl           |  293 ++++
 src/etc/checkstyle/checkstyle-suppress.xml         |   25 +
 src/etc/checkstyle/checkstyle-text.xsl             |   33 +
 src/etc/checkstyle/checkstyle-xdoc.xsl             |  129 ++
 src/etc/license/license-header.xml                 |   18 +
 src/etc/makepom/pom.template                       |   59 +
 src/example/bintray/build.xml                      |   23 +
 src/example/bintray/ivy.xml                        |   31 +
 src/example/bintray/ivysettings.xml                |   37 +
 src/example/build-a-ivy-repository/build.xml       |  120 ++
 .../settings/ivysettings-advanced.xml              |  161 ++
 .../settings/ivysettings-basic.xml                 |   30 +
 src/example/chained-resolvers/build.xml            |   38 +
 .../chainedresolvers-project/build.xml             |   81 +
 .../chainedresolvers-project/ivy.xml               |   25 +
 .../src/example/Hello.java                         |   37 +
 .../chained-resolvers/settings/ivysettings.xml     |   29 +
 src/example/configurations/jdbc-example/build.xml  |   97 ++
 src/example/configurations/jdbc-example/ivy.xml    |   44 +
 .../configurations/jdbc-example/src/dev.properties |   19 +
 .../src/example/ConfigurationsExample.java         |   64 +
 .../jdbc-example/src/prod.properties               |   19 +
 .../multi-projects/filter-framework/build.xml      |  140 ++
 .../multi-projects/filter-framework/ivy.xml        |   36 +
 .../src/filter/FilterProvider.java                 |   40 +
 .../filter-framework/src/filter/IFilter.java       |   22 +
 .../src/filter/ccimpl/CCFilter.java                |   45 +
 .../src/filter/hmimpl/HMFilter.java                |   42 +
 .../configurations/multi-projects/myapp/build.xml  |   93 ++
 .../configurations/multi-projects/myapp/ivy.xml    |   31 +
 .../multi-projects/myapp/src/myapp/Main.java       |   36 +
 src/example/dependence/build.xml                   |   42 +
 src/example/dependence/dependee/build.xml          |  110 ++
 src/example/dependence/dependee/ivy.xml            |   24 +
 .../dependence/dependee/src/standalone/Main.java   |   64 +
 src/example/dependence/depender/build.xml          |   93 ++
 src/example/dependence/depender/ivy.xml            |   24 +
 .../dependence/depender/src/depending/Main.java    |   40 +
 .../dependence/settings/ivysettings.properties     |   19 +
 src/example/dependence/settings/ivysettings.xml    |   33 +
 src/example/dual/build.xml                         |   38 +
 src/example/dual/project/build.xml                 |   81 +
 src/example/dual/project/ivy.xml                   |   25 +
 src/example/dual/project/src/example/Hello.java    |   51 +
 .../repository/commons-httpclient-ivy-2.0.2.xml    |   29 +
 src/example/dual/settings/ivysettings.xml          |   29 +
 src/example/go-ivy/build.xml                       |  151 ++
 src/example/hello-ivy/build.xml                    |   80 +
 src/example/hello-ivy/ivy.xml                      |   25 +
 src/example/hello-ivy/src/example/Hello.java       |   51 +
 src/example/multi-project/build.xml                |   69 +
 src/example/multi-project/common/build.properties  |   30 +
 src/example/multi-project/common/common.xml        |  205 +++
 .../projects/console/build.properties              |   21 +
 .../multi-project/projects/console/build.xml       |   23 +
 src/example/multi-project/projects/console/ivy.xml |   30 +
 .../projects/console/src/console/Main.java         |   82 ++
 .../multi-project/projects/find/build.properties   |   21 +
 src/example/multi-project/projects/find/build.xml  |   23 +
 src/example/multi-project/projects/find/ivy.xml    |   37 +
 .../projects/find/src/find/FindFile.java           |   48 +
 .../multi-project/projects/find/src/find/Main.java |   76 +
 .../multi-project/projects/list/build.properties   |   21 +
 src/example/multi-project/projects/list/build.xml  |   23 +
 src/example/multi-project/projects/list/ivy.xml    |   35 +
 .../projects/list/src/list/ListFile.java           |   50 +
 .../multi-project/projects/list/src/list/Main.java |   71 +
 .../multi-project/projects/size/build.properties   |   21 +
 src/example/multi-project/projects/size/build.xml  |   23 +
 src/example/multi-project/projects/size/ivy.xml    |   28 +
 .../projects/size/src/size/FileSize.java           |   45 +
 .../projects/sizewhere/build.properties            |   21 +
 .../multi-project/projects/sizewhere/build.xml     |   23 +
 .../multi-project/projects/sizewhere/ivy.xml       |   37 +
 .../projects/sizewhere/src/sizewhere/Main.java     |   71 +
 .../sizewhere/src/sizewhere/SizeWhere.java         |   37 +
 .../projects/version/build.properties              |   21 +
 .../multi-project/projects/version/build.xml       |   23 +
 src/example/multi-project/projects/version/ivy.xml |   24 +
 .../projects/version/src/version/Version.java      |   51 +
 src/example/packager-resolver/ivysettings.xml      |   35 +
 src/java/org/apache/ivy/Ivy.java                   |  975 +++++++++++++
 src/java/org/apache/ivy/Ivy14.java                 |  502 +++++++
 src/java/org/apache/ivy/Main.java                  |  591 ++++++++
 src/java/org/apache/ivy/ant/AddPathTask.java       |  103 ++
 src/java/org/apache/ivy/ant/AntBuildTrigger.java   |  172 +++
 src/java/org/apache/ivy/ant/AntCallTrigger.java    |  130 ++
 src/java/org/apache/ivy/ant/AntMessageLogger.java  |  150 ++
 src/java/org/apache/ivy/ant/BuildOBRTask.java      |  208 +++
 .../org/apache/ivy/ant/ConvertManifestTask.java    |   96 ++
 src/java/org/apache/ivy/ant/FixDepsTask.java       |   90 ++
 src/java/org/apache/ivy/ant/IvyAntSettings.java    |  391 +++++
 .../apache/ivy/ant/IvyAntVariableContainer.java    |  106 ++
 .../org/apache/ivy/ant/IvyArtifactProperty.java    |   97 ++
 src/java/org/apache/ivy/ant/IvyArtifactReport.java |  281 ++++
 src/java/org/apache/ivy/ant/IvyBuildList.java      |  569 ++++++++
 src/java/org/apache/ivy/ant/IvyBuildNumber.java    |  386 +++++
 src/java/org/apache/ivy/ant/IvyCacheFileset.java   |  204 +++
 src/java/org/apache/ivy/ant/IvyCachePath.java      |  116 ++
 src/java/org/apache/ivy/ant/IvyCacheTask.java      |  101 ++
 src/java/org/apache/ivy/ant/IvyCheck.java          |   98 ++
 src/java/org/apache/ivy/ant/IvyCleanCache.java     |   85 ++
 src/java/org/apache/ivy/ant/IvyConfigure.java      |  157 ++
 src/java/org/apache/ivy/ant/IvyConflict.java       |   77 +
 src/java/org/apache/ivy/ant/IvyConvertPom.java     |   77 +
 src/java/org/apache/ivy/ant/IvyDeliver.java        |  452 ++++++
 src/java/org/apache/ivy/ant/IvyDependency.java     |  193 +++
 .../org/apache/ivy/ant/IvyDependencyArtifact.java  |   67 +
 src/java/org/apache/ivy/ant/IvyDependencyConf.java |   64 +
 .../org/apache/ivy/ant/IvyDependencyExclude.java   |   75 +
 .../org/apache/ivy/ant/IvyDependencyInclude.java   |   62 +
 src/java/org/apache/ivy/ant/IvyDependencyTree.java |  132 ++
 .../apache/ivy/ant/IvyDependencyUpdateChecker.java |  214 +++
 src/java/org/apache/ivy/ant/IvyExclude.java        |   75 +
 .../org/apache/ivy/ant/IvyExtractFromSources.java  |  217 +++
 src/java/org/apache/ivy/ant/IvyFindRevision.java   |  104 ++
 src/java/org/apache/ivy/ant/IvyInfo.java           |  212 +++
 src/java/org/apache/ivy/ant/IvyInstall.java        |  228 +++
 src/java/org/apache/ivy/ant/IvyListModules.java    |  158 ++
 src/java/org/apache/ivy/ant/IvyMakePom.java        |  308 ++++
 src/java/org/apache/ivy/ant/IvyOverride.java       |   66 +
 .../org/apache/ivy/ant/IvyPostResolveTask.java     |  460 ++++++
 src/java/org/apache/ivy/ant/IvyPublish.java        |  481 ++++++
 src/java/org/apache/ivy/ant/IvyReport.java         |  434 ++++++
 .../org/apache/ivy/ant/IvyRepositoryReport.java    |  328 +++++
 src/java/org/apache/ivy/ant/IvyResolve.java        |  517 +++++++
 src/java/org/apache/ivy/ant/IvyResources.java      |  118 ++
 src/java/org/apache/ivy/ant/IvyRetrieve.java       |  234 +++
 src/java/org/apache/ivy/ant/IvyTask.java           |  297 ++++
 src/java/org/apache/ivy/ant/IvyVar.java            |  132 ++
 src/java/org/apache/ivy/ant/MapperAdapter.java     |   42 +
 src/java/org/apache/ivy/ant/PackageMapping.java    |   69 +
 src/java/org/apache/ivy/ant/antlib.xml             |   52 +
 .../ivy/core/ExecutionRelativeUrlResolver.java     |   34 +
 src/java/org/apache/ivy/core/IvyContext.java       |  386 +++++
 src/java/org/apache/ivy/core/IvyPatternHelper.java |  526 +++++++
 src/java/org/apache/ivy/core/IvyThread.java        |   70 +
 src/java/org/apache/ivy/core/LogOptions.java       |   57 +
 .../apache/ivy/core/NormalRelativeUrlResolver.java |   32 +
 .../org/apache/ivy/core/RelativeUrlResolver.java   |   67 +
 .../org/apache/ivy/core/cache/ArtifactOrigin.java  |  174 +++
 .../ivy/core/cache/CacheDownloadOptions.java       |   43 +
 .../ivy/core/cache/CacheMetadataOptions.java       |   88 ++
 .../ivy/core/cache/CacheResourceOptions.java       |   32 +
 src/java/org/apache/ivy/core/cache/CacheUtil.java  |   50 +
 .../core/cache/DefaultRepositoryCacheManager.java  | 1543 ++++++++++++++++++++
 .../core/cache/DefaultResolutionCacheManager.java  |  304 ++++
 .../apache/ivy/core/cache/DownloadListener.java    |   34 +
 .../core/cache/ModuleDescriptorMemoryCache.java    |  134 ++
 .../ivy/core/cache/ModuleDescriptorProvider.java   |   31 +
 .../ivy/core/cache/ModuleDescriptorWriter.java     |   30 +
 .../ivy/core/cache/ParserSettingsMonitor.java      |  154 ++
 .../ivy/core/cache/RepositoryCacheManager.java     |  194 +++
 .../ivy/core/cache/ResolutionCacheManager.java     |   48 +
 .../org/apache/ivy/core/check/CheckEngine.java     |  135 ++
 .../apache/ivy/core/check/CheckEngineSettings.java |   29 +
 .../core/deliver/DefaultPublishingDRResolver.java  |   28 +
 .../org/apache/ivy/core/deliver/DeliverEngine.java |  214 +++
 .../ivy/core/deliver/DeliverEngineSettings.java    |   26 +
 .../apache/ivy/core/deliver/DeliverOptions.java    |  270 ++++
 .../PublishingDependencyRevisionResolver.java      |   41 +
 .../org/apache/ivy/core/event/EventManager.java    |   95 ++
 .../apache/ivy/core/event/FilteredIvyListener.java |   46 +
 src/java/org/apache/ivy/core/event/IvyEvent.java   |  127 ++
 .../org/apache/ivy/core/event/IvyEventFilter.java  |  159 ++
 .../org/apache/ivy/core/event/IvyListener.java     |   24 +
 .../ivy/core/event/download/DownloadEvent.java     |   42 +
 .../event/download/EndArtifactDownloadEvent.java   |   63 +
 .../ivy/core/event/download/NeedArtifactEvent.java |   38 +
 .../core/event/download/PrepareDownloadEvent.java  |   36 +
 .../event/download/StartArtifactDownloadEvent.java |   49 +
 .../event/publish/EndArtifactPublishEvent.java     |   56 +
 .../ivy/core/event/publish/PublishEvent.java       |   77 +
 .../event/publish/StartArtifactPublishEvent.java   |   42 +
 .../event/resolve/EndResolveDependencyEvent.java   |   75 +
 .../ivy/core/event/resolve/EndResolveEvent.java    |   43 +
 .../core/event/resolve/ResolveDependencyEvent.java |   55 +
 .../ivy/core/event/resolve/ResolveEvent.java       |   36 +
 .../event/resolve/StartResolveDependencyEvent.java |   32 +
 .../ivy/core/event/resolve/StartResolveEvent.java  |   29 +
 .../event/retrieve/EndRetrieveArtifactEvent.java   |   30 +
 .../ivy/core/event/retrieve/EndRetrieveEvent.java  |   84 ++
 .../core/event/retrieve/RetrieveArtifactEvent.java |   55 +
 .../ivy/core/event/retrieve/RetrieveEvent.java     |   47 +
 .../event/retrieve/StartRetrieveArtifactEvent.java |   30 +
 .../core/event/retrieve/StartRetrieveEvent.java    |   29 +
 .../org/apache/ivy/core/install/InstallEngine.java |  211 +++
 .../ivy/core/install/InstallEngineSettings.java    |   46 +
 .../apache/ivy/core/install/InstallOptions.java    |  101 ++
 .../core/module/descriptor/AbstractArtifact.java   |   81 +
 .../descriptor/AbstractIncludeExcludeRule.java     |   94 ++
 .../ivy/core/module/descriptor/Artifact.java       |  100 ++
 .../ivy/core/module/descriptor/Configuration.java  |  247 ++++
 .../core/module/descriptor/ConfigurationAware.java |   39 +
 .../core/module/descriptor/ConfigurationGroup.java |   71 +
 .../descriptor/ConfigurationIntersection.java      |   72 +
 .../core/module/descriptor/DefaultArtifact.java    |  146 ++
 .../DefaultDependencyArtifactDescriptor.java       |  121 ++
 .../descriptor/DefaultDependencyDescriptor.java    |  706 +++++++++
 .../core/module/descriptor/DefaultExcludeRule.java |   34 +
 .../descriptor/DefaultExtendsDescriptor.java       |   93 ++
 .../core/module/descriptor/DefaultIncludeRule.java |   34 +
 .../module/descriptor/DefaultModuleDescriptor.java |  883 +++++++++++
 .../descriptor/DependencyArtifactDescriptor.java   |   70 +
 .../module/descriptor/DependencyDescriptor.java    |  141 ++
 .../descriptor/DependencyDescriptorMediator.java   |   44 +
 .../ivy/core/module/descriptor/ExcludeRule.java    |   50 +
 .../core/module/descriptor/ExtendsDescriptor.java  |   68 +
 .../core/module/descriptor/ExtraInfoHolder.java    |   76 +
 .../ivy/core/module/descriptor/IncludeRule.java    |   51 +
 .../core/module/descriptor/InheritableItem.java    |   34 +
 .../apache/ivy/core/module/descriptor/License.java |   38 +
 .../ivy/core/module/descriptor/MDArtifact.java     |  126 ++
 .../core/module/descriptor/ModuleDescriptor.java   |  293 ++++
 .../OverrideDependencyDescriptorMediator.java      |   85 ++
 .../org/apache/ivy/core/module/id/ArtifactId.java  |  107 ++
 .../ivy/core/module/id/ArtifactRevisionId.java     |  121 ++
 .../apache/ivy/core/module/id/MatcherLookup.java   |  153 ++
 .../org/apache/ivy/core/module/id/ModuleId.java    |  230 +++
 .../ivy/core/module/id/ModuleRevisionId.java       |  343 +++++
 .../org/apache/ivy/core/module/id/ModuleRules.java |  250 ++++
 .../org/apache/ivy/core/module/id/package.html     |   61 +
 .../org/apache/ivy/core/module/status/Status.java  |   48 +
 .../ivy/core/module/status/StatusManager.java      |  151 ++
 .../org/apache/ivy/core/pack/ArchivePacking.java   |   32 +
 .../apache/ivy/core/pack/OsgiBundlePacking.java    |   48 +
 .../org/apache/ivy/core/pack/Pack200Packing.java   |   55 +
 .../org/apache/ivy/core/pack/PackagingManager.java |  115 ++
 .../org/apache/ivy/core/pack/PackingRegistry.java  |   43 +
 .../org/apache/ivy/core/pack/StreamPacking.java    |   35 +
 src/java/org/apache/ivy/core/pack/ZipPacking.java  |   99 ++
 .../org/apache/ivy/core/publish/PublishEngine.java |  290 ++++
 .../ivy/core/publish/PublishEngineSettings.java    |   32 +
 .../apache/ivy/core/publish/PublishOptions.java    |  183 +++
 .../ivy/core/report/ArtifactDownloadReport.java    |  195 +++
 .../core/report/ConfigurationResolveReport.java    |  365 +++++
 .../org/apache/ivy/core/report/DownloadReport.java |   57 +
 .../org/apache/ivy/core/report/DownloadStatus.java |   62 +
 .../report/MetadataArtifactDownloadReport.java     |   63 +
 .../org/apache/ivy/core/report/ResolveReport.java  |  436 ++++++
 .../repository/RepositoryManagementEngine.java     |  339 +++++
 .../RepositoryManagementEngineSettings.java        |   24 +
 .../apache/ivy/core/resolve/DownloadOptions.java   |   27 +
 src/java/org/apache/ivy/core/resolve/IvyNode.java  | 1344 +++++++++++++++++
 .../apache/ivy/core/resolve/IvyNodeBlacklist.java  |   68 +
 .../apache/ivy/core/resolve/IvyNodeCallers.java    |  296 ++++
 .../apache/ivy/core/resolve/IvyNodeEviction.java   |  439 ++++++
 .../org/apache/ivy/core/resolve/IvyNodeUsage.java  |  326 +++++
 .../org/apache/ivy/core/resolve/ResolveData.java   |  351 +++++
 .../org/apache/ivy/core/resolve/ResolveEngine.java | 1219 ++++++++++++++++
 .../ivy/core/resolve/ResolveEngineSettings.java    |   45 +
 .../apache/ivy/core/resolve/ResolveOptions.java    |  312 ++++
 .../ivy/core/resolve/ResolveProcessException.java  |   46 +
 .../ivy/core/resolve/ResolvedModuleRevision.java   |  143 ++
 .../ivy/core/resolve/RestartResolveProcess.java    |   34 +
 .../org/apache/ivy/core/resolve/VisitData.java     |   72 +
 .../org/apache/ivy/core/resolve/VisitNode.java     |  519 +++++++
 .../apache/ivy/core/retrieve/FileNameMapper.java   |   24 +
 .../apache/ivy/core/retrieve/RetrieveEngine.java   |  496 +++++++
 .../ivy/core/retrieve/RetrieveEngineSettings.java  |   31 +
 .../apache/ivy/core/retrieve/RetrieveOptions.java  |  210 +++
 .../apache/ivy/core/retrieve/RetrieveReport.java   |  101 ++
 .../org/apache/ivy/core/search/ModuleEntry.java    |   51 +
 .../apache/ivy/core/search/OrganisationEntry.java  |   43 +
 .../org/apache/ivy/core/search/RevisionEntry.java  |   59 +
 .../org/apache/ivy/core/search/SearchEngine.java   |  437 ++++++
 .../org/apache/ivy/core/settings/IvyPattern.java   |   33 +
 .../org/apache/ivy/core/settings/IvySettings.java  | 1516 +++++++++++++++++++
 .../ivy/core/settings/IvyVariableContainer.java    |   39 +
 .../core/settings/IvyVariableContainerImpl.java    |  101 ++
 .../org/apache/ivy/core/settings/Validatable.java  |   32 +
 .../ivy/core/settings/XmlSettingsParser.java       |  648 ++++++++
 .../org/apache/ivy/core/settings/ivy.properties    |   43 +
 .../apache/ivy/core/settings/ivysettings-1.4.xml   |   28 +
 .../core/settings/ivysettings-default-chain.xml    |   26 +
 .../apache/ivy/core/settings/ivysettings-local.xml |   29 +
 .../ivy/core/settings/ivysettings-main-chain.xml   |   26 +
 .../ivy/core/settings/ivysettings-public.xml       |   23 +
 .../ivy/core/settings/ivysettings-shared.xml       |   29 +
 .../org/apache/ivy/core/settings/ivysettings.xml   |   26 +
 .../apache/ivy/core/settings/repository.properties |   24 +
 .../apache/ivy/core/settings/typedef.properties    |   67 +
 .../ivy/core/sort/CollectionOfModulesToSort.java   |  111 ++
 .../MessageBasedNonMatchingVersionReporter.java    |   46 +
 .../ivy/core/sort/ModuleDescriptorSorter.java      |  105 ++
 .../org/apache/ivy/core/sort/ModuleInSort.java     |  187 +++
 .../ivy/core/sort/NonMatchingVersionReporter.java  |   36 +
 .../sort/SilentNonMatchingVersionReporter.java     |   32 +
 .../ivy/core/sort/SimpleSortEngineSettings.java    |   45 +
 src/java/org/apache/ivy/core/sort/SortEngine.java  |  127 ++
 .../apache/ivy/core/sort/SortEngineSettings.java   |   31 +
 src/java/org/apache/ivy/core/sort/SortOptions.java |   56 +
 .../sort/WarningNonMatchingVersionReporter.java    |   32 +
 src/java/org/apache/ivy/logo.png                   |  Bin 0 -> 9085 bytes
 .../org/apache/ivy/osgi/core/BundleArtifact.java   |   48 +
 .../org/apache/ivy/osgi/core/BundleCapability.java |   92 ++
 src/java/org/apache/ivy/osgi/core/BundleInfo.java  |  381 +++++
 .../apache/ivy/osgi/core/BundleInfoAdapter.java    |  370 +++++
 .../apache/ivy/osgi/core/BundleRequirement.java    |  111 ++
 .../ivy/osgi/core/ExecutionEnvironmentProfile.java |   80 +
 .../core/ExecutionEnvironmentProfileProvider.java  |  135 ++
 .../org/apache/ivy/osgi/core/ExportPackage.java    |   70 +
 .../ivy/osgi/core/ManifestHeaderElement.java       |  113 ++
 .../apache/ivy/osgi/core/ManifestHeaderValue.java  |  416 ++++++
 .../org/apache/ivy/osgi/core/ManifestParser.java   |  302 ++++
 .../apache/ivy/osgi/core/OSGiManifestParser.java   |  103 ++
 .../apache/ivy/osgi/core/OsgiLatestStrategy.java   |   96 ++
 .../apache/ivy/osgi/core/jvm-packages.properties   |  220 +++
 src/java/org/apache/ivy/osgi/filter/AndFilter.java |   45 +
 .../org/apache/ivy/osgi/filter/CompareFilter.java  |  138 ++
 .../ivy/osgi/filter/MultiOperatorFilter.java       |   90 ++
 src/java/org/apache/ivy/osgi/filter/NotFilter.java |   36 +
 .../org/apache/ivy/osgi/filter/OSGiFilter.java     |   34 +
 .../apache/ivy/osgi/filter/OSGiFilterParser.java   |  217 +++
 src/java/org/apache/ivy/osgi/filter/OrFilter.java  |   45 +
 .../apache/ivy/osgi/filter/UniOperatorFilter.java  |   68 +
 src/java/org/apache/ivy/osgi/obr/OBRResolver.java  |  147 ++
 .../org/apache/ivy/osgi/obr/xml/Capability.java    |   55 +
 .../apache/ivy/osgi/obr/xml/CapabilityAdapter.java |  103 ++
 .../ivy/osgi/obr/xml/CapabilityProperty.java       |   51 +
 .../org/apache/ivy/osgi/obr/xml/OBRXMLParser.java  |  407 ++++++
 .../org/apache/ivy/osgi/obr/xml/OBRXMLWriter.java  |  288 ++++
 .../org/apache/ivy/osgi/obr/xml/Requirement.java   |   61 +
 .../ivy/osgi/obr/xml/RequirementAdapter.java       |  184 +++
 .../osgi/obr/xml/UnsupportedFilterException.java   |   25 +
 src/java/org/apache/ivy/osgi/p2/P2Artifact.java    |   53 +
 .../org/apache/ivy/osgi/p2/P2ArtifactParser.java   |  248 ++++
 .../org/apache/ivy/osgi/p2/P2CompositeParser.java  |  123 ++
 src/java/org/apache/ivy/osgi/p2/P2Descriptor.java  |  152 ++
 .../org/apache/ivy/osgi/p2/P2MetadataParser.java   |  922 ++++++++++++
 .../org/apache/ivy/osgi/p2/PropertiesParser.java   |   80 +
 .../org/apache/ivy/osgi/p2/XMLInputParser.java     |   30 +
 .../ivy/osgi/repo/AbstractFSManifestIterable.java  |  155 ++
 .../apache/ivy/osgi/repo/AbstractOSGiResolver.java |  540 +++++++
 .../ivy/osgi/repo/AggregatedOSGiResolver.java      |   40 +
 .../ivy/osgi/repo/AggregatedRepoDescriptor.java    |  112 ++
 .../osgi/repo/ArtifactReportManifestIterable.java  |  172 +++
 .../ivy/osgi/repo/BundleCapabilityAndLocation.java |   57 +
 .../apache/ivy/osgi/repo/BundleRepoDescriptor.java |   71 +
 .../ivy/osgi/repo/EditableRepoDescriptor.java      |  173 +++
 .../apache/ivy/osgi/repo/FSManifestIterable.java   |  126 ++
 .../apache/ivy/osgi/repo/ManifestAndLocation.java  |   54 +
 .../ivy/osgi/repo/ModuleDescriptorWrapper.java     |   99 ++
 .../ivy/osgi/repo/RelativeURLRepository.java       |   73 +
 .../org/apache/ivy/osgi/repo/RepoDescriptor.java   |   33 +
 .../ivy/osgi/repo/RepositoryManifestIterable.java  |   73 +
 .../ivy/osgi/repo/ResolverManifestIterable.java    |  205 +++
 .../apache/ivy/osgi/updatesite/PluginAdapter.java  |   81 +
 .../ivy/osgi/updatesite/UpdateSiteDescriptor.java  |   41 +
 .../ivy/osgi/updatesite/UpdateSiteLoader.java      |  338 +++++
 .../ivy/osgi/updatesite/UpdateSiteResolver.java    |  140 ++
 .../apache/ivy/osgi/updatesite/xml/Archive.java    |   32 +
 .../ivy/osgi/updatesite/xml/CategoryDef.java       |   37 +
 .../ivy/osgi/updatesite/xml/EclipseFeature.java    |  176 +++
 .../ivy/osgi/updatesite/xml/EclipsePlugin.java     |   59 +
 .../updatesite/xml/EclipseUpdateSiteParser.java    |  277 ++++
 .../ivy/osgi/updatesite/xml/FeatureParser.java     |  390 +++++
 .../apache/ivy/osgi/updatesite/xml/Require.java    |   73 +
 .../apache/ivy/osgi/updatesite/xml/UpdateSite.java |   72 +
 .../updatesite/xml/UpdateSiteDigestParser.java     |   65 +
 .../apache/ivy/osgi/util/DelegatingHandler.java    |  636 ++++++++
 src/java/org/apache/ivy/osgi/util/ParseUtil.java   |   82 ++
 src/java/org/apache/ivy/osgi/util/Version.java     |  223 +++
 .../apache/ivy/osgi/util/VersionComparator.java    |   39 +
 .../org/apache/ivy/osgi/util/VersionRange.java     |  372 +++++
 src/java/org/apache/ivy/osgi/util/ZipUtil.java     |   66 +
 .../org/apache/ivy/plugins/IvySettingsAware.java   |   24 +
 .../AbstractCircularDependencyStrategy.java        |   34 +
 .../AbstractLogCircularDependencyStrategy.java     |   56 +
 .../circular/CircularDependencyException.java      |   43 +
 .../plugins/circular/CircularDependencyHelper.java |   86 ++
 .../circular/CircularDependencyStrategy.java       |   31 +
 .../circular/ErrorCircularDependencyStrategy.java  |   38 +
 .../circular/IgnoreCircularDependencyStrategy.java |   40 +
 .../circular/WarnCircularDependencyStrategy.java   |   39 +
 .../plugins/conflict/AbstractConflictManager.java  |   53 +
 .../ivy/plugins/conflict/ConflictManager.java      |   60 +
 .../ivy/plugins/conflict/FixedConflictManager.java |   51 +
 .../conflict/LatestCompatibleConflictManager.java  |  312 ++++
 .../plugins/conflict/LatestConflictManager.java    |  171 +++
 .../ivy/plugins/conflict/NoConflictManager.java    |   32 +
 .../plugins/conflict/RegexpConflictManager.java    |  120 ++
 .../plugins/conflict/StrictConflictException.java  |   49 +
 .../plugins/conflict/StrictConflictManager.java    |   53 +
 .../ivy/plugins/latest/AbstractLatestStrategy.java |   52 +
 .../apache/ivy/plugins/latest/ArtifactInfo.java    |   24 +
 .../plugins/latest/ComparatorLatestStrategy.java   |   51 +
 .../latest/LatestLexicographicStrategy.java        |   55 +
 .../ivy/plugins/latest/LatestRevisionStrategy.java |  198 +++
 .../apache/ivy/plugins/latest/LatestStrategy.java  |   45 +
 .../ivy/plugins/latest/LatestTimeStrategy.java     |   36 +
 .../ivy/plugins/lock/AbstractLockStrategy.java     |   47 +
 .../ivy/plugins/lock/ArtifactLockStrategy.java     |   38 +
 .../ivy/plugins/lock/CreateFileLockStrategy.java   |   27 +
 .../apache/ivy/plugins/lock/DeleteOnExitHook.java  |   54 +
 .../ivy/plugins/lock/FileBasedLockStrategy.java    |  356 +++++
 .../org/apache/ivy/plugins/lock/LockStrategy.java  |   73 +
 .../ivy/plugins/lock/NIOFileLockStrategy.java      |   27 +
 .../apache/ivy/plugins/lock/NoLockStrategy.java    |   35 +
 .../plugins/matcher/AbstractPatternMatcher.java    |   63 +
 .../org/apache/ivy/plugins/matcher/AnyMatcher.java |   40 +
 .../matcher/ExactOrRegexpPatternMatcher.java       |   64 +
 .../ivy/plugins/matcher/ExactPatternMatcher.java   |   56 +
 .../ivy/plugins/matcher/GlobPatternMatcher.java    |  104 ++
 .../org/apache/ivy/plugins/matcher/MapMatcher.java |   70 +
 .../org/apache/ivy/plugins/matcher/Matcher.java    |   44 +
 .../apache/ivy/plugins/matcher/MatcherHelper.java  |   68 +
 .../org/apache/ivy/plugins/matcher/NoMatcher.java  |   41 +
 .../apache/ivy/plugins/matcher/PatternMatcher.java |   72 +
 .../ivy/plugins/matcher/RegexpPatternMatcher.java  |   90 ++
 .../org/apache/ivy/plugins/namespace/MRIDRule.java |   73 +
 .../plugins/namespace/MRIDTransformationRule.java  |  147 ++
 .../ivy/plugins/namespace/NameSpaceHelper.java     |  113 ++
 .../apache/ivy/plugins/namespace/Namespace.java    |  109 ++
 .../ivy/plugins/namespace/NamespaceRule.java       |   66 +
 .../plugins/namespace/NamespaceTransformer.java    |   26 +
 .../parser/AbstractModuleDescriptorParser.java     |  366 +++++
 .../ivy/plugins/parser/ModuleDescriptorParser.java |   68 +
 .../parser/ModuleDescriptorParserRegistry.java     |  104 ++
 .../apache/ivy/plugins/parser/ParserSettings.java  |   62 +
 .../plugins/parser/m2/DefaultPomDependencyMgt.java |   61 +
 .../ivy/plugins/parser/m2/PomDependencyMgt.java    |   33 +
 .../parser/m2/PomModuleDescriptorBuilder.java      |  736 ++++++++++
 .../parser/m2/PomModuleDescriptorParser.java       |  380 +++++
 .../parser/m2/PomModuleDescriptorWriter.java       |  392 +++++
 .../apache/ivy/plugins/parser/m2/PomReader.java    |  629 ++++++++
 .../ivy/plugins/parser/m2/PomWriterOptions.java    |  214 +++
 .../apache/ivy/plugins/parser/m2/m2-entities.ent   |  114 ++
 .../org/apache/ivy/plugins/parser/m2/pom.template  |   33 +
 .../ivy/plugins/parser/xml/UpdateOptions.java      |  222 +++
 .../parser/xml/XmlModuleDescriptorParser.java      | 1387 ++++++++++++++++++
 .../parser/xml/XmlModuleDescriptorUpdater.java     | 1340 +++++++++++++++++
 .../parser/xml/XmlModuleDescriptorWriter.java      |  576 ++++++++
 src/java/org/apache/ivy/plugins/parser/xml/ivy.xsd |  291 ++++
 .../ivy/plugins/report/LogReportOutputter.java     |  199 +++
 .../apache/ivy/plugins/report/ReportOutputter.java |   38 +
 .../ivy/plugins/report/XmlReportOutputter.java     |   75 +
 .../apache/ivy/plugins/report/XmlReportParser.java |  333 +++++
 .../apache/ivy/plugins/report/XmlReportWriter.java |  319 ++++
 .../ivy/plugins/report/ivy-report-dot-all.xsl      |   34 +
 .../apache/ivy/plugins/report/ivy-report-dot.xsl   |   49 +
 .../ivy/plugins/report/ivy-report-graph-all.xsl    |   64 +
 .../apache/ivy/plugins/report/ivy-report-graph.xsl |   85 ++
 .../org/apache/ivy/plugins/report/ivy-report.css   |  279 ++++
 .../org/apache/ivy/plugins/report/ivy-report.xsl   |  514 +++++++
 .../ivy/plugins/repository/AbstractRepository.java |  136 ++
 .../repository/ArtifactResourceResolver.java       |   29 +
 .../ivy/plugins/repository/BasicResource.java      |   76 +
 .../ivy/plugins/repository/LazyResource.java       |   97 ++
 .../plugins/repository/LocalizableResource.java    |   38 +
 .../apache/ivy/plugins/repository/Repository.java  |  153 ++
 .../repository/RepositoryCopyProgressListener.java |   56 +
 .../apache/ivy/plugins/repository/Resource.java    |  100 ++
 .../ivy/plugins/repository/ResourceDownloader.java |   38 +
 .../ivy/plugins/repository/ResourceHelper.java     |   61 +
 .../ivy/plugins/repository/TransferEvent.java      |  357 +++++
 .../ivy/plugins/repository/TransferListener.java   |   27 +
 .../plugins/repository/file/FileRepository.java    |  150 ++
 .../ivy/plugins/repository/file/FileResource.java  |   76 +
 .../ivy/plugins/repository/jar/JarRepository.java  |   89 ++
 .../ivy/plugins/repository/jar/JarResource.java    |   73 +
 .../plugins/repository/sftp/SFTPRepository.java    |  289 ++++
 .../ivy/plugins/repository/sftp/SFTPResource.java  |   87 ++
 .../repository/ssh/AbstractSshBasedRepository.java |  320 ++++
 .../plugins/repository/ssh/RemoteScpException.java |   53 +
 .../org/apache/ivy/plugins/repository/ssh/Scp.java |  611 ++++++++
 .../ivy/plugins/repository/ssh/SshCache.java       |  453 ++++++
 .../ivy/plugins/repository/ssh/SshRepository.java  |  473 ++++++
 .../ivy/plugins/repository/ssh/SshResource.java    |  139 ++
 .../plugins/repository/url/ChainedRepository.java  |  115 ++
 .../ivy/plugins/repository/url/URLRepository.java  |  141 ++
 .../ivy/plugins/repository/url/URLResource.java    |  116 ++
 .../ivy/plugins/repository/vfs/VfsRepository.java  |  216 +++
 .../ivy/plugins/repository/vfs/VfsResource.java    |  206 +++
 .../apache/ivy/plugins/repository/vfs/ivy_vfs.xml  |   50 +
 .../plugins/repository/vsftp/VsftpRepository.java  |  745 ++++++++++
 .../plugins/repository/vsftp/VsftpResource.java    |   57 +
 .../resolver/AbstractPatternsBasedResolver.java    |  366 +++++
 .../ivy/plugins/resolver/AbstractResolver.java     |  606 ++++++++
 .../plugins/resolver/AbstractSshBasedResolver.java |  147 ++
 .../apache/ivy/plugins/resolver/BasicResolver.java | 1164 +++++++++++++++
 .../ivy/plugins/resolver/BintrayResolver.java      |   81 +
 .../apache/ivy/plugins/resolver/CacheResolver.java |  185 +++
 .../apache/ivy/plugins/resolver/ChainResolver.java |  349 +++++
 .../ivy/plugins/resolver/DependencyResolver.java   |  210 +++
 .../apache/ivy/plugins/resolver/DualResolver.java  |  218 +++
 .../ivy/plugins/resolver/FileSystemResolver.java   |  323 ++++
 .../ivy/plugins/resolver/IBiblioResolver.java      |  554 +++++++
 .../ivy/plugins/resolver/IvyRepResolver.java       |  309 ++++
 .../apache/ivy/plugins/resolver/JarResolver.java   |  101 ++
 .../ivy/plugins/resolver/MirroredURLResolver.java  |  115 ++
 .../ivy/plugins/resolver/RepositoryResolver.java   |  347 +++++
 .../ivy/plugins/resolver/ResolverSettings.java     |   58 +
 .../apache/ivy/plugins/resolver/SFTPResolver.java  |   41 +
 .../apache/ivy/plugins/resolver/SshResolver.java   |   89 ++
 .../apache/ivy/plugins/resolver/URLResolver.java   |   34 +
 .../apache/ivy/plugins/resolver/VfsResolver.java   |   61 +
 .../apache/ivy/plugins/resolver/VsftpResolver.java |   74 +
 .../resolver/packager/BuiltFileResource.java       |   85 ++
 .../resolver/packager/PackagerCacheEntry.java      |  242 +++
 .../resolver/packager/PackagerResolver.java        |  255 ++++
 .../apache/ivy/plugins/resolver/packager/build.xml |   58 +
 .../ivy/plugins/resolver/packager/packager-1.0.xsd |  101 ++
 .../ivy/plugins/resolver/packager/packager.xsl     |  499 +++++++
 .../plugins/resolver/util/ApacheHttpURLLister.java |   40 +
 .../ivy/plugins/resolver/util/FileURLLister.java   |   69 +
 .../plugins/resolver/util/HasLatestStrategy.java   |   28 +
 .../plugins/resolver/util/MDResolvedResource.java  |   35 +
 .../plugins/resolver/util/ResolvedResource.java    |   48 +
 .../ivy/plugins/resolver/util/ResolverHelper.java  |  332 +++++
 .../plugins/resolver/util/ResourceMDParser.java    |   34 +
 .../ivy/plugins/resolver/util/URLLister.java       |   35 +
 .../ivy/plugins/signer/SignatureGenerator.java     |   31 +
 .../bouncycastle/OpenPGPSignatureGenerator.java    |  185 +++
 .../ivy/plugins/trigger/AbstractTrigger.java       |   83 ++
 .../org/apache/ivy/plugins/trigger/LogTrigger.java |  133 ++
 .../org/apache/ivy/plugins/trigger/Trigger.java    |   25 +
 .../plugins/version/AbstractVersionMatcher.java    |   78 +
 .../ivy/plugins/version/ChainVersionMatcher.java   |  155 ++
 .../ivy/plugins/version/ExactVersionMatcher.java   |   35 +
 .../ivy/plugins/version/LatestVersionMatcher.java  |   62 +
 src/java/org/apache/ivy/plugins/version/Match.java |  136 ++
 .../ivy/plugins/version/PatternVersionMatcher.java |   99 ++
 .../ivy/plugins/version/SubVersionMatcher.java     |   46 +
 .../apache/ivy/plugins/version/VersionMatcher.java |  110 ++
 .../ivy/plugins/version/VersionRangeMatcher.java   |  246 ++++
 .../ivy/tools/analyser/DependencyAnalyser.java     |   24 +
 .../tools/analyser/JarJarDependencyAnalyser.java   |   95 ++
 .../org/apache/ivy/tools/analyser/JarModule.java   |   46 +
 .../apache/ivy/tools/analyser/JarModuleFinder.java |   80 +
 .../ivy/tools/analyser/RepositoryAnalyser.java     |   60 +
 .../org/apache/ivy/util/AbstractMessageLogger.java |  199 +++
 src/java/org/apache/ivy/util/Checks.java           |   61 +
 src/java/org/apache/ivy/util/ChecksumHelper.java   |  168 +++
 src/java/org/apache/ivy/util/CollectionUtils.java  |   33 +
 .../org/apache/ivy/util/ConfigurationUtils.java    |   96 ++
 src/java/org/apache/ivy/util/Configurator.java     |  764 ++++++++++
 .../org/apache/ivy/util/ContextualSAXHandler.java  |   64 +
 .../org/apache/ivy/util/CopyProgressEvent.java     |   60 +
 .../org/apache/ivy/util/CopyProgressListener.java  |   29 +
 src/java/org/apache/ivy/util/Credentials.java      |  102 ++
 src/java/org/apache/ivy/util/CredentialsUtil.java  |  167 +++
 src/java/org/apache/ivy/util/DateUtil.java         |   42 +
 .../org/apache/ivy/util/DefaultMessageLogger.java  |   51 +
 .../org/apache/ivy/util/EncrytedProperties.java    |   65 +
 src/java/org/apache/ivy/util/FileResolver.java     |   44 +
 src/java/org/apache/ivy/util/FileUtil.java         |  731 ++++++++++
 src/java/org/apache/ivy/util/HexEncoder.java       |   39 +
 src/java/org/apache/ivy/util/HostUtil.java         |   53 +
 src/java/org/apache/ivy/util/MemoryUtil.java       |   90 ++
 src/java/org/apache/ivy/util/Message.java          |  218 +++
 src/java/org/apache/ivy/util/MessageLogger.java    |  108 ++
 .../org/apache/ivy/util/MessageLoggerEngine.java   |  207 +++
 .../org/apache/ivy/util/MessageLoggerHelper.java   |   55 +
 src/java/org/apache/ivy/util/PropertiesFile.java   |   75 +
 src/java/org/apache/ivy/util/StringUtils.java      |  192 +++
 src/java/org/apache/ivy/util/XMLHelper.java        |  243 +++
 src/java/org/apache/ivy/util/cli/CommandLine.java  |   58 +
 .../org/apache/ivy/util/cli/CommandLineParser.java |  141 ++
 src/java/org/apache/ivy/util/cli/Option.java       |  124 ++
 .../org/apache/ivy/util/cli/OptionBuilder.java     |   69 +
 .../org/apache/ivy/util/cli/ParseException.java    |   24 +
 .../ivy/util/extendable/DefaultExtendableItem.java |   38 +
 .../apache/ivy/util/extendable/ExtendableItem.java |   78 +
 .../ivy/util/extendable/ExtendableItemHelper.java  |   74 +
 .../extendable/UnmodifiableExtendableItem.java     |   97 ++
 src/java/org/apache/ivy/util/filter/AndFilter.java |   41 +
 .../apache/ivy/util/filter/ArtifactTypeFilter.java |   39 +
 src/java/org/apache/ivy/util/filter/Filter.java    |   22 +
 .../org/apache/ivy/util/filter/FilterHelper.java   |   82 ++
 src/java/org/apache/ivy/util/filter/NoFilter.java  |   33 +
 src/java/org/apache/ivy/util/filter/NotFilter.java |   34 +
 src/java/org/apache/ivy/util/filter/OrFilter.java  |   41 +
 .../apache/ivy/util/url/AbstractURLHandler.java    |  176 +++
 .../org/apache/ivy/util/url/ApacheURLLister.java   |  209 +++
 .../org/apache/ivy/util/url/BasicURLHandler.java   |  330 +++++
 .../org/apache/ivy/util/url/CredentialsStore.java  |   64 +
 .../org/apache/ivy/util/url/HttpClientHandler.java |  431 ++++++
 .../org/apache/ivy/util/url/IvyAuthenticator.java  |  157 ++
 src/java/org/apache/ivy/util/url/URLHandler.java   |  173 +++
 .../apache/ivy/util/url/URLHandlerDispatcher.java  |  109 ++
 .../apache/ivy/util/url/URLHandlerRegistry.java    |   73 +
 594 files changed, 87354 insertions(+)

diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..eb06170
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,258 @@
+                                 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 JCraft JSch package
+------------------------------------------------------------------------------
+Copyright (c) 2002,2003,2004,2005,2006,2007 Atsuhiko Yamanaka, JCraft,Inc. 
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+  1. Redistributions of source code must retain the above copyright notice,
+     this list of conditions and the following disclaimer.
+
+  2. 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.
+
+  3. The names of the authors may not be used to endorse or promote products
+     derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
+INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
+INC. OR ANY CONTRIBUTORS TO THIS 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 jQuery
+------------------------------------------------------------------------------
+Copyright (c) 2007 John Resig, http://jquery.com/
+
+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.
+ 
\ No newline at end of file
diff --git a/META-INF/MANIFEST.MF b/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..c4986fc
--- /dev/null
+++ b/META-INF/MANIFEST.MF
@@ -0,0 +1,112 @@
+Manifest-Version: 1.0
+Main-Class: org.apache.ivy.Main
+Bundle-Version: 2.4.0
+Bundle-Name: Ivy
+Bundle-ManifestVersion: 2
+Bundle-SymbolicName: org.apache.ivy
+Bundle-Vendor: Apache Software Foundation
+Bundle-DocURL: http://ant.apache.org/ivy/
+Import-Package: com.jcraft.jsch;resolution:=optional,
+ javax.crypto;resolution:=optional,
+ javax.swing;resolution:=optional,
+ javax.swing.event;resolution:=optional,
+ javax.xml.parsers,
+ javax.xml.transform,
+ javax.xml.transform.sax,
+ javax.xml.transform.stream,
+ org.apache.commons.httpclient;resolution:=optional,
+ org.apache.commons.httpclient.auth;resolution:=optional,
+ org.apache.commons.httpclient.methods;resolution:=optional,
+ org.apache.commons.httpclient.params;resolution:=optional,
+ org.apache.commons.httpclient.protocol;resolution:=optional,
+ org.apache.commons.net.ftp;resolution:=optional,
+ org.apache.commons.vfs;resolution:=optional,
+ org.apache.commons.vfs.impl;resolution:=optional,
+ org.apache.commons.vfs.provider;resolution:=optional,
+ org.apache.commons.vfs.provider.ftp;resolution:=optional,
+ org.apache.commons.vfs.provider.local;resolution:=optional,
+ org.apache.commons.vfs.provider.sftp;resolution:=optional,
+ org.apache.commons.vfs.provider.url;resolution:=optional,
+ org.apache.oro.text;resolution:=optional,
+ org.apache.oro.text.regex;resolution:=optional,
+ org.apache.tools.ant;resolution:=optional,
+ org.apache.tools.ant.filters;resolution:=optional,
+ org.apache.tools.ant.taskdefs;resolution:=optional,
+ org.apache.tools.ant.types;resolution:=optional,
+ org.apache.tools.ant.types.resources;resolution:=optional,
+ org.apache.tools.ant.util;resolution:=optional,
+ org.apache.webdav;resolution:=optional,
+ org.bouncycastle.bcpg;resolution:=optional,
+ org.bouncycastle.jce.provider;resolution:=optional,
+ org.bouncycastle.openpgp;resolution:=optional,
+ org.w3c.dom;resolution:=optional,
+ org.xml.sax,
+ org.xml.sax.ext,
+ org.xml.sax.helpers
+Export-Package: org.apache.ivy;version="2.0.0",
+ org.apache.ivy.ant;version="2.0.0",
+ org.apache.ivy.core;version="2.0.0",
+ org.apache.ivy.core.cache;version="2.0.0",
+ org.apache.ivy.core.check;version="2.0.0",
+ org.apache.ivy.core.deliver;version="2.0.0",
+ org.apache.ivy.core.event;version="2.0.0",
+ org.apache.ivy.core.event.download;version="2.0.0",
+ org.apache.ivy.core.event.publish;version="2.0.0",
+ org.apache.ivy.core.event.resolve;version="2.0.0",
+ org.apache.ivy.core.event.retrieve;version="2.0.0",
+ org.apache.ivy.core.install;version="2.0.0",
+ org.apache.ivy.core.module.descriptor;version="2.0.0",
+ org.apache.ivy.core.module.id;version="2.0.0",
+ org.apache.ivy.core.module.status;version="2.0.0",
+ org.apache.ivy.core.pack;version="2.4.0",
+ org.apache.ivy.core.publish;version="2.0.0",
+ org.apache.ivy.core.report;version="2.0.0",
+ org.apache.ivy.core.repository;version="2.0.0",
+ org.apache.ivy.core.resolve;version="2.0.0",
+ org.apache.ivy.core.retrieve;version="2.0.0",
+ org.apache.ivy.core.search;version="2.0.0",
+ org.apache.ivy.core.settings;version="2.0.0",
+ org.apache.ivy.core.sort;version="2.0.0",
+ org.apache.ivy.osgi.core;version="2.3.0",
+ org.apache.ivy.osgi.filter;version="2.3.0",
+ org.apache.ivy.osgi.obr;version="2.3.0",
+ org.apache.ivy.osgi.obr.xml;version="2.3.0",
+ org.apache.ivy.osgi.p2;version="2.3.0",
+ org.apache.ivy.osgi.repo;version="2.3.0",
+ org.apache.ivy.osgi.updatesite;version="2.3.0",
+ org.apache.ivy.osgi.updatesite.xml;version="2.3.0",
+ org.apache.ivy.osgi.util;version="2.3.0",
+ org.apache.ivy.plugins;version="2.0.0",
+ org.apache.ivy.plugins.circular;version="2.0.0",
+ org.apache.ivy.plugins.conflict;version="2.0.0",
+ org.apache.ivy.plugins.latest;version="2.0.0",
+ org.apache.ivy.plugins.lock;version="2.0.0",
+ org.apache.ivy.plugins.matcher;version="2.0.0",
+ org.apache.ivy.plugins.namespace;version="2.0.0",
+ org.apache.ivy.plugins.parser;version="2.0.0",
+ org.apache.ivy.plugins.parser.m2;version="2.0.0",
+ org.apache.ivy.plugins.parser.xml;version="2.0.0",
+ org.apache.ivy.plugins.report;version="2.0.0",
+ org.apache.ivy.plugins.repository;version="2.0.0",
+ org.apache.ivy.plugins.repository.file;version="2.0.0",
+ org.apache.ivy.plugins.repository.jar;version="2.3.0",
+ org.apache.ivy.plugins.repository.sftp;version="2.0.0",
+ org.apache.ivy.plugins.repository.ssh;version="2.0.0",
+ org.apache.ivy.plugins.repository.url;version="2.0.0",
+ org.apache.ivy.plugins.repository.vfs;version="2.0.0",
+ org.apache.ivy.plugins.repository.vsftp;version="2.0.0",
+ org.apache.ivy.plugins.resolver;version="2.0.0",
+ org.apache.ivy.plugins.resolver.packager;version="2.0.0",
+ org.apache.ivy.plugins.resolver.util;version="2.0.0",
+ org.apache.ivy.plugins.signer;version="2.2.0",
+ org.apache.ivy.plugins.signer.bouncycastle;version="2.2.0",
+ org.apache.ivy.plugins.trigger;version="2.0.0",
+ org.apache.ivy.plugins.version;version="2.0.0",
+ org.apache.ivy.tools.analyser;version="2.0.0",
+ org.apache.ivy.util;version="2.0.0",
+ org.apache.ivy.util.cli;version="2.0.0",
+ org.apache.ivy.util.extendable;version="2.0.0",
+ org.apache.ivy.util.filter;version="2.0.0",
+ org.apache.ivy.util.url;version="2.0.0"
+Bundle-ClassPath: .
+Bundle-RequiredExecutionEnvironment: J2SE-1.5
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..14eef16
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,16 @@
+Apache Ivy (TM)
+Copyright 2007-2014 The Apache Software Foundation
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
+
+Portions of Ivy were originally developed at
+Jayasoft SARL (http://www.jayasoft.fr/)
+and are licensed to the Apache Software Foundation under the
+"Software Grant License Agreement"
+
+SSH and SFTP support is provided by the JCraft JSch package, 
+which is open source software, available under
+the terms of a BSD style license.  
+The original software and related information is available
+at http://www.jcraft.com/jsch/.
diff --git a/README b/README
new file mode 100644
index 0000000..242a2c2
--- /dev/null
+++ b/README
@@ -0,0 +1,95 @@
+                             Apache Ivy (TM)
+-----------------------------------------------------------------------
+
+
+What is Apache Ivy?
+--------------------
+
+Apache Ivy is a tool for managing (recording, tracking, resolving and reporting) 
+project dependencies. 
+It is characterized by the following:
+
+   1. flexibility and configurability 
+       Apache Ivy is essentially process agnostic and is not tied to any 
+       methodology or structure. 
+       Instead it provides the necessary flexibility and configurability 
+       to be adapted to a broad range of dependency management and build 
+       processes.
+   2. tight integration with Apache Ant
+       while available as a standalone tool, Apache Ivy works particularly well 
+       with Apache Ant providing a number of powerful Ant tasks ranging 
+       from dependency resolution to dependency reporting and publication.
+
+
+Latest Changes:
+----------------
+
+See the list of the changes since the last release in the file doc/release-notes.html
+
+
+Supported Platforms
+--------------------
+
+Please see the the detailed documentation about Ivy compatibility there: doc/compatibility.html
+
+
+Installation
+-------------
+
+Please read doc/install.html for installation instructions.
+
+
+Licensing
+---------
+
+This software is licensed under the terms you may find in the file 
+named "LICENSE" in this directory.
+
+
+Crypto Notice
+-------------
+This distribution includes cryptographic software.  The country in 
+which you currently reside may have restrictions on the import, 
+possession, use, and/or re-export to another country, of 
+encryption software.  BEFORE using any encryption software, please 
+check your country's laws, regulations and policies concerning the
+import, possession, or use, and re-export of encryption software, to 
+see if this is permitted.  See <http://www.wassenaar.org/> for more
+information.
+
+The U.S. Government Department of Commerce, Bureau of Industry and
+Security (BIS), has classified this software as Export Commodity 
+Control Number (ECCN) 5D002.C.1, which includes information security
+software using or performing cryptographic functions with asymmetric
+algorithms.  The form and manner of this Apache Software Foundation
+distribution makes it eligible for export under the License Exception
+ENC Technology Software Unrestricted (TSU) exception (see the BIS 
+Export Administration Regulations, Section 740.13) for both object 
+code and source code.
+
+The following provides more details on the included cryptographic
+software:
+
+The Ivy ssh resolver requires the JSch library
+<http://www.jcraft.com/jsch/index.html>. 
+The sftp and https resolvers requires the Java Cryptography extensions
+<http://java.sun.com/javase/technologies/security/>.
+The PGP signature generator requires the BouncyCastle Java cryptography APIs
+<http://www.bouncycastle.org/java.html>.
+
+
+How to Get Involved
+--------------------
+
+The Apache Ivy project really needs and appreciates any contributions, 
+including documentation help, source code and feedback.  If you are interested
+in contributing, please visit http://ant.apache.org/ivy/get-involved.html.
+
+
+How to Report Issues
+--------------------
+
+The Apache Ivy project uses JIRA for issue tracking.  Please report any 
+issues you find at http://issues.apache.org/jira/browse/IVY
+
+
diff --git a/ant.patterns b/ant.patterns
new file mode 100644
index 0000000..f87adf9
--- /dev/null
+++ b/ant.patterns
@@ -0,0 +1,24 @@
+#	 ***************************************************************
+#	 * Licensed to the Apache Software Foundation (ASF) under one
+#	 * or more contributor license agreements.  See the NOTICE file
+#	 * distributed with this work for additional information
+#	 * regarding copyright ownership.  The ASF licenses this file
+#	 * to you under the Apache License, Version 2.0 (the
+#	 * "License"); you may not use this file except in compliance
+#	 * with the License.  You may obtain a copy of the License at
+#	 * 
+#	 *   http://www.apache.org/licenses/LICENSE-2.0
+#	 * 
+#	 * Unless required by applicable law or agreed to in writing,
+#	 * software distributed under the License is distributed on an
+#	 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#	 * KIND, either express or implied.  See the License for the
+#	 * specific language governing permissions and limitations
+#	 * under the License.
+#	 ***************************************************************
+
+#This section defines the sources to compile for ivy-ant.jar
+org/apache/ivy/ant/*.java
+
+#This section defines the resources to copy for ivy-ant.jar
+org/apache/ivy/ant/*.xml
diff --git a/build.properties b/build.properties
new file mode 100644
index 0000000..cc08112
--- /dev/null
+++ b/build.properties
@@ -0,0 +1,56 @@
+#	 ***************************************************************
+#	 * Licensed to the Apache Software Foundation (ASF) under one
+#	 * or more contributor license agreements.  See the NOTICE file
+#	 * distributed with this work for additional information
+#	 * regarding copyright ownership.  The ASF licenses this file
+#	 * to you under the Apache License, Version 2.0 (the
+#	 * "License"); you may not use this file except in compliance
+#	 * with the License.  You may obtain a copy of the License at
+#	 * 
+#	 *   http://www.apache.org/licenses/LICENSE-2.0
+#	 * 
+#	 * Unless required by applicable law or agreed to in writing,
+#	 * software distributed under the License is distributed on an
+#	 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#	 * KIND, either express or implied.  See the License for the
+#	 * specific language governing permissions and limitations
+#	 * under the License.
+#	 ***************************************************************
+lib.dir=${basedir}/lib
+src.dir=${basedir}/src/java
+test.dir=${basedir}/test/java
+example.dir=${basedir}/src/example
+build.dir=${basedir}/build
+classes.build.dir=${basedir}/build/classes
+core.classes.build.dir=${classes.build.dir}/core
+bootstrap.classes.build.dir=${classes.build.dir}/bootstrap
+ant.classes.build.dir=${classes.build.dir}/ant
+optional.classes.build.dir=${classes.build.dir}/optional
+all.classes.build.dir=${classes.build.dir}/all
+test.build.dir=${basedir}/build/test
+artifacts.build.dir=${basedir}/build/artifact
+distrib.dir=${basedir}/build/distrib
+doc.build.dir=${basedir}/build/doc
+reports.dir=${doc.build.dir}/reports
+test.xml.dir=${build.dir}/test-report
+test.report.dir=${reports.dir}/test
+coverage.report.dir=${reports.dir}/coverage
+javadoc.build.dir=${reports.dir}/api
+ivy.report.dir=${reports.dir}/ivy
+doc.src.dir=${basedir}/doc
+checkstyle.report.dir=${reports.dir}/checkstyle
+checkstyle.src.dir=${basedir}/src/etc/checkstyle
+rat.report.dir=${reports.dir}/rat
+
+ivy.minimum.javaversion=1.5
+debug.mode=on
+ivy.install.version=1.4.1
+
+#status=integration
+
+test.class.pattern = *Test
+
+source.. = src/java/
+output.. = bin/
+bin.includes = META-INF/,\
+               .
diff --git a/build.xml b/build.xml
new file mode 100644
index 0000000..a9b4c12
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,690 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<project name="ivy" default="coverage-report" xmlns:ivy="antlib:org.apache.ivy.ant">
+
+	<property environment="env" />
+    <property file="version.properties" />
+    <property file="build.properties" />
+
+    <property name="final.name" value="ivy.jar" />
+    
+    <target name="init-ivy-user-home" unless="ivy.use.local.home">
+        <condition property="ivy.home" value="${env.IVY_HOME}">
+            <isset property="env.IVY_HOME" />
+        </condition>
+        <property name="ivy.home" value="${user.home}/.ivy2" />
+    </target>
+    
+    <target name="init-ivy-local-home" if="ivy.use.local.home">
+        <property name="ivy.home" value="${basedir}/.ivy2" />
+    </target>
+    
+    <target name="init-ivy-home" depends="init-ivy-user-home, init-ivy-local-home" />
+
+    <target name="init-ivy" depends="compile-bootstrap, init-ivy-home">
+        <taskdef resource="org/apache/ivy/ant/antlib.xml"
+                uri="antlib:org.apache.ivy.ant">
+            <classpath>
+                <pathelement location="${core.classes.build.dir}" />
+                <pathelement location="${bootstrap.classes.build.dir}" />
+            </classpath>
+        </taskdef>
+    	
+        <ivy:configure override="true" />
+    </target>
+    
+    <target name="install" depends="init-ivy-home, jar" 
+    	description="build Ivy and install it in Ivy user home for builds using Ivy user home to load Ivy jar">
+        <property name="ivy.jar.file" value="${ivy.home}/jars/ivy.jar" />
+        <copy file="${artifacts.build.dir}/jars/${final.name}" tofile="${ivy.jar.file}" />
+    </target>
+
+    <target name="install-ant" depends="init-ivy-home, jar"
+    	description="build Ivy and install it in Ant home lib">
+        <condition property="ant.home" value="${env.ANT_HOME}">
+          <isset property="env.ANT_HOME" />
+        </condition>
+        <fail unless="ant.home" message="ANT_HOME environment variable or ant.home property required" />
+        <copy file="${artifacts.build.dir}/jars/${final.name}" tofile="${ant.home}/lib/ivy.jar" />
+    </target>
+
+    <!-- =================================================================
+         PREPARATION, CLEANING AND FLAGS TASKS
+         ================================================================= -->
+    <target name="init">
+        <path id="lib.classpath">
+            <fileset dir="${lib.dir}">
+                <include name="*.jar" />
+            </fileset>
+        </path>
+        <path id="build.bootstrap.classpath">
+            <pathelement location="${core.classes.build.dir}" />
+        </path>
+        <path id="build.ant.classpath">
+            <pathelement location="${core.classes.build.dir}" />
+            <path refid="lib.classpath" />
+        </path>
+        <path id="build.optional.classpath">
+            <path refid="build.ant.classpath" />
+        </path>
+        <path id="run.classpath">
+            <pathelement location="${core.classes.build.dir}" />
+            <pathelement location="${ant.classes.build.dir}" />
+            <pathelement location="${optional.classes.build.dir}" />
+            <path refid="lib.classpath" />
+        </path>
+        <path id="test.classpath">
+            <pathelement location="${coverage.classes.dir}" />
+            <fileset dir="${lib.dir}">
+                <include name="*.jar" />
+                <exclude name="ant.jar" />
+                <exclude name="ant-launcher.jar" />
+            	<exclude name="ant-nodeps.jar"/>
+            	<exclude name="ant-trax.jar"/>
+            </fileset>
+            <pathelement location="${core.classes.build.dir}" />
+            <pathelement location="${ant.classes.build.dir}" />
+            <pathelement location="${optional.classes.build.dir}" />
+            <pathelement path="${test.build.dir}" />
+        </path>
+    </target>
+    
+    <target name="prepare" depends="init">
+        <mkdir dir="${classes.build.dir}" />
+        <mkdir dir="${core.classes.build.dir}" />
+    	<mkdir dir="${bootstrap.classes.build.dir}" />
+        <mkdir dir="${ant.classes.build.dir}" />
+        <mkdir dir="${optional.classes.build.dir}" />
+        <mkdir dir="${all.classes.build.dir}" />
+        <mkdir dir="${test.build.dir}" />
+        <mkdir dir="${artifacts.build.dir}" />
+        <mkdir dir="${test.report.dir}" />
+    	<mkdir dir="${ivy.report.dir}" />
+    </target>
+    
+    <target name="clean" description="delete all generated files keeping sources only">
+        <delete dir="${classes.build.dir}" />
+        <delete dir="${test.build.dir}" />
+        <delete dir="${artifacts.build.dir}" />
+        <delete dir="${test.report.dir}" />
+        <delete dir="${javadoc.build.dir}" />
+        <delete dir="${doc.build.dir}" />
+        <delete dir="${build.dir}" />
+    </target>
+    
+    <target name="clean-lib">
+        <delete dir="${lib.dir}" />
+    </target>
+    
+    <target name="clean-ivy-cache" depends="init-ivy-home">
+        <delete dir="${ivy.home}/cache" />
+    </target>
+    
+    <target name="clean-ivy-home" depends="init-ivy-home">
+        <delete dir="${ivy.home}" />
+    </target>
+    
+    <target name="clean-examples" description="clean all examples">
+        <subant target="clean" failonerror="false">
+            <fileset dir="${example.dir}" includes="**/build.xml" />
+        </subant>       
+    </target>
+    
+    <target name="clean-all" depends="clean, clean-lib, clean-examples" />
+    
+    <target name="/noresolve" description="use to skip dependency resolution">
+        <property name="no.resolve" value="true" />
+    </target>
+    
+    <target name="/notest" description="use to skip tests">
+        <property name="skip.test" value="true" />
+    </target>
+    
+    <target name="/nojavadoc" description="use to skip javadoc">
+        <property name="skip.javadoc" value="true" />
+    </target>
+    
+    <target name="/localivy" description="use a local ivy home">
+        <property name="ivy.use.local.home" value="true" />
+    </target>
+    
+    <target name="/offline" depends="/noresolve" description="use to indicate no internet connection is available">
+        <property name="offline" value="true" />
+    </target>
+    
+    <target name="default-version">
+        <tstamp> 
+            <format property="pubdate" pattern="yyyyMMddHHmmss" />
+        </tstamp>
+        <property name="version.prefix" value="${target.ivy.version}-local-" />
+        <property name="build.version" value="${version.prefix}${pubdate}" />
+        <property name="bundle.version" value="${target.ivy.bundle.version}.${target.ivy.bundle.version.qualifier}${pubdate}" />
+    </target>
+
+    <!-- =================================================================
+         DEPENDENCY MNGT, COMPILATION AND JAR
+         ================================================================= -->
+    <target name="resolve" depends="init-ivy" unless="no.resolve">
+        <ivy:retrieve conf="default,test" pattern="${lib.dir}/[artifact].[ext]" sync="yes"/>
+    </target>
+
+    <target name="compile-core" depends="prepare">
+        <javac  srcdir="${src.dir}"
+                destdir="${core.classes.build.dir}"
+                sourcepath="" 
+                source="${ivy.minimum.javaversion}"
+                target="${ivy.minimum.javaversion}"
+                debug="${debug.mode}"
+                includeantruntime="no">
+            <excludesfile name="ant.patterns" />
+            <excludesfile name="optional.patterns" />
+        </javac>
+        <copy todir="${core.classes.build.dir}" includeEmptyDirs="false">
+            <fileset dir="${src.dir}">
+                <exclude name="**/*.java" />
+                <excludesfile name="ant.patterns" />
+                <excludesfile name="optional.patterns" />
+            </fileset>
+        </copy>
+        
+        <!-- copy settings files for backward compatibility with ivyconf naming -->
+        <copy file="${core.classes.build.dir}/org/apache/ivy/core/settings/ivysettings-local.xml"
+            tofile="${core.classes.build.dir}/org/apache/ivy/core/settings/ivyconf-local.xml" />
+        <copy file="${core.classes.build.dir}/org/apache/ivy/core/settings/ivysettings-default-chain.xml"
+            tofile="${core.classes.build.dir}/org/apache/ivy/core/settings/ivyconf-default-chain.xml" />
+        <copy file="${core.classes.build.dir}/org/apache/ivy/core/settings/ivysettings-main-chain.xml"
+            tofile="${core.classes.build.dir}/org/apache/ivy/core/settings/ivyconf-main-chain.xml" />
+        <copy file="${core.classes.build.dir}/org/apache/ivy/core/settings/ivysettings-public.xml"
+            tofile="${core.classes.build.dir}/org/apache/ivy/core/settings/ivyconf-public.xml" />
+        <copy file="${core.classes.build.dir}/org/apache/ivy/core/settings/ivysettings-shared.xml"
+            tofile="${core.classes.build.dir}/org/apache/ivy/core/settings/ivyconf-shared.xml" />
+        <copy file="${core.classes.build.dir}/org/apache/ivy/core/settings/ivysettings.xml"
+            tofile="${core.classes.build.dir}/org/apache/ivy/core/settings/ivyconf.xml" />
+    </target>
+
+	<!-- Build the Ant tasks with the current Ant runtime -->
+    <target name="compile-bootstrap" depends="compile-core">
+        <javac  srcdir="${src.dir}"
+                destdir="${bootstrap.classes.build.dir}"
+                sourcepath="" 
+                classpathref="build.bootstrap.classpath"
+                source="${ivy.minimum.javaversion}"
+                target="${ivy.minimum.javaversion}"
+                debug="${debug.mode}"
+                includeantruntime="yes">
+            <includesfile name="ant.patterns" />
+        </javac>
+        <copy todir="${bootstrap.classes.build.dir}" includeEmptyDirs="false">
+            <fileset dir="${src.dir}">
+                <includesfile name="ant.patterns" />
+                <exclude name="**/*.java" />
+            </fileset>
+        </copy>
+    </target>
+    
+	<!-- Build the Ant tasks with the minimal Ant runtime -->
+    <target name="compile-ant" depends="compile-core, resolve">
+        <javac  srcdir="${src.dir}"
+                destdir="${ant.classes.build.dir}"
+                sourcepath="" 
+                classpathref="build.ant.classpath"
+                source="${ivy.minimum.javaversion}"
+                target="${ivy.minimum.javaversion}"
+                debug="${debug.mode}"
+                includeantruntime="no">
+            <includesfile name="ant.patterns" />
+        </javac>
+        <copy todir="${ant.classes.build.dir}" includeEmptyDirs="false">
+            <fileset dir="${src.dir}">
+                <includesfile name="ant.patterns" />
+                <exclude name="**/*.java" />
+            </fileset>
+        </copy>
+        
+        <!-- copy antlib for backward compatibility with fr.jayasoft.ivy package -->
+        <copy file="${ant.classes.build.dir}/org/apache/ivy/ant/antlib.xml" 
+                todir="${ant.classes.build.dir}/fr/jayasoft/ivy/ant" />
+    </target>
+    
+    <target name="compile-optional" depends="compile-ant, resolve">
+        <javac  srcdir="${src.dir}"
+                destdir="${optional.classes.build.dir}"
+                sourcepath="" 
+                classpathref="build.optional.classpath"
+                source="${ivy.minimum.javaversion}"
+                target="${ivy.minimum.javaversion}"
+                debug="${debug.mode}"
+                includeantruntime="no">
+            <includesfile name="optional.patterns" />
+            <includesfile name="ant.patterns" />
+        </javac>
+        <copy todir="${core.classes.build.dir}" includeEmptyDirs="false">
+            <fileset dir="${src.dir}">
+                <includesfile name="optional.patterns" />
+                <exclude name="**/*.java" />
+            </fileset>
+        </copy>
+    </target>
+
+    <!-- =================================================================
+         Create the two jar files (Ivy core and Ivy core + Ant tasks)
+         ================================================================= -->
+    <target name="jar" depends="compile-optional, default-version" description="Create Jar files">
+        <!-- identify compiled ivy version -->
+        <echo message="version=${build.version}${line.separator}" file="${core.classes.build.dir}/module.properties" append="true" />
+        <echo message="date=${pubdate}${line.separator}" file="${core.classes.build.dir}/module.properties" append="true" />
+
+    	<mkdir dir="${artifacts.build.dir}/jars/"/>
+    	
+    	<!-- 
+    		there is a default Bundle-Version attribute in the source MANIFEST, used to ease 
+    		development in eclipse. 
+    		We remove this line to make sure we get the Bundle-Version as set in the jar task 
+    	-->
+    	<copy file="${basedir}/META-INF/MANIFEST.MF" tofile="${artifacts.build.dir}/MANIFEST.MF">
+    		<filterchain>
+    			<replaceregex pattern="Bundle-Version:.*" replace="Bundle-Version: ${bundle.version}" byline="true" />
+    		</filterchain>
+    	</copy>
+
+        <copy todir="${all.classes.build.dir}">
+            <fileset dir="${core.classes.build.dir}" />
+            <fileset dir="${ant.classes.build.dir}" />
+            <fileset dir="${optional.classes.build.dir}" />
+        </copy>
+
+        <jar destfile="${artifacts.build.dir}/jars/${final.name}"
+                manifest="${artifacts.build.dir}/MANIFEST.MF">
+            <metainf dir="${basedir}" includes="LICENSE,NOTICE" />
+            <manifest>
+                <attribute name="Specification-Title" value="Apache Ivy with Ant tasks" />
+                <attribute name="Specification-Version" value="${build.version}" />
+                <attribute name="Specification-Vendor" value="Apache Software Foundation" />
+                <attribute name="Implementation-Title" value="org.apache.ivy" />
+                <attribute name="Implementation-Version" value="${build.version}" />
+                <attribute name="Implementation-Vendor" value="Apache Software Foundation" />
+                <attribute name="Implementation-Vendor-Id" value="org.apache" />
+                <attribute name="Extension-name" value="org.apache.ivy" />
+                <attribute name="Build-Version" value="${build.version}" />
+            </manifest>
+            <fileset dir="${all.classes.build.dir}" />
+        </jar>
+    	<!-- copy main jar to ease its use as an OSGi bundle -->
+    	<copy file="${artifacts.build.dir}/jars/${final.name}" 
+    		  tofile="${artifacts.build.dir}/org.apache.ivy_${bundle.version}.jar" />
+
+        <!-- clean generated module properties file -->
+        <delete file="${core.classes.build.dir}/module.properties" />
+    </target>
+
+	<!-- =================================================================
+         PUBLISH LOCAL
+         ================================================================= -->
+    <target name="publish-local" depends="jar,sources" description="publishes Ivy to Ivy local repository">
+        <ivy:publish resolver="local" pubrevision="${build.version}"
+            artifactsPattern="${artifacts.build.dir}/[type]s/[artifact].[ext]" 
+            forcedeliver="true" />
+    </target>
+
+	<!-- =================================================================
+         TESTS
+         ================================================================= -->
+    <target name="build-custom-resolver-jar" depends="jar">
+        <mkdir dir="${build.dir}/custom-classpath" />
+        <javac  srcdir="${basedir}/test/custom-classpath"
+                destdir="${build.dir}/custom-classpath"
+                classpathref="run.classpath"
+                source="${ivy.minimum.javaversion}"
+                target="${ivy.minimum.javaversion}"
+                debug="${debug.mode}"
+                includeantruntime="no" />
+        <jar    destfile="${test.dir}/org/apache/ivy/core/settings/custom-resolver.jar"
+                basedir="${build.dir}/custom-classpath" />
+    </target>
+
+    <target name="init-tests-offline" if="offline">
+            <fileset id="test.fileset" dir="${test.dir}">
+                <include name="**/${test.class.pattern}.java" />
+                <exclude name="**/Abstract*Test.java" />
+                <not><contains text="remote.test" /></not>
+            </fileset>
+    </target>
+        
+    <target name="init-tests-online" unless="offline">
+            <fileset id="test.fileset" dir="${test.dir}">
+                <include name="**/${test.class.pattern}.java" />
+                <exclude name="**/Abstract*Test.java" />
+            </fileset>
+    </target>
+    
+    <target name="init-tests" depends="init-tests-offline, init-tests-online" />
+    
+    <target name="emma" depends="jar" unless="skip.test">
+        <ivy:cachepath organisation="emma" module="emma" revision="2.0.5312" 
+                       inline="true" conf="default" pathid="emma.classpath" 
+                       log="download-only" /> 
+        <ivy:cachepath organisation="emma" module="emma_ant" revision="2.0.5312" 
+                       inline="true" conf="default" pathid="emma.ant.classpath" transitive="false" 
+                       log="download-only" /> 
+        <taskdef resource="emma_ant.properties">
+            <classpath refid="emma.classpath" />
+            <classpath refid="emma.ant.classpath" />
+        </taskdef>
+        <property name="emma.enabled" value="true" />
+        <property name="coverage.dir" value="${build.dir}/coverage" />
+        <property name="coverage.classes.dir" value="${coverage.dir}/classes" />
+        <mkdir dir="${coverage.dir}" />
+        <mkdir dir="${coverage.classes.dir}" />
+        <emma enabled="${emma.enabled}">
+          <instr mode="copy"
+                 destdir="${coverage.dir}/classes"
+                 metadatafile="${coverage.dir}/metadata.emma">
+            <instrpath>
+                <pathelement location="${core.classes.build.dir}" />
+                <pathelement location="${ant.classes.build.dir}" />
+                <pathelement location="${optional.classes.build.dir}" />
+            </instrpath>
+          </instr>
+        </emma>
+        <delete file="${coverage.dir}/coverage.emma" />
+        <!-- add emma path to test path, because emma jars need to be available when running 
+             instrumented classes -->
+        <ivy:addpath topath="test.classpath" first="true">
+          <pathelement location="${coverage.dir}/classes" />
+          <path refid="emma.classpath" />
+        </ivy:addpath>
+    </target>
+    
+    <target name="build-test" depends="jar">
+        <javac  srcdir="${test.dir}"
+                destdir="${test.build.dir}"
+                classpathref="run.classpath"
+                source="${ivy.minimum.javaversion}"
+                target="${ivy.minimum.javaversion}"
+                debug="${debug.mode}"
+                encoding="ISO-8859-1"
+                includeantruntime="no" />
+        <copy todir="${test.build.dir}">
+            <fileset dir="${test.dir}">
+                <exclude name="**/*.java" />
+            </fileset>
+        </copy>
+    </target>
+
+	<target name="prepare-osgi-tests" depends="resolve" unless="skip.test">
+        <ant dir="${basedir}/test/test-repo" target="generate-bundles" />
+	</target>
+
+    <target name="prepare-test-jar-repositories" unless="skip.test">
+    	<mkdir dir="${basedir}/test/jar-repos" />
+        <jar destfile="${basedir}/test/jar-repos/jarrepo1.jar" >
+            <fileset dir="${basedir}/test/repositories/1" />
+        </jar>
+        <jar destfile="${basedir}/test/jar-repos/jarrepo1_subdir.jar">
+            <fileset dir="${basedir}/test/repositories" includes="1/**/*" />
+        </jar>
+    </target>
+
+	<target name="test-internal" depends="build-test, init-tests, prepare-osgi-tests, prepare-test-jar-repositories" unless="skip.test">
+        <mkdir dir="${test.xml.dir}" />
+    	
+        <junit 
+           haltonfailure="off"
+           haltonerror="off"
+           errorproperty="test.failed"
+           failureproperty="test.failed"
+           showoutput="no"
+           printsummary="yes"
+           includeantruntime="yes"
+           dir="${basedir}"
+           fork="true">
+            <classpath>
+                <path refid="test.classpath" />
+                <pathelement path="${ant.home}/lib/ant-nodeps.jar"/>
+                <pathelement path="${ant.home}/lib/ant-trax.jar"/>
+            </classpath>
+        	
+        	<!-- pass the proxy properties to the forked junit process to use correct proxy -->
+        	<syspropertyset>
+        		<propertyref prefix="http" />
+       		</syspropertyset>
+            <jvmarg value="-Demma.coverage.out.file=${coverage.dir}/coverage.emma" />
+            <jvmarg value="-Demma.coverage.out.merge=true" />
+            
+            <!-- Added this to test IVY-65 -->
+            <jvmarg value="-Duser.region=TR" />
+            <jvmarg value="-Duser.language=tr" />
+            
+            <formatter type="xml"/>
+            <batchtest todir="${test.xml.dir}">
+                <fileset refid="test.fileset" />
+            </batchtest>
+        </junit>
+    </target>
+
+    <target name="test" depends="test-internal" description="Run the test">
+        <fail if="test.failed"
+              message="At least one test has failed. See logs (in ${test.xml.dir}) for details (use the target test-report to run the test with a report)" />
+    </target>
+    
+    <!-- =================================================================
+         REPORTS AND DOCUMENTATION
+         ================================================================= -->
+    <target name="test-report" depends="test-internal" unless="skip.test">
+        <junitreport todir="${test.xml.dir}">
+            <fileset dir="${test.xml.dir}">
+                <include name="TEST-*.xml" />
+            </fileset>
+            <report format="frames" todir="${test.report.dir}" />
+        </junitreport>
+        <fail if="test.failed"
+              message="At least one test has failed. See logs (in ${test.xml.dir}) or report (in ${test.report.dir})" />
+    </target>
+    
+    <target name="coverage-report" depends="emma, test-report"  unless="skip.test"
+            description="run tests with instrumentation and generate coverage report">
+        <mkdir dir="${coverage.report.dir}" />
+        <emma>
+            <report sourcepath="${src.dir}">
+                <fileset dir="${coverage.dir}">
+                    <include name="*.emma" />
+                </fileset>
+
+                <txt outfile="${coverage.report.dir}/coverage.txt" />
+                <html outfile="${coverage.report.dir}/coverage.html" />
+             </report>
+         </emma>
+    </target>
+    
+	<target name="ivy-report" depends="resolve">
+		<ivy:report todir="${ivy.report.dir}"/>
+	</target>
+
+    <target name="javadoc" unless="skip.javadoc">
+        <javadoc destdir="${javadoc.build.dir}" useexternalfile="true">
+            <fileset dir="${src.dir}" includes="**/*.java" />
+        </javadoc>
+    </target>
+	
+    <target name="sources" depends="default-version" description="Create source archive files">
+    	<mkdir dir="${artifacts.build.dir}/sources/"/>
+        <jar destfile="${artifacts.build.dir}/sources/${final.name}">
+            <metainf dir="${basedir}" includes="LICENSE,NOTICE" />
+            <manifest>
+                <attribute name="Specification-Title" value="Apache Ivy Sources" />
+                <attribute name="Specification-Version" value="${build.version}" />
+                <attribute name="Specification-Vendor" value="Apache Software Foundation" />
+            </manifest>
+            <fileset dir="${src.dir}" />
+        </jar>
+	</target>
+	
+    <target name="fixcrlf">
+        <property name="eol.native.includes" 
+            value="**/*.html,**/*.json,**/*.java,**/*.xml,**/*.txt,**/*.MF,**/*.properties,**/*.patterns,**/*.pom,**/*.xsl,**/*.css" />
+        <property name="eol.native.excludes" 
+            value="build/**,bin/**,lib/**" />
+
+        <fileset id="eol.native.fileset" 
+            dir="${basedir}" 
+            includes="${eol.native.includes}"
+            excludes="${eol.native.excludes}" />
+        
+        <fixcrlf srcdir="${basedir}" 
+            includes="${eol.native.includes}" 
+            excludes="${eol.native.excludes}" />
+        <apply executable="svn">
+            <fileset refid="eol.native.fileset" />
+            <arg value="propset" />
+            <arg value="svn:eol-style" />
+            <arg value='"native"' />
+        </apply>
+    </target>
+    
+    <!-- Checks Ivy codebase according to ${checkstyle.src.dir}/checkstyle-config  -->
+    <target name="checkstyle-internal" depends="jar">
+        <ivy:cachepath organisation="checkstyle" module="checkstyle" revision="5.0"
+                inline="true" conf="default" pathid="checkstyle.classpath" transitive="true" 
+        		log="download-only"/>
+        <taskdef resource="checkstyletask.properties" classpathref="checkstyle.classpath" />
+
+        <mkdir dir="${checkstyle.report.dir}" />
+        <checkstyle config="${checkstyle.src.dir}/checkstyle-config" 
+        	failOnViolation="false" failureProperty="checkstyle.failed">
+        	<classpath>
+	        	<path refid="run.classpath" />
+        	</classpath>
+            <formatter type="xml" toFile="${checkstyle.report.dir}/checkstyle.xml" />
+            <fileset dir="${src.dir}">
+                <include name="**/*.java" />
+            </fileset>
+            <fileset dir="${example.dir}">
+                <include name="**/*.java" />
+            </fileset>
+        </checkstyle>
+  	</target>
+	
+    <target name="checkstyle" depends="checkstyle-internal" description="checks Ivy codebase according to ${checkstyle.src.dir}/checkstyle-config">
+    	<fail if="checkstyle.failed"
+    		message="Checkstyle has errors. See report in ${checkstyle.report.dir}" />
+    </target>
+
+    <target name="checkstyle-report" depends="checkstyle-internal">
+        <property name="checkstyle.basedir" location="${src.dir}" />
+        <xslt in="${checkstyle.report.dir}/checkstyle.xml"
+                style="${checkstyle.src.dir}/checkstyle-frames.xsl"
+                out="${checkstyle.report.dir}/output.txt">
+            <param name="basedir" expression="${checkstyle.basedir}" />
+        </xslt>
+    </target>
+
+    <target name="init-findbugs" unless="findbugs.home">
+        <!-- Findbugs: Getting Findbugs -->
+        <property name="findbugs.download.name" 
+                  value="findbugs-1.3.5"
+                  description="Name of the download file without suffix. Also the internal root directory of the ZIP."/>
+        <property name="findbugs.download.file" 
+                  value="${findbugs.download.name}.zip"
+                  description="The filename of the ZIP."/>
+        <property name="findbugs.download.url"  
+                  value="http://garr.dl.sourceforge.net/sourceforge/findbugs/${findbugs.download.file}"
+                  description="The download adress at a mirror of Sourceforge."/>
+        <property name="findbugs.download.to"   
+                  value="${build.dir}/.downloads"
+                  description="Where to store the download and 'install' Findbugs."/>
+        <available 
+          property="findbugs.home" 
+          value="${findbugs.download.to}/${findbugs.download.name}"
+          file="${findbugs.download.to}/${findbugs.download.name}/lib/findbugs.jar"
+          description="Check if Findbugs is already installed."
+        />
+      
+        <!-- Findbugs: Running Findbugs -->
+        <property name="findbugs.reportdir" 
+                  location="${reports.dir}/findbugs" 
+                  description="Where to store Findbugs results"/>
+        <property name="findbugs.raw"       
+                  value="raw.xml" 
+                  description="Findbugs Output xml-file"/>
+        <property name="findbugs.xsl"       
+                  value="fancy.xsl" 
+                  description="Which XSL to use for generating Output: default, fancy, plain, summary"/>
+        <property name="findbugs.jvmargs"   
+                  value="-Xms128m -Xmx512m" 
+                  description="JVMArgs for invoking Findbugs"/>
+
+        <mkdir dir="${findbugs.download.to}"/>
+        <get src="${findbugs.download.url}" dest="${findbugs.download.to}/${findbugs.download.file}"/>
+        <unzip src="${findbugs.download.to}/${findbugs.download.file}" dest="${findbugs.download.to}"/>
+        <property name="findbugs.home" location="${findbugs.download.to}/${findbugs.download.name}"/>
+        <mkdir dir="${findbugs.home}/plugin"/>
+    </target>
+  
+    <target name="findbugs" description="checks Ivy codebase with Findbugs" depends="init-findbugs,compile-core" xmlns:fb="http://findbugs.sourceforge.net/">
+        <path id="findbugs.real.classpath">
+           <fileset dir="${findbugs.home}/lib" includes="*.jar"/>
+        </path>
+    
+        <!-- Load the Findbugs AntTasks -->               
+        <taskdef uri="http://findbugs.sourceforge.net/" resource="edu/umd/cs/findbugs/anttask/tasks.properties" classpathref="findbugs.real.classpath" />
+            
+        <!-- Start Findbugs -->
+        <mkdir dir="${findbugs.reportdir}"/>
+        <fb:findbugs home="${findbugs.home}"
+                     classpathref="findbugs.real.classpath"
+                     output="xml:withMessages"
+                     outputFile="${findbugs.reportdir}/${findbugs.raw}"
+                     jvmargs="${findbugs.jvmargs}"
+                     projectName="${Name} ${project.version}">
+            <class location="${core.classes.build.dir}" />
+            <sourcePath path="${src.dir}" />
+        </fb:findbugs>
+        
+        <!-- Generate (human) readable output -->
+        <xslt basedir="${findbugs.reportdir}" includes="${findbugs.raw}" destdir="${findbugs.reportdir}">
+            <style>
+                <javaresource name="${findbugs.xsl}" classpathref="findbugs.real.classpath"/>
+            </style>  
+        </xslt>
+    </target>
+ 
+    <!-- =================================================================
+         IDE SPECIFIC
+         ================================================================= -->
+    <available file="${basedir}/.classpath" property="eclipse.classpath.exists" />
+    <target name="check-eclipse-classpath-overwrite" if="eclipse.classpath.exists">
+        <input message=".classpath file already exists.${line.separator}Are you sure you want to overwrite it and loose your original file?" 
+               validargs="Y,N,y,n" addproperty="eclipse.classpath.confirm" />
+        <condition property="eclipse.classpath.abort">
+            <equals arg1="${eclipse.classpath.confirm}" arg2="N" casesensitive="false" />
+        </condition>
+    </target>
+    
+    <target name="eclipse-default" depends="resolve, check-eclipse-classpath-overwrite"
+            unless="eclipse.classpath.abort"
+            description="creates a default .classpath for eclipse, using jars resolved by this ant build">
+        <copy file="${basedir}/.classpath.default" tofile="${basedir}/.classpath" overwrite="true" />
+    </target>
+
+    <target name="eclipse-ivyde" depends="check-eclipse-classpath-overwrite"
+            unless="eclipse.classpath.abort"
+            description="creates a .classpath for eclipse using Apache IvyDE version">
+        <copy file="${basedir}/.classpath.ivyde" tofile="${basedir}/.classpath" overwrite="true" />
+    </target>
+</project>
diff --git a/ivy.xml b/ivy.xml
new file mode 100644
index 0000000..d448897
--- /dev/null
+++ b/ivy.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT 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" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://ant.apache.org/ivy/schemas/ivy.xsd">
+	<info organisation="org.apache.ivy" module="ivy" revision="2.4.0" status="release" publication="20141213170938">
+		<description homepage="http://ant.apache.org/ivy/">
+		Apache Ivy is a tool for managing (recording, tracking, resolving and reporting) project dependencies.
+		</description>
+	</info>
+	<configurations>
+		<conf name="core" description="only ivy jar, without any dependencies"/>
+		<conf name="httpclient" extends="core" description="core + optional httpclient for better http handling"/>
+		<conf name="oro" extends="core" description="to use optional glob matcher"/>
+		<conf name="vfs" extends="core" description="core + optional VirtualFileSystem(VFS) support"/>
+		<conf name="sftp" extends="core" description="core + optional SFTP support"/>
+		<conf name="standalone" extends="core" description="to launch in standalone mode (from command line)"/>
+		<conf name="ant" extends="core" description="core + ant jar provided as a dependency"/>
+		<conf name="default" extends="core" description="full ivy with all dependencies"/>
+		<conf name="test" description="dependencies used for junit testing ivy" visibility="private"/>
+		<conf name="source" description="ivy sources"/>
+	</configurations>
+	<publications>
+		<artifact name="ivy" type="jar" conf="core"/>
+		<artifact name="ivy" type="source" ext="jar" conf="source"/>
+	</publications>
+	<dependencies>
+	    <dependency org="org.apache.ant" name="ant" rev="1.7.1" conf="default,ant->default"/>
+	    <dependency org="org.apache.ant" name="ant-nodeps" rev="1.7.1" conf="default"/>
+        <dependency org="org.apache.ant" name="ant-trax" rev="1.7.1" conf="default"/>
+		<dependency org="commons-httpclient" name="commons-httpclient" rev="3.0" conf="default,httpclient->runtime,master"/>
+		<dependency org="oro" name="oro" rev="2.0.8" conf="default,oro->default"/>
+		<dependency org="commons-vfs" name="commons-vfs" rev="1.0" conf="default,vfs->default"/>
+		<dependency org="com.jcraft" name="jsch" rev="0.1.50" conf="default,sftp->default"/>
+		<dependency org="com.jcraft" name="jsch.agentproxy" rev="0.0.6" conf="default,sftp->default"/>
+		<dependency org="com.jcraft" name="jsch.agentproxy.connector-factory" rev="0.0.6" conf="default,sftp->default"/>
+		<dependency org="com.jcraft" name="jsch.agentproxy.jsch" rev="0.0.6" conf="default,sftp->default"/>
+		<dependency org="org.bouncycastle" name="bcpg-jdk14" rev="1.45" conf="default"/>
+        <dependency org="org.bouncycastle" name="bcprov-jdk14" rev="1.45" conf="default"/>
+
+		<!-- Test dependencies -->
+		<dependency org="junit" name="junit" rev="3.8.2" conf="test->default"/>
+		<dependency org="commons-lang" name="commons-lang" rev="2.6" conf="test->default"/>
+		<dependency org="org.apache.ant" name="ant-testutil" rev="1.7.0" conf="test->default" transitive="false"/>
+        <dependency org="ant" name="ant-launcher" rev="1.6.2" conf="test->default" transitive="false"/>
+        <dependency org="ant-contrib" name="ant-contrib" rev="1.0b3" conf="test->default" transitive="false"/>
+		
+		<!-- This dependency is necessary for having validation in junit tests when running with JDK1.4 -->
+		<dependency org="xerces" name="xercesImpl" rev="2.6.2" conf="test->default"/>
+		<dependency org="xerces" name="xmlParserAPIs" rev="2.6.2" conf="test->default"/>
+
+		<!-- Global exclude for junit -->
+		<exclude org="junit" module="junit" conf="core,default,httpclient,oro,vfs,sftp,standalone,ant"/>
+	</dependencies>
+</ivy-module>
diff --git a/ivy.xsd b/ivy.xsd
new file mode 100644
index 0000000..fabad55
--- /dev/null
+++ b/ivy.xsd
@@ -0,0 +1,291 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
+
+	<xs:complexType name="configurations-conf">
+        <xs:attribute name="name" type="xs:string" use="required"/>
+        <xs:attribute name="transitive" type="xs:boolean"/>
+        <xs:attribute name="extends" type="xs:string"/>
+        <xs:attribute name="description" type="xs:string"/>
+        <xs:attribute name="deprecated" type="xs:string"/>
+        <xs:attribute name="visibility" default="public">
+			<xs:simpleType>
+				<xs:restriction base="xs:string">
+					<xs:enumeration value="private"/>
+					<xs:enumeration value="public"/>
+				</xs:restriction>
+			</xs:simpleType>
+		</xs:attribute>
+        <xs:anyAttribute namespace="##other" processContents="lax" />
+	</xs:complexType>
+
+	<xs:complexType name="global-exclude">
+        <xs:sequence>
+      		<xs:element name="conf" minOccurs="0" maxOccurs="unbounded">
+            	<xs:complexType>
+		            <xs:attribute name="name" type="xs:string" use="required"/>
+            	</xs:complexType>
+      		</xs:element>
+        </xs:sequence>
+        <xs:attribute name="org" type="xs:string"/>
+        <xs:attribute name="module" type="xs:string"/>
+        <xs:attribute name="artifact" type="xs:string"/>
+        <xs:attribute name="type" type="xs:string"/>
+        <xs:attribute name="ext" type="xs:string"/>
+        <xs:attribute name="conf" type="xs:string"/>
+        <xs:attribute name="matcher" type="xs:string"/>
+        <xs:anyAttribute namespace="##other" processContents="lax" />
+	</xs:complexType>
+
+  <xs:element name="ivy-module">
+      <xs:complexType>
+        <xs:sequence>
+  		    <xs:element name="info">
+            	<xs:complexType>
+			        <xs:sequence>
+			      		<xs:element name="extends" minOccurs="0" maxOccurs="unbounded">
+			      		<xs:complexType>
+			      			<xs:attribute name="organisation" type="xs:string" use="required"/>
+			      			<xs:attribute name="module" type="xs:string" use="required"/>
+			      			<xs:attribute name="revision" type="xs:string" use="required"/>
+			      			<xs:attribute name="location" type="xs:string" />
+			      			<xs:attribute name="extendType" type="xs:string" />
+			              	</xs:complexType>
+			      		</xs:element>
+			      		<xs:element name="license" minOccurs="0" maxOccurs="unbounded">
+			            	<xs:complexType>
+					            <xs:attribute name="name" type="xs:string" use="required"/>
+					            <xs:attribute name="url" type="xs:string"/>
+			            	</xs:complexType>
+			      		</xs:element>
+			      		<xs:element name="ivyauthor" minOccurs="0" maxOccurs="unbounded">
+			            	<xs:complexType>
+					            <xs:attribute name="name" type="xs:string" use="required"/>
+					            <xs:attribute name="url" type="xs:string"/>
+			            	</xs:complexType>
+			      		</xs:element>
+			      		<xs:element name="repository" minOccurs="0" maxOccurs="unbounded">
+			            	<xs:complexType>
+					            <xs:attribute name="name" type="xs:string" use="required"/>
+					            <xs:attribute name="url" type="xs:string"/>
+					            <xs:attribute name="pattern" type="xs:string"/>
+					            <xs:attribute name="ivys" type="xs:boolean"/>
+					            <xs:attribute name="artifacts" type="xs:boolean"/>
+			            	</xs:complexType>
+			      		</xs:element>
+			      		<xs:element name="description" minOccurs="0" maxOccurs="1">
+			            	<xs:complexType mixed="true">
+			            			<xs:sequence>
+			            				<xs:any minOccurs="0" maxOccurs="unbounded" processContents="lax"/>
+					            	</xs:sequence>
+					            <xs:attribute name="homepage" type="xs:string"/>
+			            	</xs:complexType>
+			      		</xs:element>
+			      		<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other" processContents="lax"/>
+			        </xs:sequence>
+		            <xs:attribute name="organisation" type="xs:string"/>
+		            <xs:attribute name="module" type="xs:string" use="required"/>
+		            <xs:attribute name="branch" type="xs:string"/>
+		            <xs:attribute name="revision" type="xs:string"/>
+		            <xs:attribute name="status" type="xs:string"/>
+		            <xs:attribute name="publication" type="xs:string"/>
+		            <xs:attribute name="resolver" type="xs:string"/>
+		            <xs:attribute name="namespace" type="xs:string"/>
+		            <xs:attribute name="default" type="xs:boolean"/>
+		            <xs:anyAttribute namespace="##other" processContents="lax" />
+	            </xs:complexType>
+      		</xs:element>
+      		<xs:element name="configurations" minOccurs="0">
+			      <xs:complexType>
+				        <xs:sequence>
+				        	<xs:choice minOccurs="0" maxOccurs="unbounded">
+					      		<xs:element name="conf" type="configurations-conf"/>
+					      		<xs:element name="include">
+					            	<xs:complexType>
+							            <xs:attribute name="file" type="xs:string"/>
+							            <xs:attribute name="url"  type="xs:string"/>
+					            	</xs:complexType>
+					      		</xs:element>
+					      	</xs:choice>
+				        </xs:sequence>
+				        <xs:attribute name="defaultconf" type="xs:string"/>
+						<xs:attribute name="defaultconfmapping" type="xs:string"/>				  
+						<xs:attribute name="confmappingoverride" type="xs:boolean" />      
+			      </xs:complexType>
+      		</xs:element>
+      		<xs:element name="publications" minOccurs="0">
+			      <xs:complexType>
+				        <xs:sequence>
+				      		<xs:element name="artifact" minOccurs="0" maxOccurs="unbounded">
+				            	<xs:complexType>
+							        <xs:sequence>
+							      		<xs:element name="conf" minOccurs="0" maxOccurs="unbounded">
+							            	<xs:complexType>
+									            <xs:attribute name="name" type="xs:string" use="required"/>
+							            	</xs:complexType>
+							      		</xs:element>
+							        </xs:sequence>
+						            <xs:attribute name="name" type="xs:string"/>
+						            <xs:attribute name="type" type="xs:string"/>
+						            <xs:attribute name="ext" type="xs:string"/>
+						            <xs:attribute name="conf" type="xs:string"/>
+						            <xs:attribute name="url" type="xs:string"/>
+                                    <xs:attribute name="packaging" type="xs:string"/>
+						            <xs:anyAttribute namespace="##other" processContents="lax" />
+				            	</xs:complexType>
+				      		</xs:element>
+				        </xs:sequence>
+						<xs:attribute name="defaultconf" type="xs:string"/>				  
+			      </xs:complexType>
+      		</xs:element>
+      		<xs:element name="dependencies" minOccurs="0">
+			      <xs:complexType>
+				        <xs:sequence>
+				      		<xs:element name="dependency" minOccurs="0" maxOccurs="unbounded">
+				            	<xs:complexType>
+							        <xs:sequence>
+							      		<xs:element name="conf" minOccurs="0" maxOccurs="unbounded">
+							            	<xs:complexType>
+										        <xs:sequence>
+										      		<xs:element name="mapped" minOccurs="0" maxOccurs="unbounded">
+										            	<xs:complexType>
+												            <xs:attribute name="name" type="xs:string" use="required"/>
+										            	</xs:complexType>
+										      		</xs:element>
+										        </xs:sequence>
+									            <xs:attribute name="name" type="xs:string" use="required"/>
+									            <xs:attribute name="mapped" type="xs:string"/>
+							            	</xs:complexType>
+							      		</xs:element>
+							      		<xs:element name="artifact" minOccurs="0" maxOccurs="unbounded">
+							            	<xs:complexType>
+										        <xs:sequence>
+										      		<xs:element name="conf" minOccurs="0" maxOccurs="unbounded">
+										            	<xs:complexType>
+												            <xs:attribute name="name" type="xs:string" use="required"/>
+										            	</xs:complexType>
+										      		</xs:element>
+										        </xs:sequence>
+									            <xs:attribute name="name" type="xs:string" use="required"/>
+									            <xs:attribute name="type" type="xs:string"/>
+									            <xs:attribute name="ext" type="xs:string"/>
+									            <xs:attribute name="conf" type="xs:string"/>
+									            <xs:attribute name="url" type="xs:string"/>
+									            <xs:anyAttribute namespace="##other" processContents="lax" />
+							            	</xs:complexType>
+							      		</xs:element>
+							      		<xs:element name="include" minOccurs="0" maxOccurs="unbounded">
+							            	<xs:complexType>
+										        <xs:sequence>
+										      		<xs:element name="conf" minOccurs="0" maxOccurs="unbounded">
+										            	<xs:complexType>
+												            <xs:attribute name="name" type="xs:string" use="required"/>
+										            	</xs:complexType>
+										      		</xs:element>
+										        </xs:sequence>
+									            <xs:attribute name="name" type="xs:string"/>
+									            <xs:attribute name="type" type="xs:string"/>
+									            <xs:attribute name="ext" type="xs:string"/>
+									            <xs:attribute name="conf" type="xs:string"/>
+									            <xs:attribute name="matcher" type="xs:string"/>
+									            <xs:anyAttribute namespace="##other" processContents="lax" />
+							            	</xs:complexType>
+							      		</xs:element>
+							      		<xs:element name="exclude" minOccurs="0" maxOccurs="unbounded">
+							            	<xs:complexType>
+										        <xs:sequence>
+										      		<xs:element name="conf" minOccurs="0" maxOccurs="unbounded">
+										            	<xs:complexType>
+												            <xs:attribute name="name" type="xs:string" use="required"/>
+										            	</xs:complexType>
+										      		</xs:element>
+										        </xs:sequence>
+									            <xs:attribute name="org" type="xs:string"/>
+									            <xs:attribute name="module" type="xs:string"/>
+									            <xs:attribute name="name" type="xs:string"/>
+									            <xs:attribute name="type" type="xs:string"/>
+									            <xs:attribute name="ext" type="xs:string"/>
+									            <xs:attribute name="conf" type="xs:string"/>
+									            <xs:attribute name="matcher" type="xs:string"/>
+									            <xs:anyAttribute namespace="##other" processContents="lax" />
+							            	</xs:complexType>
+							      		</xs:element>
+							        </xs:sequence>
+						            <xs:attribute name="org" type="xs:string"/>
+						            <xs:attribute name="name" type="xs:string" use="required"/>
+						            <xs:attribute name="branch" type="xs:string"/>
+						            <xs:attribute name="branchConstraint" type="xs:string"/>
+						            <xs:attribute name="rev" type="xs:string" use="required"/>
+						            <xs:attribute name="revConstraint" type="xs:string"/>
+						            <xs:attribute name="force" type="xs:boolean"/>
+						            <xs:attribute name="changing" type="xs:boolean" default="false"/>
+						            <xs:attribute name="transitive" type="xs:boolean" default="true"/>
+						            <xs:attribute name="conf" type="xs:string"/>
+						            <xs:anyAttribute namespace="##other" processContents="lax" />
+				            	</xs:complexType>
+				      		</xs:element>
+				      		<xs:element name="exclude" type="global-exclude" minOccurs="0" maxOccurs="unbounded" />
+				      		<xs:element name="override" minOccurs="0" maxOccurs="unbounded">
+				            	<xs:complexType>
+						            <xs:attribute name="org" type="xs:string"/>
+						            <xs:attribute name="module" type="xs:string"/>
+						            <xs:attribute name="matcher" type="xs:string"/>
+						            <xs:attribute name="rev" type="xs:string"/>
+						            <xs:attribute name="branch" type="xs:string"/>
+				            	</xs:complexType>
+				      		</xs:element>
+				      		<xs:element name="conflict" minOccurs="0" maxOccurs="unbounded">
+				            	<xs:complexType>
+						            <xs:attribute name="org" type="xs:string"/>
+						            <xs:attribute name="module" type="xs:string"/>
+						            <xs:attribute name="manager" type="xs:string"/>
+						            <xs:attribute name="rev" type="xs:string"/>
+						            <xs:attribute name="matcher" type="xs:string"/>
+						            <xs:anyAttribute namespace="##other" processContents="lax" />
+				            	</xs:complexType>
+				      		</xs:element>
+				        </xs:sequence>
+						<xs:attribute name="defaultconf" type="xs:string"/>				        
+						<xs:attribute name="defaultconfmapping" type="xs:string"/>				        
+                        <xs:attribute name="confmappingoverride" type="xs:boolean" />
+			      </xs:complexType>
+      		</xs:element>
+      		<xs:element name="conflicts" minOccurs="0">
+			      <xs:complexType>
+				        <xs:sequence>
+				      		<xs:element name="manager" maxOccurs="unbounded">
+				            	<xs:complexType>
+						            <xs:attribute name="org" type="xs:string"/>
+						            <xs:attribute name="module" type="xs:string"/>
+						            <xs:attribute name="name" type="xs:string"/>
+						            <xs:attribute name="rev" type="xs:string"/>
+						            <xs:attribute name="matcher" type="xs:string"/>
+						            <xs:anyAttribute namespace="##other" processContents="lax" />
+				            	</xs:complexType>
+				      		</xs:element>
+				        </xs:sequence>
+			      </xs:complexType>
+      		</xs:element>
+        </xs:sequence>
+        <xs:attribute name="version" type="xs:string" use="required"/>
+      </xs:complexType>
+  </xs:element>
+
+</xs:schema>
diff --git a/optional.patterns b/optional.patterns
new file mode 100644
index 0000000..6c119be
--- /dev/null
+++ b/optional.patterns
@@ -0,0 +1,37 @@
+#	 ***************************************************************
+#	 * Licensed to the Apache Software Foundation (ASF) under one
+#	 * or more contributor license agreements.  See the NOTICE file
+#	 * distributed with this work for additional information
+#	 * regarding copyright ownership.  The ASF licenses this file
+#	 * to you under the Apache License, Version 2.0 (the
+#	 * "License"); you may not use this file except in compliance
+#	 * with the License.  You may obtain a copy of the License at
+#	 * 
+#	 *   http://www.apache.org/licenses/LICENSE-2.0
+#	 * 
+#	 * Unless required by applicable law or agreed to in writing,
+#	 * software distributed under the License is distributed on an
+#	 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#	 * KIND, either express or implied.  See the License for the
+#	 * specific language governing permissions and limitations
+#	 * under the License.
+#	 ***************************************************************
+
+#This file defines the sources to compile for ivy-optional.jar
+org/apache/ivy/Main.java
+org/apache/ivy/plugins/matcher/GlobPatternMatcher.java
+org/apache/ivy/plugins/repository/sftp/**/*.java
+org/apache/ivy/plugins/repository/ssh/**/*.java
+org/apache/ivy/plugins/repository/vfs/**/*.java
+org/apache/ivy/plugins/repository/vsftp/**/*.java
+org/apache/ivy/plugins/resolver/AbstractSshBasedResolver.java
+org/apache/ivy/plugins/resolver/SFTPResolver.java
+org/apache/ivy/plugins/resolver/SshResolver.java
+org/apache/ivy/plugins/resolver/VfsResolver.java
+org/apache/ivy/plugins/resolver/VsftpResolver.java
+org/apache/ivy/plugins/resolver/packager/*.java
+org/apache/ivy/plugins/signer/bouncycastle/**/*.java
+org/apache/ivy/util/url/HttpClientHandler.java
+
+#This section defines the resources to copy for ivy-optional.jar
+org/apache/ivy/plugins/repository/vfs/*.xml
diff --git a/src/etc/checkstyle/RequiredHeader.txt b/src/etc/checkstyle/RequiredHeader.txt
new file mode 100644
index 0000000..6a5bb9a
--- /dev/null
+++ b/src/etc/checkstyle/RequiredHeader.txt
@@ -0,0 +1,17 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
diff --git a/src/etc/checkstyle/checkstyle-config b/src/etc/checkstyle/checkstyle-config
new file mode 100644
index 0000000..ae186c8
--- /dev/null
+++ b/src/etc/checkstyle/checkstyle-config
@@ -0,0 +1,152 @@
+<?xml version="1.0"?>
+<!DOCTYPE module PUBLIC "-//Puppy Crawl//DTD Check Configuration 1.1//EN" "http://www.puppycrawl.com/dtds/configuration_1_1.dtd">
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<module name="Checker">
+
+  <!-- required license file -->
+  <module name="Header">
+    <property name="headerFile" value="${checkstyle.src.dir}/RequiredHeader.txt"/>
+    <property name="ignoreLines" value="2"/>
+  </module>
+
+  <!-- Items moved out of TreeWalker for new versions of Checkstyle -->
+  <module name="FileLength"/>
+  <module name="FileTabCharacter"/>
+
+  <module name="TreeWalker">
+    <!-- Javadoc requirements -->
+    <!-- TODO uncomment this when javadoc will be improved
+    <module name="JavadocType">
+      <property name="scope" value="protected"/>
+    </module>
+    <module name="JavadocMethod">
+      <property name="scope" value="protected"/>
+      <property name="allowUndeclaredRTE" value="true"/>
+    </module>
+    <module name="JavadocVariable">
+       <property name="scope" value="public"/>
+    </module>
+	-->
+
+    <!-- element naming -->
+    <module name="PackageName"/>
+    <module name="TypeName"/>
+    <module name="ConstantName"/>
+    <module name="LocalFinalVariableName"/>
+    <module name="LocalVariableName"/>
+    <module name="MemberName"/>
+    <module name="MethodName"/>
+    <module name="ParameterName"/>
+    <module name="StaticVariableName"/>
+
+    <!-- Import conventions -->
+    <module name="AvoidStarImport"/>
+    <!-- <module name="IllegalImport"/> -->
+    <module name="RedundantImport"/>
+    <module name="UnusedImports"/>
+
+    <!-- size limits -->
+    <module name="LineLength">
+      <property name="max" value="100"/>
+      <property name="ignorePattern" value="^ *\* *[^ ]+$"/>
+      <property name="tabWidth" value="4"/>
+    </module>
+    <module name="MethodLength"/>
+    <module name="ParameterNumber"/>
+
+    <!-- whitespace checks -->
+    <module name="EmptyForIteratorPad"/>
+    <module name="NoWhitespaceAfter"/>
+    <module name="NoWhitespaceBefore"/>
+    <module name="OperatorWrap"/>
+    <module name="ParenPad"/>
+    <module name="WhitespaceAfter"/>
+    <module name="WhitespaceAround"/>
+
+    <!-- Modifier Checks -->
+    <module name="ModifierOrder"/>
+
+
+    <!-- Checks for blocks -->
+    <module name="AvoidNestedBlocks"/>
+    <module name="EmptyBlock">
+      <property name="option" value="text"/>
+    </module>
+    <module name="LeftCurly"/>
+    <module name="NeedBraces"/>
+    <module name="RightCurly"/>
+
+
+    <!-- Checks for common coding problems -->
+    <!--<module name="AvoidInlineConditionals"/> -->
+    <!--<module name="DoubleCheckedLocking"/>--> <!-- removed in checkstyle 5.6 -->
+    <module name="EmptyStatement"/>
+    <module name="EqualsHashCode"/>
+    <module name="IllegalInstantiation">
+      <property name="classes" value="java.lang.Boolean"/>
+    </module>
+    <module name="InnerAssignment"/>
+    <module name="MagicNumber"/>
+    <module name="MissingSwitchDefault"/>
+    <module name="RedundantThrows">
+      <property name="allowUnchecked" value="true"/>
+    </module>
+    <module name="SimplifyBooleanExpression"/>
+    <module name="SimplifyBooleanReturn"/>
+
+    <!-- Checks for class design -->
+    <!-- <module name="DesignForExtension"/> -->
+    <module name="FinalClass"/>
+    <module name="HideUtilityClassConstructor"/>
+    <module name="InterfaceIsType"/>
+    <module name="VisibilityModifier"/>
+
+    <!-- Miscellaneous other checks. -->
+    <module name="ArrayTypeStyle"/>
+    <!--
+    <module name="GenericIllegalRegexp">
+      <property name="format" value="\s+$"/>
+      <property name="message" value="Line has trailing spaces."/>
+    </module>
+    -->
+    <!--
+    <module name="TodoComment"/>
+    -->
+    <module name="UpperEll"/>
+    <!-- allow comment suppression of checks -->
+    <module name="FileContentsHolder"/>
+  </module>
+  
+  <!--TODO: comment this out, if Simian is not present -->
+  <!--
+  <module name="au.com.redhillconsulting.simian.SimianCheck"/>
+  -->
+  
+  <module name="SuppressionCommentFilter">
+    <property name="offCommentFormat" value="CheckStyle\:([\w\|]+) *OFF"/>
+    <property name="onCommentFormat" value="CheckStyle\:([\w\|]+) *ON"/>
+    <property name="checkFormat" value="$1"/>
+  </module>
+  
+  <module name="SuppressionFilter">
+    <property name="file" value="${basedir}/src/etc/checkstyle/checkstyle-suppress.xml"/>
+  </module>
+  
+</module>
diff --git a/src/etc/checkstyle/checkstyle-frames.xsl b/src/etc/checkstyle/checkstyle-frames.xsl
new file mode 100644
index 0000000..7fe4954
--- /dev/null
+++ b/src/etc/checkstyle/checkstyle-frames.xsl
@@ -0,0 +1,293 @@
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
+    xmlns:lxslt="http://xml.apache.org/xslt"
+    xmlns:redirect="org.apache.xalan.lib.Redirect"
+    extension-element-prefixes="redirect">
+
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT 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:output method="html" indent="yes" encoding="US-ASCII"/>
+    <xsl:decimal-format decimal-separator="." grouping-separator="," />
+
+    <xsl:param name="output.dir" select="'.'"/>
+    <xsl:param name="basedir" select="'.'"/>
+
+    <xsl:template match="checkstyle">
+        <!-- create the index.html -->
+        <redirect:write file="{$output.dir}/index.html">
+            <xsl:call-template name="index.html"/>
+        </redirect:write>
+
+        <!-- create the stylesheet.css -->
+        <redirect:write file="{$output.dir}/stylesheet.css">
+            <xsl:call-template name="stylesheet.css"/>
+        </redirect:write>
+
+        <!-- create the overview-summary.html at the root -->
+        <redirect:write file="{$output.dir}/overview-frame.html">
+            <xsl:apply-templates select="." mode="overview"/>
+        </redirect:write>
+
+        <!-- create the all-classes.html at the root -->
+        <redirect:write file="{$output.dir}/allclasses-frame.html">
+            <xsl:apply-templates select="." mode="all.classes"/>
+        </redirect:write>
+
+        <!-- process all files -->
+        <xsl:apply-templates select="file[count(error) != 0]"/>
+    </xsl:template>
+
+    <xsl:template name="index.html">
+        <html>
+            <head>
+                <title>CheckStyle Audit</title>
+            </head>
+            <frameset cols="20%,80%">
+                <frame src="allclasses-frame.html" name="fileListFrame"/>
+                <frame src="overview-frame.html" name="fileFrame"/>
+            </frameset>
+            <noframes>
+                <h2>Frame Alert</h2>
+                <p>
+                    This document is designed to be viewed using the frames feature.
+                    If you see this message, you are using a non-frame-capable web client.
+                </p>
+            </noframes>
+        </html>
+    </xsl:template>
+
+    <xsl:template name="pageHeader">
+        <table border="0" cellpadding="0" cellspacing="0" width="100%">
+            <tr>
+                <td class="text-align:right"><h2>CheckStyle Audit</h2></td>
+            </tr>
+            <tr>
+                <td class="text-align:right">Designed for use with
+                  <a href='http://checkstyle.sourceforge.net/'>CheckStyle</a> and
+                  <a href='http://ant.apache.org/'>Ant</a>.</td>
+            </tr>
+        </table>
+        <hr size="1"/>
+    </xsl:template>
+
+    <xsl:template match="checkstyle" mode="overview">
+        <html>
+            <head>
+                <link rel="stylesheet" type="text/css" href="stylesheet.css"/>
+            </head>
+            <body>
+                <!-- page header -->
+                <xsl:call-template name="pageHeader"/>
+
+                <!-- Summary part -->
+                <xsl:apply-templates select="." mode="summary"/>
+                <hr size="1" width="100%" align="left"/>
+
+                <!-- File list part -->
+                <xsl:apply-templates select="." mode="filelist"/>
+            </body>
+        </html>
+    </xsl:template>
+
+    <xsl:template name="stylesheet.css">
+        .bannercell {
+        border: 0px;
+        padding: 0px;
+        }
+        body {
+        margin-left: 10;
+        margin-right: 10;
+        font:normal 80% arial,helvetica,sanserif;
+        background-color:#FFFFFF;
+        color:#000000;
+        }
+        .oddrow td {
+        background: #efefef;
+        }
+        .evenrow td {
+        background: #fff;
+        }
+        th, td {
+        text-align: left;
+        vertical-align: top;
+        }
+        th {
+        font-weight:bold;
+        background: #ccc;
+        color: black;
+        }
+        table, th, td {
+        font-size:100%;
+        border: none
+        }
+        table.log tr td, tr th {
+
+        }
+        h2 {
+        font-weight:bold;
+        font-size:140%;
+        margin-bottom: 5;
+        }
+        h3 {
+        font-size:100%;
+        font-weight:bold;
+        background: #525D76;
+        color: white;
+        text-decoration: none;
+        padding: 5px;
+        margin-right: 2px;
+        margin-left: 2px;
+        margin-bottom: 0;
+        }
+    </xsl:template>
+
+    <!--
+    Creates an all-classes.html file that contains a link to all files.
+    -->
+    <xsl:template match="checkstyle" mode="all.classes">
+        <html>
+            <head>
+                <link rel="stylesheet" type="text/css" href="stylesheet.css"/>
+            </head>
+            <body>
+                <h2>Files</h2>
+                <p>
+                    <table width="100%">
+                        <!-- For each file create its part -->
+                        <xsl:apply-templates select="file[count(error) != 0]" mode="all.classes">
+                            <xsl:sort select="substring-after(@name, $basedir)"/>
+                        </xsl:apply-templates>
+                    </table>
+                </p>
+            </body>
+        </html>
+    </xsl:template>
+
+    <xsl:template match="checkstyle" mode="filelist">
+        <h3>Files</h3>
+        <table class="log" border="0" cellpadding="5" cellspacing="2" width="100%">
+            <tr>
+                <th>Name</th>
+                <th>Errors</th>
+            </tr>
+            <xsl:apply-templates select="file[count(error) != 0]" mode="filelist">
+                <xsl:sort select="count(error)" order="descending" data-type="number"/>
+            </xsl:apply-templates>
+        </table>
+    </xsl:template>
+
+    <xsl:template match="file" mode="filelist">
+        <tr>
+            <xsl:call-template name="alternated-row"/>
+            <td nowrap="nowrap">
+                <a>
+                    <xsl:attribute name="href">
+                        <xsl:text>files/</xsl:text><xsl:value-of select="substring-after(@name, $basedir)"/><xsl:text>.html</xsl:text>
+                    </xsl:attribute>
+                    <xsl:value-of select="substring-after(@name, $basedir)"/>
+                </a>
+            </td>
+            <td><xsl:value-of select="count(error)"/></td>
+        </tr>
+    </xsl:template>
+
+    <xsl:template match="file" mode="all.classes">
+        <tr>
+            <td nowrap="nowrap">
+                <a target="fileFrame">
+                    <xsl:attribute name="href">
+                        <xsl:text>files/</xsl:text><xsl:value-of select="substring-after(@name, $basedir)"/><xsl:text>.html</xsl:text>
+                    </xsl:attribute>
+                    <xsl:value-of select="substring-after(@name, $basedir)"/>
+                </a>
+            </td>
+        </tr>
+    </xsl:template>
+
+    <!--
+    transform string like a/b/c to ../../../
+    @param path the path to transform into a descending directory path
+    -->
+    <xsl:template name="path">
+        <xsl:param name="path"/>
+        <xsl:if test="contains($path,'/')">
+            <xsl:text>../</xsl:text>
+            <xsl:call-template name="path">
+                <xsl:with-param name="path"><xsl:value-of select="substring-after($path,'/')"/></xsl:with-param>
+            </xsl:call-template>
+        </xsl:if>
+        <xsl:if test="not(contains($path,'/')) and not($path = '')">
+            <xsl:text>../</xsl:text>
+        </xsl:if>
+    </xsl:template>
+
+    <xsl:template match="file">
+        <redirect:write file="{$output.dir}/files/{substring-after(@name, $basedir)}.html">
+            <html>
+                <head>
+                    <link rel="stylesheet" type="text/css">
+                        <xsl:attribute name="href"><xsl:call-template name="path"><xsl:with-param name="path" select="substring-after(@name, $basedir)"/></xsl:call-template><xsl:text>stylesheet.css</xsl:text></xsl:attribute>
+                    </link>
+                </head>
+                <body>
+                    <xsl:call-template name="pageHeader"/>
+                    <h3>File <xsl:value-of select="substring-after(@name, $basedir)"/></h3>
+                    <table class="log" border="0" cellpadding="5" cellspacing="2" width="100%">
+                        <tr>
+                            <th>Error Description</th>
+                            <th>Line:Column</th>
+                        </tr>
+                        <xsl:for-each select="error">
+                            <tr>
+                                <xsl:call-template name="alternated-row"/>
+                                <td><a title="{@source}"><xsl:value-of select="@message"/></a></td>
+                                <td align="center"><xsl:value-of select="@line"/><xsl:if test="@column">:<xsl:value-of select="@column"/></xsl:if></td>
+                            </tr>
+                        </xsl:for-each>
+                    </table>
+                </body>
+            </html>
+        </redirect:write>
+    </xsl:template>
+
+    <xsl:template match="checkstyle" mode="summary">
+        <h3>Summary</h3>
+        <xsl:variable name="fileCount" select="count(file)"/>
+        <xsl:variable name="errorCount" select="count(file/error)"/>
+        <xsl:variable name="fileErrorCount" select="count(file[count(error) != 0])"/>
+        <table class="log" border="0" cellpadding="5" cellspacing="2" width="100%">
+            <tr>
+                <th>Total Files</th>
+                <th>Files With Errors</th>
+                <th>Errors</th>
+            </tr>
+            <tr>
+                <xsl:call-template name="alternated-row"/>
+                <td><xsl:value-of select="$fileCount"/></td>
+                <td><xsl:value-of select="$fileErrorCount"/></td>
+                <td><xsl:value-of select="$errorCount"/></td>
+            </tr>
+        </table>
+    </xsl:template>
+
+    <xsl:template name="alternated-row">
+        <xsl:attribute name="class">
+            <xsl:if test="position() mod 2 = 1">oddrow</xsl:if>
+            <xsl:if test="position() mod 2 = 0">evenrow</xsl:if>
+        </xsl:attribute>
+    </xsl:template>
+</xsl:stylesheet>
diff --git a/src/etc/checkstyle/checkstyle-suppress.xml b/src/etc/checkstyle/checkstyle-suppress.xml
new file mode 100644
index 0000000..2565f28
--- /dev/null
+++ b/src/etc/checkstyle/checkstyle-suppress.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE suppressions PUBLIC
+    "-//Puppy Crawl//DTD Suppressions 1.0//EN"
+    "http://www.puppycrawl.com/dtds/suppressions_1_0.dtd">
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<suppressions>
+	<suppress checks="MagicNumber" files=".*[\\/]test[\\/]java[\\/].*"/>
+</suppressions>
\ No newline at end of file
diff --git a/src/etc/checkstyle/checkstyle-text.xsl b/src/etc/checkstyle/checkstyle-text.xsl
new file mode 100644
index 0000000..333aecf
--- /dev/null
+++ b/src/etc/checkstyle/checkstyle-text.xsl
@@ -0,0 +1,33 @@
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT 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:strip-space elements="checkstyle"/>
+    <xsl:preserve-space elements="file"/>
+    <xsl:output method="text"/>
+    <xsl:template match="checkstyle/file/error">
+        <xsl:value-of select="../@name"/>
+        <xsl:text>:</xsl:text>
+        <xsl:value-of select="@line"/>
+        <xsl:text>:</xsl:text>
+        <xsl:value-of select="@column"/>
+        <xsl:text> </xsl:text>
+        <xsl:value-of select="@message"/>
+    </xsl:template>
+</xsl:stylesheet>
+
diff --git a/src/etc/checkstyle/checkstyle-xdoc.xsl b/src/etc/checkstyle/checkstyle-xdoc.xsl
new file mode 100644
index 0000000..4d7ab86
--- /dev/null
+++ b/src/etc/checkstyle/checkstyle-xdoc.xsl
@@ -0,0 +1,129 @@
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
+    xmlns:lxslt="http://xml.apache.org/xslt"
+    xmlns:redirect="org.apache.xalan.lib.Redirect"
+    extension-element-prefixes="redirect">
+
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT 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:output method="xml" indent="yes"/>
+    <xsl:decimal-format decimal-separator="." grouping-separator="," />
+
+    <xsl:param name="output.dir" select="'.'"/>
+    <xsl:param name="basedir" select="'.'"/>
+
+    <xsl:template match="checkstyle">
+      <document>
+        <properties>
+          <title>Checkstyle Audit</title>
+        </properties>
+
+        <body>
+          <xsl:apply-templates select="." mode="summary"/>
+          <!-- File list part -->
+          <xsl:apply-templates select="." mode="filelist"/>
+          <xsl:apply-templates select="file[count(error) != 0]"/>
+        </body>
+      </document>
+    </xsl:template>
+
+    <xsl:template match="checkstyle" mode="filelist">
+      <section name="Files">
+        <table>
+            <tr>
+                <th>Name</th>
+                <th>Errors</th>
+            </tr>
+            <xsl:apply-templates select="file[count(error) != 0]" mode="filelist">
+                <xsl:sort select="count(error)" order="descending" data-type="number"/>
+            </xsl:apply-templates>
+        </table>
+      </section>
+    </xsl:template>
+
+    <xsl:template match="file" mode="filelist">
+        <tr>
+            <xsl:call-template name="alternated-row"/>
+            <td nowrap="nowrap">
+                <a>
+                    <xsl:attribute name="href">
+                        <xsl:text>files</xsl:text><xsl:value-of select="substring-after(@name, $basedir)"/><xsl:text>.html</xsl:text>
+                    </xsl:attribute>
+                    <xsl:value-of select="substring-after(@name, $basedir)"/>
+                </a>
+            </td>
+            <td><xsl:value-of select="count(error)"/></td>
+        </tr>
+    </xsl:template>
+
+    <xsl:template match="file">
+      <redirect:write file="{$output.dir}/files{substring-after(@name, $basedir)}.xml">
+        <document>
+          <properties>
+            <title>Checkstyle Audit</title>
+          </properties>
+
+          <body>
+            <section name="Details for {substring-after(@name, $basedir)}">
+              <table>
+                  <tr>
+                      <th>Error Description</th>
+                      <th>Line</th>
+                  </tr>
+                  <xsl:for-each select="error">
+                      <tr>
+                          <xsl:call-template name="alternated-row"/>
+                          <td><a title="{@source}"><xsl:value-of select="@message"/></a></td>
+                          <td><xsl:value-of select="@line"/></td>
+                      </tr>
+                  </xsl:for-each>
+              </table>
+            </section>
+          </body>
+        </document>
+      </redirect:write>
+    </xsl:template>
+
+    <xsl:template match="checkstyle" mode="summary">
+      <section name="Summary">
+        <xsl:variable name="fileCount" select="count(file)"/>
+        <xsl:variable name="errorCount" select="count(file/error)"/>
+        <xsl:variable name="fileErrorCount" select="count(file[count(error) != 0])"/>
+        <table>
+            <tr>
+                <th>Files</th>
+                <th>Files With Errors</th>
+                <th>Errors</th>
+            </tr>
+            <tr>
+                <xsl:call-template name="alternated-row"/>
+                <td><xsl:value-of select="$fileCount"/></td>
+                <td><xsl:value-of select="$fileErrorCount"/></td>
+                <td><xsl:value-of select="$errorCount"/></td>
+            </tr>
+        </table>
+      </section>
+    </xsl:template>
+
+    <xsl:template name="alternated-row">
+        <xsl:attribute name="class">
+            <xsl:if test="position() mod 2 = 1">oddrow</xsl:if>
+            <xsl:if test="position() mod 2 = 0">evenrow</xsl:if>
+        </xsl:attribute>
+    </xsl:template>
+</xsl:stylesheet>
+
diff --git a/src/etc/license/license-header.xml b/src/etc/license/license-header.xml
new file mode 100644
index 0000000..1c1b8cf
--- /dev/null
+++ b/src/etc/license/license-header.xml
@@ -0,0 +1,18 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
diff --git a/src/etc/makepom/pom.template b/src/etc/makepom/pom.template
new file mode 100644
index 0000000..1fe333d
--- /dev/null
+++ b/src/etc/makepom/pom.template
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<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/xsd/maven-4.0.0.xsd">
+
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.apache</groupId>
+    <artifactId>apache</artifactId>
+    <version>7</version>
+  </parent>
+  <groupId>${ivy.pom.groupId}</groupId>
+  <artifactId>${ivy.pom.artifactId}</artifactId>
+  <version>${ivy.pom.version}</version>
+  <name>Apache Ivy</name>
+  <url>${ivy.pom.url}</url>
+  <scm>
+    <connection>scm:svn:http://svn.apache.org/repos/asf/ant/ivy/core/trunk/</connection>
+    <developerConnection>scm:svn:https://svn.apache.org/repos/asf/ant/ivy/core/trunk</developerConnection>
+    <url>http://svn.apache.org/repos/asf/ant/ivy/core/trunk</url>
+  </scm>
+  <mailingLists>
+    <mailingList>
+      <name>Ant/Ivy Developers List</name>
+      <subscribe>dev-subscribe at ant.apache.org</subscribe>
+      <unsubscribe>dev-unsubscribe at ant.apache.org</unsubscribe>
+      <post>dev at ant.apache.org</post>
+      <archive>http://mail-archives.apache.org/mod_mbox/ant-dev</archive>
+    </mailingList>
+    <mailingList>
+      <name>Ivy Users List</name>
+      <subscribe>ivy-user-subscribe at ant.apache.org</subscribe>
+      <unsubscribe>ivy-user-unsubscribe at ant.apache.org</unsubscribe>
+      <post>ivy-user at ant.apache.org</post>
+      <archive>http://mail-archives.apache.org/mod_mbox/ant-ivy-user</archive>
+    </mailingList>
+  </mailingLists>
+  <issueManagement>
+    <system>jira</system>
+    <url>http://issues.apache.org/jira/browse/IVY</url>
+  </issueManagement>
+</project>
diff --git a/src/example/bintray/build.xml b/src/example/bintray/build.xml
new file mode 100644
index 0000000..c82359e
--- /dev/null
+++ b/src/example/bintray/build.xml
@@ -0,0 +1,23 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.
+-->
+<project xmlns:ivy="antlib:org.apache.ivy.ant" name="ivy-bintray-example" default="retrieve">
+    <target name="retrieve">
+        <ivy:retrieve />
+    </target>
+</project>
diff --git a/src/example/bintray/ivy.xml b/src/example/bintray/ivy.xml
new file mode 100644
index 0000000..120a43b
--- /dev/null
+++ b/src/example/bintray/ivy.xml
@@ -0,0 +1,31 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT 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="2.0">
+    <info organisation="org.apache" module="hello-ivy"/>
+    <dependencies>
+        <!-- https://jcenter.bintray.com/ -->
+        <dependency org="org.jfrog.artifactory.client" name="artifactory-cli" rev="1.0" />
+        <dependency org="org.jfrog.artifactory.client" name="artifactory-cli" rev="1.0" />
+        <dependency org="org.jfrog"                    name="build-info-api"  rev="1.3.1" />
+        <!-- https://dl.bintray.com/dsowerby/maven/ -->
+        <dependency org="uk.q3c.krail"                 name="krail"           rev="0.7.0" />
+        <!-- https://dl.bintray.com/igelgrun/batrak/ -->
+        <dependency org="igel.batrak"                  name="batrak-core"     rev="0.1" />
+    </dependencies>
+</ivy-module>
diff --git a/src/example/bintray/ivysettings.xml b/src/example/bintray/ivysettings.xml
new file mode 100644
index 0000000..15adc16
--- /dev/null
+++ b/src/example/bintray/ivysettings.xml
@@ -0,0 +1,37 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.
+-->
+<ivysettings>
+  <settings defaultResolver="chain"/>
+  <resolvers>
+    <chain name="chain">
+
+      <!-- https://jcenter.bintray.com/ -->
+      <bintray />
+
+      <!-- https://bintray.com/dsowerby/maven     -->
+      <!-- https://dl.bintray.com/dsowerby/maven/ -->
+      <bintray subject="dsowerby" repo="maven"/>
+
+      <!-- https://bintray.com/igelgrun/batrak     -->
+      <!-- https://dl.bintray.com/igelgrun/batrak/ -->
+      <bintray subject="igelgrun" repo="batrak"/>
+
+    </chain>
+  </resolvers>
+</ivysettings>
diff --git a/src/example/build-a-ivy-repository/build.xml b/src/example/build-a-ivy-repository/build.xml
new file mode 100644
index 0000000..ce83957
--- /dev/null
+++ b/src/example/build-a-ivy-repository/build.xml
@@ -0,0 +1,120 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<!-- ====================================================================== 
+     This is a sample project to build our own ivy repository.
+     ====================================================================== -->
+<project name="ivy-repository" default="maven2" xmlns:ivy="antlib:org.apache.ivy.ant">
+	<property name="settings.dir" value="settings"/>
+	<property name="from.resolver" value="libraries"/>
+	<property name="to.resolver" value="my-repository"/>
+	
+	<property name="ivy.cache.dir" value="${basedir}/cache" />
+	<property name="dest.repo.dir" value="${basedir}/myrepository" />
+
+	<property name="ivy.jar.dir" value="${user.home}/.ivy2/jars" />
+	<property name="ivy.jar.file" value="${ivy.jar.dir}/ivy.jar" />
+
+	<!-- ================================= 
+          target: load-ivy         
+            this target is not necessary if you put ivy.jar in your ant lib directory
+            if you already have ivy 1.4 in your ant lib, you can simply remove this
+            target
+         ================================= -->
+    <target name="load-ivy">
+    	<!-- try to load ivy here from home ivy dir, in case the user has not already dropped
+    	      it into ant's lib dir (note that the latter copy will always take precedence).
+    	      We will not fail as long as ivy home lib dir exists (it may be empty) and
+    	      ivy is in at least one of ant's lib dir or the ivy home lib dir. -->
+    	<path id="ivy.lib.path">
+    		<pathelement location="${ivy.jar.file}"/>
+    	</path>
+    	<taskdef resource="org/apache/ivy/ant/antlib.xml"
+    	          uri="antlib:org.apache.ivy.ant" classpathref="ivy.lib.path"/>
+    </target>    
+
+	<!-- - - - - - - - - - - - - - - - - - 
+          target: init-ivy                      
+         - - - - - - - - - - - - - - - - - -->
+    <target name="init-ivy" depends="load-ivy">
+       	<ivy:settings id="basic.settings"    file="${settings.dir}/ivysettings-basic.xml"/>
+       	<ivy:settings id="advanced.settings" file="${settings.dir}/ivysettings-advanced.xml"/>
+    </target>
+
+	
+    <!-- ================================= 
+          target: maven2
+          maven 2 no namespace and no dependencies
+         ================================= -->
+    <target name="maven2" depends="init-ivy"
+    	description="--> install module from maven 2 repository">
+    	<ivy:install settingsRef="basic.settings" 
+    		organisation="commons-lang" module="commons-lang" revision="1.0" 
+    		from="${from.resolver}" to="${to.resolver}" />
+    </target>
+
+    <!-- ================================= 
+          target: maven2-deps
+          maven 2 no namespace with dependencies
+         ================================= -->
+    <target name="maven2-deps" depends="init-ivy" 
+    	description="--> install module from maven 2 repository with dependencies">
+    	<ivy:install settingsRef="basic.settings" 
+    		organisation="org.hibernate" module="hibernate" revision="3.2.5.ga" 
+    		from="${from.resolver}" to="${to.resolver}" transitive="true" />
+    </target>
+
+    <!-- ================================= 
+          target: maven2-namespace
+          maven 2 with namespace no dependencies
+         ================================= -->
+    <target name="maven2-namespace" depends="init-ivy" 
+    	description="--> install module from maven 2 using namespaces">
+    	<ivy:install settingsRef="advanced.settings" 
+    		organisation="apache" module="commons-lang" revision="1.0" 
+    		from="${from.resolver}" to="${to.resolver}" />
+    </target>
+
+    <!-- ================================= 
+          target: maven2-namespace-deps
+          maven 2 with namespace and dependencies
+         ================================= -->
+    <target name="maven2-namespace-deps" depends="init-ivy"
+    	description="--> install module with dependencies from maven2 repo using namespaces">
+    	<ivy:install settingsRef="advanced.settings" 
+    		organisation="hibernate" module="hibernate" revision="3.2.5.ga" 
+    		from="${from.resolver}" to="${to.resolver}" transitive="true" />
+    </target>
+
+	<!-- ================================= 
+          target: clean-cache
+         ================================= -->
+    <target name="clean-cache" depends="init-ivy" description="--> clean the cache">
+    	<ivy:cleancache settingsRef="basic.settings" />
+    	<ivy:cleancache settingsRef="advanced.settings" />
+    	<delete dir="${ivy.cache.dir}" failonerror="true"  />
+    </target>
+
+	<!-- ================================= 
+          target: clean-repo
+         ================================= -->
+    <target name="clean-repo" description="--> clean the destination repository">
+    	<delete dir="${dest.repo.dir}" failonerror="true"  />
+    </target>
+</project>
diff --git a/src/example/build-a-ivy-repository/settings/ivysettings-advanced.xml b/src/example/build-a-ivy-repository/settings/ivysettings-advanced.xml
new file mode 100644
index 0000000..09ca0d4
--- /dev/null
+++ b/src/example/build-a-ivy-repository/settings/ivysettings-advanced.xml
@@ -0,0 +1,161 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<ivysettings>
+	<settings defaultResolver="my-repository"
+              defaultConflictManager="all" />		<!-- in order to get all revisions without any eviction -->
+	<caches defaultCacheDir="${ivy.cache.dir}/advanced" />
+
+	<!-- 
+	   You can override this property to use one of the mirrors listed on
+	   http://docs.codehaus.org/display/MAVENUSER/Mirrors+Repositories
+	-->
+	<property name="ibiblio-maven2-root" value="https://repo1.maven.org/maven2/" override="false" />
+
+	<namespaces>
+    	<namespace name="maven2">
+	        <rule>	<!-- imported apache maven1 projects -->
+            	<fromsystem>
+                	<src org="apache" module=".+"/>
+	
+                    <dest org="$m0" module="$m0"/>
+                </fromsystem>
+                <tosystem>
+					<src org="commons-.+" module="commons-.+" />
+					<src org="ant" module=".*" />
+					<src org="avalon-.+" module="avalon-.+" />
+					<src org="avalon" module="avalon" />
+					<src org="axis" module="axis" />
+					<src org="axis2" module="axis2" />
+					<src org="batik" module="batik" />
+					<src org="bcel" module="bcel" />
+					<src org="beehive" module="beehive" />
+					<src org="bsf" module="bsf" />
+					<src org="cactus" module="cactus" />
+					<src org="cocoon" module="cocoon" />
+					<src org="cornerstone-.+" module="cornerstone-.+" />
+					<src org="create-checksums" module="create-checksums" />
+					<src org="crimson" module="crimson" />
+					<src org="directory-.+" module="directory-.+" />
+					<src org="directory" module="directory" />
+					<src org="ecs" module="ecs" />
+					<src org="excalibur-.+" module="excalibur-.+" />
+					<src org="excalibur" module="excalibur" />
+					<src org="fop" module="fop" />
+					<src org="fulcrum" module="fulcrum" />
+					<src org="geronimo-.+" module="geronimo-.+" />
+					<src org="geronimo" module="geronimo" />
+					<src org="hivemind" module="hivemind" />
+					<src org="jakarta-regexp" module="jakarta-regexp" />
+					<src org="james" module="james" />
+					<src org="jaxme" module="jaxme" />
+					<src org="jcs-javagroups" module="jcs-javagroups" />
+					<src org="jcs" module="jcs" />
+					<src org="jspapi" module="jspapi" />
+					<src org="jstl" module="jstl" />
+					<src org="juddi" module="juddi" />
+					<src org="log4j" module="log4j" />
+					<src org="logkit" module="logkit" />
+					<src org="lucene" module="lucene" />
+					<src org="magicGball" module="magicGball" />
+					<src org="maven" module="maven" />
+					<src org="merlin-developer" module="merlin-developer" />
+					<src org="merlin" module="merlin" />
+					<src org="muse" module="muse" />
+					<src org="myfaces" module="myfaces" />
+					<src org="nekohtml" module="nekohtml" />
+					<src org="ojb" module="ojb" />
+					<src org="oro" module="oro" />
+					<src org="pluto-container" module="pluto-container" />
+					<src org="poi" module="poi" />
+					<src org="pubscribe" module="pubscribe" />
+					<src org="sandesha" module="sandesha" />
+					<src org="servletapi" module="servletapi" />
+					<src org="slide" module=".*" />
+					<src org="stratum" module="stratum" />
+					<src org="struts" module="struts" />
+					<src org="taglibs" module="taglibs" />
+					<src org="tapestry" module="tapestry" />
+					<src org="tomcat-util" module="tomcat-util" />
+					<src org="tomcat" module="tomcat" />
+					<src org="torque" module="torque" />
+					<src org="turbine" module="turbine" />
+					<src org="velocity-.+" module="velocity-.+" />
+					<src org="velocity" module="velocity" />
+					<src org="ws-commons.*" module="ws-commons.*" />
+					<src org="wsdl4j" module="wsdl4j" />
+					<src org="wsrf" module="wsrf" />
+					<src org="xalan" module="xalan" />
+					<src org="xerces" module=".*" />
+					<src org="xercesjarv" module="xercesjarv" />
+					<src org="xml-apis" module="xml-apis" />
+					<src org="xml-resolver" module="xml-resolver" />
+					<src org="xml-security" module="xml-security" />
+					<src org="xmlbeans" module="xmlbeans" />
+					<src org="xmlrpc" module="xmlrpc" />
+					
+					<dest org="apache" module="$m0"/>
+            	</tosystem>
+        	</rule>
+
+			<rule> <!-- new apache projects -->
+	    		<fromsystem>
+		        	<src org="apache" />
+        			<dest org="org.apache"/>
+		    	</fromsystem>
+			    <tosystem>
+    		    	<src org="org.apache" />
+	    		    <dest org="apache" />
+		    	</tosystem>
+			</rule>
+			<rule> <!-- hibernate -->
+	    		<fromsystem>
+		        	<src org="hibernate" />
+        			<dest org="org.hibernate"/>
+		    	</fromsystem>
+			    <tosystem>
+    		    	<src org="org.hibernate" />
+	    		    <dest org="hibernate" />
+		    	</tosystem>
+			</rule>
+			<rule> <!-- net.sf projects -->
+	    		<fromsystem>
+		        	<src org="ehcache" />
+        			<dest org="net.sf.$o0"/>
+		    	</fromsystem>
+			    <tosystem>
+    		    	<src org="net.sf.(.+)" />
+	    		    <dest org="$o1" />
+		    	</tosystem>
+			</rule>
+    	</namespace>
+	</namespaces>
+
+	<resolvers>
+	    <filesystem name="my-repository">
+	        <ivy pattern="${dest.repo.dir}/advanced/[organisation]/[module]/ivys/ivy-[revision].xml"/>
+	        <artifact pattern="${dest.repo.dir}/advanced/[organisation]/[module]/[type]s/[artifact]-[revision].[ext]"/>
+	    </filesystem>
+	
+		<ibiblio	name="libraries" 
+					root="${ibiblio-maven2-root}" 
+					m2compatible="true"
+					namespace="maven2"
+		/>
+	</resolvers>
+</ivysettings>
diff --git a/src/example/build-a-ivy-repository/settings/ivysettings-basic.xml b/src/example/build-a-ivy-repository/settings/ivysettings-basic.xml
new file mode 100644
index 0000000..2538916
--- /dev/null
+++ b/src/example/build-a-ivy-repository/settings/ivysettings-basic.xml
@@ -0,0 +1,30 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<ivysettings>
+	<settings       defaultResolver="libraries"
+			defaultConflictManager="all" />		<!-- in order to get all revisions without any eviction -->
+	<caches defaultCacheDir="${ivy.cache.dir}/no-namespace" />
+	<resolvers>
+		<ibiblio name="libraries" m2compatible="true" />
+  		<filesystem name="my-repository">
+  			<ivy pattern="${dest.repo.dir}/no-namespace/[organisation]/[module]/ivys/ivy-[revision].xml"/>
+  			<artifact pattern="${dest.repo.dir}/no-namespace/[organisation]/[module]/[type]s/[artifact]-[revision].[ext]"/>
+  		</filesystem>
+	</resolvers>
+</ivysettings>
diff --git a/src/example/chained-resolvers/build.xml b/src/example/chained-resolvers/build.xml
new file mode 100644
index 0000000..b8e0122
--- /dev/null
+++ b/src/example/chained-resolvers/build.xml
@@ -0,0 +1,38 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<project default="clean-all" xmlns:ivy="antlib:org.apache.ivy.ant">
+    
+    <!-- ================================= 
+          target: clean              
+         ================================= -->
+    <target name="clean" description="--> clean directories">
+        <ant dir="chainedresolvers-project" antfile="build.xml" target="clean" inheritall="false" inheritrefs="false" />
+    </target>
+	
+    <!-- ================================= 
+          target: clean-cache              
+         ================================= -->
+	<target name="clean-cache" description="--> clean the ivy cache">
+		<property name="ivy.settings.dir" value="settings" />
+		<ivy:settings file="${ivy.settings.dir}/ivysettings.xml" />
+		<ivy:cleancache />
+	</target>
+	
+	<target name="clean-all" depends="clean, clean-cache" description="--> clean directories and ivy cache"/>
+</project>
diff --git a/src/example/chained-resolvers/chainedresolvers-project/build.xml b/src/example/chained-resolvers/chainedresolvers-project/build.xml
new file mode 100644
index 0000000..53dcaee
--- /dev/null
+++ b/src/example/chained-resolvers/chainedresolvers-project/build.xml
@@ -0,0 +1,81 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<project name="chainedresolvers-project" default="run" xmlns:ivy="antlib:org.apache.ivy.ant">
+    <!-- some variables used -->
+    <property name="lib.dir" value="${basedir}/lib" />
+    <property name="build.dir" value="${basedir}/build" />
+    <property name="src.dir" value="${basedir}/src" />
+    
+    <!-- ivy properties used -->
+    <property name="ivy.settings.dir" value="../settings" />
+
+	<!-- paths used for compilation and run  -->
+    <path id="lib.path.id">
+        <fileset dir="${lib.dir}" />
+	</path>
+    <path id="run.path.id">
+        <path refid="lib.path.id" />
+        <path location="${build.dir}" />
+    </path>
+    
+    <ivy:settings file="${ivy.settings.dir}/ivysettings.xml" />
+    
+    <!-- ================================= 
+          target: resolve              
+         ================================= -->
+    <target name="resolve" description="--> resolve and retrieve dependencies with ivy">
+        <ivy:retrieve/>
+    </target>
+    
+    <!-- ================================= 
+          target: report              
+         ================================= -->
+    <target name="report" depends="resolve" description="--> generates a report of dependencies">
+        <ivy:report todir="${build.dir}"/>
+    </target>
+    
+    <!-- ================================= 
+          target: run
+         ================================= -->
+    <target name="run" depends="resolve" description="--> compile and run the project">
+        <mkdir dir="${build.dir}" />
+        <javac srcdir="${src.dir}" destdir="${build.dir}" classpathref="lib.path.id" />
+        <java classpathref="run.path.id" classname="example.Hello"/>
+    </target>
+    
+    <!-- ================================= 
+          target: clean              
+         ================================= -->
+    <target name="clean" description="--> clean the project">
+        <delete includeemptydirs="true">
+            <fileset dir="${basedir}">
+            	<exclude name="src/**" />
+            	<exclude name="build.xml" />
+		    	<exclude name="ivy.xml" />
+        	</fileset>
+    	</delete>
+    </target>
+	
+    <!-- ================================= 
+          target: clean-cache              
+         ================================= -->
+	<target name="clean-cache" description="--> clean the ivy cache">
+		<ivy:cleancache />
+	</target>
+</project>
diff --git a/src/example/chained-resolvers/chainedresolvers-project/ivy.xml b/src/example/chained-resolvers/chainedresolvers-project/ivy.xml
new file mode 100644
index 0000000..450d0a6
--- /dev/null
+++ b/src/example/chained-resolvers/chainedresolvers-project/ivy.xml
@@ -0,0 +1,25 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT 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="org.apache" module="chained-resolvers"/>
+    <dependencies>
+        <dependency org="commons-lang" name="commons-lang" rev="2.0" conf="default"/>
+        <dependency name="test" rev="1.0"/>
+    </dependencies>
+</ivy-module>
diff --git a/src/example/chained-resolvers/chainedresolvers-project/src/example/Hello.java b/src/example/chained-resolvers/chainedresolvers-project/src/example/Hello.java
new file mode 100644
index 0000000..d9bc0ac
--- /dev/null
+++ b/src/example/chained-resolvers/chainedresolvers-project/src/example/Hello.java
@@ -0,0 +1,37 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package example;
+
+import org.apache.commons.lang.WordUtils;
+
+/**
+ * Simple example world to show how easy it is to retreive libs with ivy !!! 
+ */
+public final class Hello {
+    public static void main(String[] args) {
+        String  message = "example world !";
+        System.out.println("standard message :" + message);
+        System.out.println("capitalized by " + WordUtils.class.getName() 
+            + " : " + WordUtils.capitalizeFully(message));
+        System.out.println("upperCased by " + test.StringUtils.class.getName() 
+            + " : " + test.StringUtils.upperCase(message));
+    }
+    
+    private Hello() {
+    }
+}
diff --git a/src/example/chained-resolvers/settings/ivysettings.xml b/src/example/chained-resolvers/settings/ivysettings.xml
new file mode 100644
index 0000000..b5790ee
--- /dev/null
+++ b/src/example/chained-resolvers/settings/ivysettings.xml
@@ -0,0 +1,29 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<ivysettings>
+  <settings defaultResolver="chain-example"/>
+  <resolvers>
+    <chain name="chain-example">
+      <filesystem name="libraries">
+        <artifact pattern="${ivy.settings.dir}/repository/[artifact]-[revision].[ext]" />
+      </filesystem>
+      <ibiblio name="ibiblio" m2compatible="true" />
+    </chain>
+  </resolvers>
+</ivysettings>
diff --git a/src/example/configurations/jdbc-example/build.xml b/src/example/configurations/jdbc-example/build.xml
new file mode 100644
index 0000000..df5eaca
--- /dev/null
+++ b/src/example/configurations/jdbc-example/build.xml
@@ -0,0 +1,97 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<project name="configurations" default="run.dev" xmlns:ivy="antlib:org.apache.ivy.ant">
+    <!-- some variables used -->
+    <property name="lib.dir" value="lib" />
+    <property name="build.dir" value="build" />
+    <property name="src.dir" value="src" />
+    
+    <!-- paths used for compilation and run  -->
+    <path id="compile.path.id">
+        <fileset dir="${lib.dir}/compile" />
+	</path>
+    <path id="lib.run.dev.id">
+        <path location="${build.dir}" />
+        <fileset dir="${lib.dir}/rundev" />
+	</path>
+	<path id="lib.run.prod.id">
+        <path location="${build.dir}" />
+		<fileset dir="${lib.dir}/runprod" />
+    </path>
+    
+    <!-- ================================= 
+          target: resolve              
+         ================================= -->
+    <target name="resolve" description="--> retreive dependencies with ivy">
+    	<!-- conf="*" will copie artifacts defined for each conf in a dir matching conf name -->
+		<ivy:retrieve pattern="${ivy.lib.dir}/[conf]/[artifact]-[revision].[ext]"/>
+    </target>    
+    
+    <!-- ================================= 
+          target: report              
+         ================================= -->
+    <target name="report" depends="resolve" description="--> generates a report of dependencies">
+        <ivy:report todir="${build.dir}"/>
+    </target>
+
+	<!-- ================================= 
+          target: run.dev
+         ================================= -->
+    <target name="run.dev" depends="resolve" description="--> compile and run the project">
+        <mkdir dir="${build.dir}" />
+        <javac srcdir="${src.dir}" destdir="${build.dir}" classpathref="lib.run.dev.id" includeAntRuntime="false"/>
+    	<copy todir="${build.dir}">
+    		<fileset dir="${src.dir}" includes="**/*.properties"></fileset>
+    	</copy>
+        <java classpathref="lib.run.dev.id" classname="example.ConfigurationsExample" fork="true">
+        	<arg value="--dev"/>
+        </java>
+    </target>
+	
+	<!-- ================================= 
+          target: run.prod
+         ================================= -->
+    <target name="run.prod" depends="resolve" description="--> compile and run the project">
+        <mkdir dir="${build.dir}" />
+        <javac srcdir="${src.dir}" destdir="${build.dir}" classpathref="lib.run.prod.id" includeAntRuntime="false"/>
+    	<copy todir="${build.dir}">
+    		<fileset dir="${src.dir}" includes="**/*.properties"></fileset>
+    	</copy>    	
+        <java classpathref="lib.run.prod.id" classname="example.ConfigurationsExample" fork="true" />
+    </target>
+    <!-- ================================= 
+          target: clean              
+         ================================= -->
+    <target name="clean" description="--> clean the project">
+        <delete includeemptydirs="true">
+            <fileset dir="${basedir}">
+            	<exclude name="src/**" />
+            	<exclude name="build.xml" />
+                <exclude name="ivy.xml" />
+        	</fileset>
+    	</delete>
+    </target>
+	
+    <!-- ================================= 
+          target: clean-cache              
+         ================================= -->
+	<target name="clean-cache" description="--> clean the ivy cache">
+		<ivy:cleancache />
+	</target>
+</project>
diff --git a/src/example/configurations/jdbc-example/ivy.xml b/src/example/configurations/jdbc-example/ivy.xml
new file mode 100644
index 0000000..41f080e
--- /dev/null
+++ b/src/example/configurations/jdbc-example/ivy.xml
@@ -0,0 +1,44 @@
+<?xml-stylesheet type="text/xsl" href="http://www.ivyrep.org/ivy-doc.xsl"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT 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="org.apache" module="configurations" >
+    	<description>
+    		This is an example project that aims to demonstrate the usage of the configuration in ivy.
+    		This project provide 4 configurations. Each configurations describe the requirement to build or run the project
+    	</description>
+    </info>
+    <configurations>
+    	<conf name="compile" description="This is this configuration that describes modules need to build our project"/>
+    	<conf name="test" extends="compile" description="This is this configuration that describes modules need to run test on our project"/>
+    	<conf name="rundev" extends="compile" description="This is this configuration that describes modules need to execute our project in a dev environement"/>
+    	<conf name="runprod"  extends="compile" description="This is this configuration that describes modules need to execute our project in a production environement"/>    	
+    </configurations>
+    
+    <dependencies>
+	    <!-- this dependency is needed for all configuration -->
+        <dependency org="commons-cli" name="commons-cli" rev="1.0" />
+        <!-- when launching our app in dev mode we use mckoi db and mckoi jdbc client conf="run.dev->embedded, client"-->
+        <dependency org="mckoi" name="mckoi" rev="1.0.2"  conf="rundev->default"/> 
+        <!-- when launching our app in production environement we needs other jdbc driver -->
+        <dependency org="mm-mysql" name="mm-mysql" rev="2.0.7" conf="runprod->default"/> 
+        <!-- junit is only need in the test configuration-->        
+        <dependency org="junit" name="junit" rev="3.8" conf="test->default"/> 
+    </dependencies>
+</ivy-module>
diff --git a/src/example/configurations/jdbc-example/src/dev.properties b/src/example/configurations/jdbc-example/src/dev.properties
new file mode 100644
index 0000000..ca2146a
--- /dev/null
+++ b/src/example/configurations/jdbc-example/src/dev.properties
@@ -0,0 +1,19 @@
+#	 ***************************************************************
+#	 * Licensed to the Apache Software Foundation (ASF) under one
+#	 * or more contributor license agreements.  See the NOTICE file
+#	 * distributed with this work for additional information
+#	 * regarding copyright ownership.  The ASF licenses this file
+#	 * to you under the Apache License, Version 2.0 (the
+#	 * "License"); you may not use this file except in compliance
+#	 * with the License.  You may obtain a copy of the License at
+#	 * 
+#	 *   http://www.apache.org/licenses/LICENSE-2.0
+#	 * 
+#	 * Unless required by applicable law or agreed to in writing,
+#	 * software distributed under the License is distributed on an
+#	 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#	 * KIND, either express or implied.  See the License for the
+#	 * specific language governing permissions and limitations
+#	 * under the License.
+#	 ***************************************************************
+driver.class=com.mckoi.JDBCDriver
diff --git a/src/example/configurations/jdbc-example/src/example/ConfigurationsExample.java b/src/example/configurations/jdbc-example/src/example/ConfigurationsExample.java
new file mode 100644
index 0000000..fba4535
--- /dev/null
+++ b/src/example/configurations/jdbc-example/src/example/ConfigurationsExample.java
@@ -0,0 +1,64 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package example;
+
+import java.io.IOException;
+import java.util.Properties;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+import org.apache.commons.cli.PosixParser;
+
+public final class ConfigurationsExample {
+    
+    public static void main(String[] args) {
+        String jdbcPropToLoad = "prod.properties";
+        CommandLineParser parser = new PosixParser();
+        Options options = new Options();
+        options.addOption("d", "dev", false, 
+            "Dev tag to launch app in dev mode. Means that app will launch embedded mckoi db.");
+        try {
+            CommandLine line = parser.parse(options, args);
+            if (line.hasOption("d")) {
+                System.err.println("App is in DEV mode");
+                jdbcPropToLoad = "dev.properties";
+            }
+        } catch (ParseException exp) {
+            System.err.println("Parsing failed.  Reason: " + exp.getMessage());
+        }
+        Properties p = new Properties();
+        try {
+            p.load(ConfigurationsExample.class.getResourceAsStream("/" + jdbcPropToLoad));
+        } catch (IOException e) {
+            System.err.println("Properties loading failed.  Reason: " + e.getMessage());
+        }
+        try {
+            String clazz = p.getProperty("driver.class");
+            Class.forName(clazz);
+            System.out.println(" Jdbc driver loaded :" + clazz);
+        } catch (ClassNotFoundException e) {
+            System.err.println("Jdbc Driver class loading failed.  Reason: " + e.getMessage());
+            e.printStackTrace();
+        }
+        
+    }
+    
+    private ConfigurationsExample() {
+    }
+}
diff --git a/src/example/configurations/jdbc-example/src/prod.properties b/src/example/configurations/jdbc-example/src/prod.properties
new file mode 100644
index 0000000..48c25ee
--- /dev/null
+++ b/src/example/configurations/jdbc-example/src/prod.properties
@@ -0,0 +1,19 @@
+#	 ***************************************************************
+#	 * Licensed to the Apache Software Foundation (ASF) under one
+#	 * or more contributor license agreements.  See the NOTICE file
+#	 * distributed with this work for additional information
+#	 * regarding copyright ownership.  The ASF licenses this file
+#	 * to you under the Apache License, Version 2.0 (the
+#	 * "License"); you may not use this file except in compliance
+#	 * with the License.  You may obtain a copy of the License at
+#	 * 
+#	 *   http://www.apache.org/licenses/LICENSE-2.0
+#	 * 
+#	 * Unless required by applicable law or agreed to in writing,
+#	 * software distributed under the License is distributed on an
+#	 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#	 * KIND, either express or implied.  See the License for the
+#	 * specific language governing permissions and limitations
+#	 * under the License.
+#	 ***************************************************************
+driver.class=org.gjt.mm.mysql.Driver
diff --git a/src/example/configurations/multi-projects/filter-framework/build.xml b/src/example/configurations/multi-projects/filter-framework/build.xml
new file mode 100644
index 0000000..566f011
--- /dev/null
+++ b/src/example/configurations/multi-projects/filter-framework/build.xml
@@ -0,0 +1,140 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<project name="filter-framework" default="publish" xmlns:ivy="antlib:org.apache.ivy.ant">
+    <!-- some variables used -->
+    <property name="lib.dir" value="lib" />
+    <property name="build.dir" value="build" />
+    <property name="distrib.dir" location="distrib" />
+    <property name="src.dir" value="src" />
+	<property name="test.dir" value="test" />
+	<property name="build.test.dir" value="${build.dir}/test-classes" />
+	<property name="report.test.dir" value="${build.dir}/test-report" />
+	<property name="revision" value="1.3" />
+	
+	<property name="ivy.local.default.root" location="${user.home}/.ivy2/local"/>
+	
+    <!-- paths used for compilation and run  -->
+    <path id="compile.path.id">
+        <fileset dir="${lib.dir}/cc-impl" />
+	</path>
+	
+    <path id="test.path.id">
+        <path location="${build.dir}" />
+    	 <path location="${build.test.dir}" />
+        <fileset dir="${lib.dir}/test" />
+	</path>
+    
+    <!-- ================================= 
+          target: resolve              
+         ================================= -->
+    <target name="resolve" description="--> retreive dependencies with ivy">
+    	<!-- conf="*" will copie artifacts defined for each conf in a dir matching conf name -->
+		<ivy:retrieve pattern="${ivy.lib.dir}/[conf]/[artifact]-[revision].[ext]"/>
+    </target>
+	
+	<!-- ================================= 
+          target: build
+         ================================= -->
+    <target name="build" depends="clean, resolve" description="--> compile and jar project">
+        <mkdir dir="${build.dir}" />
+		<mkdir dir="${distrib.dir}"/>
+        <javac srcdir="${src.dir}" destdir="${build.dir}" classpathref="compile.path.id" includeAntRuntime="false"/>
+    	<jar destfile="${distrib.dir}/filter-api.jar" >
+    		<fileset dir="${build.dir}">
+    			<include name="filter/*.class"/>
+    		</fileset>
+    	</jar>
+    	<jar destfile="${distrib.dir}/filter-hmimpl.jar" >
+    		<fileset dir="${build.dir}">
+    			<include name="filter/hmimpl/*.class"/>
+    		</fileset>
+    	</jar>    
+    	<jar destfile="${distrib.dir}/filter-ccimpl.jar" >
+    		<fileset dir="${build.dir}">
+    			<include name="filter/ccimpl/*.class"/>
+    		</fileset>
+    	</jar>        	
+    </target>
+	
+    <!-- ================================= 
+          target: test              
+         ================================= -->	
+	<target name="test" depends="build" description="--> compile and test the project">	
+		<mkdir dir="${report.test.dir}"/>
+		<mkdir dir="${build.test.dir}"/>
+		
+		<javac srcdir="${test.dir}" destdir="${build.test.dir}" classpathref="test.path.id"/>
+		<junit printsummary="yes" fork="yes" haltonfailure="yes" >
+			<classpath refid="test.path.id"/>	
+			<formatter type="plain"/>
+			<batchtest todir="${report.test.dir}" >
+			    <fileset dir="${build.test.dir}">
+			    	<include name="**/**Test.*"/>
+			    </fileset>	
+			</batchtest>
+		</junit>
+	</target>
+    <!-- ================================= 
+          target: publish              
+         ================================= -->	
+    <target name="publish" depends="test" description="--> compile test and publish this project in the local ivy repository">
+        <property name="revision" value="${revision}"/>
+		<ivy:publish artifactspattern="${distrib.dir}/[artifact].[ext]" 
+			           resolver="local"
+			           pubrevision="${revision}" 
+			           status="release"/>
+        <echo message="project ${ant.project.name} released with version ${revision}" />
+    </target>	
+    <!-- ================================= 
+          target: clean              
+         ================================= -->
+    <target name="clean" description="--> clean the project">
+        <delete includeemptydirs="true">
+            <fileset dir="${basedir}">
+            	<exclude name="src/**" />
+            	<exclude name="test/**" />
+            	<exclude name="build.xml" />
+                <exclude name="ivy.xml" />
+            	<exclude name=".*" />
+        	</fileset>
+    	</delete>
+    </target>
+	
+    <!-- ================================= 
+          target: clean-cache              
+         ================================= -->
+	<target name="clean-cache" description="--> clean the ivy cache">
+		<ivy:cleancache />
+	</target>
+
+    <!-- ================================= 
+      target: clean-local              
+     ================================= -->
+	<target name="clean-local" description="--> clean the local user repository">
+		<delete dir="${ivy.local.default.root}"/>
+	</target>
+
+	    
+    <!-- ================================= 
+          target: report              
+         ================================= -->
+    <target name="report" depends="resolve" description="--> generates a report of dependencies">
+        <ivy:report todir="${build.dir}"/>
+    </target>
+</project>
diff --git a/src/example/configurations/multi-projects/filter-framework/ivy.xml b/src/example/configurations/multi-projects/filter-framework/ivy.xml
new file mode 100644
index 0000000..952f577
--- /dev/null
+++ b/src/example/configurations/multi-projects/filter-framework/ivy.xml
@@ -0,0 +1,36 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT 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="org.apache" module="filter-framework"/>
+    <configurations>
+    	<conf name="api"  description="only provide filter framework API"/>
+    	<conf name="homemade-impl" extends="api" description="provide a home made implementation of our api"/>
+    	<conf name="cc-impl" extends="api" description="provide an implementation that use apache common collection framework"/>
+    	<conf name="test" extends="cc-impl" visibility="private" description="for testing our framework"/>
+    </configurations>
+    <publications>
+    	<artifact name="filter-api" type="jar"  conf="api" ext="jar"/>
+    	<artifact name="filter-hmimpl" type="jar"  conf="homemade-impl" ext="jar"/>
+    	<artifact name="filter-ccimpl" type="jar"  conf="cc-impl" ext="jar"/>    	
+    </publications>
+    <dependencies>
+        <dependency org="commons-collections" name="commons-collections" rev="3.1" conf="cc-impl->default"/>
+        <dependency org="junit" name="junit" rev="3.8" conf="test->default"/>
+    </dependencies>
+</ivy-module>
diff --git a/src/example/configurations/multi-projects/filter-framework/src/filter/FilterProvider.java b/src/example/configurations/multi-projects/filter-framework/src/filter/FilterProvider.java
new file mode 100644
index 0000000..2ee151a
--- /dev/null
+++ b/src/example/configurations/multi-projects/filter-framework/src/filter/FilterProvider.java
@@ -0,0 +1,40 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package filter;
+
+
+public final class FilterProvider {
+    
+    public static IFilter getFilter() {
+        try {
+            Class clazz = Class.forName("filter.ccimpl.CCFilter");
+            return (IFilter) clazz.newInstance();
+        } catch (Exception e) {
+            try {
+                Class clazz = Class.forName("filter.hmimpl.HMFilter");
+                return (IFilter) clazz.newInstance();
+            } catch (Exception e1) {
+                System.err.println("No filter implementation found in classpath !");
+            }
+            return null;
+        }
+    }
+    
+    private FilterProvider() {
+    }
+}
diff --git a/src/example/configurations/multi-projects/filter-framework/src/filter/IFilter.java b/src/example/configurations/multi-projects/filter-framework/src/filter/IFilter.java
new file mode 100644
index 0000000..7c5590e
--- /dev/null
+++ b/src/example/configurations/multi-projects/filter-framework/src/filter/IFilter.java
@@ -0,0 +1,22 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package filter;
+
+public interface IFilter {
+    String[] filter(String[] values, String prefix);
+}
diff --git a/src/example/configurations/multi-projects/filter-framework/src/filter/ccimpl/CCFilter.java b/src/example/configurations/multi-projects/filter-framework/src/filter/ccimpl/CCFilter.java
new file mode 100644
index 0000000..3808d20
--- /dev/null
+++ b/src/example/configurations/multi-projects/filter-framework/src/filter/ccimpl/CCFilter.java
@@ -0,0 +1,45 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package filter.ccimpl;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.collections.Predicate;
+import filter.IFilter;
+
+public class CCFilter implements IFilter {
+    
+    public String[] filter(String[] values, final String prefix) {
+        if (values == null) {
+            return null;
+        }
+        if (prefix == null) {
+            return values;
+        }
+
+        List result = new ArrayList(Arrays.asList(values));
+        CollectionUtils.filter(result, new Predicate() {
+            public boolean evaluate(Object o) {
+                return o != null && o.toString().startsWith(prefix);
+            }
+        });
+        return (String[]) result.toArray(new String[result.size()]);
+    }
+}
diff --git a/src/example/configurations/multi-projects/filter-framework/src/filter/hmimpl/HMFilter.java b/src/example/configurations/multi-projects/filter-framework/src/filter/hmimpl/HMFilter.java
new file mode 100644
index 0000000..d500809
--- /dev/null
+++ b/src/example/configurations/multi-projects/filter-framework/src/filter/hmimpl/HMFilter.java
@@ -0,0 +1,42 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package filter.hmimpl;
+
+import java.util.ArrayList;
+import java.util.List;
+import filter.IFilter;
+
+public class HMFilter implements IFilter {
+    
+    public String[] filter(String[] values, String prefix) {
+        if (values == null) {
+            return null;
+        }
+        if (prefix == null) {
+            return values;
+        }
+        List result = new ArrayList();
+        for (int i = 0; i < values.length; i++) {
+            String string = values[i];
+            if (string != null && string.startsWith(prefix)) {
+                result.add(string);
+            }
+        }
+        return (String[]) result.toArray(new String[result.size()]);
+    }
+}
diff --git a/src/example/configurations/multi-projects/myapp/build.xml b/src/example/configurations/multi-projects/myapp/build.xml
new file mode 100644
index 0000000..460cd43
--- /dev/null
+++ b/src/example/configurations/multi-projects/myapp/build.xml
@@ -0,0 +1,93 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<project name="myapp" default="run-cc" xmlns:ivy="antlib:org.apache.ivy.ant">
+    <!-- some variables used -->
+    <property name="lib.dir" value="lib" />
+    <property name="build.dir" value="build" />
+    <property name="src.dir" value="src" />
+    
+    <!-- paths used for compilation and run  -->
+    <path id="lib.path.id">
+        <fileset dir="${lib.dir}/build" />
+	</path>
+    <path id="run.hm.path.id">
+        <path location="${build.dir}" />
+    	<fileset dir="${lib.dir}/noexternaljar" />
+    </path>
+    <path id="run.cc.path.id">
+        <path location="${build.dir}" />
+    	<fileset dir="${lib.dir}/withexternaljar" />
+    </path>
+    <!-- ================================= 
+          target: resolve              
+         ================================= -->
+    <target name="resolve" description="--> retreive dependencies with ivy">
+    	<ivy:retrieve pattern="${ivy.lib.dir}/[conf]/[artifact].[ext]"/>
+    </target>    
+    
+    <!-- ================================= 
+          target: report              
+         ================================= -->
+    <target name="report" depends="resolve" description="--> generates a report of dependencies">
+        <ivy:report todir="${build.dir}"/>
+    </target>
+	
+	<!-- ================================= 
+          target: build
+         ================================= -->
+    <target name="build" depends="resolve" description="--> compile the project">
+        <mkdir dir="${build.dir}" />
+        <javac srcdir="${src.dir}" destdir="${build.dir}" classpathref="lib.path.id" includeAntRuntime="false"/>
+    </target>
+	
+	<!-- ================================= 
+          target: run with home made implementation
+         ================================= -->
+    <target name="run-hm" depends="build" description="--> run the project with ome made implementation">
+        <java classpathref="run.hm.path.id" classname="myapp.Main" fork="true"/>
+    </target>
+
+	<!-- ================================= 
+          target: run with ext lib implementation
+         ================================= -->
+    <target name="run-cc" depends="build" description="--> run the project with ext lib implementation">
+        <java classpathref="run.cc.path.id" classname="myapp.Main" fork="true"/>
+    </target>
+	
+    <!-- ================================= 
+          target: clean              
+         ================================= -->
+    <target name="clean" description="--> clean the project">
+        <delete includeemptydirs="true">
+            <fileset dir="${basedir}">
+            	<exclude name="src/**" />
+            	<exclude name="build.xml" />
+            	<exclude name="ivy.xml" />
+            	<exclude name=".*" />
+        	</fileset>
+    	</delete>
+    </target>
+	
+    <!-- ================================= 
+          target: clean-cache              
+         ================================= -->
+	<target name="clean-cache" description="--> clean the ivy cache">
+		<ivy:cleancache />
+	</target>
+</project>
diff --git a/src/example/configurations/multi-projects/myapp/ivy.xml b/src/example/configurations/multi-projects/myapp/ivy.xml
new file mode 100644
index 0000000..5890d33
--- /dev/null
+++ b/src/example/configurations/multi-projects/myapp/ivy.xml
@@ -0,0 +1,31 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT 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="org.apache" module="myapp"/>
+    
+    <configurations>
+       	<conf name="build" visibility="private" description="compilation only need api jar" />
+    	<conf name="noexternaljar" description="use only company jar" />
+    	<conf name="withexternaljar" description="use company jar and third party jars" />    
+    </configurations>
+    
+    <dependencies>
+        <dependency org="org.apache" name="filter-framework" rev="latest.integration" conf="build->api; noexternaljar->homemade-impl; withexternaljar->cc-impl"/>
+    </dependencies>
+</ivy-module>
diff --git a/src/example/configurations/multi-projects/myapp/src/myapp/Main.java b/src/example/configurations/multi-projects/myapp/src/myapp/Main.java
new file mode 100644
index 0000000..e5e2729
--- /dev/null
+++ b/src/example/configurations/multi-projects/myapp/src/myapp/Main.java
@@ -0,0 +1,36 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package myapp;
+
+import java.util.Arrays;
+import filter.FilterProvider;
+import filter.IFilter;
+
+public final class Main {
+    
+    public static void main(String[] args) {
+        String[] toFilter = new String[] {"one", "two", "tree", "four"};
+        IFilter filter = FilterProvider.getFilter();
+        System.out.println("Filtering with:" + filter.getClass());
+        String[] filtered = filter.filter(toFilter, "t");
+        System.out.println("Result :" + Arrays.asList(filtered));
+    }
+    
+    private Main() {
+    }
+}
diff --git a/src/example/dependence/build.xml b/src/example/dependence/build.xml
new file mode 100644
index 0000000..5c94a53
--- /dev/null
+++ b/src/example/dependence/build.xml
@@ -0,0 +1,42 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<project default="clean">
+    
+    <!-- ================================= 
+          target: clean              
+         ================================= -->
+    <target name="clean" description="--> clean directories">
+        <delete includeemptydirs="true"> 
+            <fileset dir="settings" excludes="ivysettings.*" />
+		</delete>
+        <ant dir="dependee" antfile="build.xml" target="clean" inheritall="false" inheritrefs="false" />
+        <ant dir="depender" antfile="build.xml" target="clean" inheritall="false" inheritrefs="false" />
+    </target>
+
+    <!-- ================================= 
+          target: all              
+         ================================= -->
+    <target name="all" depends="clean" description="--> make the whole example of dependency">
+        <ant dir="dependee" antfile="build.xml" target="publish" inheritall="false" inheritrefs="false" />
+        <ant dir="depender" antfile="build.xml" inheritall="false" inheritrefs="false" />
+        <ant dir="dependee" antfile="build.xml" target="publish" inheritall="false" inheritrefs="false" />
+        <ant dir="depender" antfile="build.xml" inheritall="false" inheritrefs="false" />
+    </target>
+    
+</project>
diff --git a/src/example/dependence/dependee/build.xml b/src/example/dependence/dependee/build.xml
new file mode 100644
index 0000000..d45c22d
--- /dev/null
+++ b/src/example/dependence/dependee/build.xml
@@ -0,0 +1,110 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<project name="dependee" default="run" xmlns:ivy="antlib:org.apache.ivy.ant">
+    <!-- some variables used -->
+    <property name="lib.dir" value="${basedir}/lib" />
+    <property name="build.dir" value="${basedir}/build" />
+    <property name="classes.dir" value="${build.dir}/classes" />
+    <property name="src.dir" value="${basedir}/src" />
+
+    <!-- ivy properties used -->
+    <property name="ivy.settings.dir" value="../settings" />
+    <property file="${ivy.settings.dir}/ivysettings.properties" />
+    
+    <!-- paths used for compilation and run  -->
+    <path id="lib.path.id">
+        <fileset dir="${lib.dir}" />
+	</path>
+    <path id="run.path.id">
+        <path refid="lib.path.id" />
+        <path location="${classes.dir}" />
+    </path>
+    
+    <ivy:settings file="${ivy.settings.dir}/ivysettings.xml" />
+    
+    <!-- ================================= 
+          target: resolve              
+         ================================= -->
+    <target name="resolve" description="--> resolve and retrieve dependencies with ivy">
+        <ivy:retrieve />
+    </target>
+        
+    <!-- ================================= 
+          target: report              
+         ================================= -->
+    <target name="report" depends="resolve" description="--> generates a report of dependencies">
+        <ivy:report todir="${build.dir}"/>
+    </target>
+	
+    <!-- ================================= 
+          target: compile              
+         ================================= -->
+    <target name="compile" depends="resolve" description="--> description">
+        <mkdir dir="${classes.dir}" />
+        <javac srcdir="${src.dir}" destdir="${classes.dir}" classpathref="lib.path.id" includeAntRuntime="false"/>
+    </target>
+    
+    <!-- ================================= 
+          target: run
+         ================================= -->
+    <target name="run" depends="compile" description="--> compile and run the project">
+        <java classpathref="run.path.id" classname="standalone.Main"/>
+    </target>
+    
+    <!-- ================================= 
+          target: jar              
+         ================================= -->
+    <target name="jar" depends="compile" description="--> make a jar file for this project">
+        <propertyfile file="${classes.dir}/version.properties">
+	        <entry  key="version" type="int" operation="+" default="0" />
+		</propertyfile>
+        <property file="${classes.dir}/version.properties" />
+        <jar destfile="${build.dir}/${ant.project.name}.jar">
+            <fileset dir="${classes.dir}" />
+        </jar>
+    </target>
+
+    <!-- ================================= 
+          target: publish              
+         ================================= -->
+    <target name="publish" depends="jar" description="--> publish this project in the ivy repository">
+        <property name="revision" value="${version}"/>
+        <delete file="${build.dir}/ivy.xml"/>
+		<ivy:publish artifactspattern="${build.dir}/[artifact].[ext]" 
+			resolver="projects"
+			pubrevision="${revision}" 
+			status="release"
+		/>
+        <echo message="project ${ant.project.name} released with version ${revision}" />
+    </target>
+
+
+    <!-- ================================= 
+          target: clean              
+         ================================= -->
+    <target name="clean" description="--> clean the project">
+        <delete includeemptydirs="true">
+            <fileset dir="${basedir}">
+            	<exclude name="src/**" />
+            	<exclude name="build.xml" />
+		    	<exclude name="ivy.xml" />
+        	</fileset>
+    	</delete>
+    </target>
+</project>
diff --git a/src/example/dependence/dependee/ivy.xml b/src/example/dependence/dependee/ivy.xml
new file mode 100644
index 0000000..c4196ff
--- /dev/null
+++ b/src/example/dependence/dependee/ivy.xml
@@ -0,0 +1,24 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT 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="org.apache" module="dependee"/>
+    <dependencies>
+        <dependency org="commons-lang" name="commons-lang" rev="2.0"/>
+    </dependencies>
+</ivy-module>
diff --git a/src/example/dependence/dependee/src/standalone/Main.java b/src/example/dependence/dependee/src/standalone/Main.java
new file mode 100644
index 0000000..ce28cb8
--- /dev/null
+++ b/src/example/dependence/dependee/src/standalone/Main.java
@@ -0,0 +1,64 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package standalone;
+
+import java.util.Properties;
+
+import org.apache.commons.lang.WordUtils;
+
+/**
+ * TODO write javadoc
+ */
+public final class Main {
+    /**
+     * Returns the version of the project
+     * @return a string representation of the version, null if the version could not be retreived
+     */
+    public static String getVersion() {
+        Properties p = new Properties();
+        try {
+            p.load(Main.class.getResourceAsStream("/version.properties"));
+            String version = p.getProperty("version");
+            if (version != null) {
+                return String.valueOf(Integer.parseInt(version));
+            } 
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+    
+    /**
+     * Return the same string with all words capitalized.
+     * @param str the string conatining the words to capitalize
+     * @return null if the string was null, the string with all words capitalized otherwise
+     */
+    public static String capitalizeWords(String str) {
+        System.out.println("    [" + Main.class.getName() + "] capitalizing string \"" 
+            + str + "\" using " + WordUtils.class.getName());
+        return WordUtils.capitalizeFully(str);
+    }
+    public static void main(String[] args) {
+        String message = "sentence to capitalize";
+        System.out.println("standard message : " + message);
+        System.out.println("capitalized message : " + capitalizeWords(message));
+    }
+    
+    private Main() {
+    }
+}
diff --git a/src/example/dependence/depender/build.xml b/src/example/dependence/depender/build.xml
new file mode 100644
index 0000000..2cf9249
--- /dev/null
+++ b/src/example/dependence/depender/build.xml
@@ -0,0 +1,93 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<project name="depender" default="run" xmlns:ivy="antlib:org.apache.ivy.ant">
+    <!-- some variables used -->
+    <property name="lib.dir" value="${basedir}/lib" />
+    <property name="build.dir" value="${basedir}/build" />
+    <property name="classes.dir" value="${build.dir}/classes" />
+    <property name="src.dir" value="${basedir}/src" />
+
+    <!-- ivy properties used -->
+    <property name="ivy.settings.dir" value="../settings" />
+    <property file="${ivy.settings.dir}/ivysettings.properties" />
+    
+    <!-- paths used for compilation and run  -->
+    <path id="lib.path.id">
+        <fileset dir="${lib.dir}" />
+	</path>
+    <path id="run.path.id">
+        <path refid="lib.path.id" />
+        <path location="${classes.dir}" />
+    </path>
+
+    <ivy:settings file="${ivy.settings.dir}/ivysettings.xml" />
+    
+    <!-- ================================= 
+          target: resolve              
+         ================================= -->
+    <target name="resolve" description="--> resolve and retrieve dependencies with ivy">
+        <ivy:retrieve />
+    </target>
+    
+    <!-- ================================= 
+          target: report              
+         ================================= -->
+    <target name="report" depends="resolve" description="--> generates a report of dependencies">
+        <ivy:report todir="${build.dir}" dot="true"/>
+    </target>
+    
+    <!-- ================================= 
+          target: gen-graph
+         ================================= -->
+    <target name="gen-graph" depends="report" description="--> generates a graph of dependencies (requires dot in your path - see http://www.graphviz.org/)">
+    	<property name="dot.file" value="${build.dir}/apache-depending-default.dot" />
+    	<property name="ivygraph.output.file" value="${build.dir}/graph.png" />
+    	<exec executable="dot">
+    	    <arg line="-T png -o ${ivygraph.output.file} ${dot.file}" />
+    	</exec>
+    </target>
+    
+    <!-- ================================= 
+          target: compile              
+         ================================= -->
+    <target name="compile" depends="resolve" description="--> description">
+        <mkdir dir="${classes.dir}" />
+        <javac srcdir="${src.dir}" destdir="${classes.dir}" classpathref="lib.path.id" includeAntRuntime="false"/>
+    </target>
+    
+    <!-- ================================= 
+          target: run
+         ================================= -->
+    <target name="run" depends="clean, compile" description="--> compile and run the project">
+        <java classpathref="run.path.id" classname="depending.Main"/>
+    </target>
+    
+    <!-- ================================= 
+          target: clean              
+         ================================= -->
+    <target name="clean" description="--> clean the project">
+        <delete includeemptydirs="true">
+            <fileset dir="${basedir}">
+            	<exclude name="src/**" />
+            	<exclude name="build.xml" />
+		    	<exclude name="ivy.xml" />
+        	</fileset>
+    	</delete>
+    </target>
+</project>
diff --git a/src/example/dependence/depender/ivy.xml b/src/example/dependence/depender/ivy.xml
new file mode 100644
index 0000000..1f77ce6
--- /dev/null
+++ b/src/example/dependence/depender/ivy.xml
@@ -0,0 +1,24 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT 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="org.apache" module="depender"/>
+    <dependencies>
+        <dependency name="dependee" rev="latest.integration" />
+    </dependencies>
+</ivy-module>
diff --git a/src/example/dependence/depender/src/depending/Main.java b/src/example/dependence/depender/src/depending/Main.java
new file mode 100644
index 0000000..2fa27da
--- /dev/null
+++ b/src/example/dependence/depender/src/depending/Main.java
@@ -0,0 +1,40 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package depending;
+
+/**
+ * TODO write javadoc
+ */
+public final class Main {
+    public static void main(String[] args) {
+        String standaloneVersion = standalone.Main.getVersion();
+        if (standaloneVersion != null) {
+            System.out.println("you are using version " + standaloneVersion 
+                + " of class " + standalone.Main.class.getName());
+        } else {
+            System.err.println("failed to get version of " + standalone.Main.class.getName());
+        }
+        String message = "i am " + Main.class.getName() 
+            + " and " + standalone.Main.class.getName() + " will do the job for me";
+        System.out.println("standard message : " + message);
+        System.out.println("capitalized message : " + standalone.Main.capitalizeWords(message));
+    }
+    
+    private Main() {
+    }
+}
diff --git a/src/example/dependence/settings/ivysettings.properties b/src/example/dependence/settings/ivysettings.properties
new file mode 100644
index 0000000..f68f415
--- /dev/null
+++ b/src/example/dependence/settings/ivysettings.properties
@@ -0,0 +1,19 @@
+#	 ***************************************************************
+#	 * Licensed to the Apache Software Foundation (ASF) under one
+#	 * or more contributor license agreements.  See the NOTICE file
+#	 * distributed with this work for additional information
+#	 * regarding copyright ownership.  The ASF licenses this file
+#	 * to you under the Apache License, Version 2.0 (the
+#	 * "License"); you may not use this file except in compliance
+#	 * with the License.  You may obtain a copy of the License at
+#	 * 
+#	 *   http://www.apache.org/licenses/LICENSE-2.0
+#	 * 
+#	 * Unless required by applicable law or agreed to in writing,
+#	 * software distributed under the License is distributed on an
+#	 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#	 * KIND, either express or implied.  See the License for the
+#	 * specific language governing permissions and limitations
+#	 * under the License.
+#	 ***************************************************************
+repository.dir=${ivy.settings.dir}/repository
diff --git a/src/example/dependence/settings/ivysettings.xml b/src/example/dependence/settings/ivysettings.xml
new file mode 100644
index 0000000..e06d437
--- /dev/null
+++ b/src/example/dependence/settings/ivysettings.xml
@@ -0,0 +1,33 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<ivysettings>
+	<properties file="${ivy.settings.dir}/ivysettings.properties"/>
+	<settings defaultResolver="libraries" />
+	<caches defaultCacheDir="${ivy.settings.dir}/ivy-cache" />
+	<resolvers>
+		<filesystem name="projects">
+			<artifact pattern="${repository.dir}/[artifact]-[revision].[ext]" />
+			<ivy pattern="${repository.dir}/[module]-[revision].xml" />
+		</filesystem>
+		<ibiblio name="libraries" m2compatible="true" usepoms="false" />
+	</resolvers>
+	<modules>
+		<module organisation="org.apache" name="dependee" resolver="projects"/>
+	</modules>
+</ivysettings>
diff --git a/src/example/dual/build.xml b/src/example/dual/build.xml
new file mode 100644
index 0000000..56f281f
--- /dev/null
+++ b/src/example/dual/build.xml
@@ -0,0 +1,38 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<project default="clean-all" xmlns:ivy="antlib:org.apache.ivy.ant">
+    
+    <!-- ================================= 
+          target: clean              
+         ================================= -->
+    <target name="clean" description="--> clean directories">
+        <ant dir="project" antfile="build.xml" target="clean" inheritall="false" inheritrefs="false" />
+    </target>
+	
+    <!-- ================================= 
+          target: clean-cache              
+         ================================= -->
+	<target name="clean-cache" description="--> clean the ivy cache">
+		<property name="ivy.settings.dir" value="settings" />
+		<ivy:settings file="${ivy.settings.dir}/ivysettings.xml" />
+		<ivy:cleancache />
+	</target>
+	
+	<target name="clean-all" depends="clean, clean-cache" description="--> clean directories and ivy cache"/>
+</project>
diff --git a/src/example/dual/project/build.xml b/src/example/dual/project/build.xml
new file mode 100644
index 0000000..bf550f3
--- /dev/null
+++ b/src/example/dual/project/build.xml
@@ -0,0 +1,81 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<project name="dual-ivy" default="run" xmlns:ivy="antlib:org.apache.ivy.ant">
+    <!-- some variables used -->
+    <property name="lib.dir" value="${basedir}/lib" />
+    <property name="build.dir" value="${basedir}/build" />
+    <property name="src.dir" value="${basedir}/src" />
+    
+    <!-- ivy properties used -->
+    <property name="ivy.settings.dir" value="../settings" />
+
+	<!-- paths used for compilation and run  -->
+    <path id="lib.path.id">
+        <fileset dir="${lib.dir}" />
+	</path>
+    <path id="run.path.id">
+        <path refid="lib.path.id" />
+        <path location="${build.dir}" />
+    </path>
+    
+    <ivy:settings file="${ivy.settings.dir}/ivysettings.xml" />
+    
+    <!-- ================================= 
+          target: resolve              
+         ================================= -->
+    <target name="resolve" description="--> resolve and retrieve dependencies with ivy">
+        <ivy:retrieve />
+    </target>
+    
+    <!-- ================================= 
+          target: report              
+         ================================= -->
+    <target name="report" depends="resolve" description="--> generates a report of dependencies">
+        <ivy:report todir="${build.dir}"/>
+    </target>
+    
+    <!-- ================================= 
+          target: run
+         ================================= -->
+    <target name="run" depends="resolve" description="--> compile and run the project">
+        <mkdir dir="${build.dir}" />
+        <javac srcdir="${src.dir}" destdir="${build.dir}" classpathref="lib.path.id" includeAntRuntime="false"/>
+        <java classpathref="run.path.id" classname="example.Hello"/>
+    </target>
+    
+    <!-- ================================= 
+          target: clean              
+         ================================= -->
+    <target name="clean" description="--> clean the project">
+        <delete includeemptydirs="true">
+            <fileset dir="${basedir}">
+            	<exclude name="src/**" />
+            	<exclude name="build.xml" />
+		    	<exclude name="ivy.xml" />
+        	</fileset>
+    	</delete>
+    </target>
+	
+    <!-- ================================= 
+          target: clean-cache              
+         ================================= -->
+	<target name="clean-cache" description="--> clean the ivy cache">
+		<ivy:cleancache />
+	</target>
+</project>
diff --git a/src/example/dual/project/ivy.xml b/src/example/dual/project/ivy.xml
new file mode 100644
index 0000000..02d8bab
--- /dev/null
+++ b/src/example/dual/project/ivy.xml
@@ -0,0 +1,25 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT 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="org.apache" module="hello-ivy"/>
+    <dependencies>
+        <dependency org="commons-httpclient" name="commons-httpclient" rev="2.0.2"/>
+        <dependency org="commons-lang" name="commons-lang" rev="2.0"/>
+    </dependencies>
+</ivy-module>
diff --git a/src/example/dual/project/src/example/Hello.java b/src/example/dual/project/src/example/Hello.java
new file mode 100644
index 0000000..30e6bf9
--- /dev/null
+++ b/src/example/dual/project/src/example/Hello.java
@@ -0,0 +1,51 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package example;
+
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.methods.HeadMethod;
+import org.apache.commons.lang.WordUtils;
+
+/**
+ * Simple hello world example to show how easy it is to retrieve libs with ivy, 
+ * including transitive dependencies 
+ */
+public final class Hello {
+    public static void main(String[] args) throws Exception {
+        String  message = "hello ivy !";
+        System.out.println("standard message : " + message);
+        System.out.println("capitalized by " + WordUtils.class.getName() 
+            + " : " + WordUtils.capitalizeFully(message));
+        
+        HttpClient client = new HttpClient();
+        HeadMethod head = new HeadMethod("http://www.ibiblio.org/");
+        client.executeMethod(head);
+        
+        int status = head.getStatusCode();
+        System.out.println("head status code with httpclient: " + status);
+        head.releaseConnection();
+        
+        System.out.println(
+            "now check if httpclient dependency on commons-logging has been realized");
+        Class clss = Class.forName("org.apache.commons.logging.Log");
+        System.out.println("found logging class in classpath: " + clss);
+    }
+    
+    private Hello() {
+    }
+}
diff --git a/src/example/dual/repository/commons-httpclient-ivy-2.0.2.xml b/src/example/dual/repository/commons-httpclient-ivy-2.0.2.xml
new file mode 100644
index 0000000..0b2f32c
--- /dev/null
+++ b/src/example/dual/repository/commons-httpclient-ivy-2.0.2.xml
@@ -0,0 +1,29 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT 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="commons-httpclient"
+        module="commons-httpclient"
+        revision="2.0.2"
+        status="release"
+        publication="20041010174300"/>
+    <dependencies>
+        <dependency org="commons-logging" name="commons-logging" rev="1.0.4" conf="default"/>
+    </dependencies>
+</ivy-module>
diff --git a/src/example/dual/settings/ivysettings.xml b/src/example/dual/settings/ivysettings.xml
new file mode 100644
index 0000000..9d48a8a
--- /dev/null
+++ b/src/example/dual/settings/ivysettings.xml
@@ -0,0 +1,29 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<ivysettings>
+  <settings defaultResolver="dual-example"/>
+  <resolvers>
+    <dual name="dual-example">
+      <filesystem name="ivys">
+        <ivy pattern="${ivy.settings.dir}/../repository/[module]-ivy-[revision].xml" />
+      </filesystem>
+      <ibiblio name="ibiblio" m2compatible="true" usepoms="false" />
+    </dual>
+  </resolvers>
+</ivysettings>
diff --git a/src/example/go-ivy/build.xml b/src/example/go-ivy/build.xml
new file mode 100644
index 0000000..d92b11f
--- /dev/null
+++ b/src/example/go-ivy/build.xml
@@ -0,0 +1,151 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<project name="go-ivy" default="go" xmlns:ivy="antlib:org.apache.ivy.ant">
+    <!-- 
+        this build file is a self contained project: it doesn't require anything else 
+        that ant 1.6.2 or greater and java 1.4 or greater properly installed.
+        
+        It is used to showcase how easy and straightforward it can be to use Ivy.
+        
+        This is not an example of the best pratice to use in a project, especially
+        for the java source code "generation" :-) (see generate-src target)
+        
+        To run copy this file in an empty directory, open a shell or a command window
+        in this directory and run "ant". It will download ivy and then use it to resolve 
+        the dependency of the class which is itself "contained" in this build script.
+        
+        After a successful build run "ant" again and you will see the build will be
+        much faster.
+        
+        More information can be found at http://ant.apache.org/ivy/
+        -->
+	
+	<!-- here is the version of ivy we will use. change this property to try a newer 
+         version if you want -->
+	<property name="ivy.install.version" value="2.0.0-beta1" />
+	<property name="ivy.jar.dir" value="${basedir}/ivy" />
+	<property name="ivy.jar.file" value="${ivy.jar.dir}/ivy.jar" />
+
+    <property name="build.dir" value="build" />
+    <property name="src.dir" value="src" />
+    
+    
+    <target name="download-ivy" unless="skip.download">
+    	<mkdir dir="${ivy.jar.dir}"/>
+		<!-- download Ivy from web site so that it can be used even without any special installation -->
+		<echo message="installing ivy..."/>
+    	<get src="https://repo1.maven.org/maven2/org/apache/ivy/ivy/${ivy.install.version}/ivy-${ivy.install.version}.jar"
+    		 dest="${ivy.jar.file}" usetimestamp="true"/>
+    </target>
+    
+    <!-- ================================= 
+          target: install-ivy         
+            this target is not necessary if you put ivy.jar in your ant lib directory
+            if you already have ivy in your ant lib, you can simply remove this
+            target and the dependency the 'go' target has on it
+         ================================= -->
+    <target name="install-ivy" depends="download-ivy" description="--> install ivy">
+    	<!-- try to load ivy here from local ivy dir, in case the user has not already dropped
+    	      it into ant's lib dir (note that the latter copy will always take precedence).
+    	      We will not fail as long as the ivy jar is in at least one of ant's lib dir or 
+    	      the local lib dir. -->
+    	<path id="ivy.lib.path">
+    	    <pathelement location="${ivy.jar.file}"/>
+    	</path>
+    	<taskdef resource="org/apache/ivy/ant/antlib.xml"
+    	          uri="antlib:org.apache.ivy.ant" classpathref="ivy.lib.path"/>
+    </target>    
+	
+	<!-- ================================= 
+          target: go
+                    Go ivy, go!
+         ================================= -->
+    <target name="go" depends="install-ivy, generate-src" 
+            description="--> resolve dependencies, compile and run the project">
+		<echo message="using ivy to resolve commons-lang 2.1..."/>
+        <!-- here comes the magic line: asks ivy to resolve a dependency on 
+             commons-lang 2.1 and to build an ant path with it from its cache  -->
+        <ivy:cachepath organisation="commons-lang" module="commons-lang" revision="2.1" 
+                       pathid="lib.path.id" inline="true"/>
+        
+		<echo message="compiling..."/>
+        <mkdir dir="${build.dir}" />
+        <javac srcdir="${src.dir}" destdir="${build.dir}" classpathref="lib.path.id" includeAntRuntime="false"/>
+        
+		<echo>
+We are now ready to execute our simple program with its dependency on commons-lang.
+Let's go!
+		</echo>
+        <java classname="example.Hello">
+            <classpath>
+                <path refid="lib.path.id" />
+                <path location="${build.dir}" />
+            </classpath>
+        </java>
+    </target>
+
+	<!-- ================================= 
+          target: generate-src
+            'Generates' the class source. It actually just echo a simple java 
+            source code to a file. In real life this file would already be
+            present on your file system, and this target wouldn't be necessary.
+         ================================= -->
+    <target name="generate-src">
+        <mkdir dir="${src.dir}/example" />
+        <echo file="${src.dir}/example/Hello.java">
+package example;
+
+import org.apache.commons.lang.WordUtils;
+
+public class Hello {
+    public static void main(String[] args) {
+        String  message = "hello ivy !";
+        System.out.println("standard message : " + message);
+        System.out.println("capitalized by " + WordUtils.class.getName() 
+                                + " : " + WordUtils.capitalizeFully(message));
+    }
+}
+        </echo>
+    </target>
+	
+    <!-- ================================= 
+          target: clean              
+         ================================= -->
+    <target name="clean" description="--> clean the project">
+        <delete includeemptydirs="true" quiet="true">
+            <fileset dir="${src.dir}" />
+            <fileset dir="${build.dir}" />
+    	</delete>
+    </target>
+	
+    <!-- ================================= 
+          target: clean-ivy              
+         ================================= -->
+	<target name="clean-ivy" description="--> clean the ivy installation">
+		<delete dir="${ivy.jar.dir}"/>
+	</target>
+	
+    <!-- ================================= 
+          target: clean-cache              
+         ================================= -->
+	<target name="clean-cache" depends="install-ivy" 
+			description="--> clean the ivy cache">
+		<ivy:cleancache />
+	</target>
+</project>
diff --git a/src/example/hello-ivy/build.xml b/src/example/hello-ivy/build.xml
new file mode 100644
index 0000000..1e94ea4
--- /dev/null
+++ b/src/example/hello-ivy/build.xml
@@ -0,0 +1,80 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<project name="hello-ivy" default="run" xmlns:ivy="antlib:org.apache.ivy.ant">
+    <!-- some variables used -->
+    <property name="lib.dir" value="lib" />
+    <property name="build.dir" value="build" />
+    <property name="src.dir" value="src" />
+    
+    <!-- paths used for compilation and run  -->
+    <path id="lib.path.id">
+        <fileset dir="${lib.dir}" />
+	</path>
+    <path id="run.path.id">
+        <path refid="lib.path.id" />
+        <path location="${build.dir}" />
+    </path>
+    
+    <!-- ================================= 
+          target: resolve              
+         ================================= -->
+    <target name="resolve" description="--> retreive dependencies with ivy">
+        <ivy:retrieve/>
+    </target>    
+    
+    <!-- ================================= 
+          target: report              
+         ================================= -->
+    <target name="report" depends="resolve" description="--> generates a report of dependencies">
+        <ivy:report todir="${build.dir}"/>
+    </target>
+
+    <!-- ================================= 
+          target: run
+         ================================= -->
+    <target name="run" depends="resolve" description="--> compile and run the project">
+        <mkdir dir="${build.dir}" />
+        <javac srcdir="${src.dir}" destdir="${build.dir}" classpathref="lib.path.id" includeAntRuntime="false"/>
+    	<property name="msg" value="hello ivy !"/>
+        <java classpathref="run.path.id" classname="example.Hello">
+        	<arg value="-message"/>
+        	<arg value="${msg}"/>
+    	</java>
+    </target>
+
+    <!-- ================================= 
+          target: clean              
+         ================================= -->
+    <target name="clean" description="--> clean the project">
+        <delete includeemptydirs="true">
+            <fileset dir="${basedir}">
+            	<exclude name="src/**" />
+            	<exclude name="build.xml" />
+              <exclude name="ivy.xml" />
+        	</fileset>
+    	</delete>
+    </target>
+	
+    <!-- ================================= 
+          target: clean-cache              
+         ================================= -->
+	<target name="clean-cache" description="--> clean the ivy cache">
+		<ivy:cleancache />
+	</target>
+</project>
diff --git a/src/example/hello-ivy/ivy.xml b/src/example/hello-ivy/ivy.xml
new file mode 100644
index 0000000..da3870e
--- /dev/null
+++ b/src/example/hello-ivy/ivy.xml
@@ -0,0 +1,25 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT 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="2.0">
+    <info organisation="org.apache" module="hello-ivy"/>
+    <dependencies>
+        <dependency org="commons-lang" name="commons-lang" rev="2.0"/>
+        <dependency org="commons-cli" name="commons-cli" rev="1.0"/>
+    </dependencies>
+</ivy-module>
diff --git a/src/example/hello-ivy/src/example/Hello.java b/src/example/hello-ivy/src/example/Hello.java
new file mode 100644
index 0000000..55bff13
--- /dev/null
+++ b/src/example/hello-ivy/src/example/Hello.java
@@ -0,0 +1,51 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package example;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.GnuParser;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.OptionBuilder;
+import org.apache.commons.cli.Options;
+import org.apache.commons.lang.WordUtils;
+
+/**
+ * Simple example to show how easy it is to retrieve transitive libs with ivy !!! 
+ */
+public final class Hello {
+    public static void main(String[] args) throws Exception {
+        Option msg = OptionBuilder.withArgName("msg")
+            .hasArg()
+            .withDescription("the message to capitalize")
+            .create("message");
+        Options options = new Options();
+        options.addOption(msg);
+        
+        CommandLineParser parser = new GnuParser();
+        CommandLine line = parser.parse(options, args);
+        
+        String  message = line.getOptionValue("message", "hello ivy !");
+        System.out.println("standard message : " + message);
+        System.out.println("capitalized by " + WordUtils.class.getName() 
+            + " : " + WordUtils.capitalizeFully(message));
+    }
+    
+    private Hello() {
+    }
+}
diff --git a/src/example/multi-project/build.xml b/src/example/multi-project/build.xml
new file mode 100644
index 0000000..200a439
--- /dev/null
+++ b/src/example/multi-project/build.xml
@@ -0,0 +1,69 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<project name="all" 
+         xmlns:ivy="antlib:org.apache.ivy.ant">
+         
+	<property name="ivy.jar.dir" value="${user.home}/.ivy2/jars" />
+	<property name="ivy.jar.file" value="${ivy.jar.dir}/ivy.jar" />
+
+    <property name="build.dir" value="build" />
+    <property name="src.dir" value="src" />
+    
+    
+	<!-- ================================= 
+          target: load-ivy         
+            this target is not necessary if you put ivy.jar in your ant lib directory
+            if you already have ivy 1.4 in your ant lib, you can simply remove this
+            target
+         ================================= -->
+    <target name="load-ivy">
+    	<!-- try to load ivy here from home ivy dir, in case the user has not already dropped
+    	      it into ant's lib dir (note that the latter copy will always take precedence).
+    	      We will not fail as long as ivy home lib dir exists (it may be empty) and
+    	      ivy is in at least one of ant's lib dir or the ivy home lib dir. -->
+    	<path id="ivy.lib.path">
+    		<pathelement location="${ivy.jar.file}"/>
+    	</path>
+    	<taskdef resource="org/apache/ivy/ant/antlib.xml"
+    	          uri="antlib:org.apache.ivy.ant" classpathref="ivy.lib.path"/>
+    </target>    
+         
+  <target name="buildlist" depends="load-ivy"> 
+    <ivy:buildlist reference="build-path">
+      <fileset dir="projects" includes="**/build.xml"/>
+    </ivy:buildlist>
+  </target>
+  
+  <target name="publish-all" depends="buildlist" 
+  			description="compile, jar and publish all projects in the right order">
+    <subant target="publish" buildpathref="build-path" />
+  </target>
+
+  <target name="clean-all" depends="buildlist" description="clean all projects">
+    <subant target="clean" buildpathref="build-path" />
+  </target>
+  
+  <target name="clean" depends="clean-all, load-ivy" 
+  			description="clean tutorial: delete repository, ivy cache, and all projects">
+    <delete dir="repository"/>
+  	<ivy:cleancache />
+  </target>
+  
+  
+</project>
diff --git a/src/example/multi-project/common/build.properties b/src/example/multi-project/common/build.properties
new file mode 100644
index 0000000..9552b4a
--- /dev/null
+++ b/src/example/multi-project/common/build.properties
@@ -0,0 +1,30 @@
+#	 ***************************************************************
+#	 * Licensed to the Apache Software Foundation (ASF) under one
+#	 * or more contributor license agreements.  See the NOTICE file
+#	 * distributed with this work for additional information
+#	 * regarding copyright ownership.  The ASF licenses this file
+#	 * to you under the Apache License, Version 2.0 (the
+#	 * "License"); you may not use this file except in compliance
+#	 * with the License.  You may obtain a copy of the License at
+#	 * 
+#	 *   http://www.apache.org/licenses/LICENSE-2.0
+#	 * 
+#	 * Unless required by applicable law or agreed to in writing,
+#	 * software distributed under the License is distributed on an
+#	 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#	 * KIND, either express or implied.  See the License for the
+#	 * specific language governing permissions and limitations
+#	 * under the License.
+#	 ***************************************************************
+lib.dir = ${basedir}/lib
+build.dir = ${basedir}/build
+classes.dir = ${build.dir}/classes
+src.dir = ${basedir}/src
+repository.dir=${common.dir}/../repository
+
+ivy.file = ${basedir}/ivy.xml
+
+jar.file = ${build.dir}/${ant.project.name}.jar
+main.class.name = ${ant.project.name}.Main
+
+module.version.target = 1.0
diff --git a/src/example/multi-project/common/common.xml b/src/example/multi-project/common/common.xml
new file mode 100644
index 0000000..89cd508
--- /dev/null
+++ b/src/example/multi-project/common/common.xml
@@ -0,0 +1,205 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<project name="common" 
+         xmlns:ivy="antlib:org.apache.ivy.ant">
+	<!-- a sample common ant build file, used for ivy multi-project tutorial
+	     feel free to copy and adapt it to your own needs
+	     Note that the only targets specific to ivy are:
+	        load-ivy
+	     	resolve
+	     	report
+	     	ivy-new-version
+	     	publish
+	     	publish-local
+	     	
+	     All other targets are usual ant based targets, which could have been written
+	     in a build not depending at all on ivy: 
+	     resolve constructs a lib directory based upon ivy dependencies, and then the lib dir 
+	     is used as in any classical ant build
+	     -->
+	
+	<property file="${common.dir}/build.properties"/>
+	
+	<property name="ivy.jar.dir" value="${user.home}/.ivy2/jars" />
+	<property name="ivy.jar.file" value="${ivy.jar.dir}/ivy.jar" />
+
+	<!-- ================================= 
+          target: load-ivy         
+            this target is not necessary if you put ivy.jar in your ant lib directory
+            if you already have ivy 2.0 in your ant lib, you can simply remove this
+            target
+         ================================= -->
+    <target name="load-ivy">
+    	<!-- try to load ivy here from home ivy dir, in case the user has not already dropped
+    	      it into ant's lib dir (note that the latter copy will always take precedence).
+    	      We will not fail as long as ivy home lib dir exists (it may be empty) and
+    	      ivy is in at least one of ant's lib dir or the ivy home lib dir. -->
+    	<mkdir dir="${ivy.jar.dir}" />
+    	<path id="ivy.lib.path">
+    	    <fileset dir="${ivy.jar.dir}" includes="*.jar"/>
+    	</path>
+    	<taskdef resource="org/apache/ivy/ant/antlib.xml"
+    	          uri="antlib:org.apache.ivy.ant" classpathref="ivy.lib.path"/>
+    </target>    
+
+    <path id="lib.path.id">
+        <fileset dir="${lib.dir}" />
+    </path>
+    <path id="run.path.id">
+        <path refid="lib.path.id" />
+        <path location="${classes.dir}" />
+    </path>
+
+    
+	<!-- setup ivy default configuration with some custom info -->
+	<property name="ivy.local.default.root" value="${repository.dir}/local"/>
+	<property name="ivy.shared.default.root" value="${repository.dir}/shared"/>
+
+   	<!-- here is how we would have configured ivy if we had our own ivysettings file
+    <ivy:settings file="${common.dir}/ivysettings.xml" />
+    -->
+
+	
+    <!-- ================================= 
+          target: resolve              
+         ================================= -->
+    <target name="resolve" depends="clean-lib, load-ivy" description="--> resolve and retrieve dependencies with ivy">
+        <mkdir dir="${lib.dir}"/> <!-- not usually necessary, ivy creates the directory IF there are dependencies -->
+    	
+    	<!-- the call to resolve is not mandatory, retrieve makes an implicit call if we don't -->
+    	<ivy:resolve file="${ivy.file}"/>
+    	<ivy:retrieve pattern="${lib.dir}/[artifact].[ext]" />
+    </target>
+    
+    <!-- ================================= 
+          target: report              
+         ================================= -->
+    <target name="report" depends="resolve" description="--> generates a report of dependencies">
+        <ivy:report todir="${build.dir}"/>
+    </target>
+    
+    <!-- ================================= 
+          target: compile              
+         ================================= -->
+    <target name="compile" depends="resolve" description="--> compile the project">
+        <mkdir dir="${classes.dir}" />
+        <javac srcdir="${src.dir}" destdir="${classes.dir}" classpathref="lib.path.id" debug="true" includeAntRuntime="false"/>
+    </target>
+    
+    <!-- ================================= 
+          target: run
+         ================================= -->
+    <target name="run" depends="version, compile" description="--> compile and run the project">
+        <java classpathref="run.path.id" classname="${main.class.name}"/>
+    </target>
+
+	<target name="ivy-new-version" depends="load-ivy" unless="ivy.new.revision">
+    	<!-- default module version prefix value -->
+		<property name="module.version.prefix" value="${module.version.target}-dev-b" />
+		
+    	<!-- asks to ivy an available version number -->
+		<ivy:info file="${ivy.file}" />
+    	<ivy:buildnumber 
+    		organisation="${ivy.organisation}" module="${ivy.module}" 
+    		revision="${module.version.prefix}" defaultBuildNumber="1" revSep=""/>
+	</target>
+
+    <target name="local-version">
+		<tstamp>
+			<format property="now" pattern="yyyyMMddHHmmss"/>
+		</tstamp>
+        <property name="ivy.new.revision" value="${module.version.target}-local-${now}"/>
+    </target>
+	
+	<target name="version" depends="ivy-new-version">
+    	<!-- create version file in classpath for later inclusion in jar -->
+        <mkdir dir="${classes.dir}"/>
+		<echo message="version=${ivy.new.revision}" file="${classes.dir}/${ant.project.name}.properties" append="false" />
+
+		<!-- load generated version properties file -->
+        <property file="${classes.dir}/${ant.project.name}.properties" />
+    </target>
+
+    <!-- ================================= 
+          target: jar              
+         ================================= -->
+    <target name="jar" depends="version, compile" description="--> make a jar file for this project">
+        <jar destfile="${jar.file}">
+            <fileset dir="${classes.dir}" />
+            <manifest>
+                <attribute name="Built-By" value="${user.name}"/>
+                <attribute name="Build-Version" value="${version}" />
+            </manifest>
+        </jar>
+    </target>
+
+    <!-- ================================= 
+          target: publish              
+         ================================= -->
+    <target name="publish" depends="clean-build, jar" description="--> publish this project in the ivy repository">
+    	<ivy:publish artifactspattern="${build.dir}/[artifact].[ext]" 
+    			           resolver="shared"
+    			           pubrevision="${version}" 
+    			           status="release"
+    	/>
+        <echo message="project ${ant.project.name} released with version ${version}" />
+    </target>
+
+    <!-- ================================= 
+          target: publish-local              
+         ================================= -->
+    <target name="publish-local" depends="local-version, jar" description="--> publish this project in the local ivy repository">
+    	<ivy:publish artifactspattern="${build.dir}/[artifact].[ext]" 
+    			        resolver="local"
+    			        pubrevision="${version}"
+				        pubdate="${now}"
+    			        status="integration"
+    					forcedeliver="true"
+    	/>
+        <echo message="project ${ant.project.name} published locally with version ${version}" />
+    </target>
+
+	<!-- ================================= 
+          target: clean-local              
+         ================================= -->
+	<target name="clean-local" depends="load-ivy" 
+		description="--> cleans the local repository for the current module">
+		<ivy:info file="${ivy.file}" />
+	   <delete dir="${ivy.local.default.root}/${ivy.organisation}/${ivy.module}"/>
+	</target>
+
+	<!-- ================================= 
+          target: clean-lib              
+         ================================= -->
+    <target name="clean-lib" description="--> clean the project libraries directory (dependencies)">
+        <delete includeemptydirs="true" dir="${lib.dir}"/>
+    </target>
+
+    <!-- ================================= 
+          target: clean-build              
+         ================================= -->
+    <target name="clean-build" description="--> clean the project built files">
+        <delete includeemptydirs="true" dir="${build.dir}"/>
+    </target>
+
+    <!-- ================================= 
+          target: clean              
+         ================================= -->
+    <target name="clean" depends="clean-build, clean-lib" description="--> clean the project" />
+</project>
diff --git a/src/example/multi-project/projects/console/build.properties b/src/example/multi-project/projects/console/build.properties
new file mode 100644
index 0000000..1b54925
--- /dev/null
+++ b/src/example/multi-project/projects/console/build.properties
@@ -0,0 +1,21 @@
+#	 ***************************************************************
+#	 * Licensed to the Apache Software Foundation (ASF) under one
+#	 * or more contributor license agreements.  See the NOTICE file
+#	 * distributed with this work for additional information
+#	 * regarding copyright ownership.  The ASF licenses this file
+#	 * to you under the Apache License, Version 2.0 (the
+#	 * "License"); you may not use this file except in compliance
+#	 * with the License.  You may obtain a copy of the License at
+#	 * 
+#	 *   http://www.apache.org/licenses/LICENSE-2.0
+#	 * 
+#	 * Unless required by applicable law or agreed to in writing,
+#	 * software distributed under the License is distributed on an
+#	 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#	 * KIND, either express or implied.  See the License for the
+#	 * specific language governing permissions and limitations
+#	 * under the License.
+#	 ***************************************************************
+projects.dir = ${basedir}/..
+wkspace.dir = ${projects.dir}/..
+common.dir = ${wkspace.dir}/common
diff --git a/src/example/multi-project/projects/console/build.xml b/src/example/multi-project/projects/console/build.xml
new file mode 100644
index 0000000..fa7d93c
--- /dev/null
+++ b/src/example/multi-project/projects/console/build.xml
@@ -0,0 +1,23 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<project name="console" default="compile">
+	<property file="build.properties"/>
+	
+	<import file="${common.dir}/common.xml"/>
+</project>
diff --git a/src/example/multi-project/projects/console/ivy.xml b/src/example/multi-project/projects/console/ivy.xml
new file mode 100644
index 0000000..959a58b
--- /dev/null
+++ b/src/example/multi-project/projects/console/ivy.xml
@@ -0,0 +1,30 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT 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="org.apache.ivy.example"
+        module="console"
+        status="integration"/>
+    <dependencies>
+      <dependency name="version" rev="latest.integration" conf="default" />
+      <dependency name="list" rev="latest.integration" conf="default->standalone" />
+      <dependency name="find" rev="latest.integration" conf="default->standalone" />
+      <dependency name="sizewhere" rev="latest.integration" conf="default->standalone" />
+    </dependencies>
+</ivy-module>
diff --git a/src/example/multi-project/projects/console/src/console/Main.java b/src/example/multi-project/projects/console/src/console/Main.java
new file mode 100644
index 0000000..1a328b1
--- /dev/null
+++ b/src/example/multi-project/projects/console/src/console/Main.java
@@ -0,0 +1,82 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package console;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.util.Collection;
+import java.util.Arrays;
+import java.lang.reflect.Method;
+
+
+public final class Main {
+    private static final Collection QUIT_COMMANDS = 
+        Arrays.asList(new String[] {"quit", "q", "exit"});
+    private static final Collection HELP_COMMANDS = 
+        Arrays.asList(new String[] {"help", "h", "?"});
+
+    public static void main(String[] a) throws Exception {
+      BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
+      while (true) {
+        System.out.print("> ");
+        String command = in.readLine().trim();
+        if (QUIT_COMMANDS.contains(command)) {
+          return;
+        }
+        if (HELP_COMMANDS.contains(command)) {
+          help();
+          continue;
+        }
+        String[] split = command.split(" ");
+        if (split.length == 0) {
+          error(command);
+          continue;
+        }
+        
+        try {
+          String[] args = new String[split.length - 1];
+          System.arraycopy(split, 1, args, 0, args.length);
+          Class cl = Class.forName(split[0] + ".Main");
+          Method m = cl.getMethod("main", new Class[] {String[].class});
+          m.invoke(null, new Object[] {args});
+        } catch (Exception ex) {
+          error(command);
+          continue;
+        }
+      }
+    }
+    
+    private static void help() {
+      System.out.println("available commands:");
+      System.out.println("\tquit: quit the console");
+      System.out.println("\thelp: displays this message");
+      System.out.println("\tlist -dir <dir>: list files in given directory");
+      System.out.println("\tfind -dir <dir> -name <name>: list files with given name in given dir");
+      System.out.println("\tsizewhere -dir <dir> -name <name>: "
+           + "compute total size of files with given name in given dir");
+      System.out.println("\thelp: displays this message");
+    }
+            
+    private static void error(String command) {
+      System.out.println("unknown command " + command);
+      System.out.println("type ? for help");
+    }
+
+    private Main() {
+    }
+}
diff --git a/src/example/multi-project/projects/find/build.properties b/src/example/multi-project/projects/find/build.properties
new file mode 100644
index 0000000..1b54925
--- /dev/null
+++ b/src/example/multi-project/projects/find/build.properties
@@ -0,0 +1,21 @@
+#	 ***************************************************************
+#	 * Licensed to the Apache Software Foundation (ASF) under one
+#	 * or more contributor license agreements.  See the NOTICE file
+#	 * distributed with this work for additional information
+#	 * regarding copyright ownership.  The ASF licenses this file
+#	 * to you under the Apache License, Version 2.0 (the
+#	 * "License"); you may not use this file except in compliance
+#	 * with the License.  You may obtain a copy of the License at
+#	 * 
+#	 *   http://www.apache.org/licenses/LICENSE-2.0
+#	 * 
+#	 * Unless required by applicable law or agreed to in writing,
+#	 * software distributed under the License is distributed on an
+#	 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#	 * KIND, either express or implied.  See the License for the
+#	 * specific language governing permissions and limitations
+#	 * under the License.
+#	 ***************************************************************
+projects.dir = ${basedir}/..
+wkspace.dir = ${projects.dir}/..
+common.dir = ${wkspace.dir}/common
diff --git a/src/example/multi-project/projects/find/build.xml b/src/example/multi-project/projects/find/build.xml
new file mode 100644
index 0000000..270e2cf
--- /dev/null
+++ b/src/example/multi-project/projects/find/build.xml
@@ -0,0 +1,23 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<project name="find" default="compile">
+	<property file="build.properties"/>
+	
+	<import file="${common.dir}/common.xml"/>
+</project>
diff --git a/src/example/multi-project/projects/find/ivy.xml b/src/example/multi-project/projects/find/ivy.xml
new file mode 100644
index 0000000..ce19513
--- /dev/null
+++ b/src/example/multi-project/projects/find/ivy.xml
@@ -0,0 +1,37 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT 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="org.apache.ivy.example"
+        module="find"
+        status="integration"/>
+    <configurations>
+      <conf name="core"/>
+      <conf name="standalone" extends="core"/>
+    </configurations>
+    <publications>
+      <artifact name="find" type="jar" conf="core" />
+    </publications>
+    <dependencies>
+      <dependency name="version" rev="latest.integration" conf="core->default" />
+      <dependency name="list" rev="latest.integration" conf="core" />
+      <dependency org="commons-collections" name="commons-collections" rev="3.1" conf="core->default" />
+      <dependency org="commons-cli" name="commons-cli" rev="1.0" conf="standalone->default" />
+    </dependencies>
+</ivy-module>
diff --git a/src/example/multi-project/projects/find/src/find/FindFile.java b/src/example/multi-project/projects/find/src/find/FindFile.java
new file mode 100644
index 0000000..30e22a9
--- /dev/null
+++ b/src/example/multi-project/projects/find/src/find/FindFile.java
@@ -0,0 +1,48 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package find;
+
+import version.Version;
+import list.ListFile;
+
+import java.util.Collection;
+import java.io.File;
+
+import  org.apache.commons.collections.CollectionUtils;
+import  org.apache.commons.collections.Predicate;
+
+public final class FindFile {
+  static {
+    Version.register("find");
+  }
+  
+  public static Collection find(File dir, String name) {
+    return find(ListFile.list(dir), name);
+  }
+  
+  private static Collection find(Collection files, final String name) {    
+    return CollectionUtils.select(files, new Predicate() {
+      public boolean evaluate(Object o) {
+        return ((File) o).getName().indexOf(name) != -1;
+      }
+    });
+  }
+  
+  private FindFile() {
+  }
+}
diff --git a/src/example/multi-project/projects/find/src/find/Main.java b/src/example/multi-project/projects/find/src/find/Main.java
new file mode 100644
index 0000000..63c9db0
--- /dev/null
+++ b/src/example/multi-project/projects/find/src/find/Main.java
@@ -0,0 +1,76 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package find;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.GnuParser;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.OptionBuilder;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+
+public final class Main {
+    private static Options getOptions() {
+        Option dir = OptionBuilder.withArgName("dir")
+            .hasArg()
+            .withDescription("list files in given dir")
+            .create("dir");
+        Option name = OptionBuilder.withArgName("name")
+            .hasArg()
+            .withDescription("list files with given name")
+            .create("name");
+        Options options = new Options();
+
+        options.addOption(dir);
+        options.addOption(name);
+        
+        return options;
+    }
+    
+    public static void main(String[] args) throws Exception {
+        Options options = getOptions();
+        try {
+        
+            CommandLineParser parser = new GnuParser();
+    
+            CommandLine line = parser.parse(options, args);
+            File dir = new File(line.getOptionValue("dir", "."));
+            String name = line.getOptionValue("name", "jar");
+            Collection files = FindFile.find(dir, name);
+            System.out.println("listing files in " + dir + " containing " + name);
+            for (Iterator it = files.iterator(); it.hasNext();) {
+                System.out.println("\t" + it.next() + "\n");
+            }
+        } catch (ParseException exp) {
+            // oops, something went wrong
+            System.err.println("Parsing failed.  Reason: " + exp.getMessage());
+              
+            HelpFormatter formatter = new HelpFormatter();
+            formatter.printHelp("find", options);
+        }        
+    }
+    
+    private Main() {
+    }
+}
diff --git a/src/example/multi-project/projects/list/build.properties b/src/example/multi-project/projects/list/build.properties
new file mode 100644
index 0000000..1b54925
--- /dev/null
+++ b/src/example/multi-project/projects/list/build.properties
@@ -0,0 +1,21 @@
+#	 ***************************************************************
+#	 * Licensed to the Apache Software Foundation (ASF) under one
+#	 * or more contributor license agreements.  See the NOTICE file
+#	 * distributed with this work for additional information
+#	 * regarding copyright ownership.  The ASF licenses this file
+#	 * to you under the Apache License, Version 2.0 (the
+#	 * "License"); you may not use this file except in compliance
+#	 * with the License.  You may obtain a copy of the License at
+#	 * 
+#	 *   http://www.apache.org/licenses/LICENSE-2.0
+#	 * 
+#	 * Unless required by applicable law or agreed to in writing,
+#	 * software distributed under the License is distributed on an
+#	 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#	 * KIND, either express or implied.  See the License for the
+#	 * specific language governing permissions and limitations
+#	 * under the License.
+#	 ***************************************************************
+projects.dir = ${basedir}/..
+wkspace.dir = ${projects.dir}/..
+common.dir = ${wkspace.dir}/common
diff --git a/src/example/multi-project/projects/list/build.xml b/src/example/multi-project/projects/list/build.xml
new file mode 100644
index 0000000..e86db1a
--- /dev/null
+++ b/src/example/multi-project/projects/list/build.xml
@@ -0,0 +1,23 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<project name="list" default="compile">
+	<property file="build.properties"/>
+	
+	<import file="${common.dir}/common.xml"/>
+</project>
diff --git a/src/example/multi-project/projects/list/ivy.xml b/src/example/multi-project/projects/list/ivy.xml
new file mode 100644
index 0000000..8251fdc
--- /dev/null
+++ b/src/example/multi-project/projects/list/ivy.xml
@@ -0,0 +1,35 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT 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="org.apache.ivy.example"
+        module="list"
+        status="integration"/>
+    <configurations>
+      <conf name="core"/>
+      <conf name="standalone" extends="core"/>
+    </configurations>
+    <publications>
+      <artifact name="list" type="jar" conf="core" />
+    </publications>
+    <dependencies>
+      <dependency name="version" rev="latest.integration" conf="core->default" />
+      <dependency org="commons-cli" name="commons-cli" rev="1.0" conf="standalone->default" />
+    </dependencies>
+</ivy-module>
diff --git a/src/example/multi-project/projects/list/src/list/ListFile.java b/src/example/multi-project/projects/list/src/list/ListFile.java
new file mode 100644
index 0000000..99b921a
--- /dev/null
+++ b/src/example/multi-project/projects/list/src/list/ListFile.java
@@ -0,0 +1,50 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package list;
+
+import version.Version;
+import java.util.Collection;
+import java.util.ArrayList;
+import java.io.File;
+
+public final class ListFile {
+  static {
+    Version.register("list");
+  }
+  
+  public static Collection list(File dir) {
+    Collection files = new ArrayList();
+    
+    return list(dir, files);
+  }
+  
+  private static Collection list(File file, Collection files) {
+    if (file.isDirectory()) {
+      File[] f = file.listFiles();
+      for (int i = 0; i < f.length; i++) {
+        list(f[i], files);
+      }
+    } else {
+      files.add(file);
+    }
+    return files;
+  }
+  
+  private ListFile() {
+  }
+}
diff --git a/src/example/multi-project/projects/list/src/list/Main.java b/src/example/multi-project/projects/list/src/list/Main.java
new file mode 100644
index 0000000..8874994
--- /dev/null
+++ b/src/example/multi-project/projects/list/src/list/Main.java
@@ -0,0 +1,71 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package list;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.GnuParser;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.OptionBuilder;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+
+public final class Main {
+    private static Options getOptions() {
+        Option dir = OptionBuilder.withArgName("dir")
+            .hasArg()
+            .withDescription("list files in given dir")
+            .create("dir");
+        Options options = new Options();
+
+        options.addOption(dir);
+        
+        return options;
+    }
+    
+    public static void main(String[] args) throws Exception {
+      Options options = getOptions();
+      try {
+        
+        CommandLineParser parser = new GnuParser();
+
+        CommandLine line = parser.parse(options, args);
+        File dir = new File(line.getOptionValue("dir", "."));
+        Collection files = ListFile.list(dir);
+        System.out.println("listing files in " + dir);
+        for (Iterator it = files.iterator(); it.hasNext();) {
+          System.out.println("\t" + it.next() + "\n");
+        }
+      } catch (ParseException exp) {
+          // oops, something went wrong
+          System.err.println("Parsing failed.  Reason: " + exp.getMessage());
+          
+        HelpFormatter formatter = new HelpFormatter();
+        formatter.printHelp("list", options);
+      }        
+    }
+    
+    private Main() {
+    }
+            
+}
diff --git a/src/example/multi-project/projects/size/build.properties b/src/example/multi-project/projects/size/build.properties
new file mode 100644
index 0000000..1b54925
--- /dev/null
+++ b/src/example/multi-project/projects/size/build.properties
@@ -0,0 +1,21 @@
+#	 ***************************************************************
+#	 * Licensed to the Apache Software Foundation (ASF) under one
+#	 * or more contributor license agreements.  See the NOTICE file
+#	 * distributed with this work for additional information
+#	 * regarding copyright ownership.  The ASF licenses this file
+#	 * to you under the Apache License, Version 2.0 (the
+#	 * "License"); you may not use this file except in compliance
+#	 * with the License.  You may obtain a copy of the License at
+#	 * 
+#	 *   http://www.apache.org/licenses/LICENSE-2.0
+#	 * 
+#	 * Unless required by applicable law or agreed to in writing,
+#	 * software distributed under the License is distributed on an
+#	 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#	 * KIND, either express or implied.  See the License for the
+#	 * specific language governing permissions and limitations
+#	 * under the License.
+#	 ***************************************************************
+projects.dir = ${basedir}/..
+wkspace.dir = ${projects.dir}/..
+common.dir = ${wkspace.dir}/common
diff --git a/src/example/multi-project/projects/size/build.xml b/src/example/multi-project/projects/size/build.xml
new file mode 100644
index 0000000..efaacbf
--- /dev/null
+++ b/src/example/multi-project/projects/size/build.xml
@@ -0,0 +1,23 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<project name="size" default="compile">
+	<property file="build.properties"/>
+	
+	<import file="${common.dir}/common.xml"/>
+</project>
diff --git a/src/example/multi-project/projects/size/ivy.xml b/src/example/multi-project/projects/size/ivy.xml
new file mode 100644
index 0000000..ff20d0e
--- /dev/null
+++ b/src/example/multi-project/projects/size/ivy.xml
@@ -0,0 +1,28 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT 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="org.apache.ivy.example"
+        module="size"
+        status="integration"/>
+    <dependencies>
+      <dependency name="version" rev="latest.integration" conf="default" />
+      <dependency name="list" rev="latest.integration" conf="default->core" />
+    </dependencies>
+</ivy-module>
diff --git a/src/example/multi-project/projects/size/src/size/FileSize.java b/src/example/multi-project/projects/size/src/size/FileSize.java
new file mode 100644
index 0000000..7a3e842
--- /dev/null
+++ b/src/example/multi-project/projects/size/src/size/FileSize.java
@@ -0,0 +1,45 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package size;
+
+import version.Version;
+import java.util.Collection;
+import java.util.Iterator;
+import java.io.File;
+
+public final class FileSize {
+  static {
+    Version.register("size");
+  }
+
+  public static long totalSize(File dir) {
+    return totalSize(list.ListFile.list(dir));
+  }
+  
+  public static long totalSize(Collection files) {
+    long total = 0;
+    for (Iterator it = files.iterator(); it.hasNext();) {
+      File f = (File) it.next();
+      total += f.length();
+    }
+    return total;
+  }  
+  
+  private FileSize() {
+  }
+}
diff --git a/src/example/multi-project/projects/sizewhere/build.properties b/src/example/multi-project/projects/sizewhere/build.properties
new file mode 100644
index 0000000..1b54925
--- /dev/null
+++ b/src/example/multi-project/projects/sizewhere/build.properties
@@ -0,0 +1,21 @@
+#	 ***************************************************************
+#	 * Licensed to the Apache Software Foundation (ASF) under one
+#	 * or more contributor license agreements.  See the NOTICE file
+#	 * distributed with this work for additional information
+#	 * regarding copyright ownership.  The ASF licenses this file
+#	 * to you under the Apache License, Version 2.0 (the
+#	 * "License"); you may not use this file except in compliance
+#	 * with the License.  You may obtain a copy of the License at
+#	 * 
+#	 *   http://www.apache.org/licenses/LICENSE-2.0
+#	 * 
+#	 * Unless required by applicable law or agreed to in writing,
+#	 * software distributed under the License is distributed on an
+#	 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#	 * KIND, either express or implied.  See the License for the
+#	 * specific language governing permissions and limitations
+#	 * under the License.
+#	 ***************************************************************
+projects.dir = ${basedir}/..
+wkspace.dir = ${projects.dir}/..
+common.dir = ${wkspace.dir}/common
diff --git a/src/example/multi-project/projects/sizewhere/build.xml b/src/example/multi-project/projects/sizewhere/build.xml
new file mode 100644
index 0000000..107d1ca
--- /dev/null
+++ b/src/example/multi-project/projects/sizewhere/build.xml
@@ -0,0 +1,23 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<project name="sizewhere" default="compile">
+	<property file="build.properties"/>
+	
+	<import file="${common.dir}/common.xml"/>
+</project>
diff --git a/src/example/multi-project/projects/sizewhere/ivy.xml b/src/example/multi-project/projects/sizewhere/ivy.xml
new file mode 100644
index 0000000..344cb3f
--- /dev/null
+++ b/src/example/multi-project/projects/sizewhere/ivy.xml
@@ -0,0 +1,37 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT 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="org.apache.ivy.example"
+        module="sizewhere"
+        status="integration"/>
+    <configurations>
+      <conf name="core"/>
+      <conf name="standalone" extends="core"/>
+    </configurations>
+    <publications>
+      <artifact name="sizewhere" type="jar" conf="core" />
+    </publications>
+    <dependencies>
+      <dependency name="version" rev="latest.integration" conf="core->default" />
+      <dependency name="size" rev="latest.integration" conf="core->default" />
+      <dependency name="find" rev="latest.integration" conf="core" />
+      <dependency org="commons-cli" name="commons-cli" rev="1.0" conf="standalone->default" />
+    </dependencies>
+</ivy-module>
diff --git a/src/example/multi-project/projects/sizewhere/src/sizewhere/Main.java b/src/example/multi-project/projects/sizewhere/src/sizewhere/Main.java
new file mode 100644
index 0000000..2a795ff
--- /dev/null
+++ b/src/example/multi-project/projects/sizewhere/src/sizewhere/Main.java
@@ -0,0 +1,71 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package sizewhere;
+
+import java.io.File;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.GnuParser;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.OptionBuilder;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+
+public final class Main {
+    private static Options getOptions() {
+        Option dir = OptionBuilder.withArgName("dir")
+            .hasArg()
+            .withDescription("give total size of files in given dir")
+            .create("dir");
+        Option name = OptionBuilder.withArgName("name")
+            .hasArg()
+            .withDescription("give total size of files with given name")
+            .create("name");
+        Options options = new Options();
+
+        options.addOption(dir);
+        options.addOption(name);
+        
+        return options;
+    }
+    
+    public static void main(String[] args) throws Exception {
+      Options options = getOptions();
+      try {
+        
+        CommandLineParser parser = new GnuParser();
+
+        CommandLine line = parser.parse(options, args);
+        File dir = new File(line.getOptionValue("dir", "."));
+        String name = line.getOptionValue("name", "jar");
+        System.out.println("total size of files in " + dir 
+            + " containing " + name + ": " + SizeWhere.totalSize(dir, name));
+      } catch (ParseException exp) {
+          // oops, something went wrong
+          System.err.println("Parsing failed.  Reason: " + exp.getMessage());
+          
+        HelpFormatter formatter = new HelpFormatter();
+        formatter.printHelp("sizewhere", options);
+      }        
+    }
+            
+    private Main() {
+    }
+}
diff --git a/src/example/multi-project/projects/sizewhere/src/sizewhere/SizeWhere.java b/src/example/multi-project/projects/sizewhere/src/sizewhere/SizeWhere.java
new file mode 100644
index 0000000..fc25c2a
--- /dev/null
+++ b/src/example/multi-project/projects/sizewhere/src/sizewhere/SizeWhere.java
@@ -0,0 +1,37 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package sizewhere;
+
+import version.Version;
+import size.FileSize;
+import find.FindFile;
+
+import java.io.File;
+
+public final class SizeWhere {
+  static {
+    Version.register("sizewhere");
+  }
+  
+  public static long totalSize(File dir, String name) {
+    return FileSize.totalSize(FindFile.find(dir, name));
+  }
+  
+  private SizeWhere() {
+  }
+}
diff --git a/src/example/multi-project/projects/version/build.properties b/src/example/multi-project/projects/version/build.properties
new file mode 100644
index 0000000..1b54925
--- /dev/null
+++ b/src/example/multi-project/projects/version/build.properties
@@ -0,0 +1,21 @@
+#	 ***************************************************************
+#	 * Licensed to the Apache Software Foundation (ASF) under one
+#	 * or more contributor license agreements.  See the NOTICE file
+#	 * distributed with this work for additional information
+#	 * regarding copyright ownership.  The ASF licenses this file
+#	 * to you under the Apache License, Version 2.0 (the
+#	 * "License"); you may not use this file except in compliance
+#	 * with the License.  You may obtain a copy of the License at
+#	 * 
+#	 *   http://www.apache.org/licenses/LICENSE-2.0
+#	 * 
+#	 * Unless required by applicable law or agreed to in writing,
+#	 * software distributed under the License is distributed on an
+#	 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#	 * KIND, either express or implied.  See the License for the
+#	 * specific language governing permissions and limitations
+#	 * under the License.
+#	 ***************************************************************
+projects.dir = ${basedir}/..
+wkspace.dir = ${projects.dir}/..
+common.dir = ${wkspace.dir}/common
diff --git a/src/example/multi-project/projects/version/build.xml b/src/example/multi-project/projects/version/build.xml
new file mode 100644
index 0000000..5200004
--- /dev/null
+++ b/src/example/multi-project/projects/version/build.xml
@@ -0,0 +1,23 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<project name="version" default="compile">
+	<property file="build.properties"/>
+	
+	<import file="${common.dir}/common.xml"/>
+</project>
diff --git a/src/example/multi-project/projects/version/ivy.xml b/src/example/multi-project/projects/version/ivy.xml
new file mode 100644
index 0000000..7a29b46
--- /dev/null
+++ b/src/example/multi-project/projects/version/ivy.xml
@@ -0,0 +1,24 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT 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="org.apache.ivy.example"
+        module="version"
+        status="integration"/>
+</ivy-module>
diff --git a/src/example/multi-project/projects/version/src/version/Version.java b/src/example/multi-project/projects/version/src/version/Version.java
new file mode 100644
index 0000000..22952a8
--- /dev/null
+++ b/src/example/multi-project/projects/version/src/version/Version.java
@@ -0,0 +1,51 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package version;
+
+import java.io.InputStream;
+import java.util.Properties;
+import java.util.Map;
+import java.util.HashMap;
+
+public final class Version {
+    static {
+        versions = new HashMap();
+        register("version");
+    }
+  
+    private static Map versions;
+  
+    public static void register(String module) {
+        try {
+            InputStream moduleVersion = Version.class.getResourceAsStream("/" + module
+                    + ".properties");
+            Properties props = new Properties();
+            props.load(moduleVersion);
+            String version = (String) props.get("version");
+            versions.put(module, version);
+            System.out.println("--- using " + module + " v" + version);
+        } catch (Exception ex) {
+            System.err.println("an error occurred while registering " + module + ": "
+                    + ex.getMessage());
+            ex.printStackTrace();
+        }
+    }
+    
+    private Version() {
+    }
+}
diff --git a/src/example/packager-resolver/ivysettings.xml b/src/example/packager-resolver/ivysettings.xml
new file mode 100644
index 0000000..fbbaafd
--- /dev/null
+++ b/src/example/packager-resolver/ivysettings.xml
@@ -0,0 +1,35 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<ivysettings>
+    <resolvers>
+        <packager name="ivyroundup" buildRoot="${user.home}/.ivy2/packager/build" resourceCache="${user.home}/.ivy2/packager/cache">
+
+            <!-- This defines the Ivy file location as is normally done -->
+            <ivy pattern="http://ivyroundup.googlecode.com/svn/trunk/repo/modules/[organisation]/[module]/[revision]/ivy.xml"/>
+
+            <!-- This defines the location of the build instructions -->
+            <artifact pattern="http://ivyroundup.googlecode.com/svn/trunk/repo/modules/[organisation]/[module]/[revision]/packager.xml"/>
+        </packager>
+    </resolvers>
+    <modules>
+        <module organisation=".*" name=".*" resolver="ivyroundup"/>
+    </modules>
+    <caches useOrigin="true"/>
+</ivysettings>
+
diff --git a/src/java/org/apache/ivy/Ivy.java b/src/java/org/apache/ivy/Ivy.java
new file mode 100644
index 0000000..c3799e9
--- /dev/null
+++ b/src/java/org/apache/ivy/Ivy.java
@@ -0,0 +1,975 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.ivy.core.IvyContext;
+import org.apache.ivy.core.LogOptions;
+import org.apache.ivy.core.cache.ResolutionCacheManager;
+import org.apache.ivy.core.check.CheckEngine;
+import org.apache.ivy.core.deliver.DeliverEngine;
+import org.apache.ivy.core.deliver.DeliverOptions;
+import org.apache.ivy.core.event.EventManager;
+import org.apache.ivy.core.install.InstallEngine;
+import org.apache.ivy.core.install.InstallOptions;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.publish.PublishEngine;
+import org.apache.ivy.core.publish.PublishOptions;
+import org.apache.ivy.core.report.ResolveReport;
+import org.apache.ivy.core.repository.RepositoryManagementEngine;
+import org.apache.ivy.core.resolve.ResolveData;
+import org.apache.ivy.core.resolve.ResolveEngine;
+import org.apache.ivy.core.resolve.ResolveOptions;
+import org.apache.ivy.core.resolve.ResolvedModuleRevision;
+import org.apache.ivy.core.retrieve.RetrieveEngine;
+import org.apache.ivy.core.retrieve.RetrieveOptions;
+import org.apache.ivy.core.retrieve.RetrieveReport;
+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.core.search.SearchEngine;
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.ivy.core.sort.SortEngine;
+import org.apache.ivy.core.sort.SortOptions;
+import org.apache.ivy.plugins.circular.CircularDependencyException;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+import org.apache.ivy.plugins.repository.TransferEvent;
+import org.apache.ivy.plugins.repository.TransferListener;
+import org.apache.ivy.plugins.resolver.BasicResolver;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.apache.ivy.plugins.trigger.Trigger;
+import org.apache.ivy.util.DateUtil;
+import org.apache.ivy.util.HostUtil;
+import org.apache.ivy.util.Message;
+import org.apache.ivy.util.MessageLoggerEngine;
+
+/**
+ * <a href="http://ant.apache.org/ivy/">Ivy</a> is a free java based dependency manager.
+ * <p>
+ * This class is the main class of Ivy, which acts as a Facade to all services offered by Ivy:
+ * <ul>
+ * <li>resolve dependencies</li>
+ * <li>retrieve artifacts to a local location</li>
+ * <li>deliver and publish modules</li>
+ * <li>repository search and listing</li>
+ * </ul>
+ * Here is one typical usage:
+ * 
+ * <pre>
+ * Ivy ivy = Ivy.newInstance();
+ * ivy.configure(new URL("ivysettings.xml"));
+ * ivy.resolve(new URL("ivy.xml"));
+ * </pre>
+ * 
+ * </p>
+ * <h2>Using Ivy engines directly</h2>
+ * <p>
+ * If the methods offered by the {@link Ivy} class are not flexible enough and you want to use Ivy
+ * engines directly, you need to call the methods within a single {@link IvyContext} associated to
+ * the {@link Ivy} instance you use.<br/>
+ * To do so, it is recommended to use the {@link #execute(org.apache.ivy.Ivy.IvyCallback)} method
+ * like this:
+ * 
+ * <pre>
+ * Ivy ivy = Ivy.newInstance();
+ * ivy.execute(new IvyCallback() {
+ *     public Object doInIvyContext(Ivy ivy, IvyContext context) {
+ *         // obviously we can use regular Ivy methods in the callback
+ *         ivy.configure(new URL("ivysettings.xml"));
+ *         // and we can safely use Ivy engines too
+ *         ivy.getResolveEngine().resolve(new URL("ivy.xml"));
+ *         return null;
+ *     }
+ * });
+ * </pre>
+ * 
+ * </p>
+ */
+public class Ivy {
+    /**
+     * Callback used to execute a set of Ivy related methods within an {@link IvyContext}.
+     * 
+     * @see Ivy#execute(org.apache.ivy.Ivy.IvyCallback)
+     */
+    public static interface IvyCallback {
+        /**
+         * Executes Ivy related job within an {@link IvyContext}
+         * 
+         * @param ivy
+         *            the {@link Ivy} instance to which this callback is related
+         * @param context
+         *            the {@link IvyContext} in which this callback is executed
+         * @return the result of this job, <code>null</code> if there is no result
+         */
+        public Object doInIvyContext(Ivy ivy, IvyContext context);
+    }
+
+    private static final int KILO = 1024;
+
+    /**
+     * @deprecated Use the {@link DateUtil} utility class instead.
+     */
+    @Deprecated
+    public static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat(
+            DateUtil.DATE_FORMAT_PATTERN);
+
+    /**
+     * the current version of Ivy, as displayed on the console when Ivy is initialized
+     */
+    private static final String IVY_VERSION;
+
+    /**
+     * the date at which this version of Ivy has been built. May be empty if unknown.
+     */
+    private static final String IVY_DATE;
+
+    static {
+        // initialize IVY_VERSION and IVY_DATE
+        Properties props = new Properties();
+        URL moduleURL = Message.class.getResource("/module.properties");
+        if (moduleURL != null) {
+            try {
+                InputStream module = moduleURL.openStream();
+                props.load(module);
+                module.close();
+            } catch (IOException e) {
+                // ignore this exception, we will initialize with default values
+            }
+        }
+        IVY_VERSION = props.getProperty("version", "non official version");
+        IVY_DATE = props.getProperty("date", "");
+    }
+
+    /**
+     * Returns the current version of Ivy, as displayed on the console when Ivy is initialized.
+     * 
+     * @return the current version of Ivy
+     */
+    public static String getIvyVersion() {
+        return IVY_VERSION;
+    }
+
+    /**
+     * Returns the date at which this version of Ivy has been built.
+     * <p>
+     * May be empty if unknown.
+     * 
+     * @return the date at which this version of Ivy has been built
+     */
+    public static String getIvyDate() {
+        return IVY_DATE;
+    }
+
+    /**
+     * Returns the URL at which Ivy web site can be found.
+     * 
+     * @return the URL at which Ivy web site can be found
+     */
+    public static String getIvyHomeURL() {
+        return "http://ant.apache.org/ivy/";
+    }
+
+    public static Ivy newInstance() {
+        Ivy ivy = new Ivy();
+        ivy.bind();
+        return ivy;
+    }
+
+    public static Ivy newInstance(IvySettings settings) {
+        Ivy ivy = new Ivy();
+        ivy.setSettings(settings);
+        ivy.bind();
+        return ivy;
+    }
+
+    /**
+     * True if the current processing has been requested to be interrupted, false otherwise
+     */
+    private boolean interrupted;
+
+    /**
+     * True if this instance of Ivy has already been bound to its dependencies, false otherwise.
+     * 
+     * @see bind()
+     */
+    private boolean bound;
+
+    /*
+     * Following are dependencies of the Ivy instance on instances of engines and manager which
+     * actually do the work The attributes can be set either manually using the corresponding
+     * setters, or all at once with the default implementations using the bind method
+     */
+    private IvySettings settings;
+
+    private EventManager eventManager;
+
+    private SortEngine sortEngine;
+
+    private SearchEngine searchEngine;
+
+    private CheckEngine checkEngine;
+
+    private ResolveEngine resolveEngine;
+
+    private RetrieveEngine retrieveEngine;
+
+    private DeliverEngine deliverEngine;
+
+    private PublishEngine publishEngine;
+
+    private InstallEngine installEngine;
+
+    private RepositoryManagementEngine repositoryEngine;
+
+    /**
+     * The logger engine to use to log messages when using this Ivy instance.
+     */
+    private MessageLoggerEngine loggerEngine = new MessageLoggerEngine();
+
+    /**
+     * The default constructor of Ivy allows to create an instance of Ivy with none of its
+     * dependencies (engines, settings, ...) created. If you use this constructor, it's your
+     * responsibility to set the dependencies of Ivy using the appropriate setters
+     * (setResolveEngine, ...). You can also call the bind method to set all the dependencies except
+     * those that you have provided using the setters. If you want to get an instance ready to use,
+     * prefer the use of Ivy.newInstance().
+     */
+    public Ivy() {
+    }
+
+    /**
+     * This method is used to bind this Ivy instance to required dependencies, i.e. instance of
+     * settings, engines, and so on.
+     * <p>
+     * After this call Ivy is still not configured, which means that the settings object is still
+     * empty.
+     * </p>
+     */
+    public void bind() {
+        pushContext();
+        try {
+            if (settings == null) {
+                settings = new IvySettings();
+            }
+            if (eventManager == null) {
+                eventManager = new EventManager();
+            }
+            if (sortEngine == null) {
+                sortEngine = new SortEngine(settings);
+            }
+            if (searchEngine == null) {
+                searchEngine = new SearchEngine(settings);
+            }
+            if (resolveEngine == null) {
+                resolveEngine = new ResolveEngine(settings, eventManager, sortEngine);
+            }
+            if (retrieveEngine == null) {
+                retrieveEngine = new RetrieveEngine(settings, eventManager);
+            }
+            if (deliverEngine == null) {
+                deliverEngine = new DeliverEngine(settings);
+            }
+            if (publishEngine == null) {
+                publishEngine = new PublishEngine(settings, eventManager);
+            }
+            if (installEngine == null) {
+                installEngine = new InstallEngine(settings, searchEngine, resolveEngine);
+            }
+            if (repositoryEngine == null) {
+                repositoryEngine = new RepositoryManagementEngine(settings, searchEngine,
+                        resolveEngine);
+            }
+
+            eventManager.addTransferListener(new TransferListener() {
+                public void transferProgress(TransferEvent evt) {
+                    ResolveData resolve;
+                    switch (evt.getEventType()) {
+                        case TransferEvent.TRANSFER_PROGRESS:
+                            resolve = IvyContext.getContext().getResolveData();
+                            if (resolve == null
+                                    || !LogOptions.LOG_QUIET.equals(resolve.getOptions().getLog())) {
+                                Message.progress();
+                            }
+                            break;
+                        case TransferEvent.TRANSFER_COMPLETED:
+                            resolve = IvyContext.getContext().getResolveData();
+                            if (resolve == null
+                                    || !LogOptions.LOG_QUIET.equals(resolve.getOptions().getLog())) {
+                                Message.endProgress(" (" + (evt.getTotalLength() / KILO) + "kB)");
+                            }
+                            break;
+                        default:
+                            break;
+                    }
+                }
+            });
+
+            bound = true;
+        } finally {
+            popContext();
+        }
+    }
+
+    /**
+     * Executes the given callback in the context of this Ivy instance.
+     * <p>
+     * Alternatively you can use the {@link #pushContext()} and {@link #popContext()} methods, but
+     * this is not recommended:
+     * 
+     * <pre>
+     * Object result = null;
+     * pushContext();
+     * try {
+     *     result = callback.doInIvyContext(this, IvyContext.getContext());
+     * } finally {
+     *     popContext();
+     * }
+     * doSomethingWithResult(result);
+     * </pre>
+     * 
+     * </p>
+     * 
+     * @param callback
+     * @return
+     */
+    public Object execute(IvyCallback callback) {
+        pushContext();
+        try {
+            return callback.doInIvyContext(this, IvyContext.getContext());
+        } finally {
+            popContext();
+        }
+    }
+
+    /**
+     * Pushes a new IvyContext bound to this Ivy instance if the current context is not already
+     * bound to this Ivy instance. If the current context is already bound to this Ivy instance, it
+     * pushes the current context on the context stack, so that you can (and must) always call
+     * {@link #popContext()} when you're done.
+     * <p>
+     * Alternatively, you can use the {@link #execute(org.apache.ivy.Ivy.IvyCallback)} method which
+     * takes care of everything for you.
+     * </p>
+     */
+    public void pushContext() {
+        if (IvyContext.getContext().peekIvy() != this) {
+            // the current Ivy context is associated with another Ivy instance, we push a new
+            // instance
+            IvyContext.pushNewContext();
+            IvyContext.getContext().setIvy(this);
+        } else {
+            // the current Ivy context is already associated with this Ivy instance, we only push it
+            // for popping consistency
+            IvyContext.pushContext(IvyContext.getContext());
+        }
+    }
+
+    /**
+     * Pops the current Ivy context.
+     * <p>
+     * You must call this method once and only once for each call to {@link #pushContext()}, when
+     * you're done with the your Ivy related work.
+     * </p>
+     * <p>
+     * Alternatively, you can use the {@link #execute(org.apache.ivy.Ivy.IvyCallback)} method which
+     * takes care of everything for you.
+     * </p>
+     */
+    public void popContext() {
+        IvyContext.popContext();
+    }
+
+    // ///////////////////////////////////////////////////////////////////////
+    // LOAD SETTINGS
+    // ///////////////////////////////////////////////////////////////////////
+    public void configure(File settingsFile) throws ParseException, IOException {
+        pushContext();
+        try {
+            assertBound();
+            settings.load(settingsFile);
+            postConfigure();
+        } finally {
+            popContext();
+        }
+    }
+
+    public void configure(URL settingsURL) throws ParseException, IOException {
+        pushContext();
+        try {
+            assertBound();
+            settings.load(settingsURL);
+            postConfigure();
+        } finally {
+            popContext();
+        }
+    }
+
+    public void configureDefault() throws ParseException, IOException {
+        pushContext();
+        try {
+            assertBound();
+            settings.loadDefault();
+            postConfigure();
+        } finally {
+            popContext();
+        }
+    }
+
+    /**
+     * Configures Ivy with 1.4 compatible default settings
+     */
+    public void configureDefault14() throws ParseException, IOException {
+        pushContext();
+        try {
+            assertBound();
+            settings.loadDefault14();
+            postConfigure();
+        } finally {
+            popContext();
+        }
+    }
+
+    // ///////////////////////////////////////////////////////////////////////
+    // CHECK
+    // ///////////////////////////////////////////////////////////////////////
+    public boolean check(URL ivyFile, String resolvername) {
+        pushContext();
+        try {
+            return checkEngine.check(ivyFile, resolvername);
+        } finally {
+            popContext();
+        }
+    }
+
+    // ///////////////////////////////////////////////////////////////////////
+    // RESOLVE
+    // ///////////////////////////////////////////////////////////////////////
+
+    public ResolveReport resolve(File ivySource) throws ParseException, IOException {
+        pushContext();
+        try {
+            return resolveEngine.resolve(ivySource);
+        } finally {
+            popContext();
+        }
+    }
+
+    public ResolveReport resolve(URL ivySource) throws ParseException, IOException {
+        pushContext();
+        try {
+            return resolveEngine.resolve(ivySource);
+        } finally {
+            popContext();
+        }
+    }
+
+    public ResolveReport resolve(ModuleRevisionId mrid, ResolveOptions options, boolean changing)
+            throws ParseException, IOException {
+        pushContext();
+        try {
+            return resolveEngine.resolve(mrid, options, changing);
+        } finally {
+            popContext();
+        }
+    }
+
+    public ResolveReport resolve(URL ivySource, ResolveOptions options) throws ParseException,
+            IOException {
+        pushContext();
+        try {
+            return resolveEngine.resolve(ivySource, options);
+        } finally {
+            popContext();
+        }
+    }
+
+    public ResolveReport resolve(File ivySource, ResolveOptions options) throws ParseException,
+            IOException {
+        return resolve(ivySource.toURI().toURL(), options);
+    }
+
+    public ResolveReport resolve(ModuleDescriptor md, ResolveOptions options)
+            throws ParseException, IOException {
+        pushContext();
+        try {
+            return resolveEngine.resolve(md, options);
+        } finally {
+            popContext();
+        }
+    }
+
+    // ///////////////////////////////////////////////////////////////////////
+    // INSTALL
+    // ///////////////////////////////////////////////////////////////////////
+
+    public ResolveReport install(ModuleRevisionId mrid, String from, String to,
+            InstallOptions options) throws IOException {
+        pushContext();
+        try {
+            return installEngine.install(mrid, from, to, options);
+        } finally {
+            popContext();
+        }
+    }
+
+    // ///////////////////////////////////////////////////////////////////////
+    // RETRIEVE
+    // ///////////////////////////////////////////////////////////////////////
+
+    public int retrieve(ModuleRevisionId mrid, String destFilePattern, RetrieveOptions options)
+            throws IOException {
+        pushContext();
+        try {
+            return retrieveEngine.retrieve(mrid, destFilePattern, options);
+        } finally {
+            popContext();
+        }
+    }
+
+    public RetrieveReport retrieve(ModuleRevisionId mrid, RetrieveOptions options)
+            throws IOException {
+        pushContext();
+        try {
+            return retrieveEngine.retrieve(mrid, options);
+        } finally {
+            popContext();
+        }
+    }
+
+    // ///////////////////////////////////////////////////////////////////////
+    // DELIVER
+    // ///////////////////////////////////////////////////////////////////////
+
+    public void deliver(ModuleRevisionId mrid, String revision, String destIvyPattern)
+            throws IOException, ParseException {
+        pushContext();
+        try {
+            deliverEngine.deliver(mrid, revision, destIvyPattern,
+                DeliverOptions.newInstance(settings));
+        } finally {
+            popContext();
+        }
+    }
+
+    public void deliver(String revision, String destIvyPattern, DeliverOptions options)
+            throws IOException, ParseException {
+        pushContext();
+        try {
+            deliverEngine.deliver(revision, destIvyPattern, options);
+        } finally {
+            popContext();
+        }
+    }
+
+    /**
+     * Example of use: deliver(mrid, "1.5", "target/ivy/ivy-[revision].xml",
+     * DeliverOptions.newInstance(settings).setStatus("release").setValidate(false));
+     * 
+     * @param mrid
+     * @param revision
+     * @param destIvyPattern
+     * @param options
+     * @throws IOException
+     * @throws ParseException
+     */
+    public void deliver(ModuleRevisionId mrid, String revision, String destIvyPattern,
+            DeliverOptions options) throws IOException, ParseException {
+        pushContext();
+        try {
+            deliverEngine.deliver(mrid, revision, destIvyPattern, options);
+        } finally {
+            popContext();
+        }
+    }
+
+    // ///////////////////////////////////////////////////////////////////////
+    // PUBLISH
+    // ///////////////////////////////////////////////////////////////////////
+
+    public Collection publish(ModuleRevisionId mrid, Collection srcArtifactPattern,
+            String resolverName, PublishOptions options) throws IOException {
+        pushContext();
+        try {
+            return publishEngine.publish(mrid, srcArtifactPattern, resolverName, options);
+        } finally {
+            popContext();
+        }
+    }
+
+    // ///////////////////////////////////////////////////////////////////////
+    // SORT
+    // ///////////////////////////////////////////////////////////////////////
+
+    /**
+     * Sorts the collection of IvyNode from the less dependent to the more dependent
+     */
+    public List sortNodes(Collection nodes, SortOptions options) {
+        pushContext();
+        try {
+            return getSortEngine().sortNodes(nodes, options);
+        } finally {
+            popContext();
+        }
+    }
+
+    /**
+     * Sorts the given ModuleDescriptors from the less dependent to the more dependent. This sort
+     * ensures that a ModuleDescriptor is always found in the list before all ModuleDescriptors
+     * depending directly on it.
+     * 
+     * @param moduleDescriptors
+     *            a Collection of ModuleDescriptor to sort
+     * @param options
+     *            Options to use to sort the descriptors.
+     * @return a List of sorted ModuleDescriptors
+     * @throws CircularDependencyException
+     *             if a circular dependency exists and circular dependency strategy decide to throw
+     *             an exception
+     */
+    public List sortModuleDescriptors(Collection moduleDescriptors, SortOptions options) {
+        pushContext();
+        try {
+            return getSortEngine().sortModuleDescriptors(moduleDescriptors, options);
+        } finally {
+            popContext();
+        }
+    }
+
+    // ///////////////////////////////////////////////////////////////////////
+    // SEARCH
+    // ///////////////////////////////////////////////////////////////////////
+
+    public ResolvedModuleRevision findModule(ModuleRevisionId mrid) {
+        pushContext();
+        try {
+            ResolveOptions options = new ResolveOptions();
+            options.setValidate(false);
+            return resolveEngine.findModule(mrid, options);
+        } finally {
+            popContext();
+        }
+    }
+
+    public ModuleEntry[] listModuleEntries(OrganisationEntry org) {
+        pushContext();
+        try {
+            return searchEngine.listModuleEntries(org);
+        } finally {
+            popContext();
+        }
+    }
+
+    public ModuleId[] listModules(ModuleId criteria, PatternMatcher matcher) {
+        pushContext();
+        try {
+            return searchEngine.listModules(criteria, matcher);
+        } finally {
+            popContext();
+        }
+    }
+
+    public ModuleRevisionId[] listModules(ModuleRevisionId criteria, PatternMatcher matcher) {
+        pushContext();
+        try {
+            return searchEngine.listModules(criteria, matcher);
+        } finally {
+            popContext();
+        }
+    }
+
+    public String[] listModules(String org) {
+        pushContext();
+        try {
+            return searchEngine.listModules(org);
+        } finally {
+            popContext();
+        }
+    }
+
+    public OrganisationEntry[] listOrganisationEntries() {
+        pushContext();
+        try {
+            return searchEngine.listOrganisationEntries();
+        } finally {
+            popContext();
+        }
+    }
+
+    public String[] listOrganisations() {
+        pushContext();
+        try {
+            return searchEngine.listOrganisations();
+        } finally {
+            popContext();
+        }
+    }
+
+    public RevisionEntry[] listRevisionEntries(ModuleEntry module) {
+        pushContext();
+        try {
+            return searchEngine.listRevisionEntries(module);
+        } finally {
+            popContext();
+        }
+    }
+
+    public String[] listRevisions(String org, String module) {
+        pushContext();
+        try {
+            return searchEngine.listRevisions(org, module);
+        } finally {
+            popContext();
+        }
+    }
+
+    public String[] listTokenValues(String token, Map otherTokenValues) {
+        pushContext();
+        try {
+            return searchEngine.listTokenValues(token, otherTokenValues);
+        } finally {
+            popContext();
+        }
+    }
+
+    // ///////////////////////////////////////////////////////////////////////
+    // INTERRUPTIONS
+    // ///////////////////////////////////////////////////////////////////////
+
+    /**
+     * Interrupts the current running operation, no later than interruptTimeout milliseconds after
+     * the call
+     */
+    public void interrupt() {
+        Thread operatingThread = IvyContext.getContext().getOperatingThread();
+        interrupt(operatingThread);
+    }
+
+    /**
+     * Interrupts the current running operation in the given operating thread, no later than
+     * interruptTimeout milliseconds after the call
+     */
+    public void interrupt(Thread operatingThread) {
+        if (operatingThread != null && operatingThread.isAlive()) {
+            if (operatingThread == Thread.currentThread()) {
+                throw new IllegalStateException("cannot call interrupt from ivy operating thread");
+            }
+            Message.verbose("interrupting operating thread...");
+            operatingThread.interrupt();
+            synchronized (this) {
+                interrupted = true;
+            }
+            try {
+                Message.verbose("waiting clean interruption of operating thread");
+                operatingThread.join(settings.getInterruptTimeout());
+            } catch (InterruptedException e) {
+                // reset thread interrupt status
+                Thread.currentThread().interrupt();
+            }
+            if (operatingThread.isAlive()) {
+                Message.warn("waited clean interruption for too long: stopping operating thread");
+                operatingThread.stop();
+            }
+            synchronized (this) {
+                interrupted = false;
+            }
+        }
+    }
+
+    public synchronized boolean isInterrupted() {
+        return interrupted;
+    }
+
+    /**
+     * Check if the current operation has been interrupted, and if it is the case, throw a runtime
+     * exception
+     */
+    public void checkInterrupted() {
+        if (isInterrupted()) {
+            Message.info("operation interrupted");
+            throw new RuntimeException("operation interrupted");
+        }
+    }
+
+    public static String getWorkingRevision() {
+        return "working@" + HostUtil.getLocalHostName();
+    }
+
+    public ResolutionCacheManager getResolutionCacheManager() {
+        return settings.getResolutionCacheManager();
+    }
+
+    private void assertBound() {
+        if (!bound) {
+            bind();
+        }
+    }
+
+    private void postConfigure() {
+        Collection triggers = settings.getTriggers();
+        for (Iterator iter = triggers.iterator(); iter.hasNext();) {
+            Trigger trigger = (Trigger) iter.next();
+            eventManager.addIvyListener(trigger, trigger.getEventFilter());
+        }
+
+        for (Iterator iter = settings.getResolvers().iterator(); iter.hasNext();) {
+            DependencyResolver resolver = (DependencyResolver) iter.next();
+            if (resolver instanceof BasicResolver) {
+                ((BasicResolver) resolver).setEventManager(eventManager);
+            }
+        }
+    }
+
+    public String getVariable(String name) {
+        pushContext();
+        try {
+            assertBound();
+            return settings.getVariable(name);
+        } finally {
+            popContext();
+        }
+    }
+
+    public String substitute(String str) {
+        pushContext();
+        try {
+            assertBound();
+            return settings.substitute(str);
+        } finally {
+            popContext();
+        }
+    }
+
+    public void setVariable(String varName, String value) {
+        pushContext();
+        try {
+            assertBound();
+            settings.setVariable(varName, value);
+        } finally {
+            popContext();
+        }
+    }
+
+    // ///////////////////////////////////////////////////////////////////
+    // GETTERS / SETTERS
+    // ///////////////////////////////////////////////////////////////////
+
+    public IvySettings getSettings() {
+        return settings;
+    }
+
+    public EventManager getEventManager() {
+        return eventManager;
+    }
+
+    public CheckEngine getCheckEngine() {
+        return checkEngine;
+    }
+
+    public void setCheckEngine(CheckEngine checkEngine) {
+        this.checkEngine = checkEngine;
+    }
+
+    public DeliverEngine getDeliverEngine() {
+        return deliverEngine;
+    }
+
+    public void setDeliverEngine(DeliverEngine deliverEngine) {
+        this.deliverEngine = deliverEngine;
+    }
+
+    public InstallEngine getInstallEngine() {
+        return installEngine;
+    }
+
+    public void setInstallEngine(InstallEngine installEngine) {
+        this.installEngine = installEngine;
+    }
+
+    public PublishEngine getPublishEngine() {
+        return publishEngine;
+    }
+
+    public void setPublishEngine(PublishEngine publishEngine) {
+        this.publishEngine = publishEngine;
+    }
+
+    public ResolveEngine getResolveEngine() {
+        return resolveEngine;
+    }
+
+    public void setResolveEngine(ResolveEngine resolveEngine) {
+        this.resolveEngine = resolveEngine;
+    }
+
+    public RetrieveEngine getRetrieveEngine() {
+        return retrieveEngine;
+    }
+
+    public void setRetrieveEngine(RetrieveEngine retrieveEngine) {
+        this.retrieveEngine = retrieveEngine;
+    }
+
+    public SearchEngine getSearchEngine() {
+        return searchEngine;
+    }
+
+    public void setSearchEngine(SearchEngine searchEngine) {
+        this.searchEngine = searchEngine;
+    }
+
+    public SortEngine getSortEngine() {
+        return sortEngine;
+    }
+
+    public void setSortEngine(SortEngine sortEngine) {
+        this.sortEngine = sortEngine;
+    }
+
+    public RepositoryManagementEngine getRepositoryEngine() {
+        return repositoryEngine;
+    }
+
+    public void setRepositoryEngine(RepositoryManagementEngine repositoryEngine) {
+        this.repositoryEngine = repositoryEngine;
+    }
+
+    public void setEventManager(EventManager eventManager) {
+        this.eventManager = eventManager;
+    }
+
+    public void setSettings(IvySettings settings) {
+        this.settings = settings;
+    }
+
+    public MessageLoggerEngine getLoggerEngine() {
+        return loggerEngine;
+    }
+}
diff --git a/src/java/org/apache/ivy/Ivy14.java b/src/java/org/apache/ivy/Ivy14.java
new file mode 100644
index 0000000..0ae8b7a
--- /dev/null
+++ b/src/java/org/apache/ivy/Ivy14.java
@@ -0,0 +1,502 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.text.ParseException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.ivy.core.deliver.DeliverOptions;
+import org.apache.ivy.core.deliver.PublishingDependencyRevisionResolver;
+import org.apache.ivy.core.install.InstallOptions;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.publish.PublishOptions;
+import org.apache.ivy.core.report.ArtifactDownloadReport;
+import org.apache.ivy.core.report.ResolveReport;
+import org.apache.ivy.core.resolve.DownloadOptions;
+import org.apache.ivy.core.resolve.IvyNode;
+import org.apache.ivy.core.resolve.ResolveOptions;
+import org.apache.ivy.core.resolve.ResolvedModuleRevision;
+import org.apache.ivy.core.retrieve.RetrieveOptions;
+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.core.sort.SilentNonMatchingVersionReporter;
+import org.apache.ivy.core.sort.SortOptions;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.apache.ivy.util.Message;
+import org.apache.ivy.util.filter.Filter;
+import org.apache.ivy.util.filter.FilterHelper;
+
+/**
+ * This class can be used for easy migration from Ivy 1.4 API.
+ * <p>
+ * Indeed, Ivy 2.0 API has changed substantially, so it can take time to migrate existing code using
+ * Ivy 1.4 API to the new API. Using this class it's really easy: replace your instance of Ivy by an
+ * instance of this class.
+ * <p>
+ * For instance, where you were doing:
+ * 
+ * <pre>
+ * Ivy ivy = new Ivy();
+ * </pre>
+ * 
+ * do instead:
+ * 
+ * <pre>
+ * Ivy14 ivy = new Ivy14();
+ * </pre>
+ * 
+ * And that should be enough in most cases!
+ */
+public class Ivy14 {
+    // CheckStyle:ParameterNumberCheck OFF
+    // don't check parameter numbers, since this class is here for backward compatibility
+
+    private Ivy ivy;
+
+    public Ivy14() {
+        this(Ivy.newInstance());
+    }
+
+    public Ivy14(Ivy ivy) {
+        this.ivy = ivy;
+    }
+
+    public boolean check(URL ivyFile, String resolvername) {
+        return ivy.check(ivyFile, resolvername);
+    }
+
+    public void configure(File settingsFile) throws ParseException, IOException {
+        ivy.configure(settingsFile);
+    }
+
+    public void configure(URL settingsURL) throws ParseException, IOException {
+        ivy.configure(settingsURL);
+    }
+
+    public void configureDefault() throws ParseException, IOException {
+        ivy.configureDefault();
+    }
+
+    public void deliver(ModuleRevisionId mrid, String revision, File cache, String destIvyPattern,
+            String status, Date pubdate, PublishingDependencyRevisionResolver pdrResolver,
+            boolean validate, boolean resolveDynamicRevisions) throws IOException, ParseException {
+        ivy.deliver(mrid, revision, destIvyPattern, new DeliverOptions(status, pubdate,
+                pdrResolver, validate, resolveDynamicRevisions, null));
+    }
+
+    public void deliver(ModuleRevisionId mrid, String revision, File cache, String destIvyPattern,
+            String status, Date pubdate, PublishingDependencyRevisionResolver pdrResolver,
+            boolean validate) throws IOException, ParseException {
+        deliver(mrid, revision, cache, destIvyPattern, status, pubdate, pdrResolver, validate, true);
+    }
+
+    public Map determineArtifactsToCopy(ModuleId moduleId, String[] confs, File cache,
+            String destFilePattern, String destIvyPattern, Filter artifactFilter)
+            throws ParseException, IOException {
+        return ivy.getRetrieveEngine().determineArtifactsToCopy(
+            new ModuleRevisionId(moduleId, Ivy.getWorkingRevision()),
+            destFilePattern,
+            new RetrieveOptions().setConfs(confs).setDestIvyPattern(destIvyPattern)
+                    .setArtifactFilter(artifactFilter));
+    }
+
+    public Map determineArtifactsToCopy(ModuleId moduleId, String[] confs, File cache,
+            String destFilePattern, String destIvyPattern) throws ParseException, IOException {
+        return ivy.getRetrieveEngine().determineArtifactsToCopy(
+            new ModuleRevisionId(moduleId, Ivy.getWorkingRevision()), destFilePattern,
+            new RetrieveOptions().setConfs(confs).setDestIvyPattern(destIvyPattern));
+    }
+
+    public ArtifactDownloadReport download(Artifact artifact, File cache, boolean useOrigin) {
+        Message.deprecated("using cache and useOrigin when calling download is not supported anymore");
+        return ivy.getResolveEngine().download(artifact, new DownloadOptions());
+    }
+
+    public ResolvedModuleRevision findModule(ModuleRevisionId id) {
+        ResolveOptions options = new ResolveOptions();
+        options.setValidate(false);
+        return ivy.getResolveEngine().findModule(id, options);
+    }
+
+    public IvyNode[] getDependencies(ModuleDescriptor md, String[] confs, File cache, Date date,
+            ResolveReport report, boolean validate, boolean transitive) {
+        return ivy.getResolveEngine().getDependencies(
+            md,
+            newResolveOptions(confs, null, cache, date, validate, false, transitive, false, true,
+                true, FilterHelper.NO_FILTER), report);
+    }
+
+    public IvyNode[] getDependencies(ModuleDescriptor md, String[] confs, File cache, Date date,
+            ResolveReport report, boolean validate) {
+        return ivy.getResolveEngine().getDependencies(
+            md,
+            newResolveOptions(confs, null, cache, date, validate, false, true, false, true, true,
+                FilterHelper.NO_FILTER), report);
+    }
+
+    public IvyNode[] getDependencies(URL ivySource, String[] confs, File cache, Date date,
+            boolean validate) throws ParseException, IOException {
+        return ivy.getResolveEngine().getDependencies(
+            ivySource,
+            newResolveOptions(confs, null, cache, date, validate, false, true, false, true, true,
+                FilterHelper.NO_FILTER));
+    }
+
+    public String getVariable(String name) {
+        return ivy.getVariable(name);
+    }
+
+    public ResolveReport install(ModuleRevisionId mrid, String from, String to, boolean transitive,
+            boolean validate, boolean overwrite, Filter artifactFilter, File cache,
+            String matcherName) throws IOException {
+        return ivy.install(mrid, from, to, new InstallOptions().setTransitive(transitive)
+                .setValidate(validate).setOverwrite(overwrite).setArtifactFilter(artifactFilter)
+                .setMatcherName(matcherName));
+    }
+
+    public void interrupt() {
+        ivy.interrupt();
+    }
+
+    public void interrupt(Thread operatingThread) {
+        ivy.interrupt(operatingThread);
+    }
+
+    public boolean isInterrupted() {
+        return ivy.isInterrupted();
+    }
+
+    public ModuleEntry[] listModuleEntries(OrganisationEntry org) {
+        return ivy.listModuleEntries(org);
+    }
+
+    public ModuleId[] listModules(ModuleId criteria, PatternMatcher matcher) {
+        return ivy.listModules(criteria, matcher);
+    }
+
+    public ModuleRevisionId[] listModules(ModuleRevisionId criteria, PatternMatcher matcher) {
+        return ivy.listModules(criteria, matcher);
+    }
+
+    public String[] listModules(String org) {
+        return ivy.listModules(org);
+    }
+
+    public OrganisationEntry[] listOrganisationEntries() {
+        return ivy.listOrganisationEntries();
+    }
+
+    public String[] listOrganisations() {
+        return ivy.listOrganisations();
+    }
+
+    public RevisionEntry[] listRevisionEntries(ModuleEntry module) {
+        return ivy.listRevisionEntries(module);
+    }
+
+    public String[] listRevisions(String org, String module) {
+        return ivy.listRevisions(org, module);
+    }
+
+    public String[] listTokenValues(String token, Map otherTokenValues) {
+        return ivy.listTokenValues(token, otherTokenValues);
+    }
+
+    public Collection publish(ModuleDescriptor md, DependencyResolver resolver,
+            Collection srcArtifactPattern, String srcIvyPattern, Artifact[] extraArtifacts,
+            boolean overwrite, String conf) throws IOException {
+        return ivy.getPublishEngine().publish(
+            md,
+            srcArtifactPattern,
+            resolver,
+            new PublishOptions().setSrcIvyPattern(srcIvyPattern).setExtraArtifacts(extraArtifacts)
+                    .setOverwrite(overwrite).setConfs(splitConfs(conf)));
+    }
+
+    public Collection publish(ModuleRevisionId mrid, String pubrevision, File cache,
+            Collection srcArtifactPattern, String resolverName, String srcIvyPattern,
+            String status, Date pubdate, Artifact[] extraArtifacts, boolean validate,
+            boolean overwrite, boolean update, String conf) throws IOException {
+        return ivy.publish(
+            mrid,
+            srcArtifactPattern,
+            resolverName,
+            new PublishOptions().setStatus(status).setPubdate(pubdate).setPubrevision(pubrevision)
+                    .setSrcIvyPattern(srcIvyPattern).setExtraArtifacts(extraArtifacts)
+                    .setUpdate(update).setValidate(validate).setOverwrite(overwrite)
+                    .setConfs(splitConfs(conf)));
+    }
+
+    public Collection publish(ModuleRevisionId mrid, String pubrevision, File cache,
+            String srcArtifactPattern, String resolverName, String srcIvyPattern, boolean validate,
+            boolean overwrite) throws IOException {
+        return ivy.publish(mrid, Collections.singleton(srcArtifactPattern), resolverName,
+            new PublishOptions().setPubrevision(pubrevision).setSrcIvyPattern(srcIvyPattern)
+                    .setValidate(validate).setOverwrite(overwrite));
+    }
+
+    public Collection publish(ModuleRevisionId mrid, String pubrevision, File cache,
+            String srcArtifactPattern, String resolverName, String srcIvyPattern, boolean validate)
+            throws IOException {
+        return ivy.publish(mrid, Collections.singleton(srcArtifactPattern), resolverName,
+            new PublishOptions().setPubrevision(pubrevision).setSrcIvyPattern(srcIvyPattern)
+                    .setValidate(validate));
+    }
+
+    public Collection publish(ModuleRevisionId mrid, String pubrevision, File cache,
+            String srcArtifactPattern, String resolverName, String srcIvyPattern, String status,
+            Date pubdate, Artifact[] extraArtifacts, boolean validate, boolean overwrite,
+            boolean update, String conf) throws IOException {
+        return ivy.publish(
+            mrid,
+            Collections.singleton(srcArtifactPattern),
+            resolverName,
+            new PublishOptions().setStatus(status).setPubdate(pubdate).setPubrevision(pubrevision)
+                    .setSrcIvyPattern(srcIvyPattern).setExtraArtifacts(extraArtifacts)
+                    .setUpdate(update).setValidate(validate).setOverwrite(overwrite)
+                    .setConfs(splitConfs(conf)));
+    }
+
+    public ResolveReport resolve(File ivySource) throws ParseException, IOException {
+        return ivy.resolve(ivySource);
+    }
+
+    public ResolveReport resolve(ModuleDescriptor md, String[] confs, File cache, Date date,
+            boolean validate, boolean useCacheOnly, boolean transitive, boolean useOrigin,
+            boolean download, boolean outputReport, Filter artifactFilter) throws ParseException,
+            IOException {
+        return ivy.resolve(
+            md,
+            newResolveOptions(confs, null, cache, date, validate, useCacheOnly, transitive,
+                useOrigin, download, outputReport, artifactFilter));
+    }
+
+    private ResolveOptions newResolveOptions(String[] confs, String revision, File cache,
+            Date date, boolean validate, boolean useCacheOnly, boolean transitive,
+            boolean useOrigin, boolean download, boolean outputReport, Filter artifactFilter) {
+        if (useOrigin) {
+            ivy.getSettings().useDeprecatedUseOrigin();
+        }
+        return new ResolveOptions().setConfs(confs).setRevision(revision).setValidate(validate)
+                .setUseCacheOnly(useCacheOnly).setTransitive(transitive).setDownload(download)
+                .setOutputReport(outputReport).setArtifactFilter(artifactFilter);
+    }
+
+    public ResolveReport resolve(ModuleDescriptor md, String[] confs, File cache, Date date,
+            boolean validate, boolean useCacheOnly, boolean transitive, boolean download,
+            boolean outputReport, Filter artifactFilter) throws ParseException, IOException {
+        return ivy.resolve(
+            md,
+            newResolveOptions(confs, null, cache, date, validate, useCacheOnly, transitive, false,
+                download, outputReport, artifactFilter));
+    }
+
+    public ResolveReport resolve(ModuleDescriptor md, String[] confs, File cache, Date date,
+            boolean validate, boolean useCacheOnly, boolean transitive, Filter artifactFilter)
+            throws ParseException, IOException {
+        return ivy.resolve(
+            md,
+            newResolveOptions(confs, null, cache, date, validate, useCacheOnly, transitive, false,
+                true, true, artifactFilter));
+    }
+
+    public ResolveReport resolve(ModuleDescriptor md, String[] confs, File cache, Date date,
+            boolean validate, boolean useCacheOnly, Filter artifactFilter) throws ParseException,
+            IOException {
+        return ivy.resolve(
+            md,
+            newResolveOptions(confs, null, cache, date, validate, useCacheOnly, true, false, true,
+                true, artifactFilter));
+    }
+
+    public ResolveReport resolve(ModuleRevisionId mrid, String[] confs, boolean transitive,
+            boolean changing, File cache, Date date, boolean validate, boolean useCacheOnly,
+            boolean useOrigin, Filter artifactFilter) throws ParseException, IOException {
+        return ivy.resolve(
+            mrid,
+            newResolveOptions(confs, null, cache, date, validate, useCacheOnly, transitive,
+                useOrigin, true, true, artifactFilter), changing);
+    }
+
+    public ResolveReport resolve(ModuleRevisionId mrid, String[] confs, boolean transitive,
+            boolean changing, File cache, Date date, boolean validate, boolean useCacheOnly,
+            Filter artifactFilter) throws ParseException, IOException {
+        return ivy.resolve(
+            mrid,
+            newResolveOptions(confs, null, cache, date, validate, useCacheOnly, transitive, false,
+                true, true, artifactFilter), changing);
+    }
+
+    public ResolveReport resolve(ModuleRevisionId mrid, String[] confs) throws ParseException,
+            IOException {
+        return ivy.resolve(
+            mrid,
+            newResolveOptions(confs, null, ivy.getSettings().getDefaultCache(), null, true, false,
+                true, false, true, true, FilterHelper.NO_FILTER), false);
+    }
+
+    public ResolveReport resolve(URL ivySource, String revision, String[] confs, File cache,
+            Date date, boolean validate, boolean useCacheOnly, boolean transitive,
+            boolean useOrigin, Filter artifactFilter) throws ParseException, IOException {
+        return ivy.resolve(
+            ivySource,
+            newResolveOptions(confs, revision, cache, date, validate, useCacheOnly, transitive,
+                useOrigin, true, true, artifactFilter));
+    }
+
+    public ResolveReport resolve(URL ivySource, String revision, String[] confs, File cache,
+            Date date, boolean validate, boolean useCacheOnly, boolean transitive,
+            Filter artifactFilter) throws ParseException, IOException {
+        return ivy.resolve(
+            ivySource,
+            newResolveOptions(confs, revision, cache, date, validate, useCacheOnly, transitive,
+                false, true, true, artifactFilter));
+    }
+
+    public ResolveReport resolve(URL ivySource, String revision, String[] confs, File cache,
+            Date date, boolean validate, boolean useCacheOnly, Filter artifactFilter)
+            throws ParseException, IOException {
+        return ivy.resolve(
+            ivySource,
+            newResolveOptions(confs, revision, cache, date, validate, useCacheOnly, true, false,
+                true, true, artifactFilter));
+    }
+
+    public ResolveReport resolve(URL ivySource, String revision, String[] confs, File cache,
+            Date date, boolean validate, boolean useCacheOnly) throws ParseException, IOException {
+        return ivy.resolve(
+            ivySource,
+            newResolveOptions(confs, revision, cache, date, validate, useCacheOnly, true, false,
+                true, true, FilterHelper.NO_FILTER));
+    }
+
+    public ResolveReport resolve(URL ivySource, String revision, String[] confs, File cache,
+            Date date, boolean validate) throws ParseException, IOException {
+        return ivy.resolve(
+            ivySource,
+            newResolveOptions(confs, revision, cache, date, validate, false, true, false, true,
+                true, FilterHelper.NO_FILTER));
+    }
+
+    public ResolveReport resolve(URL ivySource) throws ParseException, IOException {
+        return ivy.resolve(ivySource);
+    }
+
+    public int retrieve(ModuleId moduleId, String[] confs, File cache, String destFilePattern,
+            String destIvyPattern, Filter artifactFilter, boolean sync, boolean useOrigin,
+            boolean makeSymlinks) {
+        try {
+            return ivy.retrieve(new ModuleRevisionId(moduleId, Ivy.getWorkingRevision()),
+                destFilePattern,
+                new RetrieveOptions().setConfs(confs).setDestIvyPattern(destIvyPattern)
+                        .setArtifactFilter(artifactFilter).setSync(sync).setUseOrigin(useOrigin)
+                        .setMakeSymlinks(makeSymlinks));
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public int retrieve(ModuleId moduleId, String[] confs, File cache, String destFilePattern,
+            String destIvyPattern, Filter artifactFilter, boolean sync, boolean useOrigin) {
+        try {
+            return ivy.retrieve(new ModuleRevisionId(moduleId, Ivy.getWorkingRevision()),
+                destFilePattern,
+                new RetrieveOptions().setConfs(confs).setDestIvyPattern(destIvyPattern)
+                        .setArtifactFilter(artifactFilter).setSync(sync).setUseOrigin(useOrigin));
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public int retrieve(ModuleId moduleId, String[] confs, File cache, String destFilePattern,
+            String destIvyPattern, Filter artifactFilter) {
+        try {
+            return ivy.retrieve(new ModuleRevisionId(moduleId, Ivy.getWorkingRevision()),
+                destFilePattern,
+                new RetrieveOptions().setConfs(confs).setDestIvyPattern(destIvyPattern)
+                        .setArtifactFilter(artifactFilter));
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public int retrieve(ModuleId moduleId, String[] confs, File cache, String destFilePattern,
+            String destIvyPattern) {
+        try {
+            return ivy.retrieve(new ModuleRevisionId(moduleId, Ivy.getWorkingRevision()),
+                destFilePattern,
+                new RetrieveOptions().setConfs(confs).setDestIvyPattern(destIvyPattern));
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public int retrieve(ModuleId moduleId, String[] confs, File cache, String destFilePattern) {
+        try {
+            return ivy.retrieve(new ModuleRevisionId(moduleId, Ivy.getWorkingRevision()),
+                destFilePattern, new RetrieveOptions().setConfs(confs));
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public void setVariable(String varName, String value) {
+        ivy.setVariable(varName, value);
+    }
+
+    public List sortModuleDescriptors(Collection moduleDescriptors) {
+        return ivy
+                .sortModuleDescriptors(moduleDescriptors, new SortOptions()
+                        .setNonMatchingVersionReporter(new SilentNonMatchingVersionReporter()));
+    }
+
+    public List sortNodes(Collection nodes) {
+        return ivy
+                .sortNodes(nodes, new SortOptions()
+                        .setNonMatchingVersionReporter(new SilentNonMatchingVersionReporter()));
+    }
+
+    public String substitute(String str) {
+        return ivy.substitute(str);
+    }
+
+    private String[] splitConfs(String conf) {
+        if (conf == null || "".equals(conf)) {
+            return null;
+        }
+        String[] confs = conf.split(",");
+        for (int i = 0; i < confs.length; i++) {
+            confs[i] = confs[i].trim();
+        }
+        return confs;
+    }
+
+    // CheckStyle:ParameterNumberCheck ON
+}
diff --git a/src/java/org/apache/ivy/Main.java b/src/java/org/apache/ivy/Main.java
new file mode 100644
index 0000000..a274e64
--- /dev/null
+++ b/src/java/org/apache/ivy/Main.java
@@ -0,0 +1,591 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import org.apache.ivy.core.cache.ResolutionCacheManager;
+import org.apache.ivy.core.deliver.DeliverOptions;
+import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
+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.publish.PublishOptions;
+import org.apache.ivy.core.report.ArtifactDownloadReport;
+import org.apache.ivy.core.report.ResolveReport;
+import org.apache.ivy.core.resolve.ResolveOptions;
+import org.apache.ivy.core.retrieve.RetrieveOptions;
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorWriter;
+import org.apache.ivy.plugins.report.XmlReportParser;
+import org.apache.ivy.util.DefaultMessageLogger;
+import org.apache.ivy.util.Message;
+import org.apache.ivy.util.cli.CommandLine;
+import org.apache.ivy.util.cli.CommandLineParser;
+import org.apache.ivy.util.cli.OptionBuilder;
+import org.apache.ivy.util.cli.ParseException;
+import org.apache.ivy.util.filter.FilterHelper;
+import org.apache.ivy.util.url.CredentialsStore;
+import org.apache.ivy.util.url.URLHandler;
+import org.apache.ivy.util.url.URLHandlerDispatcher;
+import org.apache.ivy.util.url.URLHandlerRegistry;
+
+/**
+ * Class used to launch ivy as a standalone tool.
+ * <p>
+ * Valid arguments can be obtained with the -? argument.
+ */
+public final class Main {
+    private static final int HELP_WIDTH = 80;
+
+    static CommandLineParser getParser() {
+        return new CommandLineParser()
+                .addCategory("settings options")
+                .addOption(
+                    new OptionBuilder("settings").arg("settingsfile")
+                            .description("use given file for settings").create())
+                .addOption(
+                    new OptionBuilder("cache").arg("cachedir")
+                            .description("use given directory for cache").create())
+                .addOption(
+                    new OptionBuilder("novalidate").description(
+                        "do not validate ivy files against xsd").create())
+                .addOption(
+                    new OptionBuilder("m2compatible").description("use maven2 compatibility")
+                            .create())
+                .addOption(
+                    new OptionBuilder("conf").arg("settingsfile").deprecated()
+                            .description("use given file for settings").create())
+                .addOption(
+                    new OptionBuilder("useOrigin")
+                            .deprecated()
+                            .description(
+                                "use original artifact location "
+                                        + "with local resolvers instead of copying to the cache")
+                            .create())
+
+                .addCategory("resolve options")
+                .addOption(
+                    new OptionBuilder("ivy").arg("ivyfile")
+                            .description("use given file as ivy file").create())
+                .addOption(
+                    new OptionBuilder("refresh").description("refresh dynamic resolved revisions")
+                            .create())
+                .addOption(
+                    new OptionBuilder("dependency")
+                            .arg("organisation")
+                            .arg("module")
+                            .arg("revision")
+                            .description(
+                                "use this instead of ivy file to do the rest "
+                                        + "of the work with this as a dependency.").create())
+                .addOption(
+                    new OptionBuilder("confs").arg("configurations").countArgs(false)
+                            .description("resolve given configurations").create())
+                .addOption(
+                    new OptionBuilder("types").arg("types").countArgs(false)
+                            .description("comma separated list of accepted artifact types")
+                            .create())
+                .addOption(
+                    new OptionBuilder("mode").arg("resolvemode")
+                            .description("the resolve mode to use").create())
+                .addOption(
+                    new OptionBuilder("notransitive").description(
+                        "do not resolve dependencies transitively").create())
+
+                .addCategory("retrieve options")
+                .addOption(
+                    new OptionBuilder("retrieve").arg("retrievepattern")
+                            .description("use given pattern as retrieve pattern").create())
+                .addOption(
+                    new OptionBuilder("ivypattern").arg("pattern")
+                            .description("use given pattern to copy the ivy files").create())
+                .addOption(
+                    new OptionBuilder("sync").description("use sync mode for retrieve").create())
+                .addOption(
+                    new OptionBuilder("symlink").description("create symbolic links").create())
+
+                .addCategory("cache path options")
+                .addOption(
+                    new OptionBuilder("cachepath")
+                            .arg("cachepathfile")
+                            .description(
+                                "outputs a classpath consisting of all dependencies in cache "
+                                        + "(including transitive ones) "
+                                        + "of the given ivy file to the given cachepathfile")
+                            .create())
+
+                .addCategory("deliver options")
+                .addOption(
+                    new OptionBuilder("deliverto").arg("ivypattern")
+                            .description("use given pattern as resolved ivy file pattern").create())
+
+                .addCategory("publish options")
+                .addOption(
+                    new OptionBuilder("publish").arg("resolvername")
+                            .description("use given resolver to publish to").create())
+                .addOption(
+                    new OptionBuilder("publishpattern").arg("artpattern")
+                            .description("use given pattern to find artifacts to publish").create())
+                .addOption(
+                    new OptionBuilder("revision").arg("revision")
+                            .description("use given revision to publish the module").create())
+                .addOption(
+                    new OptionBuilder("status").arg("status")
+                            .description("use given status to publish the module").create())
+                .addOption(
+                    new OptionBuilder("overwrite").description(
+                        "overwrite files in the repository if they exist").create())
+
+                .addCategory("http auth options")
+                .addOption(
+                    new OptionBuilder("realm").arg("realm")
+                            .description("use given realm for HTTP AUTH").create())
+                .addOption(
+                    new OptionBuilder("host").arg("host")
+                            .description("use given host for HTTP AUTH").create())
+                .addOption(
+                    new OptionBuilder("username").arg("username")
+                            .description("use given username for HTTP AUTH").create())
+                .addOption(
+                    new OptionBuilder("passwd").arg("passwd")
+                            .description("use given password for HTTP AUTH").create())
+
+                .addCategory("launcher options")
+                .addOption(
+                    new OptionBuilder("main").arg("main")
+                            .description("the FQCN of the main class to launch").create())
+                .addOption(
+                    new OptionBuilder("args").arg("args").countArgs(false)
+                            .description("the arguments to give to the launched process").create())
+                .addOption(
+                    new OptionBuilder("cp").arg("cp")
+                            .description("extra classpath to use when launching process").create())
+
+                .addCategory("message options")
+                .addOption(
+                    new OptionBuilder("debug").description("set message level to debug").create())
+                .addOption(
+                    new OptionBuilder("verbose").description("set message level to verbose")
+                            .create())
+                .addOption(
+                    new OptionBuilder("warn").description("set message level to warn").create())
+                .addOption(
+                    new OptionBuilder("error").description("set message level to error").create())
+
+                .addCategory("help options")
+                .addOption(new OptionBuilder("?").description("display this help").create())
+                .addOption(
+                    new OptionBuilder("deprecated").description("show deprecated options").create())
+                .addOption(
+                    new OptionBuilder("version").description("displays version information")
+                            .create());
+    }
+
+    public static void main(String[] args) throws Exception {
+        CommandLineParser parser = getParser();
+        try {
+            run(parser, args);
+            System.exit(0);
+        } catch (ParseException ex) {
+            System.err.println(ex.getMessage());
+            usage(parser, false);
+            System.exit(1);
+        }
+    }
+
+    static void run(CommandLineParser parser, String[] args) throws Exception {
+        // parse the command line arguments
+        CommandLine line = parser.parse(args);
+
+        if (line.hasOption("?")) {
+            usage(parser, line.hasOption("deprecated"));
+            return;
+        }
+
+        if (line.hasOption("version")) {
+            System.out.println("Apache Ivy " + Ivy.getIvyVersion() + " - " + Ivy.getIvyDate()
+                    + " :: " + Ivy.getIvyHomeURL());
+            return;
+        }
+
+        boolean validate = line.hasOption("novalidate") ? false : true;
+
+        Ivy ivy = Ivy.newInstance();
+        initMessage(line, ivy);
+        IvySettings settings = initSettings(line, ivy);
+        ivy.pushContext();
+
+        File cache = new File(settings.substitute(line.getOptionValue("cache", settings
+                .getDefaultCache().getAbsolutePath())));
+
+        if (line.hasOption("cache")) {
+            // override default cache path with user supplied cache path
+            settings.setDefaultCache(cache);
+        }
+
+        if (!cache.exists()) {
+            cache.mkdirs();
+        } else if (!cache.isDirectory()) {
+            error(cache + " is not a directory");
+        }
+
+        String[] confs;
+        if (line.hasOption("confs")) {
+            confs = line.getOptionValues("confs");
+        } else {
+            confs = new String[] {"*"};
+        }
+
+        File ivyfile;
+        if (line.hasOption("dependency")) {
+            String[] dep = line.getOptionValues("dependency");
+            ivyfile = File.createTempFile("ivy", ".xml");
+            ivyfile.deleteOnExit();
+            DefaultModuleDescriptor md = DefaultModuleDescriptor
+                    .newDefaultInstance(ModuleRevisionId.newInstance(dep[0], dep[1] + "-caller",
+                        "working"));
+            DefaultDependencyDescriptor dd = new DefaultDependencyDescriptor(md,
+                    ModuleRevisionId.newInstance(dep[0], dep[1], dep[2]), false, false, true);
+            for (int i = 0; i < confs.length; i++) {
+                dd.addDependencyConfiguration("default", confs[i]);
+            }
+            md.addDependency(dd);
+            XmlModuleDescriptorWriter.write(md, ivyfile);
+            confs = new String[] {"default"};
+        } else {
+            ivyfile = new File(settings.substitute(line.getOptionValue("ivy", "ivy.xml")));
+            if (!ivyfile.exists()) {
+                error("ivy file not found: " + ivyfile);
+            } else if (ivyfile.isDirectory()) {
+                error("ivy file is not a file: " + ivyfile);
+            }
+        }
+
+        if (line.hasOption("useOrigin")) {
+            ivy.getSettings().useDeprecatedUseOrigin();
+        }
+        ResolveOptions resolveOptions = new ResolveOptions()
+                .setConfs(confs)
+                .setValidate(validate)
+                .setResolveMode(line.getOptionValue("mode"))
+                .setArtifactFilter(
+                    FilterHelper.getArtifactTypeFilter(line.getOptionValues("types")));
+        if (line.hasOption("notransitive")) {
+            resolveOptions.setTransitive(false);
+        }
+        if (line.hasOption("refresh")) {
+            resolveOptions.setRefresh(true);
+        }
+        ResolveReport report = ivy.resolve(ivyfile.toURI().toURL(), resolveOptions);
+        if (report.hasError()) {
+            System.exit(1);
+        }
+        ModuleDescriptor md = report.getModuleDescriptor();
+
+        if (confs.length == 1 && "*".equals(confs[0])) {
+            confs = md.getConfigurationsNames();
+        }
+        if (line.hasOption("retrieve")) {
+            String retrievePattern = settings.substitute(line.getOptionValue("retrieve"));
+            if (retrievePattern.indexOf("[") == -1) {
+                retrievePattern = retrievePattern + "/lib/[conf]/[artifact].[ext]";
+            }
+            String ivyPattern = settings.substitute(line.getOptionValue("ivypattern"));
+            ivy.retrieve(
+                md.getModuleRevisionId(),
+                retrievePattern,
+                new RetrieveOptions()
+                        .setConfs(confs)
+                        .setSync(line.hasOption("sync"))
+                        .setUseOrigin(line.hasOption("useOrigin"))
+                        .setDestIvyPattern(ivyPattern)
+                        .setArtifactFilter(
+                            FilterHelper.getArtifactTypeFilter(line.getOptionValues("types")))
+                        .setMakeSymlinks(line.hasOption("symlink"))
+                        .setMakeSymlinksInMass(line.hasOption("symlinkmass")));
+        }
+        if (line.hasOption("cachepath")) {
+            outputCachePath(ivy, cache, md, confs,
+                line.getOptionValue("cachepath", "ivycachepath.txt"));
+        }
+
+        if (line.hasOption("revision")) {
+            ivy.deliver(
+                md.getResolvedModuleRevisionId(),
+                settings.substitute(line.getOptionValue("revision")),
+                settings.substitute(line.getOptionValue("deliverto", "ivy-[revision].xml")),
+                DeliverOptions.newInstance(settings)
+                        .setStatus(settings.substitute(line.getOptionValue("status", "release")))
+                        .setValidate(validate));
+            if (line.hasOption("publish")) {
+                ivy.publish(
+                    md.getResolvedModuleRevisionId(),
+                    Collections.singleton(settings.substitute(line.getOptionValue("publishpattern",
+                        "distrib/[type]s/[artifact]-[revision].[ext]"))),
+                    line.getOptionValue("publish"),
+                    new PublishOptions()
+                            .setPubrevision(settings.substitute(line.getOptionValue("revision")))
+                            .setValidate(validate)
+                            .setSrcIvyPattern(
+                                settings.substitute(line.getOptionValue("deliverto",
+                                    "ivy-[revision].xml")))
+                            .setOverwrite(line.hasOption("overwrite")));
+            }
+        }
+        if (line.hasOption("main")) {
+            // check if the option cp has been set
+            List fileList = getExtraClasspathFileList(line);
+
+            // merge -args and left over args
+            String[] fargs = line.getOptionValues("args");
+            if (fargs == null) {
+                fargs = new String[0];
+            }
+            String[] extra = line.getLeftOverArgs();
+            if (extra == null) {
+                extra = new String[0];
+            }
+            String[] params = new String[fargs.length + extra.length];
+            System.arraycopy(fargs, 0, params, 0, fargs.length);
+            System.arraycopy(extra, 0, params, fargs.length, extra.length);
+            // invoke with given main class and merged params
+            invoke(ivy, cache, md, confs, fileList, line.getOptionValue("main"), params);
+        }
+        ivy.getLoggerEngine().popLogger();
+        ivy.popContext();
+    }
+
+    /**
+     * Parses the <code>cp</code> option from the command line, and returns a list of {@link File}.
+     * <p>
+     * All the files contained in the returned List exist, non existing files are simply skipped
+     * with a warning.
+     * </p>
+     * 
+     * @param line
+     *            the command line in which the cp option shold be parsed
+     * @return a List of files to include as extra classpath entries, or <code>null</code> if no cp
+     *         option was provided.
+     */
+    private static List/* <File> */getExtraClasspathFileList(CommandLine line) {
+        List fileList = null;
+        if (line.hasOption("cp")) {
+            fileList = new ArrayList/* <File> */();
+            String[] cpArray = line.getOptionValues("cp");
+            for (int index = 0; index < cpArray.length; index++) {
+                StringTokenizer tokenizer = new StringTokenizer(cpArray[index],
+                        System.getProperty("path.separator"));
+                while (tokenizer.hasMoreTokens()) {
+                    String token = tokenizer.nextToken();
+                    File file = new File(token);
+                    if (file.exists()) {
+                        fileList.add(file);
+                    } else {
+                        Message.warn("Skipping extra classpath '" + file
+                                + "' as it does not exist.");
+                    }
+                }
+            }
+        }
+        return fileList;
+    }
+
+    private static IvySettings initSettings(CommandLine line, Ivy ivy)
+            throws java.text.ParseException, IOException, ParseException {
+        IvySettings settings = ivy.getSettings();
+        settings.addAllVariables(System.getProperties());
+        if (line.hasOption("m2compatible")) {
+            settings.setVariable("ivy.default.configuration.m2compatible", "true");
+        }
+
+        configureURLHandler(line.getOptionValue("realm", null), line.getOptionValue("host", null),
+            line.getOptionValue("username", null), line.getOptionValue("passwd", null));
+
+        String settingsPath = line.getOptionValue("settings", "");
+        if ("".equals(settingsPath)) {
+            settingsPath = line.getOptionValue("conf", "");
+            if (!"".equals(settingsPath)) {
+                Message.deprecated("-conf is deprecated, use -settings instead");
+            }
+        }
+        if ("".equals(settingsPath)) {
+            ivy.configureDefault();
+        } else {
+            File conffile = new File(settingsPath);
+            if (!conffile.exists()) {
+                error("ivy configuration file not found: " + conffile);
+            } else if (conffile.isDirectory()) {
+                error("ivy configuration file is not a file: " + conffile);
+            }
+            ivy.configure(conffile);
+        }
+        return settings;
+    }
+
+    private static void initMessage(CommandLine line, Ivy ivy) {
+        if (line.hasOption("debug")) {
+            ivy.getLoggerEngine().pushLogger(new DefaultMessageLogger(Message.MSG_DEBUG));
+        } else if (line.hasOption("verbose")) {
+            ivy.getLoggerEngine().pushLogger(new DefaultMessageLogger(Message.MSG_VERBOSE));
+        } else if (line.hasOption("warn")) {
+            ivy.getLoggerEngine().pushLogger(new DefaultMessageLogger(Message.MSG_WARN));
+        } else if (line.hasOption("error")) {
+            ivy.getLoggerEngine().pushLogger(new DefaultMessageLogger(Message.MSG_ERR));
+        } else {
+            ivy.getLoggerEngine().pushLogger(new DefaultMessageLogger(Message.MSG_INFO));
+        }
+    }
+
+    private static void outputCachePath(Ivy ivy, File cache, ModuleDescriptor md, String[] confs,
+            String outFile) {
+        try {
+            String pathSeparator = System.getProperty("path.separator");
+            StringBuffer buf = new StringBuffer();
+            Collection all = new LinkedHashSet();
+            ResolutionCacheManager cacheMgr = ivy.getResolutionCacheManager();
+            XmlReportParser parser = new XmlReportParser();
+            for (int i = 0; i < confs.length; i++) {
+                String resolveId = ResolveOptions.getDefaultResolveId(md);
+                File report = cacheMgr.getConfigurationResolveReportInCache(resolveId, confs[i]);
+                parser.parse(report);
+
+                all.addAll(Arrays.asList(parser.getArtifactReports()));
+            }
+            for (Iterator iter = all.iterator(); iter.hasNext();) {
+                ArtifactDownloadReport artifact = (ArtifactDownloadReport) iter.next();
+                if (artifact.getLocalFile() != null) {
+                    buf.append(artifact.getLocalFile().getCanonicalPath());
+                    buf.append(pathSeparator);
+                }
+            }
+
+            PrintWriter writer = new PrintWriter(new FileOutputStream(outFile));
+            if (buf.length() > 0) {
+                writer.println(buf.substring(0, buf.length() - pathSeparator.length()));
+            }
+            writer.close();
+            System.out.println("cachepath output to " + outFile);
+
+        } catch (Exception ex) {
+            throw new RuntimeException("impossible to build ivy cache path: " + ex.getMessage(), ex);
+        }
+    }
+
+    private static void invoke(Ivy ivy, File cache, ModuleDescriptor md, String[] confs,
+            List fileList, String mainclass, String[] args) {
+        List urls = new ArrayList();
+
+        // Add option cp (extra classpath) urls
+        if (fileList != null && fileList.size() > 0) {
+            for (Iterator iter = fileList.iterator(); iter.hasNext();) {
+                File file = (File) iter.next();
+                try {
+                    urls.add(file.toURI().toURL());
+                } catch (MalformedURLException e) {
+                    // Should not happen, just ignore.
+                }
+            }
+        }
+
+        try {
+            Collection all = new LinkedHashSet();
+            ResolutionCacheManager cacheMgr = ivy.getResolutionCacheManager();
+            XmlReportParser parser = new XmlReportParser();
+            for (int i = 0; i < confs.length; i++) {
+                String resolveId = ResolveOptions.getDefaultResolveId(md);
+                File report = cacheMgr.getConfigurationResolveReportInCache(resolveId, confs[i]);
+                parser.parse(report);
+
+                all.addAll(Arrays.asList(parser.getArtifactReports()));
+            }
+            for (Iterator iter = all.iterator(); iter.hasNext();) {
+                ArtifactDownloadReport artifact = (ArtifactDownloadReport) iter.next();
+
+                if (artifact.getLocalFile() != null) {
+                    urls.add(artifact.getLocalFile().toURI().toURL());
+                }
+            }
+        } catch (Exception ex) {
+            throw new RuntimeException("impossible to build ivy cache path: " + ex.getMessage(), ex);
+        }
+
+        URLClassLoader classLoader = new URLClassLoader((URL[]) urls.toArray(new URL[urls.size()]),
+                Main.class.getClassLoader());
+
+        try {
+            Class c = classLoader.loadClass(mainclass);
+
+            Method mainMethod = c.getMethod("main", new Class[] {String[].class});
+
+            Thread.currentThread().setContextClassLoader(classLoader);
+            mainMethod.invoke(null, new Object[] {(args == null ? new String[0] : args)});
+        } catch (ClassNotFoundException cnfe) {
+            throw new RuntimeException("Could not find class: " + mainclass, cnfe);
+        } catch (SecurityException e) {
+            throw new RuntimeException("Could not find main method: " + mainclass, e);
+        } catch (NoSuchMethodException e) {
+            throw new RuntimeException("Could not find main method: " + mainclass, e);
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException("No permissions to invoke main method: " + mainclass, e);
+        } catch (InvocationTargetException e) {
+            throw new RuntimeException("Unexpected exception invoking main method: " + mainclass, e);
+        }
+    }
+
+    private static void configureURLHandler(String realm, String host, String username,
+            String passwd) {
+        CredentialsStore.INSTANCE.addCredentials(realm, host, username, passwd);
+
+        URLHandlerDispatcher dispatcher = new URLHandlerDispatcher();
+        URLHandler httpHandler = URLHandlerRegistry.getHttp();
+        dispatcher.setDownloader("http", httpHandler);
+        dispatcher.setDownloader("https", httpHandler);
+        URLHandlerRegistry.setDefault(dispatcher);
+    }
+
+    private static void error(String msg) throws ParseException {
+        throw new ParseException(msg);
+    }
+
+    private static void usage(CommandLineParser parser, boolean showDeprecated) {
+        // automatically generate the help statement
+        PrintWriter pw = new PrintWriter(System.out);
+        parser.printHelp(pw, HELP_WIDTH, "ivy", showDeprecated);
+        pw.flush();
+    }
+
+    private Main() {
+    }
+}
diff --git a/src/java/org/apache/ivy/ant/AddPathTask.java b/src/java/org/apache/ivy/ant/AddPathTask.java
new file mode 100644
index 0000000..2397657
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/AddPathTask.java
@@ -0,0 +1,103 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.Task;
+import org.apache.tools.ant.types.DirSet;
+import org.apache.tools.ant.types.FileList;
+import org.apache.tools.ant.types.FileSet;
+import org.apache.tools.ant.types.Path;
+import org.apache.tools.ant.types.Path.PathElement;
+
+/**
+ * This task is not directly related to ivy, but is useful in some modular build systems. The idea
+ * is to be able to contribute new sub path elements to an existing path.
+ */
+public class AddPathTask extends Task {
+    private String toPath;
+
+    private boolean first = false;
+
+    private Path toAdd;
+
+    public String getTopath() {
+        return toPath;
+    }
+
+    public void setTopath(String toPath) {
+        this.toPath = toPath;
+    }
+
+    public void setProject(Project project) {
+        super.setProject(project);
+        toAdd = new Path(project);
+    }
+
+    public void execute() throws BuildException {
+        Object element = getProject().getReference(toPath);
+        if (element == null) {
+            throw new BuildException("destination path not found: " + toPath);
+        }
+        if (!(element instanceof Path)) {
+            throw new BuildException("destination path is not a path: " + element.getClass());
+        }
+        Path dest = (Path) element;
+        if (first) {
+            // now way to add path elements at te beginning of the existing path: we do the opposite
+            // and replace the reference
+            toAdd.append(dest);
+            getProject().addReference(toPath, toAdd);
+        } else {
+            dest.append(toAdd);
+        }
+    }
+
+    public void add(Path path) throws BuildException {
+        toAdd.add(path);
+    }
+
+    public void addDirset(DirSet dset) throws BuildException {
+        toAdd.addDirset(dset);
+    }
+
+    public void addFilelist(FileList fl) throws BuildException {
+        toAdd.addFilelist(fl);
+    }
+
+    public void addFileset(FileSet fs) throws BuildException {
+        toAdd.addFileset(fs);
+    }
+
+    public Path createPath() throws BuildException {
+        return toAdd.createPath();
+    }
+
+    public PathElement createPathElement() throws BuildException {
+        return toAdd.createPathElement();
+    }
+
+    public boolean isFirst() {
+        return first;
+    }
+
+    public void setFirst(boolean first) {
+        this.first = first;
+    }
+}
diff --git a/src/java/org/apache/ivy/ant/AntBuildTrigger.java b/src/java/org/apache/ivy/ant/AntBuildTrigger.java
new file mode 100644
index 0000000..6b0dd3f
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/AntBuildTrigger.java
@@ -0,0 +1,172 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.ivy.core.IvyContext;
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.core.event.IvyEvent;
+import org.apache.ivy.plugins.trigger.AbstractTrigger;
+import org.apache.ivy.plugins.trigger.Trigger;
+import org.apache.ivy.util.Message;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.taskdefs.Ant;
+import org.apache.tools.ant.taskdefs.Property;
+
+/**
+ * Triggers an ant build on an event occurence.
+ * <p>
+ * Example of use:
+ * 
+ * <pre>
+ * <ant-build-trigger event="pre-resolve-dependency" 
+ *                    filter="revision=latest.integration"
+ *                    antfile="/path/to/[module]/build.xml" 
+ *                    target="compile"/>
+ * </pre>
+ * 
+ * Triggers an ant build for any dependency in asked in latest.integration, just before resolving
+ * the dependency.
+ * </p>
+ * <p>
+ * The onlyonce property is used to tell if the ant build should be triggered only once, or several
+ * times in the same build.
+ * </p>
+ * 
+ * @see AntCallTrigger
+ * @since 1.4
+ */
+public class AntBuildTrigger extends AbstractTrigger implements Trigger {
+    private boolean onlyOnce = true;
+
+    private String target = null;
+
+    private Collection builds = new ArrayList();
+
+    private String buildFilePattern;
+
+    private String prefix;
+
+    public void progress(IvyEvent event) {
+        File f = getBuildFile(event);
+        if (f.exists()) {
+            if (onlyOnce && isBuilt(f)) {
+                Message.verbose("target build file already built, skipping: " + f);
+            } else {
+                Ant ant = new Ant();
+                Project project = (Project) IvyContext
+                        .peekInContextStack(IvyTask.ANT_PROJECT_CONTEXT_KEY);
+                if (project == null) {
+                    project = new Project();
+                    project.init();
+                }
+
+                ant.setProject(project);
+                ant.setTaskName("ant");
+
+                ant.setAntfile(f.getAbsolutePath());
+                ant.setInheritAll(false);
+                String target = getTarget();
+                if (target != null) {
+                    ant.setTarget(target);
+                }
+                Map atts = event.getAttributes();
+                for (Iterator iter = atts.keySet().iterator(); iter.hasNext();) {
+                    String key = (String) iter.next();
+                    String value = (String) atts.get(key);
+                    if (value != null) {
+                        Property p = ant.createProperty();
+                        p.setName(prefix == null ? key : prefix + key);
+                        p.setValue(value);
+                    }
+                }
+
+                Message.verbose("triggering build: " + f + " target=" + target + " for " + event);
+                try {
+                    ant.execute();
+                } catch (BuildException e) {
+                    Message.verbose("Exception occurred while executing target " + target);
+                    throw e;
+                }
+                markBuilt(f);
+
+                Message.debug("triggered build finished: " + f + " target=" + target + " for "
+                        + event);
+            }
+        } else {
+            Message.verbose("no build file found for dependency, skipping: " + f);
+        }
+    }
+
+    private void markBuilt(File f) {
+        builds.add(f.getAbsolutePath());
+    }
+
+    private boolean isBuilt(File f) {
+        return builds.contains(f.getAbsolutePath());
+    }
+
+    private File getBuildFile(IvyEvent event) {
+        return IvyContext
+                .getContext()
+                .getSettings()
+                .resolveFile(
+                    IvyPatternHelper.substituteTokens(getBuildFilePattern(), event.getAttributes()));
+    }
+
+    public String getBuildFilePattern() {
+        return buildFilePattern;
+    }
+
+    public void setAntfile(String pattern) {
+        buildFilePattern = pattern;
+    }
+
+    public String getTarget() {
+        return target;
+    }
+
+    public void setTarget(String target) {
+        this.target = target;
+    }
+
+    public boolean isOnlyonce() {
+        return onlyOnce;
+    }
+
+    public void setOnlyonce(boolean onlyonce) {
+        onlyOnce = onlyonce;
+    }
+
+    public String getPrefix() {
+        return prefix;
+    }
+
+    public void setPrefix(String prefix) {
+        this.prefix = prefix;
+        if (!prefix.endsWith(".")) {
+            this.prefix += ".";
+        }
+    }
+}
diff --git a/src/java/org/apache/ivy/ant/AntCallTrigger.java b/src/java/org/apache/ivy/ant/AntCallTrigger.java
new file mode 100644
index 0000000..005eea1
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/AntCallTrigger.java
@@ -0,0 +1,130 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.ivy.core.IvyContext;
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.core.event.IvyEvent;
+import org.apache.ivy.plugins.trigger.AbstractTrigger;
+import org.apache.ivy.plugins.trigger.Trigger;
+import org.apache.ivy.util.Message;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.taskdefs.CallTarget;
+import org.apache.tools.ant.taskdefs.Property;
+
+/**
+ * Triggers an call to an ant target on an event occurence.
+ * <p>
+ * This trigger only works when ivy is called from an ant build file, otherwise the trigger only log
+ * a failure.
+ * <p>
+ * Example of use in an ivysettings file:
+ * 
+ * <pre>
+ * <ant-call-trigger event="post-download-artifact" filter="type=zip"
+ * target="unzip"/>
+ * </pre>
+ * 
+ * Triggers a call to the target "unzip" for any downloaded artifact of type zip
+ * 
+ * @see AntBuildTrigger
+ * @since 1.4
+ */
+public class AntCallTrigger extends AbstractTrigger implements Trigger {
+    private boolean onlyonce = true;
+
+    private String target = null;
+
+    private Collection calls = new ArrayList();
+
+    private String prefix;
+
+    public void progress(IvyEvent event) {
+        Project project = (Project) IvyContext.peekInContextStack(IvyTask.ANT_PROJECT_CONTEXT_KEY);
+        if (project == null) {
+            Message.info("ant call trigger can only be used from an ant build. Ignoring.");
+            return;
+        }
+        if (onlyonce && isTriggered(event)) {
+            Message.verbose("call already triggered for this event, skipping: " + event);
+        } else {
+            CallTarget call = new CallTarget();
+
+            call.setProject(project);
+            call.setTaskName("antcall");
+
+            Map attributes = event.getAttributes();
+            String target = IvyPatternHelper.substituteTokens(getTarget(), attributes);
+            call.setTarget(target);
+
+            for (Iterator iter = attributes.keySet().iterator(); iter.hasNext();) {
+                String key = (String) iter.next();
+                String value = (String) attributes.get(key);
+                Property p = call.createParam();
+                p.setName(prefix == null ? key : prefix + key);
+                p.setValue(value == null ? "" : value);
+            }
+
+            Message.verbose("triggering ant call: target=" + target + " for " + event);
+            call.execute();
+            markTriggered(event);
+
+            Message.debug("triggered ant call finished: target=" + target + " for " + event);
+        }
+    }
+
+    private void markTriggered(IvyEvent event) {
+        calls.add(event);
+    }
+
+    private boolean isTriggered(IvyEvent event) {
+        return calls.contains(event);
+    }
+
+    public String getTarget() {
+        return target;
+    }
+
+    public void setTarget(String target) {
+        this.target = target;
+    }
+
+    public boolean isOnlyonce() {
+        return onlyonce;
+    }
+
+    public void setOnlyonce(boolean onlyonce) {
+        this.onlyonce = onlyonce;
+    }
+
+    public String getPrefix() {
+        return prefix;
+    }
+
+    public void setPrefix(String prefix) {
+        this.prefix = prefix;
+        if (!prefix.endsWith(".")) {
+            this.prefix += ".";
+        }
+    }
+}
diff --git a/src/java/org/apache/ivy/ant/AntMessageLogger.java b/src/java/org/apache/ivy/ant/AntMessageLogger.java
new file mode 100644
index 0000000..85547f9
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/AntMessageLogger.java
@@ -0,0 +1,150 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import org.apache.ivy.Ivy;
+import org.apache.ivy.util.AbstractMessageLogger;
+import org.apache.ivy.util.Checks;
+import org.apache.ivy.util.MessageLogger;
+import org.apache.tools.ant.BuildEvent;
+import org.apache.tools.ant.BuildListener;
+import org.apache.tools.ant.ProjectComponent;
+import org.apache.tools.ant.Task;
+
+/**
+ * Implementation of the simple message facility for ant.
+ */
+public class AntMessageLogger extends AbstractMessageLogger {
+    private static final int PROGRESS_LOG_PERIOD = 1500;
+
+    /**
+     * Creates and register an {@link AntMessageLogger} for the given {@link Task}, with the given
+     * {@link Ivy} instance.
+     * <p>
+     * The created instance will automatically be unregistered from the Ivy instance when the task
+     * finishes.
+     * </p>
+     * 
+     * @param task
+     *            the task the logger should use for logging
+     * @param ivy
+     *            the ivy instance on which the logger should be registered
+     */
+    public static void register(ProjectComponent task, final Ivy ivy) {
+        MessageLogger current = ivy.getLoggerEngine().peekLogger();
+        if (current instanceof AntMessageLogger && task instanceof Task
+                && ((AntMessageLogger) current).task instanceof Task) {
+            Task currentTask = (Task) ((AntMessageLogger) current).task;
+
+            if ((currentTask.getTaskName() != null)
+                    && currentTask.getTaskName().equals(((Task) task).getTaskName())) {
+                // The current AntMessageLogger already logs with the same
+                // prefix as the given task. So we shouldn't do anything...
+                return;
+            }
+        }
+
+        AntMessageLogger logger = new AntMessageLogger(task);
+        ivy.getLoggerEngine().pushLogger(logger);
+        task.getProject().addBuildListener(new BuildListener() {
+            private int stackDepth = 0;
+
+            public void buildFinished(BuildEvent event) {
+            }
+
+            public void buildStarted(BuildEvent event) {
+            }
+
+            public void targetStarted(BuildEvent event) {
+            }
+
+            public void targetFinished(BuildEvent event) {
+            }
+
+            public void taskStarted(BuildEvent event) {
+                stackDepth++;
+            }
+
+            public void taskFinished(BuildEvent event) {
+                // NB: There is somtimes task created by an other task
+                // in that case, we should not uninit Message. The log should stay associated
+                // with the initial task, except if it was an antcall, ant or subant target
+                // NB2 : Testing the identity of the task is not enought, event.getTask() return
+                // an instance of UnknownElement is wrapping the concrete instance
+                stackDepth--;
+                if (stackDepth == -1) {
+                    ivy.getLoggerEngine().popLogger();
+                    event.getProject().removeBuildListener(this);
+                }
+            }
+
+            public void messageLogged(BuildEvent event) {
+            }
+        });
+
+    }
+
+    private ProjectComponent task;
+
+    private long lastProgressFlush = 0;
+
+    private StringBuffer buf = new StringBuffer();
+
+    /**
+     * Constructs a new AntMEssageImpl instance.
+     * 
+     * @param antProjectComponent
+     *            the ant project component this message implementation should use for logging. Must
+     *            not be <code>null</code>.
+     */
+    protected AntMessageLogger(ProjectComponent task) {
+        Checks.checkNotNull(task, "task");
+        this.task = task;
+    }
+
+    public void log(String msg, int level) {
+        task.log(msg, level);
+    }
+
+    public void rawlog(String msg, int level) {
+        task.getProject().log(msg, level);
+    }
+
+    public void doProgress() {
+        buf.append(".");
+        if (lastProgressFlush == 0) {
+            lastProgressFlush = System.currentTimeMillis();
+        }
+        // log with ant causes a new line -> we do it only once in a while
+        if (System.currentTimeMillis() - lastProgressFlush > PROGRESS_LOG_PERIOD) {
+            task.log(buf.toString());
+            buf.setLength(0);
+            lastProgressFlush = System.currentTimeMillis();
+        }
+    }
+
+    public void doEndProgress(String msg) {
+        task.log(buf + msg);
+        buf.setLength(0);
+        lastProgressFlush = 0;
+    }
+
+    public String toString() {
+        return "AntMessageLogger:" + task;
+    }
+}
diff --git a/src/java/org/apache/ivy/ant/BuildOBRTask.java b/src/java/org/apache/ivy/ant/BuildOBRTask.java
new file mode 100644
index 0000000..226167a
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/BuildOBRTask.java
@@ -0,0 +1,208 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.text.ParseException;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.xml.transform.TransformerConfigurationException;
+
+import org.apache.ivy.Ivy;
+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.settings.IvySettings;
+import org.apache.ivy.osgi.obr.xml.OBRXMLWriter;
+import org.apache.ivy.osgi.repo.ArtifactReportManifestIterable;
+import org.apache.ivy.osgi.repo.FSManifestIterable;
+import org.apache.ivy.osgi.repo.ManifestAndLocation;
+import org.apache.ivy.osgi.repo.ResolverManifestIterable;
+import org.apache.ivy.plugins.resolver.BasicResolver;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.apache.ivy.util.Message;
+import org.apache.tools.ant.BuildException;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+
+public class BuildOBRTask extends IvyCacheTask {
+
+    private String resolverName = null;
+
+    private File file = null;
+
+    private String cacheName = null;
+
+    private String encoding = "UTF-8";
+
+    private boolean indent = true;
+
+    private File baseDir;
+
+    private boolean quiet;
+
+    private List<String> sourceTypes = Arrays.asList("source", "sources", "src");
+
+    public void setResolver(String resolverName) {
+        this.resolverName = resolverName;
+    }
+
+    public void setCache(String cacheName) {
+        this.cacheName = cacheName;
+    }
+
+    public void setOut(File file) {
+        this.file = file;
+    }
+
+    public void setEncoding(String encoding) {
+        this.encoding = encoding;
+    }
+
+    public void setIndent(boolean indent) {
+        this.indent = indent;
+    }
+
+    public void setBaseDir(File dir) {
+        this.baseDir = dir;
+    }
+
+    public void setQuiet(boolean quiet) {
+        this.quiet = quiet;
+    }
+
+    public void setSourceType(String sourceType) {
+        this.sourceTypes = Arrays.asList(sourceType.split(","));
+    }
+
+    protected void prepareTask() {
+        // if browsing a folder, not need for an Ivy instance
+        if (baseDir == null) {
+            super.prepareTask();
+        }
+        // ensure the configured source type get also resolved
+        if (getType() != null && !getType().equals("*") && sourceTypes != null
+                && !sourceTypes.isEmpty()) {
+            StringBuilder buffer = new StringBuilder(getType());
+            for (String sourceType : sourceTypes) {
+                buffer.append(",");
+                buffer.append(sourceType);
+            }
+            setType(buffer.toString());
+        }
+    }
+
+    public void doExecute() throws BuildException {
+        if (file == null) {
+            throw new BuildException("No output file specified: use the attribute 'out'");
+        }
+
+        Iterable<ManifestAndLocation> it;
+        if (resolverName != null) {
+            if (baseDir != null) {
+                throw new BuildException("specify only one of 'resolver' or 'baseDir'");
+            }
+            if (cacheName != null) {
+                throw new BuildException("specify only one of 'resolver' or 'cache'");
+            }
+            Ivy ivy = getIvyInstance();
+            IvySettings settings = ivy.getSettings();
+            DependencyResolver resolver = settings.getResolver(resolverName);
+            if (resolver == null) {
+                throw new BuildException("the resolver '" + resolverName + "' was not found");
+            }
+            if (!(resolver instanceof BasicResolver)) {
+                throw new BuildException("the type of resolver '" + resolver.getClass().getName()
+                        + "' is not supported.");
+            }
+            it = new ResolverManifestIterable((BasicResolver) resolver);
+        } else if (baseDir != null) {
+            if (cacheName != null) {
+                throw new BuildException("specify only one of 'baseDir' or 'cache'");
+            }
+            if (!baseDir.isDirectory()) {
+                throw new BuildException(baseDir + " is not a directory");
+            }
+            it = new FSManifestIterable(baseDir);
+        } else if (cacheName != null) {
+            Ivy ivy = getIvyInstance();
+            RepositoryCacheManager cacheManager = ivy.getSettings().getRepositoryCacheManager(
+                cacheName);
+            if (!(cacheManager instanceof DefaultRepositoryCacheManager)) {
+                throw new BuildException("the type of cache '" + cacheManager.getClass().getName()
+                        + "' is not supported.");
+            }
+            File basedir = ((DefaultRepositoryCacheManager) cacheManager).getBasedir();
+            it = new FSManifestIterable(basedir);
+        } else {
+            prepareAndCheck();
+            try {
+                it = new ArtifactReportManifestIterable(getArtifactReports(), sourceTypes);
+            } catch (ParseException e) {
+                throw new BuildException("Impossible to parse the artifact reports: "
+                        + e.getMessage(), e);
+            } catch (IOException e) {
+                throw new BuildException("Impossible to read the artifact reports: "
+                        + e.getMessage(), e);
+            }
+        }
+
+        OutputStream out;
+        try {
+            out = new FileOutputStream(file);
+        } catch (FileNotFoundException e) {
+            throw new BuildException(file + " was not found", e);
+        }
+
+        ContentHandler hd;
+        try {
+            hd = OBRXMLWriter.newHandler(out, encoding, indent);
+        } catch (TransformerConfigurationException e) {
+            throw new BuildException("Sax configuration error: " + e.getMessage(), e);
+        }
+
+        class AntMessageLogger2 extends AntMessageLogger {
+            AntMessageLogger2() {
+                super(BuildOBRTask.this);
+            }
+        }
+        IvyContext.getContext().getMessageLogger();
+        Message.setDefaultLogger(new AntMessageLogger2());
+
+        try {
+            OBRXMLWriter.writeManifests(it, hd, quiet);
+        } catch (SAXException e) {
+            throw new BuildException("Sax serialisation error: " + e.getMessage(), e);
+        }
+
+        try {
+            out.flush();
+            out.close();
+        } catch (IOException e) {
+            // don't care
+        }
+
+        Message.sumupProblems();
+    }
+
+}
diff --git a/src/java/org/apache/ivy/ant/ConvertManifestTask.java b/src/java/org/apache/ivy/ant/ConvertManifestTask.java
new file mode 100644
index 0000000..ab470d7
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/ConvertManifestTask.java
@@ -0,0 +1,96 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.text.ParseException;
+import java.util.jar.Manifest;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.osgi.core.BundleInfo;
+import org.apache.ivy.osgi.core.BundleInfoAdapter;
+import org.apache.ivy.osgi.core.ExecutionEnvironmentProfileProvider;
+import org.apache.ivy.osgi.core.ManifestParser;
+import org.apache.ivy.osgi.core.OSGiManifestParser;
+import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorWriter;
+import org.apache.tools.ant.BuildException;
+
+public class ConvertManifestTask extends IvyTask {
+
+    private File manifest = null;
+
+    private File ivyFile = null;
+
+    private ExecutionEnvironmentProfileProvider profileProvider;
+
+    public void setProfileProvider(ExecutionEnvironmentProfileProvider profileProvider) {
+        this.profileProvider = profileProvider;
+    }
+
+    public void setManifest(File manifest) {
+        this.manifest = manifest;
+    }
+
+    public void setIvyFile(File ivyFile) {
+        this.ivyFile = ivyFile;
+    }
+
+    public void doExecute() throws BuildException {
+        if (ivyFile == null) {
+            throw new BuildException("destination ivy file is required for convertmanifest task");
+        }
+        if (manifest == null) {
+            throw new BuildException("source manifest file is required for convertmanifest task");
+        }
+        if (profileProvider == null) {
+            try {
+                profileProvider = new ExecutionEnvironmentProfileProvider();
+            } catch (IOException e) {
+                throw new BuildException("Enable to load the default environment profiles", e);
+            }
+        }
+
+        Manifest m;
+        try {
+            m = new Manifest(new FileInputStream(manifest));
+        } catch (FileNotFoundException e) {
+            throw new BuildException("the manifest file '" + manifest + "' was not found", e);
+        } catch (IOException e) {
+            throw new BuildException("the manifest file '" + manifest + "' could not be read", e);
+        }
+
+        BundleInfo bundleInfo;
+        try {
+            bundleInfo = ManifestParser.parseManifest(m);
+        } catch (ParseException e) {
+            throw new BuildException("Incorrect manifest file '" + manifest + "'", e);
+        }
+        ModuleDescriptor md = BundleInfoAdapter.toModuleDescriptor(
+            OSGiManifestParser.getInstance(), null, bundleInfo, m, profileProvider);
+
+        try {
+            XmlModuleDescriptorWriter.write(md, ivyFile);
+        } catch (IOException e) {
+            throw new BuildException("The ivyFile '" + ivyFile + "' could not be written", e);
+        }
+    }
+
+}
diff --git a/src/java/org/apache/ivy/ant/FixDepsTask.java b/src/java/org/apache/ivy/ant/FixDepsTask.java
new file mode 100644
index 0000000..1be9222
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/FixDepsTask.java
@@ -0,0 +1,90 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.core.report.ResolveReport;
+import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorWriter;
+import org.apache.tools.ant.BuildException;
+
+public class FixDepsTask extends IvyPostResolveTask {
+
+    private File dest;
+
+    private List/* <Keep> */keeps = new ArrayList();
+
+    public void setToFile(File dest) {
+        this.dest = dest;
+    }
+
+    public static class Keep {
+
+        private String org;
+
+        private String module;
+
+        public void setOrg(String org) {
+            this.org = org;
+        }
+
+        public void setModule(String module) {
+            this.module = module;
+        }
+    }
+
+    public Keep createKeep() {
+        Keep k = new Keep();
+        keeps.add(k);
+        return k;
+    }
+
+    public void doExecute() throws BuildException {
+        prepareAndCheck();
+
+        if (dest == null) {
+            throw new BuildException("Missing required parameter 'tofile'");
+        }
+        if (dest.exists() && dest.isDirectory()) {
+            throw new BuildException("The destination file '" + dest.getAbsolutePath()
+                    + "' already exist and is a folder");
+        }
+
+        ResolveReport report = getResolvedReport();
+
+        List/* <ModuleId> */midToKeep = new ArrayList();
+        for (int i = 0; i < keeps.size(); i++) {
+            midToKeep.add(ModuleId.newInstance(((Keep) keeps.get(i)).org,
+                ((Keep) keeps.get(i)).module));
+        }
+
+        ModuleDescriptor md = report.toFixedModuleDescriptor(getSettings(), midToKeep);
+        try {
+            XmlModuleDescriptorWriter.write(md, dest);
+        } catch (IOException e) {
+            throw new BuildException("Failed to write into the file " + dest.getAbsolutePath()
+                    + " (" + e.getMessage() + ")", e);
+        }
+    }
+
+}
diff --git a/src/java/org/apache/ivy/ant/IvyAntSettings.java b/src/java/org/apache/ivy/ant/IvyAntSettings.java
new file mode 100644
index 0000000..660ebbe
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/IvyAntSettings.java
@@ -0,0 +1,391 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.text.ParseException;
+import java.util.Properties;
+
+import org.apache.ivy.Ivy;
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.ivy.core.settings.IvyVariableContainer;
+import org.apache.ivy.util.Message;
+import org.apache.ivy.util.url.CredentialsStore;
+import org.apache.ivy.util.url.URLHandler;
+import org.apache.ivy.util.url.URLHandlerDispatcher;
+import org.apache.ivy.util.url.URLHandlerRegistry;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.ProjectComponent;
+import org.apache.tools.ant.Task;
+import org.apache.tools.ant.taskdefs.Property;
+import org.apache.tools.ant.types.DataType;
+
+public class IvyAntSettings extends DataType {
+
+    public static class Credentials {
+        private String realm;
+
+        private String host;
+
+        private String username;
+
+        private String passwd;
+
+        public String getPasswd() {
+            return this.passwd;
+        }
+
+        public void setPasswd(String passwd) {
+            this.passwd = passwd;
+        }
+
+        public String getRealm() {
+            return this.realm;
+        }
+
+        public void setRealm(String realm) {
+            this.realm = format(realm);
+        }
+
+        public String getHost() {
+            return this.host;
+        }
+
+        public void setHost(String host) {
+            this.host = format(host);
+        }
+
+        public String getUsername() {
+            return this.username;
+        }
+
+        public void setUsername(String userName) {
+            this.username = format(userName);
+        }
+    }
+
+    private Ivy ivyEngine = null;
+
+    private File file = null;
+
+    private URL url = null;
+
+    private String realm = null;
+
+    private String host = null;
+
+    private String userName = null;
+
+    private String passwd = null;
+
+    private String id = "ivy.instance";
+
+    private boolean autoRegistered = false;
+
+    /**
+     * Returns the default ivy settings of this classloader. If it doesn't exist yet, a new one is
+     * created using the given project to back the VariableContainer.
+     * 
+     * @param project
+     *            TODO add text.
+     * @return An IvySetting instance.
+     */
+    public static IvyAntSettings getDefaultInstance(ProjectComponent task) {
+        Project project = task.getProject();
+        Object defaultInstanceObj = project.getReference("ivy.instance");
+        if (defaultInstanceObj != null
+                && defaultInstanceObj.getClass().getClassLoader() != IvyAntSettings.class
+                        .getClassLoader()) {
+            task.log("ivy.instance reference an ivy:settings defined in an other classloader.  "
+                    + "An new default one will be used in this project.", Project.MSG_WARN);
+            defaultInstanceObj = null;
+        }
+        if (defaultInstanceObj != null && !(defaultInstanceObj instanceof IvyAntSettings)) {
+            throw new BuildException("ivy.instance reference a "
+                    + defaultInstanceObj.getClass().getName()
+                    + " an not an IvyAntSettings.  Please don't use this reference id ()");
+        }
+        if (defaultInstanceObj == null) {
+            task.log("No ivy:settings found for the default reference 'ivy.instance'.  "
+                    + "A default instance will be used", Project.MSG_VERBOSE);
+
+            IvyAntSettings settings = new IvyAntSettings();
+            settings.setProject(project);
+            project.addReference("ivy.instance", settings);
+            settings.createIvyEngine(task);
+            return settings;
+        } else {
+            return (IvyAntSettings) defaultInstanceObj;
+        }
+    }
+
+    /*
+     * Keep this for backwards compatibility!
+     */
+    public static IvyAntSettings getDefaultInstance(Task task) {
+        return getDefaultInstance((ProjectComponent) task);
+    }
+
+    public File getFile() {
+        return file;
+    }
+
+    public URL getUrl() {
+        return url;
+    }
+
+    public String getPasswd() {
+        return passwd;
+    }
+
+    public void setPasswd(String aPasswd) {
+        passwd = aPasswd;
+    }
+
+    public String getRealm() {
+        return realm;
+    }
+
+    public void setRealm(String aRealm) {
+        realm = format(aRealm);
+    }
+
+    public String getHost() {
+        return host;
+    }
+
+    public void setHost(String aHost) {
+        host = format(aHost);
+    }
+
+    public String getUsername() {
+        return userName;
+    }
+
+    public void setUsername(String aUserName) {
+        userName = format(aUserName);
+    }
+
+    public void setProject(Project p) {
+        super.setProject(p);
+
+        if ("ivy.instance".equals(id) && !getProject().getReferences().containsKey(id)) {
+            // register ourselfs as default settings, just in case the id attribute is not set
+            getProject().addReference("ivy.instance", this);
+            autoRegistered = true;
+        }
+    }
+
+    private static String format(String str) {
+        return str == null ? str : (str.trim().length() == 0 ? null : str.trim());
+    }
+
+    public void addConfiguredCredentials(Credentials c) {
+        CredentialsStore.INSTANCE.addCredentials(c.getRealm(), c.getHost(), c.getUsername(),
+            c.getPasswd());
+    }
+
+    public void setFile(File file) {
+        this.file = file;
+    }
+
+    public void setUrl(String confUrl) throws MalformedURLException {
+        this.url = new URL(confUrl);
+    }
+
+    public void setUrl(URL url) {
+        this.url = url;
+    }
+
+    /*
+     * This is usually not necessary to define a reference in Ant, but it's the only way to know the
+     * id of the settings, which we use to set ant properties.
+     */
+    public void setId(String id) {
+        if (autoRegistered && getProject().getReference(this.id) == this) {
+            getProject().getReferences().remove(this.id);
+            autoRegistered = false;
+        }
+        this.id = id;
+
+        if (getProject() != null) {
+            getProject().addReference(this.id, this);
+        }
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    /**
+     * Return the configured Ivy instance.
+     * 
+     * @return Returns the configured Ivy instance.
+     */
+    public Ivy getConfiguredIvyInstance(ProjectComponent task) {
+        if (ivyEngine == null) {
+            createIvyEngine(task);
+        }
+        return ivyEngine;
+    }
+
+    /*
+     * Keep this for backwards compatibility!
+     */
+    public Ivy getConfiguredIvyInstance(Task task) {
+        return getConfiguredIvyInstance((ProjectComponent) task);
+    }
+
+    void createIvyEngine(final ProjectComponent task) {
+        Project project = task.getProject();
+        Property prop = new Property() {
+            public void execute() throws BuildException {
+                addProperties(getDefaultProperties(task));
+            }
+        };
+        prop.setProject(project);
+        prop.init();
+        prop.execute();
+
+        IvyAntVariableContainer ivyAntVariableContainer = new IvyAntVariableContainer(project);
+        IvySettings settings = new IvySettings(ivyAntVariableContainer);
+        settings.setBaseDir(project.getBaseDir());
+
+        if (file == null && url == null) {
+            defineDefaultSettingFile(ivyAntVariableContainer, task);
+        }
+
+        Ivy ivy = Ivy.newInstance(settings);
+        try {
+            ivy.pushContext();
+            AntMessageLogger.register(task, ivy);
+
+            Message.showInfo();
+            configureURLHandler();
+            if (file != null) {
+                if (!file.exists()) {
+                    throw new BuildException("settings file does not exist: " + file);
+                }
+                ivy.configure(file);
+            } else {
+                if (url == null) {
+                    throw new AssertionError(
+                            "ivy setting should have either a file, either an url,"
+                                    + " and if not defineDefaultSettingFile must set it.");
+                }
+                ivy.configure(url);
+            }
+            ivyAntVariableContainer.updateProject(id);
+            ivyEngine = ivy;
+        } catch (ParseException e) {
+            throw new BuildException("impossible to configure ivy:settings with given "
+                    + (file != null ? "file: " + file : "url: " + url) + " : " + e, e);
+        } catch (IOException e) {
+            throw new BuildException("impossible to configure ivy:settings with given "
+                    + (file != null ? "file: " + file : "url: " + url) + " : " + e, e);
+        } finally {
+            ivy.popContext();
+        }
+    }
+
+    protected Properties getDefaultProperties(ProjectComponent task) {
+        URL url = IvySettings.getDefaultPropertiesURL();
+        // this is copy of loadURL code from ant Property task (not available in 1.5.1)
+        Properties props = new Properties();
+        task.log("Loading " + url, Project.MSG_VERBOSE);
+        try {
+            InputStream is = url.openStream();
+            try {
+                props.load(is);
+            } finally {
+                if (is != null) {
+                    is.close();
+                }
+            }
+        } catch (IOException ex) {
+            throw new BuildException(ex);
+        }
+        return props;
+    }
+
+    /**
+     * Set file or url to its default value
+     * 
+     * @param variableContainer
+     */
+    private void defineDefaultSettingFile(IvyVariableContainer variableContainer,
+            ProjectComponent task) {
+        String settingsFileName = variableContainer.getVariable("ivy.conf.file");
+        if (settingsFileName != null
+                && !settingsFileName.equals(variableContainer.getVariable("ivy.settings.file"))) {
+            task.log("DEPRECATED: 'ivy.conf.file' is deprecated, use 'ivy.settings.file' instead",
+                Project.MSG_INFO);
+        } else {
+            settingsFileName = variableContainer.getVariable("ivy.settings.file");
+        }
+        File[] settingsLocations = new File[] {
+                new File(getProject().getBaseDir(), settingsFileName),
+                new File(getProject().getBaseDir(), "ivyconf.xml"), new File(settingsFileName),
+                new File("ivyconf.xml")};
+        for (int i = 0; i < settingsLocations.length; i++) {
+            file = settingsLocations[i];
+            task.log("searching settings file: trying " + file, Project.MSG_VERBOSE);
+            if (file.exists()) {
+                break;
+            }
+        }
+        if (!file.exists()) {
+            file = null;
+            if (Boolean.valueOf(getProject().getProperty("ivy.14.compatible")).booleanValue()) {
+                task.log("no settings file found, using Ivy 1.4 default...", Project.MSG_VERBOSE);
+                url = IvySettings.getDefault14SettingsURL();
+            } else {
+                String settingsFileUrl = variableContainer.getVariable("ivy.settings.url");
+                if (settingsFileUrl != null) {
+                    try {
+                        url = new URL(settingsFileUrl);
+                    } catch (MalformedURLException e) {
+                        throw new BuildException(
+                                "Impossible to configure ivy:settings with given url: "
+                                        + settingsFileUrl + ": " + e.getMessage(), e);
+                    }
+                } else {
+                    task.log("no settings file found, using default...", Project.MSG_VERBOSE);
+                    url = IvySettings.getDefaultSettingsURL();
+                }
+            }
+        }
+    }
+
+    private void configureURLHandler() {
+        // TODO : the credentialStore should also be scoped
+        CredentialsStore.INSTANCE.addCredentials(getRealm(), getHost(), getUsername(), getPasswd());
+
+        URLHandlerDispatcher dispatcher = new URLHandlerDispatcher();
+        URLHandler httpHandler = URLHandlerRegistry.getHttp();
+        dispatcher.setDownloader("http", httpHandler);
+        dispatcher.setDownloader("https", httpHandler);
+        URLHandlerRegistry.setDefault(dispatcher);
+    }
+
+}
diff --git a/src/java/org/apache/ivy/ant/IvyAntVariableContainer.java b/src/java/org/apache/ivy/ant/IvyAntVariableContainer.java
new file mode 100644
index 0000000..fa25a8d
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/IvyAntVariableContainer.java
@@ -0,0 +1,106 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.ivy.core.settings.IvyVariableContainer;
+import org.apache.ivy.core.settings.IvyVariableContainerImpl;
+import org.apache.ivy.util.Message;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.taskdefs.Property;
+
+class IvyAntVariableContainer extends IvyVariableContainerImpl implements IvyVariableContainer {
+
+    private Map overwrittenProperties = new HashMap();
+
+    private Project project;
+
+    public IvyAntVariableContainer(Project project) {
+        this.project = project;
+    }
+
+    public String getVariable(String name) {
+        String r = (String) overwrittenProperties.get(name);
+        if (r == null) {
+            r = project.getProperty(name);
+        }
+        if (r == null) {
+            r = super.getVariable(name);
+        }
+        return r;
+    }
+
+    public void setVariable(String varName, String value, boolean overwrite) {
+        if (overwrite) {
+            Message.debug("setting '" + varName + "' to '" + value + "'");
+            overwrittenProperties.put(varName, substitute(value));
+        } else {
+            super.setVariable(varName, value, overwrite);
+        }
+    }
+
+    /**
+     * Updates the Ant Project used in this container with variables set in Ivy.
+     * 
+     * All variables defined in Ivy will be set in the Ant project under two names:
+     * <ul>
+     * <li>the name of the variable</li>
+     * <li>the name of the variable suffxied with a dot + the given id, if the given id is not null</li>
+     * </ul>
+     * 
+     * @param id
+     *            The identifier of the settings in which the variables have been set, which should
+     *            be used as property names suffix
+     */
+    public void updateProject(String id) {
+        Map r = new HashMap(super.getVariables());
+        r.putAll(overwrittenProperties);
+        for (Iterator it = r.entrySet().iterator(); it.hasNext();) {
+            Entry entry = (Entry) it.next();
+
+            setPropertyIfNotSet((String) entry.getKey(), (String) entry.getValue());
+            if (id != null) {
+                setPropertyIfNotSet((String) entry.getKey() + "." + id, (String) entry.getValue());
+            }
+        }
+
+        if (getEnvironmentPrefix() != null) {
+            Property propTask = new Property();
+            propTask.setProject(project);
+            propTask.setEnvironment(getEnvironmentPrefix());
+            propTask.init();
+            propTask.execute();
+        }
+    }
+
+    private void setPropertyIfNotSet(String property, String value) {
+        if (project.getProperty(property) == null) {
+            project.setProperty(property, value);
+        }
+    }
+
+    public Object clone() {
+        IvyAntVariableContainer result = (IvyAntVariableContainer) super.clone();
+        result.overwrittenProperties = (HashMap) ((HashMap) this.overwrittenProperties).clone();
+        return result;
+    }
+}
diff --git a/src/java/org/apache/ivy/ant/IvyArtifactProperty.java b/src/java/org/apache/ivy/ant/IvyArtifactProperty.java
new file mode 100644
index 0000000..5c598f9
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/IvyArtifactProperty.java
@@ -0,0 +1,97 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import java.io.File;
+
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.core.cache.ResolutionCacheManager;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.resolve.ResolveOptions;
+import org.apache.ivy.plugins.report.XmlReportParser;
+import org.apache.tools.ant.BuildException;
+
+/**
+ * Set a set of ant properties according to the last artifact resolved
+ */
+public class IvyArtifactProperty extends IvyPostResolveTask {
+    private String name;
+
+    private String value;
+
+    private boolean overwrite = false;
+
+    public String getName() {
+        return this.name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getValue() {
+        return this.value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+    public void setOverwrite(boolean overwrite) {
+        this.overwrite = overwrite;
+
+    }
+
+    public void doExecute() throws BuildException {
+        prepareAndCheck();
+
+        try {
+            ResolutionCacheManager cacheMgr = getIvyInstance().getResolutionCacheManager();
+            String[] confs = splitConfs(getConf());
+            String resolveId = getResolveId();
+            if (resolveId == null) {
+                resolveId = ResolveOptions.getDefaultResolveId(getResolvedModuleId());
+            }
+            XmlReportParser parser = new XmlReportParser();
+            for (int i = 0; i < confs.length; i++) {
+                File report = cacheMgr.getConfigurationResolveReportInCache(resolveId, confs[i]);
+                parser.parse(report);
+
+                Artifact[] artifacts = parser.getArtifacts();
+                for (int j = 0; j < artifacts.length; j++) {
+                    Artifact artifact = artifacts[j];
+                    String name = IvyPatternHelper.substitute(getSettings().substitute(getName()),
+                        artifact, confs[i]);
+                    String value = IvyPatternHelper.substitute(
+                        getSettings().substitute(getValue()), artifact, confs[i]);
+                    setProperty(name, value);
+                }
+            }
+        } catch (Exception ex) {
+            throw new BuildException("impossible to add artifact properties: " + ex, ex);
+        }
+    }
+
+    private void setProperty(String name, String value) {
+        if (overwrite) {
+            getProject().setProperty(name, value);
+        } else {
+            getProject().setNewProperty(name, value);
+        }
+    }
+}
diff --git a/src/java/org/apache/ivy/ant/IvyArtifactReport.java b/src/java/org/apache/ivy/ant/IvyArtifactReport.java
new file mode 100644
index 0000000..88520f1
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/IvyArtifactReport.java
@@ -0,0 +1,281 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.text.ParseException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerFactoryConfigurationError;
+import javax.xml.transform.sax.SAXTransformerFactory;
+import javax.xml.transform.sax.TransformerHandler;
+import javax.xml.transform.stream.StreamResult;
+
+import org.apache.ivy.core.cache.ArtifactOrigin;
+import org.apache.ivy.core.cache.RepositoryCacheManager;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.report.ArtifactDownloadReport;
+import org.apache.ivy.core.resolve.IvyNode;
+import org.apache.ivy.core.resolve.ResolveOptions;
+import org.apache.ivy.core.resolve.ResolvedModuleRevision;
+import org.apache.ivy.core.retrieve.RetrieveOptions;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+/**
+ * Generates a report of all artifacts involved during the last resolve.
+ */
+public class IvyArtifactReport extends IvyPostResolveTask {
+    private File tofile;
+
+    private String pattern;
+
+    public File getTofile() {
+        return tofile;
+    }
+
+    public void setTofile(File aFile) {
+        tofile = aFile;
+    }
+
+    public String getPattern() {
+        return pattern;
+    }
+
+    public void setPattern(String aPattern) {
+        pattern = aPattern;
+    }
+
+    public void doExecute() throws BuildException {
+        prepareAndCheck();
+        if (tofile == null) {
+            throw new BuildException(
+                    "no destination file name: please provide it through parameter 'tofile'");
+        }
+
+        pattern = getProperty(pattern, getSettings(), "ivy.retrieve.pattern");
+
+        try {
+            String[] confs = splitConfs(getConf());
+            ModuleDescriptor md = null;
+            if (getResolveId() != null) {
+                md = (ModuleDescriptor) getResolvedDescriptor(getResolveId());
+            } else {
+                md = (ModuleDescriptor) getResolvedDescriptor(getOrganisation(), getModule(), false);
+            }
+            IvyNode[] dependencies = getIvyInstance().getResolveEngine().getDependencies(
+                md,
+                ((ResolveOptions) new ResolveOptions().setLog(getLog())).setConfs(confs)
+                        .setResolveId(getResolveId()).setValidate(doValidate(getSettings())), null);
+
+            Map artifactsToCopy = getIvyInstance().getRetrieveEngine().determineArtifactsToCopy(
+                ModuleRevisionId.newInstance(getOrganisation(), getModule(), getRevision()),
+                pattern,
+                ((RetrieveOptions) new RetrieveOptions().setLog(getLog())).setConfs(confs)
+                        .setResolveId(getResolveId()));
+
+            Map moduleRevToArtifactsMap = new HashMap();
+            for (Iterator iter = artifactsToCopy.keySet().iterator(); iter.hasNext();) {
+                ArtifactDownloadReport artifact = (ArtifactDownloadReport) iter.next();
+                Set moduleRevArtifacts = (Set) moduleRevToArtifactsMap.get(artifact.getArtifact()
+                        .getModuleRevisionId());
+                if (moduleRevArtifacts == null) {
+                    moduleRevArtifacts = new HashSet();
+                    moduleRevToArtifactsMap.put(artifact.getArtifact().getModuleRevisionId(),
+                        moduleRevArtifacts);
+                }
+                moduleRevArtifacts.add(artifact);
+            }
+
+            generateXml(dependencies, moduleRevToArtifactsMap, artifactsToCopy);
+        } catch (ParseException e) {
+            log(e.getMessage(), Project.MSG_ERR);
+            throw new BuildException("syntax errors in ivy file: " + e, e);
+        } catch (IOException e) {
+            throw new BuildException("impossible to generate report: " + e, e);
+        }
+    }
+
+    private void generateXml(IvyNode[] dependencies, Map moduleRevToArtifactsMap,
+            Map artifactsToCopy) {
+        try {
+            FileOutputStream fileOuputStream = new FileOutputStream(tofile);
+            try {
+                TransformerHandler saxHandler = createTransformerHandler(fileOuputStream);
+
+                saxHandler.startDocument();
+                saxHandler.startElement(null, "modules", "modules", new AttributesImpl());
+
+                for (int i = 0; i < dependencies.length; i++) {
+                    IvyNode dependency = dependencies[i];
+                    if (dependency.getModuleRevision() == null || dependency.isCompletelyEvicted()) {
+                        continue;
+                    }
+
+                    startModule(saxHandler, dependency);
+
+                    Set artifactsOfModuleRev = (Set) moduleRevToArtifactsMap.get(dependency
+                            .getModuleRevision().getId());
+                    if (artifactsOfModuleRev != null) {
+                        for (Iterator iter = artifactsOfModuleRev.iterator(); iter.hasNext();) {
+                            ArtifactDownloadReport artifact = (ArtifactDownloadReport) iter.next();
+
+                            RepositoryCacheManager cache = dependency.getModuleRevision()
+                                    .getArtifactResolver().getRepositoryCacheManager();
+
+                            startArtifact(saxHandler, artifact.getArtifact());
+
+                            writeOriginLocationIfPresent(cache, saxHandler, artifact);
+                            writeCacheLocationIfPresent(cache, saxHandler, artifact);
+
+                            Set artifactDestPaths = (Set) artifactsToCopy.get(artifact);
+                            for (Iterator iterator = artifactDestPaths.iterator(); iterator
+                                    .hasNext();) {
+                                String artifactDestPath = (String) iterator.next();
+                                writeRetrieveLocation(saxHandler, artifactDestPath);
+                            }
+                            saxHandler.endElement(null, "artifact", "artifact");
+                        }
+                    }
+                    saxHandler.endElement(null, "module", "module");
+                }
+                saxHandler.endElement(null, "modules", "modules");
+                saxHandler.endDocument();
+            } finally {
+                fileOuputStream.close();
+            }
+        } catch (SAXException e) {
+            throw new BuildException("impossible to generate report", e);
+        } catch (TransformerConfigurationException e) {
+            throw new BuildException("impossible to generate report", e);
+        } catch (IOException e) {
+            throw new BuildException("impossible to generate report", e);
+        }
+    }
+
+    private TransformerHandler createTransformerHandler(FileOutputStream fileOuputStream)
+            throws TransformerFactoryConfigurationError, TransformerConfigurationException,
+            SAXException {
+        SAXTransformerFactory transformerFact = (SAXTransformerFactory) SAXTransformerFactory
+                .newInstance();
+        TransformerHandler saxHandler = transformerFact.newTransformerHandler();
+        saxHandler.getTransformer().setOutputProperty(OutputKeys.ENCODING, "UTF-8");
+        saxHandler.getTransformer().setOutputProperty(OutputKeys.INDENT, "yes");
+        saxHandler.setResult(new StreamResult(fileOuputStream));
+        return saxHandler;
+    }
+
+    private void startModule(TransformerHandler saxHandler, IvyNode dependency) throws SAXException {
+        AttributesImpl moduleAttrs = new AttributesImpl();
+        moduleAttrs.addAttribute(null, "organisation", "organisation", "CDATA", dependency
+                .getModuleId().getOrganisation());
+        moduleAttrs.addAttribute(null, "name", "name", "CDATA", dependency.getModuleId().getName());
+        ResolvedModuleRevision moduleRevision = dependency.getModuleRevision();
+        moduleAttrs.addAttribute(null, "rev", "rev", "CDATA", moduleRevision.getId().getRevision());
+        moduleAttrs.addAttribute(null, "status", "status", "CDATA", moduleRevision.getDescriptor()
+                .getStatus());
+        saxHandler.startElement(null, "module", "module", moduleAttrs);
+    }
+
+    private void startArtifact(TransformerHandler saxHandler, Artifact artifact)
+            throws SAXException {
+        AttributesImpl artifactAttrs = new AttributesImpl();
+        artifactAttrs.addAttribute(null, "name", "name", "CDATA", artifact.getName());
+        artifactAttrs.addAttribute(null, "ext", "ext", "CDATA", artifact.getExt());
+        artifactAttrs.addAttribute(null, "type", "type", "CDATA", artifact.getType());
+        saxHandler.startElement(null, "artifact", "artifact", artifactAttrs);
+    }
+
+    private void writeOriginLocationIfPresent(RepositoryCacheManager cache,
+            TransformerHandler saxHandler, ArtifactDownloadReport artifact) throws IOException,
+            SAXException {
+        ArtifactOrigin origin = artifact.getArtifactOrigin();
+        if (!ArtifactOrigin.isUnknown(origin)) {
+            String originName = origin.getLocation();
+            boolean isOriginLocal = origin.isLocal();
+
+            String originLocation;
+            AttributesImpl originLocationAttrs = new AttributesImpl();
+            if (isOriginLocal) {
+                originLocationAttrs.addAttribute(null, "is-local", "is-local", "CDATA", "true");
+                originLocation = originName.replace('\\', '/');
+            } else {
+                originLocationAttrs.addAttribute(null, "is-local", "is-local", "CDATA", "false");
+                originLocation = originName;
+            }
+            saxHandler
+                    .startElement(null, "origin-location", "origin-location", originLocationAttrs);
+            char[] originLocationAsChars = originLocation.toCharArray();
+            saxHandler.characters(originLocationAsChars, 0, originLocationAsChars.length);
+            saxHandler.endElement(null, "origin-location", "origin-location");
+        }
+    }
+
+    private void writeCacheLocationIfPresent(RepositoryCacheManager cache,
+            TransformerHandler saxHandler, ArtifactDownloadReport artifact) throws SAXException {
+        File archiveInCache = artifact.getLocalFile();
+
+        if (archiveInCache != null) {
+            saxHandler.startElement(null, "cache-location", "cache-location", new AttributesImpl());
+            char[] archiveInCacheAsChars = archiveInCache.getPath().replace('\\', '/')
+                    .toCharArray();
+            saxHandler.characters(archiveInCacheAsChars, 0, archiveInCacheAsChars.length);
+            saxHandler.endElement(null, "cache-location", "cache-location");
+        }
+    }
+
+    private void writeRetrieveLocation(TransformerHandler saxHandler, String artifactDestPath)
+            throws SAXException {
+        artifactDestPath = removeLeadingPath(getProject().getBaseDir(), new File(artifactDestPath));
+
+        saxHandler.startElement(null, "retrieve-location", "retrieve-location",
+            new AttributesImpl());
+        char[] artifactDestPathAsChars = artifactDestPath.replace('\\', '/').toCharArray();
+        saxHandler.characters(artifactDestPathAsChars, 0, artifactDestPathAsChars.length);
+        saxHandler.endElement(null, "retrieve-location", "retrieve-location");
+    }
+
+    // method largely inspired by ant 1.6.5 FileUtils method
+    public String removeLeadingPath(File leading, File path) {
+        String l = leading.getAbsolutePath();
+        String p = path.getAbsolutePath();
+        if (l.equals(p)) {
+            return "";
+        }
+
+        // ensure that l ends with a /
+        // so we never think /foo was a parent directory of /foobar
+        if (!l.endsWith(File.separator)) {
+            l += File.separator;
+        }
+        return (p.startsWith(l)) ? p.substring(l.length()) : p;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/ant/IvyBuildList.java b/src/java/org/apache/ivy/ant/IvyBuildList.java
new file mode 100644
index 0000000..0620b49
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/IvyBuildList.java
@@ -0,0 +1,569 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+import org.apache.ivy.Ivy;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.ivy.core.sort.SortOptions;
+import org.apache.ivy.plugins.parser.ModuleDescriptorParserRegistry;
+import org.apache.ivy.util.Message;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.DirectoryScanner;
+import org.apache.tools.ant.types.FileSet;
+import org.apache.tools.ant.types.Path;
+
+/**
+ * Creates an ant filelist of files (usually build.xml) ordered according to the dependencies
+ * declared in ivy files.
+ */
+public class IvyBuildList extends IvyTask {
+    public static final class OnMissingDescriptor {
+        public static final String HEAD = "head";
+
+        public static final String TAIL = "tail";
+
+        public static final String SKIP = "skip";
+
+        public static final String FAIL = "fail";
+
+        public static final String WARN = "warn";
+
+        private OnMissingDescriptor() {
+        }
+    }
+
+    public static final String DESCRIPTOR_REQUIRED = "required";
+
+    private List buildFileSets = new ArrayList(); // List (FileSet)
+
+    private String reference;
+
+    private boolean haltOnError = true;
+
+    private String onMissingDescriptor = OnMissingDescriptor.HEAD;
+
+    private boolean reverse = false;
+
+    private String ivyFilePath;
+
+    private String root = "*";
+
+    private boolean excludeRoot = false;
+
+    private String leaf = "*";
+
+    private String delimiter = ",";
+
+    private boolean excludeLeaf = false;
+
+    private boolean onlydirectdep = false;
+
+    private String restartFrom = "*";
+
+    public void addFileset(FileSet buildFiles) {
+        buildFileSets.add(buildFiles);
+    }
+
+    public String getReference() {
+        return reference;
+    }
+
+    public void setReference(String reference) {
+        this.reference = reference;
+    }
+
+    public String getRoot() {
+        return root;
+    }
+
+    public void setRoot(String root) {
+        this.root = root;
+    }
+
+    public boolean isExcludeRoot() {
+        return excludeRoot;
+    }
+
+    public void setExcludeRoot(boolean root) {
+        excludeRoot = root;
+    }
+
+    public String getLeaf() {
+        return leaf;
+    }
+
+    public void setLeaf(String leaf) {
+        this.leaf = leaf;
+    }
+
+    public boolean isExcludeLeaf() {
+        return excludeLeaf;
+    }
+
+    public void setExcludeLeaf(boolean excludeLeaf) {
+        this.excludeLeaf = excludeLeaf;
+    }
+
+    public String getDelimiter() {
+        return delimiter;
+    }
+
+    public void setDelimiter(String delimiter) {
+        this.delimiter = delimiter;
+    }
+
+    public boolean getOnlydirectdep() {
+        return onlydirectdep;
+    }
+
+    public void setOnlydirectdep(boolean onlydirectdep) {
+        this.onlydirectdep = onlydirectdep;
+    }
+
+    public void doExecute() throws BuildException {
+        if (reference == null) {
+            throw new BuildException("reference should be provided in ivy build list");
+        }
+        if (buildFileSets.isEmpty()) {
+            throw new BuildException(
+                    "at least one nested fileset should be provided in ivy build list");
+        }
+
+        Ivy ivy = getIvyInstance();
+        IvySettings settings = ivy.getSettings();
+
+        ivyFilePath = getProperty(ivyFilePath, settings, "ivy.buildlist.ivyfilepath");
+
+        Path path = new Path(getProject());
+
+        Map buildFiles = new HashMap(); // Map (ModuleDescriptor -> File buildFile)
+        List independent = new ArrayList();
+        List noDescriptor = new ArrayList();
+        Collection mds = new ArrayList();
+
+        Set rootModuleNames = new LinkedHashSet();
+        if (!"*".equals(root)) {
+            StringTokenizer st = new StringTokenizer(root, delimiter);
+            while (st.hasMoreTokens()) {
+                rootModuleNames.add(st.nextToken());
+            }
+        }
+
+        Set leafModuleNames = new LinkedHashSet();
+        if (!"*".equals(leaf)) {
+            StringTokenizer st = new StringTokenizer(leaf, delimiter);
+            while (st.hasMoreTokens()) {
+                leafModuleNames.add(st.nextToken());
+            }
+        }
+
+        Set restartFromModuleNames = new LinkedHashSet();
+        if (!"*".equals(restartFrom)) {
+            StringTokenizer st = new StringTokenizer(restartFrom, delimiter);
+            // Only accept one (first) module
+            restartFromModuleNames.add(st.nextToken());
+        }
+
+        for (ListIterator iter = buildFileSets.listIterator(); iter.hasNext();) {
+            FileSet fs = (FileSet) iter.next();
+            DirectoryScanner ds = fs.getDirectoryScanner(getProject());
+            String[] builds = ds.getIncludedFiles();
+            for (int i = 0; i < builds.length; i++) {
+                File buildFile = new File(ds.getBasedir(), builds[i]);
+                File ivyFile = getIvyFileFor(buildFile);
+                if (!ivyFile.exists()) {
+                    onMissingDescriptor(buildFile, ivyFile, noDescriptor);
+                } else {
+                    try {
+                        ModuleDescriptor md = ModuleDescriptorParserRegistry.getInstance()
+                                .parseDescriptor(settings, ivyFile.toURI().toURL(),
+                                    doValidate(settings));
+                        buildFiles.put(md, buildFile);
+                        mds.add(md);
+                        Message.debug("Add " + md.getModuleRevisionId().getModuleId());
+                    } catch (Exception ex) {
+                        if (haltOnError) {
+                            throw new BuildException("impossible to parse ivy file for "
+                                    + buildFile + ": ivyfile=" + ivyFile + " exception=" + ex, ex);
+                        } else {
+                            Message.warn("impossible to parse ivy file for " + buildFile
+                                    + ": ivyfile=" + ivyFile + " exception=" + ex.getMessage());
+                            Message.info("\t=> adding it at the beginning of the path");
+                            independent.add(buildFile);
+                        }
+                    }
+                }
+            }
+        }
+
+        List leafModuleDescriptors = convertModuleNamesToModuleDescriptors(mds, leafModuleNames,
+            "leaf");
+        List rootModuleDescriptors = convertModuleNamesToModuleDescriptors(mds, rootModuleNames,
+            "root");
+        List restartFromModuleDescriptors = convertModuleNamesToModuleDescriptors(mds,
+            restartFromModuleNames, "restartFrom");
+
+        if (!rootModuleDescriptors.isEmpty()) {
+            Message.info("Filtering modules based on roots " + rootModuleNames);
+            mds = filterModulesFromRoot(mds, rootModuleDescriptors);
+        }
+        if (!leafModuleDescriptors.isEmpty()) {
+            Message.info("Filtering modules based on leafs " + leafModuleNames);
+            mds = filterModulesFromLeaf(mds, leafModuleDescriptors);
+        }
+
+        List sortedModules = ivy.sortModuleDescriptors(mds, SortOptions.DEFAULT);
+
+        if (!OnMissingDescriptor.TAIL.equals(onMissingDescriptor)) {
+            for (ListIterator iter = noDescriptor.listIterator(); iter.hasNext();) {
+                File buildFile = (File) iter.next();
+                addBuildFile(path, buildFile);
+            }
+        }
+        for (ListIterator iter = independent.listIterator(); iter.hasNext();) {
+            File buildFile = (File) iter.next();
+            addBuildFile(path, buildFile);
+        }
+        if (isReverse()) {
+            Collections.reverse(sortedModules);
+        }
+        // Remove modules that are before the restartFrom point
+        // Independent modules (without valid ivy file) can not be addressed
+        // so they are not removed from build path.
+        if (!restartFromModuleDescriptors.isEmpty()) {
+            boolean foundRestartFrom = false;
+            List keptModules = new ArrayList();
+            ModuleDescriptor restartFromModuleDescriptor = (ModuleDescriptor) restartFromModuleDescriptors
+                    .get(0);
+            for (ListIterator iter = sortedModules.listIterator(); iter.hasNext();) {
+                ModuleDescriptor md = (ModuleDescriptor) iter.next();
+                if (md.equals(restartFromModuleDescriptor)) {
+                    foundRestartFrom = true;
+                }
+                if (foundRestartFrom) {
+                    keptModules.add(md);
+                }
+            }
+            sortedModules = keptModules;
+        }
+        StringBuffer order = new StringBuffer();
+        for (ListIterator iter = sortedModules.listIterator(); iter.hasNext();) {
+            ModuleDescriptor md = (ModuleDescriptor) iter.next();
+            order.append(md.getModuleRevisionId().getModuleId());
+            if (iter.hasNext()) {
+                order.append(", ");
+            }
+            File buildFile = (File) buildFiles.get(md);
+            addBuildFile(path, buildFile);
+        }
+        if (OnMissingDescriptor.TAIL.equals(onMissingDescriptor)) {
+            for (ListIterator iter = noDescriptor.listIterator(); iter.hasNext();) {
+                File buildFile = (File) iter.next();
+                addBuildFile(path, buildFile);
+            }
+        }
+
+        getProject().addReference(getReference(), path);
+        getProject().setProperty("ivy.sorted.modules", order.toString());
+    }
+
+    private void onMissingDescriptor(File buildFile, File ivyFile, List noDescriptor) {
+        if (OnMissingDescriptor.SKIP.equals(onMissingDescriptor)) {
+            Message.debug("skipping " + buildFile + ": descriptor " + ivyFile + " doesn't exist");
+        } else if (OnMissingDescriptor.FAIL.equals(onMissingDescriptor)) {
+            throw new BuildException(
+                    "a module has no module descriptor and onMissingDescriptor=fail. "
+                            + "Build file: " + buildFile + ". Expected descriptor: " + ivyFile);
+        } else {
+            if (OnMissingDescriptor.WARN.equals(onMissingDescriptor)) {
+                Message.warn("a module has no module descriptor. " + "Build file: " + buildFile
+                        + ". Expected descriptor: " + ivyFile);
+            }
+            Message.verbose("no descriptor for "
+                    + buildFile
+                    + ": descriptor="
+                    + ivyFile
+                    + ": adding it at the "
+                    + (OnMissingDescriptor.TAIL.equals(onMissingDescriptor) ? "tail" : "head"
+                            + " of the path"));
+            Message.verbose("\t(change onMissingDescriptor if you want to take another action");
+            noDescriptor.add(buildFile);
+        }
+    }
+
+    private List convertModuleNamesToModuleDescriptors(Collection mds, Set moduleNames, String kind) {
+        List result = new ArrayList();
+        Set foundModuleNames = new HashSet();
+
+        for (Iterator it = mds.iterator(); it.hasNext();) {
+            ModuleDescriptor md = (ModuleDescriptor) it.next();
+            String name = md.getModuleRevisionId().getModuleId().getName();
+            if (moduleNames.contains(name)) {
+                foundModuleNames.add(name);
+                result.add(md);
+            }
+        }
+
+        if (foundModuleNames.size() < moduleNames.size()) {
+            Set missingModules = new HashSet(moduleNames);
+            missingModules.removeAll(foundModuleNames);
+
+            StringBuffer missingNames = new StringBuffer();
+            String sep = "";
+            for (Iterator it = missingModules.iterator(); it.hasNext();) {
+                missingNames.append(sep);
+                missingNames.append(it.next());
+                sep = ", ";
+            }
+
+            throw new BuildException("unable to find " + kind + " module(s) "
+                    + missingNames.toString() + " in build fileset");
+        }
+
+        return result;
+    }
+
+    /**
+     * Returns a collection of ModuleDescriptors that are conatined in the input collection of
+     * ModuleDescriptors and upon which the root module depends
+     * 
+     * @param mds
+     *            input collection of ModuleDescriptors
+     * @param rootmd
+     *            root module
+     * @return filtered list of modules
+     */
+    private Collection filterModulesFromRoot(Collection mds, List rootmds) {
+        // Make a map of ModuleId objects -> ModuleDescriptors
+        Map moduleIdMap = new HashMap();
+        for (Iterator iter = mds.iterator(); iter.hasNext();) {
+            ModuleDescriptor md = ((ModuleDescriptor) iter.next());
+            moduleIdMap.put(md.getModuleRevisionId().getModuleId(), md);
+        }
+
+        // recursively process the nodes
+        Set toKeep = new LinkedHashSet();
+
+        Iterator it = rootmds.iterator();
+        while (it.hasNext()) {
+            ModuleDescriptor rootmd = (ModuleDescriptor) it.next();
+            processFilterNodeFromRoot(rootmd, toKeep, moduleIdMap);
+            // With the excluderoot attribute set to true, take the rootmd out of the toKeep set.
+            if (excludeRoot) {
+                // Only for logging purposes
+                Message.verbose("Excluded module "
+                        + rootmd.getModuleRevisionId().getModuleId().getName());
+            } else {
+                toKeep.add(rootmd);
+            }
+        }
+
+        // just for logging
+        for (Iterator iter = toKeep.iterator(); iter.hasNext();) {
+            ModuleDescriptor md = ((ModuleDescriptor) iter.next());
+            Message.verbose("Kept module " + md.getModuleRevisionId().getModuleId().getName());
+        }
+
+        return toKeep;
+    }
+
+    /**
+     * Adds the current node to the toKeep collection and then processes the each of the direct
+     * dependencies of this node that appear in the moduleIdMap (indicating that the dependency is
+     * part of this BuildList)
+     * 
+     * @param node
+     *            the node to be processed
+     * @param toKeep
+     *            the set of ModuleDescriptors that should be kept
+     * @param moduleIdMap
+     *            reference mapping of moduleId to ModuleDescriptor that are part of the BuildList
+     */
+    private void processFilterNodeFromRoot(ModuleDescriptor node, Set toKeep, Map moduleIdMap) {
+        // toKeep.add(node);
+
+        DependencyDescriptor[] deps = node.getDependencies();
+        for (int i = 0; i < deps.length; i++) {
+            ModuleId id = deps[i].getDependencyId();
+            ModuleDescriptor md = (ModuleDescriptor) moduleIdMap.get(id);
+            // we test if this module id has a module descriptor, and if it isn't already in the
+            // toKeep Set, in which there's probably a circular dependency
+            if (md != null && !toKeep.contains(md)) {
+                toKeep.add(md);
+                if (!getOnlydirectdep()) {
+                    processFilterNodeFromRoot(md, toKeep, moduleIdMap);
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns a collection of ModuleDescriptors that are conatined in the input collection of
+     * ModuleDescriptors which depends on the leaf module
+     * 
+     * @param mds
+     *            input collection of ModuleDescriptors
+     * @param leafmd
+     *            leaf module
+     * @return filtered list of modules
+     */
+    private Collection filterModulesFromLeaf(Collection mds, List leafmds) {
+        // Make a map of ModuleId objects -> ModuleDescriptors
+        Map moduleIdMap = new HashMap();
+        for (Iterator iter = mds.iterator(); iter.hasNext();) {
+            ModuleDescriptor md = ((ModuleDescriptor) iter.next());
+            moduleIdMap.put(md.getModuleRevisionId().getModuleId(), md);
+        }
+
+        // recursively process the nodes
+        Set toKeep = new LinkedHashSet();
+        Iterator it = leafmds.iterator();
+        while (it.hasNext()) {
+            ModuleDescriptor leafmd = (ModuleDescriptor) it.next();
+            // With the excludeleaf attribute set to true, take the rootmd out of the toKeep set.
+            if (excludeLeaf) {
+                Message.verbose("Excluded module "
+                        + leafmd.getModuleRevisionId().getModuleId().getName());
+            } else {
+                toKeep.add(leafmd);
+            }
+            processFilterNodeFromLeaf(leafmd, toKeep, moduleIdMap);
+        }
+
+        // just for logging
+        for (Iterator iter = toKeep.iterator(); iter.hasNext();) {
+            ModuleDescriptor md = ((ModuleDescriptor) iter.next());
+            Message.verbose("Kept module " + md.getModuleRevisionId().getModuleId().getName());
+        }
+
+        return toKeep;
+    }
+
+    /**
+     * Search in the moduleIdMap modules depending on node, add them to the toKeep set and process
+     * them recursively.
+     * 
+     * @param node
+     *            the node to be processed
+     * @param toKeep
+     *            the set of ModuleDescriptors that should be kept
+     * @param moduleIdMap
+     *            reference mapping of moduleId to ModuleDescriptor that are part of the BuildList
+     */
+    private void processFilterNodeFromLeaf(ModuleDescriptor node, Set toKeep, Map moduleIdMap) {
+        for (Iterator iter = moduleIdMap.values().iterator(); iter.hasNext();) {
+            ModuleDescriptor md = (ModuleDescriptor) iter.next();
+            DependencyDescriptor[] deps = md.getDependencies();
+            for (int i = 0; i < deps.length; i++) {
+                ModuleId id = deps[i].getDependencyId();
+                if (node.getModuleRevisionId().getModuleId().equals(id) && !toKeep.contains(md)) {
+                    toKeep.add(md);
+                    if (!getOnlydirectdep()) {
+                        processFilterNodeFromLeaf(md, toKeep, moduleIdMap);
+                    }
+                }
+            }
+        }
+    }
+
+    private void addBuildFile(Path path, File buildFile) {
+        FileSet fs = new FileSet();
+        fs.setFile(buildFile);
+        path.addFileset(fs);
+    }
+
+    private File getIvyFileFor(File buildFile) {
+        return new File(buildFile.getParentFile(), ivyFilePath);
+    }
+
+    public boolean isHaltonerror() {
+        return haltOnError;
+    }
+
+    public void setHaltonerror(boolean haltOnError) {
+        this.haltOnError = haltOnError;
+    }
+
+    public String getIvyfilepath() {
+        return ivyFilePath;
+    }
+
+    public void setIvyfilepath(String ivyFilePath) {
+        this.ivyFilePath = ivyFilePath;
+    }
+
+    public String getOnMissingDescriptor() {
+        return onMissingDescriptor;
+    }
+
+    public void setOnMissingDescriptor(String onMissingDescriptor) {
+        this.onMissingDescriptor = onMissingDescriptor;
+    }
+
+    /**
+     * @deprecated use {@link #getOnMissingDescriptor()} instead.
+     */
+    @Deprecated
+    public boolean isSkipbuildwithoutivy() {
+        return onMissingDescriptor == OnMissingDescriptor.SKIP;
+    }
+
+    /**
+     * @deprecated use {@link #setOnMissingDescriptor(String)} instead.
+     */
+    @Deprecated
+    public void setSkipbuildwithoutivy(boolean skipBuildFilesWithoutIvy) {
+        Message.deprecated("skipbuildwithoutivy is deprecated, use onMissingDescriptor instead.");
+        this.onMissingDescriptor = skipBuildFilesWithoutIvy ? OnMissingDescriptor.SKIP
+                : OnMissingDescriptor.FAIL;
+    }
+
+    public boolean isReverse() {
+        return reverse;
+    }
+
+    public void setReverse(boolean reverse) {
+        this.reverse = reverse;
+    }
+
+    public String getRestartFrom() {
+        return restartFrom;
+    }
+
+    public void setRestartFrom(String restartFrom) {
+        this.restartFrom = restartFrom;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/ant/IvyBuildNumber.java b/src/java/org/apache/ivy/ant/IvyBuildNumber.java
new file mode 100644
index 0000000..7f7ce7d
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/IvyBuildNumber.java
@@ -0,0 +1,386 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import java.util.List;
+import java.util.ListIterator;
+
+import org.apache.ivy.Ivy;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.resolve.ResolvedModuleRevision;
+import org.apache.ivy.core.search.SearchEngine;
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.ivy.plugins.latest.ArtifactInfo;
+import org.apache.ivy.plugins.latest.LatestStrategy;
+import org.apache.ivy.plugins.matcher.ExactOrRegexpPatternMatcher;
+import org.apache.ivy.plugins.matcher.ExactPatternMatcher;
+import org.apache.ivy.plugins.matcher.Matcher;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.apache.ivy.plugins.version.VersionMatcher;
+import org.apache.tools.ant.BuildException;
+
+/**
+ * Look for the latest module in the repository matching the given criteria, and sets a set of
+ * properties according to what was found.
+ */
+public class IvyBuildNumber extends IvyTask {
+
+    public static class ResolvedModuleRevisionArtifactInfo implements ArtifactInfo {
+        private ModuleRevisionId rmr;
+
+        public ResolvedModuleRevisionArtifactInfo(ModuleRevisionId rmr) {
+            this.rmr = rmr;
+        }
+
+        public String getRevision() {
+            return rmr.getRevision();
+        }
+
+        public long getLastModified() {
+            return -1;
+        }
+
+    }
+
+    private String organisation;
+
+    private String module;
+
+    private String branch;
+
+    private String revision;
+
+    private String revSep = ".";
+
+    private String prefix = "ivy.";
+
+    private String defaultValue = "0";
+
+    private String defaultBuildNumber = "0";
+
+    private String resolver = null;
+
+    public String getModule() {
+        return module;
+    }
+
+    public void setModule(String module) {
+        this.module = module;
+    }
+
+    public String getOrganisation() {
+        return organisation;
+    }
+
+    public void setOrganisation(String organisation) {
+        this.organisation = organisation;
+    }
+
+    public String getRevision() {
+        return revision;
+    }
+
+    public void setRevision(String revision) {
+        this.revision = revision;
+    }
+
+    public String getBranch() {
+        return branch;
+    }
+
+    public void setBranch(String branch) {
+        this.branch = branch;
+    }
+
+    public String getDefault() {
+        return defaultValue;
+    }
+
+    public void setDefault(String default1) {
+        defaultValue = default1;
+    }
+
+    public String getResolver() {
+        return resolver;
+    }
+
+    public void setResolver(String resolver) {
+        this.resolver = resolver;
+    }
+
+    public String getPrefix() {
+        return prefix;
+    }
+
+    public void setPrefix(String prefix) {
+        this.prefix = prefix;
+    }
+
+    public void doExecute() throws BuildException {
+        if (organisation == null) {
+            throw new BuildException("no organisation provided for ivy buildnumber task");
+        }
+        if (module == null) {
+            throw new BuildException("no module name provided for ivy buildnumber task");
+        }
+        if (prefix == null) {
+            throw new BuildException("null prefix not allowed");
+        }
+
+        Ivy ivy = getIvyInstance();
+        IvySettings settings = ivy.getSettings();
+        if (branch == null) {
+            branch = settings.getDefaultBranch(new ModuleId(organisation, module));
+        }
+        if (revision == null || revision.length() == 0) {
+            revision = "latest.integration";
+        } else if (!revision.endsWith("+")) {
+            revision = revision + "+";
+        }
+        if (!prefix.endsWith(".") && prefix.length() > 0) {
+            prefix = prefix + ".";
+        }
+
+        SearchEngine searcher = new SearchEngine(settings);
+
+        PatternMatcher patternMatcher = new PatternMatcher() {
+            private PatternMatcher exact = new ExactPatternMatcher();
+
+            private PatternMatcher regexp = new ExactOrRegexpPatternMatcher();
+
+            public Matcher getMatcher(String expression) {
+                if (expression.equals(organisation) || expression.equals(module)
+                        || expression.equals(branch)) {
+                    return exact.getMatcher(expression);
+                }
+                return regexp.getMatcher(expression);
+            }
+
+            public String getName() {
+                return "buildnumber-matcher";
+            }
+        };
+        ModuleRevisionId[] revisions;
+        if (resolver == null) {
+            revisions = searcher.listModules(
+                ModuleRevisionId.newInstance(organisation, module, branch, ".*"), patternMatcher);
+        } else {
+            DependencyResolver depResolver = settings.getResolver(resolver);
+            if (depResolver == null) {
+                throw new BuildException("Unknown resolver: " + resolver);
+            }
+            revisions = searcher.listModules(depResolver,
+                ModuleRevisionId.newInstance(organisation, module, branch, ".*"), patternMatcher);
+        }
+
+        ArtifactInfo[] infos = new ArtifactInfo[revisions.length];
+        for (int i = 0; i < revisions.length; i++) {
+            infos[i] = new ResolvedModuleRevisionArtifactInfo(revisions[i]);
+        }
+
+        VersionMatcher matcher = settings.getVersionMatcher();
+        LatestStrategy latestStrategy = settings.getLatestStrategy("latest-revision");
+        List sorted = latestStrategy.sort(infos);
+
+        ModuleRevisionId askedMrid = ModuleRevisionId.newInstance(organisation, module, branch,
+            revision);
+
+        String foundRevision = null;
+        for (ListIterator iter = sorted.listIterator(sorted.size()); iter.hasPrevious();) {
+            ResolvedModuleRevisionArtifactInfo info = (ResolvedModuleRevisionArtifactInfo) iter
+                    .previous();
+
+            if (!matcher.accept(askedMrid, info.rmr)) {
+                continue;
+            }
+
+            if (matcher.needModuleDescriptor(askedMrid, info.rmr)) {
+                ResolvedModuleRevision rmr = ivy.findModule(info.rmr);
+                if (matcher.accept(askedMrid, rmr.getDescriptor())) {
+                    foundRevision = info.rmr.getRevision();
+                }
+            } else {
+                foundRevision = info.rmr.getRevision();
+            }
+
+            if (foundRevision != null) {
+                break;
+            }
+        }
+
+        NewRevision newRevision = computeNewRevision(foundRevision);
+        setProperty("revision", newRevision.getRevision());
+        setProperty("new.revision", newRevision.getNewRevision());
+        setProperty("build.number", newRevision.getBuildNumber());
+        setProperty("new.build.number", newRevision.getNewBuildNumber());
+    }
+
+    private void setProperty(String propertyName, String value) {
+        if (value != null) {
+            getProject().setProperty(prefix + propertyName, value);
+        }
+    }
+
+    private NewRevision computeNewRevision(String revision) {
+        String revPrefix = "latest.integration".equals(this.revision) ? "" : this.revision
+                .substring(0, this.revision.length() - 1);
+        if (revision != null && !revision.startsWith(revPrefix)) {
+            throw new BuildException("invalid exception found in repository: '" + revision
+                    + "' for '" + revPrefix + "'");
+        }
+        if (revision == null) {
+            if (revPrefix.length() > 0) {
+                return new NewRevision(revision, revPrefix
+                        + (revPrefix.endsWith(revSep) ? defaultBuildNumber : revSep
+                                + defaultBuildNumber), null, defaultBuildNumber);
+            } else {
+                Range r = findLastNumber(defaultValue);
+                if (r == null) { // no number found
+                    return new NewRevision(revision, defaultValue, null, null);
+                } else {
+                    long n = Long.parseLong(defaultValue.substring(r.getStartIndex(),
+                        r.getEndIndex()));
+                    return new NewRevision(revision, defaultValue, null, String.valueOf(n));
+                }
+            }
+        }
+        Range r;
+        if (revPrefix.length() == 0) {
+            r = findLastNumber(revision);
+            if (r == null) {
+                return new NewRevision(revision, revision
+                        + (revision.endsWith(revSep) ? "1" : revSep + "1"), null, "1");
+            }
+        } else {
+            r = findFirstNumber(revision, revPrefix.length());
+            if (r == null) {
+                return new NewRevision(revision, revPrefix
+                        + (revPrefix.endsWith(revSep) ? "1" : revSep + "1"), null, "1");
+            }
+        }
+        long n = Long.parseLong(revision.substring(r.getStartIndex(), r.getEndIndex())) + 1;
+        return new NewRevision(revision, revision.substring(0, r.getStartIndex()) + n,
+                String.valueOf(n - 1), String.valueOf(n));
+    }
+
+    private Range findFirstNumber(String str, int startIndex) {
+        // let's find the first digit in the string
+        int startNumberIndex = startIndex;
+        while (startNumberIndex < str.length() && !Character.isDigit(str.charAt(startNumberIndex))) {
+            startNumberIndex++;
+        }
+        if (startNumberIndex == str.length()) {
+            return null;
+        }
+        // let's find the end of the number
+        int endNumberIndex = startNumberIndex + 1;
+        while (endNumberIndex < str.length() && Character.isDigit(str.charAt(endNumberIndex))) {
+            endNumberIndex++;
+        }
+        return new Range(startNumberIndex, endNumberIndex);
+    }
+
+    private Range findLastNumber(String str) {
+        int endNumberIndex = str.length() - 1;
+        while (endNumberIndex >= 0 && !Character.isDigit(str.charAt(endNumberIndex))) {
+            endNumberIndex--;
+        }
+        int startNumberIndex = endNumberIndex == -1 ? -1 : endNumberIndex - 1;
+        while (startNumberIndex >= 0 && Character.isDigit(str.charAt(startNumberIndex))) {
+            startNumberIndex--;
+        }
+        endNumberIndex++;
+        startNumberIndex++;
+        if (startNumberIndex == endNumberIndex) { // no number found
+            return null;
+        } else {
+            return new Range(startNumberIndex, endNumberIndex);
+        }
+    }
+
+    private static class Range {
+        private int startIndex;
+
+        private int endIndex;
+
+        public Range(int startIndex, int endIndex) {
+            this.startIndex = startIndex;
+            this.endIndex = endIndex;
+        }
+
+        public int getStartIndex() {
+            return startIndex;
+        }
+
+        public int getEndIndex() {
+            return endIndex;
+        }
+    }
+
+    private static class NewRevision {
+        private String revision;
+
+        private String newRevision;
+
+        private String buildNumber;
+
+        private String newBuildNumber;
+
+        public NewRevision(String revision, String newRevision, String buildNumber,
+                String newBuildNumber) {
+            this.revision = revision;
+            this.newRevision = newRevision;
+            this.buildNumber = buildNumber;
+            this.newBuildNumber = newBuildNumber;
+        }
+
+        public String getRevision() {
+            return revision;
+        }
+
+        public String getNewRevision() {
+            return newRevision;
+        }
+
+        public String getBuildNumber() {
+            return buildNumber;
+        }
+
+        public String getNewBuildNumber() {
+            return newBuildNumber;
+        }
+    }
+
+    public String getRevSep() {
+        return revSep;
+    }
+
+    public void setRevSep(String revSep) {
+        this.revSep = revSep;
+    }
+
+    public String getDefaultBuildNumber() {
+        return defaultBuildNumber;
+    }
+
+    public void setDefaultBuildNumber(String defaultBuildNumber) {
+        this.defaultBuildNumber = defaultBuildNumber;
+    }
+}
diff --git a/src/java/org/apache/ivy/ant/IvyCacheFileset.java b/src/java/org/apache/ivy/ant/IvyCacheFileset.java
new file mode 100644
index 0000000..891a336
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/IvyCacheFileset.java
@@ -0,0 +1,204 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import java.io.File;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+import org.apache.ivy.core.report.ArtifactDownloadReport;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.DirectoryScanner;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.types.FileSet;
+import org.apache.tools.ant.types.PatternSet.NameEntry;
+
+/**
+ * Creates an ant fileset consisting in all artifacts found during a resolve. Note that this task is
+ * not compatible with the useOrigin mode.
+ */
+public class IvyCacheFileset extends IvyCacheTask {
+    private String setid;
+
+    public String getSetid() {
+        return setid;
+    }
+
+    public void setSetid(String id) {
+        setid = id;
+    }
+
+    public void setUseOrigin(boolean useOrigin) {
+        if (useOrigin) {
+            throw new UnsupportedOperationException(
+                    "the cachefileset task does not support the useOrigin mode, since filesets "
+                            + "require to have only one root directory. Please use the the cachepath "
+                            + "task instead");
+        }
+    }
+
+    public void doExecute() throws BuildException {
+        prepareAndCheck();
+        if (setid == null) {
+            throw new BuildException("setid is required in ivy cachefileset");
+        }
+        try {
+            List paths = getArtifactReports();
+            File base = null;
+            for (Iterator iter = paths.iterator(); iter.hasNext();) {
+                ArtifactDownloadReport a = (ArtifactDownloadReport) iter.next();
+                if (a.getLocalFile() != null) {
+                    base = getBaseDir(base, a.getLocalFile());
+                }
+            }
+
+            FileSet fileset;
+            if (base == null) {
+                fileset = new EmptyFileSet();
+            } else {
+                fileset = new FileSet();
+                fileset.setDir(base);
+                for (Iterator iter = paths.iterator(); iter.hasNext();) {
+                    ArtifactDownloadReport a = (ArtifactDownloadReport) iter.next();
+                    if (a.getLocalFile() != null) {
+                        NameEntry ne = fileset.createInclude();
+                        ne.setName(getPath(base, a.getLocalFile()));
+                    }
+                }
+            }
+
+            fileset.setProject(getProject());
+            getProject().addReference(setid, fileset);
+        } catch (Exception ex) {
+            throw new BuildException("impossible to build ivy cache fileset: " + ex, ex);
+        }
+    }
+
+    /**
+     * Returns the path of the file relative to the given base directory.
+     * 
+     * @param base
+     *            the parent directory to which the file must be evaluated.
+     * @param file
+     *            the file for which the path should be returned
+     * @return the path of the file relative to the given base directory.
+     */
+    private String getPath(File base, File file) {
+        String absoluteBasePath = base.getAbsolutePath();
+
+        int beginIndex = absoluteBasePath.length();
+
+        // checks if the basePath ends with the file separator (which can for instance
+        // happen if the basePath is the root on unix)
+        if (!absoluteBasePath.endsWith(File.separator)) {
+            beginIndex++; // skip the seperator char as well
+        }
+
+        return file.getAbsolutePath().substring(beginIndex);
+    }
+
+    /**
+     * Returns the common base directory between a current base directory and a given file.
+     * <p>
+     * The returned base directory must be a parent of both the current base and the given file.
+     * </p>
+     * 
+     * @param base
+     *            the current base directory, may be null.
+     * @param file
+     *            the file for which the new base directory should be returned.
+     * @return the common base directory between a current base directory and a given file.
+     */
+    File getBaseDir(File base, File file) {
+        if (base == null) {
+            return file.getParentFile().getAbsoluteFile();
+        } else {
+            Iterator bases = getParents(base).iterator();
+            Iterator fileParents = getParents(file.getAbsoluteFile()).iterator();
+            File result = null;
+            while (bases.hasNext() && fileParents.hasNext()) {
+                File next = (File) bases.next();
+                if (next.equals(fileParents.next())) {
+                    result = next;
+                } else {
+                    break;
+                }
+            }
+            return result;
+        }
+    }
+
+    /**
+     * @return a list of files, starting with the root and ending with the file itself
+     */
+    private LinkedList/* <File> */getParents(File file) {
+        LinkedList r = new LinkedList();
+        while (file != null) {
+            r.addFirst(file);
+            file = file.getParentFile();
+        }
+        return r;
+    }
+
+    private static class EmptyFileSet extends FileSet {
+
+        private DirectoryScanner ds = new EmptyDirectoryScanner();
+
+        public Iterator iterator() {
+            return new EmptyIterator();
+        }
+
+        public Object clone() {
+            return new EmptyFileSet();
+        }
+
+        public int size() {
+            return 0;
+        }
+
+        public DirectoryScanner getDirectoryScanner(Project project) {
+            return ds;
+        }
+    }
+
+    private static class EmptyIterator implements Iterator {
+
+        public boolean hasNext() {
+            return false;
+        }
+
+        public Object next() {
+            throw new NoSuchElementException("EmptyFileSet Iterator");
+        }
+
+        public void remove() {
+            throw new IllegalStateException("EmptyFileSet Iterator");
+        }
+
+    }
+
+    private static class EmptyDirectoryScanner extends DirectoryScanner {
+
+        public String[] getIncludedFiles() {
+            return new String[0];
+        }
+
+    }
+}
diff --git a/src/java/org/apache/ivy/ant/IvyCachePath.java b/src/java/org/apache/ivy/ant/IvyCachePath.java
new file mode 100644
index 0000000..3fe7645
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/IvyCachePath.java
@@ -0,0 +1,116 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import java.io.File;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.ivy.core.report.ArtifactDownloadReport;
+import org.apache.ivy.osgi.core.BundleInfo;
+import org.apache.ivy.osgi.core.ManifestParser;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.types.Path;
+
+/**
+ * Creates an ant path consisting in all artifacts found during a resolve.
+ */
+public class IvyCachePath extends IvyCacheTask {
+
+    private String pathid;
+
+    private String id;
+
+    private boolean osgi = false;
+
+    public String getPathid() {
+        return pathid;
+    }
+
+    public void setPathid(String id) {
+        pathid = id;
+    }
+
+    public void setOsgi(boolean osgi) {
+        this.osgi = osgi;
+    }
+
+    /**
+     * @deprecated use setPathid instead
+     * @param id
+     */
+    @Deprecated
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public void doExecute() throws BuildException {
+        prepareAndCheck();
+        if (pathid == null) {
+            if (id != null) {
+                pathid = id;
+                log("ID IS DEPRECATED, PLEASE USE PATHID INSTEAD", Project.MSG_WARN);
+            } else {
+                throw new BuildException("pathid is required in ivy classpath");
+            }
+        }
+        try {
+            Path path = new Path(getProject());
+            getProject().addReference(pathid, path);
+            for (Iterator iter = getArtifactReports().iterator(); iter.hasNext();) {
+                ArtifactDownloadReport a = (ArtifactDownloadReport) iter.next();
+                File f = a.getLocalFile();
+                if (a.getUnpackedLocalFile() != null) {
+                    f = a.getUnpackedLocalFile();
+                }
+                addToPath(path, f);
+            }
+        } catch (Exception ex) {
+            throw new BuildException("impossible to build ivy path: " + ex, ex);
+        }
+
+    }
+
+    protected void addToPath(Path path, File f) throws Exception {
+        if (!osgi || !f.isDirectory()) {
+            path.createPathElement().setLocation(f);
+            return;
+        }
+        File manifest = new File(f, "META-INF/MANIFEST.MF");
+        if (!manifest.exists()) {
+            path.createPathElement().setLocation(f);
+            return;
+        }
+        BundleInfo bundleInfo = ManifestParser.parseManifest(manifest);
+        List/* <String> */cp = bundleInfo.getClasspath();
+        if (cp == null) {
+            path.createPathElement().setLocation(f);
+            return;
+        }
+        for (int i = 0; i < cp.size(); i++) {
+            String p = (String) cp.get(i);
+            if (p.equals(".")) {
+                path.createPathElement().setLocation(f);
+            } else {
+                path.createPathElement().setLocation(new File(f, p));
+            }
+        }
+    }
+
+}
diff --git a/src/java/org/apache/ivy/ant/IvyCacheTask.java b/src/java/org/apache/ivy/ant/IvyCacheTask.java
new file mode 100644
index 0000000..2360860
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/IvyCacheTask.java
@@ -0,0 +1,101 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.ivy.core.cache.ResolutionCacheManager;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.report.ArtifactDownloadReport;
+import org.apache.ivy.core.report.ConfigurationResolveReport;
+import org.apache.ivy.core.report.ResolveReport;
+import org.apache.ivy.core.resolve.ResolveOptions;
+import org.apache.ivy.plugins.report.XmlReportParser;
+import org.apache.ivy.util.Message;
+import org.apache.tools.ant.BuildException;
+
+/**
+ * Base class for the cache path related classes: cachepath and cachefileset. Most of the behviour
+ * is common to the two, since only the produced element differs.
+ */
+public abstract class IvyCacheTask extends IvyPostResolveTask {
+
+    protected List<ArtifactDownloadReport> getArtifactReports() throws BuildException,
+            ParseException, IOException {
+        Collection<ArtifactDownloadReport> artifacts = getAllArtifactReports();
+        List<ArtifactDownloadReport> ret = new ArrayList<ArtifactDownloadReport>();
+        for (ArtifactDownloadReport artifactReport : artifacts) {
+            if (getArtifactFilter().accept(artifactReport.getArtifact())) {
+                ret.add(artifactReport);
+            }
+        }
+
+        return ret;
+    }
+
+    private Collection<ArtifactDownloadReport> getAllArtifactReports() throws ParseException,
+            IOException {
+        String[] confs = splitConfs(getConf());
+        Collection<ArtifactDownloadReport> all = new LinkedHashSet<ArtifactDownloadReport>();
+
+        ResolveReport report = getResolvedReport();
+        if (report != null) {
+            Message.debug("using internal report instance to get artifacts list");
+            for (int i = 0; i < confs.length; i++) {
+                ConfigurationResolveReport configurationReport = report
+                        .getConfigurationReport(confs[i]);
+                if (configurationReport == null) {
+                    throw new BuildException("bad confs provided: " + confs[i]
+                            + " not found among " + Arrays.asList(report.getConfigurations()));
+                }
+                Set<ModuleRevisionId> revisions = configurationReport.getModuleRevisionIds();
+                for (ModuleRevisionId revId : revisions) {
+                    ArtifactDownloadReport[] aReports = configurationReport
+                            .getDownloadReports(revId);
+                    all.addAll(Arrays.asList(aReports));
+                }
+            }
+        } else {
+            Message.debug("using stored report to get artifacts list");
+
+            XmlReportParser parser = new XmlReportParser();
+            ResolutionCacheManager cacheMgr = getIvyInstance().getResolutionCacheManager();
+            String resolvedId = getResolveId();
+            if (resolvedId == null) {
+                resolvedId = ResolveOptions.getDefaultResolveId(getResolvedModuleId());
+            }
+            for (int i = 0; i < confs.length; i++) {
+                File reportFile = cacheMgr.getConfigurationResolveReportInCache(resolvedId,
+                    confs[i]);
+                parser.parse(reportFile);
+
+                ArtifactDownloadReport[] aReports = parser.getArtifactReports();
+                all.addAll(Arrays.asList(aReports));
+            }
+        }
+        return all;
+    }
+}
diff --git a/src/java/org/apache/ivy/ant/IvyCheck.java b/src/java/org/apache/ivy/ant/IvyCheck.java
new file mode 100644
index 0000000..c101274
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/IvyCheck.java
@@ -0,0 +1,98 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.ivy.Ivy;
+import org.apache.ivy.util.Message;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.DirectoryScanner;
+import org.apache.tools.ant.types.FileSet;
+
+/**
+ * Checks the given ivy file using current configuration to see if all dependencies are available,
+ * with good confs. If a resolver name is given, it also checks that the declared publications are
+ * available in the corresponding resolver. Note that the check is not performed recursively, i.e.
+ * if a dependency has itself dependencies badly described or not available, this check will not
+ * discover it.
+ */
+public class IvyCheck extends IvyTask {
+    private File file = null;
+
+    private List filesets = new ArrayList();
+
+    private String resolvername;
+
+    public File getFile() {
+        return file;
+    }
+
+    public void setFile(File file) {
+        this.file = file;
+    }
+
+    /**
+     * Adds a set of files to check.
+     * 
+     * @param set
+     *            a set of files to check
+     */
+    public void addFileset(FileSet set) {
+        filesets.add(set);
+    }
+
+    public String getResolvername() {
+        return resolvername;
+    }
+
+    public void setResolvername(String resolverName) {
+        resolvername = resolverName;
+    }
+
+    public void doExecute() throws BuildException {
+        try {
+            Ivy ivy = getIvyInstance();
+            if (file != null) {
+                if (ivy.check(file.toURI().toURL(), resolvername)) {
+                    Message.verbose("checked " + file + ": OK");
+                }
+            }
+            for (int i = 0; i < filesets.size(); i++) {
+                FileSet fs = (FileSet) filesets.get(i);
+                DirectoryScanner ds = fs.getDirectoryScanner(getProject());
+
+                File fromDir = fs.getDir(getProject());
+
+                String[] srcFiles = ds.getIncludedFiles();
+                for (int j = 0; j < srcFiles.length; j++) {
+                    File file = new File(fromDir, srcFiles[j]);
+                    if (ivy.check(file.toURI().toURL(), resolvername)) {
+                        Message.verbose("checked " + file + ": OK");
+                    }
+                }
+            }
+        } catch (MalformedURLException e) {
+            throw new BuildException("impossible to convert a file to an url! " + e, e);
+        }
+    }
+
+}
diff --git a/src/java/org/apache/ivy/ant/IvyCleanCache.java b/src/java/org/apache/ivy/ant/IvyCleanCache.java
new file mode 100644
index 0000000..251f3e2
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/IvyCleanCache.java
@@ -0,0 +1,85 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import org.apache.ivy.core.cache.RepositoryCacheManager;
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.tools.ant.BuildException;
+
+/**
+ * Cleans the content of Ivy cache(s).
+ */
+public class IvyCleanCache extends IvyTask {
+    public static final String ALL = "*";
+
+    public static final String NONE = "NONE";
+
+    private boolean resolution = true;
+
+    private String cache = ALL;
+
+    public String getCache() {
+        return cache;
+    }
+
+    /**
+     * Sets the name of the repository cache to clean, '*' for all caches, 'NONE' for no repository
+     * cache cleaning at all.
+     * 
+     * @param cache
+     *            the name of the cache to clean. Must not be <code>null</code>.
+     */
+    public void setCache(String cache) {
+        this.cache = cache;
+    }
+
+    public boolean isResolution() {
+        return resolution;
+    }
+
+    /**
+     * Sets weither the resolution cache should be cleaned or not.
+     * 
+     * @param resolution
+     *            <code>true</code> if the resolution cache should be cleaned, <code>false</code>
+     *            otherwise.
+     */
+    public void setResolution(boolean resolution) {
+        this.resolution = resolution;
+    }
+
+    public void doExecute() throws BuildException {
+        IvySettings settings = getIvyInstance().getSettings();
+        if (isResolution()) {
+            settings.getResolutionCacheManager().clean();
+        }
+        if (ALL.equals(getCache())) {
+            RepositoryCacheManager[] caches = settings.getRepositoryCacheManagers();
+            for (int i = 0; i < caches.length; i++) {
+                caches[i].clean();
+            }
+        } else if (!NONE.equals(getCache())) {
+            RepositoryCacheManager cache = settings.getRepositoryCacheManager(getCache());
+            if (cache == null) {
+                throw new BuildException("unknown cache '" + getCache() + "'");
+            } else {
+                cache.clean();
+            }
+        }
+    }
+}
diff --git a/src/java/org/apache/ivy/ant/IvyConfigure.java b/src/java/org/apache/ivy/ant/IvyConfigure.java
new file mode 100644
index 0000000..5e05adc
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/IvyConfigure.java
@@ -0,0 +1,157 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.Task;
+
+/**
+ * Configure Ivy with an ivysettings.xml file
+ */
+public class IvyConfigure extends Task {
+
+    /**
+     * Use to override a previous definition of settings with the same id
+     */
+    public static final String OVERRIDE_TRUE = "true";
+
+    /**
+     * Use to avoid overriding a previous definition of settings with the same id
+     */
+    public static final String OVERRIDE_FALSE = "false";
+
+    /**
+     * Use to raise an error if attempting to override a previous definition of settings with the
+     * same id
+     */
+    public static final String OVERRIDE_NOT_ALLOWED = "notallowed";
+
+    private static final Collection OVERRIDE_VALUES = Arrays.asList(new String[] {OVERRIDE_TRUE,
+            OVERRIDE_FALSE, OVERRIDE_NOT_ALLOWED});
+
+    private String override = OVERRIDE_NOT_ALLOWED;
+
+    private IvyAntSettings settings = new IvyAntSettings();
+
+    public void setSettingsId(String settingsId) {
+        settings.setId(settingsId);
+    }
+
+    public String getSettingsId() {
+        return settings.getId();
+    }
+
+    public void setOverride(String override) {
+        if (!OVERRIDE_VALUES.contains(override)) {
+            throw new IllegalArgumentException("invalid override value '" + override + "'. "
+                    + "Valid values are " + OVERRIDE_VALUES);
+        }
+        this.override = override;
+    }
+
+    public String getOverride() {
+        return override;
+    }
+
+    public File getFile() {
+        return settings.getFile();
+    }
+
+    public void setFile(File file) {
+        settings.setFile(file);
+    }
+
+    public URL getUrl() {
+        return settings.getUrl();
+    }
+
+    public void setUrl(String url) throws MalformedURLException {
+        settings.setUrl(url);
+    }
+
+    public void setUrl(URL url) {
+        if (url == null) {
+            throw new NullPointerException("Cannot set a null URL");
+        }
+        settings.setUrl(url);
+    }
+
+    public String getRealm() {
+        return settings.getRealm();
+    }
+
+    public void setRealm(String realm) {
+        settings.setRealm(realm);
+    }
+
+    public String getHost() {
+        return settings.getHost();
+    }
+
+    public void setHost(String host) {
+        settings.setHost(host);
+    }
+
+    public String getUserName() {
+        return settings.getUsername();
+    }
+
+    public void setUserName(String userName) {
+        settings.setUsername(userName);
+    }
+
+    public String getPasswd() {
+        return settings.getPasswd();
+    }
+
+    public void setPasswd(String passwd) {
+        settings.setPasswd(passwd);
+    }
+
+    public void execute() throws BuildException {
+        String settingsId = settings.getId();
+        Object otherRef = getProject().getReference(settingsId);
+
+        if ((otherRef != null) && OVERRIDE_NOT_ALLOWED.equals(override)) {
+            throw new BuildException(
+                    "Overriding a previous definition of ivy:settings with the id '" + settingsId
+                            + "' is not allowed when using override='" + OVERRIDE_NOT_ALLOWED
+                            + "'.");
+        }
+
+        if ((otherRef != null) && OVERRIDE_FALSE.equals(override)) {
+            verbose("A settings definition is already available for " + settingsId + ": skipping");
+            return;
+        }
+
+        settings.setProject(getProject());
+        getProject().addReference(settingsId, settings);
+        settings.createIvyEngine(this);
+    }
+
+    private void verbose(String msg) {
+        log(msg, Project.MSG_VERBOSE);
+    }
+}
diff --git a/src/java/org/apache/ivy/ant/IvyConflict.java b/src/java/org/apache/ivy/ant/IvyConflict.java
new file mode 100644
index 0000000..a4a2a1d
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/IvyConflict.java
@@ -0,0 +1,77 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+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.FixedConflictManager;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+
+public class IvyConflict {
+
+    private String org;
+
+    private String module;
+
+    private String manager;
+
+    private String rev;
+
+    private String matcher;
+
+    public void setOrg(String org) {
+        this.org = org;
+    }
+
+    public void setModule(String module) {
+        this.module = module;
+    }
+
+    public void setManager(String manager) {
+        this.manager = manager;
+    }
+
+    public void setRev(String rev) {
+        this.rev = rev;
+    }
+
+    public void setMatcher(String matcher) {
+        this.matcher = matcher;
+    }
+
+    void addConflict(DefaultModuleDescriptor md, IvySettings settings) {
+        String matcherName = matcher == null ? PatternMatcher.EXACT : matcher;
+        String orgPattern = org == null ? PatternMatcher.ANY_EXPRESSION : org;
+        String modulePattern = module == null ? PatternMatcher.ANY_EXPRESSION : module;
+        ConflictManager cm = null;
+        if (rev != null) {
+            String[] revs = rev.split(",");
+            for (int i = 0; i < revs.length; i++) {
+                revs[i] = revs[i].trim();
+            }
+            cm = new FixedConflictManager(revs);
+        } else if (manager != null) {
+            cm = settings.getConflictManager(manager);
+        }
+        md.addConflictManager(new ModuleId(orgPattern, modulePattern),
+            settings.getMatcher(matcherName), cm);
+    }
+
+}
diff --git a/src/java/org/apache/ivy/ant/IvyConvertPom.java b/src/java/org/apache/ivy/ant/IvyConvertPom.java
new file mode 100644
index 0000000..cfd2209
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/IvyConvertPom.java
@@ -0,0 +1,77 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.text.ParseException;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.plugins.parser.m2.PomModuleDescriptorParser;
+import org.apache.ivy.plugins.repository.url.URLResource;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+
+/**
+ * Convert a pom to an ivy file
+ */
+public class IvyConvertPom extends IvyTask {
+    private File pomFile = null;
+
+    private File ivyFile = null;
+
+    public File getPomFile() {
+        return pomFile;
+    }
+
+    public void setPomFile(File file) {
+        pomFile = file;
+    }
+
+    public File getIvyFile() {
+        return ivyFile;
+    }
+
+    public void setIvyFile(File ivyFile) {
+        this.ivyFile = ivyFile;
+    }
+
+    public void doExecute() throws BuildException {
+        try {
+            if (pomFile == null) {
+                throw new BuildException("source pom file is required for convertpom task");
+            }
+            if (ivyFile == null) {
+                throw new BuildException("destination ivy file is required for convertpom task");
+            }
+            ModuleDescriptor md = PomModuleDescriptorParser.getInstance().parseDescriptor(
+                getSettings(), pomFile.toURI().toURL(), false);
+            PomModuleDescriptorParser.getInstance().toIvyFile(pomFile.toURI().toURL().openStream(),
+                new URLResource(pomFile.toURI().toURL()), getIvyFile(), md);
+        } catch (MalformedURLException e) {
+            throw new BuildException("unable to convert given pom file to url: " + pomFile + ": "
+                    + e, e);
+        } catch (ParseException e) {
+            log(e.getMessage(), Project.MSG_ERR);
+            throw new BuildException("syntax errors in pom file " + pomFile + ": " + e, e);
+        } catch (Exception e) {
+            throw new BuildException("impossible convert given pom file to ivy file: " + e
+                    + " from=" + pomFile + " to=" + ivyFile, e);
+        }
+    }
+}
diff --git a/src/java/org/apache/ivy/ant/IvyDeliver.java b/src/java/org/apache/ivy/ant/IvyDeliver.java
new file mode 100644
index 0000000..06473c9
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/IvyDeliver.java
@@ -0,0 +1,452 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import java.io.File;
+import java.util.Date;
+
+import org.apache.ivy.Ivy;
+import org.apache.ivy.core.deliver.DefaultPublishingDRResolver;
+import org.apache.ivy.core.deliver.DeliverOptions;
+import org.apache.ivy.core.deliver.PublishingDependencyRevisionResolver;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.module.status.StatusManager;
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.ivy.util.DateUtil;
+import org.apache.ivy.util.Message;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.taskdefs.CallTarget;
+import org.apache.tools.ant.taskdefs.Echo;
+import org.apache.tools.ant.taskdefs.Input;
+import org.apache.tools.ant.taskdefs.Property;
+
+/**
+ * Trigger the delivery of a module, which may consist in a recursive delivery of dependencies and
+ * on the replacement in the ivy file of dynamic revisions (like latest.integration) by static ones.
+ */
+public class IvyDeliver extends IvyTask {
+    private final class DeliverDRResolver extends DefaultPublishingDRResolver {
+        public String resolve(ModuleDescriptor published, String publishedStatus,
+                ModuleRevisionId depMrid, String depStatus) {
+            if (StatusManager.getCurrent().isIntegration(publishedStatus)) {
+                // published status is integration one, nothing to ask
+                return super.resolve(published, publishedStatus, depMrid, depStatus);
+            }
+
+            // we are publishing a delivery (a non integration module)
+
+            if (!StatusManager.getCurrent().isIntegration(depStatus)) {
+                // dependency is already a delivery, nothing to ask
+                return super.resolve(published, publishedStatus, depMrid, depStatus);
+            }
+
+            // the dependency is not a delivery
+
+            String statusProperty = depMrid.getName() + "." + depMrid.getRevision() + ".status";
+            String versionProperty = depMrid.getName() + "." + depMrid.getRevision() + ".version";
+            String deliveredProperty = depMrid.getName() + "." + depMrid.getRevision()
+                    + ".delivered";
+
+            String version = getProject().getProperty(versionProperty);
+            String status = getProject().getProperty(statusProperty);
+            String delivered = getProject().getProperty(deliveredProperty);
+            Message.debug("found version = " + version + " status=" + status + " delivered="
+                    + delivered);
+            if (version != null && status != null) {
+                if ("true".equals(delivered)) {
+                    // delivery has already been done : just return the value
+                    return version;
+                } else {
+                    deliverDependency(depMrid, version, status, depStatus);
+                    loadDeliveryList();
+                    return version;
+                }
+            }
+
+            /**
+             * By setting these properties: recursive.delivery.status and
+             * recursive.delivery.version, then if the specific status/version is not found, then we
+             * will use the status/version set in these global properties. This will apply to all
+             * artifacts in the system. This patch is meant to be used for recursive deliveries so
+             * that all deliveries will use the global status/version unless a more specific one is
+             * set.
+             */
+            String globalStatusProperty = "recursive.delivery.status";
+            String globalVersionProperty = "recursive.delivery.version";
+            version = getProject().getProperty(globalVersionProperty);
+            status = getProject().getProperty(globalStatusProperty);
+            if (version != null && status != null) {
+                // found global delivery properties
+                delivered = getProject().getProperty(
+                    "recursive." + depMrid.getName() + ".delivered");
+                Message.debug("found global version = " + version + " and global status=" + status
+                        + " - delivered = " + delivered);
+                if ("true".equals(delivered)) {
+                    // delivery has already been done : just return the value
+                    return version;
+                } else {
+                    getProject().setProperty(statusProperty, status);
+                    deliverDependency(depMrid, version, status, depStatus);
+                    loadDeliveryList();
+                    return version;
+                }
+            }
+
+            // we must ask the user what version and status he want to have
+            // for the dependency
+            Input input = (Input) getProject().createTask("input");
+            input.setOwningTarget(getOwningTarget());
+            input.init();
+
+            // ask status
+            input.setMessage(depMrid.getName() + " " + depMrid.getRevision()
+                    + ": please enter a status: ");
+            input.setValidargs(StatusManager.getCurrent().getDeliveryStatusListString());
+            input.setAddproperty(statusProperty);
+            input.perform();
+            status = getProject().getProperty(statusProperty);
+            appendDeliveryList(statusProperty + " = " + status);
+
+            // ask version
+            input.setMessage(depMrid.getName() + " " + depMrid.getRevision()
+                    + ": please enter a version: ");
+            input.setValidargs(null);
+            input.setAddproperty(versionProperty);
+            input.perform();
+
+            version = getProject().getProperty(versionProperty);
+            appendDeliveryList(versionProperty + " = " + version);
+            deliverDependency(depMrid, version, status, depStatus);
+
+            loadDeliveryList();
+
+            return version;
+        }
+
+        public void deliverDependency(ModuleRevisionId depMrid, String version, String status,
+                String depStatus) {
+            // call deliver target if any
+            if (deliverTarget != null && deliverTarget.trim().length() > 0) {
+
+                CallTarget ct = (CallTarget) getProject().createTask("antcall");
+                ct.setOwningTarget(getOwningTarget());
+                ct.init();
+                ct.setTarget(deliverTarget);
+                ct.setInheritAll(true);
+                ct.setInheritRefs(true);
+                Property param = ct.createParam();
+                param.setName("dependency.name");
+                param.setValue(depMrid.getName());
+                param = ct.createParam();
+                param.setName("dependency.published.status");
+                param.setValue(status);
+                param = ct.createParam();
+                param.setName("dependency.published.version");
+                param.setValue(version);
+                param = ct.createParam();
+                param.setName("dependency.version");
+                param.setValue(depMrid.getRevision());
+                param = ct.createParam();
+                param.setName("dependency.status");
+                param.setValue(depStatus == null ? "null" : depStatus);
+
+                ct.perform();
+
+                String deliveredProperty = depMrid.getName() + "." + depMrid.getRevision()
+                        + ".delivered";
+                getProject().setProperty(deliveredProperty, "true");
+                appendDeliveryList(deliveredProperty + " = true");
+
+                getProject().setProperty("recursive." + depMrid.getName() + ".delivered", "true");
+                appendDeliveryList("recursive." + depMrid.getName() + ".delivered" + " = true");
+            }
+        }
+
+    }
+
+    private String organisation;
+
+    private String module;
+
+    private String revision;
+
+    private String pubRevision;
+
+    private String deliverpattern;
+
+    private String status;
+
+    private String pubdate;
+
+    private String deliverTarget;
+
+    private File deliveryList;
+
+    private boolean replacedynamicrev = true;
+
+    private boolean replaceForcedRev = false;
+
+    private String resolveId;
+
+    private String conf;
+
+    private String pubBranch;
+
+    private boolean generateRevConstraint = true;
+
+    private boolean merge = true;
+
+    public void setCache(File cache) {
+        cacheAttributeNotSupported();
+    }
+
+    public String getDeliverpattern() {
+        return deliverpattern;
+    }
+
+    public void setDeliverpattern(String destivypattern) {
+        this.deliverpattern = destivypattern;
+    }
+
+    public String getModule() {
+        return module;
+    }
+
+    public void setModule(String module) {
+        this.module = module;
+    }
+
+    public String getOrganisation() {
+        return organisation;
+    }
+
+    public void setOrganisation(String organisation) {
+        this.organisation = organisation;
+    }
+
+    public String getPubdate() {
+        return pubdate;
+    }
+
+    public void setPubdate(String pubdate) {
+        this.pubdate = pubdate;
+    }
+
+    public String getPubrevision() {
+        return pubRevision;
+    }
+
+    public void setPubrevision(String pubRevision) {
+        this.pubRevision = pubRevision;
+    }
+
+    public String getPubbranch() {
+        return pubBranch;
+    }
+
+    public void setPubbranch(String pubBranch) {
+        this.pubBranch = pubBranch;
+    }
+
+    public String getRevision() {
+        return revision;
+    }
+
+    public void setRevision(String revision) {
+        this.revision = revision;
+    }
+
+    public String getStatus() {
+        return status;
+    }
+
+    public void setStatus(String status) {
+        this.status = status;
+    }
+
+    public void setDelivertarget(String deliverTarget) {
+        this.deliverTarget = deliverTarget;
+    }
+
+    public void setDeliveryList(File deliveryList) {
+        this.deliveryList = deliveryList;
+    }
+
+    public boolean isReplacedynamicrev() {
+        return replacedynamicrev;
+    }
+
+    public void setReplacedynamicrev(boolean replacedynamicrev) {
+        this.replacedynamicrev = replacedynamicrev;
+    }
+
+    public boolean isReplaceForcedRev() {
+        return replaceForcedRev;
+    }
+
+    public void setReplaceForcedRev(boolean replaceForcedRev) {
+        this.replaceForcedRev = replaceForcedRev;
+    }
+
+    public String getResolveId() {
+        return resolveId;
+    }
+
+    public void setResolveId(String resolveId) {
+        this.resolveId = resolveId;
+    }
+
+    public String getConf() {
+        return conf;
+    }
+
+    public void setConf(String confs) {
+        conf = confs;
+    }
+
+    public boolean isGenerateRevConstraint() {
+        return generateRevConstraint;
+    }
+
+    public void setGenerateRevConstraint(boolean generateRevConstraint) {
+        this.generateRevConstraint = generateRevConstraint;
+    }
+
+    public boolean isMerge() {
+        return merge;
+    }
+
+    public void setMerge(boolean merge) {
+        this.merge = merge;
+    }
+
+    public void doExecute() throws BuildException {
+        Ivy ivy = getIvyInstance();
+        IvySettings settings = ivy.getSettings();
+
+        organisation = getProperty(organisation, settings, "ivy.organisation", resolveId);
+        module = getProperty(module, settings, "ivy.module", resolveId);
+        revision = getProperty(revision, settings, "ivy.revision", resolveId);
+        pubBranch = getProperty(pubBranch, settings, "ivy.deliver.branch");
+        pubRevision = getProperty(pubRevision, settings, "ivy.deliver.revision");
+        deliverpattern = getProperty(deliverpattern, settings, "ivy.deliver.ivy.pattern");
+        status = getProperty(status, settings, "ivy.status");
+        if (deliveryList == null) {
+            String deliveryListPath = getProperty(settings, "ivy.delivery.list.file");
+            if (deliveryListPath == null) {
+                deliveryList = new File(System.getProperty("java.io.tmpdir")
+                        + "/delivery.properties");
+            } else {
+                deliveryList = getProject().resolveFile(settings.substitute(deliveryListPath));
+            }
+        }
+        if (resolveId == null) {
+            if (organisation == null) {
+                throw new BuildException("no organisation provided for ivy deliver task: "
+                        + "It can either be set explicitely via the attribute 'organisation' "
+                        + "or via 'ivy.organisation' property or a prior call to <resolve/>");
+            }
+            if (module == null) {
+                throw new BuildException("no module name provided for ivy deliver task: "
+                        + "It can either be set explicitely via the attribute 'module' "
+                        + "or via 'ivy.module' property or a prior call to <resolve/>");
+            }
+        }
+        if (revision == null) {
+            revision = Ivy.getWorkingRevision();
+        }
+        Date pubdate = getPubDate(this.pubdate, new Date());
+        if (pubRevision == null) {
+            if (revision.startsWith("working@")) {
+                pubRevision = DateUtil.format(pubdate);
+            } else {
+                pubRevision = revision;
+            }
+        }
+        if (deliverpattern == null) {
+            throw new BuildException(
+                    "deliver ivy pattern is missing: either provide it as parameters "
+                            + "or through ivy.deliver.ivy.pattern properties");
+        }
+        if (status == null) {
+            throw new BuildException(
+                    "no status provided: either provide it as parameter or through "
+                            + "the ivy.status.default property");
+        }
+
+        ModuleRevisionId mrid = null;
+        if (resolveId == null) {
+            mrid = ModuleRevisionId.newInstance(organisation, module, revision);
+        }
+        boolean isLeading = false;
+        try {
+            if (!deliveryList.exists()) {
+                isLeading = true;
+            }
+
+            loadDeliveryList();
+
+            PublishingDependencyRevisionResolver drResolver;
+            if (deliverTarget != null && deliverTarget.trim().length() > 0) {
+                drResolver = new DeliverDRResolver();
+            } else {
+                drResolver = new DefaultPublishingDRResolver();
+            }
+
+            DeliverOptions options = new DeliverOptions(status, pubdate, drResolver,
+                    doValidate(settings), replacedynamicrev, splitConfs(conf))
+                    .setResolveId(resolveId).setReplaceForcedRevisions(isReplaceForcedRev())
+                    .setGenerateRevConstraint(generateRevConstraint).setMerge(merge)
+                    .setPubBranch(pubBranch);
+            if (mrid == null) {
+                ivy.deliver(pubRevision, deliverpattern, options);
+            } else {
+                ivy.deliver(mrid, pubRevision, deliverpattern, options);
+            }
+        } catch (Exception e) {
+            throw new BuildException("impossible to deliver " + mrid == null ? resolveId : mrid
+                    + ": " + e, e);
+        } finally {
+            if (isLeading) {
+                if (deliveryList.exists()) {
+                    deliveryList.delete();
+                }
+            }
+        }
+    }
+
+    private void loadDeliveryList() {
+        Property property = (Property) getProject().createTask("property");
+        property.setOwningTarget(getOwningTarget());
+        property.init();
+        property.setFile(deliveryList);
+        property.perform();
+    }
+
+    private void appendDeliveryList(String msg) {
+        Echo echo = (Echo) getProject().createTask("echo");
+        echo.setOwningTarget(getOwningTarget());
+        echo.init();
+        echo.setFile(deliveryList);
+        echo.setMessage(msg + "\n");
+        echo.setAppend(true);
+        echo.perform();
+    }
+
+}
diff --git a/src/java/org/apache/ivy/ant/IvyDependency.java b/src/java/org/apache/ivy/ant/IvyDependency.java
new file mode 100644
index 0000000..dbc8f21
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/IvyDependency.java
@@ -0,0 +1,193 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.DefaultExcludeRule;
+import org.apache.ivy.core.module.descriptor.DefaultIncludeRule;
+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.apache.ivy.core.settings.IvySettings;
+import org.apache.tools.ant.BuildException;
+
+public class IvyDependency {
+
+    private List/* <IvyDependencyConf> */confs = new ArrayList();
+
+    private List/* <IvyDependencyArtifact> */artifacts = new ArrayList();
+
+    private List/* <IvyDependencyExclude> */excludes = new ArrayList();
+
+    private List/* <IvyDependencyIncludes> */includes = new ArrayList();
+
+    private String org;
+
+    private String name;
+
+    private String rev;
+
+    private String branch;
+
+    private String conf;
+
+    private boolean changing;
+
+    private boolean force;
+
+    private boolean transitive = true;
+
+    public IvyDependencyConf createConf() {
+        IvyDependencyConf c = new IvyDependencyConf();
+        confs.add(c);
+        return c;
+    }
+
+    public IvyDependencyArtifact createArtifact() {
+        IvyDependencyArtifact artifact = new IvyDependencyArtifact();
+        artifacts.add(artifact);
+        return artifact;
+    }
+
+    public IvyDependencyExclude createExclude() {
+        IvyDependencyExclude exclude = new IvyDependencyExclude();
+        excludes.add(exclude);
+        return exclude;
+    }
+
+    public IvyDependencyInclude createInclude() {
+        IvyDependencyInclude include = new IvyDependencyInclude();
+        includes.add(include);
+        return include;
+    }
+
+    public String getOrg() {
+        return org;
+    }
+
+    public void setOrg(String org) {
+        this.org = org;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getRev() {
+        return rev;
+    }
+
+    public void setRev(String rev) {
+        this.rev = rev;
+    }
+
+    public String getBranch() {
+        return branch;
+    }
+
+    public void setBranch(String branch) {
+        this.branch = branch;
+    }
+
+    public String getConf() {
+        return conf;
+    }
+
+    public void setConf(String conf) {
+        this.conf = conf;
+    }
+
+    public boolean isChanging() {
+        return changing;
+    }
+
+    public void setChanging(boolean changing) {
+        this.changing = changing;
+    }
+
+    public boolean isForce() {
+        return force;
+    }
+
+    public void setForce(boolean force) {
+        this.force = force;
+    }
+
+    public boolean isTransitive() {
+        return transitive;
+    }
+
+    public void setTransitive(boolean transitive) {
+        this.transitive = transitive;
+    }
+
+    DependencyDescriptor asDependencyDescriptor(ModuleDescriptor md, String masterConf,
+            IvySettings settings) {
+        if (org == null) {
+            throw new BuildException("'org' is required on ");
+        }
+        if (name == null) {
+            throw new BuildException("'name' is required when using inline mode");
+        }
+        ModuleRevisionId mrid = ModuleRevisionId.newInstance(org, name, branch, rev);
+        DefaultDependencyDescriptor dd = new DefaultDependencyDescriptor(md, mrid, force, changing,
+                transitive);
+        if (conf != null) {
+            dd.addDependencyConfiguration(masterConf, conf);
+        } else {
+            dd.addDependencyConfiguration(masterConf, "*");
+        }
+
+        Iterator itConfs = confs.iterator();
+        while (itConfs.hasNext()) {
+            IvyDependencyConf c = (IvyDependencyConf) itConfs.next();
+            c.addConf(dd, masterConf);
+        }
+
+        Iterator itArtifacts = artifacts.iterator();
+        while (itArtifacts.hasNext()) {
+            IvyDependencyArtifact artifact = (IvyDependencyArtifact) itArtifacts.next();
+            artifact.addArtifact(dd, masterConf);
+        }
+
+        Iterator itExcludes = excludes.iterator();
+        while (itExcludes.hasNext()) {
+            IvyDependencyExclude exclude = (IvyDependencyExclude) itExcludes.next();
+            DefaultExcludeRule rule = exclude.asRule(settings);
+            dd.addExcludeRule(masterConf, rule);
+        }
+
+        Iterator itIncludes = includes.iterator();
+        while (itIncludes.hasNext()) {
+            IvyDependencyInclude include = (IvyDependencyInclude) itIncludes.next();
+            DefaultIncludeRule rule = include.asRule(settings);
+            dd.addIncludeRule(masterConf, rule);
+        }
+
+        return dd;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/ant/IvyDependencyArtifact.java b/src/java/org/apache/ivy/ant/IvyDependencyArtifact.java
new file mode 100644
index 0000000..e9d7197
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/IvyDependencyArtifact.java
@@ -0,0 +1,67 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import org.apache.ivy.core.module.descriptor.DefaultDependencyArtifactDescriptor;
+import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+import org.apache.tools.ant.BuildException;
+
+public class IvyDependencyArtifact {
+
+    private String name;
+
+    private String type;
+
+    private String ext;
+
+    private String url;
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public void setExt(String ext) {
+        this.ext = ext;
+    }
+
+    public void setUrl(String url) {
+        this.url = url;
+    }
+
+    void addArtifact(DefaultDependencyDescriptor dd, String masterConf) {
+        String typePattern = type == null ? PatternMatcher.ANY_EXPRESSION : type;
+        String extPattern = ext == null ? typePattern : ext;
+        URL u;
+        try {
+            u = url == null ? null : new URL(url);
+        } catch (MalformedURLException e) {
+            throw new BuildException("Malformed url in the artifact: " + e.getMessage(), e);
+        }
+        DefaultDependencyArtifactDescriptor dad = new DefaultDependencyArtifactDescriptor(dd, name,
+                typePattern, extPattern, u, null);
+        dd.addDependencyArtifact(masterConf, dad);
+    }
+}
diff --git a/src/java/org/apache/ivy/ant/IvyDependencyConf.java b/src/java/org/apache/ivy/ant/IvyDependencyConf.java
new file mode 100644
index 0000000..c42c956
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/IvyDependencyConf.java
@@ -0,0 +1,64 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
+
+public class IvyDependencyConf {
+
+    private List/* <IvyDependencyConfMapped> */mappeds = new ArrayList();
+
+    public static class IvyDependencyConfMapped {
+        private String name;
+
+        public void setName(String name) {
+            this.name = name;
+        }
+
+    }
+
+    private String mapped;
+
+    public void setMapped(String mapped) {
+        this.mapped = mapped;
+    }
+
+    public IvyDependencyConfMapped createMapped() {
+        IvyDependencyConfMapped m = new IvyDependencyConfMapped();
+        mappeds.add(m);
+        return m;
+    }
+
+    void addConf(DefaultDependencyDescriptor dd, String masterConf) {
+        if (mapped != null) {
+            String[] mappeds = mapped.split(",");
+            for (int i = 0; i < mappeds.length; i++) {
+                dd.addDependencyConfiguration(masterConf, mappeds[i].trim());
+            }
+        }
+        Iterator itMappeds = mappeds.iterator();
+        while (itMappeds.hasNext()) {
+            IvyDependencyConfMapped m = (IvyDependencyConfMapped) itMappeds.next();
+            dd.addDependencyConfiguration(masterConf, m.name);
+        }
+    }
+}
diff --git a/src/java/org/apache/ivy/ant/IvyDependencyExclude.java b/src/java/org/apache/ivy/ant/IvyDependencyExclude.java
new file mode 100644
index 0000000..5976fdc
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/IvyDependencyExclude.java
@@ -0,0 +1,75 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+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.core.settings.IvySettings;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+
+public class IvyDependencyExclude {
+
+    private String org;
+
+    private String module;
+
+    private String name;
+
+    private String type;
+
+    private String ext;
+
+    private String matcher;
+
+    public void setOrg(String org) {
+        this.org = org;
+    }
+
+    public void setModule(String module) {
+        this.module = module;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public void setExt(String ext) {
+        this.ext = ext;
+    }
+
+    public void setMatcher(String matcher) {
+        this.matcher = matcher;
+    }
+
+    DefaultExcludeRule asRule(IvySettings settings) {
+        String matcherName = matcher == null ? PatternMatcher.EXACT : matcher;
+        String orgPattern = org == null ? PatternMatcher.ANY_EXPRESSION : org;
+        String modulePattern = module == null ? PatternMatcher.ANY_EXPRESSION : module;
+        String namePattern = name == null ? PatternMatcher.ANY_EXPRESSION : name;
+        String typePattern = type == null ? PatternMatcher.ANY_EXPRESSION : type;
+        String extPattern = ext == null ? typePattern : ext;
+        ArtifactId aid = new ArtifactId(new ModuleId(orgPattern, modulePattern), namePattern,
+                typePattern, extPattern);
+        return new DefaultExcludeRule(aid, settings.getMatcher(matcherName), null);
+    }
+}
diff --git a/src/java/org/apache/ivy/ant/IvyDependencyInclude.java b/src/java/org/apache/ivy/ant/IvyDependencyInclude.java
new file mode 100644
index 0000000..c5e5cf1
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/IvyDependencyInclude.java
@@ -0,0 +1,62 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import org.apache.ivy.core.module.descriptor.DefaultIncludeRule;
+import org.apache.ivy.core.module.id.ArtifactId;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+
+public class IvyDependencyInclude {
+
+    private String name;
+
+    private String type;
+
+    private String ext;
+
+    private String matcher;
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public void setExt(String ext) {
+        this.ext = ext;
+    }
+
+    public void setMatcher(String matcher) {
+        this.matcher = matcher;
+    }
+
+    DefaultIncludeRule asRule(IvySettings settings) {
+        String matcherName = matcher == null ? PatternMatcher.EXACT : matcher;
+        String namePattern = name == null ? PatternMatcher.ANY_EXPRESSION : name;
+        String typePattern = type == null ? PatternMatcher.ANY_EXPRESSION : type;
+        String extPattern = ext == null ? typePattern : ext;
+        ArtifactId aid = new ArtifactId(new ModuleId(PatternMatcher.ANY_EXPRESSION,
+                PatternMatcher.ANY_EXPRESSION), namePattern, typePattern, extPattern);
+        return new DefaultIncludeRule(aid, settings.getMatcher(matcherName), null);
+    }
+
+}
diff --git a/src/java/org/apache/ivy/ant/IvyDependencyTree.java b/src/java/org/apache/ivy/ant/IvyDependencyTree.java
new file mode 100644
index 0000000..f7c9fee
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/IvyDependencyTree.java
@@ -0,0 +1,132 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.report.ResolveReport;
+import org.apache.ivy.core.resolve.IvyNode;
+import org.apache.ivy.core.resolve.IvyNodeCallers.Caller;
+import org.apache.ivy.core.resolve.IvyNodeEviction.EvictionData;
+import org.apache.tools.ant.BuildException;
+
+public class IvyDependencyTree extends IvyPostResolveTask {
+
+    private Map/* <ModuleRevisionId, List<IvyNode>> */dependencies = new HashMap/*
+                                                                                 * <ModuleRevisionId,
+                                                                                 * List<IvyNode>>
+                                                                                 */();
+
+    private boolean showEvicted = false;
+
+    public void doExecute() throws BuildException {
+        prepareAndCheck();
+        ResolveReport report = getResolvedReport();
+        log("Dependency tree for " + report.getResolveId());
+        ModuleRevisionId mrid = report.getModuleDescriptor().getModuleRevisionId();
+        // make dependency tree easier to fetch informations
+        for (Iterator iterator = report.getDependencies().iterator(); iterator.hasNext();) {
+            IvyNode dependency = (IvyNode) iterator.next();
+            populateDependencyTree(dependency, mrid, report);
+        }
+        printDependencies((List) dependencies.get(mrid), 0);
+    }
+
+    private void printDependencies(List/* <IvyNode> */dependencyList, int indent) {
+        for (Iterator iterator = dependencyList.iterator(); iterator.hasNext();) {
+            IvyNode dependency = (IvyNode) iterator.next();
+            boolean evicted = dependency.isEvicted(getConf());
+            if (evicted && !showEvicted) {
+                continue;
+            }
+            StringBuffer sb = new StringBuffer();
+            if (indent > 0) {
+                for (int i = 0; i < indent; i++) {
+                    if (i == indent - 1 && !iterator.hasNext() && !hasDependencies(dependency)) {
+                        sb.append("   ");
+                    } else {
+                        sb.append("|  ");
+                    }
+
+                }
+            }
+            if (iterator.hasNext()) {
+                sb.append("+- ");
+            } else {
+                sb.append("\\- ");
+            }
+            sb.append(dependency.getId().toString());
+            if (evicted && showEvicted) {
+                EvictionData evictedData = dependency.getEvictedData(getConf());
+                if (evictedData.isTransitivelyEvicted()) {
+                    sb.append(" transitively");
+                } else {
+                    sb.append(" evicted by ");
+                    sb.append(evictedData.getSelected());
+                    sb.append(" in ").append(evictedData.getParent());
+                    if (evictedData.getDetail() != null) {
+                        sb.append(" ").append(evictedData.getDetail());
+                    }
+                }
+            }
+            log(sb.toString());
+
+            printDependencies((List) dependencies.get(dependency.getId()), indent + 1);
+        }
+    }
+
+    private boolean hasDependencies(IvyNode dependency) {
+        List dependencyList = (List) dependencies.get(dependency.getId());
+        return dependencyList.size() > 0;
+    }
+
+    private void populateDependencyTree(IvyNode dependency, ModuleRevisionId currentMrid,
+            ResolveReport report) {
+        registerNodeIfNecessary(dependency.getId());
+        for (int i = 0; i < dependency.getAllCallers().length; i++) {
+            Caller caller = dependency.getAllCallers()[i];
+            addDependency(caller.getModuleRevisionId(), dependency);
+        }
+    }
+
+    private void registerNodeIfNecessary(ModuleRevisionId moduleRevisionId) {
+        if (!dependencies.containsKey(moduleRevisionId)) {
+            dependencies.put(moduleRevisionId, new ArrayList/* <IvyNode> */());
+        }
+    }
+
+    private void addDependency(ModuleRevisionId moduleRevisionId, IvyNode dependency) {
+        registerNodeIfNecessary(moduleRevisionId);
+        List/* <IvyNode> */list = (List) dependencies.get(moduleRevisionId);
+        list.add(dependency);
+    }
+
+    public boolean isShowEvicted() {
+        return showEvicted;
+    }
+
+    public void setShowEvicted(boolean showEvicted) {
+        this.showEvicted = showEvicted;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/ant/IvyDependencyUpdateChecker.java b/src/java/org/apache/ivy/ant/IvyDependencyUpdateChecker.java
new file mode 100644
index 0000000..d416e57
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/IvyDependencyUpdateChecker.java
@@ -0,0 +1,214 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import java.io.IOException;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.ivy.core.module.descriptor.Configuration;
+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.apache.ivy.core.report.ResolveReport;
+import org.apache.ivy.core.resolve.IvyNode;
+import org.apache.ivy.core.resolve.ResolveOptions;
+import org.apache.tools.ant.BuildException;
+
+public class IvyDependencyUpdateChecker extends IvyPostResolveTask {
+
+    private String revisionToCheck = "latest.integration";
+
+    private boolean download = false;
+
+    private boolean checkIfChanged = false;
+
+    private boolean showTransitive = false;
+
+    public void doExecute() throws BuildException {
+        prepareAndCheck();
+
+        ModuleDescriptor originalModuleDescriptor = getResolvedReport().getModuleDescriptor();
+        // clone module descriptor
+        DefaultModuleDescriptor latestModuleDescriptor = new DefaultModuleDescriptor(
+                originalModuleDescriptor.getModuleRevisionId(),
+                originalModuleDescriptor.getStatus(), originalModuleDescriptor.getPublicationDate());
+        // copy configurations
+        for (int i = 0; i < originalModuleDescriptor.getConfigurations().length; i++) {
+            Configuration configuration = originalModuleDescriptor.getConfigurations()[i];
+            latestModuleDescriptor.addConfiguration(configuration);
+        }
+        // clone dependency and add new one with the requested revisionToCheck
+        for (int i = 0; i < originalModuleDescriptor.getDependencies().length; i++) {
+            DependencyDescriptor dependencyDescriptor = originalModuleDescriptor.getDependencies()[i];
+            ModuleRevisionId upToDateMrid = ModuleRevisionId.newInstance(
+                dependencyDescriptor.getDependencyRevisionId(), revisionToCheck);
+            latestModuleDescriptor.addDependency(dependencyDescriptor.clone(upToDateMrid));
+        }
+
+        // resolve
+        ResolveOptions resolveOptions = new ResolveOptions();
+        resolveOptions.setDownload(isDownload());
+        resolveOptions.setLog(getLog());
+        resolveOptions.setConfs(splitConfs(getConf()));
+        resolveOptions.setCheckIfChanged(checkIfChanged);
+
+        ResolveReport latestReport;
+        try {
+            latestReport = getIvyInstance().getResolveEngine().resolve(latestModuleDescriptor,
+                resolveOptions);
+
+            displayDependencyUpdates(getResolvedReport(), latestReport);
+            if (showTransitive) {
+                displayNewDependencyOnLatest(getResolvedReport(), latestReport);
+                displayMissingDependencyOnLatest(getResolvedReport(), latestReport);
+            }
+
+        } catch (ParseException e) {
+            throw new BuildException("impossible to resolve dependencies:\n\t" + e, e);
+        } catch (IOException e) {
+            throw new BuildException("impossible to resolve dependencies:\n\t" + e, e);
+        }
+
+    }
+
+    private void displayDependencyUpdates(ResolveReport originalReport, ResolveReport latestReport) {
+        log("Dependencies updates available :");
+        boolean dependencyUpdateDetected = false;
+        for (Iterator iterator = latestReport.getDependencies().iterator(); iterator.hasNext();) {
+            IvyNode latest = (IvyNode) iterator.next();
+            for (Iterator iterator2 = originalReport.getDependencies().iterator(); iterator2
+                    .hasNext();) {
+                IvyNode originalDependency = (IvyNode) iterator2.next();
+                if (originalDependency.getModuleId().equals(latest.getModuleId())) {
+                    if (!originalDependency.getResolvedId().getRevision()
+                            .equals(latest.getResolvedId().getRevision())) {
+                        // is this dependency a transitive dependency ? or direct dependency
+                        // (unfortunatly
+                        // .isTranstive() methods doesn't have the same meaning)
+                        boolean isTransitiveDependency = latest.getDependencyDescriptor(latest
+                                .getRoot()) == null;
+                        if ((!isTransitiveDependency) || (isTransitiveDependency && showTransitive)) {
+                            StringBuffer sb = new StringBuffer();
+                            sb.append("\t")//
+                                    .append(originalDependency.getResolvedId().getOrganisation()) //
+                                    .append('#')//
+                                    .append(originalDependency.getResolvedId().getName())//
+                                    .append(isTransitiveDependency ? " (transitive)" : "") //
+                                    .append("\t")//
+                                    .append(originalDependency.getResolvedId().getRevision())//
+                                    .append(" -> ")//
+                                    .append(latest.getResolvedId().getRevision());
+                            log(sb.toString());
+                            dependencyUpdateDetected = true;
+                        }
+                    }
+
+                }
+            }
+        }
+        if (!dependencyUpdateDetected) {
+            log("\tAll dependencies are up to date");
+        }
+    }
+
+    private void displayMissingDependencyOnLatest(ResolveReport originalReport,
+            ResolveReport latestReport) {
+        List/* <ModuleRevisionId> */listOfMissingDependencyOnLatest = new ArrayList/*
+                                                                                    * <ModuleRevisionId
+                                                                                    * >
+                                                                                    */();
+        for (Iterator iterator = originalReport.getDependencies().iterator(); iterator.hasNext();) {
+            IvyNode originalDependency = (IvyNode) iterator.next();
+            boolean dependencyFound = false;
+            for (Iterator iterator2 = latestReport.getDependencies().iterator(); iterator2
+                    .hasNext();) {
+                IvyNode latest = (IvyNode) iterator2.next();
+                if (originalDependency.getModuleId().equals(latest.getModuleId())) {
+                    dependencyFound = true;
+                }
+            }
+            if (!dependencyFound) {
+                listOfMissingDependencyOnLatest.add(originalDependency.getId());
+            }
+        }
+
+        if (listOfMissingDependencyOnLatest.size() > 0) {
+            log("List of missing dependency on latest resolve :");
+            for (Iterator iterator = listOfMissingDependencyOnLatest.iterator(); iterator.hasNext();) {
+                ModuleRevisionId moduleRevisionId = (ModuleRevisionId) iterator.next();
+                log("\t" + moduleRevisionId.toString());
+            }
+        }
+    }
+
+    private void displayNewDependencyOnLatest(ResolveReport originalReport,
+            ResolveReport latestReport) {
+        List/* <ModuleRevisionId> */listOfNewDependencyOnLatest = new ArrayList/* <ModuleRevisionId> */();
+        for (Iterator iterator = latestReport.getDependencies().iterator(); iterator.hasNext();) {
+            IvyNode latest = (IvyNode) iterator.next();
+
+            boolean dependencyFound = false;
+            for (Iterator iterator2 = originalReport.getDependencies().iterator(); iterator2
+                    .hasNext();) {
+                IvyNode originalDependency = (IvyNode) iterator2.next();
+                if (originalDependency.getModuleId().equals(latest.getModuleId())) {
+                    dependencyFound = true;
+                }
+            }
+            if (!dependencyFound) {
+                listOfNewDependencyOnLatest.add(latest.getId());
+            }
+        }
+        if (listOfNewDependencyOnLatest.size() > 0) {
+            log("List of new dependency on latest resolve :");
+            for (Iterator iterator = listOfNewDependencyOnLatest.iterator(); iterator.hasNext();) {
+                ModuleRevisionId moduleRevisionId = (ModuleRevisionId) iterator.next();
+                log("\t" + moduleRevisionId.toString());
+            }
+        }
+    }
+
+    public String getRevisionToCheck() {
+        return revisionToCheck;
+    }
+
+    public void setRevisionToCheck(String revisionToCheck) {
+        this.revisionToCheck = revisionToCheck;
+    }
+
+    public boolean isDownload() {
+        return download;
+    }
+
+    public void setDownload(boolean download) {
+        this.download = download;
+    }
+
+    public boolean isShowTransitive() {
+        return showTransitive;
+    }
+
+    public void setShowTransitive(boolean showTransitive) {
+        this.showTransitive = showTransitive;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/ant/IvyExclude.java b/src/java/org/apache/ivy/ant/IvyExclude.java
new file mode 100644
index 0000000..ad13c07
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/IvyExclude.java
@@ -0,0 +1,75 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+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.core.settings.IvySettings;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+
+public class IvyExclude {
+
+    private String org;
+
+    private String module;
+
+    private String artifact;
+
+    private String type;
+
+    private String ext;
+
+    private String matcher;
+
+    public void setOrg(String org) {
+        this.org = org;
+    }
+
+    public void setModule(String module) {
+        this.module = module;
+    }
+
+    public void setArtifact(String artifact) {
+        this.artifact = artifact;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public void setExt(String ext) {
+        this.ext = ext;
+    }
+
+    public void setMatcher(String matcher) {
+        this.matcher = matcher;
+    }
+
+    DefaultExcludeRule asRule(IvySettings settings) {
+        String matcherName = matcher == null ? PatternMatcher.EXACT : matcher;
+        String orgPattern = org == null ? PatternMatcher.ANY_EXPRESSION : org;
+        String modulePattern = module == null ? PatternMatcher.ANY_EXPRESSION : module;
+        String artifactPattern = artifact == null ? PatternMatcher.ANY_EXPRESSION : artifact;
+        String typePattern = type == null ? PatternMatcher.ANY_EXPRESSION : type;
+        String extPattern = ext == null ? typePattern : ext;
+        ArtifactId aid = new ArtifactId(new ModuleId(orgPattern, modulePattern), artifactPattern,
+                typePattern, extPattern);
+        return new DefaultExcludeRule(aid, settings.getMatcher(matcherName), null);
+    }
+}
diff --git a/src/java/org/apache/ivy/ant/IvyExtractFromSources.java b/src/java/org/apache/ivy/ant/IvyExtractFromSources.java
new file mode 100644
index 0000000..a7e6cd5
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/IvyExtractFromSources.java
@@ -0,0 +1,217 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.Task;
+import org.apache.tools.ant.filters.LineContainsRegExp;
+import org.apache.tools.ant.filters.TokenFilter;
+import org.apache.tools.ant.taskdefs.Concat;
+import org.apache.tools.ant.types.FileSet;
+import org.apache.tools.ant.types.FilterChain;
+import org.apache.tools.ant.types.RegularExpression;
+
+/**
+ * Extracts imports from a set of java sources and generate corresponding ivy file
+ */
+public class IvyExtractFromSources extends Task {
+    public static class Ignore {
+        private String packageName;
+
+        public String getPackage() {
+            return packageName;
+        }
+
+        public void setPackage(String package1) {
+            packageName = package1;
+        }
+    }
+
+    private String organisation;
+
+    private String module;
+
+    private String revision;
+
+    private String status;
+
+    private List ignoredPackaged = new ArrayList(); // List (String package)
+
+    private Map mapping = new HashMap(); // Map (String package -> ModuleRevisionId)
+
+    private Concat concat = new Concat();
+
+    private File to;
+
+    public void addConfiguredIgnore(Ignore ignore) {
+        ignoredPackaged.add(ignore.getPackage());
+    }
+
+    public File getTo() {
+        return to;
+    }
+
+    public void setTo(File to) {
+        this.to = to;
+    }
+
+    public String getModule() {
+        return module;
+    }
+
+    public void setModule(String module) {
+        this.module = module;
+    }
+
+    public String getOrganisation() {
+        return organisation;
+    }
+
+    public void setOrganisation(String organisation) {
+        this.organisation = organisation;
+    }
+
+    public String getRevision() {
+        return revision;
+    }
+
+    public void setRevision(String revision) {
+        this.revision = revision;
+    }
+
+    public String getStatus() {
+        return status;
+    }
+
+    public void setStatus(String status) {
+        this.status = status;
+    }
+
+    public void addConfiguredMapping(PackageMapping mapping) {
+        this.mapping.put(mapping.getPackage(), mapping.getModuleRevisionId());
+    }
+
+    public void addFileSet(FileSet fileSet) {
+        concat.addFileset(fileSet);
+    }
+
+    public void execute() throws BuildException {
+        configureConcat();
+        Writer out = new StringWriter();
+        concat.setWriter(out);
+        concat.execute();
+        Set importsSet = new HashSet(Arrays.asList(out.toString().split("\n")));
+        Set dependencies = new HashSet();
+        for (Iterator iter = importsSet.iterator(); iter.hasNext();) {
+            String pack = ((String) iter.next()).trim();
+            ModuleRevisionId mrid = getMapping(pack);
+            if (mrid != null) {
+                dependencies.add(mrid);
+            }
+        }
+        try {
+            PrintWriter writer = new PrintWriter(new FileOutputStream(to));
+            writer.println("<ivy-module version=\"1.0\">");
+            writer.println("\t<info organisation=\"" + organisation + "\"");
+            writer.println("\t       module=\"" + module + "\"");
+            if (revision != null) {
+                writer.println("\t       revision=\"" + revision + "\"");
+            }
+            if (status != null) {
+                writer.println("\t       status=\"" + status + "\"");
+            } else {
+                writer.println("\t       status=\"integration\"");
+            }
+            writer.println("\t/>");
+            if (!dependencies.isEmpty()) {
+                writer.println("\t<dependencies>");
+                for (Iterator iter = dependencies.iterator(); iter.hasNext();) {
+                    ModuleRevisionId mrid = (ModuleRevisionId) iter.next();
+                    writer.println("\t\t<dependency org=\"" + mrid.getOrganisation() + "\" name=\""
+                            + mrid.getName() + "\" rev=\"" + mrid.getRevision() + "\"/>");
+                }
+                writer.println("\t</dependencies>");
+            }
+            writer.println("</ivy-module>");
+            writer.close();
+            log(dependencies.size() + " dependencies put in " + to);
+        } catch (FileNotFoundException e) {
+            throw new BuildException("impossible to create file " + to + ": " + e, e);
+        }
+    }
+
+    /**
+     * @param pack
+     * @return
+     */
+    private ModuleRevisionId getMapping(String pack) {
+        String askedPack = pack;
+        ModuleRevisionId ret = null;
+        while (ret == null && pack.length() > 0) {
+            if (ignoredPackaged.contains(pack)) {
+                return null;
+            }
+            ret = (ModuleRevisionId) mapping.get(pack);
+            int lastDotIndex = pack.lastIndexOf('.');
+            if (lastDotIndex != -1) {
+                pack = pack.substring(0, lastDotIndex);
+            } else {
+                break;
+            }
+        }
+        if (ret == null) {
+            log("no mapping found for " + askedPack, Project.MSG_VERBOSE);
+        }
+        return ret;
+    }
+
+    private void configureConcat() {
+        concat.setProject(getProject());
+        concat.setTaskName(getTaskName());
+        FilterChain filterChain = new FilterChain();
+        LineContainsRegExp lcre = new LineContainsRegExp();
+        RegularExpression regexp = new RegularExpression();
+        regexp.setPattern("^import .+;");
+        lcre.addConfiguredRegexp(regexp);
+        filterChain.add(lcre);
+        TokenFilter tf = new TokenFilter();
+        TokenFilter.ReplaceRegex rre = new TokenFilter.ReplaceRegex();
+        rre.setPattern("import (.+);.*");
+        rre.setReplace("\\1");
+        tf.add(rre);
+        filterChain.add(tf);
+        concat.addFilterChain(filterChain);
+    }
+}
diff --git a/src/java/org/apache/ivy/ant/IvyFindRevision.java b/src/java/org/apache/ivy/ant/IvyFindRevision.java
new file mode 100644
index 0000000..32171cf
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/IvyFindRevision.java
@@ -0,0 +1,104 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import org.apache.ivy.Ivy;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.resolve.ResolvedModuleRevision;
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.tools.ant.BuildException;
+
+/**
+ * Look for the latest module in the repository matching the given criteria, and sets a set of
+ * properties according to what was found.
+ */
+public class IvyFindRevision extends IvyTask {
+    private String organisation;
+
+    private String module;
+
+    private String branch;
+
+    private String revision;
+
+    private String property = "ivy.revision";
+
+    public String getModule() {
+        return module;
+    }
+
+    public void setModule(String module) {
+        this.module = module;
+    }
+
+    public String getOrganisation() {
+        return organisation;
+    }
+
+    public void setOrganisation(String organisation) {
+        this.organisation = organisation;
+    }
+
+    public String getRevision() {
+        return revision;
+    }
+
+    public void setRevision(String revision) {
+        this.revision = revision;
+    }
+
+    public String getBranch() {
+        return branch;
+    }
+
+    public void setBranch(String branch) {
+        this.branch = branch;
+    }
+
+    public String getProperty() {
+        return property;
+    }
+
+    public void setProperty(String prefix) {
+        this.property = prefix;
+    }
+
+    public void doExecute() throws BuildException {
+        if (organisation == null) {
+            throw new BuildException("no organisation provided for ivy findrevision task");
+        }
+        if (module == null) {
+            throw new BuildException("no module name provided for ivy findrevision task");
+        }
+        if (revision == null) {
+            throw new BuildException("no revision provided for ivy findrevision task");
+        }
+
+        Ivy ivy = getIvyInstance();
+        IvySettings settings = ivy.getSettings();
+        if (branch == null) {
+            branch = settings.getDefaultBranch(new ModuleId(organisation, module));
+        }
+        ResolvedModuleRevision rmr = ivy.findModule(ModuleRevisionId.newInstance(organisation,
+            module, branch, revision));
+        if (rmr != null) {
+            getProject().setProperty(property, rmr.getId().getRevision());
+        }
+    }
+}
diff --git a/src/java/org/apache/ivy/ant/IvyInfo.java b/src/java/org/apache/ivy/ant/IvyInfo.java
new file mode 100644
index 0000000..0bbf939
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/IvyInfo.java
@@ -0,0 +1,212 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.ivy.Ivy;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.Configuration;
+import org.apache.ivy.core.module.descriptor.Configuration.Visibility;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.resolve.ResolvedModuleRevision;
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.ivy.plugins.parser.ModuleDescriptorParserRegistry;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+
+/**
+ * Parses information about an ivy file and make them available in ant.
+ */
+public class IvyInfo extends IvyTask {
+    private File file = null;
+
+    private String organisation;
+
+    private String module;
+
+    private String branch;
+
+    private String revision;
+
+    private String property = "ivy";
+
+    public File getFile() {
+        return file;
+    }
+
+    public void setFile(File file) {
+        this.file = file;
+    }
+
+    public String getModule() {
+        return module;
+    }
+
+    public void setModule(String module) {
+        this.module = module;
+    }
+
+    public String getOrganisation() {
+        return organisation;
+    }
+
+    public void setOrganisation(String organisation) {
+        this.organisation = organisation;
+    }
+
+    public String getRevision() {
+        return revision;
+    }
+
+    public void setRevision(String revision) {
+        this.revision = revision;
+    }
+
+    public String getBranch() {
+        return branch;
+    }
+
+    public void setBranch(String branch) {
+        this.branch = branch;
+    }
+
+    public String getProperty() {
+        return property;
+    }
+
+    public void setProperty(String prefix) {
+        this.property = prefix;
+    }
+
+    public void doExecute() throws BuildException {
+        Ivy ivy = getIvyInstance();
+        IvySettings settings = ivy.getSettings();
+
+        try {
+            if (organisation != null || module != null || revision != null || branch != null) {
+                if (organisation == null) {
+                    throw new BuildException("no organisation provided for ivy info task");
+                }
+                if (module == null) {
+                    throw new BuildException("no module name provided for ivy info task");
+                }
+                if (revision == null) {
+                    throw new BuildException("no revision provided for ivy info task");
+                }
+
+                if (branch == null) {
+                    settings.getDefaultBranch(new ModuleId(organisation, module));
+                }
+                ResolvedModuleRevision rmr = ivy.findModule(ModuleRevisionId.newInstance(
+                    organisation, module, branch, revision));
+                if (rmr != null) {
+                    ModuleDescriptor md = rmr.getDescriptor();
+                    ModuleRevisionId mrid = rmr.getId();
+                    setProperties(md, mrid);
+                }
+            } else {
+                if (file == null) {
+                    file = getProject().resolveFile(getProperty(settings, "ivy.dep.file"));
+                }
+                ModuleDescriptor md = ModuleDescriptorParserRegistry.getInstance().parseDescriptor(
+                    settings, file.toURI().toURL(), doValidate(settings));
+                ModuleRevisionId mrid = md.getModuleRevisionId();
+                setProperties(md, mrid);
+            }
+        } catch (MalformedURLException e) {
+            throw new BuildException("unable to convert given ivy file to url: " + file + ": " + e,
+                    e);
+        } catch (ParseException e) {
+            log(e.getMessage(), Project.MSG_ERR);
+            throw new BuildException("syntax errors in ivy file: " + e, e);
+        } catch (Exception e) {
+            throw new BuildException("impossible to resolve dependencies: " + e, e);
+        }
+    }
+
+    private void setProperties(ModuleDescriptor md, ModuleRevisionId mrid) {
+        getProject().setProperty(property + ".organisation", mrid.getOrganisation());
+        getProject().setProperty(property + ".module", mrid.getName());
+        if (mrid.getBranch() != null) {
+            getProject().setProperty(property + ".branch", mrid.getBranch());
+        }
+        getProject().setProperty(property + ".revision", mrid.getRevision());
+        getProject().setProperty(property + ".status", md.getStatus());
+        if (md.getPublicationDate() != null) {
+            getProject().setProperty(property + ".publication",
+                Long.toString(md.getPublicationDate().getTime()));
+        }
+
+        Map extra = mrid.getExtraAttributes();
+        for (Iterator iter = extra.entrySet().iterator(); iter.hasNext();) {
+            Entry entry = (Entry) iter.next();
+            getProject().setProperty(property + ".extra." + entry.getKey(),
+                (String) entry.getValue());
+        }
+
+        getProject().setProperty(property + ".configurations",
+            mergeConfs(md.getConfigurationsNames()));
+
+        // store the public configurations in a separate property
+        Configuration[] configs = md.getConfigurations();
+        List publicConfigsList = new ArrayList();
+        for (int i = 0; i < configs.length; i++) {
+            String name = configs[i].getName();
+            if (Visibility.PUBLIC.equals(configs[i].getVisibility())) {
+                publicConfigsList.add(name);
+            }
+
+            if (configs[i].getDescription() != null) {
+                getProject().setProperty(property + ".configuration." + name + ".desc",
+                    configs[i].getDescription());
+            }
+        }
+        String[] publicConfigs = (String[]) publicConfigsList.toArray(new String[publicConfigsList
+                .size()]);
+        getProject().setProperty(property + ".public.configurations", mergeConfs(publicConfigs));
+
+        Artifact[] artifacts = md.getAllArtifacts();
+        for (int i = 0; i < artifacts.length; i++) {
+            int id = i + 1;
+            getProject()
+                    .setProperty(property + ".artifact." + id + ".name", artifacts[i].getName());
+            getProject()
+                    .setProperty(property + ".artifact." + id + ".type", artifacts[i].getType());
+            getProject().setProperty(property + ".artifact." + id + ".ext", artifacts[i].getExt());
+            getProject().setProperty(property + ".artifact." + id + ".conf",
+                mergeConfs(artifacts[i].getConfigurations()));
+
+            Map artiExtra = artifacts[i].getExtraAttributes();
+            for (Iterator iter = artiExtra.entrySet().iterator(); iter.hasNext();) {
+                Entry entry = (Entry) iter.next();
+                getProject().setProperty(property + ".artifact." + id + ".extra." + entry.getKey(),
+                    (String) entry.getValue());
+            }
+        }
+    }
+}
diff --git a/src/java/org/apache/ivy/ant/IvyInstall.java b/src/java/org/apache/ivy/ant/IvyInstall.java
new file mode 100644
index 0000000..1982e19
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/IvyInstall.java
@@ -0,0 +1,228 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import java.io.File;
+
+import org.apache.ivy.Ivy;
+import org.apache.ivy.core.install.InstallOptions;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.report.ResolveReport;
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+import org.apache.ivy.util.filter.FilterHelper;
+import org.apache.tools.ant.BuildException;
+
+/**
+ * Allow to install a module or a set of module from repository to another one.
+ */
+public class IvyInstall extends IvyTask {
+    private String organisation;
+
+    private String module;
+
+    private String revision;
+
+    private String branch;
+
+    private String conf = "*";
+
+    private boolean overwrite = false;
+
+    private String from;
+
+    private String to;
+
+    private boolean transitive;
+
+    private String type;
+
+    private String matcher = PatternMatcher.EXACT;
+
+    private boolean haltOnFailure = true;
+
+    private boolean installOriginalMetadata = false;
+
+    public void doExecute() throws BuildException {
+        Ivy ivy = getIvyInstance();
+        IvySettings settings = ivy.getSettings();
+        if (organisation == null) {
+            throw new BuildException("no organisation provided for ivy publish task: "
+                    + "It can either be set explicitely via the attribute 'organisation' "
+                    + "or via 'ivy.organisation' property or a prior call to <resolve/>");
+        }
+        if (module == null && PatternMatcher.EXACT.equals(matcher)) {
+            throw new BuildException("no module name provided for ivy publish task: "
+                    + "It can either be set explicitely via the attribute 'module' "
+                    + "or via 'ivy.module' property or a prior call to <resolve/>");
+        } else if (module == null && !PatternMatcher.EXACT.equals(matcher)) {
+            module = PatternMatcher.ANY_EXPRESSION;
+        }
+        if (revision == null && PatternMatcher.EXACT.equals(matcher)) {
+            throw new BuildException("no module revision provided for ivy publish task: "
+                    + "It can either be set explicitely via the attribute 'revision' "
+                    + "or via 'ivy.revision' property or a prior call to <resolve/>");
+        } else if (revision == null && !PatternMatcher.EXACT.equals(matcher)) {
+            revision = PatternMatcher.ANY_EXPRESSION;
+        }
+        if (branch == null && PatternMatcher.EXACT.equals(matcher)) {
+            branch = settings.getDefaultBranch(ModuleId.newInstance(organisation, module));
+        } else if (branch == null && !PatternMatcher.EXACT.equals(matcher)) {
+            branch = PatternMatcher.ANY_EXPRESSION;
+        }
+        if (from == null) {
+            throw new BuildException(
+                    "no from resolver name: please provide it through parameter 'from'");
+        }
+        if (to == null) {
+            throw new BuildException(
+                    "no to resolver name: please provide it through parameter 'to'");
+        }
+        ModuleRevisionId mrid = ModuleRevisionId
+                .newInstance(organisation, module, branch, revision);
+
+        ResolveReport report;
+        try {
+            report = ivy.install(
+                mrid,
+                from,
+                to,
+                new InstallOptions().setTransitive(transitive).setValidate(doValidate(settings))
+                        .setOverwrite(overwrite).setConfs(conf.split(","))
+                        .setArtifactFilter(FilterHelper.getArtifactTypeFilter(type))
+                        .setMatcherName(matcher)
+                        .setInstallOriginalMetadata(installOriginalMetadata));
+        } catch (Exception e) {
+            throw new BuildException("impossible to install " + mrid + ": " + e, e);
+        }
+
+        if (report.hasError() && isHaltonfailure()) {
+            throw new BuildException(
+                    "Problem happened while installing modules - see output for details");
+        }
+    }
+
+    public boolean isHaltonfailure() {
+        return haltOnFailure;
+    }
+
+    public void setHaltonfailure(boolean haltOnFailure) {
+        this.haltOnFailure = haltOnFailure;
+    }
+
+    public void setCache(File cache) {
+        cacheAttributeNotSupported();
+    }
+
+    public String getModule() {
+        return module;
+    }
+
+    public void setModule(String module) {
+        this.module = module;
+    }
+
+    public String getBranch() {
+        return branch;
+    }
+
+    public void setBranch(String branch) {
+        this.branch = branch;
+    }
+
+    public String getOrganisation() {
+        return organisation;
+    }
+
+    public void setOrganisation(String organisation) {
+        this.organisation = organisation;
+    }
+
+    public String getRevision() {
+        return revision;
+    }
+
+    public void setRevision(String revision) {
+        this.revision = revision;
+    }
+
+    public boolean isOverwrite() {
+        return overwrite;
+    }
+
+    public void setOverwrite(boolean overwrite) {
+        this.overwrite = overwrite;
+    }
+
+    public String getFrom() {
+        return from;
+    }
+
+    public void setFrom(String from) {
+        this.from = from;
+    }
+
+    public String getTo() {
+        return to;
+    }
+
+    public void setTo(String to) {
+        this.to = to;
+    }
+
+    public boolean isTransitive() {
+        return transitive;
+    }
+
+    public void setTransitive(boolean transitive) {
+        this.transitive = transitive;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public String getMatcher() {
+        return matcher;
+    }
+
+    public void setMatcher(String matcher) {
+        this.matcher = matcher;
+    }
+
+    public String getConf() {
+        return conf;
+    }
+
+    public void setConf(String conf) {
+        this.conf = conf;
+    }
+
+    public boolean isInstallOriginalMetadata() {
+        return installOriginalMetadata;
+    }
+
+    public void setInstallOriginalMetadata(boolean installOriginalMetadata) {
+        this.installOriginalMetadata = installOriginalMetadata;
+    }
+}
diff --git a/src/java/org/apache/ivy/ant/IvyListModules.java b/src/java/org/apache/ivy/ant/IvyListModules.java
new file mode 100644
index 0000000..a516571
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/IvyListModules.java
@@ -0,0 +1,158 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import org.apache.ivy.Ivy;
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.search.SearchEngine;
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.apache.tools.ant.BuildException;
+
+/**
+ * Look for modules in the repository matching the given criteria, and sets a set of properties
+ * according to what was found.
+ */
+public class IvyListModules extends IvyTask {
+    private String organisation;
+
+    private String module;
+
+    private String branch = PatternMatcher.ANY_EXPRESSION;
+
+    private String revision;
+
+    private String matcher = PatternMatcher.EXACT_OR_REGEXP;
+
+    private String property;
+
+    private String value;
+
+    private String resolver;
+
+    public String getMatcher() {
+        return matcher;
+    }
+
+    public void setMatcher(String matcher) {
+        this.matcher = matcher;
+    }
+
+    public String getModule() {
+        return module;
+    }
+
+    public void setModule(String module) {
+        this.module = module;
+    }
+
+    public String getProperty() {
+        return property;
+    }
+
+    public void setProperty(String name) {
+        this.property = name;
+    }
+
+    public String getOrganisation() {
+        return organisation;
+    }
+
+    public void setOrganisation(String organisation) {
+        this.organisation = organisation;
+    }
+
+    public String getRevision() {
+        return revision;
+    }
+
+    public void setRevision(String revision) {
+        this.revision = revision;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+    public String getBranch() {
+        return branch;
+    }
+
+    public void setBranch(String branch) {
+        this.branch = branch;
+    }
+
+    public void setResolver(String resolver) {
+        this.resolver = resolver;
+    }
+
+    public String getResolver() {
+        return resolver;
+    }
+
+    public void doExecute() throws BuildException {
+        if (organisation == null) {
+            throw new BuildException("no organisation provided for ivy listmodules task");
+        }
+        if (module == null) {
+            throw new BuildException("no module name provided for ivy listmodules task");
+        }
+        if (revision == null) {
+            throw new BuildException("no revision provided for ivy listmodules task");
+        }
+        if (property == null) {
+            throw new BuildException("no property provided for ivy listmodules task");
+        }
+        if (value == null) {
+            throw new BuildException("no value provided for ivy listmodules task");
+        }
+
+        Ivy ivy = getIvyInstance();
+        IvySettings settings = ivy.getSettings();
+
+        SearchEngine searcher = new SearchEngine(settings);
+        PatternMatcher patternMatcher = settings.getMatcher(matcher);
+
+        ModuleRevisionId[] mrids;
+        if (resolver == null) {
+            mrids = searcher.listModules(
+                ModuleRevisionId.newInstance(organisation, module, branch, revision),
+                patternMatcher);
+        } else {
+            DependencyResolver depResolver = settings.getResolver(resolver);
+            if (depResolver == null) {
+                throw new BuildException("Unknown resolver: " + resolver);
+            }
+            mrids = searcher.listModules(depResolver,
+                ModuleRevisionId.newInstance(organisation, module, branch, revision),
+                patternMatcher);
+        }
+
+        for (int i = 0; i < mrids.length; i++) {
+            String name = IvyPatternHelper.substitute(settings.substitute(property), mrids[i]);
+            String value = IvyPatternHelper.substitute(settings.substitute(this.value), mrids[i]);
+            getProject().setProperty(name, value);
+        }
+    }
+}
diff --git a/src/java/org/apache/ivy/ant/IvyMakePom.java b/src/java/org/apache/ivy/ant/IvyMakePom.java
new file mode 100644
index 0000000..406b155
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/IvyMakePom.java
@@ -0,0 +1,308 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.plugins.parser.m2.PomModuleDescriptorWriter;
+import org.apache.ivy.plugins.parser.m2.PomWriterOptions;
+import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorParser;
+import org.apache.ivy.util.FileUtil;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+
+/**
+ * Convert an ivy file to a pom
+ */
+public class IvyMakePom extends IvyTask {
+    public class Mapping {
+        private String conf;
+
+        private String scope;
+
+        public String getConf() {
+            return conf;
+        }
+
+        public void setConf(String conf) {
+            this.conf = conf;
+        }
+
+        public String getScope() {
+            return scope;
+        }
+
+        public void setScope(String scope) {
+            this.scope = scope;
+        }
+    }
+
+    public class Dependency {
+        private String group = null;
+
+        private String artifact = null;
+
+        private String version = null;
+
+        private String scope = null;
+
+        private String type = null;
+
+        private String classifier = null;
+
+        private boolean optional = false;
+
+        public String getGroup() {
+            return group;
+        }
+
+        public void setGroup(String group) {
+            this.group = group;
+        }
+
+        public String getArtifact() {
+            return artifact;
+        }
+
+        public void setArtifact(String artifact) {
+            this.artifact = artifact;
+        }
+
+        public String getVersion() {
+            return version;
+        }
+
+        public void setVersion(String version) {
+            this.version = version;
+        }
+
+        public String getScope() {
+            return scope;
+        }
+
+        public void setScope(String scope) {
+            this.scope = scope;
+        }
+
+        public String getType() {
+            return type;
+        }
+
+        public void setType(String type) {
+            this.type = type;
+        }
+
+        public String getClassifier() {
+            return classifier;
+        }
+
+        public void setClassifier(String classifier) {
+            this.classifier = classifier;
+        }
+
+        public boolean getOptional() {
+            return optional;
+        }
+
+        public void setOptional(boolean optional) {
+            this.optional = optional;
+        }
+    }
+
+    private String artifactName;
+
+    private String artifactPackaging;
+
+    private File pomFile = null;
+
+    private File headerFile = null;
+
+    private File templateFile = null;
+
+    private boolean printIvyInfo = true;
+
+    private String conf;
+
+    private File ivyFile = null;
+
+    private String description;
+
+    private Collection mappings = new ArrayList();
+
+    private Collection dependencies = new ArrayList();
+
+    public File getPomFile() {
+        return pomFile;
+    }
+
+    public void setPomFile(File file) {
+        pomFile = file;
+    }
+
+    public File getIvyFile() {
+        return ivyFile;
+    }
+
+    public void setIvyFile(File ivyFile) {
+        this.ivyFile = ivyFile;
+    }
+
+    public File getHeaderFile() {
+        return headerFile;
+    }
+
+    public void setHeaderFile(File headerFile) {
+        this.headerFile = headerFile;
+    }
+
+    public File getTemplateFile() {
+        return templateFile;
+    }
+
+    public void setTemplateFile(File templateFile) {
+        this.templateFile = templateFile;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public boolean isPrintIvyInfo() {
+        return printIvyInfo;
+    }
+
+    public void setPrintIvyInfo(boolean printIvyInfo) {
+        this.printIvyInfo = printIvyInfo;
+    }
+
+    public String getConf() {
+        return conf;
+    }
+
+    public void setConf(String conf) {
+        this.conf = conf;
+    }
+
+    public String getArtifactName() {
+        return artifactName;
+    }
+
+    public void setArtifactName(String artifactName) {
+        this.artifactName = artifactName;
+    }
+
+    public String getArtifactPackaging() {
+        return artifactPackaging;
+    }
+
+    public void setArtifactPackaging(String artifactPackaging) {
+        this.artifactPackaging = artifactPackaging;
+    }
+
+    public Mapping createMapping() {
+        Mapping mapping = new Mapping();
+        this.mappings.add(mapping);
+        return mapping;
+    }
+
+    public Dependency createDependency() {
+        Dependency dependency = new Dependency();
+        this.dependencies.add(dependency);
+        return dependency;
+    }
+
+    public void doExecute() throws BuildException {
+        try {
+            if (ivyFile == null) {
+                throw new BuildException("source ivy file is required for makepom task");
+            }
+            if (pomFile == null) {
+                throw new BuildException("destination pom file is required for makepom task");
+            }
+            ModuleDescriptor md = XmlModuleDescriptorParser.getInstance().parseDescriptor(
+                getSettings(), ivyFile.toURI().toURL(), false);
+            PomModuleDescriptorWriter.write(md, pomFile, getPomWriterOptions());
+        } catch (MalformedURLException e) {
+            throw new BuildException("unable to convert given ivy file to url: " + ivyFile + ": "
+                    + e, e);
+        } catch (ParseException e) {
+            log(e.getMessage(), Project.MSG_ERR);
+            throw new BuildException("syntax errors in ivy file " + ivyFile + ": " + e, e);
+        } catch (Exception e) {
+            throw new BuildException("impossible convert given ivy file to pom file: " + e
+                    + " from=" + ivyFile + " to=" + pomFile, e);
+        }
+    }
+
+    private PomWriterOptions getPomWriterOptions() throws IOException {
+        PomWriterOptions options = new PomWriterOptions();
+        options.setConfs(splitConfs(conf)).setArtifactName(getArtifactName())
+                .setArtifactPackaging(getArtifactPackaging()).setPrintIvyInfo(isPrintIvyInfo())
+                .setDescription(getDescription()).setExtraDependencies(getDependencies())
+                .setTemplate(getTemplateFile());
+
+        if (!mappings.isEmpty()) {
+            options.setMapping(new PomWriterOptions.ConfigurationScopeMapping(getMappingsMap()));
+        }
+
+        if (headerFile != null) {
+            options.setLicenseHeader(FileUtil.readEntirely(getHeaderFile()));
+        }
+
+        return options;
+    }
+
+    private Map getMappingsMap() {
+        Map mappingsMap = new HashMap();
+        for (Iterator iter = mappings.iterator(); iter.hasNext();) {
+            Mapping mapping = (Mapping) iter.next();
+            String[] mappingConfs = splitConfs(mapping.getConf());
+            for (int i = 0; i < mappingConfs.length; i++) {
+                if (!mappingsMap.containsKey(mappingConfs[i])) {
+                    mappingsMap.put(mappingConfs[i], mapping.getScope());
+                }
+            }
+        }
+        return mappingsMap;
+    }
+
+    private List getDependencies() {
+        List result = new ArrayList();
+        for (Iterator iter = dependencies.iterator(); iter.hasNext();) {
+            Dependency dependency = (Dependency) iter.next();
+            result.add(new PomWriterOptions.ExtraDependency(dependency.getGroup(), dependency
+                    .getArtifact(), dependency.getVersion(), dependency.getScope(), dependency
+                    .getType(), dependency.getClassifier(), dependency.getOptional()));
+        }
+        return result;
+    }
+}
diff --git a/src/java/org/apache/ivy/ant/IvyOverride.java b/src/java/org/apache/ivy/ant/IvyOverride.java
new file mode 100644
index 0000000..ac7e6ad
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/IvyOverride.java
@@ -0,0 +1,66 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.apache.ivy.core.module.descriptor.OverrideDependencyDescriptorMediator;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+
+public class IvyOverride {
+
+    private String org;
+
+    private String module;
+
+    private String branch;
+
+    private String rev;
+
+    private String matcher;
+
+    public void setOrg(String org) {
+        this.org = org;
+    }
+
+    public void setModule(String module) {
+        this.module = module;
+    }
+
+    public void setBranch(String branch) {
+        this.branch = branch;
+    }
+
+    public void setRev(String rev) {
+        this.rev = rev;
+    }
+
+    public void setMatcher(String matcher) {
+        this.matcher = matcher;
+    }
+
+    void addOverride(DefaultModuleDescriptor md, IvySettings settings) {
+        String matcherName = matcher == null ? PatternMatcher.EXACT : matcher;
+        String orgPattern = org == null ? PatternMatcher.ANY_EXPRESSION : org;
+        String modulePattern = module == null ? PatternMatcher.ANY_EXPRESSION : module;
+        md.addDependencyDescriptorMediator(new ModuleId(orgPattern, modulePattern),
+            settings.getMatcher(matcherName), new OverrideDependencyDescriptorMediator(branch, rev));
+    }
+
+}
diff --git a/src/java/org/apache/ivy/ant/IvyPostResolveTask.java b/src/java/org/apache/ivy/ant/IvyPostResolveTask.java
new file mode 100644
index 0000000..0e0e6fe
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/IvyPostResolveTask.java
@@ -0,0 +1,460 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Iterator;
+
+import org.apache.ivy.Ivy;
+import org.apache.ivy.core.cache.ResolutionCacheManager;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.report.ResolveReport;
+import org.apache.ivy.core.resolve.ResolveOptions;
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.ivy.util.Message;
+import org.apache.ivy.util.StringUtils;
+import org.apache.ivy.util.filter.Filter;
+import org.apache.ivy.util.filter.FilterHelper;
+import org.apache.tools.ant.BuildException;
+
+/**
+ * Base class for tasks needing to be performed after a resolve.
+ */
+public abstract class IvyPostResolveTask extends IvyTask {
+    private String conf;
+
+    private boolean haltOnFailure = true;
+
+    private boolean transitive = true;
+
+    private boolean inline = false;
+
+    private String organisation;
+
+    private String branch = null;
+
+    private String module;
+
+    private String revision = "latest.integration";
+
+    private String resolveId;
+
+    private String type;
+
+    private File file;
+
+    private Filter artifactFilter = null;
+
+    private boolean useOrigin = false;
+
+    private Boolean keep = null;
+
+    private boolean refresh = false;
+
+    private String resolveMode = null;
+
+    private String log = ResolveOptions.LOG_DEFAULT;
+
+    private boolean changing = false;
+
+    private IvyResolve resolve = new IvyResolve();
+
+    public boolean isUseOrigin() {
+        return useOrigin;
+    }
+
+    public void setUseOrigin(boolean useOrigin) {
+        this.useOrigin = useOrigin;
+    }
+
+    public String getLog() {
+        return log;
+    }
+
+    public void setLog(String log) {
+        this.log = log;
+    }
+
+    public IvyDependency createDependency() {
+        return resolve.createDependency();
+    }
+
+    public IvyExclude createExclude() {
+        return resolve.createExclude();
+    }
+
+    public IvyConflict createConflict() {
+        return resolve.createConflict();
+    }
+
+    protected void prepareAndCheck() {
+        Ivy ivy = getIvyInstance();
+        IvySettings settings = ivy.getSettings();
+
+        boolean orgAndModSetManually = (organisation != null) && (module != null);
+
+        organisation = getProperty(organisation, settings, "ivy.organisation");
+        module = getProperty(module, settings, "ivy.module");
+
+        if (file == null) {
+            String fileName = getProperty(settings, "ivy.resolved.file", resolveId);
+            if (fileName != null) {
+                file = getProject().resolveFile(fileName);
+            }
+        }
+
+        if (isInline()) {
+            conf = conf == null ? "*" : conf;
+            if (organisation == null) {
+                throw new BuildException(
+                        "no organisation provided for ivy cache task in inline mode: "
+                                + "It can either be set explicitely via the attribute 'organisation' "
+                                + "or via 'ivy.organisation' property");
+            }
+            if (module == null) {
+                throw new BuildException(
+                        "no module name provided for ivy cache task in inline mode: "
+                                + "It can either be set explicitely via the attribute 'module' "
+                                + "or via 'ivy.module' property");
+            }
+            String[] toResolve = getConfsToResolve(getOrganisation(), getModule() + "-caller",
+                conf, true);
+            // When we make an inline resolution, we can not resolve private confs.
+            for (int i = 0; i < toResolve.length; i++) {
+                if ("*".equals(toResolve[i])) {
+                    toResolve[i] = "*(public)";
+                }
+            }
+            if (toResolve.length > 0) {
+                Message.verbose("using inline mode to resolve " + getOrganisation() + " "
+                        + getModule() + " " + getRevision() + " ("
+                        + StringUtils.join(toResolve, ", ") + ")");
+                IvyResolve resolve = setupResolve(isHaltonfailure(), isUseOrigin());
+                resolve.setOrganisation(getOrganisation());
+                resolve.setModule(getModule());
+                resolve.setBranch(getBranch());
+                resolve.setRevision(getRevision());
+                resolve.setInline(true);
+                resolve.setChanging(isChanging());
+                resolve.setConf(conf);
+                resolve.setResolveId(resolveId);
+                resolve.setTransitive(isTransitive());
+                resolve.execute();
+            } else {
+                Message.verbose("inline resolve already done for " + getOrganisation() + " "
+                        + getModule() + " " + getRevision() + " (" + conf + ")");
+            }
+            if ("*".equals(conf)) {
+                conf = StringUtils.join(
+                    getResolvedConfigurations(getOrganisation(), getModule() + "-caller", true),
+                    ", ");
+            }
+        } else {
+            Message.debug("using standard ensure resolved");
+
+            // if the organization and module has been manually specified, we'll reuse the resolved
+            // data from another build (there is no way to know which configurations were resolved
+            // there (TODO: maybe we can check which reports exist and extract the configurations
+            // from these report names?)
+            if (!orgAndModSetManually) {
+                ensureResolved(settings);
+            }
+
+            conf = getProperty(conf, settings, "ivy.resolved.configurations");
+            if ("*".equals(conf)) {
+                conf = getProperty(settings, "ivy.resolved.configurations");
+                if (conf == null) {
+                    throw new BuildException("bad conf provided for ivy cache task: "
+                            + "'*' can only be used with a prior call to <resolve/>");
+                }
+            }
+        }
+        organisation = getProperty(organisation, settings, "ivy.organisation");
+        module = getProperty(module, settings, "ivy.module");
+        if (organisation == null) {
+            throw new BuildException("no organisation provided for ivy cache task: "
+                    + "It can either be set explicitely via the attribute 'organisation' "
+                    + "or via 'ivy.organisation' property or a prior call to <resolve/>");
+        }
+        if (module == null) {
+            throw new BuildException("no module name provided for ivy cache task: "
+                    + "It can either be set explicitely via the attribute 'module' "
+                    + "or via 'ivy.module' property or a prior call to <resolve/>");
+        }
+        if (conf == null) {
+            throw new BuildException("no conf provided for ivy cache task: "
+                    + "It can either be set explicitely via the attribute 'conf' or "
+                    + "via 'ivy.resolved.configurations' property or a prior call to <resolve/>");
+        }
+
+        artifactFilter = FilterHelper.getArtifactTypeFilter(type);
+    }
+
+    protected void ensureResolved(IvySettings settings) {
+        String requestedConfigs = getProperty(getConf(), settings, "ivy.resolved.configurations");
+
+        String[] confs = null;
+        if (getResolveId() != null) {
+            confs = getConfsToResolve(getResolveId(), requestedConfigs);
+        } else {
+            confs = getConfsToResolve(getOrganisation(), getModule(), requestedConfigs, false);
+        }
+
+        if (confs.length > 0) {
+            IvyResolve resolve = setupResolve(isHaltonfailure(), isUseOrigin());
+            resolve.setFile(getFile());
+            resolve.setTransitive(isTransitive());
+            resolve.setConf(StringUtils.join(confs, ", "));
+            resolve.setResolveId(getResolveId());
+            resolve.execute();
+        }
+    }
+
+    protected String[] getConfsToResolve(String org, String module, String conf, boolean strict) {
+        ModuleDescriptor reference = (ModuleDescriptor) getResolvedDescriptor(org, module, strict);
+        String[] rconfs = getResolvedConfigurations(org, module, strict);
+        return getConfsToResolve(reference, conf, rconfs);
+    }
+
+    protected String[] getConfsToResolve(String resolveId, String conf) {
+        ModuleDescriptor reference = (ModuleDescriptor) getResolvedDescriptor(resolveId, false);
+        if (reference == null) {
+            // assume the module has been resolved outside this build, resolve the required
+            // configurations again
+            // TODO: find a way to discover which confs were resolved by that previous resolve
+            if (conf == null) {
+                return new String[] {"*"};
+            } else {
+                return splitConfs(conf);
+            }
+        }
+        String[] rconfs = (String[]) getProject().getReference(
+            "ivy.resolved.configurations.ref." + resolveId);
+        return getConfsToResolve(reference, conf, rconfs);
+    }
+
+    private String[] getConfsToResolve(ModuleDescriptor reference, String conf, String[] rconfs) {
+        Message.debug("calculating configurations to resolve");
+
+        if (reference == null) {
+            Message.debug("module not yet resolved, all confs still need to be resolved");
+            if (conf == null) {
+                return new String[] {"*"};
+            } else {
+                return splitConfs(conf);
+            }
+        } else if (conf != null) {
+            String[] confs;
+            if ("*".equals(conf)) {
+                confs = reference.getConfigurationsNames();
+            } else {
+                confs = splitConfs(conf);
+            }
+
+            HashSet rconfsSet = new HashSet(Arrays.asList(rconfs));
+
+            // for each resolved configuration, check if the report still exists
+            ResolutionCacheManager cache = getSettings().getResolutionCacheManager();
+            for (Iterator it = rconfsSet.iterator(); it.hasNext();) {
+                String resolvedConf = (String) it.next();
+                String resolveId = getResolveId();
+                if (resolveId == null) {
+                    resolveId = ResolveOptions.getDefaultResolveId(reference);
+                }
+                File report = cache.getConfigurationResolveReportInCache(resolveId, resolvedConf);
+                if (!report.exists()) {
+                    // the report doesn't exist any longer, we have to recreate it...
+                    it.remove();
+                }
+            }
+
+            HashSet confsSet = new HashSet(Arrays.asList(confs));
+            Message.debug("resolved configurations:   " + rconfsSet);
+            Message.debug("asked configurations:      " + confsSet);
+            confsSet.removeAll(rconfsSet);
+            Message.debug("to resolve configurations: " + confsSet);
+            return (String[]) confsSet.toArray(new String[confsSet.size()]);
+        } else {
+            Message.debug("module already resolved, no configuration to resolve");
+            return new String[0];
+        }
+
+    }
+
+    protected IvyResolve setupResolve(boolean haltOnFailure, boolean useOrigin) {
+        Message.verbose("no resolved descriptor found: launching default resolve");
+        resolve.setTaskName(getTaskName());
+        resolve.setProject(getProject());
+        resolve.setHaltonfailure(haltOnFailure);
+        resolve.setUseOrigin(useOrigin);
+        resolve.setValidate(doValidate(getSettings()));
+        resolve.setKeep(isKeep());
+        resolve.setRefresh(isRefresh());
+        resolve.setLog(getLog());
+        resolve.setSettingsRef(getSettingsRef());
+        resolve.setResolveMode(getResolveMode());
+        return resolve;
+    }
+
+    protected ModuleRevisionId getResolvedMrid() {
+        return new ModuleRevisionId(getResolvedModuleId(),
+                getRevision() == null ? Ivy.getWorkingRevision() : getRevision());
+    }
+
+    protected ModuleId getResolvedModuleId() {
+        return isInline() ? new ModuleId(getOrganisation(), getModule() + "-caller")
+                : new ModuleId(getOrganisation(), getModule());
+    }
+
+    protected ResolveReport getResolvedReport() {
+        return getResolvedReport(getOrganisation(), isInline() ? getModule() + "-caller"
+                : getModule(), resolveId);
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public String getConf() {
+        return conf;
+    }
+
+    public void setConf(String conf) {
+        this.conf = conf;
+    }
+
+    public String getModule() {
+        return module;
+    }
+
+    public void setModule(String module) {
+        this.module = module;
+    }
+
+    public String getOrganisation() {
+        return organisation;
+    }
+
+    public void setOrganisation(String organisation) {
+        this.organisation = organisation;
+    }
+
+    public String getBranch() {
+        return branch;
+    }
+
+    public void setBranch(String branch) {
+        this.branch = branch;
+    }
+
+    public boolean isHaltonfailure() {
+        return haltOnFailure;
+    }
+
+    public void setHaltonfailure(boolean haltOnFailure) {
+        this.haltOnFailure = haltOnFailure;
+    }
+
+    public void setCache(File cache) {
+        cacheAttributeNotSupported();
+    }
+
+    public String getRevision() {
+        return revision;
+    }
+
+    public void setRevision(String rev) {
+        revision = rev;
+    }
+
+    public Filter getArtifactFilter() {
+        return artifactFilter;
+    }
+
+    public boolean isTransitive() {
+        return transitive;
+    }
+
+    public void setTransitive(boolean transitive) {
+        this.transitive = transitive;
+    }
+
+    public boolean isInline() {
+        return inline;
+    }
+
+    public void setInline(boolean inline) {
+        this.inline = inline;
+    }
+
+    public void setResolveId(String resolveId) {
+        this.resolveId = resolveId;
+    }
+
+    public String getResolveId() {
+        return resolveId;
+    }
+
+    public void setFile(File file) {
+        this.file = file;
+    }
+
+    public File getFile() {
+        return file;
+    }
+
+    public void setKeep(boolean keep) {
+        this.keep = Boolean.valueOf(keep);
+    }
+
+    public boolean isKeep() {
+        return this.keep == null ? !isInline() : this.keep.booleanValue();
+    }
+
+    public void setChanging(boolean changing) {
+        this.changing = changing;
+    }
+
+    public boolean isChanging() {
+        return this.changing;
+    }
+
+    public void setRefresh(boolean refresh) {
+        this.refresh = refresh;
+    }
+
+    public boolean isRefresh() {
+        return refresh;
+    }
+
+    public String getResolveMode() {
+        return resolveMode;
+    }
+
+    public void setResolveMode(String resolveMode) {
+        this.resolveMode = resolveMode;
+    }
+}
diff --git a/src/java/org/apache/ivy/ant/IvyPublish.java b/src/java/org/apache/ivy/ant/IvyPublish.java
new file mode 100644
index 0000000..d46b701
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/IvyPublish.java
@@ -0,0 +1,481 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import java.io.File;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.ivy.Ivy;
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.id.ArtifactRevisionId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.publish.PublishOptions;
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.ivy.util.DateUtil;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.DynamicAttribute;
+
+/**
+ * This task allow to publish a module revision to an Ivy repository.
+ */
+public class IvyPublish extends IvyTask {
+    private String organisation;
+
+    private String module;
+
+    private String revision;
+
+    private String pubRevision;
+
+    private String srcivypattern;
+
+    private String status;
+
+    private String conf = null;
+
+    private String pubdate;
+
+    private String deliverTarget;
+
+    private String publishResolverName = null;
+
+    private List artifactspattern = new ArrayList();
+
+    private File deliveryList;
+
+    private boolean publishivy = true;
+
+    private boolean warnonmissing = true;
+
+    private boolean haltonmissing = true;
+
+    private boolean overwrite = false;
+
+    private boolean update = false;
+
+    private boolean merge = true;
+
+    private boolean replacedynamicrev = true;
+
+    private boolean forcedeliver;
+
+    private Collection artifacts = new ArrayList();
+
+    private String pubBranch;
+
+    public void setCache(File cache) {
+        cacheAttributeNotSupported();
+    }
+
+    public String getSrcivypattern() {
+        return srcivypattern;
+    }
+
+    public void setSrcivypattern(String destivypattern) {
+        srcivypattern = destivypattern;
+    }
+
+    /**
+     * @deprecated use {@link #getSrcivypattern()} instead.
+     */
+    @Deprecated
+    public String getDeliverivypattern() {
+        return srcivypattern;
+    }
+
+    /**
+     * @deprecated use {@link #setSrcivypattern(String)} instead.
+     */
+    @Deprecated
+    public void setDeliverivypattern(String destivypattern) {
+        srcivypattern = destivypattern;
+    }
+
+    public String getModule() {
+        return module;
+    }
+
+    public void setModule(String module) {
+        this.module = module;
+    }
+
+    public String getOrganisation() {
+        return organisation;
+    }
+
+    public void setOrganisation(String organisation) {
+        this.organisation = organisation;
+    }
+
+    public String getPubdate() {
+        return pubdate;
+    }
+
+    public void setPubdate(String pubdate) {
+        this.pubdate = pubdate;
+    }
+
+    public String getPubrevision() {
+        return pubRevision;
+    }
+
+    public void setPubrevision(String pubRevision) {
+        this.pubRevision = pubRevision;
+    }
+
+    public String getPubbranch() {
+        return pubBranch;
+    }
+
+    public void setPubbranch(String pubBranch) {
+        this.pubBranch = pubBranch;
+    }
+
+    public String getRevision() {
+        return revision;
+    }
+
+    public void setRevision(String revision) {
+        this.revision = revision;
+    }
+
+    public String getStatus() {
+        return status;
+    }
+
+    public void setStatus(String status) {
+        this.status = status;
+    }
+
+    public void setConf(String conf) {
+        this.conf = conf;
+    }
+
+    public void setDelivertarget(String deliverTarget) {
+        this.deliverTarget = deliverTarget;
+    }
+
+    public void setDeliveryList(File deliveryList) {
+        this.deliveryList = deliveryList;
+    }
+
+    public String getResolver() {
+        return publishResolverName;
+    }
+
+    public void setResolver(String publishResolverName) {
+        this.publishResolverName = publishResolverName;
+    }
+
+    public String getArtifactspattern() {
+        return (String) (artifactspattern.isEmpty() ? null : artifactspattern.get(0));
+    }
+
+    public void setArtifactspattern(String artifactsPattern) {
+        artifactspattern.clear();
+        artifactspattern.add(artifactsPattern);
+    }
+
+    public void addArtifactspattern(String artifactsPattern) {
+        artifactspattern.add(artifactsPattern);
+    }
+
+    public void addConfiguredArtifacts(ArtifactsPattern p) {
+        artifactspattern.add(p.getPattern());
+    }
+
+    public boolean isReplacedynamicrev() {
+        return replacedynamicrev;
+    }
+
+    public void setReplacedynamicrev(boolean replacedynamicrev) {
+        this.replacedynamicrev = replacedynamicrev;
+    }
+
+    public boolean isMerge() {
+        return merge;
+    }
+
+    public void setMerge(boolean merge) {
+        this.merge = merge;
+    }
+
+    public void doExecute() throws BuildException {
+        Ivy ivy = getIvyInstance();
+        IvySettings settings = ivy.getSettings();
+
+        organisation = getProperty(organisation, settings, "ivy.organisation");
+        module = getProperty(module, settings, "ivy.module");
+        revision = getProperty(revision, settings, "ivy.revision");
+        pubBranch = getProperty(pubBranch, settings, "ivy.deliver.branch");
+        pubRevision = getProperty(pubRevision, settings, "ivy.deliver.revision");
+        if (artifactspattern.isEmpty()) {
+            String p = getProperty(null, settings, "ivy.publish.src.artifacts.pattern");
+            if (p != null) {
+                artifactspattern.add(p);
+            }
+        }
+        if (srcivypattern == null) {
+            srcivypattern = getArtifactspattern();
+        }
+        status = getProperty(status, settings, "ivy.status");
+        if (organisation == null) {
+            throw new BuildException("no organisation provided for ivy publish task: "
+                    + "It can either be set explicitely via the attribute 'organisation' "
+                    + "or via 'ivy.organisation' property or a prior call to <resolve/>");
+        }
+        if (module == null) {
+            throw new BuildException("no module name provided for ivy publish task: "
+                    + "It can either be set explicitely via the attribute 'module' "
+                    + "or via 'ivy.module' property or a prior call to <resolve/>");
+        }
+        if (revision == null) {
+            throw new BuildException("no module revision provided for ivy publish task: "
+                    + "It can either be set explicitely via the attribute 'revision' "
+                    + "or via 'ivy.revision' property or a prior call to <resolve/>");
+        }
+        if (artifactspattern.isEmpty()) {
+            throw new BuildException(
+                    "no artifacts pattern: either provide it through parameter or "
+                            + "through ivy.publish.src.artifacts.pattern property");
+        }
+        if (publishResolverName == null) {
+            throw new BuildException(
+                    "no publish deliver name: please provide it through parameter 'resolver'");
+        }
+        if ("working".equals(revision)) {
+            revision = Ivy.getWorkingRevision();
+        }
+        Date pubdate = getPubDate(this.pubdate, new Date());
+        if (pubRevision == null) {
+            if (revision.startsWith("working@")) {
+                pubRevision = DateUtil.format(pubdate);
+            } else {
+                pubRevision = revision;
+            }
+        }
+        if (status == null) {
+            throw new BuildException("no status provided: either provide it as parameter "
+                    + "or through the ivy.status.default property");
+        }
+        ModuleRevisionId mrid = ModuleRevisionId.newInstance(organisation, module, revision);
+        try {
+            File ivyFile = getProject().resolveFile(
+                IvyPatternHelper.substitute(srcivypattern, organisation, module, pubRevision,
+                    "ivy", "ivy", "xml"));
+            if (publishivy && (!ivyFile.exists() || forcedeliver)) {
+                IvyDeliver deliver = new IvyDeliver();
+                deliver.setSettingsRef(getSettingsRef());
+                deliver.setTaskName(getTaskName());
+                deliver.setProject(getProject());
+                deliver.setDeliverpattern(getSrcivypattern());
+                deliver.setDelivertarget(deliverTarget);
+                deliver.setDeliveryList(deliveryList);
+                deliver.setModule(getModule());
+                deliver.setOrganisation(getOrganisation());
+                deliver.setPubdate(DateUtil.format(pubdate));
+                deliver.setPubrevision(getPubrevision());
+                deliver.setPubbranch(getPubbranch());
+                deliver.setRevision(getRevision());
+                deliver.setStatus(getStatus());
+                deliver.setValidate(doValidate(settings));
+                deliver.setReplacedynamicrev(isReplacedynamicrev());
+                deliver.setMerge(merge);
+                deliver.setConf(conf);
+
+                deliver.execute();
+            }
+
+            ivy.publish(
+                mrid,
+                artifactspattern,
+                publishResolverName,
+                new PublishOptions()
+                        .setPubrevision(getPubrevision())
+                        .setPubbranch(getPubbranch())
+                        .setSrcIvyPattern(publishivy ? srcivypattern : null)
+                        .setStatus(getStatus())
+                        .setPubdate(pubdate)
+                        .setExtraArtifacts(
+                            (Artifact[]) artifacts.toArray(new Artifact[artifacts.size()]))
+                        .setValidate(doValidate(settings)).setOverwrite(overwrite)
+                        .setUpdate(update).setMerge(merge).setWarnOnMissing(warnonmissing)
+                        .setHaltOnMissing(haltonmissing).setConfs(splitConfs(conf)));
+        } catch (Exception e) {
+            if (e instanceof BuildException) {
+                throw (BuildException) e;
+            }
+            throw new BuildException("impossible to publish artifacts for " + mrid + ": " + e, e);
+        }
+    }
+
+    public PublishArtifact createArtifact() {
+        PublishArtifact art = new PublishArtifact();
+        artifacts.add(art);
+        return art;
+    }
+
+    public boolean isPublishivy() {
+        return publishivy;
+    }
+
+    public void setPublishivy(boolean publishivy) {
+        this.publishivy = publishivy;
+    }
+
+    public boolean isWarnonmissing() {
+        return warnonmissing;
+    }
+
+    public void setWarnonmissing(boolean warnonmissing) {
+        this.warnonmissing = warnonmissing;
+    }
+
+    public boolean isHaltonmissing() {
+        return haltonmissing;
+    }
+
+    public void setHaltonmissing(boolean haltonmissing) {
+        this.haltonmissing = haltonmissing;
+    }
+
+    public boolean isOverwrite() {
+        return overwrite;
+    }
+
+    public void setOverwrite(boolean overwrite) {
+        this.overwrite = overwrite;
+    }
+
+    public void setForcedeliver(boolean b) {
+        forcedeliver = b;
+    }
+
+    public boolean isForcedeliver() {
+        return forcedeliver;
+    }
+
+    public boolean isUpdate() {
+        return update;
+    }
+
+    public void setUpdate(boolean update) {
+        this.update = update;
+    }
+
+    public class PublishArtifact implements Artifact, DynamicAttribute {
+        private String ext;
+
+        private String name;
+
+        private String type;
+
+        private Map extra = new HashMap();
+
+        public String[] getConfigurations() {
+            return null;
+        }
+
+        public String getExt() {
+            return ext == null ? type : ext;
+        }
+
+        public ArtifactRevisionId getId() {
+            return null;
+        }
+
+        public ModuleRevisionId getModuleRevisionId() {
+            return null;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public Date getPublicationDate() {
+            return null;
+        }
+
+        public String getType() {
+            return type;
+        }
+
+        public URL getUrl() {
+            return null;
+        }
+
+        public void setExt(String ext) {
+            this.ext = ext;
+        }
+
+        public void setName(String name) {
+            this.name = name;
+        }
+
+        public void setType(String type) {
+            this.type = type;
+        }
+
+        public String getAttribute(String attName) {
+            return (String) extra.get(attName);
+        }
+
+        public Map getAttributes() {
+            return extra;
+        }
+
+        public String getExtraAttribute(String attName) {
+            return (String) extra.get(attName);
+        }
+
+        public Map getExtraAttributes() {
+            return extra;
+        }
+
+        public Map getQualifiedExtraAttributes() {
+            return extra;
+        }
+
+        public boolean isMetadata() {
+            return false;
+        }
+
+        public void setDynamicAttribute(String name, String value) {
+            extra.put(name, value);
+        }
+    }
+
+    public static class ArtifactsPattern {
+        private String pattern;
+
+        public String getPattern() {
+            return pattern;
+        }
+
+        public void setPattern(String pattern) {
+            this.pattern = pattern;
+        }
+    }
+}
diff --git a/src/java/org/apache/ivy/ant/IvyReport.java b/src/java/org/apache/ivy/ant/IvyReport.java
new file mode 100644
index 0000000..e4f024e
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/IvyReport.java
@@ -0,0 +1,434 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
+
+import org.apache.ivy.Ivy;
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.core.cache.ResolutionCacheManager;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.resolve.ResolveOptions;
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.ivy.plugins.report.XmlReportOutputter;
+import org.apache.ivy.plugins.report.XmlReportParser;
+import org.apache.ivy.util.FileUtil;
+import org.apache.ivy.util.Message;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.taskdefs.XSLTProcess;
+import org.apache.tools.ant.util.JAXPUtils;
+
+/**
+ * This ant task let users generates reports (html, xml, graphml, ...) from the last resolve done.
+ */
+public class IvyReport extends IvyTask {
+    private File todir;
+
+    private String organisation;
+
+    private String module;
+
+    private String conf;
+
+    private boolean graph = true;
+
+    private boolean dot = false;
+
+    private boolean xml = false;
+
+    private boolean xsl = true;
+
+    private File xslFile;
+
+    private String outputpattern;
+
+    private String xslext = "html";
+
+    private List params = new ArrayList();
+
+    private String resolveId;
+
+    private ModuleRevisionId mRevId;
+
+    public File getTodir() {
+        return todir;
+    }
+
+    public void setTodir(File todir) {
+        this.todir = todir;
+    }
+
+    public void setCache(File cache) {
+        cacheAttributeNotSupported();
+    }
+
+    public String getConf() {
+        return conf;
+    }
+
+    public void setConf(String conf) {
+        this.conf = conf;
+    }
+
+    public String getModule() {
+        return module;
+    }
+
+    public void setModule(String module) {
+        this.module = module;
+    }
+
+    public String getOrganisation() {
+        return organisation;
+    }
+
+    public void setOrganisation(String organisation) {
+        this.organisation = organisation;
+    }
+
+    public boolean isGraph() {
+        return graph;
+    }
+
+    public void setGraph(boolean graph) {
+        this.graph = graph;
+    }
+
+    public File getXslfile() {
+        return xslFile;
+    }
+
+    public void setXslfile(File xslFile) {
+        this.xslFile = xslFile;
+    }
+
+    public String getOutputpattern() {
+        return outputpattern;
+    }
+
+    public void setOutputpattern(String outputpattern) {
+        this.outputpattern = outputpattern;
+    }
+
+    public String getResolveId() {
+        return resolveId;
+    }
+
+    public void setResolveId(String resolveId) {
+        this.resolveId = resolveId;
+    }
+
+    public void doExecute() throws BuildException {
+        Ivy ivy = getIvyInstance();
+        IvySettings settings = ivy.getSettings();
+
+        conf = getProperty(conf, settings, "ivy.resolved.configurations", resolveId);
+        if ("*".equals(conf)) {
+            conf = getProperty(settings, "ivy.resolved.configurations", resolveId);
+        }
+        if (conf == null) {
+            throw new BuildException("no conf provided for ivy report task: "
+                    + "It can either be set explicitely via the attribute 'conf' or "
+                    + "via 'ivy.resolved.configurations' property or a prior call to <resolve/>");
+        }
+        if (todir == null) {
+            String t = getProperty(settings, "ivy.report.todir");
+            if (t != null) {
+                todir = getProject().resolveFile(t);
+            }
+        }
+        if (todir != null && todir.exists()) {
+            todir.mkdirs();
+        }
+        outputpattern = getProperty(outputpattern, settings, "ivy.report.output.pattern");
+        if (outputpattern == null) {
+            outputpattern = "[organisation]-[module]-[conf].[ext]";
+        }
+
+        if (todir != null && todir.exists() && !todir.isDirectory()) {
+            throw new BuildException("destination directory should be a directory !");
+        }
+
+        if (resolveId == null) {
+            organisation = getProperty(organisation, settings, "ivy.organisation", resolveId);
+            module = getProperty(module, settings, "ivy.module", resolveId);
+
+            if (organisation == null) {
+                throw new BuildException("no organisation provided for ivy report task: "
+                        + "It can either be set explicitely via the attribute 'organisation' or "
+                        + "via 'ivy.organisation' property or a prior call to <resolve/>");
+            }
+            if (module == null) {
+                throw new BuildException("no module name provided for ivy report task: "
+                        + "It can either be set explicitely via the attribute 'module' or "
+                        + "via 'ivy.module' property or a prior call to <resolve/>");
+            }
+
+            resolveId = ResolveOptions.getDefaultResolveId(new ModuleId(organisation, module));
+        }
+
+        try {
+            String[] confs = splitConfs(conf);
+            if (xsl) {
+                genreport(confs);
+            }
+            if (xml) {
+                genxml(confs);
+            }
+            if (graph) {
+                genStyled(confs, getStylePath("ivy-report-graph.xsl"), "graphml");
+            }
+            if (dot) {
+                genStyled(confs, getStylePath("ivy-report-dot.xsl"), "dot");
+            }
+        } catch (IOException e) {
+            throw new BuildException("impossible to generate report: " + e, e);
+        }
+    }
+
+    private void genxml(String[] confs) throws IOException {
+        ResolutionCacheManager cacheMgr = getIvyInstance().getResolutionCacheManager();
+        for (int i = 0; i < confs.length; i++) {
+            File xml = cacheMgr.getConfigurationResolveReportInCache(resolveId, confs[i]);
+
+            File out;
+            if (todir != null) {
+                out = new File(todir, getOutputPattern(confs[i], "xml"));
+            } else {
+                out = getProject().resolveFile(getOutputPattern(confs[i], "xml"));
+            }
+
+            FileUtil.copy(xml, out, null);
+        }
+    }
+
+    private void genreport(String[] confs) throws IOException {
+        genStyled(confs, getReportStylePath(), xslext);
+
+        // copy the css if required
+        if (xslFile == null) {
+            File css;
+            if (todir != null) {
+                css = new File(todir, "ivy-report.css");
+            } else {
+                css = getProject().resolveFile("ivy-report.css");
+            }
+
+            if (!css.exists()) {
+                Message.debug("copying report css to " + css.getAbsolutePath());
+                FileUtil.copy(XmlReportOutputter.class.getResourceAsStream("ivy-report.css"), css,
+                    null);
+            }
+        }
+    }
+
+    private File getReportStylePath() throws IOException {
+        if (xslFile != null) {
+            return xslFile;
+        }
+        // style should be a file (and not an url)
+        // so we have to copy it from classpath to cache
+        ResolutionCacheManager cacheMgr = getIvyInstance().getResolutionCacheManager();
+        File style = new File(cacheMgr.getResolutionCacheRoot(), "ivy-report.xsl");
+        if (!style.exists()) {
+            Message.debug("copying ivy-report.xsl to " + style.getAbsolutePath());
+            FileUtil.copy(XmlReportOutputter.class.getResourceAsStream("ivy-report.xsl"), style,
+                null);
+        }
+        return style;
+    }
+
+    private String getOutputPattern(String conf, String ext) {
+        if (mRevId == null) {
+            ResolutionCacheManager cacheMgr = getIvyInstance().getResolutionCacheManager();
+
+            XmlReportParser parser = new XmlReportParser();
+            File reportFile = cacheMgr.getConfigurationResolveReportInCache(resolveId, conf);
+
+            try {
+                parser.parse(reportFile);
+            } catch (ParseException e) {
+                throw new BuildException("Error occurred while parsing reportfile '"
+                        + reportFile.getAbsolutePath() + "'", e);
+            }
+
+            // get the resolve module
+            mRevId = parser.getResolvedModule();
+        }
+
+        return IvyPatternHelper.substitute(outputpattern, mRevId.getOrganisation(),
+            mRevId.getName(), mRevId.getRevision(), "", "", ext, conf,
+            mRevId.getQualifiedExtraAttributes(), null);
+    }
+
+    private void genStyled(String[] confs, File style, String ext) throws IOException {
+        ResolutionCacheManager cacheMgr = getIvyInstance().getResolutionCacheManager();
+
+        // process the report with xslt to generate dot file
+        File out;
+        if (todir != null) {
+            out = todir;
+        } else {
+            out = getProject().getBaseDir();
+        }
+
+        InputStream xsltStream = null;
+        try {
+            // create stream to stylesheet
+            xsltStream = new BufferedInputStream(new FileInputStream(style));
+            Source xsltSource = new StreamSource(xsltStream, JAXPUtils.getSystemId(style));
+
+            // create transformer
+            TransformerFactory tFactory = TransformerFactory.newInstance();
+            Transformer transformer = tFactory.newTransformer(xsltSource);
+
+            // add standard parameters
+            transformer.setParameter("confs", conf);
+            transformer.setParameter("extension", xslext);
+
+            // add the provided XSLT parameters
+            for (Iterator it = params.iterator(); it.hasNext();) {
+                XSLTProcess.Param param = (XSLTProcess.Param) it.next();
+                transformer.setParameter(param.getName(), param.getExpression());
+            }
+
+            // create the report
+            for (int i = 0; i < confs.length; i++) {
+                File reportFile = cacheMgr
+                        .getConfigurationResolveReportInCache(resolveId, confs[i]);
+                File outFile = new File(out, getOutputPattern(confs[i], ext));
+
+                log("Processing " + reportFile + " to " + outFile);
+
+                // make sure the output directory exist
+                File outFileDir = outFile.getParentFile();
+                if (!outFileDir.exists()) {
+                    if (!outFileDir.mkdirs()) {
+                        throw new BuildException("Unable to create directory: "
+                                + outFileDir.getAbsolutePath());
+                    }
+                }
+
+                InputStream inStream = null;
+                OutputStream outStream = null;
+                try {
+                    inStream = new BufferedInputStream(new FileInputStream(reportFile));
+                    outStream = new BufferedOutputStream(new FileOutputStream(outFile));
+                    StreamResult res = new StreamResult(outStream);
+                    Source src = new StreamSource(inStream, JAXPUtils.getSystemId(style));
+                    transformer.transform(src, res);
+                } catch (TransformerException e) {
+                    throw new BuildException(e);
+                } finally {
+                    if (inStream != null) {
+                        try {
+                            inStream.close();
+                        } catch (IOException e) {
+                            // ignore
+                        }
+                    }
+                    if (outStream != null) {
+                        try {
+                            outStream.close();
+                        } catch (IOException e) {
+                            // ignore
+                        }
+                    }
+                }
+            }
+        } catch (TransformerConfigurationException e) {
+            throw new BuildException(e);
+        } finally {
+            if (xsltStream != null) {
+                try {
+                    xsltStream.close();
+                } catch (IOException e) {
+                    // ignore
+                }
+            }
+        }
+    }
+
+    private File getStylePath(String styleResourceName) throws IOException {
+        // style should be a file (and not an url)
+        // so we have to copy it from classpath to cache
+        ResolutionCacheManager cacheMgr = getIvyInstance().getResolutionCacheManager();
+        File style = new File(cacheMgr.getResolutionCacheRoot(), styleResourceName);
+        FileUtil.copy(XmlReportOutputter.class.getResourceAsStream(styleResourceName), style, null);
+        return style;
+    }
+
+    public boolean isXml() {
+        return xml;
+    }
+
+    public void setXml(boolean xml) {
+        this.xml = xml;
+    }
+
+    public boolean isXsl() {
+        return xsl;
+    }
+
+    public void setXsl(boolean xsl) {
+        this.xsl = xsl;
+    }
+
+    public String getXslext() {
+        return xslext;
+    }
+
+    public void setXslext(String xslext) {
+        this.xslext = xslext;
+    }
+
+    public XSLTProcess.Param createParam() {
+        XSLTProcess.Param result = new XSLTProcess.Param();
+        params.add(result);
+        return result;
+    }
+
+    public boolean isDot() {
+        return dot;
+    }
+
+    public void setDot(boolean dot) {
+        this.dot = dot;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/ant/IvyRepositoryReport.java b/src/java/org/apache/ivy/ant/IvyRepositoryReport.java
new file mode 100644
index 0000000..0b725fc
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/IvyRepositoryReport.java
@@ -0,0 +1,328 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.ivy.Ivy;
+import org.apache.ivy.core.cache.ResolutionCacheManager;
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.report.ResolveReport;
+import org.apache.ivy.core.resolve.ResolveOptions;
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+import org.apache.ivy.plugins.report.XmlReportOutputter;
+import org.apache.ivy.util.FileUtil;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.taskdefs.XSLTProcess;
+
+/**
+ * Generates a report of dependencies of a set of modules in the repository. The set of modules is
+ * specified using organisation/module and matcher.
+ */
+public class IvyRepositoryReport extends IvyTask {
+    private String organisation = "*";
+
+    private String module;
+
+    private String branch;
+
+    private String revision = "latest.integration";
+
+    private String matcher = PatternMatcher.EXACT_OR_REGEXP;
+
+    private File todir;
+
+    private boolean graph = false;
+
+    private boolean dot = false;
+
+    private boolean xml = true;
+
+    private boolean xsl = false;
+
+    private String xslFile;
+
+    private String outputname = "ivy-repository-report";
+
+    private String xslext = "html";
+
+    private List params = new ArrayList();
+
+    public void doExecute() throws BuildException {
+        Ivy ivy = getIvyInstance();
+        IvySettings settings = ivy.getSettings();
+        if (xsl && xslFile == null) {
+            throw new BuildException("xsl file is mandatory when using xsl generation");
+        }
+        if (module == null && PatternMatcher.EXACT.equals(matcher)) {
+            throw new BuildException("no module name provided for ivy repository graph task: "
+                    + "It can either be set explicitely via the attribute 'module' or "
+                    + "via 'ivy.module' property or a prior call to <resolve/>");
+        } else if (module == null && !PatternMatcher.EXACT.equals(matcher)) {
+            module = PatternMatcher.ANY_EXPRESSION;
+        }
+        ModuleRevisionId mrid = ModuleRevisionId.newInstance(organisation, module, revision);
+
+        try {
+            ModuleRevisionId criteria = null;
+
+            if ((revision == null) || settings.getVersionMatcher().isDynamic(mrid)) {
+                criteria = new ModuleRevisionId(new ModuleId(organisation, module), branch, "*");
+            } else {
+                criteria = new ModuleRevisionId(new ModuleId(organisation, module), branch,
+                        revision);
+            }
+
+            ModuleRevisionId[] mrids = ivy.listModules(criteria, settings.getMatcher(matcher));
+
+            // replace all found revisions with the original requested revision
+            Set modules = new HashSet();
+            for (int i = 0; i < mrids.length; i++) {
+                modules.add(ModuleRevisionId.newInstance(mrids[i], revision));
+            }
+
+            mrids = (ModuleRevisionId[]) modules.toArray(new ModuleRevisionId[modules.size()]);
+            ModuleDescriptor md = DefaultModuleDescriptor.newCallerInstance(mrids, true, false);
+            String resolveId = ResolveOptions.getDefaultResolveId(md);
+            ResolveReport report = ivy.resolve(md, new ResolveOptions().setResolveId(resolveId)
+                    .setValidate(doValidate(settings)));
+
+            ResolutionCacheManager cacheMgr = getIvyInstance().getResolutionCacheManager();
+            new XmlReportOutputter().output(report, cacheMgr, new ResolveOptions());
+            if (graph) {
+                gengraph(cacheMgr, md.getModuleRevisionId().getOrganisation(), md
+                        .getModuleRevisionId().getName());
+            }
+            if (dot) {
+                gendot(cacheMgr, md.getModuleRevisionId().getOrganisation(), md
+                        .getModuleRevisionId().getName());
+            }
+            if (xml) {
+
+                FileUtil.copy(cacheMgr.getConfigurationResolveReportInCache(resolveId, "default"),
+                    new File(getTodir(), outputname + ".xml"), null);
+            }
+            if (xsl) {
+                genreport(cacheMgr, md.getModuleRevisionId().getOrganisation(), md
+                        .getModuleRevisionId().getName());
+            }
+        } catch (Exception e) {
+            throw new BuildException("impossible to generate graph for " + mrid + ": " + e, e);
+        }
+    }
+
+    private void genreport(ResolutionCacheManager cache, String organisation, String module)
+            throws IOException {
+        // first process the report with xslt
+        XSLTProcess xslt = new XSLTProcess();
+        xslt.setTaskName(getTaskName());
+        xslt.setProject(getProject());
+        xslt.init();
+
+        String resolveId = ResolveOptions.getDefaultResolveId(new ModuleId(organisation, module));
+        xslt.setIn(cache.getConfigurationResolveReportInCache(resolveId, "default"));
+        xslt.setOut(new File(getTodir(), outputname + "." + xslext));
+
+        xslt.setStyle(xslFile);
+
+        XSLTProcess.Param param = xslt.createParam();
+        param.setName("extension");
+        param.setExpression(xslext);
+
+        // add the provided XSLT parameters
+        for (Iterator it = params.iterator(); it.hasNext();) {
+            param = (XSLTProcess.Param) it.next();
+            XSLTProcess.Param realParam = xslt.createParam();
+            realParam.setName(param.getName());
+            realParam.setExpression(param.getExpression());
+        }
+
+        xslt.execute();
+    }
+
+    private void gengraph(ResolutionCacheManager cache, String organisation, String module)
+            throws IOException {
+        gen(cache, organisation, module, getGraphStylePath(cache.getResolutionCacheRoot()),
+            "graphml");
+    }
+
+    private String getGraphStylePath(File cache) throws IOException {
+        // style should be a file (and not an url)
+        // so we have to copy it from classpath to cache
+        File style = new File(cache, "ivy-report-graph-all.xsl");
+        FileUtil.copy(XmlReportOutputter.class.getResourceAsStream("ivy-report-graph-all.xsl"),
+            style, null);
+        return style.getAbsolutePath();
+    }
+
+    private void gendot(ResolutionCacheManager cache, String organisation, String module)
+            throws IOException {
+        gen(cache, organisation, module, getDotStylePath(cache.getResolutionCacheRoot()), "dot");
+    }
+
+    private String getDotStylePath(File cache) throws IOException {
+        // style should be a file (and not an url)
+        // so we have to copy it from classpath to cache
+        File style = new File(cache, "ivy-report-dot-all.xsl");
+        FileUtil.copy(XmlReportOutputter.class.getResourceAsStream("ivy-report-dot-all.xsl"),
+            style, null);
+        return style.getAbsolutePath();
+    }
+
+    private void gen(ResolutionCacheManager cache, String organisation, String module,
+            String style, String ext) throws IOException {
+        XSLTProcess xslt = new XSLTProcess();
+        xslt.setTaskName(getTaskName());
+        xslt.setProject(getProject());
+        xslt.init();
+
+        String resolveId = ResolveOptions.getDefaultResolveId(new ModuleId(organisation, module));
+        xslt.setIn(cache.getConfigurationResolveReportInCache(resolveId, "default"));
+        xslt.setOut(new File(getTodir(), outputname + "." + ext));
+        xslt.setBasedir(cache.getResolutionCacheRoot());
+        xslt.setStyle(style);
+        xslt.execute();
+    }
+
+    public File getTodir() {
+        if (todir == null && getProject() != null) {
+            return getProject().getBaseDir();
+        }
+        return todir;
+    }
+
+    public void setTodir(File todir) {
+        this.todir = todir;
+    }
+
+    public boolean isGraph() {
+        return graph;
+    }
+
+    public void setGraph(boolean graph) {
+        this.graph = graph;
+    }
+
+    public String getXslfile() {
+        return xslFile;
+    }
+
+    public void setXslfile(String xslFile) {
+        this.xslFile = xslFile;
+    }
+
+    public boolean isXml() {
+        return xml;
+    }
+
+    public void setXml(boolean xml) {
+        this.xml = xml;
+    }
+
+    public boolean isXsl() {
+        return xsl;
+    }
+
+    public void setXsl(boolean xsl) {
+        this.xsl = xsl;
+    }
+
+    public String getXslext() {
+        return xslext;
+    }
+
+    public void setXslext(String xslext) {
+        this.xslext = xslext;
+    }
+
+    public XSLTProcess.Param createParam() {
+        XSLTProcess.Param result = new XSLTProcess.Param();
+        params.add(result);
+        return result;
+    }
+
+    public String getOutputname() {
+        return outputname;
+    }
+
+    public void setOutputname(String outputpattern) {
+        outputname = outputpattern;
+    }
+
+    public void setCache(File cache) {
+        cacheAttributeNotSupported();
+    }
+
+    public String getMatcher() {
+        return matcher;
+    }
+
+    public void setMatcher(String matcher) {
+        this.matcher = matcher;
+    }
+
+    public String getModule() {
+        return module;
+    }
+
+    public void setModule(String module) {
+        this.module = module;
+    }
+
+    public String getOrganisation() {
+        return organisation;
+    }
+
+    public void setOrganisation(String organisation) {
+        this.organisation = organisation;
+    }
+
+    public String getRevision() {
+        return revision;
+    }
+
+    public void setRevision(String revision) {
+        this.revision = revision;
+    }
+
+    public String getBranch() {
+        return branch;
+    }
+
+    public void setBranch(String branch) {
+        this.branch = branch;
+    }
+
+    public boolean isDot() {
+        return dot;
+    }
+
+    public void setDot(boolean dot) {
+        this.dot = dot;
+    }
+}
diff --git a/src/java/org/apache/ivy/ant/IvyResolve.java b/src/java/org/apache/ivy/ant/IvyResolve.java
new file mode 100644
index 0000000..5dd7757
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/IvyResolve.java
@@ -0,0 +1,517 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.ivy.Ivy;
+import org.apache.ivy.core.LogOptions;
+import org.apache.ivy.core.module.descriptor.DefaultExcludeRule;
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.ExtendsDescriptor;
+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.apache.ivy.core.resolve.ResolveProcessException;
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.ivy.util.Message;
+import org.apache.ivy.util.filter.FilterHelper;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+
+/**
+ * This task allow to call the Ivy dependency resolution from ant.
+ */
+public class IvyResolve extends IvyTask {
+    private File file = null;
+
+    private String conf = null;
+
+    private String organisation = null;
+
+    private String module = null;
+
+    private String branch = null;
+
+    private String revision = null;
+
+    private String pubdate = null;
+
+    private boolean inline = false;
+
+    private boolean haltOnFailure = true;
+
+    private boolean showProgress = true;
+
+    private boolean useCacheOnly = false;
+
+    private String type = null;
+
+    private boolean transitive = true;
+
+    private boolean refresh = false;
+
+    private boolean changing = false;
+
+    private Boolean keep = null;
+
+    private String failureProperty = null;
+
+    private boolean useOrigin = false;
+
+    private String resolveMode = null;
+
+    private String resolveId = null;
+
+    private String log = ResolveOptions.LOG_DEFAULT;
+
+    private boolean checkIfChanged = true; // for backward compatibility
+
+    private List/* <IvyDependency> */dependencies = new ArrayList();
+
+    private List/* <IvyExclude> */excludes = new ArrayList();
+
+    private List/* <IvyConflict> */conflicts = new ArrayList();
+
+    public boolean isUseOrigin() {
+        return useOrigin;
+    }
+
+    public void setUseOrigin(boolean useOrigin) {
+        this.useOrigin = useOrigin;
+    }
+
+    public String getDate() {
+        return pubdate;
+    }
+
+    public void setDate(String pubdate) {
+        this.pubdate = pubdate;
+    }
+
+    public String getRevision() {
+        return revision;
+    }
+
+    public void setRevision(String revision) {
+        this.revision = revision;
+    }
+
+    public String getBranch() {
+        return branch;
+    }
+
+    public void setBranch(String branch) {
+        this.branch = branch;
+    }
+
+    public void setCache(File cache) {
+        cacheAttributeNotSupported();
+    }
+
+    public String getConf() {
+        return conf;
+    }
+
+    public void setConf(String conf) {
+        this.conf = conf;
+    }
+
+    public File getFile() {
+        return file;
+    }
+
+    public void setFile(File file) {
+        this.file = file;
+    }
+
+    public boolean isHaltonfailure() {
+        return haltOnFailure;
+    }
+
+    public void setHaltonfailure(boolean haltOnFailure) {
+        this.haltOnFailure = haltOnFailure;
+    }
+
+    public void setShowprogress(boolean show) {
+        this.showProgress = show;
+    }
+
+    public boolean isUseCacheOnly() {
+        return useCacheOnly;
+    }
+
+    public void setUseCacheOnly(boolean useCacheOnly) {
+        this.useCacheOnly = useCacheOnly;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public boolean isRefresh() {
+        return refresh;
+    }
+
+    public void setRefresh(boolean refresh) {
+        this.refresh = refresh;
+    }
+
+    public String getLog() {
+        return log;
+    }
+
+    public void setLog(String log) {
+        this.log = log;
+    }
+
+    /**
+     * @deprecated Use {@link #setFailureProperty(String)} instead
+     */
+    @Deprecated
+    public void setFailurePropery(String failureProperty) {
+        log("The 'failurepropery' attribute is deprecated. "
+                + "Please use the 'failureproperty' attribute instead", Project.MSG_WARN);
+        setFailureProperty(failureProperty);
+    }
+
+    public void setFailureProperty(String failureProperty) {
+        this.failureProperty = failureProperty;
+    }
+
+    public String getFailureProperty() {
+        return failureProperty;
+    }
+
+    public IvyDependency createDependency() {
+        IvyDependency dep = new IvyDependency();
+        dependencies.add(dep);
+        return dep;
+    }
+
+    public IvyExclude createExclude() {
+        IvyExclude ex = new IvyExclude();
+        excludes.add(ex);
+        return ex;
+    }
+
+    public IvyConflict createConflict() {
+        IvyConflict c = new IvyConflict();
+        conflicts.add(c);
+        return c;
+    }
+
+    protected void prepareTask() {
+        super.prepareTask();
+        Message.setShowProgress(showProgress);
+    }
+
+    public void doExecute() throws BuildException {
+        Ivy ivy = getIvyInstance();
+        IvySettings settings = ivy.getSettings();
+        try {
+            conf = getProperty(conf, settings, "ivy.configurations");
+            type = getProperty(type, settings, "ivy.resolve.default.type.filter");
+            String[] confs = splitConfs(conf);
+
+            boolean childs = !dependencies.isEmpty() || !excludes.isEmpty() || !conflicts.isEmpty();
+
+            ResolveReport report;
+            if (childs) {
+                if (isInline()) {
+                    throw new BuildException("the inline mode is incompatible with child elements");
+                }
+                if (organisation != null) {
+                    throw new BuildException("'organisation' is not allowed with child elements");
+                }
+                if (module != null) {
+                    throw new BuildException("'module' is not allowed with child elements");
+                }
+                if (file != null) {
+                    throw new BuildException("'file' not allowed with child elements");
+                }
+                if (!getAllowedLogOptions().contains(log)) {
+                    throw new BuildException("invalid option for 'log': " + log
+                            + ". Available options are " + getAllowedLogOptions());
+                }
+
+                ModuleRevisionId mrid = ModuleRevisionId.newInstance("", "",
+                    Ivy.getWorkingRevision());
+                DefaultModuleDescriptor md = DefaultModuleDescriptor.newBasicInstance(mrid, null);
+
+                Iterator itDeps = dependencies.iterator();
+                while (itDeps.hasNext()) {
+                    IvyDependency dep = (IvyDependency) itDeps.next();
+                    DependencyDescriptor dd = dep.asDependencyDescriptor(md, "default", settings);
+                    md.addDependency(dd);
+                }
+
+                Iterator itExcludes = excludes.iterator();
+                while (itExcludes.hasNext()) {
+                    IvyExclude exclude = (IvyExclude) itExcludes.next();
+                    DefaultExcludeRule rule = exclude.asRule(settings);
+                    rule.addConfiguration("default");
+                    md.addExcludeRule(rule);
+                }
+
+                Iterator itConflicts = conflicts.iterator();
+                while (itConflicts.hasNext()) {
+                    IvyConflict conflict = (IvyConflict) itConflicts.next();
+                    conflict.addConflict(md, settings);
+                }
+
+                report = ivy
+                        .resolve(md, getResolveOptions(ivy, new String[] {"default"}, settings));
+            } else if (isInline()) {
+                if (organisation == null) {
+                    throw new BuildException("'organisation' is required when using inline mode");
+                }
+                if (module == null) {
+                    throw new BuildException("'module' is required when using inline mode");
+                }
+                if (file != null) {
+                    throw new BuildException("'file' not allowed when using inline mode");
+                }
+                if (!getAllowedLogOptions().contains(log)) {
+                    throw new BuildException("invalid option for 'log': " + log
+                            + ". Available options are " + getAllowedLogOptions());
+                }
+                for (int i = 0; i < confs.length; i++) {
+                    if ("*".equals(confs[i])) {
+                        confs[i] = "*(public)";
+                    }
+                }
+                if (revision == null) {
+                    revision = "latest.integration";
+                }
+                report = ivy.resolve(
+                    ModuleRevisionId.newInstance(organisation, module, branch, revision),
+                    getResolveOptions(ivy, confs, settings), changing);
+
+            } else {
+                if (organisation != null) {
+                    throw new BuildException(
+                            "'organisation' not allowed when not using 'org' attribute");
+                }
+                if (module != null) {
+                    throw new BuildException("'module' not allowed when not using 'org' attribute");
+                }
+                if (file == null) {
+                    file = getProject().resolveFile(getProperty(settings, "ivy.dep.file"));
+                }
+                report = ivy.resolve(file.toURI().toURL(), getResolveOptions(ivy, confs, settings));
+            }
+            if (report.hasError()) {
+                if (failureProperty != null) {
+                    getProject().setProperty(failureProperty, "true");
+                }
+                if (isHaltonfailure()) {
+                    throw new BuildException("resolve failed - see output for details");
+                }
+            }
+            setResolved(report, resolveId, isKeep());
+            confs = report.getConfigurations();
+
+            if (isKeep()) {
+                ModuleDescriptor md = report.getModuleDescriptor();
+                // put resolved infos in ant properties and ivy variables
+                // putting them in ivy variables is important to be able to change from one resolve
+                // call to the other
+                String mdOrg = md.getModuleRevisionId().getOrganisation();
+                String mdName = md.getModuleRevisionId().getName();
+                String mdRev = md.getResolvedModuleRevisionId().getRevision();
+                getProject().setProperty("ivy.organisation", mdOrg);
+                settings.setVariable("ivy.organisation", mdOrg);
+                getProject().setProperty("ivy.module", mdName);
+                settings.setVariable("ivy.module", mdName);
+                getProject().setProperty("ivy.revision", mdRev);
+                settings.setVariable("ivy.revision", mdRev);
+                for (int i = 0; i < md.getInheritedDescriptors().length; i++) {
+                    ExtendsDescriptor parent = md.getInheritedDescriptors()[i];
+                    String parentOrg = parent.getResolvedParentRevisionId().getOrganisation();
+                    String parentModule = parent.getResolvedParentRevisionId().getName();
+                    String parentRevision = parent.getResolvedParentRevisionId().getRevision();
+                    String parentBranch = parent.getResolvedParentRevisionId().getBranch();
+                    getProject().setProperty("ivy.parent[" + i + "].organisation", parentOrg);
+                    settings.setVariable("ivy.parent[" + i + "].organisation", parentOrg);
+                    getProject().setProperty("ivy.parent[" + i + "].module", parentModule);
+                    settings.setVariable("ivy.parent[" + i + "].module", parentModule);
+                    getProject().setProperty("ivy.parent[" + i + "].revision", parentRevision);
+                    settings.setVariable("ivy.parent[" + i + "].revision", parentRevision);
+                    if (parentBranch != null) {
+                        getProject().setProperty("ivy.parent[" + i + "].branch", parentBranch);
+                        settings.setVariable("ivy.parent[" + i + "].branch", parentBranch);
+                    }
+                }
+                getProject().setProperty("ivy.parents.count",
+                    String.valueOf(md.getInheritedDescriptors().length));
+                settings.setVariable("ivy.parents.count",
+                    String.valueOf(md.getInheritedDescriptors().length));
+
+                Boolean hasChanged = null;
+                if (getCheckIfChanged()) {
+                    hasChanged = Boolean.valueOf(report.hasChanged());
+                    getProject().setProperty("ivy.deps.changed", hasChanged.toString());
+                    settings.setVariable("ivy.deps.changed", hasChanged.toString());
+                }
+                getProject().setProperty("ivy.resolved.configurations", mergeConfs(confs));
+                settings.setVariable("ivy.resolved.configurations", mergeConfs(confs));
+                if (file != null) {
+                    getProject().setProperty("ivy.resolved.file", file.getAbsolutePath());
+                    settings.setVariable("ivy.resolved.file", file.getAbsolutePath());
+                }
+                if (resolveId != null) {
+                    getProject().setProperty("ivy.organisation." + resolveId, mdOrg);
+                    settings.setVariable("ivy.organisation." + resolveId, mdOrg);
+                    getProject().setProperty("ivy.module." + resolveId, mdName);
+                    settings.setVariable("ivy.module." + resolveId, mdName);
+                    getProject().setProperty("ivy.revision." + resolveId, mdRev);
+                    settings.setVariable("ivy.revision." + resolveId, mdRev);
+                    if (getCheckIfChanged()) {
+                        // hasChanged has already been set earlier
+                        getProject().setProperty("ivy.deps.changed." + resolveId,
+                            hasChanged.toString());
+                        settings.setVariable("ivy.deps.changed." + resolveId, hasChanged.toString());
+                    }
+                    getProject().setProperty("ivy.resolved.configurations." + resolveId,
+                        mergeConfs(confs));
+                    settings.setVariable("ivy.resolved.configurations." + resolveId,
+                        mergeConfs(confs));
+                    if (file != null) {
+                        getProject().setProperty("ivy.resolved.file." + resolveId,
+                            file.getAbsolutePath());
+                        settings.setVariable("ivy.resolved.file." + resolveId,
+                            file.getAbsolutePath());
+                    }
+                }
+            }
+        } catch (MalformedURLException e) {
+            throw new BuildException("unable to convert given ivy file to url: " + file + ": " + e,
+                    e);
+        } catch (ParseException e) {
+            log(e.getMessage(), Project.MSG_ERR);
+            throw new BuildException("syntax errors in ivy file: " + e, e);
+        } catch (ResolveProcessException e) {
+            throw new BuildException("impossible to resolve dependencies:\n\t" + e.getMessage(), e);
+        } catch (Exception e) {
+            throw new BuildException("impossible to resolve dependencies:\n\t" + e, e);
+        }
+    }
+
+    protected Collection/* <String> */getAllowedLogOptions() {
+        return Arrays.asList(new String[] {LogOptions.LOG_DEFAULT, LogOptions.LOG_DOWNLOAD_ONLY,
+                LogOptions.LOG_QUIET});
+    }
+
+    private ResolveOptions getResolveOptions(Ivy ivy, String[] confs, IvySettings settings) {
+        if (useOrigin) {
+            settings.useDeprecatedUseOrigin();
+        }
+        return ((ResolveOptions) new ResolveOptions().setLog(log)).setConfs(confs)
+                .setValidate(doValidate(settings))
+                .setArtifactFilter(FilterHelper.getArtifactTypeFilter(type)).setRevision(revision)
+                .setDate(getPubDate(pubdate, null)).setUseCacheOnly(useCacheOnly)
+                .setRefresh(refresh).setTransitive(transitive).setResolveMode(resolveMode)
+                .setResolveId(resolveId).setCheckIfChanged(checkIfChanged);
+    }
+
+    public String getModule() {
+        return module;
+    }
+
+    public void setModule(String module) {
+        this.module = module;
+    }
+
+    public String getOrganisation() {
+        return organisation;
+    }
+
+    public void setOrganisation(String organisation) {
+        this.organisation = organisation;
+    }
+
+    public boolean isTransitive() {
+        return transitive;
+    }
+
+    public void setTransitive(boolean transitive) {
+        this.transitive = transitive;
+    }
+
+    public boolean isChanging() {
+        return changing;
+    }
+
+    public void setChanging(boolean changing) {
+        this.changing = changing;
+    }
+
+    public boolean isKeep() {
+        return keep == null ? organisation == null : keep.booleanValue();
+    }
+
+    public void setKeep(boolean keep) {
+        this.keep = Boolean.valueOf(keep);
+    }
+
+    public boolean isInline() {
+        return inline;
+    }
+
+    public void setInline(boolean inline) {
+        this.inline = inline;
+    }
+
+    public String getResolveId() {
+        return resolveId;
+    }
+
+    public void setResolveId(String resolveId) {
+        this.resolveId = resolveId;
+    }
+
+    public String getResolveMode() {
+        return resolveMode;
+    }
+
+    public void setResolveMode(String resolveMode) {
+        this.resolveMode = resolveMode;
+    }
+
+    public boolean getCheckIfChanged() {
+        return checkIfChanged;
+    }
+
+    public void setCheckIfChanged(boolean checkIfChanged) {
+        this.checkIfChanged = checkIfChanged;
+    }
+}
diff --git a/src/java/org/apache/ivy/ant/IvyResources.java b/src/java/org/apache/ivy/ant/IvyResources.java
new file mode 100644
index 0000000..41ec043
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/IvyResources.java
@@ -0,0 +1,118 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.ivy.core.report.ArtifactDownloadReport;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Location;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.types.Reference;
+import org.apache.tools.ant.types.ResourceCollection;
+import org.apache.tools.ant.types.resources.BaseResourceCollectionWrapper;
+import org.apache.tools.ant.types.resources.FileResource;
+
+public class IvyResources extends IvyCacheTask implements ResourceCollection {
+
+    /**
+     * Delegate for the implementation of the resource collection
+     */
+    private class IvyBaseResourceCollectionWrapper extends BaseResourceCollectionWrapper {
+
+        protected Collection getCollection() {
+            return resolveResources(null);
+        }
+
+    }
+
+    private IvyBaseResourceCollectionWrapper wrapper = new IvyBaseResourceCollectionWrapper();
+
+    // delegate the ProjectComponent API on the wrapper
+
+    public void setLocation(Location location) {
+        super.setLocation(location);
+        wrapper.setLocation(location);
+    }
+
+    public void setProject(Project project) {
+        super.setProject(project);
+        wrapper.setProject(project);
+    }
+
+    public void setDescription(String desc) {
+        super.setDescription(desc);
+        wrapper.setDescription(desc);
+    }
+
+    // delegate the DataType API on the wrapper
+
+    public void setRefid(Reference ref) {
+        wrapper.setRefid(ref);
+    }
+
+    // delegate the AbstractResourceCollectionWrapper API on the wrapper
+
+    public void setCache(boolean b) {
+        wrapper.setCache(b);
+    }
+
+    // implementation of the Resource Collection API
+
+    public boolean isFilesystemOnly() {
+        return true;
+    }
+
+    public Iterator iterator() {
+        return wrapper.iterator();
+    }
+
+    public int size() {
+        return wrapper.size();
+    }
+
+    // convert the ivy reports into an Ant Resource collection
+
+    private Collection resolveResources(String id) throws BuildException {
+        prepareAndCheck();
+        try {
+            List/* <FileResource> */resources = new ArrayList();
+            if (id != null) {
+                getProject().addReference(id, this);
+            }
+            for (Iterator iter = getArtifactReports().iterator(); iter.hasNext();) {
+                ArtifactDownloadReport a = (ArtifactDownloadReport) iter.next();
+                resources.add(new FileResource(a.getLocalFile()));
+            }
+            return resources;
+        } catch (Exception ex) {
+            throw new BuildException("impossible to build ivy resources: " + ex, ex);
+        }
+    }
+
+    // implementation of the IvyPostResolveTask API
+
+    public void doExecute() throws BuildException {
+        // TODO : maybe there is a way to implement it ?
+        throw new BuildException("ivy:resources should not be used as a Ant Task");
+    }
+
+}
diff --git a/src/java/org/apache/ivy/ant/IvyRetrieve.java b/src/java/org/apache/ivy/ant/IvyRetrieve.java
new file mode 100644
index 0000000..d333cd8
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/IvyRetrieve.java
@@ -0,0 +1,234 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.apache.ivy.core.LogOptions;
+import org.apache.ivy.core.retrieve.RetrieveOptions;
+import org.apache.ivy.core.retrieve.RetrieveReport;
+import org.apache.ivy.util.filter.Filter;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.types.FileSet;
+import org.apache.tools.ant.types.Mapper;
+import org.apache.tools.ant.types.Path;
+import org.apache.tools.ant.types.PatternSet;
+import org.apache.tools.ant.util.FileNameMapper;
+
+/**
+ * This task allow to retrieve dependencies from the cache to a local directory like a lib dir.
+ */
+public class IvyRetrieve extends IvyPostResolveTask {
+
+    private static final Collection OVERWRITEMODE_VALUES = Arrays.asList(new String[] {
+            RetrieveOptions.OVERWRITEMODE_ALWAYS, RetrieveOptions.OVERWRITEMODE_NEVER,
+            RetrieveOptions.OVERWRITEMODE_NEWER, RetrieveOptions.OVERWRITEMODE_DIFFERENT});
+
+    private String pattern;
+
+    private String ivypattern = null;
+
+    private boolean sync = false;
+
+    private boolean symlink = false;
+
+    private boolean symlinkmass = false;
+
+    private String overwriteMode = RetrieveOptions.OVERWRITEMODE_NEWER;
+
+    private String pathId = null;
+
+    private String setId = null;
+
+    private Mapper mapper = null;
+
+    public String getPattern() {
+        return pattern;
+    }
+
+    public void setPattern(String pattern) {
+        this.pattern = pattern;
+    }
+
+    public String getPathId() {
+        return pathId;
+    }
+
+    public void setPathId(String pathId) {
+        this.pathId = pathId;
+    }
+
+    public String getSetId() {
+        return setId;
+    }
+
+    public void setSetId(String setId) {
+        this.setId = setId;
+    }
+
+    public void doExecute() throws BuildException {
+        prepareAndCheck();
+
+        if (!getAllowedLogOptions().contains(getLog())) {
+            throw new BuildException("invalid option for 'log': " + getLog()
+                    + ". Available options are " + getAllowedLogOptions());
+        }
+
+        pattern = getProperty(pattern, getSettings(), "ivy.retrieve.pattern");
+        try {
+            Filter artifactFilter = getArtifactFilter();
+            RetrieveReport report = getIvyInstance().retrieve(
+                getResolvedMrid(),
+                ((RetrieveOptions) new RetrieveOptions().setLog(getLog()))
+                        .setConfs(splitConfs(getConf())).setDestArtifactPattern(pattern)
+                        .setDestIvyPattern(ivypattern).setArtifactFilter(artifactFilter)
+                        .setSync(sync).setOverwriteMode(getOverwriteMode())
+                        .setUseOrigin(isUseOrigin()).setMakeSymlinks(symlink)
+                        .setMakeSymlinksInMass(symlinkmass).setResolveId(getResolveId())
+                        .setMapper(mapper == null ? null : new MapperAdapter(mapper)));
+
+            int targetsCopied = report.getNbrArtifactsCopied();
+            boolean haveTargetsBeenCopied = targetsCopied > 0;
+            getProject().setProperty("ivy.nb.targets.copied", String.valueOf(targetsCopied));
+            getProject().setProperty("ivy.targets.copied", String.valueOf(haveTargetsBeenCopied));
+
+            if (getPathId() != null) {
+                Path path = new Path(getProject());
+                getProject().addReference(getPathId(), path);
+
+                for (Iterator iter = report.getRetrievedFiles().iterator(); iter.hasNext();) {
+                    path.createPathElement().setLocation((File) iter.next());
+                }
+            }
+
+            if (getSetId() != null) {
+                FileSet fileset = new FileSet();
+                fileset.setProject(getProject());
+                getProject().addReference(getSetId(), fileset);
+
+                fileset.setDir(report.getRetrieveRoot());
+
+                for (Iterator iter = report.getRetrievedFiles().iterator(); iter.hasNext();) {
+                    PatternSet.NameEntry ne = fileset.createInclude();
+                    ne.setName(getPath(report.getRetrieveRoot(), (File) iter.next()));
+                }
+            }
+        } catch (Exception ex) {
+            throw new BuildException("impossible to ivy retrieve: " + ex, ex);
+        }
+    }
+
+    protected Collection/* <String> */getAllowedLogOptions() {
+        return Arrays.asList(new String[] {LogOptions.LOG_DEFAULT, LogOptions.LOG_DOWNLOAD_ONLY,
+                LogOptions.LOG_QUIET});
+    }
+
+    public String getIvypattern() {
+        return ivypattern;
+    }
+
+    public void setIvypattern(String ivypattern) {
+        this.ivypattern = ivypattern;
+    }
+
+    public boolean isSync() {
+        return sync;
+    }
+
+    public void setSync(boolean sync) {
+        this.sync = sync;
+    }
+
+    /**
+     * Option to create symlinks instead of copying.
+     */
+    public void setSymlink(boolean symlink) {
+        this.symlink = symlink;
+    }
+
+    /**
+     * Option to create symlinks in one mass action, instead of separately.
+     */
+    public void setSymlinkmass(boolean symlinkmass) {
+        this.symlinkmass = symlinkmass;
+    }
+
+    public void setOverwriteMode(String overwriteMode) {
+        if (!OVERWRITEMODE_VALUES.contains(overwriteMode)) {
+            throw new IllegalArgumentException("invalid overwriteMode value '" + overwriteMode
+                    + "'. " + "Valid values are " + OVERWRITEMODE_VALUES);
+        }
+        this.overwriteMode = overwriteMode;
+    }
+
+    public String getOverwriteMode() {
+        return overwriteMode;
+    }
+
+    /**
+     * Add a mapper to convert the file names.
+     * 
+     * @param mapper
+     *            a <code>Mapper</code> value.
+     */
+    public void addMapper(Mapper mapper) {
+        if (this.mapper != null) {
+            throw new IllegalArgumentException("Cannot define more than one mapper");
+        }
+        this.mapper = mapper;
+    }
+
+    /**
+     * Add a nested filenamemapper.
+     * 
+     * @param fileNameMapper
+     *            the mapper to add.
+     */
+    public void add(FileNameMapper fileNameMapper) {
+        Mapper m = new Mapper(getProject());
+        m.add(fileNameMapper);
+        addMapper(m);
+    }
+
+    /**
+     * Returns the path of the file relative to the given base directory.
+     * 
+     * @param base
+     *            the parent directory to which the file must be evaluated.
+     * @param file
+     *            the file for which the path should be returned
+     * @return the path of the file relative to the given base directory.
+     */
+    private String getPath(File base, File file) {
+        String absoluteBasePath = base.getAbsolutePath();
+
+        int beginIndex = absoluteBasePath.length();
+
+        // checks if the basePath ends with the file separator (which can for instance
+        // happen if the basePath is the root on unix)
+        if (!absoluteBasePath.endsWith(File.separator)) {
+            beginIndex++; // skip the seperator char as well
+        }
+
+        return file.getAbsolutePath().substring(beginIndex);
+    }
+
+}
diff --git a/src/java/org/apache/ivy/ant/IvyTask.java b/src/java/org/apache/ivy/ant/IvyTask.java
new file mode 100644
index 0000000..3533480
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/IvyTask.java
@@ -0,0 +1,297 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import java.util.Date;
+import java.util.Locale;
+
+import org.apache.ivy.Ivy;
+import org.apache.ivy.core.IvyContext;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.report.ResolveReport;
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.ivy.util.DateUtil;
+import org.apache.ivy.util.Message;
+import org.apache.ivy.util.StringUtils;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Task;
+import org.apache.tools.ant.types.Reference;
+
+/**
+ * Base class for all ivy ant tasks, deal particularly with ivy instance storage in ant project.
+ */
+public abstract class IvyTask extends Task {
+    public static final String ANT_PROJECT_CONTEXT_KEY = "ant-project";
+
+    private Boolean validate = null;
+
+    private Reference antIvyEngineRef = null;
+
+    protected boolean doValidate(IvySettings ivy) {
+        if (validate != null) {
+            return validate.booleanValue();
+        }
+        return ivy.doValidate();
+    }
+
+    public boolean isValidate() {
+        return validate == null ? true : validate.booleanValue();
+    }
+
+    public void setValidate(boolean validate) {
+        this.validate = Boolean.valueOf(validate);
+    }
+
+    public void setSettingsRef(Reference ref) {
+        antIvyEngineRef = ref;
+    }
+
+    public Reference getSettingsRef() {
+        return antIvyEngineRef;
+    }
+
+    protected IvySettings getSettings() {
+        return getIvyInstance().getSettings();
+    }
+
+    protected Ivy getIvyInstance() {
+        Object antIvyEngine;
+        if (antIvyEngineRef != null) {
+            antIvyEngine = antIvyEngineRef.getReferencedObject(getProject());
+            if (!antIvyEngine.getClass().getName().equals(IvyAntSettings.class.getName())) {
+                throw new BuildException(antIvyEngineRef.getRefId()
+                        + " doesn't reference an ivy:settings", getLocation());
+            }
+            if (!(antIvyEngine instanceof IvyAntSettings)) {
+                throw new BuildException(antIvyEngineRef.getRefId()
+                        + " has been defined in a different classloader.  "
+                        + "Please use the same loader when defining your task, or "
+                        + "redeclare your ivy:settings in this classloader", getLocation());
+            }
+        } else {
+            antIvyEngine = IvyAntSettings.getDefaultInstance(this);
+        }
+        Ivy ivy = ((IvyAntSettings) antIvyEngine).getConfiguredIvyInstance(this);
+        AntMessageLogger.register(this, ivy);
+        return ivy;
+    }
+
+    protected void setResolved(ResolveReport report, boolean keep) {
+        ModuleDescriptor md = report.getModuleDescriptor();
+        String[] confs = report.getConfigurations();
+        if (keep) {
+            getProject().addReference("ivy.resolved.report", report);
+            getProject().addReference("ivy.resolved.configurations.ref", confs);
+            getProject().addReference("ivy.resolved.descriptor", md);
+        }
+        String suffix = md.getModuleRevisionId().getModuleId().getOrganisation() + "."
+                + md.getModuleRevisionId().getModuleId().getName();
+        getProject().addReference("ivy.resolved.report." + suffix, report);
+        getProject().addReference("ivy.resolved.descriptor." + suffix, md);
+        getProject().addReference("ivy.resolved.configurations.ref." + suffix, confs);
+    }
+
+    protected void setResolved(ResolveReport report, String resolveId, boolean keep) {
+        setResolved(report, keep);
+        if (resolveId != null) {
+            ModuleDescriptor md = report.getModuleDescriptor();
+            String[] confs = report.getConfigurations();
+            getProject().addReference("ivy.resolved.report." + resolveId, report);
+            getProject().addReference("ivy.resolved.descriptor." + resolveId, md);
+            getProject().addReference("ivy.resolved.configurations.ref." + resolveId, confs);
+        }
+    }
+
+    protected String[] getResolvedConfigurations(String org, String module, boolean strict) {
+        return (String[]) getReference("ivy.resolved.configurations.ref", org, module, strict);
+    }
+
+    protected Object getResolvedDescriptor(String resolveId) {
+        return getResolvedDescriptor(resolveId, true);
+    }
+
+    protected Object getResolvedDescriptor(String resolveId, boolean strict) {
+        Object result = getProject().getReference("ivy.resolved.descriptor." + resolveId);
+        if (strict && (result == null)) {
+            throw new BuildException("ModuleDescriptor for resolve with id '" + resolveId
+                    + "' not found.");
+        }
+        return result;
+    }
+
+    protected Object getResolvedDescriptor(String org, String module) {
+        return getResolvedDescriptor(org, module, false);
+    }
+
+    protected Object getResolvedDescriptor(String org, String module, boolean strict) {
+        return getReference("ivy.resolved.descriptor", org, module, strict);
+    }
+
+    private Object getReference(String prefix, String org, String module, boolean strict) {
+        Object reference = null;
+        if (org != null && module != null) {
+            reference = getProject().getReference(prefix + "." + org + "." + module);
+        }
+        if (!strict && reference == null) {
+            reference = getProject().getReference(prefix);
+        }
+        return reference;
+    }
+
+    protected ResolveReport getResolvedReport(String org, String module, String resolveId) {
+        ResolveReport result = null;
+
+        if (resolveId == null) {
+            result = (ResolveReport) getReference("ivy.resolved.report", org, module, false);
+        } else {
+            result = (ResolveReport) getReference("ivy.resolved.report." + resolveId, null, null,
+                false);
+        }
+
+        return result;
+    }
+
+    protected String[] splitConfs(String conf) {
+        if (conf == null) {
+            return null;
+        }
+        String[] confs = conf.split(",");
+        for (int i = 0; i < confs.length; i++) {
+            confs[i] = confs[i].trim();
+        }
+        return confs;
+    }
+
+    protected String mergeConfs(String[] conf) {
+        return StringUtils.join(conf, ", ");
+    }
+
+    protected static Date getPubDate(String date, Date def) {
+        if (date != null) {
+            if ("now".equals(date.toLowerCase(Locale.US))) {
+                return new Date();
+            }
+            try {
+                return DateUtil.parse(date);
+            } catch (Exception ex) {
+                throw new BuildException("Publication date provided in bad format. Should be '"
+                        + DateUtil.DATE_FORMAT_PATTERN + "' and not '" + date + "'!");
+            }
+        } else {
+            return def;
+        }
+    }
+
+    protected String getProperty(String value, IvySettings ivy, String name) {
+        if (value == null) {
+            return getProperty(ivy, name);
+        } else {
+            value = ivy.substitute(value);
+            Message.debug("parameter found as attribute value: " + name + "=" + value);
+            return value;
+        }
+    }
+
+    protected String getProperty(String value, IvySettings ivy, String name, String resolveId) {
+        if (resolveId == null) {
+            return getProperty(value, ivy, name);
+        } else {
+            return getProperty(value, ivy, name + "." + resolveId);
+        }
+    }
+
+    protected String getProperty(IvySettings ivy, String name, String resolveId) {
+        if (resolveId == null) {
+            return getProperty(ivy, name);
+        } else {
+            return getProperty(ivy, name + "." + resolveId);
+        }
+    }
+
+    protected String getProperty(IvySettings ivy, String name) {
+        String val = ivy.getVariable(name);
+        if (val == null) {
+            val = ivy.substitute(getProject().getProperty(name));
+            if (val != null) {
+                Message.debug("parameter found as ant project property: " + name + "=" + val);
+            } else {
+                Message.debug("parameter not found: " + name);
+            }
+        } else {
+            val = ivy.substitute(val);
+            Message.debug("parameter found as ivy variable: " + name + "=" + val);
+        }
+        return val;
+    }
+
+    /**
+     * Called when task starts its execution.
+     */
+    protected void prepareTask() {
+        getProject().setProperty("ivy.version", Ivy.getIvyVersion());
+
+        // push current project and Ivy on the stack in context
+        IvyContext.pushNewCopyContext();
+        IvyContext.getContext().setIvy(getIvyInstance());
+        IvyContext.getContext().push(ANT_PROJECT_CONTEXT_KEY, getProject());
+    }
+
+    /**
+     * Called when task is about to finish Should clean up all state related information (stacks for
+     * example)
+     */
+    protected void finalizeTask() {
+        if (!IvyContext.getContext().pop(ANT_PROJECT_CONTEXT_KEY, getProject())) {
+            Message.error("ANT project poped from stack not equals current !. Ignoring");
+        }
+        IvyContext.popContext();
+    }
+
+    /**
+     * Ant task execute. Calls prepareTask, doExecute, finalzeTask
+     */
+    public final void execute() throws BuildException {
+        try {
+            prepareTask();
+            doExecute();
+        } finally {
+            finalizeTask();
+        }
+    }
+
+    /**
+     * The real logic of task execution after project has been set in the context. MUST be
+     * implemented by subclasses
+     * 
+     * @throws BuildException
+     */
+    public abstract void doExecute() throws BuildException;
+
+    public String toString() {
+        return getClass().getName() + ":" + getTaskName();
+    }
+
+    /**
+     * Informs the user that the cache attribute is not supported any more.
+     */
+    protected void cacheAttributeNotSupported() {
+        throw new BuildException(
+                "cache attribute is not supported any more. See IVY-685 for details.");
+    }
+
+}
diff --git a/src/java/org/apache/ivy/ant/IvyVar.java b/src/java/org/apache/ivy/ant/IvyVar.java
new file mode 100644
index 0000000..cd8d670
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/IvyVar.java
@@ -0,0 +1,132 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Iterator;
+import java.util.Properties;
+
+import org.apache.ivy.Ivy;
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.tools.ant.BuildException;
+
+/**
+ * This task let user set ivy variables from ant.
+ */
+public class IvyVar extends IvyTask {
+    private String name;
+
+    private String value;
+
+    private File file;
+
+    private String url;
+
+    private String prefix;
+
+    public File getFile() {
+        return file;
+    }
+
+    public void setFile(File aFile) {
+        file = aFile;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String aName) {
+        name = aName;
+    }
+
+    public String getPrefix() {
+        return prefix;
+    }
+
+    public void setPrefix(String aPrefix) {
+        prefix = aPrefix;
+    }
+
+    public String getUrl() {
+        return url;
+    }
+
+    public void setUrl(String aUrl) {
+        url = aUrl;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String aValue) {
+        value = aValue;
+    }
+
+    public void doExecute() throws BuildException {
+        Ivy ivy = getIvyInstance();
+        IvySettings settings = ivy.getSettings();
+        if (getName() != null) {
+            settings.setVariable(getVarName(getName()), getValue());
+        } else {
+            Properties props = new Properties();
+            InputStream is = null;
+            try {
+                if (getFile() != null) {
+                    is = new FileInputStream(getFile());
+                } else if (getUrl() != null) {
+                    is = new URL(getUrl()).openStream();
+                } else {
+                    throw new BuildException("specify either name or file or url to ivy var task");
+                }
+                props.load(is);
+            } catch (Exception ex) {
+                throw new BuildException("impossible to load variables from file: " + ex, ex);
+            } finally {
+                if (is != null) {
+                    try {
+                        is.close();
+                    } catch (Exception e) {
+                        // ignore
+                    }
+                }
+            }
+            for (Iterator iter = props.keySet().iterator(); iter.hasNext();) {
+                String name = (String) iter.next();
+                String value = (String) props.get(name);
+                settings.setVariable(getVarName(name), value);
+            }
+        }
+    }
+
+    private String getVarName(String name) {
+        String prefix = getPrefix();
+        if (prefix != null) {
+            if (prefix.endsWith(".")) {
+                return prefix + name;
+            } else {
+                return prefix + "." + name;
+            }
+        }
+        return name;
+    }
+}
diff --git a/src/java/org/apache/ivy/ant/MapperAdapter.java b/src/java/org/apache/ivy/ant/MapperAdapter.java
new file mode 100644
index 0000000..a9ceab7
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/MapperAdapter.java
@@ -0,0 +1,42 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import org.apache.ivy.core.retrieve.FileNameMapper;
+import org.apache.tools.ant.types.Mapper;
+
+class MapperAdapter implements FileNameMapper {
+
+    private static final String[] EMPTY = new String[0];
+
+    private Mapper mapper;
+
+    MapperAdapter(Mapper mapper) {
+        this.mapper = mapper;
+    }
+
+    public String[] mapFileName(String fileName) {
+        String[] result = mapper.getImplementation().mapFileName(fileName);
+        if (result == null) {
+            result = EMPTY;
+        }
+
+        return result;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/ant/PackageMapping.java b/src/java/org/apache/ivy/ant/PackageMapping.java
new file mode 100644
index 0000000..c848057
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/PackageMapping.java
@@ -0,0 +1,69 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+
+/**
+ * Describes a mapping between a package name and an org name revision uple
+ */
+public class PackageMapping {
+    private String pkg;
+
+    private String organisation;
+
+    private String module;
+
+    private String revision;
+
+    public String getModule() {
+        return module;
+    }
+
+    public void setModule(String module) {
+        this.module = module;
+    }
+
+    public String getOrganisation() {
+        return organisation;
+    }
+
+    public void setOrganisation(String organisation) {
+        this.organisation = organisation;
+    }
+
+    public String getRevision() {
+        return revision;
+    }
+
+    public void setRevision(String revision) {
+        this.revision = revision;
+    }
+
+    public String getPackage() {
+        return pkg;
+    }
+
+    public void setPackage(String package1) {
+        pkg = package1;
+    }
+
+    public ModuleRevisionId getModuleRevisionId() {
+        return ModuleRevisionId.newInstance(organisation, module, revision);
+    }
+}
diff --git a/src/java/org/apache/ivy/ant/antlib.xml b/src/java/org/apache/ivy/ant/antlib.xml
new file mode 100644
index 0000000..ac32740
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/antlib.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<antlib xmlns:current="ant:current">
+    <typedef name="settings" classname="org.apache.ivy.ant.IvyAntSettings"/>
+    <typedef name="resources" classname="org.apache.ivy.ant.IvyResources" onerror="report"/>
+	<taskdef name="configure" classname="org.apache.ivy.ant.IvyConfigure"/>
+	<taskdef name="resolve" classname="org.apache.ivy.ant.IvyResolve"/>
+	<taskdef name="retrieve" classname="org.apache.ivy.ant.IvyRetrieve"/>
+	<taskdef name="deliver" classname="org.apache.ivy.ant.IvyDeliver"/>
+	<taskdef name="publish" classname="org.apache.ivy.ant.IvyPublish"/>
+	<taskdef name="extract" classname="org.apache.ivy.ant.IvyExtractFromSources"/>
+	<taskdef name="cachepath" classname="org.apache.ivy.ant.IvyCachePath"/>
+	<taskdef name="cachefileset" classname="org.apache.ivy.ant.IvyCacheFileset"/>
+	<taskdef name="report" classname="org.apache.ivy.ant.IvyReport"/>
+	<taskdef name="repreport" classname="org.apache.ivy.ant.IvyRepositoryReport"/>
+	<taskdef name="var" classname="org.apache.ivy.ant.IvyVar"/>
+	<taskdef name="check" classname="org.apache.ivy.ant.IvyCheck"/>
+	<taskdef name="artifactproperty" classname="org.apache.ivy.ant.IvyArtifactProperty"/>
+	<taskdef name="buildlist" classname="org.apache.ivy.ant.IvyBuildList"/>
+	<taskdef name="install" classname="org.apache.ivy.ant.IvyInstall"/>
+	<taskdef name="convertpom" classname="org.apache.ivy.ant.IvyConvertPom"/>
+	<taskdef name="makepom" classname="org.apache.ivy.ant.IvyMakePom"/>
+	<taskdef name="artifactreport" classname="org.apache.ivy.ant.IvyArtifactReport"/>
+	<taskdef name="info" classname="org.apache.ivy.ant.IvyInfo"/>
+	<taskdef name="addpath" classname="org.apache.ivy.ant.AddPathTask"/>
+	<taskdef name="listmodules" classname="org.apache.ivy.ant.IvyListModules"/>
+	<taskdef name="findrevision" classname="org.apache.ivy.ant.IvyFindRevision"/>
+	<taskdef name="buildnumber" classname="org.apache.ivy.ant.IvyBuildNumber"/>
+	<taskdef name="cleancache" classname="org.apache.ivy.ant.IvyCleanCache"/>
+    <taskdef name="buildobr" classname="org.apache.ivy.ant.BuildOBRTask" />
+    <taskdef name="convertmanifest" classname="org.apache.ivy.ant.ConvertManifestTask" />
+    <taskdef name="fixdeps" classname="org.apache.ivy.ant.FixDepsTask" />
+    <taskdef name="dependencytree" classname="org.apache.ivy.ant.IvyDependencyTree"/>
+    <taskdef name="checkdepsupdate" classname="org.apache.ivy.ant.IvyDependencyUpdateChecker"/>
+</antlib>
diff --git a/src/java/org/apache/ivy/core/ExecutionRelativeUrlResolver.java b/src/java/org/apache/ivy/core/ExecutionRelativeUrlResolver.java
new file mode 100644
index 0000000..db972cb
--- /dev/null
+++ b/src/java/org/apache/ivy/core/ExecutionRelativeUrlResolver.java
@@ -0,0 +1,34 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+/**
+ * Resolve relative url relatively to the current execution directory instead of relatively to the
+ * context. This is was actually done prior 2.0. This class allow thus to work in a backward
+ * compatible mode.
+ */
+public class ExecutionRelativeUrlResolver extends RelativeUrlResolver {
+
+    public URL getURL(URL context, String url) throws MalformedURLException {
+        return new URL(url);
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/IvyContext.java b/src/java/org/apache/ivy/core/IvyContext.java
new file mode 100644
index 0000000..1e28a4b
--- /dev/null
+++ b/src/java/org/apache/ivy/core/IvyContext.java
@@ -0,0 +1,386 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core;
+
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Stack;
+
+import org.apache.ivy.Ivy;
+import org.apache.ivy.core.event.EventManager;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.resolve.ResolveData;
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.ivy.plugins.circular.CircularDependencyStrategy;
+import org.apache.ivy.util.Message;
+import org.apache.ivy.util.MessageLogger;
+
+/**
+ * This class represents an execution context of an Ivy action. It contains several getters to
+ * retrieve information, like the used Ivy instance, the cache location...
+ * 
+ * @see IvyThread
+ */
+public class IvyContext {
+
+    private static ThreadLocal/* <Stack<IvyContext>> */current = new ThreadLocal();
+
+    private Ivy defaultIvy;
+
+    private WeakReference/* <Ivy> */ivy = new WeakReference(null);
+
+    private Map contextMap = new HashMap();
+
+    private Thread operatingThread;
+
+    private ResolveData resolveData;
+
+    private DependencyDescriptor dd;
+
+    public IvyContext() {
+    }
+
+    public IvyContext(IvyContext ctx) {
+        defaultIvy = ctx.defaultIvy;
+        ivy = ctx.ivy;
+        contextMap = new HashMap(ctx.contextMap);
+        operatingThread = ctx.operatingThread;
+        resolveData = ctx.resolveData;
+        dd = ctx.dd;
+    }
+
+    public static IvyContext getContext() {
+        Stack cur = getCurrentStack();
+        if (cur.isEmpty()) {
+            cur.push(new IvyContext());
+        }
+        return (IvyContext) cur.peek();
+    }
+
+    private static Stack/* <IvyContext> */getCurrentStack() {
+        Stack cur = (Stack) current.get();
+        if (cur == null) {
+            cur = new Stack();
+            current.set(cur);
+        }
+        return cur;
+    }
+
+    /**
+     * Creates a new IvyContext and pushes it as the current context in the current thread.
+     * <p>
+     * {@link #popContext()} should usually be called when the job for which this context has been
+     * pushed is finished.
+     * </p>
+     * 
+     * @return the newly pushed context
+     */
+    public static IvyContext pushNewContext() {
+        return pushContext(new IvyContext());
+    }
+
+    /**
+     * Creates a new IvyContext as a copy of the current one and pushes it as the current context in
+     * the current thread.
+     * <p>
+     * {@link #popContext()} should usually be called when the job for which this context has been
+     * pushed is finished.
+     * </p>
+     * 
+     * @return the newly pushed context
+     */
+    public static IvyContext pushNewCopyContext() {
+        return pushContext(new IvyContext(getContext()));
+    }
+
+    /**
+     * Changes the context associated with this thread. This is especially useful when launching a
+     * new thread, to associate it with the same context as the initial one. Do not forget to call
+     * {@link #popContext()} when done.
+     * 
+     * @param context
+     *            the new context to use in this thread.
+     * @return the pushed context
+     */
+    public static IvyContext pushContext(IvyContext context) {
+        getCurrentStack().push(context);
+        return context;
+    }
+
+    /**
+     * Pops one context used with this thread. This is usually called after having finished a task
+     * for which a call to {@link #pushNewContext()} or {@link #pushContext(IvyContext)} was done
+     * prior to beginning the task.
+     * 
+     * @return the popped context
+     */
+    public static IvyContext popContext() {
+        return (IvyContext) getCurrentStack().pop();
+    }
+
+    /**
+     * Reads the first object from the list saved under given key in the first context from the
+     * context stack in which this key is defined. If value under key in any of the contexts form
+     * the stack represents non List object then a RuntimeException is thrown.
+     * <p>
+     * This methods does a similar job to {@link #peek(String)}, except that it considers the whole
+     * context stack and not only one instance.
+     * </p>
+     * 
+     * @param key
+     *            context key for the string
+     * @return top object from the list (index 0) of the first context in the stack containing this
+     *         key or null if no key or list empty in all contexts from the context stack
+     * @see #peek(String)
+     */
+    public static Object peekInContextStack(String key) {
+        Object value = null;
+        Stack contextStack = getCurrentStack();
+        for (int i = contextStack.size() - 1; i >= 0 && value == null; i--) {
+            IvyContext ctx = (IvyContext) contextStack.get(i);
+            value = ctx.peek(key);
+        }
+        return value;
+    }
+
+    /**
+     * Returns the current ivy instance.
+     * <p>
+     * When calling any public ivy method on an ivy instance, a reference to this instance is put in
+     * this context, and thus accessible using this method, until no code reference this instance
+     * and the garbage collector collects it.
+     * </p>
+     * <p>
+     * Then, or if no ivy method has been called, a default ivy instance is returned by this method,
+     * so that it never returns <code>null</code>.
+     * </p>
+     * 
+     * @return the current ivy instance
+     */
+    public Ivy getIvy() {
+        Ivy ivy = peekIvy();
+        return ivy == null ? getDefaultIvy() : ivy;
+    }
+
+    /**
+     * Returns the Ivy instance associated with this context, or <code>null</code> if no such
+     * instance is currently associated with this context.
+     * <p>
+     * If you want get a default Ivy instance in case no instance if currently associated, use
+     * {@link #getIvy()}.
+     * </p>
+     * 
+     * @return the current ivy instance, or <code>null</code> if there is no current ivy instance.
+     */
+    public Ivy peekIvy() {
+        Ivy ivy = (Ivy) this.ivy.get();
+        return ivy;
+    }
+
+    private Ivy getDefaultIvy() {
+        if (defaultIvy == null) {
+            defaultIvy = Ivy.newInstance();
+            try {
+                defaultIvy.configureDefault();
+            } catch (Exception e) {
+                Message.debug(e);
+                // ???
+            }
+        }
+        return defaultIvy;
+    }
+
+    public void setIvy(Ivy ivy) {
+        this.ivy = new WeakReference(ivy);
+        operatingThread = Thread.currentThread();
+    }
+
+    public IvySettings getSettings() {
+        return getIvy().getSettings();
+    }
+
+    public CircularDependencyStrategy getCircularDependencyStrategy() {
+        return getSettings().getCircularDependencyStrategy();
+    }
+
+    public Object get(String key) {
+        WeakReference ref = (WeakReference) contextMap.get(key);
+        return ref == null ? null : ref.get();
+    }
+
+    public void set(String key, Object value) {
+        contextMap.put(key, new WeakReference(value));
+    }
+
+    /**
+     * Reads the first object from the list saved under given key in the context. If value under key
+     * represents non List object then a RuntimeException is thrown.
+     * 
+     * @param key
+     *            context key for the string
+     * @return top object from the list (index 0) or null if no key or list empty
+     */
+    public Object peek(String key) {
+        synchronized (contextMap) {
+            Object o = contextMap.get(key);
+            if (o == null) {
+                return null;
+            }
+            if (o instanceof List) {
+                if (((List) o).size() == 0) {
+                    return null;
+                }
+                Object ret = ((List) o).get(0);
+                return ret;
+            } else {
+                throw new RuntimeException("Cannot top from non List object " + o);
+            }
+        }
+    }
+
+    /**
+     * Removes and returns first object from the list saved under given key in the context. If value
+     * under key represents non List object then a RuntimeException is thrown.
+     * 
+     * @param key
+     *            context key for the string
+     * @return top object from the list (index 0) or null if no key or list empty
+     */
+    public Object pop(String key) {
+        synchronized (contextMap) {
+            Object o = contextMap.get(key);
+            if (o == null) {
+                return null;
+            }
+            if (o instanceof List) {
+                if (((List) o).size() == 0) {
+                    return null;
+                }
+                Object ret = ((List) o).remove(0);
+                return ret;
+            } else {
+                throw new RuntimeException("Cannot pop from non List object " + o);
+            }
+        }
+    }
+
+    /**
+     * Removes and returns first object from the list saved under given key in the context but only
+     * if it equals the given expectedValue - if not a false value is returned. If value under key
+     * represents non List object then a RuntimeException is thrown.
+     * 
+     * @param key
+     *            context key for the string
+     * @return true if the r
+     */
+    public boolean pop(String key, Object expectedValue) {
+        synchronized (contextMap) {
+            Object o = contextMap.get(key);
+            if (o == null) {
+                return false;
+            }
+            if (o instanceof List) {
+                if (((List) o).size() == 0) {
+                    return false;
+                }
+                Object top = ((List) o).get(0);
+                if (!top.equals(expectedValue)) {
+                    return false;
+                }
+                ((List) o).remove(0);
+                return true;
+            } else {
+                throw new RuntimeException("Cannot pop from non List object " + o);
+            }
+        }
+    }
+
+    /**
+     * Puts a new object at the start of the list saved under given key in the context. If value
+     * under key represents non List object then a RuntimeException is thrown. If no list exists
+     * under given key a new LinkedList is created. This is kept without WeakReference in opposite
+     * to the put() results.
+     * 
+     * @param key
+     *            key context key for the string
+     * @param value
+     *            value to be saved under the key
+     */
+    public void push(String key, Object value) {
+        synchronized (contextMap) {
+            if (!contextMap.containsKey(key)) {
+                contextMap.put(key, new LinkedList());
+            }
+            Object o = contextMap.get(key);
+            if (o instanceof List) {
+                ((List) o).add(0, value);
+            } else {
+                throw new RuntimeException("Cannot push to non List object " + o);
+            }
+        }
+    }
+
+    public Thread getOperatingThread() {
+        return operatingThread;
+    }
+
+    public MessageLogger getMessageLogger() {
+        // calling getIvy() instead of peekIvy() is not possible here: it will initialize a default
+        // Ivy instance, with default settings, but settings themselves may log messages and lead to
+        // a call to this method. So we use the current Ivy instance if any, or the default Ivy
+        // instance, or the default MessageLogger.
+        Ivy ivy = peekIvy();
+        if (ivy == null) {
+            if (defaultIvy == null) {
+                return Message.getDefaultLogger();
+            } else {
+                return defaultIvy.getLoggerEngine();
+            }
+        } else {
+            return ivy.getLoggerEngine();
+        }
+    }
+
+    public EventManager getEventManager() {
+        return getIvy().getEventManager();
+    }
+
+    public void checkInterrupted() {
+        getIvy().checkInterrupted();
+    }
+
+    public void setResolveData(ResolveData data) {
+        this.resolveData = data;
+    }
+
+    public ResolveData getResolveData() {
+        return resolveData;
+    }
+
+    public void setDependencyDescriptor(DependencyDescriptor dd) {
+        this.dd = dd;
+    }
+
+    public DependencyDescriptor getDependencyDescriptor() {
+        return dd;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/IvyPatternHelper.java b/src/java/org/apache/ivy/core/IvyPatternHelper.java
new file mode 100644
index 0000000..3300b36
--- /dev/null
+++ b/src/java/org/apache/ivy/core/IvyPatternHelper.java
@@ -0,0 +1,526 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Stack;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.ivy.core.cache.ArtifactOrigin;
+import org.apache.ivy.core.cache.RepositoryCacheManager;
+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.ivy.core.settings.IvyVariableContainer;
+import org.apache.ivy.core.settings.IvyVariableContainerImpl;
+import org.apache.ivy.util.Message;
+
+/**
+ */
+public final class IvyPatternHelper {
+
+    private IvyPatternHelper() {
+        // Helper class
+    }
+
+    public static final String CONF_KEY = "conf";
+
+    public static final String TYPE_KEY = "type";
+
+    public static final String EXT_KEY = "ext";
+
+    public static final String ARTIFACT_KEY = "artifact";
+
+    public static final String BRANCH_KEY = "branch";
+
+    public static final String REVISION_KEY = "revision";
+
+    public static final String MODULE_KEY = "module";
+
+    public static final String ORGANISATION_KEY = "organisation";
+
+    public static final String ORGANISATION_KEY2 = "organization";
+
+    public static final String ORGANISATION_PATH_KEY = "orgPath";
+
+    public static final String ORIGINAL_ARTIFACTNAME_KEY = "originalname";
+
+    private static final Pattern PARAM_PATTERN = Pattern.compile("\\@\\{(.*?)\\}");
+
+    private static final Pattern VAR_PATTERN = Pattern.compile("\\$\\{(.*?)\\}");
+
+    public static String substitute(String pattern, ModuleRevisionId moduleRevision) {
+        return substitute(pattern, moduleRevision.getOrganisation(), moduleRevision.getName(),
+            moduleRevision.getBranch(), moduleRevision.getRevision(), "ivy", "ivy", "xml", null,
+            null, moduleRevision.getQualifiedExtraAttributes(), null);
+    }
+
+    public static String substitute(String pattern, ModuleRevisionId moduleRevision,
+            String artifact, String type, String ext) {
+        return substitute(pattern, moduleRevision, new DefaultArtifact(moduleRevision, null,
+                artifact, type, ext));
+    }
+
+    public static String substitute(String pattern, Artifact artifact) {
+        return substitute(pattern, artifact, (String) null);
+    }
+
+    public static String substitute(String pattern, Artifact artifact, ArtifactOrigin origin) {
+        return substitute(pattern, artifact.getModuleRevisionId(), artifact, (String) null, origin);
+    }
+
+    public static String substitute(String pattern, Artifact artifact, String conf) {
+        return substitute(pattern, artifact.getModuleRevisionId(), artifact, conf,
+            (ArtifactOrigin) null);
+    }
+
+    public static String substitute(String pattern, ModuleRevisionId mrid, Artifact artifact) {
+        return substitute(pattern, mrid, artifact, (String) null, (ArtifactOrigin) null);
+    }
+
+    public static String substitute(String pattern, ModuleRevisionId mrid, Artifact artifact,
+            String conf, ArtifactOrigin origin) {
+        return substitute(pattern, mrid.getOrganisation(), mrid.getName(), mrid.getBranch(),
+            mrid.getRevision(), artifact.getName(), artifact.getType(), artifact.getExt(), conf,
+            origin, mrid.getQualifiedExtraAttributes(), artifact.getQualifiedExtraAttributes());
+    }
+
+    public static String substitute(String pattern, String org, String module, String revision,
+            String artifact, String type, String ext) {
+        return substitute(pattern, org, module, (String) null, revision, artifact, type, ext,
+            (String) null, (ArtifactOrigin) null, (Map) null, (Map) null);
+    }
+
+    // CheckStyle:ParameterNumber OFF
+    public static String substitute(String pattern, String org, String module, String revision,
+            String artifact, String type, String ext, String conf) {
+        return substitute(pattern, org, module, (String) null, revision, artifact, type, ext, conf,
+            (ArtifactOrigin) null, (Map) null, (Map) null);
+    }
+
+    public static String substitute(String pattern, String org, String module, String revision,
+            String artifact, String type, String ext, String conf, Map extraModuleAttributes,
+            Map extraArtifactAttributes) {
+        return substitute(pattern, org, module, (String) null, revision, artifact, type, ext, conf,
+            (ArtifactOrigin) null, extraModuleAttributes, extraArtifactAttributes);
+    }
+
+    public static String substitute(String pattern, String org, String module, String branch,
+            String revision, String artifact, String type, String ext, String conf,
+            ArtifactOrigin origin, Map extraModuleAttributes, Map extraArtifactAttributes) {
+        Map tokens = new HashMap();
+        if (extraModuleAttributes != null) {
+            for (Iterator entries = extraModuleAttributes.entrySet().iterator(); entries.hasNext();) {
+                Map.Entry entry = (Map.Entry) entries.next();
+                String token = (String) entry.getKey();
+                if (token.indexOf(':') > 0) {
+                    token = token.substring(token.indexOf(':') + 1);
+                }
+                tokens.put(token, entry.getValue());
+            }
+        }
+        if (extraArtifactAttributes != null) {
+            for (Iterator entries = extraArtifactAttributes.entrySet().iterator(); entries
+                    .hasNext();) {
+                Map.Entry entry = (Map.Entry) entries.next();
+                String token = (String) entry.getKey();
+                if (token.indexOf(':') > 0) {
+                    token = token.substring(token.indexOf(':') + 1);
+                }
+                tokens.put(token, entry.getValue());
+            }
+        }
+        tokens.put(ORGANISATION_KEY, org == null ? "" : org);
+        tokens.put(ORGANISATION_KEY2, org == null ? "" : org);
+        tokens.put(ORGANISATION_PATH_KEY, org == null ? "" : org.replace('.', '/'));
+        tokens.put(MODULE_KEY, module == null ? "" : module);
+        tokens.put(BRANCH_KEY, branch == null ? "" : branch);
+        tokens.put(REVISION_KEY, revision == null ? "" : revision);
+        tokens.put(ARTIFACT_KEY, artifact == null ? module : artifact);
+        tokens.put(TYPE_KEY, type == null ? "jar" : type);
+        tokens.put(EXT_KEY, ext == null ? "jar" : ext);
+        tokens.put(CONF_KEY, conf == null ? "default" : conf);
+        if (origin == null) {
+            tokens.put(ORIGINAL_ARTIFACTNAME_KEY, new OriginalArtifactNameValue(org, module,
+                    branch, revision, artifact, type, ext, extraModuleAttributes,
+                    extraArtifactAttributes));
+        } else {
+            tokens.put(ORIGINAL_ARTIFACTNAME_KEY, new OriginalArtifactNameValue(origin));
+        }
+
+        return substituteTokens(pattern, tokens);
+    }
+
+    // CheckStyle:ParameterNumber ON
+
+    public static String substituteVariables(String pattern, Map variables) {
+        return substituteVariables(pattern, new IvyVariableContainerImpl(variables), new Stack());
+    }
+
+    public static String substituteVariables(String pattern, IvyVariableContainer variables) {
+        return substituteVariables(pattern, variables, new Stack());
+    }
+
+    private static String substituteVariables(String pattern, IvyVariableContainer variables,
+            Stack substituting) {
+        // if you supply null, null is what you get
+        if (pattern == null) {
+            return null;
+        }
+
+        Matcher m = VAR_PATTERN.matcher(pattern);
+
+        boolean useVariables = false;
+        StringBuffer sb = null;
+        while (m.find()) {
+            if (!useVariables) {
+                useVariables = true;
+                sb = new StringBuffer();
+            }
+            String var = m.group(1);
+            String val = variables.getVariable(var);
+            if (val != null) {
+                int index = substituting.indexOf(var);
+                if (index != -1) {
+                    List cycle = new ArrayList(substituting.subList(index, substituting.size()));
+                    cycle.add(var);
+                    throw new IllegalArgumentException("cyclic variable definition: cycle = "
+                            + cycle);
+                }
+                substituting.push(var);
+                val = substituteVariables(val, variables, substituting);
+                substituting.pop();
+            } else {
+                val = m.group();
+            }
+            m.appendReplacement(sb, val.replaceAll("\\\\", "\\\\\\\\").replaceAll("\\$", "\\\\\\$"));
+        }
+        if (useVariables) {
+            m.appendTail(sb);
+            return sb.toString();
+        } else {
+            return pattern;
+        }
+    }
+
+    public static String substituteTokens(String pattern, Map tokens) {
+        Map tokensCopy = new HashMap(tokens);
+        if (tokensCopy.containsKey(ORGANISATION_KEY) && !tokensCopy.containsKey(ORGANISATION_KEY2)) {
+            tokensCopy.put(ORGANISATION_KEY2, tokensCopy.get(ORGANISATION_KEY));
+        }
+        if (tokensCopy.containsKey(ORGANISATION_KEY)
+                && !tokensCopy.containsKey(ORGANISATION_PATH_KEY)) {
+            String org = (String) tokensCopy.get(ORGANISATION_KEY);
+            tokensCopy.put(ORGANISATION_PATH_KEY, org == null ? "" : org.replace('.', '/'));
+        }
+
+        StringBuffer buffer = new StringBuffer();
+
+        char[] chars = pattern.toCharArray();
+
+        StringBuffer optionalPart = null;
+        StringBuffer tokenBuffer = null;
+        boolean insideOptionalPart = false;
+        boolean insideToken = false;
+        boolean tokenSeen = false;
+        boolean tokenHadValue = false;
+
+        for (int i = 0; i < chars.length; i++) {
+            switch (chars[i]) {
+                case '(':
+                    if (insideOptionalPart) {
+                        throw new IllegalArgumentException(
+                                "invalid start of optional part at position " + i + " in pattern "
+                                        + pattern);
+                    }
+
+                    optionalPart = new StringBuffer();
+                    insideOptionalPart = true;
+                    tokenSeen = false;
+                    tokenHadValue = false;
+                    break;
+
+                case ')':
+                    if (!insideOptionalPart || insideToken) {
+                        throw new IllegalArgumentException(
+                                "invalid end of optional part at position " + i + " in pattern "
+                                        + pattern);
+                    }
+
+                    if (tokenHadValue) {
+                        buffer.append(optionalPart.toString());
+                    } else if (!tokenSeen) {
+                        buffer.append('(').append(optionalPart.toString()).append(')');
+                    }
+
+                    insideOptionalPart = false;
+                    break;
+
+                case '[':
+                    if (insideToken) {
+                        throw new IllegalArgumentException("invalid start of token at position "
+                                + i + " in pattern " + pattern);
+                    }
+
+                    tokenBuffer = new StringBuffer();
+                    insideToken = true;
+                    break;
+
+                case ']':
+                    if (!insideToken) {
+                        throw new IllegalArgumentException("invalid end of token at position " + i
+                                + " in pattern " + pattern);
+                    }
+
+                    String token = tokenBuffer.toString();
+                    Object tokenValue = tokensCopy.get(token);
+                    String value = (tokenValue == null) ? null : tokenValue.toString();
+
+                    if (insideOptionalPart) {
+                        tokenHadValue = (value != null) && (value.length() > 0);
+                        optionalPart.append(value);
+                    } else {
+                        if (value == null) { // the token wasn't set, it's kept as is
+                            value = "[" + token + "]";
+                        }
+                        buffer.append(value);
+                    }
+
+                    insideToken = false;
+                    tokenSeen = true;
+                    break;
+
+                default:
+                    if (insideToken) {
+                        tokenBuffer.append(chars[i]);
+                    } else if (insideOptionalPart) {
+                        optionalPart.append(chars[i]);
+                    } else {
+                        buffer.append(chars[i]);
+                    }
+
+                    break;
+            }
+        }
+
+        if (insideToken) {
+            throw new IllegalArgumentException("last token hasn't been closed in pattern "
+                    + pattern);
+        }
+
+        if (insideOptionalPart) {
+            throw new IllegalArgumentException("optional part hasn't been closed in pattern "
+                    + pattern);
+        }
+
+        return buffer.toString();
+    }
+
+    public static String substituteVariable(String pattern, String variable, String value) {
+        StringBuffer buf = new StringBuffer(pattern);
+        substituteVariable(buf, variable, value);
+        return buf.toString();
+    }
+
+    public static void substituteVariable(StringBuffer buf, String variable, String value) {
+        String from = "${" + variable + "}";
+        int fromLength = from.length();
+        for (int index = buf.indexOf(from); index != -1; index = buf.indexOf(from, index)) {
+            buf.replace(index, index + fromLength, value);
+        }
+    }
+
+    public static String substituteToken(String pattern, String token, String value) {
+        StringBuffer buf = new StringBuffer(pattern);
+        substituteToken(buf, token, value);
+        return buf.toString();
+    }
+
+    public static void substituteToken(StringBuffer buf, String token, String value) {
+        String from = getTokenString(token);
+        int fromLength = from.length();
+        for (int index = buf.indexOf(from); index != -1; index = buf.indexOf(from, index)) {
+            buf.replace(index, index + fromLength, value);
+        }
+    }
+
+    public static String getTokenString(String token) {
+        return "[" + token + "]";
+    }
+
+    public static String substituteParams(String pattern, Map params) {
+        return substituteParams(pattern, new IvyVariableContainerImpl(params), new Stack());
+    }
+
+    private static String substituteParams(String pattern, IvyVariableContainer params,
+            Stack substituting) {
+        // TODO : refactor this with substituteVariables
+        // if you supply null, null is what you get
+        if (pattern == null) {
+            return null;
+        }
+
+        Matcher m = PARAM_PATTERN.matcher(pattern);
+
+        StringBuffer sb = new StringBuffer();
+        while (m.find()) {
+            String var = m.group(1);
+            String val = params.getVariable(var);
+            if (val != null) {
+                int index = substituting.indexOf(var);
+                if (index != -1) {
+                    List cycle = new ArrayList(substituting.subList(index, substituting.size()));
+                    cycle.add(var);
+                    throw new IllegalArgumentException("cyclic param definition: cycle = " + cycle);
+                }
+                substituting.push(var);
+                val = substituteVariables(val, params, substituting);
+                substituting.pop();
+            } else {
+                val = m.group();
+            }
+            m.appendReplacement(sb, val.replaceAll("\\\\", "\\\\\\\\").replaceAll("\\@", "\\\\\\@"));
+        }
+        m.appendTail(sb);
+
+        return sb.toString();
+    }
+
+    /**
+     * This class returns the original name of the artifact 'on demand'. This is done to avoid
+     * having to read the cached datafile containing the original location of the artifact if we
+     * don't need it.
+     */
+    private static class OriginalArtifactNameValue {
+        // module properties
+        private String org;
+
+        private String moduleName;
+
+        private String branch;
+
+        private String revision;
+
+        private Map extraModuleAttributes;
+
+        // artifact properties
+        private String artifactName;
+
+        private String artifactType;
+
+        private String artifactExt;
+
+        private Map extraArtifactAttributes;
+
+        // cached origin;
+        private ArtifactOrigin origin;
+
+        public OriginalArtifactNameValue(String org, String moduleName, String branch,
+                String revision, String artifactName, String artifactType, String artifactExt,
+                Map extraModuleAttributes, Map extraArtifactAttributes) {
+            this.org = org;
+            this.moduleName = moduleName;
+            this.branch = branch;
+            this.revision = revision;
+            this.artifactName = artifactName;
+            this.artifactType = artifactType;
+            this.artifactExt = artifactExt;
+            this.extraModuleAttributes = extraModuleAttributes;
+            this.extraArtifactAttributes = extraArtifactAttributes;
+        }
+
+        /**
+         * @param origin
+         */
+        public OriginalArtifactNameValue(ArtifactOrigin origin) {
+            this.origin = origin;
+        }
+
+        // Called by substituteTokens only if the original artifact name is needed
+        public String toString() {
+            if (origin == null) {
+                ModuleRevisionId revId = ModuleRevisionId.newInstance(org, moduleName, branch,
+                    revision, extraModuleAttributes);
+                Artifact artifact = new DefaultArtifact(revId, null, artifactName, artifactType,
+                        artifactExt, extraArtifactAttributes);
+
+                // TODO cache: see how we could know which actual cache manager to use, since this
+                // will fail when using a resolver in a chain with a specific cache manager
+                RepositoryCacheManager cacheManager = IvyContext.getContext().getSettings()
+                        .getResolver(revId).getRepositoryCacheManager();
+
+                origin = cacheManager.getSavedArtifactOrigin(artifact);
+
+                if (ArtifactOrigin.isUnknown(origin)) {
+                    Message.debug("no artifact origin found for " + artifact + " in "
+                            + cacheManager);
+                    return null;
+                }
+            }
+
+            if (ArtifactOrigin.isUnknown(origin)) {
+                return null;
+            }
+
+            // we assume that the original filename is the last part of the original file location
+            String location = origin.getLocation();
+            int lastPathIndex = location.lastIndexOf('/');
+            if (lastPathIndex == -1) {
+                lastPathIndex = location.lastIndexOf('\\');
+            }
+            int lastColonIndex = location.lastIndexOf('.');
+
+            return location.substring(lastPathIndex + 1, lastColonIndex);
+        }
+    }
+
+    public static String getTokenRoot(String pattern) {
+        int index = pattern.indexOf('[');
+        if (index == -1) {
+            return pattern;
+        } else {
+            // it could be that pattern is something like "lib/([optional]/)[module]"
+            // we don't want the '(' in the result
+            int optionalIndex = pattern.indexOf('(');
+            if (optionalIndex >= 0) {
+                index = Math.min(index, optionalIndex);
+            }
+            return pattern.substring(0, index);
+        }
+    }
+
+    public static String getFirstToken(String pattern) {
+        if (pattern == null) {
+            return null;
+        }
+        int startIndex = pattern.indexOf('[');
+        if (startIndex == -1) {
+            return null;
+        }
+        int endIndex = pattern.indexOf(']', startIndex);
+        if (endIndex == -1) {
+            return null;
+        }
+        return pattern.substring(startIndex + 1, endIndex);
+    }
+}
diff --git a/src/java/org/apache/ivy/core/IvyThread.java b/src/java/org/apache/ivy/core/IvyThread.java
new file mode 100644
index 0000000..b0d2a51
--- /dev/null
+++ b/src/java/org/apache/ivy/core/IvyThread.java
@@ -0,0 +1,70 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core;
+
+/**
+ * A simple thread subclass associated the same IvyContext as the thread in which it is
+ * instanciated. If you override the run target, then you will have to call initContext() to do the
+ * association with the original IvyContext.
+ * 
+ * @see IvyContext
+ */
+public class IvyThread extends Thread {
+    private IvyContext context = IvyContext.getContext();
+
+    public IvyThread() {
+        super();
+    }
+
+    public IvyThread(Runnable target, String name) {
+        super(target, name);
+    }
+
+    public IvyThread(Runnable target) {
+        super(target);
+    }
+
+    public IvyThread(String name) {
+        super(name);
+    }
+
+    public IvyThread(ThreadGroup group, Runnable target, String name, long stackSize) {
+        super(group, target, name, stackSize);
+    }
+
+    public IvyThread(ThreadGroup group, Runnable target, String name) {
+        super(group, target, name);
+    }
+
+    public IvyThread(ThreadGroup group, Runnable target) {
+        super(group, target);
+    }
+
+    public IvyThread(ThreadGroup group, String name) {
+        super(group, name);
+    }
+
+    public void run() {
+        initContext();
+        super.run();
+    }
+
+    protected void initContext() {
+        IvyContext.pushContext(context);
+    }
+}
diff --git a/src/java/org/apache/ivy/core/LogOptions.java b/src/java/org/apache/ivy/core/LogOptions.java
new file mode 100644
index 0000000..942f481
--- /dev/null
+++ b/src/java/org/apache/ivy/core/LogOptions.java
@@ -0,0 +1,57 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core;
+
+public class LogOptions {
+    /**
+     * Defaults log settings. Output all usual messages during the resolve process.
+     */
+    public static final String LOG_DEFAULT = "default";
+
+    /**
+     * This log setting disable all usual messages but download ones.
+     */
+    public static final String LOG_DOWNLOAD_ONLY = "download-only";
+
+    /**
+     * This log setting disable all usual messages during the resolve process.
+     */
+    public static final String LOG_QUIET = "quiet";
+
+    /**
+     * The log settings to use. One of {@link #LOG_DEFAULT}, {@link #LOG_DOWNLOAD_ONLY},
+     * {@link #LOG_QUIET}
+     */
+    private String log = LOG_DEFAULT;
+
+    public LogOptions() {
+    }
+
+    public LogOptions(LogOptions options) {
+        log = options.log;
+    }
+
+    public String getLog() {
+        return log;
+    }
+
+    public LogOptions setLog(String log) {
+        this.log = log;
+        return this;
+    }
+}
diff --git a/src/java/org/apache/ivy/core/NormalRelativeUrlResolver.java b/src/java/org/apache/ivy/core/NormalRelativeUrlResolver.java
new file mode 100644
index 0000000..691560a
--- /dev/null
+++ b/src/java/org/apache/ivy/core/NormalRelativeUrlResolver.java
@@ -0,0 +1,32 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+/**
+ * Normal implementation of RelativeUrlResolver.
+ */
+public class NormalRelativeUrlResolver extends RelativeUrlResolver {
+
+    public URL getURL(URL context, String url) throws MalformedURLException {
+        return new URL(context, url);
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/RelativeUrlResolver.java b/src/java/org/apache/ivy/core/RelativeUrlResolver.java
new file mode 100644
index 0000000..2fac8cb
--- /dev/null
+++ b/src/java/org/apache/ivy/core/RelativeUrlResolver.java
@@ -0,0 +1,67 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+/**
+ * Resolve an file or url relatively to its context.
+ */
+public abstract class RelativeUrlResolver {
+
+    /**
+     * Resolve the url in the context of context.
+     * 
+     * @param context
+     *            The URL of the ressource containing the reference url
+     * @param url
+     *            a relative or absolution url string
+     * @throws MalformedURLException
+     */
+    public abstract URL getURL(URL context, String url) throws MalformedURLException;
+
+    /**
+     * Relsovle file or url path relatively to a context. file is considered first. If file is not
+     * defined, url will be considered.
+     * 
+     * @param context
+     *            The URL of the ressource containing the reference file or url
+     * @param file
+     *            a relative or absolute path
+     * @param url
+     *            a relative or absolution url string
+     * @return the resulting url or null if faile and url are null.
+     * @throws MalformedURLException
+     */
+    public URL getURL(URL context, String file, String url) throws MalformedURLException {
+        if (file != null) {
+            File f = new File(file);
+            if (f.isAbsolute()) {
+                return f.toURI().toURL();
+            } else {
+                return getURL(context, file);
+            }
+        } else if (url != null) {
+            return getURL(context, url);
+        } else {
+            return null;
+        }
+    }
+}
diff --git a/src/java/org/apache/ivy/core/cache/ArtifactOrigin.java b/src/java/org/apache/ivy/core/cache/ArtifactOrigin.java
new file mode 100644
index 0000000..e132b2e
--- /dev/null
+++ b/src/java/org/apache/ivy/core/cache/ArtifactOrigin.java
@@ -0,0 +1,174 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.cache;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.util.Checks;
+
+/**
+ * This class contains information about the origin of an artifact.
+ * 
+ * @see org.apache.ivy.plugins.resolver.BasicResolver
+ * @see org.apache.ivy.plugins.resolver.util.ResolvedResource
+ */
+public class ArtifactOrigin {
+    private static final String UNKNOWN = "UNKNOWN";
+
+    /**
+     * ArtifactOrigin instance used when the origin is unknown.
+     */
+    public static final ArtifactOrigin unkwnown(Artifact artifact) {
+        return new ArtifactOrigin(artifact, false, UNKNOWN);
+    }
+
+    public static final boolean isUnknown(ArtifactOrigin artifact) {
+        return artifact == null || UNKNOWN.equals(artifact.getLocation());
+    }
+
+    public static final boolean isUnknown(String location) {
+        return location == null || UNKNOWN.equals(location);
+    }
+
+    private static final int MAGIC_HASH_VALUE = 31;
+
+    private boolean isLocal;
+
+    private String location;
+
+    private Artifact artifact;
+
+    private Long lastChecked;
+
+    private boolean exists = true;
+
+    /**
+     * Create a new instance
+     * 
+     * @param artifact
+     *            the artifact pointed by this location. Must not be <code>null</code>.
+     * @param isLocal
+     *            <code>boolean</code> value indicating if the resource is local (on the
+     *            filesystem).
+     * @param location
+     *            the location of the resource (normally a url). Must not be <code>null</code>.
+     */
+    public ArtifactOrigin(Artifact artifact, boolean isLocal, String location) {
+        Checks.checkNotNull(artifact, "artifact");
+        Checks.checkNotNull(location, "location");
+        this.artifact = artifact;
+        this.isLocal = isLocal;
+        this.location = location;
+    }
+
+    /**
+     * Is this resource local to this host, i.e. is it on the file system?
+     * 
+     * @return <code>boolean</code> value indicating if the resource is local.
+     */
+    public boolean isLocal() {
+        return isLocal;
+    }
+
+    /**
+     * Return the location of the resource (normally a url)
+     * 
+     * @return the location of the resource
+     */
+    public String getLocation() {
+        return location;
+    }
+
+    public void setLocation(String location) {
+        this.location = location;
+    }
+
+    /**
+     * Return the artifact that this location is pointing at.
+     * 
+     * @return the artifact that this location is pointing at.
+     */
+    public Artifact getArtifact() {
+        return artifact;
+    }
+
+    /**
+     * The last time the resource was checked to be up to date. Maybe <code>null</code> if this
+     * information is not actually used by in some case.
+     * 
+     * @return
+     */
+    public Long getLastChecked() {
+        return lastChecked;
+    }
+
+    public void setLastChecked(Long lastChecked) {
+        this.lastChecked = lastChecked;
+    }
+
+    public boolean isExists() {
+        return exists;
+    }
+
+    public void setExist(boolean exists) {
+        this.exists = exists;
+    }
+
+    public String toString() {
+        return "ArtifactOrigin { isLocal=" + isLocal + ", location=" + location + ", lastChecked="
+                + lastChecked + ", exists=" + exists + "}";
+    }
+
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        ArtifactOrigin that = (ArtifactOrigin) o;
+
+        if (isLocal != that.isLocal) {
+            return false;
+        }
+        if (!location.equals(that.location)) {
+            return false;
+        }
+        if (lastChecked == null) {
+            if (that.lastChecked != null) {
+                return false;
+            }
+        } else if (!lastChecked.equals(that.lastChecked)) {
+            return false;
+        }
+        if (exists != that.exists) {
+            return false;
+        }
+
+        return true;
+    }
+
+    public int hashCode() {
+        int result;
+        result = (isLocal ? 1 : 0);
+        result = MAGIC_HASH_VALUE * result + location.hashCode();
+        result = MAGIC_HASH_VALUE * result + ((lastChecked == null) ? 0 : lastChecked.hashCode());
+        result = MAGIC_HASH_VALUE * result + (exists ? 1 : 0);
+        return result;
+    }
+}
diff --git a/src/java/org/apache/ivy/core/cache/CacheDownloadOptions.java b/src/java/org/apache/ivy/core/cache/CacheDownloadOptions.java
new file mode 100644
index 0000000..674abf2
--- /dev/null
+++ b/src/java/org/apache/ivy/core/cache/CacheDownloadOptions.java
@@ -0,0 +1,43 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.cache;
+
+public class CacheDownloadOptions {
+
+    private DownloadListener listener = null;
+
+    private boolean force = false;
+
+    public DownloadListener getListener() {
+        return listener;
+    }
+
+    public CacheDownloadOptions setListener(DownloadListener listener) {
+        this.listener = listener;
+        return this;
+    }
+
+    public boolean isForce() {
+        return force;
+    }
+
+    public CacheDownloadOptions setForce(boolean force) {
+        this.force = force;
+        return this;
+    }
+}
diff --git a/src/java/org/apache/ivy/core/cache/CacheMetadataOptions.java b/src/java/org/apache/ivy/core/cache/CacheMetadataOptions.java
new file mode 100644
index 0000000..a1c77b4
--- /dev/null
+++ b/src/java/org/apache/ivy/core/cache/CacheMetadataOptions.java
@@ -0,0 +1,88 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.cache;
+
+import org.apache.ivy.plugins.namespace.Namespace;
+
+public class CacheMetadataOptions extends CacheDownloadOptions {
+    private boolean validate = false;
+
+    private Namespace namespace = Namespace.SYSTEM_NAMESPACE;
+
+    private Boolean isCheckmodified = null;
+
+    private String changingMatcherName = null;
+
+    private String changingPattern = null;
+
+    private boolean checkTTL = true;
+
+    public Namespace getNamespace() {
+        return namespace;
+    }
+
+    public CacheMetadataOptions setNamespace(Namespace namespace) {
+        this.namespace = namespace;
+        return this;
+    }
+
+    public boolean isValidate() {
+        return validate;
+    }
+
+    public CacheMetadataOptions setValidate(boolean validate) {
+        this.validate = validate;
+        return this;
+    }
+
+    public Boolean isCheckmodified() {
+        return isCheckmodified;
+    }
+
+    public CacheMetadataOptions setCheckmodified(Boolean isCheckmodified) {
+        this.isCheckmodified = isCheckmodified;
+        return this;
+    }
+
+    public String getChangingMatcherName() {
+        return changingMatcherName;
+    }
+
+    public CacheMetadataOptions setChangingMatcherName(String changingMatcherName) {
+        this.changingMatcherName = changingMatcherName;
+        return this;
+    }
+
+    public String getChangingPattern() {
+        return changingPattern;
+    }
+
+    public CacheMetadataOptions setChangingPattern(String changingPattern) {
+        this.changingPattern = changingPattern;
+        return this;
+    }
+
+    public CacheMetadataOptions setCheckTTL(boolean checkTTL) {
+        this.checkTTL = checkTTL;
+        return this;
+    }
+
+    public boolean isCheckTTL() {
+        return checkTTL;
+    }
+}
diff --git a/src/java/org/apache/ivy/core/cache/CacheResourceOptions.java b/src/java/org/apache/ivy/core/cache/CacheResourceOptions.java
new file mode 100644
index 0000000..9e8ef9f
--- /dev/null
+++ b/src/java/org/apache/ivy/core/cache/CacheResourceOptions.java
@@ -0,0 +1,32 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.cache;
+
+public class CacheResourceOptions extends CacheDownloadOptions {
+
+    // by default, a ttl of 1 hour
+    private long ttl = 1000 * 60 * 60;
+
+    public void setTtl(long ttl) {
+        this.ttl = ttl;
+    }
+
+    public long getTtl() {
+        return ttl;
+    }
+}
diff --git a/src/java/org/apache/ivy/core/cache/CacheUtil.java b/src/java/org/apache/ivy/core/cache/CacheUtil.java
new file mode 100644
index 0000000..71b2c14
--- /dev/null
+++ b/src/java/org/apache/ivy/core/cache/CacheUtil.java
@@ -0,0 +1,50 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.cache;
+
+/**
+ * Utility class providing some cache related facilities.
+ * 
+ */
+public final class CacheUtil {
+
+    /**
+     * Checks that the given pattern is acceptable as a cache pattern
+     * 
+     * @param cachePattern
+     *            the pattern to check
+     * @throws IllegalArgumentException
+     *             if the pattern isn't acceptable as cache pattern
+     */
+    public static void checkCachePattern(String cachePattern) {
+        if (cachePattern == null) {
+            throw new IllegalArgumentException("null cache pattern not allowed.");
+        }
+        if (cachePattern.startsWith("..")) {
+            throw new IllegalArgumentException("invalid cache pattern: '" + cachePattern
+                    + "': cache patterns must not lead outside cache directory");
+        }
+        if (cachePattern.startsWith("/")) {
+            throw new IllegalArgumentException("invalid cache pattern: '" + cachePattern
+                    + "': cache patterns must not be absolute");
+        }
+    }
+
+    private CacheUtil() {
+    }
+}
diff --git a/src/java/org/apache/ivy/core/cache/DefaultRepositoryCacheManager.java b/src/java/org/apache/ivy/core/cache/DefaultRepositoryCacheManager.java
new file mode 100644
index 0000000..55f498d
--- /dev/null
+++ b/src/java/org/apache/ivy/core/cache/DefaultRepositoryCacheManager.java
@@ -0,0 +1,1543 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.cache;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.text.ParseException;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import org.apache.ivy.Ivy;
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DefaultArtifact;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ArtifactRevisionId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.module.id.ModuleRules;
+import org.apache.ivy.core.pack.PackagingManager;
+import org.apache.ivy.core.report.ArtifactDownloadReport;
+import org.apache.ivy.core.report.DownloadStatus;
+import org.apache.ivy.core.report.MetadataArtifactDownloadReport;
+import org.apache.ivy.core.resolve.ResolvedModuleRevision;
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.ivy.plugins.IvySettingsAware;
+import org.apache.ivy.plugins.lock.LockStrategy;
+import org.apache.ivy.plugins.matcher.ExactPatternMatcher;
+import org.apache.ivy.plugins.matcher.MapMatcher;
+import org.apache.ivy.plugins.matcher.Matcher;
+import org.apache.ivy.plugins.matcher.NoMatcher;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+import org.apache.ivy.plugins.namespace.NameSpaceHelper;
+import org.apache.ivy.plugins.parser.ModuleDescriptorParser;
+import org.apache.ivy.plugins.parser.ModuleDescriptorParserRegistry;
+import org.apache.ivy.plugins.parser.ParserSettings;
+import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorParser;
+import org.apache.ivy.plugins.repository.ArtifactResourceResolver;
+import org.apache.ivy.plugins.repository.LocalizableResource;
+import org.apache.ivy.plugins.repository.Repository;
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.plugins.repository.ResourceDownloader;
+import org.apache.ivy.plugins.repository.ResourceHelper;
+import org.apache.ivy.plugins.resolver.AbstractResolver;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.apache.ivy.plugins.resolver.util.ResolvedResource;
+import org.apache.ivy.util.Checks;
+import org.apache.ivy.util.FileUtil;
+import org.apache.ivy.util.HexEncoder;
+import org.apache.ivy.util.Message;
+import org.apache.ivy.util.PropertiesFile;
+
+public class DefaultRepositoryCacheManager implements RepositoryCacheManager, IvySettingsAware {
+    private static final String DEFAULT_ARTIFACT_PATTERN = "[organisation]/[module](/[branch])/[type]s/[artifact]-[revision](-[classifier])(.[ext])";
+
+    private static final String DEFAULT_DATA_FILE_PATTERN = "[organisation]/[module](/[branch])/ivydata-[revision].properties";
+
+    private static final String DEFAULT_IVY_PATTERN = "[organisation]/[module](/[branch])/ivy-[revision].xml";
+
+    private static final int DEFAULT_MEMORY_CACHE_SIZE = 150;
+
+    private static MessageDigest SHA_DIGEST;
+    static {
+        try {
+            SHA_DIGEST = MessageDigest.getInstance("SHA1");
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException("The SHA1 algorithm is not available in your classpath", e);
+        }
+    }
+
+    private IvySettings settings;
+
+    private File basedir;
+
+    private LockStrategy lockStrategy;
+
+    private String name;
+
+    private String ivyPattern;
+
+    private String dataFilePattern = DEFAULT_DATA_FILE_PATTERN;
+
+    private String artifactPattern;
+
+    private String lockStrategyName;
+
+    private String changingPattern;
+
+    private String changingMatcherName = PatternMatcher.EXACT_OR_REGEXP;
+
+    private Boolean checkmodified;
+
+    private Boolean useOrigin;
+
+    private ModuleRules/* <Long> */ttlRules = new ModuleRules();
+
+    private Long defaultTTL = null;
+
+    private ModuleDescriptorMemoryCache memoryModuleDescrCache;
+
+    private PackagingManager packagingManager = new PackagingManager();
+
+    public DefaultRepositoryCacheManager() {
+    }
+
+    public DefaultRepositoryCacheManager(String name, IvySettings settings, File basedir) {
+        setName(name);
+        setSettings(settings);
+        setBasedir(basedir);
+    }
+
+    public IvySettings getSettings() {
+        return settings;
+    }
+
+    public void setSettings(IvySettings settings) {
+        this.settings = settings;
+        packagingManager.setSettings(settings);
+    }
+
+    public File getIvyFileInCache(ModuleRevisionId mrid) {
+        String file = IvyPatternHelper.substitute(getIvyPattern(),
+            DefaultArtifact.newIvyArtifact(mrid, null));
+        return new File(getRepositoryCacheRoot(), file);
+    }
+
+    public String getIvyPattern() {
+        if (ivyPattern == null) {
+            if (settings != null) {
+                ivyPattern = settings.getDefaultCacheIvyPattern();
+            }
+            if (ivyPattern == null) {
+                ivyPattern = DEFAULT_IVY_PATTERN;
+            }
+        }
+        return ivyPattern;
+    }
+
+    public String getArtifactPattern() {
+        if (artifactPattern == null) {
+            if (settings != null) {
+                artifactPattern = settings.getDefaultCacheArtifactPattern();
+            }
+            if (artifactPattern == null) {
+                artifactPattern = DEFAULT_ARTIFACT_PATTERN;
+            }
+        }
+        return artifactPattern;
+    }
+
+    public void setArtifactPattern(String artifactPattern) {
+        CacheUtil.checkCachePattern(artifactPattern);
+        this.artifactPattern = artifactPattern;
+    }
+
+    public File getBasedir() {
+        if (basedir == null) {
+            basedir = settings.getDefaultRepositoryCacheBasedir();
+        }
+        return basedir;
+    }
+
+    public void setBasedir(File cache) {
+        this.basedir = cache;
+    }
+
+    public long getDefaultTTL() {
+        if (defaultTTL == null) {
+            defaultTTL = new Long(parseDuration(settings.getVariable("ivy.cache.ttl.default")));
+        }
+        return defaultTTL.longValue();
+    }
+
+    public void setDefaultTTL(long defaultTTL) {
+        this.defaultTTL = new Long(defaultTTL);
+    }
+
+    public void setDefaultTTL(String defaultTTL) {
+        this.defaultTTL = new Long(parseDuration(defaultTTL));
+    }
+
+    public String getDataFilePattern() {
+        return dataFilePattern;
+    }
+
+    public void setDataFilePattern(String dataFilePattern) {
+        CacheUtil.checkCachePattern(dataFilePattern);
+        this.dataFilePattern = dataFilePattern;
+    }
+
+    public void setIvyPattern(String ivyPattern) {
+        CacheUtil.checkCachePattern(ivyPattern);
+        this.ivyPattern = ivyPattern;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getChangingMatcherName() {
+        return changingMatcherName;
+    }
+
+    public void setChangingMatcher(String changingMatcherName) {
+        this.changingMatcherName = changingMatcherName;
+    }
+
+    public String getChangingPattern() {
+        return changingPattern;
+    }
+
+    public void setChangingPattern(String changingPattern) {
+        this.changingPattern = changingPattern;
+    }
+
+    public void addTTL(Map attributes, PatternMatcher matcher, long duration) {
+        ttlRules.defineRule(new MapMatcher(attributes, matcher), new Long(duration));
+    }
+
+    public void addConfiguredTtl(Map/* <String,String> */attributes) {
+        String duration = (String) attributes.remove("duration");
+        if (duration == null) {
+            throw new IllegalArgumentException("'duration' attribute is mandatory for ttl");
+        }
+        String matcher = (String) attributes.remove("matcher");
+        addTTL(attributes,
+            matcher == null ? ExactPatternMatcher.INSTANCE : settings.getMatcher(matcher),
+            parseDuration(duration));
+    }
+
+    public void setMemorySize(int size) {
+        memoryModuleDescrCache = new ModuleDescriptorMemoryCache(size);
+    }
+
+    public ModuleDescriptorMemoryCache getMemoryCache() {
+        if (memoryModuleDescrCache == null) {
+            memoryModuleDescrCache = new ModuleDescriptorMemoryCache(DEFAULT_MEMORY_CACHE_SIZE);
+        }
+        return memoryModuleDescrCache;
+    }
+
+    private static final Pattern DURATION_PATTERN = Pattern
+            .compile("(?:(\\d+)d)? ?(?:(\\d+)h)? ?(?:(\\d+)m)? ?(?:(\\d+)s)? ?(?:(\\d+)ms)?");
+
+    private static final int MILLIS_IN_SECONDS = 1000;
+
+    private static final int MILLIS_IN_MINUTES = 60 * MILLIS_IN_SECONDS;
+
+    private static final int MILLIS_IN_HOUR = 60 * MILLIS_IN_MINUTES;
+
+    private static final int MILLIS_IN_DAY = 24 * MILLIS_IN_HOUR;
+
+    private long parseDuration(String duration) {
+        if (duration == null) {
+            return 0;
+        }
+        if ("eternal".equals(duration)) {
+            return Long.MAX_VALUE;
+        }
+        java.util.regex.Matcher m = DURATION_PATTERN.matcher(duration);
+        if (m.matches()) {
+            // CheckStyle:MagicNumber| OFF
+            int days = getGroupIntValue(m, 1);
+            int hours = getGroupIntValue(m, 2);
+            int minutes = getGroupIntValue(m, 3);
+            int seconds = getGroupIntValue(m, 4);
+            int millis = getGroupIntValue(m, 5);
+            // CheckStyle:MagicNumber| ON
+
+            return days * MILLIS_IN_DAY + hours * MILLIS_IN_HOUR + minutes * MILLIS_IN_MINUTES
+                    + seconds * MILLIS_IN_SECONDS + millis;
+        } else {
+            throw new IllegalArgumentException("invalid duration '" + duration
+                    + "': it must match " + DURATION_PATTERN.pattern() + " or 'eternal'");
+        }
+    }
+
+    private int getGroupIntValue(java.util.regex.Matcher m, int groupNumber) {
+        String g = m.group(groupNumber);
+        return g == null || g.length() == 0 ? 0 : Integer.parseInt(g);
+    }
+
+    /**
+     * True if this cache should check lastmodified date to know if ivy files are up to date.
+     * 
+     * @return
+     */
+    public boolean isCheckmodified() {
+        if (checkmodified == null) {
+            if (getSettings() != null) {
+                String check = getSettings().getVariable("ivy.resolver.default.check.modified");
+                return check != null ? Boolean.valueOf(check).booleanValue() : false;
+            } else {
+                return false;
+            }
+        } else {
+            return checkmodified.booleanValue();
+        }
+    }
+
+    public void setCheckmodified(boolean check) {
+        checkmodified = Boolean.valueOf(check);
+    }
+
+    /**
+     * True if this cache should use artifacts original location when possible, false if they should
+     * be copied to cache.
+     */
+    public boolean isUseOrigin() {
+        if (useOrigin == null) {
+            if (getSettings() != null) {
+                return getSettings().isDefaultUseOrigin();
+            } else {
+                return false;
+            }
+        } else {
+            return useOrigin.booleanValue();
+        }
+    }
+
+    public void setUseOrigin(boolean b) {
+        useOrigin = Boolean.valueOf(b);
+    }
+
+    /**
+     * Returns a File object pointing to where the artifact can be found on the local file system.
+     * This is usually in the cache, but it can be directly in the repository if it is local and if
+     * the resolve has been done with useOrigin = true
+     */
+    public File getArchiveFileInCache(Artifact artifact) {
+        ArtifactOrigin origin = getSavedArtifactOrigin(artifact);
+        return getArchiveFileInCache(artifact, origin);
+    }
+
+    /**
+     * Returns a File object pointing to where the artifact can be found on the local file system.
+     * This is usually in the cache, but it can be directly in the repository if it is local and if
+     * the resolve has been done with useOrigin = true
+     */
+    public File getArchiveFileInCache(Artifact artifact, ArtifactOrigin origin) {
+        File archive = new File(getRepositoryCacheRoot(), getArchivePathInCache(artifact, origin));
+        if (!archive.exists() && !ArtifactOrigin.isUnknown(origin) && origin.isLocal()) {
+            File original = Checks.checkAbsolute(origin.getLocation(), artifact
+                    + " origin location");
+            if (original.exists()) {
+                return original;
+            }
+        }
+        return archive;
+    }
+
+    /**
+     * Returns a File object pointing to where the artifact can be found on the local file system,
+     * using or not the original location depending on the availability of origin information
+     * provided as parameter and the setting of useOrigin. If useOrigin is false, this method will
+     * always return the file in the cache.
+     */
+    private File getArchiveFileInCache(Artifact artifact, ArtifactOrigin origin, boolean useOrigin) {
+        if (useOrigin && !ArtifactOrigin.isUnknown(origin) && origin.isLocal()) {
+            return Checks.checkAbsolute(origin.getLocation(), artifact + " origin location");
+        } else {
+            return new File(getRepositoryCacheRoot(), getArchivePathInCache(artifact, origin));
+        }
+    }
+
+    public String getArchivePathInCache(Artifact artifact) {
+        return IvyPatternHelper.substitute(getArtifactPattern(), artifact);
+    }
+
+    public String getArchivePathInCache(Artifact artifact, ArtifactOrigin origin) {
+        if (isOriginalMetadataArtifact(artifact)) {
+            return IvyPatternHelper.substitute(getIvyPattern() + ".original", artifact, origin);
+        } else {
+            return IvyPatternHelper.substitute(getArtifactPattern(), artifact, origin);
+        }
+    }
+
+    /**
+     * Saves the information of which resolver was used to resolve a md, so that this info can be
+     * retrieve later (even after a jvm restart) by getSavedResolverName(ModuleDescriptor md)
+     * 
+     * @param md
+     *            the module descriptor resolved
+     * @param name
+     *            resolver name
+     */
+    private void saveResolver(ModuleDescriptor md, String name) {
+        // should always be called with a lock on module metadata artifact
+        PropertiesFile cdf = getCachedDataFile(md);
+        cdf.setProperty("resolver", name);
+        cdf.save();
+    }
+
+    /**
+     * Saves the information of which resolver was used to resolve a md, so that this info can be
+     * retrieve later (even after a jvm restart) by getSavedArtResolverName(ModuleDescriptor md)
+     * 
+     * @param md
+     *            the module descriptor resolved
+     * @param name
+     *            artifact resolver name
+     */
+    public void saveResolvers(ModuleDescriptor md, String metadataResolverName,
+            String artifactResolverName) {
+        ModuleRevisionId mrid = md.getResolvedModuleRevisionId();
+        if (!lockMetadataArtifact(mrid)) {
+            Message.error("impossible to acquire lock for " + mrid);
+            return;
+        }
+        try {
+            PropertiesFile cdf = getCachedDataFile(md);
+            cdf.setProperty("resolver", metadataResolverName);
+            cdf.setProperty("artifact.resolver", artifactResolverName);
+            cdf.save();
+        } finally {
+            unlockMetadataArtifact(mrid);
+        }
+    }
+
+    private String getSavedResolverName(ModuleDescriptor md) {
+        // should always be called with a lock on module metadata artifact
+        PropertiesFile cdf = getCachedDataFile(md);
+        return cdf.getProperty("resolver");
+    }
+
+    private String getSavedArtResolverName(ModuleDescriptor md) {
+        // should always be called with a lock on module metadata artifact
+        PropertiesFile cdf = getCachedDataFile(md);
+        return cdf.getProperty("artifact.resolver");
+    }
+
+    void saveArtifactOrigin(Artifact artifact, ArtifactOrigin origin) {
+        // should always be called with a lock on module metadata artifact
+        PropertiesFile cdf = getCachedDataFile(artifact.getModuleRevisionId());
+        cdf.setProperty(getIsLocalKey(artifact), String.valueOf(origin.isLocal()));
+        cdf.setProperty(getLocationKey(artifact), origin.getLocation());
+        cdf.setProperty(getOriginalKey(artifact), getPrefixKey(origin.getArtifact()));
+        if (origin.getLastChecked() != null) {
+            cdf.setProperty(getLastCheckedKey(artifact), origin.getLastChecked().toString());
+        }
+        cdf.setProperty(getExistsKey(artifact), Boolean.toString(origin.isExists()));
+        cdf.save();
+    }
+
+    private void removeSavedArtifactOrigin(Artifact artifact) {
+        // should always be called with a lock on module metadata artifact
+        PropertiesFile cdf = getCachedDataFile(artifact.getModuleRevisionId());
+        cdf.remove(getLocationKey(artifact));
+        cdf.remove(getIsLocalKey(artifact));
+        cdf.remove(getLastCheckedKey(artifact));
+        cdf.remove(getOriginalKey(artifact));
+        cdf.save();
+    }
+
+    private static final Pattern ARTIFACT_KEY_PATTERN = Pattern
+            .compile(".*:(.*)#(.*)#(.*)#(.*)(\\.location)?");
+
+    public ArtifactOrigin getSavedArtifactOrigin(Artifact artifact) {
+        ModuleRevisionId mrid = artifact.getModuleRevisionId();
+        if (!lockMetadataArtifact(mrid)) {
+            Message.error("impossible to acquire lock for " + mrid);
+            return ArtifactOrigin.unkwnown(artifact);
+        }
+        try {
+            PropertiesFile cdf = getCachedDataFile(artifact.getModuleRevisionId());
+            String location = cdf.getProperty(getLocationKey(artifact));
+            String local = cdf.getProperty(getIsLocalKey(artifact));
+            String lastChecked = cdf.getProperty(getLastCheckedKey(artifact));
+            String exists = cdf.getProperty(getExistsKey(artifact));
+            String original = cdf.getProperty(getOriginalKey(artifact));
+
+            boolean isLocal = Boolean.valueOf(local).booleanValue();
+
+            if (location == null) {
+                // origin has not been specified, return null
+                return ArtifactOrigin.unkwnown(artifact);
+            }
+
+            if (original != null) {
+                // original artifact key artifact:[name]#[type]#[ext]#[hashcode]
+                java.util.regex.Matcher m = ARTIFACT_KEY_PATTERN.matcher(original);
+                if (m.matches()) {
+                    String origName = m.group(1);
+                    String origType = m.group(2);
+                    String origExt = m.group(3);
+
+                    ArtifactRevisionId originArtifactId = ArtifactRevisionId.newInstance(
+                        artifact.getModuleRevisionId(), origName, origType, origExt);
+                    // second check: verify the hashcode of the cached artifact
+                    if (m.group(4).equals("" + originArtifactId.hashCode())) {
+                        try {
+                            artifact = new DefaultArtifact(originArtifactId,
+                                    artifact.getPublicationDate(), new URL(location), true);
+                        } catch (MalformedURLException e) {
+                            Message.debug(e);
+                        }
+                    }
+                }
+            } else {
+                // Fallback if cached with old version:
+
+                // if the origin artifact has another extension (e.g. .pom) then make a synthetic
+                // origin artifact for it
+                if (!location.endsWith("." + artifact.getExt())) {
+                    // try to find other cached artifact info with same location. This must be the
+                    // origin. We must parse the key as we do not know for sure what the original
+                    // artifact is named.
+                    Iterator it = cdf.entrySet().iterator();
+                    String ownLocationKey = getLocationKey(artifact);
+                    while (it.hasNext()) {
+                        Map.Entry entry = (Map.Entry) it.next();
+                        if (entry.getValue().equals(location)
+                                && !ownLocationKey.equals(entry.getKey())) {
+                            // found a match, key is
+                            // artifact:[name]#[type]#[ext]#[hashcode].location
+                            java.util.regex.Matcher m = ARTIFACT_KEY_PATTERN.matcher((String) entry
+                                    .getKey());
+                            if (m.matches()) {
+                                String origName = m.group(1);
+                                String origType = m.group(2);
+                                String origExt = m.group(3);
+
+                                // first check: the type should end in .original
+                                if (!origType.endsWith(".original")) {
+                                    continue;
+                                }
+
+                                ArtifactRevisionId originArtifactId = ArtifactRevisionId
+                                        .newInstance(artifact.getModuleRevisionId(), origName,
+                                            origType, origExt);
+                                // second check: verify the hashcode of the cached artifact
+                                if (m.group(4).equals("" + originArtifactId.hashCode())) {
+                                    try {
+                                        artifact = new DefaultArtifact(originArtifactId,
+                                                artifact.getPublicationDate(), new URL(location),
+                                                true);
+                                    } catch (MalformedURLException e) {
+                                        Message.debug(e);
+                                    }
+                                }
+                                break;
+                            }
+                        }
+                    }
+                }
+            }
+
+            ArtifactOrigin origin = new ArtifactOrigin(artifact, isLocal, location);
+            if (lastChecked != null) {
+                origin.setLastChecked(Long.valueOf(lastChecked));
+            }
+            if (exists != null) {
+                origin.setExist(Boolean.valueOf(exists).booleanValue());
+            }
+
+            return origin;
+        } finally {
+            unlockMetadataArtifact(mrid);
+        }
+    }
+
+    /**
+     * Creates the unique prefix key that will reference the artifact within the properties.
+     * 
+     * @param artifact
+     *            the artifact to create the unique key from. Cannot be null.
+     * @return the unique prefix key as a string.
+     */
+    private String getPrefixKey(Artifact artifact) {
+        // use the hashcode as a uuid for the artifact (fingers crossed)
+        int hashCode = artifact.getId().hashCode();
+        // use just some visual cue
+        return "artifact:" + artifact.getName() + "#" + artifact.getType() + "#"
+                + artifact.getExt() + "#" + hashCode;
+    }
+
+    /**
+     * Returns the key used to identify the location of the artifact.
+     * 
+     * @param artifact
+     *            the artifact to generate the key from. Cannot be null.
+     * @return the key to be used to reference the artifact location.
+     */
+    private String getLocationKey(Artifact artifact) {
+        String prefix = getPrefixKey(artifact);
+        return prefix + ".location";
+    }
+
+    /**
+     * Returns the key used to identify if the artifact is local.
+     * 
+     * @param artifact
+     *            the artifact to generate the key from. Cannot be null.
+     * @return the key to be used to reference the artifact locality.
+     */
+    private String getIsLocalKey(Artifact artifact) {
+        String prefix = getPrefixKey(artifact);
+        return prefix + ".is-local";
+    }
+
+    /**
+     * Returns the key used to identify the last time the artifact was checked to be up to date.
+     * 
+     * @param artifact
+     *            the artifact to generate the key from. Cannot be null.
+     * @return the key to be used to reference the artifact's last check date.
+     */
+    private String getLastCheckedKey(Artifact artifact) {
+        String prefix = getPrefixKey(artifact);
+        return prefix + ".lastchecked";
+    }
+
+    /**
+     * Returns the key used to identify the existence of the remote artifact.
+     * 
+     * @param artifact
+     *            the artifact to generate the key from. Cannot be null.
+     * @return the key to be used to reference the existence of the artifact.
+     */
+    private String getExistsKey(Artifact artifact) {
+        String prefix = getPrefixKey(artifact);
+        return prefix + ".exists";
+    }
+
+    /**
+     * Returns the key used to identify the original artifact.
+     * 
+     * @param artifact
+     *            the artifact to generate the key from. Cannot be null.
+     * @return the key to be used to reference the original artifact.
+     */
+    private String getOriginalKey(Artifact artifact) {
+        String prefix = getPrefixKey(artifact);
+        return prefix + ".original";
+    }
+
+    private PropertiesFile getCachedDataFile(ModuleDescriptor md) {
+        return getCachedDataFile(md.getResolvedModuleRevisionId());
+    }
+
+    private PropertiesFile getCachedDataFile(ModuleRevisionId mRevId) {
+        return new PropertiesFile(new File(getRepositoryCacheRoot(), IvyPatternHelper.substitute(
+            getDataFilePattern(), mRevId)), "ivy cached data file for " + mRevId);
+    }
+
+    public ResolvedModuleRevision findModuleInCache(DependencyDescriptor dd,
+            ModuleRevisionId requestedRevisionId, CacheMetadataOptions options,
+            String expectedResolver) {
+        ModuleRevisionId mrid = requestedRevisionId;
+        if (isCheckmodified(dd, requestedRevisionId, options)) {
+            Message.verbose("don't use cache for " + mrid + ": checkModified=true");
+            return null;
+        }
+        if (isChanging(dd, requestedRevisionId, options)) {
+            Message.verbose("don't use cache for " + mrid + ": changing=true");
+            return null;
+        }
+        return doFindModuleInCache(mrid, options, expectedResolver);
+    }
+
+    private ResolvedModuleRevision doFindModuleInCache(ModuleRevisionId mrid,
+            CacheMetadataOptions options, String expectedResolver) {
+        if (!lockMetadataArtifact(mrid)) {
+            Message.error("impossible to acquire lock for " + mrid);
+            return null;
+        }
+
+        boolean unlock = true;
+
+        try {
+            if (settings.getVersionMatcher().isDynamic(mrid)) {
+                String resolvedRevision = getResolvedRevision(mrid, options);
+                if (resolvedRevision != null) {
+                    Message.verbose("found resolved revision in cache: " + mrid + " => "
+                            + resolvedRevision);
+
+                    // we have found another module in the cache, make sure we unlock
+                    // the original module
+                    unlockMetadataArtifact(mrid);
+                    mrid = ModuleRevisionId.newInstance(mrid, resolvedRevision);
+
+                    // don't forget to request a lock on the new module!
+                    if (!lockMetadataArtifact(mrid)) {
+                        Message.error("impossible to acquire lock for " + mrid);
+
+                        // we couldn't lock the new module, so no need to unlock it
+                        unlock = false;
+                        return null;
+                    }
+                } else {
+                    return null;
+                }
+            }
+
+            File ivyFile = getIvyFileInCache(mrid);
+            if (ivyFile.exists()) {
+                // found in cache !
+                try {
+                    ModuleDescriptorParser parser = getModuleDescriptorParser(ivyFile);
+                    ModuleDescriptor depMD = getMdFromCache(parser, options, ivyFile);
+                    String resolverName = getSavedResolverName(depMD);
+                    String artResolverName = getSavedArtResolverName(depMD);
+                    DependencyResolver resolver = settings.getResolver(resolverName);
+                    if (resolver == null) {
+                        Message.debug("\tresolver not found: " + resolverName
+                                + " => trying to use the one configured for " + mrid);
+                        resolver = settings.getResolver(depMD.getResolvedModuleRevisionId());
+                        if (resolver != null) {
+                            Message.debug("\tconfigured resolver found for "
+                                    + depMD.getResolvedModuleRevisionId() + ": "
+                                    + resolver.getName() + ": saving this data");
+                            saveResolver(depMD, resolver.getName());
+                        }
+                    }
+                    DependencyResolver artResolver = settings.getResolver(artResolverName);
+                    if (artResolver == null) {
+                        artResolver = resolver;
+                    }
+                    if (resolver != null) {
+                        Message.debug("\tfound ivy file in cache for " + mrid + " (resolved by "
+                                + resolver.getName() + "): " + ivyFile);
+                        if (expectedResolver == null || expectedResolver.equals(resolver.getName())) {
+                            MetadataArtifactDownloadReport madr = new MetadataArtifactDownloadReport(
+                                    depMD.getMetadataArtifact());
+                            madr.setDownloadStatus(DownloadStatus.NO);
+                            madr.setSearched(false);
+                            madr.setLocalFile(ivyFile);
+                            madr.setSize(ivyFile.length());
+                            madr.setArtifactOrigin(getSavedArtifactOrigin(depMD
+                                    .getMetadataArtifact()));
+                            if (madr.getArtifactOrigin().isExists()) {
+                                if (madr.getArtifactOrigin().isLocal()
+                                        && madr.getArtifactOrigin().getArtifact().getUrl() != null) {
+                                    madr.setOriginalLocalFile(new File(madr.getArtifactOrigin()
+                                            .getArtifact().getUrl().toURI()));
+                                } else {
+                                    // find locally cached file
+                                    madr.setOriginalLocalFile(getArchiveFileInCache(madr
+                                            .getArtifactOrigin().getArtifact()));
+                                }
+                            }
+                            return new ResolvedModuleRevision(resolver, artResolver, depMD, madr);
+                        } else {
+                            Message.debug("found module in cache but with a different resolver: "
+                                    + "discarding: " + mrid + "; expected resolver="
+                                    + expectedResolver + "; resolver=" + resolver.getName());
+                        }
+                    } else {
+                        Message.debug("\tresolver not found: " + resolverName
+                                + " => cannot use cached ivy file for " + mrid);
+                    }
+                } catch (Exception e) {
+                    // will try with resolver
+                    Message.debug("\tproblem while parsing cached ivy file for: " + mrid, e);
+                }
+            } else {
+                Message.debug("\tno ivy file in cache for " + mrid + ": tried " + ivyFile);
+            }
+        } finally {
+            if (unlock) {
+                unlockMetadataArtifact(mrid);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Choose write module descriptor parser for a given moduleDescriptor
+     * 
+     * @param moduleDescriptorFile
+     *            a given module descriptor
+     * @return
+     */
+    protected ModuleDescriptorParser getModuleDescriptorParser(File moduleDescriptorFile) {
+        return XmlModuleDescriptorParser.getInstance();
+    }
+
+    private class MyModuleDescriptorProvider implements ModuleDescriptorProvider {
+
+        private final ModuleDescriptorParser mdParser;
+
+        private final ParserSettings settings;
+
+        public MyModuleDescriptorProvider(ModuleDescriptorParser mdParser, ParserSettings settings) {
+            this.mdParser = mdParser;
+            this.settings = settings;
+        }
+
+        public ModuleDescriptor provideModule(ParserSettings ivySettings, File descriptorURL,
+                boolean validate) throws ParseException, IOException {
+            return mdParser.parseDescriptor(settings, descriptorURL.toURI().toURL(), validate);
+        }
+    }
+
+    private ModuleDescriptor getMdFromCache(ModuleDescriptorParser mdParser,
+            CacheMetadataOptions options, File ivyFile) throws ParseException, IOException {
+        ModuleDescriptorMemoryCache cache = getMemoryCache();
+        ModuleDescriptorProvider mdProvider = new MyModuleDescriptorProvider(mdParser, settings);
+        return cache.get(ivyFile, settings, options.isValidate(), mdProvider);
+    }
+
+    private ModuleDescriptor getStaledMd(ModuleDescriptorParser mdParser,
+            CacheMetadataOptions options, File ivyFile, ParserSettings parserSettings)
+            throws ParseException, IOException {
+        ModuleDescriptorMemoryCache cache = getMemoryCache();
+        ModuleDescriptorProvider mdProvider = new MyModuleDescriptorProvider(mdParser,
+                parserSettings);
+        return cache.getStale(ivyFile, settings, options.isValidate(), mdProvider);
+    }
+
+    private String getResolvedRevision(ModuleRevisionId mrid, CacheMetadataOptions options) {
+        if (!lockMetadataArtifact(mrid)) {
+            Message.error("impossible to acquire lock for " + mrid);
+            return null;
+        }
+        try {
+            String resolvedRevision = null;
+            if (options.isForce()) {
+                Message.verbose("refresh mode: no check for cached resolved revision for " + mrid);
+                return null;
+            }
+            PropertiesFile cachedResolvedRevision = getCachedDataFile(mrid);
+            resolvedRevision = cachedResolvedRevision.getProperty("resolved.revision");
+            if (resolvedRevision == null) {
+                Message.verbose(getName() + ": no cached resolved revision for " + mrid);
+                return null;
+            }
+
+            String resolvedTime = cachedResolvedRevision.getProperty("resolved.time");
+            if (resolvedTime == null) {
+                Message.verbose(getName()
+                        + ": inconsistent or old cache: no cached resolved time for " + mrid);
+                saveResolvedRevision(mrid, resolvedRevision);
+                return resolvedRevision;
+            }
+            if (options.isCheckTTL()) {
+                long expiration = Long.parseLong(resolvedTime) + getTTL(mrid);
+                if (expiration > 0 // negative expiration means that Long.MAX_VALUE has been
+                                   // exceeded
+                        && System.currentTimeMillis() > expiration) {
+                    Message.verbose(getName() + ": cached resolved revision expired for " + mrid);
+                    return null;
+                }
+            }
+            return resolvedRevision;
+        } finally {
+            unlockMetadataArtifact(mrid);
+        }
+    }
+
+    public void saveResolvedRevision(ModuleRevisionId mrid, String revision) {
+        if (!lockMetadataArtifact(mrid)) {
+            Message.error("impossible to acquire lock for " + mrid);
+            return;
+        }
+        try {
+            PropertiesFile cachedResolvedRevision = getCachedDataFile(mrid);
+            cachedResolvedRevision.setProperty("resolved.time",
+                String.valueOf(System.currentTimeMillis()));
+            cachedResolvedRevision.setProperty("resolved.revision", revision);
+            cachedResolvedRevision.save();
+        } finally {
+            unlockMetadataArtifact(mrid);
+        }
+    }
+
+    public long getTTL(ModuleRevisionId mrid) {
+        Long ttl = (Long) ttlRules.getRule(mrid);
+        return ttl == null ? getDefaultTTL() : ttl.longValue();
+    }
+
+    public String toString() {
+        return name;
+    }
+
+    public File getRepositoryCacheRoot() {
+        return getBasedir();
+    }
+
+    public LockStrategy getLockStrategy() {
+        if (lockStrategy == null) {
+            if (lockStrategyName != null) {
+                lockStrategy = settings.getLockStrategy(lockStrategyName);
+            } else {
+                lockStrategy = settings.getDefaultLockStrategy();
+            }
+        }
+        return lockStrategy;
+    }
+
+    public void setLockStrategy(LockStrategy lockStrategy) {
+        this.lockStrategy = lockStrategy;
+    }
+
+    public void setLockStrategy(String lockStrategyName) {
+        this.lockStrategyName = lockStrategyName;
+    }
+
+    public ArtifactDownloadReport download(Artifact artifact,
+            ArtifactResourceResolver resourceResolver, ResourceDownloader resourceDownloader,
+            CacheDownloadOptions options) {
+        final ArtifactDownloadReport adr = new ArtifactDownloadReport(artifact);
+        boolean useOrigin = isUseOrigin();
+
+        // TODO: see if we could lock on the artifact to download only, instead of the module
+        // metadata artifact. We'd need to store artifact origin and is local in artifact specific
+        // file to do so, or lock the metadata artifact only to update artifact origin, which would
+        // mean acquiring nested locks, which can be a dangerous thing
+        ModuleRevisionId mrid = artifact.getModuleRevisionId();
+        if (!lockMetadataArtifact(mrid)) {
+            adr.setDownloadStatus(DownloadStatus.FAILED);
+            adr.setDownloadDetails("impossible to get lock for " + mrid);
+            return adr;
+        }
+        try {
+            DownloadListener listener = options.getListener();
+            if (listener != null) {
+                listener.needArtifact(this, artifact);
+            }
+            ArtifactOrigin origin = getSavedArtifactOrigin(artifact);
+            // if we can use origin file, we just ask ivy for the file in cache, and it will
+            // return the original one if possible. If we are not in useOrigin mode, we use the
+            // getArchivePath method which always return a path in the actual cache
+            File archiveFile = getArchiveFileInCache(artifact, origin, useOrigin);
+
+            if (archiveFile.exists() && !options.isForce()) {
+                adr.setDownloadStatus(DownloadStatus.NO);
+                adr.setSize(archiveFile.length());
+                adr.setArtifactOrigin(origin);
+                adr.setLocalFile(archiveFile);
+            } else {
+                long start = System.currentTimeMillis();
+                try {
+                    ResolvedResource artifactRef = resourceResolver.resolve(artifact);
+                    if (artifactRef != null) {
+                        Resource artifactRes = artifactRef.getResource();
+                        origin = new ArtifactOrigin(artifact, artifactRes.isLocal(),
+                                artifactRes.getName());
+                        if (useOrigin && artifactRes.isLocal()) {
+                            if (artifactRes instanceof LocalizableResource) {
+                                origin.setLocation(((LocalizableResource) artifactRes).getFile()
+                                        .getAbsolutePath());
+                            }
+                            saveArtifactOrigin(artifact, origin);
+                            archiveFile = getArchiveFileInCache(artifact, origin);
+                            adr.setDownloadStatus(DownloadStatus.NO);
+                            adr.setSize(archiveFile.length());
+                            adr.setArtifactOrigin(origin);
+                            adr.setLocalFile(archiveFile);
+                        } else {
+                            // refresh archive file now that we better now its origin
+                            archiveFile = getArchiveFileInCache(artifact, origin, useOrigin);
+                            if (ResourceHelper.equals(artifactRes, archiveFile)) {
+                                throw new IllegalStateException("invalid settings for '"
+                                        + resourceResolver
+                                        + "': pointing repository to ivy cache is forbidden !");
+                            }
+                            if (listener != null) {
+                                listener.startArtifactDownload(this, artifactRef, artifact, origin);
+                            }
+
+                            resourceDownloader.download(artifact, artifactRes, archiveFile);
+                            adr.setSize(archiveFile.length());
+                            saveArtifactOrigin(artifact, origin);
+                            adr.setDownloadTimeMillis(System.currentTimeMillis() - start);
+                            adr.setDownloadStatus(DownloadStatus.SUCCESSFUL);
+                            adr.setArtifactOrigin(origin);
+                            adr.setLocalFile(archiveFile);
+                        }
+                    } else {
+                        adr.setDownloadStatus(DownloadStatus.FAILED);
+                        adr.setDownloadDetails(ArtifactDownloadReport.MISSING_ARTIFACT);
+                        adr.setDownloadTimeMillis(System.currentTimeMillis() - start);
+                    }
+                } catch (Exception ex) {
+                    Message.debug(ex);
+                    adr.setDownloadStatus(DownloadStatus.FAILED);
+                    adr.setDownloadDetails(ex.getMessage());
+                    adr.setDownloadTimeMillis(System.currentTimeMillis() - start);
+                }
+            }
+            if (adr.getDownloadStatus() != DownloadStatus.FAILED) {
+                unpackArtifact(artifact, adr, options);
+            }
+            if (listener != null) {
+                listener.endArtifactDownload(this, artifact, adr, archiveFile);
+            }
+            return adr;
+        } finally {
+            unlockMetadataArtifact(mrid);
+        }
+    }
+
+    private void unpackArtifact(Artifact artifact, ArtifactDownloadReport adr,
+            CacheDownloadOptions options) {
+        Artifact unpacked = packagingManager.getUnpackedArtifact(artifact);
+        if (unpacked == null) {
+            // nothing to unpack
+            return;
+        }
+
+        File archiveFile = getArchiveFileInCache(unpacked, null, false);
+        if (archiveFile.exists() && !options.isForce()) {
+            adr.setUnpackedLocalFile(archiveFile);
+        } else {
+            Message.info("\tUnpacking " + artifact.getId());
+            try {
+                packagingManager.unpackArtifact(artifact, adr.getLocalFile(), archiveFile);
+                adr.setUnpackedLocalFile(archiveFile);
+            } catch (Exception e) {
+                Message.debug(e);
+                adr.setDownloadStatus(DownloadStatus.FAILED);
+                adr.setDownloadDetails("The packed artifact " + artifact.getId()
+                        + " could not be unpacked (" + e.getMessage() + ")");
+            }
+        }
+    }
+
+    public ArtifactDownloadReport downloadRepositoryResource(final Resource resource, String name,
+            String type, String extension, CacheResourceOptions options, Repository repository) {
+
+        String hash = computeResourceNameHash(resource);
+        ModuleRevisionId mrid = ModuleRevisionId.newInstance("_repository_metadata_", hash,
+            Ivy.getWorkingRevision());
+        Artifact artifact = new DefaultArtifact(mrid, null, name, type, extension);
+        final ArtifactDownloadReport adr = new ArtifactDownloadReport(artifact);
+        boolean useOrigin = isUseOrigin();
+
+        try {
+            DownloadListener listener = options.getListener();
+            if (listener != null) {
+                listener.needArtifact(this, artifact);
+            }
+            ArtifactOrigin savedOrigin = getSavedArtifactOrigin(artifact);
+            File archiveFile = getArchiveFileInCache(artifact, savedOrigin, useOrigin);
+
+            ArtifactOrigin origin = new ArtifactOrigin(artifact, resource.isLocal(),
+                    resource.getName());
+
+            if (!options.isForce()
+            // if the local file has been checked to be up to date enough recently, don't download
+                    && checkCacheUptodate(archiveFile, resource, savedOrigin, origin,
+                        options.getTtl())) {
+                if (archiveFile.exists()) {
+                    saveArtifactOrigin(artifact, origin);
+                    adr.setDownloadStatus(DownloadStatus.NO);
+                    adr.setSize(archiveFile.length());
+                    adr.setArtifactOrigin(savedOrigin);
+                    adr.setLocalFile(archiveFile);
+                } else {
+                    // we trust the cache to says that the resource doesn't exist
+                    adr.setDownloadStatus(DownloadStatus.FAILED);
+                    adr.setDownloadDetails("Remote resource is known to not exist");
+                }
+            } else {
+                long start = System.currentTimeMillis();
+                origin.setLastChecked(new Long(start));
+                try {
+                    ResolvedResource artifactRef = new ResolvedResource(resource,
+                            Ivy.getWorkingRevision());
+                    if (useOrigin && resource.isLocal()) {
+                        saveArtifactOrigin(artifact, origin);
+                        archiveFile = getArchiveFileInCache(artifact, origin);
+                        adr.setDownloadStatus(DownloadStatus.NO);
+                        adr.setSize(archiveFile.length());
+                        adr.setArtifactOrigin(origin);
+                        adr.setLocalFile(archiveFile);
+                    } else {
+                        if (listener != null) {
+                            listener.startArtifactDownload(this, artifactRef, artifact, origin);
+                        }
+
+                        // actual download
+                        if (archiveFile.exists()) {
+                            archiveFile.delete();
+                        }
+                        File part = new File(archiveFile.getAbsolutePath() + ".part");
+                        repository.get(resource.getName(), part);
+                        if (!part.renameTo(archiveFile)) {
+                            throw new IOException(
+                                    "impossible to move part file to definitive one: " + part
+                                            + " -> " + archiveFile);
+                        }
+
+                        adr.setSize(archiveFile.length());
+                        saveArtifactOrigin(artifact, origin);
+                        adr.setDownloadTimeMillis(System.currentTimeMillis() - start);
+                        adr.setDownloadStatus(DownloadStatus.SUCCESSFUL);
+                        adr.setArtifactOrigin(origin);
+                        adr.setLocalFile(archiveFile);
+                    }
+                } catch (Exception ex) {
+                    Message.debug(ex);
+                    origin.setExist(false);
+                    saveArtifactOrigin(artifact, origin);
+                    adr.setDownloadStatus(DownloadStatus.FAILED);
+                    adr.setDownloadDetails(ex.getMessage());
+                    adr.setDownloadTimeMillis(System.currentTimeMillis() - start);
+                }
+            }
+            if (listener != null) {
+                listener.endArtifactDownload(this, artifact, adr, archiveFile);
+            }
+            return adr;
+        } finally {
+            unlockMetadataArtifact(mrid);
+        }
+    }
+
+    /**
+     * Compute a SHA1 of the resource name, encoded in base64, so we can use it as a file name.
+     * 
+     * @param resource
+     *            the resource which name will be hashed
+     * @return the hash
+     */
+    private String computeResourceNameHash(Resource resource) {
+        byte[] shaDigest;
+        try {
+            shaDigest = SHA_DIGEST.digest(resource.getName().getBytes("UTF-8"));
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException("UTF-8 not supported", e);
+        }
+        return HexEncoder.encode(shaDigest);
+    }
+
+    /**
+     * Check that a cached file can be considered up to date and thus not downloaded
+     * 
+     * @param archiveFile
+     *            the file in the cache
+     * @param resource
+     *            the remote resource to check
+     * @param savedOrigin
+     *            the saved origin which contains that last checked date
+     * @param origin
+     *            the origin in which to store the new last checked date
+     * @param ttl
+     *            the time to live to consider the cache up to date
+     * @return <code>true</code> if the cache is considered up to date
+     */
+    private boolean checkCacheUptodate(File archiveFile, Resource resource,
+            ArtifactOrigin savedOrigin, ArtifactOrigin origin, long ttl) {
+        long time = System.currentTimeMillis();
+        if (savedOrigin.getLastChecked() != null
+                && (time - savedOrigin.getLastChecked().longValue()) < ttl) {
+            // still in the ttl period, no need to check, trust the cache
+            if (!archiveFile.exists()) {
+                // but if the local archive doesn't exist, trust the cache only if the cached origin
+                // says that the remote resource doesn't exist either
+                return !savedOrigin.isExists();
+            }
+            return true;
+        }
+        if (!archiveFile.exists()) {
+            // the the file doesn't exist in the cache, obviously not up to date
+            return false;
+        }
+        origin.setLastChecked(new Long(time));
+        // check if the local resource is up to date regarding the remote one
+        return archiveFile.lastModified() >= resource.getLastModified();
+    }
+
+    public void originalToCachedModuleDescriptor(DependencyResolver resolver,
+            ResolvedResource orginalMetadataRef, Artifact requestedMetadataArtifact,
+            ResolvedModuleRevision rmr, ModuleDescriptorWriter writer) {
+        ModuleDescriptor md = rmr.getDescriptor();
+        Artifact originalMetadataArtifact = getOriginalMetadataArtifact(requestedMetadataArtifact);
+        File mdFileInCache = getIvyFileInCache(md.getResolvedModuleRevisionId());
+
+        ModuleRevisionId mrid = requestedMetadataArtifact.getModuleRevisionId();
+        if (!lockMetadataArtifact(mrid)) {
+            Message.warn("impossible to acquire lock for: " + mrid);
+            return;
+        }
+        try {
+            File originalFileInCache = getArchiveFileInCache(originalMetadataArtifact);
+            writer.write(orginalMetadataRef, md, originalFileInCache, mdFileInCache);
+
+            getMemoryCache().putInCache(mdFileInCache, new ParserSettingsMonitor(settings), true,
+                md);
+            saveResolvers(md, resolver.getName(), resolver.getName());
+
+            if (!md.isDefault()) {
+                rmr.getReport().setOriginalLocalFile(originalFileInCache);
+            }
+            rmr.getReport().setLocalFile(mdFileInCache);
+        } catch (RuntimeException e) {
+            throw e;
+        } catch (Exception e) {
+            String metadataRef;
+            if (orginalMetadataRef == null) {
+                metadataRef = String.valueOf(md.getResolvedModuleRevisionId());
+            } else {
+                metadataRef = String.valueOf(orginalMetadataRef);
+            }
+            Message.warn("impossible to put metadata file in cache: " + metadataRef, e);
+        } finally {
+            unlockMetadataArtifact(mrid);
+        }
+    }
+
+    public ResolvedModuleRevision cacheModuleDescriptor(DependencyResolver resolver,
+            final ResolvedResource mdRef, DependencyDescriptor dd, Artifact moduleArtifact,
+            ResourceDownloader downloader, CacheMetadataOptions options) throws ParseException {
+        Date cachedPublicationDate = null;
+        ArtifactDownloadReport report;
+        ModuleRevisionId mrid = moduleArtifact.getModuleRevisionId();
+        if (!lockMetadataArtifact(mrid)) {
+            Message.error("impossible to acquire lock for " + mrid);
+            return null;
+        }
+
+        BackupResourceDownloader backupDownloader = new BackupResourceDownloader(downloader);
+
+        try {
+            if (!moduleArtifact.isMetadata()) {
+                // the descriptor we are trying to cache is a default one, not much to do
+                // just make sure the old artifacts are deleted...
+                if (isChanging(dd, mrid, options)) {
+                    long repoLastModified = mdRef.getLastModified();
+
+                    Artifact transformedArtifact = NameSpaceHelper.transform(moduleArtifact,
+                        options.getNamespace().getToSystemTransformer());
+                    ArtifactOrigin origin = getSavedArtifactOrigin(transformedArtifact);
+                    File artFile = getArchiveFileInCache(transformedArtifact, origin, false);
+                    if (artFile.exists() && repoLastModified > artFile.lastModified()) {
+                        // artifacts have changed, they should be downloaded again
+                        Message.verbose(mrid + " has changed: deleting old artifacts");
+                        Message.debug("deleting " + artFile);
+                        if (!artFile.delete()) {
+                            Message.error("Couldn't delete outdated artifact from cache: "
+                                    + artFile);
+                            return null;
+                        }
+                        removeSavedArtifactOrigin(transformedArtifact);
+                    }
+                }
+                return null;
+            }
+
+            // now let's see if we can find it in cache and if it is up to date
+            ResolvedModuleRevision rmr = doFindModuleInCache(mrid, options, null);
+            if (rmr != null) {
+                if (rmr.getDescriptor().isDefault() && rmr.getResolver() != resolver) {
+                    Message.verbose("\t" + getName() + ": found revision in cache: " + mrid
+                            + " (resolved by " + rmr.getResolver().getName()
+                            + "): but it's a default one, maybe we can find a better one");
+                } else {
+                    if (!isCheckmodified(dd, mrid, options) && !isChanging(dd, mrid, options)) {
+                        Message.verbose("\t" + getName() + ": revision in cache: " + mrid);
+                        rmr.getReport().setSearched(true);
+                        return rmr;
+                    }
+                    long repLastModified = mdRef.getLastModified();
+                    long cacheLastModified = rmr.getDescriptor().getLastModified();
+                    if (!rmr.getDescriptor().isDefault() && repLastModified <= cacheLastModified) {
+                        Message.verbose("\t" + getName() + ": revision in cache (not updated): "
+                                + mrid);
+                        rmr.getReport().setSearched(true);
+                        return rmr;
+                    } else {
+                        Message.verbose("\t" + getName()
+                                + ": revision in cache is not up to date: " + mrid);
+                        if (isChanging(dd, mrid, options)) {
+                            // ivy file has been updated, we should see if it has a new publication
+                            // date to see if a new download is required (in case the dependency is
+                            // a changing one)
+                            cachedPublicationDate = rmr.getDescriptor()
+                                    .getResolvedPublicationDate();
+                        }
+                    }
+                }
+            }
+
+            Artifact originalMetadataArtifact = getOriginalMetadataArtifact(moduleArtifact);
+            // now download module descriptor and parse it
+            report = download(originalMetadataArtifact, new ArtifactResourceResolver() {
+                public ResolvedResource resolve(Artifact artifact) {
+                    return mdRef;
+                }
+            }, backupDownloader, new CacheDownloadOptions().setListener(options.getListener())
+                    .setForce(true));
+            Message.verbose("\t" + report);
+
+            if (report.getDownloadStatus() == DownloadStatus.FAILED) {
+                Message.warn("problem while downloading module descriptor: " + mdRef.getResource()
+                        + ": " + report.getDownloadDetails() + " ("
+                        + report.getDownloadTimeMillis() + "ms)");
+                return null;
+            }
+
+            try {
+                ModuleDescriptorParser parser = ModuleDescriptorParserRegistry.getInstance()
+                        .getParser(mdRef.getResource());
+                ParserSettings parserSettings = settings;
+                if (resolver instanceof AbstractResolver) {
+                    parserSettings = ((AbstractResolver) resolver).getParserSettings();
+                }
+                ModuleDescriptor md = getStaledMd(parser, options, report.getLocalFile(),
+                    parserSettings);
+                if (md == null) {
+                    throw new IllegalStateException(
+                            "module descriptor parser returned a null module descriptor, "
+                                    + "which is not allowed. " + "parser=" + parser
+                                    + "; parser class=" + parser.getClass().getName()
+                                    + "; module descriptor resource=" + mdRef.getResource());
+                }
+                Message.debug("\t" + getName() + ": parsed downloaded md file for " + mrid
+                        + "; parsed=" + md.getModuleRevisionId());
+
+                // check if we should delete old artifacts
+                boolean deleteOldArtifacts = false;
+                if (cachedPublicationDate != null
+                        && !cachedPublicationDate.equals(md.getResolvedPublicationDate())) {
+                    // artifacts have changed, they should be downloaded again
+                    Message.verbose(mrid + " has changed: deleting old artifacts");
+                    deleteOldArtifacts = true;
+                }
+                if (deleteOldArtifacts) {
+                    String[] confs = md.getConfigurationsNames();
+                    for (int i = 0; i < confs.length; i++) {
+                        Artifact[] arts = md.getArtifacts(confs[i]);
+                        for (int j = 0; j < arts.length; j++) {
+                            Artifact transformedArtifact = NameSpaceHelper.transform(arts[j],
+                                options.getNamespace().getToSystemTransformer());
+                            ArtifactOrigin origin = getSavedArtifactOrigin(transformedArtifact);
+                            File artFile = getArchiveFileInCache(transformedArtifact, origin, false);
+                            if (artFile.exists()) {
+                                Message.debug("deleting " + artFile);
+                                if (!artFile.delete()) {
+                                    // Old artifacts couldn't get deleted!
+                                    // Restore the original ivy file so the next time we
+                                    // resolve the old artifacts are deleted again
+                                    backupDownloader.restore();
+                                    Message.error("Couldn't delete outdated artifact from cache: "
+                                            + artFile);
+                                    return null;
+                                }
+                            }
+                            removeSavedArtifactOrigin(transformedArtifact);
+                        }
+                    }
+                } else if (isChanging(dd, mrid, options)) {
+                    Message.verbose(mrid
+                            + " is changing, but has not changed: will trust cached artifacts if any");
+                }
+
+                MetadataArtifactDownloadReport madr = new MetadataArtifactDownloadReport(
+                        md.getMetadataArtifact());
+                madr.setSearched(true);
+                madr.setDownloadStatus(report.getDownloadStatus());
+                madr.setDownloadDetails(report.getDownloadDetails());
+                madr.setArtifactOrigin(report.getArtifactOrigin());
+                madr.setDownloadTimeMillis(report.getDownloadTimeMillis());
+                madr.setOriginalLocalFile(report.getLocalFile());
+                madr.setSize(report.getSize());
+
+                Artifact transformedMetadataArtifact = NameSpaceHelper.transform(
+                    md.getMetadataArtifact(), options.getNamespace().getToSystemTransformer());
+                saveArtifactOrigin(transformedMetadataArtifact, report.getArtifactOrigin());
+
+                return new ResolvedModuleRevision(resolver, resolver, md, madr);
+            } catch (IOException ex) {
+                Message.warn("io problem while parsing ivy file: " + mdRef.getResource(), ex);
+                return null;
+            }
+        } finally {
+            unlockMetadataArtifact(mrid);
+            backupDownloader.cleanUp();
+        }
+
+    }
+
+    // lock used to lock all metadata related information access
+    private boolean lockMetadataArtifact(ModuleRevisionId mrid) {
+        Artifact artifact = getDefaultMetadataArtifact(mrid);
+        try {
+            // we need to provide an artifact origin to be sure we do not end up in a stack overflow
+            // if the cache pattern is using original name, and the substitution thus trying to get
+            // the saved artifact origin value which in turns calls this method
+            return getLockStrategy().lockArtifact(artifact,
+                getArchiveFileInCache(artifact, getDefaultMetadataArtifactOrigin(mrid)));
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt(); // reset interrupt status
+            throw new RuntimeException("operation interrupted");
+        }
+    }
+
+    private void unlockMetadataArtifact(ModuleRevisionId mrid) {
+        Artifact artifact = getDefaultMetadataArtifact(mrid);
+        getLockStrategy().unlockArtifact(artifact,
+            getArchiveFileInCache(artifact, getDefaultMetadataArtifactOrigin(mrid)));
+    }
+
+    private ArtifactOrigin getDefaultMetadataArtifactOrigin(ModuleRevisionId mrid) {
+        // it's important to say the origin is not local to make sure it won't ever be used for
+        // anything else than original token
+        return new ArtifactOrigin(DefaultArtifact.newIvyArtifact(mrid, null), false,
+                getIvyFileInCache(mrid).getPath());
+    }
+
+    private Artifact getDefaultMetadataArtifact(ModuleRevisionId mrid) {
+        return new DefaultArtifact(mrid, new Date(), "metadata", "metadata", "ivy", true);
+    }
+
+    // not used any more, but maybe useful for finer grain locking when downloading artifacts
+    // private boolean lockArtifact(Artifact artifact) {
+    // try {
+    // return getLockStrategy().lockArtifact(artifact,
+    // getArchiveFileInCache(artifact, null));
+    // } catch (InterruptedException e) {
+    // Thread.currentThread().interrupt(); // reset interrupt status
+    // throw new RuntimeException("operation interrupted");
+    // }
+    // }
+    //
+    // private void unlockArtifact(Artifact artifact) {
+    // getLockStrategy().unlockArtifact(artifact, getArchiveFileInCache(artifact, null));
+    // }
+
+    public Artifact getOriginalMetadataArtifact(Artifact moduleArtifact) {
+        return DefaultArtifact.cloneWithAnotherType(moduleArtifact, moduleArtifact.getType()
+                + ".original");
+    }
+
+    private boolean isOriginalMetadataArtifact(Artifact artifact) {
+        return artifact.isMetadata() && artifact.getType().endsWith(".original");
+    }
+
+    private boolean isChanging(DependencyDescriptor dd, ModuleRevisionId requestedRevisionId,
+            CacheMetadataOptions options) {
+        return dd.isChanging()
+                || getChangingMatcher(options).matches(requestedRevisionId.getRevision());
+    }
+
+    private Matcher getChangingMatcher(CacheMetadataOptions options) {
+        String changingPattern = options.getChangingPattern() != null ? options
+                .getChangingPattern() : this.changingPattern;
+        if (changingPattern == null) {
+            return NoMatcher.INSTANCE;
+        }
+        String changingMatcherName = options.getChangingMatcherName() != null ? options
+                .getChangingMatcherName() : this.changingMatcherName;
+        PatternMatcher matcher = settings.getMatcher(changingMatcherName);
+        if (matcher == null) {
+            throw new IllegalStateException("unknown matcher '" + changingMatcherName
+                    + "'. It is set as changing matcher in " + this);
+        }
+        return matcher.getMatcher(changingPattern);
+    }
+
+    private boolean isCheckmodified(DependencyDescriptor dd, ModuleRevisionId requestedRevisionId,
+            CacheMetadataOptions options) {
+        if (options.isCheckmodified() != null) {
+            return options.isCheckmodified().booleanValue();
+        }
+        return isCheckmodified();
+    }
+
+    public void clean() {
+        FileUtil.forceDelete(getBasedir());
+    }
+
+    public void dumpSettings() {
+        Message.verbose("\t" + getName());
+        Message.debug("\t\tivyPattern: " + getIvyPattern());
+        Message.debug("\t\tartifactPattern: " + getArtifactPattern());
+        Message.debug("\t\tlockingStrategy: " + getLockStrategy().getName());
+        Message.debug("\t\tchangingPattern: " + getChangingPattern());
+        Message.debug("\t\tchangingMatcher: " + getChangingMatcherName());
+    }
+
+    /**
+     * Resource downloader which makes a copy of the previously existing file before overriding it.
+     * <p>
+     * The backup file can be restored or cleanuped later
+     */
+    private final class BackupResourceDownloader implements ResourceDownloader {
+
+        private ResourceDownloader delegate;
+
+        private File backup;
+
+        private String originalPath;
+
+        private BackupResourceDownloader(ResourceDownloader delegate) {
+            this.delegate = delegate;
+        }
+
+        public void download(Artifact artifact, Resource resource, File dest) throws IOException {
+            // keep a copy of the original file
+            if (dest.exists()) {
+                originalPath = dest.getAbsolutePath();
+                backup = new File(dest.getAbsolutePath() + ".backup");
+                FileUtil.copy(dest, backup, null, true);
+            }
+            delegate.download(artifact, resource, dest);
+        }
+
+        public void restore() throws IOException {
+            if ((backup != null) && backup.exists()) {
+                File original = new File(originalPath);
+                FileUtil.copy(backup, original, null, true);
+                backup.delete();
+            }
+        }
+
+        public void cleanUp() {
+            if ((backup != null) && backup.exists()) {
+                backup.delete();
+            }
+        }
+
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/cache/DefaultResolutionCacheManager.java b/src/java/org/apache/ivy/core/cache/DefaultResolutionCacheManager.java
new file mode 100644
index 0000000..2683702
--- /dev/null
+++ b/src/java/org/apache/ivy/core/cache/DefaultResolutionCacheManager.java
@@ -0,0 +1,304 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.cache;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.text.ParseException;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.core.RelativeUrlResolver;
+import org.apache.ivy.core.module.descriptor.ExtendsDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.module.status.StatusManager;
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.ivy.plugins.IvySettingsAware;
+import org.apache.ivy.plugins.conflict.ConflictManager;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+import org.apache.ivy.plugins.namespace.Namespace;
+import org.apache.ivy.plugins.parser.ModuleDescriptorParser;
+import org.apache.ivy.plugins.parser.ParserSettings;
+import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorParser;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.apache.ivy.util.FileUtil;
+
+public class DefaultResolutionCacheManager implements ResolutionCacheManager, IvySettingsAware {
+
+    private static final String DEFAULT_CACHE_RESOLVED_IVY_PATTERN = "resolved-[organisation]-[module]-[revision].xml";
+
+    private static final String DEFAULT_CACHE_RESOLVED_IVY_PROPERTIES_PATTERN = "resolved-[organisation]-[module]-[revision].properties";
+
+    private String resolvedIvyPattern = DEFAULT_CACHE_RESOLVED_IVY_PATTERN;
+
+    private String resolvedIvyPropertiesPattern = DEFAULT_CACHE_RESOLVED_IVY_PROPERTIES_PATTERN;
+
+    private File basedir;
+
+    private String name = "resolution-cache";
+
+    private IvySettings settings;
+
+    public DefaultResolutionCacheManager() {
+    }
+
+    public DefaultResolutionCacheManager(File basedir) {
+        setBasedir(basedir);
+    }
+
+    public void setSettings(IvySettings settings) {
+        this.settings = settings;
+    }
+
+    public File getResolutionCacheRoot() {
+        return basedir;
+    }
+
+    public File getBasedir() {
+        return basedir;
+    }
+
+    public void setBasedir(File basedir) {
+        this.basedir = basedir;
+    }
+
+    public String getResolvedIvyPattern() {
+        return resolvedIvyPattern;
+    }
+
+    public void setResolvedIvyPattern(String cacheResolvedIvyPattern) {
+        this.resolvedIvyPattern = cacheResolvedIvyPattern;
+    }
+
+    public String getResolvedIvyPropertiesPattern() {
+        return resolvedIvyPropertiesPattern;
+    }
+
+    public void setResolvedIvyPropertiesPattern(String cacheResolvedIvyPropertiesPattern) {
+        this.resolvedIvyPropertiesPattern = cacheResolvedIvyPropertiesPattern;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public File getResolvedIvyFileInCache(ModuleRevisionId mrid) {
+        String file = IvyPatternHelper.substitute(getResolvedIvyPattern(), mrid.getOrganisation(),
+            mrid.getName(), mrid.getRevision(), "ivy", "ivy", "xml");
+        return new File(getResolutionCacheRoot(), file);
+    }
+
+    public File getResolvedIvyPropertiesInCache(ModuleRevisionId mrid) {
+        String file = IvyPatternHelper.substitute(getResolvedIvyPropertiesPattern(),
+            mrid.getOrganisation(), mrid.getName(), mrid.getRevision(), "ivy", "ivy", "xml");
+        return new File(getResolutionCacheRoot(), file);
+    }
+
+    public File getConfigurationResolveReportInCache(String resolveId, String conf) {
+        return new File(getResolutionCacheRoot(), resolveId + "-" + conf + ".xml");
+    }
+
+    public File[] getConfigurationResolveReportsInCache(final String resolveId) {
+        final String prefix = resolveId + "-";
+        final String suffix = ".xml";
+        return getResolutionCacheRoot().listFiles(new FilenameFilter() {
+            public boolean accept(File dir, String name) {
+                return (name.startsWith(prefix) && name.endsWith(suffix));
+            }
+        });
+    }
+
+    public ModuleDescriptor getResolvedModuleDescriptor(ModuleRevisionId mrid)
+            throws ParseException, IOException {
+        File ivyFile = getResolvedIvyFileInCache(mrid);
+        if (!ivyFile.exists()) {
+            throw new IllegalStateException("Ivy file not found in cache for " + mrid + "!");
+        }
+
+        Properties paths = new Properties();
+
+        File parentsFile = getResolvedIvyPropertiesInCache(ModuleRevisionId.newInstance(mrid,
+            mrid.getRevision() + "-parents"));
+        if (parentsFile.exists()) {
+            FileInputStream in = new FileInputStream(parentsFile);
+            paths.load(in);
+            in.close();
+        }
+
+        ParserSettings pSettings = new CacheParserSettings(settings, paths);
+
+        URL ivyFileURL = ivyFile.toURI().toURL();
+        return getModuleDescriptorParser(ivyFile).parseDescriptor(pSettings, ivyFileURL, false);
+    }
+
+    /**
+     * Choose write module descriptor parser for a given moduleDescriptor
+     * 
+     * @param moduleDescriptorFile
+     *            a given module descriptor
+     * @return
+     */
+    protected ModuleDescriptorParser getModuleDescriptorParser(File moduleDescriptorFile) {
+        return XmlModuleDescriptorParser.getInstance();
+    }
+
+    public void saveResolvedModuleDescriptor(ModuleDescriptor md) throws ParseException,
+            IOException {
+        ModuleRevisionId mrevId = md.getResolvedModuleRevisionId();
+        File ivyFileInCache = getResolvedIvyFileInCache(mrevId);
+        md.toIvyFile(ivyFileInCache);
+
+        Properties paths = new Properties();
+        saveLocalParents(mrevId, md, ivyFileInCache, paths);
+
+        if (!paths.isEmpty()) {
+            File parentsFile = getResolvedIvyPropertiesInCache(ModuleRevisionId.newInstance(mrevId,
+                mrevId.getRevision() + "-parents"));
+            FileOutputStream out = new FileOutputStream(parentsFile);
+            paths.store(out, null);
+            out.close();
+        }
+    }
+
+    private void saveLocalParents(ModuleRevisionId baseMrevId, ModuleDescriptor md, File mdFile,
+            Properties paths) throws ParseException, IOException {
+        ExtendsDescriptor[] parents = md.getInheritedDescriptors();
+        for (int i = 0; i < parents.length; i++) {
+            if (!parents[i].isLocal()) {
+                // we store only local parents in the cache!
+                continue;
+            }
+
+            ModuleDescriptor parent = parents[i].getParentMd();
+            ModuleRevisionId pRevId = ModuleRevisionId.newInstance(baseMrevId,
+                baseMrevId.getRevision() + "-parent." + paths.size());
+            File parentFile = getResolvedIvyFileInCache(pRevId);
+            parent.toIvyFile(parentFile);
+
+            paths.setProperty(mdFile.getName() + "|" + parents[i].getLocation(),
+                parentFile.getAbsolutePath());
+            saveLocalParents(baseMrevId, parent, parentFile, paths);
+        }
+    }
+
+    public String toString() {
+        return name;
+    }
+
+    public void clean() {
+        FileUtil.forceDelete(getBasedir());
+    }
+
+    private static class CacheParserSettings implements ParserSettings {
+
+        private ParserSettings delegate;
+
+        private Map parentPaths;
+
+        public CacheParserSettings(ParserSettings delegate, Map parentPaths) {
+            this.delegate = delegate;
+            this.parentPaths = parentPaths;
+        }
+
+        public String substitute(String value) {
+            return delegate.substitute(value);
+        }
+
+        public Map substitute(Map strings) {
+            return delegate.substitute(strings);
+        }
+
+        public ResolutionCacheManager getResolutionCacheManager() {
+            return delegate.getResolutionCacheManager();
+        }
+
+        public ConflictManager getConflictManager(String name) {
+            return delegate.getConflictManager(name);
+        }
+
+        public PatternMatcher getMatcher(String matcherName) {
+            return delegate.getMatcher(matcherName);
+        }
+
+        public Namespace getNamespace(String namespace) {
+            return delegate.getNamespace(namespace);
+        }
+
+        public StatusManager getStatusManager() {
+            return delegate.getStatusManager();
+        }
+
+        public RelativeUrlResolver getRelativeUrlResolver() {
+            return new MapURLResolver(parentPaths, delegate.getRelativeUrlResolver());
+        }
+
+        public DependencyResolver getResolver(ModuleRevisionId mRevId) {
+            return delegate.getResolver(mRevId);
+        }
+
+        public File resolveFile(String filename) {
+            return delegate.resolveFile(filename);
+        }
+
+        public String getDefaultBranch(ModuleId moduleId) {
+            return delegate.getDefaultBranch(moduleId);
+        }
+
+        public Namespace getContextNamespace() {
+            return delegate.getContextNamespace();
+        }
+    }
+
+    private static class MapURLResolver extends RelativeUrlResolver {
+
+        private Map paths;
+
+        private RelativeUrlResolver delegate;
+
+        private MapURLResolver(Map paths, RelativeUrlResolver delegate) {
+            this.paths = paths;
+            this.delegate = delegate;
+        }
+
+        public URL getURL(URL context, String url) throws MalformedURLException {
+            String path = context.getPath();
+            if (path.indexOf('/') >= 0) {
+                String file = path.substring(path.lastIndexOf('/') + 1);
+
+                if (paths.containsKey(file + "|" + url)) {
+                    File result = new File(paths.get(file + "|" + url).toString());
+                    return result.toURI().toURL();
+                }
+            }
+
+            return delegate.getURL(context, url);
+        }
+    }
+}
diff --git a/src/java/org/apache/ivy/core/cache/DownloadListener.java b/src/java/org/apache/ivy/core/cache/DownloadListener.java
new file mode 100644
index 0000000..50f7741
--- /dev/null
+++ b/src/java/org/apache/ivy/core/cache/DownloadListener.java
@@ -0,0 +1,34 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.cache;
+
+import java.io.File;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.report.ArtifactDownloadReport;
+import org.apache.ivy.plugins.resolver.util.ResolvedResource;
+
+public interface DownloadListener {
+    public void needArtifact(RepositoryCacheManager cache, Artifact artifact);
+
+    public void startArtifactDownload(RepositoryCacheManager cache, ResolvedResource rres,
+            Artifact artifact, ArtifactOrigin origin);
+
+    public void endArtifactDownload(RepositoryCacheManager cache, Artifact artifact,
+            ArtifactDownloadReport adr, File archiveFile);
+}
diff --git a/src/java/org/apache/ivy/core/cache/ModuleDescriptorMemoryCache.java b/src/java/org/apache/ivy/core/cache/ModuleDescriptorMemoryCache.java
new file mode 100644
index 0000000..c4207a9
--- /dev/null
+++ b/src/java/org/apache/ivy/core/cache/ModuleDescriptorMemoryCache.java
@@ -0,0 +1,134 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.cache;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.ParseException;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.plugins.parser.ParserSettings;
+import org.apache.ivy.util.Message;
+
+/**
+ * Cache ModuleDescriptors so that when the same module is used twice (in multi-module build for
+ * instance), it is parsed only once. This cache is has a limited size, and keep the most recently
+ * used entries. The entry in the cache are invalidated if there is a change to one variable used in
+ * the module descriptor.
+ */
+class ModuleDescriptorMemoryCache {
+
+    private final int maxSize;
+
+    private final LinkedHashMap/* <File,CacheEntry> */valueMap;
+
+    /**
+     * Create a cache of the given size
+     * 
+     * @param size
+     */
+    public ModuleDescriptorMemoryCache(int size) {
+        this.maxSize = size;
+        this.valueMap = new LinkedHashMap(size);
+    }
+
+    public ModuleDescriptor get(File ivyFile, ParserSettings ivySettings, boolean validated,
+            ModuleDescriptorProvider mdProvider) throws ParseException, IOException {
+
+        ModuleDescriptor descriptor = getFromCache(ivyFile, ivySettings, validated);
+        if (descriptor == null) {
+            descriptor = getStale(ivyFile, ivySettings, validated, mdProvider);
+        }
+        return descriptor;
+    }
+
+    /**
+     * Get the module descriptor from the mdProvider and store it into the cache.
+     */
+    public ModuleDescriptor getStale(File ivyFile, ParserSettings ivySettings, boolean validated,
+            ModuleDescriptorProvider mdProvider) throws ParseException, IOException {
+        ParserSettingsMonitor settingsMonitor = new ParserSettingsMonitor(ivySettings);
+        ModuleDescriptor descriptor = mdProvider.provideModule(
+            settingsMonitor.getMonitoredSettings(), ivyFile, validated);
+        putInCache(ivyFile, settingsMonitor, validated, descriptor);
+        return descriptor;
+    }
+
+    ModuleDescriptor getFromCache(File ivyFile, ParserSettings ivySettings, boolean validated) {
+        if (maxSize <= 0) {
+            // cache is disbaled
+            return null;
+        }
+        CacheEntry entry = (CacheEntry) valueMap.get(ivyFile);
+        if (entry != null) {
+            if (entry.isStale(validated, ivySettings)) {
+                Message.debug("Entry is found in the ModuleDescriptorCache but entry should be "
+                        + "reevaluated : " + ivyFile);
+                valueMap.remove(ivyFile);
+                return null;
+            } else {
+                // Move the entry at the end of the list
+                valueMap.remove(ivyFile);
+                valueMap.put(ivyFile, entry);
+                Message.debug("Entry is found in the ModuleDescriptorCache : " + ivyFile);
+                return entry.md;
+            }
+        } else {
+            Message.debug("No entry is found in the ModuleDescriptorCache : " + ivyFile);
+            return null;
+        }
+    }
+
+    void putInCache(File url, ParserSettingsMonitor ivySettingsMonitor, boolean validated,
+            ModuleDescriptor descriptor) {
+        if (maxSize <= 0) {
+            // cache is disabled
+            return;
+        }
+        if (valueMap.size() >= maxSize) {
+            Message.debug("ModuleDescriptorCache is full, remove one entry");
+            Iterator it = valueMap.values().iterator();
+            it.next();
+            it.remove();
+        }
+        valueMap.put(url, new CacheEntry(descriptor, validated, ivySettingsMonitor));
+    }
+
+    private static class CacheEntry {
+        private final ModuleDescriptor md;
+
+        private final boolean validated;
+
+        private final ParserSettingsMonitor parserSettingsMonitor;
+
+        CacheEntry(ModuleDescriptor md, boolean validated,
+                ParserSettingsMonitor parserSettingsMonitor) {
+            this.md = md;
+            this.validated = validated;
+            this.parserSettingsMonitor = parserSettingsMonitor;
+        }
+
+        boolean isStale(boolean validated, ParserSettings newParserSettings) {
+            return (validated && !this.validated)
+                    || parserSettingsMonitor.hasChanged(newParserSettings);
+        }
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/cache/ModuleDescriptorProvider.java b/src/java/org/apache/ivy/core/cache/ModuleDescriptorProvider.java
new file mode 100644
index 0000000..96cae4d
--- /dev/null
+++ b/src/java/org/apache/ivy/core/cache/ModuleDescriptorProvider.java
@@ -0,0 +1,31 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.cache;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.ParseException;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.plugins.parser.ParserSettings;
+
+interface ModuleDescriptorProvider {
+
+    public ModuleDescriptor provideModule(ParserSettings ivySettings, File descriptorFile,
+            boolean validate) throws ParseException, IOException;
+}
diff --git a/src/java/org/apache/ivy/core/cache/ModuleDescriptorWriter.java b/src/java/org/apache/ivy/core/cache/ModuleDescriptorWriter.java
new file mode 100644
index 0000000..ee8542d
--- /dev/null
+++ b/src/java/org/apache/ivy/core/cache/ModuleDescriptorWriter.java
@@ -0,0 +1,30 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.cache;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.ParseException;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.plugins.resolver.util.ResolvedResource;
+
+public interface ModuleDescriptorWriter {
+    public void write(ResolvedResource originalMdResource, ModuleDescriptor md, File src, File dest)
+            throws IOException, ParseException;
+}
diff --git a/src/java/org/apache/ivy/core/cache/ParserSettingsMonitor.java b/src/java/org/apache/ivy/core/cache/ParserSettingsMonitor.java
new file mode 100644
index 0000000..58c7f31
--- /dev/null
+++ b/src/java/org/apache/ivy/core/cache/ParserSettingsMonitor.java
@@ -0,0 +1,154 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.cache;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.ivy.core.RelativeUrlResolver;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.module.status.StatusManager;
+import org.apache.ivy.plugins.conflict.ConflictManager;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+import org.apache.ivy.plugins.namespace.Namespace;
+import org.apache.ivy.plugins.parser.ParserSettings;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.apache.ivy.util.Message;
+
+/**
+ * Keep traces of the usage of a ParserSettings in order to check afterwards that the relevant
+ * settings didn't changed.
+ * <p>
+ * A ParserSettingsMonitor provide a ParserSettings that must be used in place of the orignal one.
+ * </p>
+ * <p>
+ * The current implementation consider that a settings changed iff one of the used variable has
+ * changed.
+ * </p>
+ */
+class ParserSettingsMonitor {
+
+    private ParserSettings delegatedSettings;
+
+    private final Map/* <String,String> */substitutes;
+
+    public ParserSettingsMonitor(ParserSettings settings) {
+        this.delegatedSettings = settings;
+        this.substitutes = new HashMap();
+    }
+
+    /**
+     * @return The parser settings that must be used in place of the orignal settings The returned
+     *         object delegates all the call to the original settings.
+     */
+    public ParserSettings getMonitoredSettings() {
+        return monitoredSettings;
+    }
+
+    /**
+     * Free the ressource used during the monitoring, keeping only the info required to evaluate
+     * hasChanged.
+     */
+    public void endMonitoring() {
+        monitoredSettings = null;
+        delegatedSettings = null;
+    }
+
+    /**
+     * Check if the newSettings is compatible with the original settings that has been monitored.
+     * Only the info that was actually used is compared.
+     */
+    public boolean hasChanged(ParserSettings newSettings) {
+        for (Iterator it = substitutes.entrySet().iterator(); it.hasNext();) {
+            Map.Entry entry = (Entry) it.next();
+            String key = (String) entry.getKey();
+            Object oldValue = entry.getValue();
+            String newValue = newSettings.substitute(key);
+            if (!oldValue.equals(newValue)) {
+                Message.debug("settings variable has changed for : " + entry.getKey());
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private ParserSettings monitoredSettings = new ParserSettings() {
+
+        public ConflictManager getConflictManager(String name) {
+            return delegatedSettings.getConflictManager(name);
+        }
+
+        public PatternMatcher getMatcher(String matcherName) {
+            return delegatedSettings.getMatcher(matcherName);
+        }
+
+        public Namespace getNamespace(String namespace) {
+            return delegatedSettings.getNamespace(namespace);
+        }
+
+        public RelativeUrlResolver getRelativeUrlResolver() {
+            return delegatedSettings.getRelativeUrlResolver();
+        }
+
+        public ResolutionCacheManager getResolutionCacheManager() {
+            return delegatedSettings.getResolutionCacheManager();
+        }
+
+        public DependencyResolver getResolver(ModuleRevisionId mRevId) {
+            return delegatedSettings.getResolver(mRevId);
+        }
+
+        public StatusManager getStatusManager() {
+            return delegatedSettings.getStatusManager();
+        }
+
+        public File resolveFile(String filename) {
+            return delegatedSettings.resolveFile(filename);
+        }
+
+        public String getDefaultBranch(ModuleId moduleId) {
+            return delegatedSettings.getDefaultBranch(moduleId);
+        }
+
+        public Namespace getContextNamespace() {
+            return delegatedSettings.getContextNamespace();
+        }
+
+        public Map substitute(Map strings) {
+            Map substituted = new LinkedHashMap();
+            for (Iterator it = strings.entrySet().iterator(); it.hasNext();) {
+                Map.Entry entry = (Map.Entry) it.next();
+                substituted.put(entry.getKey(), substitute((String) entry.getValue()));
+            }
+            return substituted;
+        }
+
+        public String substitute(String value) {
+            String r = delegatedSettings.substitute(value);
+            if (value != null && value != r) {
+                substitutes.put(value, r);
+            }
+            return r;
+        }
+    };
+}
diff --git a/src/java/org/apache/ivy/core/cache/RepositoryCacheManager.java b/src/java/org/apache/ivy/core/cache/RepositoryCacheManager.java
new file mode 100644
index 0000000..f6c5c24
--- /dev/null
+++ b/src/java/org/apache/ivy/core/cache/RepositoryCacheManager.java
@@ -0,0 +1,194 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.cache;
+
+import java.text.ParseException;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+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.apache.ivy.core.report.ArtifactDownloadReport;
+import org.apache.ivy.core.resolve.ResolvedModuleRevision;
+import org.apache.ivy.plugins.repository.ArtifactResourceResolver;
+import org.apache.ivy.plugins.repository.Repository;
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.plugins.repository.ResourceDownloader;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.apache.ivy.plugins.resolver.util.ResolvedResource;
+
+public interface RepositoryCacheManager {
+
+    /**
+     * Returns the name of the repository cache manager.
+     * 
+     * @return the name of the repository cache manager.
+     */
+    public abstract String getName();
+
+    /**
+     * Saves the information of which resolvers were used to resolve a module (both for metadata and
+     * artifact), so that this info can be loaded later (even after a jvm restart) for the use of
+     * {@link #findModuleInCache(DependencyDescriptor, CacheMetadataOptions, String)}.
+     * 
+     * @param md
+     *            the module descriptor resolved
+     * @param metadataResolverName
+     *            metadata resolver name
+     * @param artifactResolverName
+     *            artifact resolver name
+     */
+    public abstract void saveResolvers(ModuleDescriptor descriptor, String metadataResolverName,
+            String artifactResolverName);
+
+    /**
+     * Returns the artifact origin of the given artifact as saved in this cache.
+     * <p>
+     * If the origin is unknown, the returned ArtifactOrigin instance will return true when
+     * {@link ArtifactOrigin#isUnknown(ArtifactOrigin)} is called.
+     * 
+     * @param artifact
+     *            the artifact for which the saved artifact origin should be returned.
+     * @return the artifact origin of the given artifact as saved in this cache
+     */
+    public abstract ArtifactOrigin getSavedArtifactOrigin(Artifact artifact);
+
+    /**
+     * Search a module descriptor in cache for a mrid
+     * 
+     * @param dd
+     *            the dependency descriptor identifying the module to search
+     * @param requestedRevisionId
+     *            the requested dependency module revision id identifying the module to search
+     * @param options
+     *            options on how caching should be handled
+     * @param expectedResolver
+     *            the resolver with which the md in cache must have been resolved to be returned,
+     *            null if this doesn't matter
+     * @return the ResolvedModuleRevision corresponding to the module found, null if none correct
+     *         has been found in cache
+     */
+    public abstract ResolvedModuleRevision findModuleInCache(DependencyDescriptor dd,
+            ModuleRevisionId requestedRevisionId, CacheMetadataOptions options,
+            String expectedResolver);
+
+    /**
+     * Downloads an artifact to this cache.
+     * 
+     * @param artifact
+     *            the artifact to download
+     * @param resourceResolver
+     *            a resource resolver to use if the artifact needs to be resolved to a Resource for
+     *            downloading
+     * @param resourceDownloader
+     *            a resource downloader to use if actual download of the resource is needed
+     * @param options
+     *            a set of options to adjust the download
+     * @return a report indicating how the download was performed
+     */
+    public abstract ArtifactDownloadReport download(Artifact artifact,
+            ArtifactResourceResolver resourceResolver, ResourceDownloader resourceDownloader,
+            CacheDownloadOptions options);
+
+    /**
+     * Download some repository resource and put it in the cache.
+     * <p>
+     * If the cached version is considered enough up to date, no downloading is done.
+     * 
+     * @param resource
+     *            the resource of the file to put in cache
+     * @param name
+     *            the descriptive name of the resource (helps while manually looking into the cache
+     *            files)
+     * @param type
+     *            the type of the resource (helps while manually looking into the cache files)
+     * @param extension
+     *            the extension of the resource (helps while manually looking into the cache files)
+     * @param options
+     *            a set of options to adjust the download
+     * @param repository
+     *            the repository which resolve the content of the resource
+     * @return a report indicating how the download was performed
+     */
+    public ArtifactDownloadReport downloadRepositoryResource(Resource resource, String name,
+            String type, String extension, CacheResourceOptions options, Repository repository);
+
+    /**
+     * Caches an original module descriptor.
+     * <p>
+     * After this call, the original module descriptor file (with no modification nor conversion)
+     * should be available as a local file.
+     * </p>
+     * 
+     * @param resolver
+     *            the dependency resolver from which the cache request comes from
+     * @param orginalMetadataRef
+     *            a resolved resource pointing to the remote original module descriptor
+     * @param dd
+     *            the dependency descriptor for which the module descriptor should be cached
+     * @param requestedMetadataArtifact
+     *            the module descriptor artifact as requested originally
+     * @param downloader
+     *            a ResourceDownloader able to download the original module descriptor resource if
+     *            required by this cache implementation
+     * @param options
+     *            options to apply to cache this module descriptor
+     * @return a {@link ResolvedModuleRevision} representing the local cached module descriptor, or
+     *         null if it failed
+     * @throws ParseException
+     *             if an exception occurred while parsing the module descriptor
+     */
+    public ResolvedModuleRevision cacheModuleDescriptor(DependencyResolver resolver,
+            ResolvedResource orginalMetadataRef, DependencyDescriptor dd,
+            Artifact requestedMetadataArtifact, ResourceDownloader downloader,
+            CacheMetadataOptions options) throws ParseException;
+
+    /**
+     * Stores a standardized version of an original module descriptor in the cache for later use.
+     * 
+     * @param resolver
+     *            the dependency resolver from which the cache request comes from
+     * @param orginalMetadataRef
+     *            a resolved resource pointing to the remote original module descriptor
+     * @param requestedMetadataArtifact
+     *            the module descriptor artifact as requested originally
+     * @param rmr
+     *            the {@link ResolvedModuleRevision} representing the local cached module descriptor
+     * @param writer
+     *            a {@link ModuleDescriptorWriter} able to write the module descriptor to a stream.
+     */
+    public void originalToCachedModuleDescriptor(DependencyResolver resolver,
+            ResolvedResource orginalMetadataRef, Artifact requestedMetadataArtifact,
+            ResolvedModuleRevision rmr, ModuleDescriptorWriter writer);
+
+    /**
+     * Cleans the whole cache.
+     */
+    public void clean();
+
+    /**
+     * Caches a dynamic revision constraint resolution.
+     * 
+     * @param dynamicMrid
+     *            the dynamic module revision id
+     * @param revision
+     *            the resolved revision
+     */
+    public void saveResolvedRevision(ModuleRevisionId dynamicMrid, String revision);
+
+}
diff --git a/src/java/org/apache/ivy/core/cache/ResolutionCacheManager.java b/src/java/org/apache/ivy/core/cache/ResolutionCacheManager.java
new file mode 100644
index 0000000..9ba33d3
--- /dev/null
+++ b/src/java/org/apache/ivy/core/cache/ResolutionCacheManager.java
@@ -0,0 +1,48 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.cache;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.ParseException;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+
+public interface ResolutionCacheManager {
+
+    File getResolutionCacheRoot();
+
+    File getResolvedIvyFileInCache(ModuleRevisionId mrid);
+
+    File getResolvedIvyPropertiesInCache(ModuleRevisionId mrid);
+
+    File getConfigurationResolveReportInCache(String resolveId, String conf);
+
+    File[] getConfigurationResolveReportsInCache(final String resolveId);
+
+    ModuleDescriptor getResolvedModuleDescriptor(ModuleRevisionId mrid) throws ParseException,
+            IOException;
+
+    void saveResolvedModuleDescriptor(ModuleDescriptor md) throws ParseException, IOException;
+
+    /**
+     * Cleans the whole cache.
+     */
+    void clean();
+}
diff --git a/src/java/org/apache/ivy/core/check/CheckEngine.java b/src/java/org/apache/ivy/core/check/CheckEngine.java
new file mode 100644
index 0000000..34d6323
--- /dev/null
+++ b/src/java/org/apache/ivy/core/check/CheckEngine.java
@@ -0,0 +1,135 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.check;
+
+import java.io.IOException;
+import java.net.URL;
+import java.text.ParseException;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.resolve.ResolveData;
+import org.apache.ivy.core.resolve.ResolveEngine;
+import org.apache.ivy.core.resolve.ResolveOptions;
+import org.apache.ivy.core.resolve.ResolvedModuleRevision;
+import org.apache.ivy.plugins.parser.ModuleDescriptorParserRegistry;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.apache.ivy.util.Message;
+
+public class CheckEngine {
+    private CheckEngineSettings settings;
+
+    private ResolveEngine resolveEngine;
+
+    public CheckEngine(CheckEngineSettings settings, ResolveEngine resolveEngine) {
+        this.settings = settings;
+        this.resolveEngine = resolveEngine;
+    }
+
+    /**
+     * Checks the given ivy file using current settings to see if all dependencies are available,
+     * with good confs. If a resolver name is given, it also checks that the declared publications
+     * are available in the corresponding resolver. Note that the check is not performed
+     * recursively, i.e. if a dependency has itself dependencies badly described or not available,
+     * this check will not discover it.
+     */
+    public boolean check(URL ivyFile, String resolvername) {
+        try {
+            boolean result = true;
+            // parse ivy file
+            ModuleDescriptor md = ModuleDescriptorParserRegistry.getInstance().parseDescriptor(
+                settings, ivyFile, settings.doValidate());
+
+            // check publications if possible
+            if (resolvername != null) {
+                DependencyResolver resolver = settings.getResolver(resolvername);
+                String[] confs = md.getConfigurationsNames();
+                Set artifacts = new HashSet();
+                for (int i = 0; i < confs.length; i++) {
+                    artifacts.addAll(Arrays.asList(md.getArtifacts(confs[i])));
+                }
+                for (Iterator iter = artifacts.iterator(); iter.hasNext();) {
+                    Artifact art = (Artifact) iter.next();
+                    if (!resolver.exists(art)) {
+                        Message.info("declared publication not found: " + art);
+                        result = false;
+                    }
+                }
+            }
+
+            // check dependencies
+            DependencyDescriptor[] dds = md.getDependencies();
+            ResolveData data = new ResolveData(resolveEngine, new ResolveOptions());
+            for (int i = 0; i < dds.length; i++) {
+                // check master confs
+                String[] masterConfs = dds[i].getModuleConfigurations();
+                for (int j = 0; j < masterConfs.length; j++) {
+                    if (!"*".equals(masterConfs[j].trim())
+                            && md.getConfiguration(masterConfs[j]) == null) {
+                        Message.info("dependency required in non existing conf for " + ivyFile
+                                + " \n\tin " + dds[i] + ": " + masterConfs[j]);
+                        result = false;
+                    }
+                }
+                // resolve
+                DependencyResolver resolver = settings
+                        .getResolver(dds[i].getDependencyRevisionId());
+                ResolvedModuleRevision rmr = resolver.getDependency(dds[i], data);
+                if (rmr == null) {
+                    Message.info("dependency not found in " + ivyFile + ":\n\t" + dds[i]);
+                    result = false;
+                } else {
+                    String[] depConfs = dds[i].getDependencyConfigurations(md
+                            .getConfigurationsNames());
+                    for (int j = 0; j < depConfs.length; j++) {
+                        if (!Arrays.asList(rmr.getDescriptor().getConfigurationsNames()).contains(
+                            depConfs[j])) {
+                            Message.info("dependency configuration is missing for " + ivyFile
+                                    + "\n\tin " + dds[i] + ": " + depConfs[j]);
+                            result = false;
+                        }
+                        Artifact[] arts = rmr.getDescriptor().getArtifacts(depConfs[j]);
+                        for (int k = 0; k < arts.length; k++) {
+                            if (!resolver.exists(arts[k])) {
+                                Message.info("dependency artifact is missing for " + ivyFile
+                                        + "\n\t in " + dds[i] + ": " + arts[k]);
+                                result = false;
+                            }
+                        }
+                    }
+                }
+            }
+            return result;
+        } catch (ParseException e) {
+            Message.info("parse problem on " + ivyFile, e);
+            return false;
+        } catch (IOException e) {
+            Message.info("io problem on " + ivyFile, e);
+            return false;
+        } catch (Exception e) {
+            Message.info("problem on " + ivyFile, e);
+            return false;
+        }
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/check/CheckEngineSettings.java b/src/java/org/apache/ivy/core/check/CheckEngineSettings.java
new file mode 100644
index 0000000..c700795
--- /dev/null
+++ b/src/java/org/apache/ivy/core/check/CheckEngineSettings.java
@@ -0,0 +1,29 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.check;
+
+import org.apache.ivy.plugins.parser.ParserSettings;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+
+public interface CheckEngineSettings extends ParserSettings {
+
+    boolean doValidate();
+
+    DependencyResolver getResolver(String resolvername);
+
+}
diff --git a/src/java/org/apache/ivy/core/deliver/DefaultPublishingDRResolver.java b/src/java/org/apache/ivy/core/deliver/DefaultPublishingDRResolver.java
new file mode 100644
index 0000000..50f26e5
--- /dev/null
+++ b/src/java/org/apache/ivy/core/deliver/DefaultPublishingDRResolver.java
@@ -0,0 +1,28 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.deliver;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+
+public class DefaultPublishingDRResolver implements PublishingDependencyRevisionResolver {
+    public String resolve(ModuleDescriptor published, String publishedStatus,
+            ModuleRevisionId depMrid, String status) {
+        return depMrid.getRevision();
+    }
+}
diff --git a/src/java/org/apache/ivy/core/deliver/DeliverEngine.java b/src/java/org/apache/ivy/core/deliver/DeliverEngine.java
new file mode 100644
index 0000000..05ac1c7
--- /dev/null
+++ b/src/java/org/apache/ivy/core/deliver/DeliverEngine.java
@@ -0,0 +1,214 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.deliver;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.text.ParseException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.core.cache.ResolutionCacheManager;
+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.apache.ivy.plugins.parser.xml.UpdateOptions;
+import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorUpdater;
+import org.apache.ivy.plugins.report.XmlReportParser;
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.util.ConfigurationUtils;
+import org.apache.ivy.util.Message;
+import org.xml.sax.SAXException;
+
+public class DeliverEngine {
+    private DeliverEngineSettings settings;
+
+    public DeliverEngine(DeliverEngineSettings settings) {
+        this.settings = settings;
+    }
+
+    /**
+     * Delivers a resolved ivy file based upon last resolve call status. If resolve report file
+     * cannot be found in cache, then it throws an IllegalStateException (maybe resolve has not been
+     * called before ?).
+     * 
+     * @param revision
+     *            the revision to which the module should be delivered
+     * @param destIvyPattern
+     *            the pattern to which the delivered ivy file should be written
+     * @param options
+     *            the options with which deliver should be done
+     */
+    public void deliver(String revision, String destIvyPattern, DeliverOptions options)
+            throws IOException, ParseException {
+        String resolveId = options.getResolveId();
+        if (resolveId == null) {
+            throw new IllegalArgumentException("A resolveId must be specified for delivering.");
+        }
+        File[] files = getCache().getConfigurationResolveReportsInCache(resolveId);
+        if (files.length == 0) {
+            throw new IllegalStateException("No previous resolve found for id '" + resolveId
+                    + "' Please resolve dependencies before delivering.");
+        }
+        XmlReportParser parser = new XmlReportParser();
+        parser.parse(files[0]);
+        ModuleRevisionId mrid = parser.getResolvedModule();
+        deliver(mrid, revision, destIvyPattern, options);
+    }
+
+    private ResolutionCacheManager getCache() {
+        return settings.getResolutionCacheManager();
+    }
+
+    /**
+     * Delivers a resolved ivy file based upon last resolve call status. If resolve report file
+     * cannot be found in cache, then it throws an IllegalStateException (maybe resolve has not been
+     * called before ?).
+     * 
+     * @param mrid
+     *            the module revision id of the module to deliver
+     * @param revision
+     *            the revision to which the module should be delivered
+     * @param destIvyPattern
+     *            the pattern to which the delivered ivy file should be written
+     * @param options
+     *            the options with which deliver should be done
+     */
+    public void deliver(ModuleRevisionId mrid, String revision, String destIvyPattern,
+            DeliverOptions options) throws IOException, ParseException {
+        Message.info(":: delivering :: " + mrid + " :: " + revision + " :: " + options.getStatus()
+                + " :: " + options.getPubdate());
+        Message.verbose("\toptions = " + options);
+        long start = System.currentTimeMillis();
+        destIvyPattern = settings.substitute(destIvyPattern);
+
+        // 1) find the resolved module descriptor in cache
+        ModuleDescriptor md = getCache().getResolvedModuleDescriptor(mrid);
+        md.setResolvedModuleRevisionId(ModuleRevisionId.newInstance(md.getModuleRevisionId(),
+            options.getPubBranch() == null ? mrid.getBranch() : options.getPubBranch(), revision));
+        md.setResolvedPublicationDate(options.getPubdate());
+
+        // 2) parse resolvedRevisions From properties file
+        Map resolvedRevisions = new HashMap(); // Map (ModuleId -> String revision)
+        Map resolvedBranches = new HashMap(); // Map (ModuleId -> String branch)
+        Map dependenciesStatus = new HashMap(); // Map (ModuleId -> String status)
+        File ivyProperties = getCache().getResolvedIvyPropertiesInCache(mrid);
+        if (!ivyProperties.exists()) {
+            throw new IllegalStateException("ivy properties not found in cache for " + mrid
+                    + "; please resolve dependencies before delivering!");
+        }
+        Properties props = new Properties();
+        FileInputStream in = new FileInputStream(ivyProperties);
+        props.load(in);
+        in.close();
+
+        for (Iterator iter = props.keySet().iterator(); iter.hasNext();) {
+            String depMridStr = (String) iter.next();
+            String[] parts = props.getProperty(depMridStr).split(" ");
+            ModuleRevisionId decodedMrid = ModuleRevisionId.decode(depMridStr);
+            if (options.isResolveDynamicRevisions()) {
+                resolvedRevisions.put(decodedMrid, parts[0]);
+                if (parts.length >= 4) {
+                    if (parts[3] != null && !"null".equals(parts[3])) {
+                        resolvedBranches.put(decodedMrid, parts[3]);
+                    }
+                }
+            }
+            dependenciesStatus.put(decodedMrid, parts[1]);
+
+            if (options.isReplaceForcedRevisions()) {
+                if (parts.length <= 2) {
+                    // maybe the properties file was generated by an older Ivy version
+                    // so it is possible that this part doesn't exist.
+                    throw new IllegalStateException("ivy properties file generated by an older"
+                            + " version of Ivy which doesn't support replacing forced revisions!");
+                }
+
+                resolvedRevisions.put(decodedMrid, parts[2]);
+            }
+        }
+
+        // 3) use pdrResolver to resolve dependencies info
+        Map resolvedDependencies = new HashMap(); // Map (ModuleRevisionId -> String revision)
+        DependencyDescriptor[] dependencies = md.getDependencies();
+        for (int i = 0; i < dependencies.length; i++) {
+            String rev = (String) resolvedRevisions.get(dependencies[i].getDependencyRevisionId());
+            if (rev == null) {
+                rev = dependencies[i].getDependencyRevisionId().getRevision();
+            }
+            String bra = (String) resolvedBranches.get(dependencies[i].getDependencyRevisionId());
+            if (bra == null || "null".equals(bra)) {
+                bra = dependencies[i].getDependencyRevisionId().getBranch();
+            }
+            String depStatus = (String) dependenciesStatus.get(dependencies[i]
+                    .getDependencyRevisionId());
+            ModuleRevisionId mrid2 = null;
+            if (bra == null) {
+                mrid2 = ModuleRevisionId
+                        .newInstance(dependencies[i].getDependencyRevisionId(), rev);
+            } else {
+                mrid2 = ModuleRevisionId.newInstance(dependencies[i].getDependencyRevisionId(),
+                    bra, rev);
+            }
+            resolvedDependencies.put(dependencies[i].getDependencyRevisionId(), options
+                    .getPdrResolver().resolve(md, options.getStatus(), mrid2, depStatus));
+        }
+
+        // 4) copy the source resolved ivy to the destination specified,
+        // updating status, revision and dependency revisions obtained by
+        // PublishingDependencyRevisionResolver
+        File publishedIvy = settings.resolveFile(IvyPatternHelper.substitute(destIvyPattern,
+            md.getResolvedModuleRevisionId()));
+        Message.info("\tdelivering ivy file to " + publishedIvy);
+
+        String[] confs = ConfigurationUtils.replaceWildcards(options.getConfs(), md);
+        Set confsToRemove = new HashSet(Arrays.asList(md.getConfigurationsNames()));
+        confsToRemove.removeAll(Arrays.asList(confs));
+
+        try {
+            UpdateOptions opts = new UpdateOptions()
+                    .setSettings(settings)
+                    .setResolvedRevisions(resolvedDependencies)
+                    .setStatus(options.getStatus())
+                    .setRevision(revision)
+                    .setBranch(options.getPubBranch())
+                    .setPubdate(options.getPubdate())
+                    .setGenerateRevConstraint(options.isGenerateRevConstraint())
+                    .setMerge(options.isMerge())
+                    .setMergedDescriptor(md)
+                    .setConfsToExclude(
+                        (String[]) confsToRemove.toArray(new String[confsToRemove.size()]));
+            if (!resolvedBranches.isEmpty()) {
+                opts = opts.setResolvedBranches(resolvedBranches);
+            }
+            Resource res = md.getResource();
+            XmlModuleDescriptorUpdater.update(res.openStream(), res, publishedIvy, opts);
+        } catch (SAXException ex) {
+            throw new RuntimeException("bad ivy file in cache for " + mrid, ex);
+        }
+
+        Message.verbose("\tdeliver done (" + (System.currentTimeMillis() - start) + "ms)");
+    }
+}
diff --git a/src/java/org/apache/ivy/core/deliver/DeliverEngineSettings.java b/src/java/org/apache/ivy/core/deliver/DeliverEngineSettings.java
new file mode 100644
index 0000000..2589f33
--- /dev/null
+++ b/src/java/org/apache/ivy/core/deliver/DeliverEngineSettings.java
@@ -0,0 +1,26 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.deliver;
+
+import org.apache.ivy.plugins.parser.ParserSettings;
+
+public interface DeliverEngineSettings extends ParserSettings {
+
+    String substitute(String destIvyPattern);
+
+}
diff --git a/src/java/org/apache/ivy/core/deliver/DeliverOptions.java b/src/java/org/apache/ivy/core/deliver/DeliverOptions.java
new file mode 100644
index 0000000..577e3dc
--- /dev/null
+++ b/src/java/org/apache/ivy/core/deliver/DeliverOptions.java
@@ -0,0 +1,270 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.deliver;
+
+import java.util.Date;
+
+import org.apache.ivy.core.settings.IvySettings;
+
+/**
+ * A set of options used to do a deliver.
+ */
+public class DeliverOptions {
+    private String status;
+
+    private Date pubdate;
+
+    private PublishingDependencyRevisionResolver pdrResolver = new DefaultPublishingDRResolver();
+
+    private boolean validate = true;
+
+    private boolean resolveDynamicRevisions = true;
+
+    private boolean replaceForcedRevisions = false;
+
+    private String resolveId;
+
+    private String[] confs;
+
+    private String pubBranch;
+
+    /**
+     * True to indicate that the revConstraint attribute should be generated if applicable, false to
+     * never generate the revConstraint attribute.
+     */
+    private boolean generateRevConstraint = true;
+
+    /** true to merge parent descriptor elements into delivered child descriptor */
+    private boolean merge = true;
+
+    /**
+     * Returns an instance of DeliverOptions with options corresponding to default values taken from
+     * the given settings.
+     * 
+     * @param settings
+     *            The settings to use to get default option values
+     * @return a DeliverOptions instance ready to be used or customized
+     */
+    public static DeliverOptions newInstance(IvySettings settings) {
+        return new DeliverOptions(null, new Date(), new DefaultPublishingDRResolver(),
+                settings.doValidate(), true, null);
+    }
+
+    /**
+     * Creates an instance of DeliverOptions which require to be configured using the appropriate
+     * setters.
+     */
+    public DeliverOptions() {
+    }
+
+    /**
+     * Creates an instance of DeliverOptions with all options explicitly set.
+     */
+    public DeliverOptions(String status, Date pubDate,
+            PublishingDependencyRevisionResolver pdrResolver, boolean validate,
+            boolean resolveDynamicRevisions, String[] confs) {
+        this.status = status;
+        this.pubdate = pubDate;
+        this.pdrResolver = pdrResolver;
+        this.validate = validate;
+        this.resolveDynamicRevisions = resolveDynamicRevisions;
+        this.confs = confs;
+    }
+
+    /**
+     * Return the pdrResolver that will be used during deliver for each dependency to get its
+     * published information. This can particularly useful when the deliver is made for a release,
+     * and when we wish to deliver each dependency which is still in integration. The
+     * PublishingDependencyRevisionResolver can then do the delivering work for the dependency and
+     * return the new (delivered) dependency info (with the delivered revision). Note that
+     * PublishingDependencyRevisionResolver is only called for each <b>direct</b> dependency.
+     * 
+     * @return the pdrResolver that will be used during deliver
+     */
+    public PublishingDependencyRevisionResolver getPdrResolver() {
+        return pdrResolver;
+    }
+
+    /**
+     * Sets the pdrResolver that will be used during deliver for each dependency to get its
+     * published information. This can particularly useful when the deliver is made for a release,
+     * and when we wish to deliver each dependency which is still in integration. The
+     * PublishingDependencyRevisionResolver can then do the delivering work for the dependency and
+     * return the new (delivered) dependency info (with the delivered revision). Note that
+     * PublishingDependencyRevisionResolver is only called for each <b>direct</b> dependency.
+     * 
+     * @return the instance of DeliverOptions on which the method has been called, for easy method
+     *         chaining
+     */
+    public DeliverOptions setPdrResolver(PublishingDependencyRevisionResolver pdrResolver) {
+        this.pdrResolver = pdrResolver;
+        return this;
+    }
+
+    public boolean isResolveDynamicRevisions() {
+        return resolveDynamicRevisions;
+    }
+
+    public DeliverOptions setResolveDynamicRevisions(boolean resolveDynamicRevisions) {
+        this.resolveDynamicRevisions = resolveDynamicRevisions;
+        return this;
+    }
+
+    public boolean isReplaceForcedRevisions() {
+        return replaceForcedRevisions;
+    }
+
+    public DeliverOptions setReplaceForcedRevisions(boolean replaceForcedRevisions) {
+        this.replaceForcedRevisions = replaceForcedRevisions;
+        return this;
+    }
+
+    public boolean isValidate() {
+        return validate;
+    }
+
+    public DeliverOptions setValidate(boolean validate) {
+        this.validate = validate;
+        return this;
+    }
+
+    public Date getPubdate() {
+        return pubdate;
+    }
+
+    public DeliverOptions setPubdate(Date pubdate) {
+        this.pubdate = pubdate;
+        return this;
+    }
+
+    /**
+     * Returns the status to which the module should be delivered, or null if the current status
+     * should be kept.
+     * 
+     * @return the status to which the module should be delivered
+     */
+    public String getStatus() {
+        return status;
+    }
+
+    /**
+     * Sets the status to which the module should be delivered, use null if the current status
+     * should be kept.
+     * 
+     * @return the instance of DeliverOptions on which the method has been called, for easy method
+     *         chaining
+     */
+    public DeliverOptions setStatus(String status) {
+        this.status = status;
+        return this;
+    }
+
+    /**
+     * Returns the id of a previous resolve to use for delivering.
+     * 
+     * @return the id of a previous resolve
+     */
+    public String getResolveId() {
+        return resolveId;
+    }
+
+    /**
+     * Sets the id of a previous resolve to use for delivering.
+     * 
+     * @param resolveId
+     *            the id of a previous resolve
+     * @return the instance of DeliverOptions on which the method has been called, for easy method
+     *         chaining
+     */
+    public DeliverOptions setResolveId(String resolveId) {
+        this.resolveId = resolveId;
+        return this;
+    }
+
+    /**
+     * Return the configurations which must be deliverd. Returns <tt>null</tt> if all configurations
+     * has to be deliverd. Attention: the returned array can contain wildcards!
+     * 
+     * @return the configurations to deliver
+     */
+    public String[] getConfs() {
+        return confs;
+    }
+
+    /**
+     * Sets the configurations to deliver.
+     * 
+     * @param confs
+     *            the configurations to deliver
+     * @return the instance of DeliverOptions on which the method has been called, for easy method
+     *         chaining
+     */
+    public DeliverOptions setConfs(String[] confs) {
+        this.confs = confs;
+        return this;
+    }
+
+    /**
+     * Returns the branch with which the Ivy file should be delivered, or <code>null</code> if
+     * branch info shouldn't be changed.
+     * 
+     * @return the branch with which the Ivy file should be delivered
+     */
+    public String getPubBranch() {
+        return pubBranch;
+    }
+
+    /**
+     * Sets the branch with which the Ivy file should be delivered.
+     * 
+     * @param pubBranch
+     *            the branch with which the Ivy file should be delivered
+     * @return the instance of DeliverOptions on which the method has been called, for easy method
+     *         chaining
+     */
+    public DeliverOptions setPubBranch(String pubBranch) {
+        this.pubBranch = pubBranch;
+        return this;
+    }
+
+    public boolean isGenerateRevConstraint() {
+        return generateRevConstraint;
+    }
+
+    public DeliverOptions setGenerateRevConstraint(boolean generateRevConstraint) {
+        this.generateRevConstraint = generateRevConstraint;
+        return this;
+    }
+
+    public boolean isMerge() {
+        return merge;
+    }
+
+    public DeliverOptions setMerge(boolean merge) {
+        this.merge = merge;
+        return this;
+    }
+
+    public String toString() {
+        return "status=" + status + " pubdate=" + pubdate + " validate=" + validate
+                + " resolveDynamicRevisions=" + resolveDynamicRevisions + " merge=" + merge
+                + " resolveId=" + resolveId + " pubBranch=" + pubBranch;
+
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/deliver/PublishingDependencyRevisionResolver.java b/src/java/org/apache/ivy/core/deliver/PublishingDependencyRevisionResolver.java
new file mode 100644
index 0000000..aa632e3
--- /dev/null
+++ b/src/java/org/apache/ivy/core/deliver/PublishingDependencyRevisionResolver.java
@@ -0,0 +1,41 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.deliver;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+
+/**
+ *
+ */
+public interface PublishingDependencyRevisionResolver {
+
+    /**
+     * Returns the revision of the dependency for the publishing of the 'published' module in
+     * 'publishedStatus' status.
+     * 
+     * @param published
+     * @param publishedStatus
+     * @param depMrid
+     * @param status
+     * @return the revision of the dependency
+     */
+    String resolve(ModuleDescriptor published, String publishedStatus, ModuleRevisionId depMrid,
+            String status);
+
+}
diff --git a/src/java/org/apache/ivy/core/event/EventManager.java b/src/java/org/apache/ivy/core/event/EventManager.java
new file mode 100644
index 0000000..743b7d4
--- /dev/null
+++ b/src/java/org/apache/ivy/core/event/EventManager.java
@@ -0,0 +1,95 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.event;
+
+import java.util.Arrays;
+
+import javax.swing.event.EventListenerList;
+
+import org.apache.ivy.plugins.repository.TransferEvent;
+import org.apache.ivy.plugins.repository.TransferListener;
+import org.apache.ivy.util.filter.Filter;
+
+public class EventManager implements TransferListener {
+
+    private EventListenerList listeners = new EventListenerList();
+
+    public void addIvyListener(IvyListener listener) {
+        listeners.add(IvyListener.class, listener);
+    }
+
+    public void addIvyListener(IvyListener listener, String eventName) {
+        addIvyListener(listener, new IvyEventFilter(eventName, null, null));
+    }
+
+    public void addIvyListener(IvyListener listener, Filter filter) {
+        listeners.add(IvyListener.class, new FilteredIvyListener(listener, filter));
+    }
+
+    public void removeIvyListener(IvyListener listener) {
+        listeners.remove(IvyListener.class, listener);
+        IvyListener[] listeners = this.listeners.getListeners(IvyListener.class);
+        for (int i = 0; i < listeners.length; i++) {
+            if (listeners[i] instanceof FilteredIvyListener) {
+                if (listener.equals(((FilteredIvyListener) listeners[i]).getIvyListener())) {
+                    this.listeners.remove(IvyListener.class, listeners[i]);
+                }
+            }
+        }
+    }
+
+    public boolean hasIvyListener(IvyListener listener) {
+        return Arrays.asList(listeners.getListeners(IvyListener.class)).contains(listener);
+    }
+
+    public void fireIvyEvent(IvyEvent evt) {
+        Object[] listeners = this.listeners.getListenerList();
+        for (int i = listeners.length - 2; i >= 0; i -= 2) {
+            if (listeners[i] == IvyListener.class) {
+                ((IvyListener) listeners[i + 1]).progress(evt);
+            }
+        }
+    }
+
+    public void addTransferListener(TransferListener listener) {
+        listeners.add(TransferListener.class, listener);
+    }
+
+    public void removeTransferListener(TransferListener listener) {
+        listeners.remove(TransferListener.class, listener);
+    }
+
+    public boolean hasTransferListener(TransferListener listener) {
+        return Arrays.asList(listeners.getListeners(TransferListener.class)).contains(listener);
+    }
+
+    protected void fireTransferEvent(TransferEvent evt) {
+        Object[] listeners = this.listeners.getListenerList();
+        for (int i = listeners.length - 2; i >= 0; i -= 2) {
+            if (listeners[i] == TransferListener.class) {
+                ((TransferListener) listeners[i + 1]).transferProgress(evt);
+            }
+        }
+    }
+
+    public void transferProgress(TransferEvent evt) {
+        fireTransferEvent(evt);
+        fireIvyEvent(evt);
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/event/FilteredIvyListener.java b/src/java/org/apache/ivy/core/event/FilteredIvyListener.java
new file mode 100644
index 0000000..e52512d
--- /dev/null
+++ b/src/java/org/apache/ivy/core/event/FilteredIvyListener.java
@@ -0,0 +1,46 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.event;
+
+import org.apache.ivy.util.filter.Filter;
+
+public class FilteredIvyListener implements IvyListener {
+    private IvyListener listener;
+
+    private Filter filter;
+
+    public FilteredIvyListener(IvyListener listener, Filter filter) {
+        this.listener = listener;
+        this.filter = filter;
+    }
+
+    public IvyListener getIvyListener() {
+        return listener;
+    }
+
+    public Filter getFilter() {
+        return filter;
+    }
+
+    public void progress(IvyEvent event) {
+        if (filter.accept(event)) {
+            listener.progress(event);
+        }
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/event/IvyEvent.java b/src/java/org/apache/ivy/core/event/IvyEvent.java
new file mode 100644
index 0000000..b6b938f
--- /dev/null
+++ b/src/java/org/apache/ivy/core/event/IvyEvent.java
@@ -0,0 +1,127 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.event;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.ivy.core.IvyContext;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.util.StringUtils;
+
+/**
+ * The root of all ivy events Any ivy event knows which ivy instance triggered the event (the
+ * source) and also has a name and a map of attributes. The name of the event represents the event
+ * type, usually there is a one - one mapping between event names and IvyEvent subclass, even if
+ * this is not mandatory. Example: pre-resolve pre-resolve-dependency post-download The map of
+ * attributes is a Map from String keys to String values. It is especially useful to filter events,
+ * and to get some of their essential data in some context where access to Java types is not easy
+ * (in an ant build file, for example), Example: pre-resolve (organisation=foo, module=bar,
+ * revision=1.0, conf=default) post-download (organisation=foo, module=bar, revision=1.0,
+ * artifact=foo-test, type=jar, ext=jar)
+ */
+public class IvyEvent {
+    private EventManager source;
+
+    private String name;
+
+    private Map attributes = new HashMap();
+
+    protected IvyEvent(String name) {
+        this.source = IvyContext.getContext().getEventManager();
+        this.name = name;
+    }
+
+    /**
+     * Should only be called during event object construction, since events should be immutable
+     * 
+     * @param key
+     * @param value
+     */
+    protected void addAttribute(String key, String value) {
+        attributes.put(key, value);
+    }
+
+    protected void addMDAttributes(ModuleDescriptor md) {
+        addMridAttributes(md.getResolvedModuleRevisionId());
+    }
+
+    protected void addMridAttributes(ModuleRevisionId mrid) {
+        addModuleIdAttributes(mrid.getModuleId());
+        addAttribute("revision", mrid.getRevision());
+        addAttribute("branch", mrid.getBranch());
+        addAttributes(mrid.getQualifiedExtraAttributes());
+        addAttributes(mrid.getExtraAttributes());
+    }
+
+    protected void addModuleIdAttributes(ModuleId moduleId) {
+        addAttribute("organisation", moduleId.getOrganisation());
+        addAttribute("module", moduleId.getName());
+    }
+
+    protected void addConfsAttribute(String[] confs) {
+        addAttribute("conf", StringUtils.join(confs, ", "));
+    }
+
+    protected void addAttributes(Map attributes) {
+        this.attributes.putAll(attributes);
+    }
+
+    public EventManager getSource() {
+        return source;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Returns the attributes of this event, as a Map(String->String)
+     * 
+     * @return the attributes of this event, as a Map(String->String)
+     */
+    public Map getAttributes() {
+        return new HashMap(attributes);
+    }
+
+    public String toString() {
+        return getName() + " " + getAttributes();
+    }
+
+    public boolean equals(Object obj) {
+        if (!(obj instanceof IvyEvent)) {
+            return false;
+        }
+        IvyEvent e = (IvyEvent) obj;
+
+        return getSource().equals(e.getSource()) && getName().equals(e.getName())
+                && attributes.equals(e.attributes);
+    }
+
+    public int hashCode() {
+        // CheckStyle:MagicNumber| OFF
+        int hash = 37;
+        hash = 13 * hash + getSource().hashCode();
+        hash = 13 * hash + getName().hashCode();
+        hash = 13 * hash + attributes.hashCode();
+        // CheckStyle:MagicNumber| ON
+        return hash;
+    }
+}
diff --git a/src/java/org/apache/ivy/core/event/IvyEventFilter.java b/src/java/org/apache/ivy/core/event/IvyEventFilter.java
new file mode 100644
index 0000000..d47059f
--- /dev/null
+++ b/src/java/org/apache/ivy/core/event/IvyEventFilter.java
@@ -0,0 +1,159 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.event;
+
+import org.apache.ivy.plugins.matcher.ExactPatternMatcher;
+import org.apache.ivy.plugins.matcher.Matcher;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+import org.apache.ivy.util.filter.AndFilter;
+import org.apache.ivy.util.filter.Filter;
+import org.apache.ivy.util.filter.NoFilter;
+import org.apache.ivy.util.filter.NotFilter;
+import org.apache.ivy.util.filter.OrFilter;
+
+/**
+ * A filter implementation filtering {@link IvyEvent} based upon an event name and a filter
+ * expression. The name will be matched against the event name using the {@link PatternMatcher} used
+ * to construct this object. The filter expression is a string describing how the event should be
+ * filtered according to its attributes values. The matching between the filter values and the event
+ * attribute values is done using the {@link PatternMatcher} used to construct this object. Here are
+ * some examples:
+ * <table>
+ * <tr>
+ * <td>expression</td>
+ * <td>effect</td>
+ * </tr>
+ * <tr>
+ * <td>type=zip</td>
+ * <td>accepts event with a type attribute matching zip</td>
+ * </tr>
+ * <tr>
+ * <td>type=zip,jar</td>
+ * <td>accepts event with a type attribute matching zip or jar</td>
+ * </tr>
+ * <tr>
+ * <td>type=src AND ext=zip</td>
+ * <td>accepts event with a type attribute matching src AND an ext attribute matching zip</td>
+ * </tr>
+ * <tr>
+ * <td>type=src OR ext=zip</td>
+ * <td>accepts event with a type attribute matching src OR an ext attribute matching zip</td>
+ * </tr>
+ * <tr>
+ * <td>NOT type=src</td>
+ * <td>accepts event with a type attribute NOT matching src</td>
+ * </tr>
+ * </table>
+ * Combination of these can be used, but no parentheses are supported right now, so only the default
+ * priority can be used. The priority order is this one: AND OR NOT = This means that artifact=foo
+ * AND ext=zip OR type=src will match event with artifact matching foo AND (ext matching zip OR type
+ * matching src)
+ * 
+ * @since 1.4
+ */
+public class IvyEventFilter implements Filter {
+    private static final String NOT = "NOT ";
+
+    private static final String OR = " OR ";
+
+    private static final String AND = " AND ";
+
+    private PatternMatcher matcher;
+
+    private Filter nameFilter;
+
+    private Filter attFilter;
+
+    public IvyEventFilter(String event, String filterExpression, PatternMatcher matcher) {
+        this.matcher = matcher == null ? ExactPatternMatcher.INSTANCE : matcher;
+        if (event == null) {
+            nameFilter = NoFilter.INSTANCE;
+        } else {
+            final Matcher eventNameMatcher = this.matcher.getMatcher(event);
+            nameFilter = new Filter() {
+                public boolean accept(Object o) {
+                    IvyEvent e = (IvyEvent) o;
+                    return eventNameMatcher.matches(e.getName());
+                }
+            };
+        }
+        attFilter = filterExpression == null || filterExpression.trim().length() == 0 ? NoFilter.INSTANCE
+                : parseExpression(filterExpression);
+    }
+
+    private Filter parseExpression(String filterExpression) {
+        // expressions handled for the moment: (informal grammar)
+        // EXP := SIMPLE_EXP | AND_EXP | OR_EXP | NOT_EXP
+        // AND_EXP := EXP && EXP
+        // OR_EXP := EXP || EXP
+        // NOT_EXP := ! EXP
+        // SIMPLE_EXP := attname = comma, separated, list, of, accepted, values
+        // example: organisation = foo && module = bar, baz
+        filterExpression = filterExpression.trim();
+        int index = filterExpression.indexOf(AND);
+        if (index == -1) {
+            index = filterExpression.indexOf(OR);
+            if (index == -1) {
+                if (filterExpression.startsWith(NOT)) {
+                    return new NotFilter(parseExpression(filterExpression.substring(NOT.length())));
+                } else {
+                    index = filterExpression.indexOf("=");
+                    if (index == -1) {
+                        throw new IllegalArgumentException("bad filter expression: "
+                                + filterExpression + ": no equal sign found");
+                    }
+                    final String attname = filterExpression.substring(0, index).trim();
+                    String[] values = filterExpression.substring(index + 1).trim().split(",");
+                    final Matcher[] matchers = new Matcher[values.length];
+                    for (int i = 0; i < values.length; i++) {
+                        matchers[i] = matcher.getMatcher(values[i].trim());
+                    }
+                    return new Filter() {
+                        public boolean accept(Object o) {
+                            IvyEvent e = (IvyEvent) o;
+                            String val = (String) e.getAttributes().get(attname);
+                            if (val == null) {
+                                return false;
+                            }
+                            for (int i = 0; i < matchers.length; i++) {
+                                if (matchers[i].matches(val)) {
+                                    return true;
+                                }
+                            }
+                            return false;
+                        }
+                    };
+                }
+            } else {
+                return new OrFilter(parseExpression(filterExpression.substring(0, index)),
+                        parseExpression(filterExpression.substring(index + OR.length())));
+            }
+        } else {
+            return new AndFilter(parseExpression(filterExpression.substring(0, index)),
+                    parseExpression(filterExpression.substring(index + AND.length())));
+        }
+    }
+
+    public boolean accept(Object o) {
+        if (!(o instanceof IvyEvent)) {
+            return false;
+        }
+        return nameFilter.accept(o) && attFilter.accept(o);
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/event/IvyListener.java b/src/java/org/apache/ivy/core/event/IvyListener.java
new file mode 100644
index 0000000..881b3cd
--- /dev/null
+++ b/src/java/org/apache/ivy/core/event/IvyListener.java
@@ -0,0 +1,24 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.event;
+
+import java.util.EventListener;
+
+public interface IvyListener extends EventListener {
+    public void progress(IvyEvent event);
+}
diff --git a/src/java/org/apache/ivy/core/event/download/DownloadEvent.java b/src/java/org/apache/ivy/core/event/download/DownloadEvent.java
new file mode 100644
index 0000000..5b160a3
--- /dev/null
+++ b/src/java/org/apache/ivy/core/event/download/DownloadEvent.java
@@ -0,0 +1,42 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.event.download;
+
+import org.apache.ivy.core.event.IvyEvent;
+import org.apache.ivy.core.module.descriptor.Artifact;
+
+public abstract class DownloadEvent extends IvyEvent {
+    private Artifact artifact;
+
+    public DownloadEvent(String name, Artifact artifact) {
+        super(name);
+        this.artifact = artifact;
+        addArtifactAttributes(this.artifact);
+    }
+
+    protected void addArtifactAttributes(Artifact artifact) {
+        addMridAttributes(artifact.getModuleRevisionId());
+        addAttributes(artifact.getAttributes());
+        addAttribute("metadata", String.valueOf(artifact.isMetadata()));
+    }
+
+    public Artifact getArtifact() {
+        return artifact;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/event/download/EndArtifactDownloadEvent.java b/src/java/org/apache/ivy/core/event/download/EndArtifactDownloadEvent.java
new file mode 100644
index 0000000..c22d4b4
--- /dev/null
+++ b/src/java/org/apache/ivy/core/event/download/EndArtifactDownloadEvent.java
@@ -0,0 +1,63 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.event.download;
+
+import java.io.File;
+
+import org.apache.ivy.core.cache.ArtifactOrigin;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.report.ArtifactDownloadReport;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+
+public class EndArtifactDownloadEvent extends DownloadEvent {
+    public static final String NAME = "post-download-artifact";
+
+    private DependencyResolver resolver;
+
+    private ArtifactDownloadReport report;
+
+    public EndArtifactDownloadEvent(DependencyResolver resolver, Artifact artifact,
+            ArtifactDownloadReport report, File dest) {
+        super(NAME, artifact);
+        this.resolver = resolver;
+        this.report = report;
+        addAttribute("resolver", this.resolver.getName());
+        addAttribute("status", this.report.getDownloadStatus().toString());
+        addAttribute("details", this.report.getDownloadDetails());
+        addAttribute("size", String.valueOf(this.report.getSize()));
+        addAttribute("file", dest.getAbsolutePath());
+        addAttribute("duration", String.valueOf(this.report.getDownloadTimeMillis()));
+        ArtifactOrigin origin = report.getArtifactOrigin();
+        if (!ArtifactOrigin.isUnknown(origin)) {
+            addAttribute("origin", origin.getLocation());
+            addAttribute("local", String.valueOf(origin.isLocal()));
+        } else {
+            addAttribute("origin", "");
+            addAttribute("local", "");
+        }
+    }
+
+    public ArtifactDownloadReport getReport() {
+        return report;
+    }
+
+    public DependencyResolver getResolver() {
+        return resolver;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/event/download/NeedArtifactEvent.java b/src/java/org/apache/ivy/core/event/download/NeedArtifactEvent.java
new file mode 100644
index 0000000..a1a9f2e
--- /dev/null
+++ b/src/java/org/apache/ivy/core/event/download/NeedArtifactEvent.java
@@ -0,0 +1,38 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.event.download;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+
+public class NeedArtifactEvent extends DownloadEvent {
+    public static final String NAME = "need-artifact";
+
+    private DependencyResolver resolver;
+
+    public NeedArtifactEvent(DependencyResolver resolver, Artifact artifact) {
+        super(NAME, artifact);
+        this.resolver = resolver;
+        addAttribute("resolver", this.resolver.getName());
+    }
+
+    public DependencyResolver getResolver() {
+        return resolver;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/event/download/PrepareDownloadEvent.java b/src/java/org/apache/ivy/core/event/download/PrepareDownloadEvent.java
new file mode 100644
index 0000000..a5df433
--- /dev/null
+++ b/src/java/org/apache/ivy/core/event/download/PrepareDownloadEvent.java
@@ -0,0 +1,36 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.event.download;
+
+import org.apache.ivy.core.event.IvyEvent;
+import org.apache.ivy.core.module.descriptor.Artifact;
+
+public class PrepareDownloadEvent extends IvyEvent {
+    public static final String NAME = "prepare-download";
+
+    private Artifact[] artifacts;
+
+    public PrepareDownloadEvent(Artifact[] artifacts) {
+        super(NAME);
+        this.artifacts = artifacts;
+    }
+
+    public Artifact[] getArtifacts() {
+        return artifacts;
+    }
+}
diff --git a/src/java/org/apache/ivy/core/event/download/StartArtifactDownloadEvent.java b/src/java/org/apache/ivy/core/event/download/StartArtifactDownloadEvent.java
new file mode 100644
index 0000000..a30d67c
--- /dev/null
+++ b/src/java/org/apache/ivy/core/event/download/StartArtifactDownloadEvent.java
@@ -0,0 +1,49 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.event.download;
+
+import org.apache.ivy.core.cache.ArtifactOrigin;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+
+public class StartArtifactDownloadEvent extends DownloadEvent {
+    public static final String NAME = "pre-download-artifact";
+
+    private DependencyResolver resolver;
+
+    private ArtifactOrigin origin;
+
+    public StartArtifactDownloadEvent(DependencyResolver resolver, Artifact artifact,
+            ArtifactOrigin origin) {
+        super(NAME, artifact);
+        this.resolver = resolver;
+        this.origin = origin;
+        addAttribute("resolver", this.resolver.getName());
+        addAttribute("origin", origin.getLocation());
+        addAttribute("local", String.valueOf(origin.isLocal()));
+    }
+
+    public DependencyResolver getResolver() {
+        return resolver;
+    }
+
+    public ArtifactOrigin getOrigin() {
+        return origin;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/event/publish/EndArtifactPublishEvent.java b/src/java/org/apache/ivy/core/event/publish/EndArtifactPublishEvent.java
new file mode 100644
index 0000000..36c98ce
--- /dev/null
+++ b/src/java/org/apache/ivy/core/event/publish/EndArtifactPublishEvent.java
@@ -0,0 +1,56 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.event.publish;
+
+import java.io.File;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+
+/**
+ * Event fired after artifact publication has finished (possibly in error). Triggers registered on
+ * {@link #NAME} will be notified of these events.
+ * 
+ * @see DependencyResolver#publish(Artifact, File, boolean)
+ */
+public class EndArtifactPublishEvent extends PublishEvent {
+
+    private static final long serialVersionUID = -65690169431499422L;
+
+    public static final String NAME = "post-publish-artifact";
+
+    public static final String STATUS_SUCCESSFUL = "successful";
+
+    public static final String STATUS_FAILED = "failed";
+
+    private final boolean successful;
+
+    public EndArtifactPublishEvent(DependencyResolver resolver, Artifact artifact, File data,
+            boolean overwrite, boolean successful) {
+        super(NAME, resolver, artifact, data, overwrite);
+        this.successful = successful;
+        addAttribute("status", isSuccessful() ? STATUS_SUCCESSFUL : STATUS_FAILED);
+    }
+
+    /**
+     * @return true iff no errors were encountered during the publication
+     */
+    public boolean isSuccessful() {
+        return successful;
+    }
+}
diff --git a/src/java/org/apache/ivy/core/event/publish/PublishEvent.java b/src/java/org/apache/ivy/core/event/publish/PublishEvent.java
new file mode 100644
index 0000000..72ec32a
--- /dev/null
+++ b/src/java/org/apache/ivy/core/event/publish/PublishEvent.java
@@ -0,0 +1,77 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.event.publish;
+
+import java.io.File;
+
+import org.apache.ivy.core.event.IvyEvent;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+
+/**
+ * Base class for events fired during {@link DependencyResolver#publish(Artifact, File, boolean)}.
+ * 
+ * @see StartArtifactPublishEvent
+ * @see EndArtifactPublishEvent
+ */
+public abstract class PublishEvent extends IvyEvent {
+
+    private final DependencyResolver resolver;
+
+    private final Artifact artifact;
+
+    private final File data;
+
+    private final boolean overwrite;
+
+    protected PublishEvent(String name, DependencyResolver resolver, Artifact artifact, File data,
+            boolean overwrite) {
+        super(name);
+        this.resolver = resolver;
+        this.artifact = artifact;
+        this.data = data;
+        this.overwrite = overwrite;
+
+        addMridAttributes(artifact.getModuleRevisionId());
+        addAttributes(artifact.getAttributes());
+        addAttribute("resolver", resolver.getName());
+        addAttribute("file", data.getAbsolutePath());
+        addAttribute("overwrite", String.valueOf(overwrite));
+    }
+
+    /** @return the resolver into which the artifact is being published */
+    public DependencyResolver getResolver() {
+        return resolver;
+    }
+
+    /** @return a local file containing the artifact data */
+    public File getData() {
+        return data;
+    }
+
+    /** @return metadata about the artifact being published */
+    public Artifact getArtifact() {
+        return artifact;
+    }
+
+    /** @return true iff this event overwrites existing resolver data for this artifact */
+    public boolean isOverwrite() {
+        return overwrite;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/event/publish/StartArtifactPublishEvent.java b/src/java/org/apache/ivy/core/event/publish/StartArtifactPublishEvent.java
new file mode 100644
index 0000000..2682eb5
--- /dev/null
+++ b/src/java/org/apache/ivy/core/event/publish/StartArtifactPublishEvent.java
@@ -0,0 +1,42 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.event.publish;
+
+import java.io.File;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+
+/**
+ * Event fired just before an artifact is published into a resolver. Triggers registered on
+ * {@link #NAME} will be notified of these events.
+ * 
+ * @see DependencyResolver#publish(Artifact, File, boolean)
+ */
+public class StartArtifactPublishEvent extends PublishEvent {
+
+    private static final long serialVersionUID = -1134274781039590219L;
+
+    public static final String NAME = "pre-publish-artifact";
+
+    public StartArtifactPublishEvent(DependencyResolver resolver, Artifact artifact, File data,
+            boolean overwrite) {
+        super(NAME, resolver, artifact, data, overwrite);
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/event/resolve/EndResolveDependencyEvent.java b/src/java/org/apache/ivy/core/event/resolve/EndResolveDependencyEvent.java
new file mode 100644
index 0000000..3f01d10
--- /dev/null
+++ b/src/java/org/apache/ivy/core/event/resolve/EndResolveDependencyEvent.java
@@ -0,0 +1,75 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.event.resolve;
+
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.resolve.ResolvedModuleRevision;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+
+public class EndResolveDependencyEvent extends ResolveDependencyEvent {
+    public static final String NAME = "post-resolve-dependency";
+
+    private ResolvedModuleRevision module;
+
+    private long duration;
+
+    public EndResolveDependencyEvent(DependencyResolver resolver, DependencyDescriptor dd,
+            ModuleRevisionId requestedRevisionId, ResolvedModuleRevision module, long duration) {
+        super(NAME, resolver, dd, requestedRevisionId);
+        this.module = module;
+        this.duration = duration;
+        addAttribute("duration", String.valueOf(duration));
+        if (this.module != null) {
+            // override revision from the dependency descriptor
+            addAttribute("revision", this.module.getDescriptor().getResolvedModuleRevisionId()
+                    .getRevision());
+            // now that we have loaded the dependency descriptor, we can put the extra attributes
+            // contained in the descriptor too
+            addAttributes(this.module.getDescriptor().getResolvedModuleRevisionId()
+                    .getQualifiedExtraAttributes());
+            addAttributes(this.module.getDescriptor().getResolvedModuleRevisionId()
+                    .getExtraAttributes());
+
+            addAttribute("resolved", "true");
+        } else {
+            addAttribute("resolved", "false");
+        }
+    }
+
+    public ResolvedModuleRevision getModule() {
+        return module;
+    }
+
+    /**
+     * Returns the time elapsed to resolve the dependency.
+     * <p>
+     * The time elapsed to resolve a dependency includes the time required to locate the the actual
+     * revision if the dependency descriptor use a version constraint, and to download the module
+     * metadata if necessary. It doesn't include any conflict management operations nor transitive
+     * dependency management. It's basically the time elapsed since the corresponding
+     * {@link StartResolveDependencyEvent}
+     * </p>
+     * 
+     * @return the time elapsed to resolve the dependency.
+     */
+    public long getDuration() {
+        return duration;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/event/resolve/EndResolveEvent.java b/src/java/org/apache/ivy/core/event/resolve/EndResolveEvent.java
new file mode 100644
index 0000000..7ea6775
--- /dev/null
+++ b/src/java/org/apache/ivy/core/event/resolve/EndResolveEvent.java
@@ -0,0 +1,43 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.event.resolve;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.report.ResolveReport;
+
+public class EndResolveEvent extends ResolveEvent {
+    public static final String NAME = "post-resolve";
+
+    private ResolveReport report;
+
+    public EndResolveEvent(ModuleDescriptor md, String[] confs, ResolveReport report) {
+        super(NAME, md, confs);
+        this.report = report;
+        addAttribute("resolve-id", String.valueOf(report.getResolveId()));
+        addAttribute("nb-dependencies", String.valueOf(report.getDependencies().size()));
+        addAttribute("nb-artifacts", String.valueOf(report.getArtifacts().size()));
+        addAttribute("resolve-duration", String.valueOf(report.getResolveTime()));
+        addAttribute("download-duration", String.valueOf(report.getDownloadTime()));
+        addAttribute("download-size", String.valueOf(report.getDownloadSize()));
+    }
+
+    public ResolveReport getReport() {
+        return report;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/event/resolve/ResolveDependencyEvent.java b/src/java/org/apache/ivy/core/event/resolve/ResolveDependencyEvent.java
new file mode 100644
index 0000000..97af58c
--- /dev/null
+++ b/src/java/org/apache/ivy/core/event/resolve/ResolveDependencyEvent.java
@@ -0,0 +1,55 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.event.resolve;
+
+import org.apache.ivy.core.event.IvyEvent;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+
+public class ResolveDependencyEvent extends IvyEvent {
+    private DependencyResolver resolver;
+
+    private DependencyDescriptor dd;
+
+    protected ResolveDependencyEvent(String name, DependencyResolver resolver,
+            DependencyDescriptor dd, ModuleRevisionId requestedRevisionId) {
+        super(name);
+        this.resolver = resolver;
+        this.dd = dd;
+        addAttribute("resolver", this.resolver.getName());
+        addMridAttributes(this.dd.getDependencyRevisionId());
+        addAttributes(this.dd.getQualifiedExtraAttributes());
+        addAttributes(this.dd.getExtraAttributes());
+        addAttribute("req-revision", requestedRevisionId.getRevision());
+        addAttribute("req-revision-default", dd.getDependencyRevisionId().getRevision());
+        addAttribute("req-revision-dynamic", dd.getDynamicConstraintDependencyRevisionId()
+                .getRevision());
+        addAttribute("req-branch", requestedRevisionId.getBranch());
+        addAttribute("req-branch-default", dd.getDependencyRevisionId().getBranch());
+    }
+
+    public DependencyDescriptor getDependencyDescriptor() {
+        return dd;
+    }
+
+    public DependencyResolver getResolver() {
+        return resolver;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/event/resolve/ResolveEvent.java b/src/java/org/apache/ivy/core/event/resolve/ResolveEvent.java
new file mode 100644
index 0000000..140cf3d
--- /dev/null
+++ b/src/java/org/apache/ivy/core/event/resolve/ResolveEvent.java
@@ -0,0 +1,36 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.event.resolve;
+
+import org.apache.ivy.core.event.IvyEvent;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+
+public class ResolveEvent extends IvyEvent {
+    private ModuleDescriptor md;
+
+    protected ResolveEvent(String name, ModuleDescriptor md, String[] confs) {
+        super(name);
+        this.md = md;
+        addMDAttributes(md);
+        addConfsAttribute(confs);
+    }
+
+    public ModuleDescriptor getModuleDescriptor() {
+        return md;
+    }
+}
diff --git a/src/java/org/apache/ivy/core/event/resolve/StartResolveDependencyEvent.java b/src/java/org/apache/ivy/core/event/resolve/StartResolveDependencyEvent.java
new file mode 100644
index 0000000..db5c825
--- /dev/null
+++ b/src/java/org/apache/ivy/core/event/resolve/StartResolveDependencyEvent.java
@@ -0,0 +1,32 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.event.resolve;
+
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+
+public class StartResolveDependencyEvent extends ResolveDependencyEvent {
+    public static final String NAME = "pre-resolve-dependency";
+
+    public StartResolveDependencyEvent(DependencyResolver resolver, DependencyDescriptor dd,
+            ModuleRevisionId requestedRevisionId) {
+        super(NAME, resolver, dd, requestedRevisionId);
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/event/resolve/StartResolveEvent.java b/src/java/org/apache/ivy/core/event/resolve/StartResolveEvent.java
new file mode 100644
index 0000000..687ef73
--- /dev/null
+++ b/src/java/org/apache/ivy/core/event/resolve/StartResolveEvent.java
@@ -0,0 +1,29 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.event.resolve;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+
+public class StartResolveEvent extends ResolveEvent {
+    public static final String NAME = "pre-resolve";
+
+    public StartResolveEvent(ModuleDescriptor md, String[] confs) {
+        super(NAME, md, confs);
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/event/retrieve/EndRetrieveArtifactEvent.java b/src/java/org/apache/ivy/core/event/retrieve/EndRetrieveArtifactEvent.java
new file mode 100644
index 0000000..5054117
--- /dev/null
+++ b/src/java/org/apache/ivy/core/event/retrieve/EndRetrieveArtifactEvent.java
@@ -0,0 +1,30 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.event.retrieve;
+
+import java.io.File;
+
+import org.apache.ivy.core.report.ArtifactDownloadReport;
+
+public class EndRetrieveArtifactEvent extends RetrieveArtifactEvent {
+    public static final String NAME = "post-retrieve-artifact";
+
+    public EndRetrieveArtifactEvent(ArtifactDownloadReport report, File destFile) {
+        super(NAME, report, destFile);
+    }
+}
diff --git a/src/java/org/apache/ivy/core/event/retrieve/EndRetrieveEvent.java b/src/java/org/apache/ivy/core/event/retrieve/EndRetrieveEvent.java
new file mode 100644
index 0000000..ad0514b
--- /dev/null
+++ b/src/java/org/apache/ivy/core/event/retrieve/EndRetrieveEvent.java
@@ -0,0 +1,84 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.event.retrieve;
+
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.retrieve.RetrieveOptions;
+
+public class EndRetrieveEvent extends RetrieveEvent {
+    public static final String NAME = "post-retrieve";
+
+    private long duration;
+
+    private int nbCopied;
+
+    private int nbUpToDate;
+
+    private long totalCopiedSize;
+
+    public EndRetrieveEvent(ModuleRevisionId mrid, String[] confs, long elapsedTime,
+            int targetsCopied, int targetsUpToDate, long totalCopiedSize, RetrieveOptions options) {
+        super(NAME, mrid, confs, options);
+
+        this.duration = elapsedTime;
+        this.nbCopied = targetsCopied;
+        this.nbUpToDate = targetsUpToDate;
+        this.totalCopiedSize = totalCopiedSize;
+        addAttribute("duration", String.valueOf(elapsedTime));
+        addAttribute("size", String.valueOf(totalCopiedSize));
+        addAttribute("nbCopied", String.valueOf(targetsCopied));
+        addAttribute("nbUptodate", String.valueOf(targetsUpToDate));
+    }
+
+    /**
+     * Duration of the retrieve operation, in ms.
+     * 
+     * @return Duration of the retrieve operation, in ms.
+     */
+    public long getDuration() {
+        return duration;
+    }
+
+    /**
+     * Number of artifacts which were copied (or symlinked) during the retrieve
+     * 
+     * @return Number of artifacts which were copied during the retrieve.
+     */
+    public int getNbCopied() {
+        return nbCopied;
+    }
+
+    /**
+     * Number of artifacts which were not copied since they were already present and up to date.
+     * 
+     * @return Number of artifacts which were not copied since they were already present and up to
+     *         date.
+     */
+    public int getNbUpToDate() {
+        return nbUpToDate;
+    }
+
+    /**
+     * Total size of all copied (or symlinked) artifacts, in bytes.
+     * 
+     * @return Total size of all copied (or symlinked) artifacts, in bytes.
+     */
+    public long getTotalCopiedSize() {
+        return totalCopiedSize;
+    }
+}
diff --git a/src/java/org/apache/ivy/core/event/retrieve/RetrieveArtifactEvent.java b/src/java/org/apache/ivy/core/event/retrieve/RetrieveArtifactEvent.java
new file mode 100644
index 0000000..9b134ca
--- /dev/null
+++ b/src/java/org/apache/ivy/core/event/retrieve/RetrieveArtifactEvent.java
@@ -0,0 +1,55 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.event.retrieve;
+
+import java.io.File;
+
+import org.apache.ivy.core.event.IvyEvent;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.report.ArtifactDownloadReport;
+
+public class RetrieveArtifactEvent extends IvyEvent {
+    private ArtifactDownloadReport report;
+
+    private File destFile;
+
+    public RetrieveArtifactEvent(String name, ArtifactDownloadReport report, File destFile) {
+        super(name);
+        addArtifactAttributes(report.getArtifact());
+
+        this.report = report;
+        this.destFile = destFile;
+        addAttribute("from", report.getLocalFile().getAbsolutePath());
+        addAttribute("to", destFile.getAbsolutePath());
+        addAttribute("size", String.valueOf(destFile.length()));
+    }
+
+    protected void addArtifactAttributes(Artifact artifact) {
+        addMridAttributes(artifact.getModuleRevisionId());
+        addAttributes(artifact.getAttributes());
+        addAttribute("metadata", String.valueOf(artifact.isMetadata()));
+    }
+
+    public File getDestFile() {
+        return destFile;
+    }
+
+    public ArtifactDownloadReport getReport() {
+        return report;
+    }
+}
diff --git a/src/java/org/apache/ivy/core/event/retrieve/RetrieveEvent.java b/src/java/org/apache/ivy/core/event/retrieve/RetrieveEvent.java
new file mode 100644
index 0000000..a7199cd
--- /dev/null
+++ b/src/java/org/apache/ivy/core/event/retrieve/RetrieveEvent.java
@@ -0,0 +1,47 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.event.retrieve;
+
+import org.apache.ivy.core.event.IvyEvent;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.retrieve.RetrieveOptions;
+
+public class RetrieveEvent extends IvyEvent {
+    private ModuleRevisionId mrid;
+
+    private RetrieveOptions options;
+
+    protected RetrieveEvent(String name, ModuleRevisionId mrid, String[] confs,
+            RetrieveOptions options) {
+        super(name);
+        this.mrid = mrid;
+        addMridAttributes(mrid);
+        addConfsAttribute(confs);
+        addAttribute("symlink", String.valueOf(options.isMakeSymlinks()));
+        addAttribute("sync", String.valueOf(options.isSync()));
+        this.options = options;
+    }
+
+    public ModuleRevisionId getModuleRevisionId() {
+        return mrid;
+    }
+
+    public RetrieveOptions getOptions() {
+        return options;
+    }
+}
diff --git a/src/java/org/apache/ivy/core/event/retrieve/StartRetrieveArtifactEvent.java b/src/java/org/apache/ivy/core/event/retrieve/StartRetrieveArtifactEvent.java
new file mode 100644
index 0000000..eb0ccf1
--- /dev/null
+++ b/src/java/org/apache/ivy/core/event/retrieve/StartRetrieveArtifactEvent.java
@@ -0,0 +1,30 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.event.retrieve;
+
+import java.io.File;
+
+import org.apache.ivy.core.report.ArtifactDownloadReport;
+
+public class StartRetrieveArtifactEvent extends RetrieveArtifactEvent {
+    public static final String NAME = "pre-retrieve-artifact";
+
+    public StartRetrieveArtifactEvent(ArtifactDownloadReport report, File destFile) {
+        super(NAME, report, destFile);
+    }
+}
diff --git a/src/java/org/apache/ivy/core/event/retrieve/StartRetrieveEvent.java b/src/java/org/apache/ivy/core/event/retrieve/StartRetrieveEvent.java
new file mode 100644
index 0000000..66b1678
--- /dev/null
+++ b/src/java/org/apache/ivy/core/event/retrieve/StartRetrieveEvent.java
@@ -0,0 +1,29 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.event.retrieve;
+
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.retrieve.RetrieveOptions;
+
+public class StartRetrieveEvent extends RetrieveEvent {
+    public static final String NAME = "pre-retrieve";
+
+    public StartRetrieveEvent(ModuleRevisionId mrid, String[] confs, RetrieveOptions options) {
+        super(NAME, mrid, confs, options);
+    }
+}
diff --git a/src/java/org/apache/ivy/core/install/InstallEngine.java b/src/java/org/apache/ivy/core/install/InstallEngine.java
new file mode 100644
index 0000000..b213fbb
--- /dev/null
+++ b/src/java/org/apache/ivy/core/install/InstallEngine.java
@@ -0,0 +1,211 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.install;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Date;
+
+import org.apache.ivy.core.cache.ArtifactOrigin;
+import org.apache.ivy.core.module.descriptor.Configuration;
+import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.report.ArtifactDownloadReport;
+import org.apache.ivy.core.report.MetadataArtifactDownloadReport;
+import org.apache.ivy.core.report.ResolveReport;
+import org.apache.ivy.core.resolve.DownloadOptions;
+import org.apache.ivy.core.resolve.IvyNode;
+import org.apache.ivy.core.resolve.ResolveEngine;
+import org.apache.ivy.core.resolve.ResolveOptions;
+import org.apache.ivy.core.search.SearchEngine;
+import org.apache.ivy.plugins.conflict.NoConflictManager;
+import org.apache.ivy.plugins.matcher.ExactPatternMatcher;
+import org.apache.ivy.plugins.matcher.MatcherHelper;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.apache.ivy.util.Message;
+
+public class InstallEngine {
+    private InstallEngineSettings settings;
+
+    private ResolveEngine resolveEngine;
+
+    private SearchEngine searchEngine;
+
+    public InstallEngine(InstallEngineSettings settings, SearchEngine searchEngine,
+            ResolveEngine resolveEngine) {
+        this.settings = settings;
+        this.searchEngine = searchEngine;
+        this.resolveEngine = resolveEngine;
+    }
+
+    public ResolveReport install(ModuleRevisionId mrid, String from, String to,
+            InstallOptions options) throws IOException {
+        DependencyResolver fromResolver = settings.getResolver(from);
+        DependencyResolver toResolver = settings.getResolver(to);
+        if (fromResolver == null) {
+            throw new IllegalArgumentException("unknown resolver " + from
+                    + ". Available resolvers are: " + settings.getResolverNames());
+        }
+        if (toResolver == null) {
+            throw new IllegalArgumentException("unknown resolver " + to
+                    + ". Available resolvers are: " + settings.getResolverNames());
+        }
+        PatternMatcher matcher = settings.getMatcher(options.getMatcherName());
+        if (matcher == null) {
+            throw new IllegalArgumentException("unknown matcher " + options.getMatcherName()
+                    + ". Available matchers are: " + settings.getMatcherNames());
+        }
+
+        // build module file declaring the dependency
+        Message.info(":: installing " + mrid + " ::");
+        DependencyResolver oldDicator = resolveEngine.getDictatorResolver();
+        boolean log = settings.logNotConvertedExclusionRule();
+        try {
+            settings.setLogNotConvertedExclusionRule(true);
+            resolveEngine.setDictatorResolver(fromResolver);
+
+            DefaultModuleDescriptor md = new DefaultModuleDescriptor(ModuleRevisionId.newInstance(
+                "apache", "ivy-install", "1.0"), settings.getStatusManager().getDefaultStatus(),
+                    new Date());
+            String resolveId = ResolveOptions.getDefaultResolveId(md);
+            md.addConfiguration(new Configuration("default"));
+            md.addConflictManager(new ModuleId(ExactPatternMatcher.ANY_EXPRESSION,
+                    ExactPatternMatcher.ANY_EXPRESSION), ExactPatternMatcher.INSTANCE,
+                new NoConflictManager());
+
+            for (int c = 0; c < options.getConfs().length; c++) {
+                final String[] depConfs = options.getConfs();
+
+                for (int j = 0; j < depConfs.length; j++) {
+                    final String depConf = depConfs[j].trim();
+
+                    if (MatcherHelper.isExact(matcher, mrid)) {
+                        DefaultDependencyDescriptor dd = new DefaultDependencyDescriptor(md, mrid,
+                                false, false, options.isTransitive());
+                        dd.addDependencyConfiguration("default", depConf);
+                        md.addDependency(dd);
+                    } else {
+                        ModuleRevisionId[] mrids = searchEngine.listModules(fromResolver, mrid,
+                            matcher);
+
+                        for (int i = 0; i < mrids.length; i++) {
+                            Message.info("\tfound " + mrids[i] + " to install: adding to the list");
+                            DefaultDependencyDescriptor dd = new DefaultDependencyDescriptor(md,
+                                    mrids[i], false, false, options.isTransitive());
+                            dd.addDependencyConfiguration("default", depConf);
+                            md.addDependency(dd);
+                        }
+                    }
+                }
+            }
+
+            // resolve using appropriate resolver
+            ResolveReport report = new ResolveReport(md, resolveId);
+
+            Message.info(":: resolving dependencies ::");
+            ResolveOptions resolveOptions = new ResolveOptions().setResolveId(resolveId)
+                    .setConfs(new String[] {"default"}).setValidate(options.isValidate());
+            IvyNode[] dependencies = resolveEngine.getDependencies(md, resolveOptions, report);
+            report.setDependencies(Arrays.asList(dependencies), options.getArtifactFilter());
+
+            Message.info(":: downloading artifacts to cache ::");
+            resolveEngine.downloadArtifacts(report, options.getArtifactFilter(),
+                new DownloadOptions());
+
+            // now that everything is in cache, we can publish all these modules
+            Message.info(":: installing in " + to + " ::");
+            for (int i = 0; i < dependencies.length; i++) {
+                ModuleDescriptor depmd = dependencies[i].getDescriptor();
+                if (depmd != null) {
+                    ModuleRevisionId depMrid = depmd.getModuleRevisionId();
+                    Message.verbose("installing " + depMrid);
+                    boolean successfullyPublished = false;
+                    try {
+                        toResolver.beginPublishTransaction(depMrid, options.isOverwrite());
+
+                        // publish artifacts
+                        ArtifactDownloadReport[] artifacts = report.getArtifactsReports(depMrid);
+                        for (int j = 0; j < artifacts.length; j++) {
+                            if (artifacts[j].getLocalFile() != null) {
+                                toResolver.publish(artifacts[j].getArtifact(),
+                                    artifacts[j].getLocalFile(), options.isOverwrite());
+                            }
+                        }
+
+                        // publish metadata
+                        MetadataArtifactDownloadReport artifactDownloadReport = dependencies[i]
+                                .getModuleRevision().getReport();
+                        File localIvyFile = artifactDownloadReport.getLocalFile();
+                        toResolver.publish(depmd.getMetadataArtifact(), localIvyFile,
+                            options.isOverwrite());
+
+                        if (options.isInstallOriginalMetadata()) {
+                            if (artifactDownloadReport.getArtifactOrigin() != null
+                                    && artifactDownloadReport.getArtifactOrigin().isExists()
+                                    && !ArtifactOrigin.isUnknown(artifactDownloadReport
+                                            .getArtifactOrigin())
+                                    && artifactDownloadReport.getArtifactOrigin().getArtifact() != null
+                                    && artifactDownloadReport.getArtifactOrigin().getArtifact()
+                                            .getType().endsWith(".original")
+                                    && !artifactDownloadReport
+                                            .getArtifactOrigin()
+                                            .getArtifact()
+                                            .getType()
+                                            .equals(
+                                                depmd.getMetadataArtifact().getType() + ".original")) {
+                                // publish original metadata artifact, too, as it has a different
+                                // type
+                                toResolver.publish(artifactDownloadReport.getArtifactOrigin()
+                                        .getArtifact(), artifactDownloadReport
+                                        .getOriginalLocalFile(), options.isOverwrite());
+                            }
+                        }
+
+                        // end module publish
+                        toResolver.commitPublishTransaction();
+                        successfullyPublished = true;
+                    } finally {
+                        if (!successfullyPublished) {
+                            toResolver.abortPublishTransaction();
+                        }
+                    }
+                }
+            }
+
+            Message.info(":: install resolution report ::");
+
+            // output report
+            resolveEngine
+                    .outputReport(report, settings.getResolutionCacheManager(), resolveOptions);
+
+            return report;
+        } finally {
+            // IVY-834: log the problems if there were any...
+            Message.sumupProblems();
+
+            resolveEngine.setDictatorResolver(oldDicator);
+            settings.setLogNotConvertedExclusionRule(log);
+        }
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/install/InstallEngineSettings.java b/src/java/org/apache/ivy/core/install/InstallEngineSettings.java
new file mode 100644
index 0000000..9f3426b
--- /dev/null
+++ b/src/java/org/apache/ivy/core/install/InstallEngineSettings.java
@@ -0,0 +1,46 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.install;
+
+import java.util.Collection;
+
+import org.apache.ivy.core.module.status.StatusManager;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+import org.apache.ivy.plugins.parser.ParserSettings;
+import org.apache.ivy.plugins.report.ReportOutputter;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+
+public interface InstallEngineSettings extends ParserSettings {
+
+    DependencyResolver getResolver(String from);
+
+    Collection getResolverNames();
+
+    ReportOutputter[] getReportOutputters();
+
+    void setLogNotConvertedExclusionRule(boolean log);
+
+    StatusManager getStatusManager();
+
+    boolean logNotConvertedExclusionRule();
+
+    PatternMatcher getMatcher(String matcherName);
+
+    Collection getMatcherNames();
+
+}
diff --git a/src/java/org/apache/ivy/core/install/InstallOptions.java b/src/java/org/apache/ivy/core/install/InstallOptions.java
new file mode 100644
index 0000000..5bb4182
--- /dev/null
+++ b/src/java/org/apache/ivy/core/install/InstallOptions.java
@@ -0,0 +1,101 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.install;
+
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+import org.apache.ivy.util.filter.Filter;
+import org.apache.ivy.util.filter.FilterHelper;
+
+public class InstallOptions {
+    private boolean transitive = true;
+
+    private boolean validate = true;
+
+    private boolean overwrite = false;
+
+    private boolean installOriginalMetadata = false;
+
+    private String[] confs = {"*"};
+
+    private Filter artifactFilter = FilterHelper.NO_FILTER;
+
+    private String matcherName = PatternMatcher.EXACT;
+
+    public boolean isTransitive() {
+        return transitive;
+    }
+
+    public InstallOptions setTransitive(boolean transitive) {
+        this.transitive = transitive;
+        return this;
+    }
+
+    public boolean isValidate() {
+        return validate;
+    }
+
+    public InstallOptions setValidate(boolean validate) {
+        this.validate = validate;
+        return this;
+    }
+
+    public boolean isOverwrite() {
+        return overwrite;
+    }
+
+    public InstallOptions setOverwrite(boolean overwrite) {
+        this.overwrite = overwrite;
+        return this;
+    }
+
+    public Filter getArtifactFilter() {
+        return artifactFilter;
+    }
+
+    public InstallOptions setArtifactFilter(Filter artifactFilter) {
+        this.artifactFilter = artifactFilter == null ? FilterHelper.NO_FILTER : artifactFilter;
+        return this;
+    }
+
+    public String getMatcherName() {
+        return matcherName;
+    }
+
+    public InstallOptions setMatcherName(String matcherName) {
+        this.matcherName = matcherName;
+        return this;
+    }
+
+    public String[] getConfs() {
+        return confs;
+    }
+
+    public InstallOptions setConfs(String[] conf) {
+        this.confs = conf;
+        return this;
+    }
+
+    public boolean isInstallOriginalMetadata() {
+        return installOriginalMetadata;
+    }
+
+    public InstallOptions setInstallOriginalMetadata(boolean installOriginalMetadata) {
+        this.installOriginalMetadata = installOriginalMetadata;
+        return this;
+    }
+}
diff --git a/src/java/org/apache/ivy/core/module/descriptor/AbstractArtifact.java b/src/java/org/apache/ivy/core/module/descriptor/AbstractArtifact.java
new file mode 100644
index 0000000..95b175c
--- /dev/null
+++ b/src/java/org/apache/ivy/core/module/descriptor/AbstractArtifact.java
@@ -0,0 +1,81 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.module.descriptor;
+
+import java.util.Map;
+
+/**
+ *
+ */
+public abstract class AbstractArtifact implements Artifact {
+    public AbstractArtifact() {
+    }
+
+    public boolean equals(Object obj) {
+        if (!(obj instanceof Artifact)) {
+            return false;
+        }
+        Artifact art = (Artifact) obj;
+        return getModuleRevisionId().equals(art.getModuleRevisionId())
+                && getPublicationDate() == null ? (art.getPublicationDate() == null)
+                : getPublicationDate().equals(art.getPublicationDate())
+                        && getName().equals(art.getName()) && getExt().equals(art.getExt())
+                        && getType().equals(art.getType())
+                        && getQualifiedExtraAttributes().equals(art.getQualifiedExtraAttributes());
+    }
+
+    public int hashCode() {
+        // CheckStyle:MagicNumber| OFF
+        int hash = 33;
+        hash = hash * 17 + getModuleRevisionId().hashCode();
+        if (getPublicationDate() != null) {
+            hash = hash * 17 + getPublicationDate().hashCode();
+        }
+        hash = hash * 17 + getName().hashCode();
+        hash = hash * 17 + getExt().hashCode();
+        hash = hash * 17 + getType().hashCode();
+        hash = hash * 17 + getQualifiedExtraAttributes().hashCode();
+        // CheckStyle:MagicNumber| ON
+        return hash;
+    }
+
+    public String toString() {
+        return String.valueOf(getId());
+    }
+
+    public String getAttribute(String attName) {
+        return getId().getAttribute(attName);
+    }
+
+    public Map getAttributes() {
+        return getId().getAttributes();
+    }
+
+    public String getExtraAttribute(String attName) {
+        return getId().getExtraAttribute(attName);
+    }
+
+    public Map getExtraAttributes() {
+        return getId().getExtraAttributes();
+    }
+
+    public Map getQualifiedExtraAttributes() {
+        return getId().getQualifiedExtraAttributes();
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/module/descriptor/AbstractIncludeExcludeRule.java b/src/java/org/apache/ivy/core/module/descriptor/AbstractIncludeExcludeRule.java
new file mode 100644
index 0000000..ccd262a
--- /dev/null
+++ b/src/java/org/apache/ivy/core/module/descriptor/AbstractIncludeExcludeRule.java
@@ -0,0 +1,94 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.module.descriptor;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Map;
+
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.core.module.id.ArtifactId;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+import org.apache.ivy.util.extendable.UnmodifiableExtendableItem;
+
+/**
+ * Abstract class used as implementation for both {@link IncludeRule} and {@link ExcludeRule}, since
+ * their contract is almost identical
+ */
+public abstract class AbstractIncludeExcludeRule extends UnmodifiableExtendableItem implements
+        ConfigurationAware {
+
+    private ArtifactId id;
+
+    private Collection confs = new ArrayList();
+
+    private PatternMatcher patternMatcher;
+
+    public AbstractIncludeExcludeRule(ArtifactId aid, PatternMatcher matcher, Map extraAttributes) {
+        super(null, extraAttributes);
+        id = aid;
+        patternMatcher = matcher;
+        initStandardAttributes();
+    }
+
+    private void initStandardAttributes() {
+        setStandardAttribute(IvyPatternHelper.ORGANISATION_KEY, id.getModuleId().getOrganisation());
+        setStandardAttribute(IvyPatternHelper.MODULE_KEY, id.getModuleId().getName());
+        setStandardAttribute(IvyPatternHelper.ARTIFACT_KEY, id.getName());
+        setStandardAttribute(IvyPatternHelper.TYPE_KEY, id.getType());
+        setStandardAttribute(IvyPatternHelper.EXT_KEY, id.getExt());
+        setStandardAttribute("matcher", patternMatcher.getName());
+    }
+
+    public boolean equals(Object obj) {
+        if (!(obj instanceof AbstractIncludeExcludeRule)) {
+            return false;
+        }
+        AbstractIncludeExcludeRule rule = (AbstractIncludeExcludeRule) obj;
+        return getId().equals(rule.getId());
+    }
+
+    public int hashCode() {
+        return getId().hashCode();
+    }
+
+    /**
+     * Add a configuration for this rule
+     * 
+     * @param conf
+     */
+    public void addConfiguration(String conf) {
+        confs.add(conf);
+    }
+
+    public ArtifactId getId() {
+        return id;
+    }
+
+    public String[] getConfigurations() {
+        return (String[]) confs.toArray(new String[confs.size()]);
+    }
+
+    public PatternMatcher getMatcher() {
+        return patternMatcher;
+    }
+
+    public String toString() {
+        return id + "(" + confs + ")";
+    }
+}
diff --git a/src/java/org/apache/ivy/core/module/descriptor/Artifact.java b/src/java/org/apache/ivy/core/module/descriptor/Artifact.java
new file mode 100644
index 0000000..3797839
--- /dev/null
+++ b/src/java/org/apache/ivy/core/module/descriptor/Artifact.java
@@ -0,0 +1,100 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.module.descriptor;
+
+import java.net.URL;
+import java.util.Date;
+
+import org.apache.ivy.core.module.id.ArtifactRevisionId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.util.extendable.ExtendableItem;
+
+/**
+ * Representation of a published 'file' in the development environment. An artifact is generally a
+ * file that is produced by a project build. This is typically a <code>jar</code>, a
+ * <code>war</code>, an <code>ear</code>, a <code>zip</code>, a <code>deb</code>, etc.
+ */
+public interface Artifact extends ExtendableItem {
+
+    /**
+     * Returns the resolved module revision id for this artifact
+     * 
+     * @return the resolved module revision id.
+     */
+    ModuleRevisionId getModuleRevisionId();
+
+    /**
+     * Returns the resolved publication date for this artifact
+     * 
+     * @return the resolved publication date. Never null.
+     */
+    Date getPublicationDate();
+
+    /**
+     * Return the name of the artifact, generally 'part' of the basename of the file.
+     * 
+     * @return the name of the artifact. Never null.
+     */
+    String getName();
+
+    /**
+     * Returns the type of the artifact, typically 'jar', 'source', 'javadoc', 'debian', ...
+     * 
+     * @return the type of the artifact. Never null.
+     */
+    String getType();
+
+    /**
+     * Retrieve the extension of the artifact. The extension is without dot (ie. 'jar' and not
+     * '.jar')
+     * 
+     * @return the extension of the artifact. Never null.
+     */
+    String getExt();
+
+    /**
+     * Returns the url at which this artifact can be found independently of ivy configuration. This
+     * can be null (and is usually for standard artifacts)
+     * 
+     * @return url at which this artifact can be found independently of ivy configuration
+     */
+    URL getUrl();
+
+    /**
+     * Returns the list of configurations where this artifact is associated to.
+     * 
+     * @return the list of configuration this artifact is associated to. Never null.
+     */
+    String[] getConfigurations();
+
+    /**
+     * Return the specific identifier of this artifact.
+     * 
+     * @return the id of the artifact
+     */
+    ArtifactRevisionId getId();
+
+    /**
+     * Returns true if this artifact represents a module metadata artifact, false if it's a
+     * published artifact
+     * 
+     * @return true if this artifact represents a module metadata artifact, false if it's a
+     *         published artifact
+     */
+    boolean isMetadata();
+}
diff --git a/src/java/org/apache/ivy/core/module/descriptor/Configuration.java b/src/java/org/apache/ivy/core/module/descriptor/Configuration.java
new file mode 100644
index 0000000..30d111b
--- /dev/null
+++ b/src/java/org/apache/ivy/core/module/descriptor/Configuration.java
@@ -0,0 +1,247 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.module.descriptor;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.util.extendable.DefaultExtendableItem;
+
+/**
+ * Represents a module configuration
+ */
+public class Configuration extends DefaultExtendableItem implements InheritableItem {
+    public static final class Visibility {
+        public static final Visibility PUBLIC = new Visibility("public");
+
+        public static final Visibility PRIVATE = new Visibility("private");
+
+        public static Visibility getVisibility(String name) {
+            if ("private".equals(name)) {
+                return PRIVATE;
+            } else if ("public".equals(name)) {
+                return PUBLIC;
+            } else {
+                throw new IllegalArgumentException("unknwon visibility " + name);
+            }
+        }
+
+        private String name;
+
+        private Visibility(String name) {
+            this.name = name;
+        }
+
+        public String toString() {
+            return name;
+        }
+    }
+
+    public static Collection/* <Configuration> */findConfigurationExtending(String conf,
+            Configuration[] confs) {
+        Collection extendingConfs = new ArrayList();
+        for (int i = 0; i < confs.length; i++) {
+            if (confs[i] != null && Arrays.asList(confs[i].getExtends()).contains(conf)) {
+                extendingConfs.add(confs[i]);
+                extendingConfs.addAll(findConfigurationExtending(confs[i].getName(), confs));
+            }
+        }
+        return extendingConfs;
+    }
+
+    private String name;
+
+    private String description;
+
+    private String[] extendsFrom;
+
+    private Visibility visibility;
+
+    private boolean transitive = true;
+
+    private String deprecated;
+
+    private ModuleRevisionId sourceModule;
+
+    /**
+     * Creates a new configuration.
+     * 
+     * @param name
+     *            the name of the configuration
+     */
+    public Configuration(String name) {
+        this(name, Visibility.PUBLIC, null, null, true, null);
+    }
+
+    public Configuration(Configuration source, ModuleRevisionId sourceModule) {
+        this(source.getAttributes(), source.getQualifiedExtraAttributes(), source.getName(), source
+                .getVisibility(), source.getDescription(), source.getExtends(), source
+                .isTransitive(), source.getDeprecated(), sourceModule);
+    }
+
+    /**
+     * Creates a new configuration.
+     * 
+     * @param name
+     *            the name of the configuration
+     * @param visibility
+     *            the visibility of the configuration
+     * @param description
+     *            a description
+     * @param ext
+     *            the configurations to extend from
+     * @param transitive
+     *            indicates if the configuration is transitive
+     * @param deprecated
+     *            the deprecation message
+     */
+    public Configuration(String name, Visibility visibility, String description, String[] ext,
+            boolean transitive, String deprecated) {
+        this(null, null, name, visibility, description, ext, transitive, deprecated, null);
+    }
+
+    private Configuration(Map attributes, Map extraAttributes, String name, Visibility visibility,
+            String description, String[] ext, boolean transitive, String deprecated,
+            ModuleRevisionId sourceModule) {
+        super(attributes, extraAttributes);
+
+        if (name == null) {
+            throw new NullPointerException("null configuration name not allowed");
+        }
+        if (visibility == null) {
+            throw new NullPointerException("null visibility not allowed");
+        }
+        this.name = name;
+        this.visibility = visibility;
+        this.description = description;
+        if (ext == null) {
+            extendsFrom = new String[0];
+        } else {
+            extendsFrom = new String[ext.length];
+            for (int i = 0; i < ext.length; i++) {
+                extendsFrom[i] = ext[i].trim();
+            }
+        }
+        this.transitive = transitive;
+        this.deprecated = deprecated;
+        this.sourceModule = sourceModule;
+    }
+
+    /**
+     * Returns the deprecation message, or <tt>null</tt> if not specified.
+     * 
+     * @return Returns the deprecation message.
+     */
+    public String getDeprecated() {
+        return deprecated;
+    }
+
+    /**
+     * @return Returns the description. It may be null.
+     */
+    public String getDescription() {
+        return description;
+    }
+
+    /**
+     * @return Returns the extends. May be empty, but never null.
+     */
+    public String[] getExtends() {
+        return extendsFrom;
+    }
+
+    /**
+     * @return Returns the name. Never null;
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * @return Returns the visibility. Never null.
+     */
+    public Visibility getVisibility() {
+        return visibility;
+    }
+
+    /**
+     * @return Returns the transitive.
+     */
+    public final boolean isTransitive() {
+        return transitive;
+    }
+
+    public ModuleRevisionId getSourceModule() {
+        return sourceModule;
+    }
+
+    public String toString() {
+        return name;
+    }
+
+    public boolean equals(Object obj) {
+        if (!(obj instanceof Configuration)) {
+            return false;
+        }
+        return ((Configuration) obj).getName().equals(getName());
+    }
+
+    public int hashCode() {
+        return getName().hashCode();
+    }
+
+    public void replaceWildcards(ModuleDescriptor md) {
+        if (this != md.getConfiguration(name)) {
+            throw new IllegalArgumentException(
+                    "The given ModuleDescriptor doesn't own this configuration!");
+        }
+
+        Configuration[] configs = md.getConfigurations();
+
+        Set newExtends = new LinkedHashSet();
+        for (int j = 0; j < extendsFrom.length; j++) {
+            if ("*".equals(extendsFrom[j])) {
+                addOther(configs, null, newExtends);
+            } else if ("*(public)".equals(extendsFrom[j])) {
+                addOther(configs, Visibility.PUBLIC, newExtends);
+            } else if ("*(private)".equals(extendsFrom[j])) {
+                addOther(configs, Visibility.PRIVATE, newExtends);
+            } else {
+                newExtends.add(extendsFrom[j]);
+            }
+        }
+
+        this.extendsFrom = (String[]) newExtends.toArray(new String[newExtends.size()]);
+    }
+
+    private void addOther(Configuration[] allConfigs, Visibility visibility, Set configs) {
+        for (int i = 0; i < allConfigs.length; i++) {
+            String currentName = allConfigs[i].getName();
+            if (!name.equals(currentName)
+                    && ((visibility == null) || visibility.equals(allConfigs[i].getVisibility()))) {
+                configs.add(currentName);
+            }
+        }
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/module/descriptor/ConfigurationAware.java b/src/java/org/apache/ivy/core/module/descriptor/ConfigurationAware.java
new file mode 100644
index 0000000..2982f7a
--- /dev/null
+++ b/src/java/org/apache/ivy/core/module/descriptor/ConfigurationAware.java
@@ -0,0 +1,39 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.module.descriptor;
+
+/**
+ * Objects implementing this interface are aware of module configurations, and can thus be added to
+ * configurations, and list their configurations.
+ */
+public interface ConfigurationAware {
+    /**
+     * Returns the configurations of the module to which the object is attached
+     * 
+     * @return an array of configuration names to which the object is attached
+     */
+    public String[] getConfigurations();
+
+    /**
+     * Tells this object that it will now be part of the given configuration
+     * 
+     * @param confName
+     *            the name of the configuration to which the object is now attached
+     */
+    public void addConfiguration(String confName);
+}
diff --git a/src/java/org/apache/ivy/core/module/descriptor/ConfigurationGroup.java b/src/java/org/apache/ivy/core/module/descriptor/ConfigurationGroup.java
new file mode 100644
index 0000000..b25bebd
--- /dev/null
+++ b/src/java/org/apache/ivy/core/module/descriptor/ConfigurationGroup.java
@@ -0,0 +1,71 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.module.descriptor;
+
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * A configuration which is actually a group of several configurations.
+ */
+public class ConfigurationGroup extends Configuration {
+
+    private final Map/* <String, Configuration> */members;
+
+    public ConfigurationGroup(String confName, Map /* <String, Configuration> */members) {
+        super(confName);
+        this.members = members;
+    }
+
+    /**
+     * Returns the list of configurations' names this object is a group of.
+     * <p>
+     * This list is built from the configuration name, if some of these configuration names have
+     * actually not been recognized in the module, they will be <code>null</code> when accessed from
+     * {@link #getIntersectedConfiguration(String)}.
+     * </p>
+     * 
+     * @return the list of configurations' names this object is an intersection of.
+     */
+    public String[] getMembersConfigurationNames() {
+        return (String[]) members.keySet().toArray(new String[members.size()]);
+    }
+
+    /**
+     * Returns the {@link Configuration} object for the given conf name, or <code>null</code> if the
+     * given conf name is not part of this group or if this conf name isn't defined in the module in
+     * which this group has been built.
+     * 
+     * @param confName
+     *            the name of the configuration to return.
+     * @return the member {@link Configuration} object for the given conf name
+     */
+    public Configuration getMemberConfiguration(String confName) {
+        return (Configuration) members.get(confName);
+    }
+
+    public Visibility getVisibility() {
+        for (Iterator it = members.values().iterator(); it.hasNext();) {
+            Configuration c = (Configuration) it.next();
+            if (c != null && Visibility.PRIVATE.equals(c.getVisibility())) {
+                return Visibility.PRIVATE;
+            }
+        }
+        return Visibility.PUBLIC;
+    }
+}
diff --git a/src/java/org/apache/ivy/core/module/descriptor/ConfigurationIntersection.java b/src/java/org/apache/ivy/core/module/descriptor/ConfigurationIntersection.java
new file mode 100644
index 0000000..beba0ca
--- /dev/null
+++ b/src/java/org/apache/ivy/core/module/descriptor/ConfigurationIntersection.java
@@ -0,0 +1,72 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.module.descriptor;
+
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * A configuration which is actually the intersection of several configurations.
+ */
+public class ConfigurationIntersection extends Configuration {
+
+    private final Map/* <String, Configuration> */intersectedConfs;
+
+    public ConfigurationIntersection(String confName,
+            Map /* <String, Configuration> */intersectedConfs) {
+        super(confName);
+        this.intersectedConfs = intersectedConfs;
+    }
+
+    /**
+     * Returns the list of configurations' names this object is an intersection of.
+     * <p>
+     * This list is built from the configuration name, if some of these configuration names have
+     * actually not been recognized in the module, they will be <code>null</code> when accessed from
+     * {@link #getIntersectedConfiguration(String)}.
+     * </p>
+     * 
+     * @return the list of configurations' names this object is an intersection of.
+     */
+    public String[] getIntersectedConfigurationNames() {
+        return (String[]) intersectedConfs.keySet().toArray(new String[intersectedConfs.size()]);
+    }
+
+    /**
+     * Returns the intersected {@link Configuration} object for the given conf name, or
+     * <code>null</code> if the given conf name is not part of this intersection or if this conf
+     * name isn't defined in the module in which this intersection has been built.
+     * 
+     * @param confName
+     *            the name of the configuration to return.
+     * @return the intersected {@link Configuration} object for the given conf name
+     */
+    public Configuration getIntersectedConfiguration(String confName) {
+        return (Configuration) intersectedConfs.get(confName);
+    }
+
+    public Visibility getVisibility() {
+        for (Iterator it = intersectedConfs.values().iterator(); it.hasNext();) {
+            Configuration c = (Configuration) it.next();
+            if (c != null && Visibility.PRIVATE.equals(c.getVisibility())) {
+                return Visibility.PRIVATE;
+            }
+        }
+        return Visibility.PUBLIC;
+    }
+}
diff --git a/src/java/org/apache/ivy/core/module/descriptor/DefaultArtifact.java b/src/java/org/apache/ivy/core/module/descriptor/DefaultArtifact.java
new file mode 100644
index 0000000..3ea103d
--- /dev/null
+++ b/src/java/org/apache/ivy/core/module/descriptor/DefaultArtifact.java
@@ -0,0 +1,146 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.module.descriptor;
+
+import java.net.URL;
+import java.util.Date;
+import java.util.Map;
+
+import org.apache.ivy.core.module.id.ArtifactRevisionId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+
+/**
+ *
+ */
+public class DefaultArtifact extends AbstractArtifact {
+
+    public static Artifact newIvyArtifact(ModuleRevisionId mrid, Date pubDate) {
+        return new DefaultArtifact(mrid, pubDate, "ivy", "ivy", "xml", true);
+    }
+
+    public static Artifact newPomArtifact(ModuleRevisionId mrid, Date pubDate) {
+        return new DefaultArtifact(mrid, pubDate, mrid.getName(), "pom", "pom", true);
+    }
+
+    public static Artifact cloneWithAnotherExt(Artifact artifact, String newExt) {
+        return cloneWithAnotherTypeAndExt(artifact, artifact.getType(), newExt);
+    }
+
+    public static Artifact cloneWithAnotherType(Artifact artifact, String newType) {
+        return cloneWithAnotherTypeAndExt(artifact, newType, artifact.getExt());
+    }
+
+    public static Artifact cloneWithAnotherTypeAndExt(Artifact artifact, String newType,
+            String newExt) {
+        return new DefaultArtifact(ArtifactRevisionId.newInstance(artifact.getModuleRevisionId(),
+            artifact.getName(), newType, newExt, artifact.getQualifiedExtraAttributes()),
+                artifact.getPublicationDate(), artifact.getUrl(), artifact.isMetadata());
+    }
+
+    public static Artifact cloneWithAnotherName(Artifact artifact, String name) {
+        return new DefaultArtifact(ArtifactRevisionId.newInstance(artifact.getModuleRevisionId(),
+            name, artifact.getType(), artifact.getExt(), artifact.getQualifiedExtraAttributes()),
+                artifact.getPublicationDate(), artifact.getUrl(), artifact.isMetadata());
+    }
+
+    public static Artifact cloneWithAnotherMrid(Artifact artifact, ModuleRevisionId mrid) {
+        return new DefaultArtifact(ArtifactRevisionId.newInstance(mrid, artifact.getName(),
+            artifact.getType(), artifact.getExt(), artifact.getQualifiedExtraAttributes()),
+                artifact.getPublicationDate(), artifact.getUrl(), artifact.isMetadata());
+    }
+
+    private Date publicationDate;
+
+    private ArtifactRevisionId arid;
+
+    private URL url;
+
+    private boolean isMetadata = false;
+
+    public DefaultArtifact(ModuleRevisionId mrid, Date publicationDate, String name, String type,
+            String ext) {
+        this(mrid, publicationDate, name, type, ext, null, null);
+    }
+
+    public DefaultArtifact(ModuleRevisionId mrid, Date publicationDate, String name, String type,
+            String ext, boolean isMetadata) {
+        this(mrid, publicationDate, name, type, ext, null, null);
+        this.isMetadata = isMetadata;
+    }
+
+    public DefaultArtifact(ModuleRevisionId mrid, Date publicationDate, String name, String type,
+            String ext, Map extraAttributes) {
+        this(mrid, publicationDate, name, type, ext, null, extraAttributes);
+    }
+
+    public DefaultArtifact(ModuleRevisionId mrid, Date publicationDate, String name, String type,
+            String ext, URL url, Map extraAttributes) {
+        this(ArtifactRevisionId.newInstance(mrid, name, type, ext, extraAttributes),
+                publicationDate, url, false);
+    }
+
+    public DefaultArtifact(ArtifactRevisionId arid, Date publicationDate, URL url,
+            boolean isMetadata) {
+        if (arid == null) {
+            throw new NullPointerException("null arid not allowed");
+        }
+        if (publicationDate == null) {
+            publicationDate = new Date();
+        }
+        this.publicationDate = publicationDate;
+        this.arid = arid;
+        this.url = url;
+        this.isMetadata = isMetadata;
+    }
+
+    public ModuleRevisionId getModuleRevisionId() {
+        return arid.getModuleRevisionId();
+    }
+
+    public String getName() {
+        return arid.getName();
+    }
+
+    public Date getPublicationDate() {
+        return publicationDate;
+    }
+
+    public String getType() {
+        return arid.getType();
+    }
+
+    public String getExt() {
+        return arid.getExt();
+    }
+
+    public ArtifactRevisionId getId() {
+        return arid;
+    }
+
+    public String[] getConfigurations() {
+        return new String[0];
+    }
+
+    public URL getUrl() {
+        return url;
+    }
+
+    public boolean isMetadata() {
+        return isMetadata;
+    }
+}
diff --git a/src/java/org/apache/ivy/core/module/descriptor/DefaultDependencyArtifactDescriptor.java b/src/java/org/apache/ivy/core/module/descriptor/DefaultDependencyArtifactDescriptor.java
new file mode 100644
index 0000000..42202f9
--- /dev/null
+++ b/src/java/org/apache/ivy/core/module/descriptor/DefaultDependencyArtifactDescriptor.java
@@ -0,0 +1,121 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.module.descriptor;
+
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Map;
+
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.util.Checks;
+import org.apache.ivy.util.extendable.UnmodifiableExtendableItem;
+
+public class DefaultDependencyArtifactDescriptor extends UnmodifiableExtendableItem implements
+        DependencyArtifactDescriptor, ConfigurationAware {
+
+    private Collection confs = new ArrayList();
+
+    private URL url;
+
+    private String name;
+
+    private String type;
+
+    private String ext;
+
+    private DependencyDescriptor dd;
+
+    /**
+     * @param dd
+     * @param name
+     * @param type
+     * @param url
+     */
+    public DefaultDependencyArtifactDescriptor(DependencyDescriptor dd, String name, String type,
+            String ext, URL url, Map extraAttributes) {
+        super(null, extraAttributes);
+        Checks.checkNotNull(dd, "dd");
+        Checks.checkNotNull(name, "name");
+        Checks.checkNotNull(type, "type");
+        Checks.checkNotNull(ext, "ext");
+        this.dd = dd;
+        this.name = name;
+        this.type = type;
+        this.ext = ext;
+        this.url = url;
+        initStandardAttributes();
+    }
+
+    private void initStandardAttributes() {
+        setStandardAttribute(IvyPatternHelper.ARTIFACT_KEY, getName());
+        setStandardAttribute(IvyPatternHelper.TYPE_KEY, getType());
+        setStandardAttribute(IvyPatternHelper.EXT_KEY, getExt());
+        setStandardAttribute("url", url != null ? String.valueOf(url) : "");
+    }
+
+    public boolean equals(Object obj) {
+        if (!(obj instanceof DependencyArtifactDescriptor)) {
+            return false;
+        }
+        DependencyArtifactDescriptor dad = (DependencyArtifactDescriptor) obj;
+        return getAttributes().equals(dad.getAttributes());
+    }
+
+    public int hashCode() {
+        return getAttributes().hashCode();
+    }
+
+    /**
+     * Add a configuration for this artifact.
+     * 
+     * @param conf
+     */
+    public void addConfiguration(String conf) {
+        confs.add(conf);
+    }
+
+    public DependencyDescriptor getDependencyDescriptor() {
+        return dd;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public String getExt() {
+        return ext;
+    }
+
+    public String[] getConfigurations() {
+        return (String[]) confs.toArray(new String[confs.size()]);
+    }
+
+    public URL getUrl() {
+        return url;
+    }
+
+    public String toString() {
+        return "DA:" + name + "." + ext + "(" + type + ") " + "(" + confs + ")"
+                + (url == null ? "" : url.toString());
+    }
+}
diff --git a/src/java/org/apache/ivy/core/module/descriptor/DefaultDependencyDescriptor.java b/src/java/org/apache/ivy/core/module/descriptor/DefaultDependencyDescriptor.java
new file mode 100644
index 0000000..11ca953
--- /dev/null
+++ b/src/java/org/apache/ivy/core/module/descriptor/DefaultDependencyDescriptor.java
@@ -0,0 +1,706 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.module.descriptor;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+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.MatcherHelper;
+import org.apache.ivy.plugins.namespace.NameSpaceHelper;
+import org.apache.ivy.plugins.namespace.Namespace;
+import org.apache.ivy.plugins.namespace.NamespaceTransformer;
+import org.apache.ivy.util.Checks;
+
+/**
+ * This class can be used as the default implementation for DependencyDescriptor. It implements
+ * required methods and enables to fill dependency information with the addDependencyConfiguration
+ * method.
+ */
+public class DefaultDependencyDescriptor implements DependencyDescriptor {
+    private static final Pattern SELF_FALLBACK_PATTERN = Pattern
+            .compile("@(\\+[^\\(]+)?(\\(.*\\))?");
+
+    private static final Pattern THIS_FALLBACK_PATTERN = Pattern
+            .compile("#(\\+[^\\(]+)?(\\(.*\\))?");
+
+    /**
+     * Transforms the given dependency descriptor of the given namespace and return a new dependency
+     * descriptor in the system namespace. <i>Note that exclude rules are not converted in system
+     * namespace, because they aren't transformable (the name space hasn't the ability to convert
+     * regular expressions). However, method doesExclude will work with system artifacts.</i>
+     * 
+     * @param dd
+     * @param ns
+     * @return
+     */
+    public static DependencyDescriptor transformInstance(DependencyDescriptor dd, Namespace ns) {
+        NamespaceTransformer t = ns.getToSystemTransformer();
+        if (t.isIdentity()) {
+            return dd;
+        }
+        DefaultDependencyDescriptor newdd = transformInstance(dd, t, false);
+        newdd.namespace = ns;
+        return newdd;
+    }
+
+    /**
+     * Transforms a dependency descriptor using the given transformer. Note that no namespace info
+     * will be attached to the transformed dependency descriptor, so calling doesExclude is not
+     * recommended (doesExclude only works when namespace is properly set)
+     * 
+     * @param dd
+     * @param t
+     * @return
+     */
+    public static DefaultDependencyDescriptor transformInstance(DependencyDescriptor dd,
+            NamespaceTransformer t, boolean fromSystem) {
+        ModuleRevisionId transformParentId = t.transform(dd.getParentRevisionId());
+        ModuleRevisionId transformMrid = t.transform(dd.getDependencyRevisionId());
+        ModuleRevisionId transformDynamicMrid = t.transform(dd
+                .getDynamicConstraintDependencyRevisionId());
+        DefaultDependencyDescriptor newdd = new DefaultDependencyDescriptor(null, transformMrid,
+                transformDynamicMrid, dd.isForce(), dd.isChanging(), dd.isTransitive());
+
+        newdd.parentId = transformParentId;
+        ModuleRevisionId sourceModule = dd.getSourceModule();
+        if (sourceModule != null) {
+            newdd.sourceModule = t.transform(sourceModule);
+        }
+
+        String[] moduleConfs = dd.getModuleConfigurations();
+        if (moduleConfs.length == 1 && "*".equals(moduleConfs[0])) {
+            if (dd instanceof DefaultDependencyDescriptor) {
+                DefaultDependencyDescriptor ddd = (DefaultDependencyDescriptor) dd;
+                newdd.confs = new LinkedHashMap(ddd.confs);
+                newdd.setExcludeRules(new LinkedHashMap(ddd.getExcludeRules()));
+                newdd.setIncludeRules(new LinkedHashMap(ddd.getIncludeRules()));
+                newdd.setDependencyArtifacts(new LinkedHashMap(ddd.getDependencyArtifacts()));
+            } else {
+                throw new IllegalArgumentException(
+                        "dependency descriptor transformation does not support * module confs "
+                                + "with descriptors which aren't DefaultDependencyDescriptor");
+            }
+        } else {
+            for (int i = 0; i < moduleConfs.length; i++) {
+                newdd.confs.put(moduleConfs[i],
+                    new ArrayList(Arrays.asList(dd.getDependencyConfigurations(moduleConfs[i]))));
+                newdd.getExcludeRules().put(moduleConfs[i],
+                    new ArrayList(Arrays.asList(dd.getExcludeRules(moduleConfs[i]))));
+                newdd.getIncludeRules().put(moduleConfs[i],
+                    new ArrayList(Arrays.asList(dd.getIncludeRules(moduleConfs[i]))));
+                newdd.getDependencyArtifacts().put(moduleConfs[i],
+                    new ArrayList(Arrays.asList(dd.getDependencyArtifacts(moduleConfs[i]))));
+            }
+        }
+        if (fromSystem) {
+            newdd.asSystem = dd;
+        }
+        return newdd;
+    }
+
+    private final ModuleRevisionId revId;
+
+    private ModuleRevisionId dynamicRevId;
+
+    private Map/* <String,List<String>> */confs = new LinkedHashMap();
+
+    // Map (String masterConf -> Collection(DependencyArtifactDescriptor))
+    private Map dependencyArtifacts; // initialized on demand only for memory consumption reason
+
+    // Map (String masterConf -> Collection(IncludeRule))
+    private Map includeRules; // initialized on demand only for memory consumption reason
+
+    // Map (String masterConf -> Collection(ExcludeRule))
+    private Map excludeRules; // initialized on demand only for memory consumption reason
+
+    /**
+     * Used to indicate that this revision must be used in case of conflicts, independently of
+     * conflicts manager
+     */
+    private boolean isForce;
+
+    /**
+     * Used to indicate that the dependency is a changing one, i.e. that ivy should not rely on the
+     * version to know if it can trust artifacts in cache
+     */
+    private boolean isChanging;
+
+    private ModuleRevisionId parentId;
+
+    private boolean isTransitive = true;
+
+    /**
+     * This namespace should be used to check
+     */
+    private Namespace namespace = null;
+
+    private final ModuleDescriptor md;
+
+    private DependencyDescriptor asSystem = this;
+
+    private ModuleRevisionId sourceModule;
+
+    private DefaultDependencyDescriptor(DefaultDependencyDescriptor dd, ModuleRevisionId revision) {
+        Checks.checkNotNull(dd, "dd");
+        Checks.checkNotNull(revision, "revision");
+
+        if (!revision.getModuleId().equals(dd.getDependencyId())) {
+            throw new IllegalArgumentException(
+                    "new ModuleRevisionId MUST have the same ModuleId as original one."
+                            + " original = " + dd.getDependencyId() + " new = "
+                            + revision.getModuleId());
+        }
+        md = dd.md;
+        parentId = dd.parentId;
+        revId = revision;
+        dynamicRevId = dd.dynamicRevId;
+        isForce = dd.isForce;
+        isChanging = dd.isChanging;
+        isTransitive = dd.isTransitive;
+        namespace = dd.namespace;
+        confs.putAll(dd.confs);
+        excludeRules = dd.excludeRules == null ? null : new LinkedHashMap(dd.excludeRules);
+        includeRules = dd.includeRules == null ? null : new LinkedHashMap(dd.includeRules);
+        dependencyArtifacts = dd.dependencyArtifacts == null ? null : new LinkedHashMap(
+                dd.dependencyArtifacts);
+        sourceModule = dd.sourceModule;
+    }
+
+    public DefaultDependencyDescriptor(ModuleDescriptor md, ModuleRevisionId mrid, boolean force,
+            boolean changing, boolean transitive) {
+        this(md, mrid, mrid, force, changing, transitive);
+    }
+
+    public DefaultDependencyDescriptor(ModuleRevisionId mrid, boolean force) {
+        this(mrid, force, false);
+    }
+
+    public DefaultDependencyDescriptor(ModuleRevisionId mrid, boolean force, boolean changing) {
+        this(null, mrid, mrid, force, changing, true);
+    }
+
+    public DefaultDependencyDescriptor(ModuleDescriptor md, ModuleRevisionId mrid,
+            ModuleRevisionId dynamicConstraint, boolean force, boolean changing, boolean transitive) {
+        Checks.checkNotNull(mrid, "mrid");
+        Checks.checkNotNull(dynamicConstraint, "dynamicConstraint");
+
+        this.md = md;
+        revId = mrid;
+        dynamicRevId = dynamicConstraint;
+        isForce = force;
+        isChanging = changing;
+        isTransitive = transitive;
+        sourceModule = md == null ? null : md.getModuleRevisionId();
+    }
+
+    public ModuleId getDependencyId() {
+        return getDependencyRevisionId().getModuleId();
+    }
+
+    public ModuleRevisionId getDependencyRevisionId() {
+        return revId;
+    }
+
+    public ModuleRevisionId getDynamicConstraintDependencyRevisionId() {
+        return dynamicRevId;
+    }
+
+    public String[] getModuleConfigurations() {
+        return (String[]) confs.keySet().toArray(new String[confs.keySet().size()]);
+    }
+
+    public String[] getDependencyConfigurations(String moduleConfiguration) {
+        return getDependencyConfigurations(moduleConfiguration, moduleConfiguration);
+    }
+
+    /**
+     * Return the dependency configurations mapped to the given moduleConfiguration, actually
+     * resolved because of the given requestedConfiguration
+     * <p>
+     * Usually requestedConfiguration and moduleConfiguration are the same, except when a conf
+     * extends another, then the moduleConfiguration is the configuration currently resolved (the
+     * extended one), and requestedConfiguration is the one actually requested initially (the
+     * extending one). Both moduleConfiguration and requestedConfiguration are configurations of the
+     * caller, the array returned is composed of the required configurations of the dependency
+     * described by this descriptor.
+     */
+    public String[] getDependencyConfigurations(String moduleConfiguration,
+            String requestedConfiguration) {
+        if (md != null) {
+            Configuration c = md.getConfiguration(moduleConfiguration);
+            if (c instanceof ConfigurationIntersection) {
+                ConfigurationIntersection intersection = (ConfigurationIntersection) c;
+                Set /* <String> */intersectedDepConfs = new HashSet();
+                String[] intersected = intersection.getIntersectedConfigurationNames();
+                for (int i = 0; i < intersected.length; i++) {
+                    Collection depConfs = getDependencyConfigurationsIncludingExtending(
+                        intersected[i], requestedConfiguration);
+                    if (intersectedDepConfs.isEmpty()) {
+                        intersectedDepConfs.addAll(depConfs);
+                    } else {
+                        if (intersectedDepConfs.contains("*")) {
+                            intersectedDepConfs.remove("*");
+                            intersectedDepConfs.addAll(depConfs);
+                        } else if (depConfs.contains("*")) {
+                            // nothing to do, intersection of 'something'
+                            // with 'everything' is 'something'
+                        } else {
+                            Set /* <String> */intersectedDepConfsCopy = intersectedDepConfs;
+                            intersectedDepConfs = new HashSet();
+                            for (Iterator it = intersectedDepConfsCopy.iterator(); it.hasNext();) {
+                                String intersectedDepConf = (String) it.next();
+                                if (depConfs.contains(intersectedDepConf)) {
+                                    // the conf is present in both sets,
+                                    // so it is in the intersection
+                                    intersectedDepConfs.add(intersectedDepConf);
+                                    continue;
+                                }
+                                /*
+                                 * we do not handle special confs like *!sg or [cond]* in right hand
+                                 * confs yet: it would require supporting parenthesis grouping in
+                                 * configurations intersection interpretation
+                                 * 
+                                 * for (Iterator it2 = depConfs.iterator(); it2.hasNext();) { String
+                                 * depConf = (String) it2.next(); if (depConf.startsWith("*")) { if
+                                 * (intersectedDepConf .indexOf("(" + depConf + ")") != -1) {
+                                 * intersectedDepConfs.add(intersectedDepConf); } else {
+                                 * intersectedDepConfs.add( "(" + intersectedDepConf + ")+(" +
+                                 * depConf + ")"); } } else if (intersectedDepConf.startsWith("*"))
+                                 * { if (depConf .indexOf("(" + intersectedDepConf + ")") != -1) {
+                                 * intersectedDepConfs.add(depConf); } else {
+                                 * intersectedDepConfs.add( depConf + "+" + intersectedDepConf); } }
+                                 * }
+                                 */
+                            }
+                        }
+                    }
+                }
+                List confsList = (List) confs.get(moduleConfiguration);
+                if (confsList != null) {
+                    intersectedDepConfs.addAll(confsList);
+                }
+                if (intersectedDepConfs.isEmpty()) {
+                    List defConfs = (List) confs.get("*");
+                    if (defConfs != null) {
+                        for (Iterator it = defConfs.iterator(); it.hasNext();) {
+                            String mappedConf = (String) it.next();
+                            if (mappedConf != null && mappedConf.startsWith("@+")) {
+                                return new String[] {moduleConfiguration + mappedConf.substring(1)};
+                            } else if (mappedConf != null && mappedConf.equals("@")) {
+                                return new String[] {moduleConfiguration};
+                            }
+                        }
+                    }
+                }
+                return (String[]) intersectedDepConfs
+                        .toArray(new String[intersectedDepConfs.size()]);
+            } else if (c instanceof ConfigurationGroup) {
+                ConfigurationGroup group = (ConfigurationGroup) c;
+                Set /* <String> */groupDepConfs = new HashSet();
+                String[] members = group.getMembersConfigurationNames();
+                for (int i = 0; i < members.length; i++) {
+                    Collection depConfs = getDependencyConfigurationsIncludingExtending(members[i],
+                        requestedConfiguration);
+                    groupDepConfs.addAll(depConfs);
+                }
+                return (String[]) groupDepConfs.toArray(new String[groupDepConfs.size()]);
+            }
+        }
+
+        List confsList = (List) confs.get(moduleConfiguration);
+        if (confsList == null) {
+            // there is no mapping defined for this configuration, add the 'other' mappings.
+            confsList = (List) confs.get("%");
+        }
+        List defConfs = (List) confs.get("*");
+        Collection ret = new LinkedHashSet();
+        if (confsList != null) {
+            ret.addAll(confsList);
+        }
+        if (defConfs != null) {
+            ret.addAll(defConfs);
+        }
+
+        Collection replacedRet = new LinkedHashSet();
+        for (Iterator iter = ret.iterator(); iter.hasNext();) {
+            String c = (String) iter.next();
+            String replacedConf = replaceSelfFallbackPattern(c, moduleConfiguration);
+            if (replacedConf == null) {
+                replacedConf = replaceThisFallbackPattern(c, requestedConfiguration);
+            }
+            if (replacedConf != null) {
+                c = replacedConf;
+            }
+            replacedRet.add(c);
+        }
+        ret = replacedRet;
+        if (ret.remove("*")) {
+            StringBuffer r = new StringBuffer("*");
+            // merge excluded configurations as one conf like *!A!B
+            for (Iterator iter = ret.iterator(); iter.hasNext();) {
+                String c = (String) iter.next();
+                if (c.startsWith("!")) {
+                    r.append(c);
+                }
+            }
+            return new String[] {r.toString()};
+        }
+        return (String[]) ret.toArray(new String[ret.size()]);
+    }
+
+    private Collection getDependencyConfigurationsIncludingExtending(String conf,
+            String requestedConfiguration) {
+        Set/* <String> */allDepConfs = new LinkedHashSet();
+        allDepConfs
+                .addAll(Arrays.asList(getDependencyConfigurations(conf, requestedConfiguration)));
+
+        Collection extendingConfs = Configuration.findConfigurationExtending(conf,
+            md.getConfigurations());
+        for (Iterator it = extendingConfs.iterator(); it.hasNext();) {
+            Configuration extendingConf = (Configuration) it.next();
+            allDepConfs.addAll(Arrays.asList(getDependencyConfigurations(extendingConf.getName(),
+                requestedConfiguration)));
+        }
+        return allDepConfs;
+    }
+
+    protected static String replaceSelfFallbackPattern(final String conf,
+            final String moduleConfiguration) {
+        return replaceFallbackConfigurationPattern(SELF_FALLBACK_PATTERN, conf, moduleConfiguration);
+    }
+
+    protected static String replaceThisFallbackPattern(final String conf,
+            final String requestedConfiguration) {
+        return replaceFallbackConfigurationPattern(THIS_FALLBACK_PATTERN, conf,
+            requestedConfiguration);
+    }
+
+    /**
+     * Replaces fallback patterns with correct values if fallback pattern exists.
+     * 
+     * @param pattern
+     *            pattern to look for
+     * @param conf
+     *            configuration mapping from dependency element
+     * @param moduleConfiguration
+     *            module's configuration to use for replacement
+     * @return Replaced string if pattern matched. Otherwise null.
+     */
+    protected static String replaceFallbackConfigurationPattern(final Pattern pattern,
+            final String conf, final String moduleConfiguration) {
+        Matcher matcher = pattern.matcher(conf);
+        if (matcher.matches()) {
+            String mappedConf = moduleConfiguration;
+            if (matcher.group(1) != null) {
+                mappedConf = mappedConf + matcher.group(1);
+            }
+            if (matcher.group(2) != null) {
+                mappedConf = mappedConf + matcher.group(2);
+            }
+            return mappedConf;
+        }
+        return null;
+    }
+
+    public String[] getDependencyConfigurations(String[] moduleConfigurations) {
+        Set confs = new LinkedHashSet();
+        for (int i = 0; i < moduleConfigurations.length; i++) {
+            confs.addAll(Arrays.asList(getDependencyConfigurations(moduleConfigurations[i])));
+        }
+        if (confs.contains("*")) {
+            return new String[] {"*"};
+        }
+        return (String[]) confs.toArray(new String[confs.size()]);
+    }
+
+    public DependencyArtifactDescriptor[] getDependencyArtifacts(String moduleConfiguration) {
+        Collection artifacts = getCollectionForConfiguration(moduleConfiguration,
+            dependencyArtifacts);
+        return (DependencyArtifactDescriptor[]) artifacts
+                .toArray(new DependencyArtifactDescriptor[artifacts.size()]);
+    }
+
+    public IncludeRule[] getIncludeRules(String moduleConfiguration) {
+        Collection rules = getCollectionForConfiguration(moduleConfiguration, includeRules);
+        return (IncludeRule[]) rules.toArray(new IncludeRule[rules.size()]);
+    }
+
+    public ExcludeRule[] getExcludeRules(String moduleConfiguration) {
+        Collection rules = getCollectionForConfiguration(moduleConfiguration, excludeRules);
+        return (ExcludeRule[]) rules.toArray(new ExcludeRule[rules.size()]);
+    }
+
+    private Set getCollectionForConfiguration(String moduleConfiguration, Map collectionMap) {
+        if (collectionMap == null || collectionMap.isEmpty()) {
+            return Collections.EMPTY_SET;
+        }
+        Collection artifacts = (Collection) collectionMap.get(moduleConfiguration);
+        Collection defArtifacts = (Collection) collectionMap.get("*");
+        Set ret = new LinkedHashSet();
+        if (artifacts != null) {
+            ret.addAll(artifacts);
+        }
+        if (defArtifacts != null) {
+            ret.addAll(defArtifacts);
+        }
+        return ret;
+    }
+
+    public DependencyArtifactDescriptor[] getDependencyArtifacts(String[] moduleConfigurations) {
+        Set artifacts = new LinkedHashSet();
+        for (int i = 0; i < moduleConfigurations.length; i++) {
+            artifacts.addAll(Arrays.asList(getDependencyArtifacts(moduleConfigurations[i])));
+        }
+        return (DependencyArtifactDescriptor[]) artifacts
+                .toArray(new DependencyArtifactDescriptor[artifacts.size()]);
+    }
+
+    public IncludeRule[] getIncludeRules(String[] moduleConfigurations) {
+        Set rules = new LinkedHashSet();
+        for (int i = 0; i < moduleConfigurations.length; i++) {
+            rules.addAll(Arrays.asList(getIncludeRules(moduleConfigurations[i])));
+        }
+        return (IncludeRule[]) rules.toArray(new IncludeRule[rules.size()]);
+    }
+
+    public ExcludeRule[] getExcludeRules(String[] moduleConfigurations) {
+        Set rules = new LinkedHashSet();
+        for (int i = 0; i < moduleConfigurations.length; i++) {
+            rules.addAll(Arrays.asList(getExcludeRules(moduleConfigurations[i])));
+        }
+        return (ExcludeRule[]) rules.toArray(new ExcludeRule[rules.size()]);
+    }
+
+    public DependencyArtifactDescriptor[] getAllDependencyArtifacts() {
+        if (dependencyArtifacts == null) {
+            return new DependencyArtifactDescriptor[0];
+        }
+        Set ret = mergeAll(dependencyArtifacts);
+        return (DependencyArtifactDescriptor[]) ret.toArray(new DependencyArtifactDescriptor[ret
+                .size()]);
+    }
+
+    public IncludeRule[] getAllIncludeRules() {
+        if (includeRules == null) {
+            return new IncludeRule[0];
+        }
+        Set ret = mergeAll(includeRules);
+        return (IncludeRule[]) ret.toArray(new IncludeRule[ret.size()]);
+    }
+
+    public ExcludeRule[] getAllExcludeRules() {
+        if (excludeRules == null) {
+            return new ExcludeRule[0];
+        }
+        Set ret = mergeAll(excludeRules);
+        return (ExcludeRule[]) ret.toArray(new ExcludeRule[ret.size()]);
+    }
+
+    private Set mergeAll(Map artifactsMap) {
+        Set ret = new LinkedHashSet();
+        for (Iterator it = artifactsMap.values().iterator(); it.hasNext();) {
+            Collection artifacts = (Collection) it.next();
+            ret.addAll(artifacts);
+        }
+        return ret;
+    }
+
+    public void addDependencyConfiguration(String masterConf, String depConf) {
+        if ((md != null) && !"*".equals(masterConf) && !"%".equals(masterConf)) {
+            Configuration config;
+            if (masterConf.startsWith("!")) {
+                config = md.getConfiguration(masterConf.substring(1));
+            } else {
+                config = md.getConfiguration(masterConf);
+            }
+            if (config == null) {
+                throw new IllegalArgumentException("Cannot add dependency '" + revId
+                        + "' to configuration '" + masterConf + "' of module "
+                        + md.getModuleRevisionId() + " because this configuration doesn't exist!");
+            }
+            if (config instanceof ConfigurationGroup) {
+                ConfigurationGroup group = (ConfigurationGroup) config;
+                String[] members = group.getMembersConfigurationNames();
+                for (int i = 0; i < members.length; i++) {
+                    addDependencyConfiguration(members[i], depConf);
+                }
+                return;
+            }
+        }
+
+        List confsList = (List) confs.get(masterConf);
+        if (confsList == null) {
+            confsList = new ArrayList();
+            confs.put(masterConf, confsList);
+        }
+        if (!confsList.contains(depConf)) {
+            confsList.add(depConf);
+        }
+    }
+
+    public void addDependencyArtifact(String masterConf, DependencyArtifactDescriptor dad) {
+        addObjectToConfiguration(masterConf, dad, getDependencyArtifacts());
+    }
+
+    public void addIncludeRule(String masterConf, IncludeRule rule) {
+        addObjectToConfiguration(masterConf, rule, getIncludeRules());
+    }
+
+    public void addExcludeRule(String masterConf, ExcludeRule rule) {
+        addObjectToConfiguration(masterConf, rule, getExcludeRules());
+    }
+
+    private void addObjectToConfiguration(String callerConf, Object toAdd, Map confsMap) {
+        Collection col = (Collection) confsMap.get(callerConf);
+        if (col == null) {
+            col = new ArrayList();
+            confsMap.put(callerConf, col);
+        }
+        col.add(toAdd);
+    }
+
+    /**
+     * only works when namespace is properly set. The behaviour is not specified if namespace is not
+     * set
+     */
+    public boolean doesExclude(String[] moduleConfigurations, ArtifactId artifactId) {
+        if (namespace != null) {
+            artifactId = NameSpaceHelper
+                    .transform(artifactId, namespace.getFromSystemTransformer());
+        }
+        ExcludeRule[] rules = getExcludeRules(moduleConfigurations);
+        for (int i = 0; i < rules.length; i++) {
+            if (MatcherHelper.matches(rules[i].getMatcher(), rules[i].getId(), artifactId)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if this descriptor contains any exclusion rule
+     * 
+     * @return
+     */
+    public boolean canExclude() {
+        return excludeRules != null && !excludeRules.isEmpty();
+    }
+
+    public String toString() {
+        return "dependency: " + revId + " " + confs;
+    }
+
+    public boolean isForce() {
+        return isForce;
+    }
+
+    public ModuleRevisionId getParentRevisionId() {
+        return md != null ? md.getResolvedModuleRevisionId() : parentId;
+    }
+
+    public boolean isChanging() {
+        return isChanging;
+    }
+
+    public boolean isTransitive() {
+        return isTransitive;
+    }
+
+    public Namespace getNamespace() {
+        return namespace;
+    }
+
+    public String getAttribute(String attName) {
+        return revId.getAttribute(attName);
+    }
+
+    public Map getAttributes() {
+        return revId.getAttributes();
+    }
+
+    public String getExtraAttribute(String attName) {
+        return revId.getExtraAttribute(attName);
+    }
+
+    public Map getExtraAttributes() {
+        return revId.getExtraAttributes();
+    }
+
+    public Map getQualifiedExtraAttributes() {
+        return revId.getQualifiedExtraAttributes();
+    }
+
+    public DependencyDescriptor asSystem() {
+        return asSystem;
+    }
+
+    private void setDependencyArtifacts(Map dependencyArtifacts) {
+        this.dependencyArtifacts = dependencyArtifacts;
+    }
+
+    private Map getDependencyArtifacts() {
+        if (dependencyArtifacts == null) {
+            dependencyArtifacts = new LinkedHashMap();
+        }
+        return dependencyArtifacts;
+    }
+
+    private void setIncludeRules(Map includeRules) {
+        this.includeRules = includeRules;
+    }
+
+    private Map getIncludeRules() {
+        if (includeRules == null) {
+            includeRules = new LinkedHashMap();
+        }
+        return includeRules;
+    }
+
+    private void setExcludeRules(Map excludeRules) {
+        this.excludeRules = excludeRules;
+    }
+
+    private Map getExcludeRules() {
+        if (excludeRules == null) {
+            excludeRules = new LinkedHashMap();
+        }
+        return excludeRules;
+    }
+
+    public ModuleRevisionId getSourceModule() {
+        return sourceModule;
+    }
+
+    public DependencyDescriptor clone(ModuleRevisionId revision) {
+        return new DefaultDependencyDescriptor(this, revision);
+    }
+}
diff --git a/src/java/org/apache/ivy/core/module/descriptor/DefaultExcludeRule.java b/src/java/org/apache/ivy/core/module/descriptor/DefaultExcludeRule.java
new file mode 100644
index 0000000..52acf82
--- /dev/null
+++ b/src/java/org/apache/ivy/core/module/descriptor/DefaultExcludeRule.java
@@ -0,0 +1,34 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.module.descriptor;
+
+import java.util.Map;
+
+import org.apache.ivy.core.module.id.ArtifactId;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+
+public class DefaultExcludeRule extends AbstractIncludeExcludeRule implements ExcludeRule {
+
+    public DefaultExcludeRule(ArtifactId aid, PatternMatcher matcher, Map extraAttributes) {
+        super(aid, matcher, extraAttributes);
+    }
+
+    public String toString() {
+        return "E:" + super.toString();
+    }
+}
diff --git a/src/java/org/apache/ivy/core/module/descriptor/DefaultExtendsDescriptor.java b/src/java/org/apache/ivy/core/module/descriptor/DefaultExtendsDescriptor.java
new file mode 100644
index 0000000..1c7be57
--- /dev/null
+++ b/src/java/org/apache/ivy/core/module/descriptor/DefaultExtendsDescriptor.java
@@ -0,0 +1,93 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.module.descriptor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+
+public class DefaultExtendsDescriptor implements ExtendsDescriptor {
+
+    private ModuleDescriptor parent;
+
+    private String location;
+
+    private List extendsTypes;
+
+    private boolean local;
+
+    public DefaultExtendsDescriptor(ModuleDescriptor parent, String location, String[] types) {
+        this(parent, location, types, false);
+    }
+
+    public DefaultExtendsDescriptor(ModuleDescriptor parent, String location, String[] types,
+            boolean local) {
+        this.parent = parent;
+        this.location = location;
+        this.local = local;
+        this.extendsTypes = new ArrayList(types.length);
+        for (int i = 0; i < types.length; ++i) {
+            extendsTypes.add(types[i]);
+        }
+    }
+
+    public ModuleRevisionId getParentRevisionId() {
+        return parent.getModuleRevisionId();
+    }
+
+    public ModuleRevisionId getResolvedParentRevisionId() {
+        return parent.getResolvedModuleRevisionId();
+    }
+
+    public ModuleDescriptor getParentMd() {
+        return parent;
+    }
+
+    public String getLocation() {
+        return location;
+    }
+
+    public String[] getExtendsTypes() {
+        return (String[]) extendsTypes.toArray(new String[extendsTypes.size()]);
+    }
+
+    public boolean isAllInherited() {
+        return extendsTypes.contains("all");
+    }
+
+    public boolean isInfoInherited() {
+        return isAllInherited() || extendsTypes.contains("info");
+    }
+
+    public boolean isDescriptionInherited() {
+        return isAllInherited() || extendsTypes.contains("description");
+    }
+
+    public boolean areConfigurationsInherited() {
+        return isAllInherited() || extendsTypes.contains("configurations");
+    }
+
+    public boolean areDependenciesInherited() {
+        return isAllInherited() || extendsTypes.contains("dependencies");
+    }
+
+    public boolean isLocal() {
+        return local;
+    }
+}
diff --git a/src/java/org/apache/ivy/core/module/descriptor/DefaultIncludeRule.java b/src/java/org/apache/ivy/core/module/descriptor/DefaultIncludeRule.java
new file mode 100644
index 0000000..87ddc58
--- /dev/null
+++ b/src/java/org/apache/ivy/core/module/descriptor/DefaultIncludeRule.java
@@ -0,0 +1,34 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.module.descriptor;
+
+import java.util.Map;
+
+import org.apache.ivy.core.module.id.ArtifactId;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+
+public class DefaultIncludeRule extends AbstractIncludeExcludeRule implements IncludeRule {
+
+    public DefaultIncludeRule(ArtifactId aid, PatternMatcher matcher, Map extraAttributes) {
+        super(aid, matcher, extraAttributes);
+    }
+
+    public String toString() {
+        return "I:" + super.toString();
+    }
+}
diff --git a/src/java/org/apache/ivy/core/module/descriptor/DefaultModuleDescriptor.java b/src/java/org/apache/ivy/core/module/descriptor/DefaultModuleDescriptor.java
new file mode 100644
index 0000000..2c544c5
--- /dev/null
+++ b/src/java/org/apache/ivy/core/module/descriptor/DefaultModuleDescriptor.java
@@ -0,0 +1,883 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.module.descriptor;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+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.core.module.id.ModuleRules;
+import org.apache.ivy.core.module.status.StatusManager;
+import org.apache.ivy.plugins.conflict.ConflictManager;
+import org.apache.ivy.plugins.matcher.MapMatcher;
+import org.apache.ivy.plugins.matcher.MatcherHelper;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+import org.apache.ivy.plugins.namespace.NameSpaceHelper;
+import org.apache.ivy.plugins.namespace.Namespace;
+import org.apache.ivy.plugins.namespace.NamespaceTransformer;
+import org.apache.ivy.plugins.parser.ModuleDescriptorParser;
+import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorParser;
+import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorWriter;
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.plugins.version.VersionMatcher;
+import org.apache.ivy.util.Message;
+
+/**
+ *
+ */
+public class DefaultModuleDescriptor implements ModuleDescriptor {
+
+    public static DefaultModuleDescriptor newDefaultInstance(ModuleRevisionId mrid) {
+        return newDefaultInstance(mrid, null);
+    }
+
+    public static DefaultModuleDescriptor newCallerInstance(ModuleRevisionId mrid, String[] confs,
+            boolean transitive, boolean changing) {
+        DefaultModuleDescriptor moduleDescriptor = new DefaultModuleDescriptor(
+                ModuleRevisionId.newInstance(mrid.getOrganisation(), mrid.getName() + "-caller",
+                    "working"), "integration", null, true);
+        for (int i = 0; i < confs.length; i++) {
+            moduleDescriptor.addConfiguration(new Configuration(confs[i]));
+        }
+        moduleDescriptor.setLastModified(System.currentTimeMillis());
+        DefaultDependencyDescriptor dd = new DefaultDependencyDescriptor(moduleDescriptor, mrid,
+                true, changing, transitive);
+        for (int j = 0; j < confs.length; j++) {
+            dd.addDependencyConfiguration(confs[j], confs[j]);
+        }
+        moduleDescriptor.addDependency(dd);
+
+        return moduleDescriptor;
+    }
+
+    public static DefaultModuleDescriptor newCallerInstance(ModuleRevisionId[] mrid,
+            boolean transitive, boolean changing) {
+        DefaultModuleDescriptor moduleDescriptor = new DefaultModuleDescriptor(
+                ModuleRevisionId.newInstance("caller", "all-caller", "working"), "integration",
+                null, true);
+        moduleDescriptor.addConfiguration(new Configuration(DEFAULT_CONFIGURATION));
+        moduleDescriptor.setLastModified(System.currentTimeMillis());
+        for (int i = 0; i < mrid.length; i++) {
+            DefaultDependencyDescriptor dd = new DefaultDependencyDescriptor(moduleDescriptor,
+                    mrid[i], true, changing, transitive);
+            dd.addDependencyConfiguration(DEFAULT_CONFIGURATION, "*");
+            moduleDescriptor.addDependency(dd);
+        }
+
+        return moduleDescriptor;
+    }
+
+    public static DefaultModuleDescriptor newDefaultInstance(ModuleRevisionId mrid,
+            DependencyArtifactDescriptor[] artifacts) {
+        DefaultModuleDescriptor moduleDescriptor = new DefaultModuleDescriptor(mrid, "release",
+                null, true);
+        moduleDescriptor.addConfiguration(new Configuration(DEFAULT_CONFIGURATION));
+        if (artifacts != null && artifacts.length > 0) {
+            for (int i = 0; i < artifacts.length; i++) {
+                moduleDescriptor.addArtifact(DEFAULT_CONFIGURATION,
+                    new MDArtifact(moduleDescriptor, artifacts[i].getName(),
+                            artifacts[i].getType(), artifacts[i].getExt(), artifacts[i].getUrl(),
+                            artifacts[i].getExtraAttributes()));
+            }
+        } else {
+            moduleDescriptor.addArtifact(DEFAULT_CONFIGURATION, new MDArtifact(moduleDescriptor,
+                    mrid.getName(), "jar", "jar"));
+        }
+        moduleDescriptor.setLastModified(System.currentTimeMillis());
+        return moduleDescriptor;
+    }
+
+    public static DefaultModuleDescriptor newBasicInstance(ModuleRevisionId mrid,
+            Date publicationDate) {
+        DefaultModuleDescriptor moduleDescriptor = new DefaultModuleDescriptor(mrid, "release",
+                publicationDate, false);
+        moduleDescriptor.addConfiguration(new Configuration(DEFAULT_CONFIGURATION));
+        moduleDescriptor.addArtifact(DEFAULT_CONFIGURATION,
+            new MDArtifact(moduleDescriptor, mrid.getName(), "jar", "jar"));
+        return moduleDescriptor;
+    }
+
+    /**
+     * Transforms the given module descriptor of the given namespace and return a new module
+     * descriptor in the system namespace. <i>Note that dependency exclude rules are not converted
+     * in system namespace, because they aren't transformable (the name space hasn't the ability to
+     * convert regular expressions)</i>
+     * 
+     * @param md
+     * @param ns
+     * @return
+     */
+    public static ModuleDescriptor transformInstance(ModuleDescriptor md, Namespace ns) {
+        NamespaceTransformer t = ns.getToSystemTransformer();
+        if (t.isIdentity()) {
+            return md;
+        }
+        DefaultModuleDescriptor nmd = new DefaultModuleDescriptor(md.getParser(), md.getResource());
+        nmd.revId = t.transform(md.getModuleRevisionId());
+        nmd.resolvedRevId = t.transform(md.getResolvedModuleRevisionId());
+        nmd.status = md.getStatus();
+        nmd.publicationDate = md.getPublicationDate();
+        nmd.resolvedPublicationDate = md.getResolvedPublicationDate();
+
+        ExtendsDescriptor[] ed = md.getInheritedDescriptors();
+        for (int i = 0; i < ed.length; ++i) {
+            ModuleDescriptor parentMd = ed[i].getParentMd();
+            DefaultModuleDescriptor parentNmd = new DefaultModuleDescriptor(parentMd.getParser(),
+                    parentMd.getResource());
+            parentNmd.revId = t.transform(parentMd.getModuleRevisionId());
+            parentNmd.resolvedRevId = t.transform(parentMd.getResolvedModuleRevisionId());
+            parentNmd.status = parentMd.getStatus();
+            parentNmd.publicationDate = parentMd.getPublicationDate();
+            parentNmd.resolvedPublicationDate = parentMd.getResolvedPublicationDate();
+
+            nmd.inheritedDescriptors.add(new DefaultExtendsDescriptor(parentNmd, ed[i]
+                    .getLocation(), ed[i].getExtendsTypes()));
+        }
+
+        DependencyDescriptor[] dd = md.getDependencies();
+        for (int i = 0; i < dd.length; i++) {
+            nmd.dependencies.add(NameSpaceHelper.toSystem(dd[i], ns));
+        }
+        Configuration[] confs = md.getConfigurations();
+        for (int i = 0; i < confs.length; i++) {
+            nmd.configurations.put(confs[i].getName(), confs[i]);
+            Artifact[] arts = md.getArtifacts(confs[i].getName());
+            for (int j = 0; j < arts.length; j++) {
+                nmd.addArtifact(confs[i].getName(), NameSpaceHelper.transform(arts[j], t));
+            }
+        }
+        nmd.setDefault(md.isDefault());
+        if (md instanceof DefaultModuleDescriptor) {
+            DefaultModuleDescriptor dmd = (DefaultModuleDescriptor) md;
+            nmd.conflictManagers = (ModuleRules) dmd.conflictManagers.clone();
+            nmd.dependencyDescriptorMediators = (ModuleRules) dmd.dependencyDescriptorMediators
+                    .clone();
+        } else {
+            Message.warn("transformed module descriptor is not a default module descriptor: "
+                    + "impossible to copy conflict manager and version mediation configuration: "
+                    + md);
+        }
+        nmd.licenses.addAll(Arrays.asList(md.getLicenses()));
+        nmd.homePage = md.getHomePage();
+        nmd.description = md.getDescription();
+        nmd.lastModified = md.getLastModified();
+        nmd.extraAttributesNamespaces = md.getExtraAttributesNamespaces();
+        nmd.extraInfos = md.getExtraInfos();
+        nmd.namespace = ns;
+
+        return nmd;
+    }
+
+    private ModuleRevisionId revId;
+
+    private ModuleRevisionId resolvedRevId;
+
+    private String status = StatusManager.getCurrent().getDefaultStatus();
+
+    private Date publicationDate;
+
+    private Date resolvedPublicationDate;
+
+    private List dependencies = new ArrayList(); // List (DependencyDescriptor)
+
+    private Map configurations = new LinkedHashMap(); // Map(String conf -> Configuration)
+
+    private Map artifactsByConf = new HashMap(); // Map (String conf -> Collection(Artifact))
+
+    private Collection artifacts = new LinkedHashSet(); // Collection(Artifact)
+
+    // all artifacts could also be found in the artifactsByConf map, but here we can
+    // preserve the order
+
+    private boolean isDefault = false;
+
+    private ModuleRules conflictManagers = new ModuleRules();
+
+    private ModuleRules dependencyDescriptorMediators = new ModuleRules();
+
+    private List licenses = new ArrayList(); // List(License)
+
+    private String homePage;
+
+    private String description = "";
+
+    private long lastModified = 0;
+
+    private Namespace namespace;
+
+    private String defaultConf;
+
+    private String defaultConfMapping;
+
+    private boolean mappingOverride;
+
+    private ModuleDescriptorParser parser;
+
+    private Resource resource;
+
+    private List excludeRules = new ArrayList(); // List(ExcludeRule)
+
+    private Artifact metadataArtifact;
+
+    private List inheritedDescriptors = new ArrayList(); // List(ExtendsDescriptor)
+
+    private Map/* <String,String> */extraAttributesNamespaces = new LinkedHashMap();
+
+    private List<ExtraInfoHolder> extraInfos = new ArrayList<ExtraInfoHolder>();
+
+    public DefaultModuleDescriptor(ModuleRevisionId id, String status, Date pubDate) {
+        this(id, status, pubDate, false);
+    }
+
+    public DefaultModuleDescriptor(ModuleRevisionId id, String status, Date pubDate,
+            boolean isDefault) {
+        if (id == null) {
+            throw new NullPointerException("null module revision id not allowed");
+        }
+        if (status == null) {
+            throw new NullPointerException("null status not allowed");
+        }
+        this.revId = id;
+        this.resolvedRevId = id;
+        this.status = status;
+        this.publicationDate = pubDate;
+        this.resolvedPublicationDate = publicationDate == null ? new Date() : publicationDate;
+        this.isDefault = isDefault;
+        this.parser = XmlModuleDescriptorParser.getInstance();
+    }
+
+    /**
+     * IMPORTANT : at least call setModuleRevisionId and setResolvedPublicationDate with instances
+     * created by this constructor !
+     */
+    public DefaultModuleDescriptor(ModuleDescriptorParser parser, Resource res) {
+        this.parser = parser;
+        resource = res;
+    }
+
+    public Artifact getMetadataArtifact() {
+        if (metadataArtifact == null) {
+            metadataArtifact = DefaultArtifact.newIvyArtifact(resolvedRevId,
+                resolvedPublicationDate);
+        }
+        return metadataArtifact;
+    }
+
+    public void setModuleArtifact(Artifact moduleArtifact) {
+        this.metadataArtifact = moduleArtifact;
+    }
+
+    public boolean isDefault() {
+        return isDefault;
+    }
+
+    public void setPublicationDate(Date publicationDate) {
+        this.publicationDate = publicationDate;
+        if (resolvedPublicationDate == null) {
+            resolvedPublicationDate = publicationDate == null ? new Date() : publicationDate;
+        }
+    }
+
+    public Date getPublicationDate() {
+        return publicationDate;
+    }
+
+    public void setResolvedPublicationDate(Date publicationDate) {
+        if (publicationDate == null) {
+            throw new NullPointerException("null publication date not allowed");
+        }
+        resolvedPublicationDate = publicationDate;
+    }
+
+    public Date getResolvedPublicationDate() {
+        return resolvedPublicationDate;
+    }
+
+    public String getRevision() {
+        return getResolvedModuleRevisionId().getRevision();
+    }
+
+    public void setModuleRevisionId(ModuleRevisionId revId) {
+        if (revId == null) {
+            throw new NullPointerException("null module revision id not allowed");
+        }
+        this.revId = revId;
+        if (resolvedRevId == null) {
+            resolvedRevId = revId;
+        }
+    }
+
+    public void setResolvedModuleRevisionId(ModuleRevisionId revId) {
+        resolvedRevId = revId;
+    }
+
+    public void setStatus(String status) {
+        this.status = status;
+    }
+
+    public void addInheritedDescriptor(ExtendsDescriptor descriptor) {
+        inheritedDescriptors.add(descriptor);
+    }
+
+    public void addDependency(DependencyDescriptor dependency) {
+        dependencies.add(dependency);
+    }
+
+    public void addConfiguration(Configuration conf) {
+        configurations.put(conf.getName(), conf);
+    }
+
+    /**
+     * Artifact configurations are not used since added artifact may not be entirely completed, so
+     * its configurations data may not be accurate
+     * 
+     * @param conf
+     * @param artifact
+     */
+    public void addArtifact(String conf, Artifact artifact) {
+        Configuration c = getConfiguration(conf);
+        if (c == null) {
+            throw new IllegalArgumentException("Cannot add artifact '"
+                    + artifact.getId().getArtifactId().getShortDescription()
+                    + "' to configuration '" + conf + "' of module " + revId
+                    + " because this configuration doesn't exist!");
+        }
+        if (c instanceof ConfigurationGroup) {
+            ConfigurationGroup group = (ConfigurationGroup) c;
+            String[] members = group.getMembersConfigurationNames();
+            for (int i = 0; i < members.length; i++) {
+                addArtifact(members[i], artifact);
+            }
+        } else {
+            Collection artifacts = (Collection) artifactsByConf.get(conf);
+            if (artifacts == null) {
+                artifacts = new ArrayList();
+                artifactsByConf.put(conf, artifacts);
+            }
+            artifacts.add(artifact);
+            this.artifacts.add(artifact);
+        }
+    }
+
+    public ModuleRevisionId getModuleRevisionId() {
+        return revId;
+    }
+
+    public ModuleRevisionId getResolvedModuleRevisionId() {
+        return resolvedRevId;
+    }
+
+    public String getStatus() {
+        return status;
+    }
+
+    public ExtendsDescriptor[] getInheritedDescriptors() {
+        return (ExtendsDescriptor[]) inheritedDescriptors
+                .toArray(new ExtendsDescriptor[inheritedDescriptors.size()]);
+    }
+
+    public Configuration[] getConfigurations() {
+        return (Configuration[]) configurations.values().toArray(
+            new Configuration[configurations.size()]);
+    }
+
+    public String[] getConfigurationsNames() {
+        return (String[]) configurations.keySet().toArray(new String[configurations.size()]);
+    }
+
+    public String[] getPublicConfigurationsNames() {
+        List ret = new ArrayList();
+        for (Iterator iter = configurations.values().iterator(); iter.hasNext();) {
+            Configuration conf = (Configuration) iter.next();
+            if (conf.getVisibility() == Configuration.Visibility.PUBLIC) {
+                ret.add(conf.getName());
+            }
+        }
+        return (String[]) ret.toArray(new String[ret.size()]);
+    }
+
+    /**
+     * Returns the configuration object with the given name in the current module descriptor, null
+     * if not found.
+     */
+    public Configuration getConfiguration(String confName) {
+        Configuration configuration = (Configuration) configurations.get(confName);
+        if (configuration == null && confName != null) {
+            // let's first check if the configuration is a conf group
+            Matcher m = Pattern.compile("\\*\\[([^=]+)\\=([^\\]]+)\\]").matcher(confName);
+            if (m.matches()) {
+                String attName = m.group(1);
+                String attValue = m.group(2);
+
+                // this is a conf group, let's search for its members
+                Map /* <String,Configuration> */members = new LinkedHashMap();
+                for (Iterator it = configurations.values().iterator(); it.hasNext();) {
+                    Configuration conf = (Configuration) it.next();
+                    if (attValue.equals(conf.getAttribute(attName))) {
+                        members.put(conf.getName(), conf);
+                    }
+                }
+                return new ConfigurationGroup(confName, members);
+            }
+
+            // let's see if a configuration intersection is requested
+            String[] confs = confName.split("\\+");
+            if (confs.length <= 1) {
+                return null;
+            }
+            Map /* <String,Configuration> */intersectedConfs = new LinkedHashMap();
+            for (int i = 0; i < confs.length; i++) {
+                Configuration c = (Configuration) configurations.get(confs[i]);
+                if (c == null) {
+                    Message.verbose("missing configuration '" + confs[i] + "' from intersection "
+                            + confName + " in " + this);
+                    return null;
+                }
+                intersectedConfs.put(confs[i], c);
+            }
+            return new ConfigurationIntersection(confName, intersectedConfs);
+        }
+        return configuration;
+    }
+
+    public Artifact[] getArtifacts(String conf) {
+        Configuration c = getConfiguration(conf);
+        if (c == null) {
+            return new Artifact[0];
+        }
+        Collection artifacts = (Collection) artifactsByConf.get(conf);
+        if (c instanceof ConfigurationIntersection) {
+            ConfigurationIntersection intersection = (ConfigurationIntersection) c;
+            String[] intersected = intersection.getIntersectedConfigurationNames();
+            Set/* <Artifact> */intersectedArtifacts = new LinkedHashSet();
+            for (int j = 0; j < intersected.length; j++) {
+                Collection arts = getArtifactsIncludingExtending(intersected[j]);
+                if (intersectedArtifacts.isEmpty()) {
+                    intersectedArtifacts.addAll(arts);
+                } else {
+                    intersectedArtifacts.retainAll(arts);
+                }
+            }
+            if (artifacts != null) {
+                intersectedArtifacts.addAll(artifacts);
+            }
+            return (Artifact[]) intersectedArtifacts.toArray(new Artifact[intersectedArtifacts
+                    .size()]);
+        } else if (c instanceof ConfigurationGroup) {
+            ConfigurationGroup group = (ConfigurationGroup) c;
+            String[] members = group.getMembersConfigurationNames();
+            Set/* <Artifact> */groupArtifacts = new LinkedHashSet();
+            for (int i = 0; i < members.length; i++) {
+                groupArtifacts.addAll(getArtifactsIncludingExtending(members[i]));
+            }
+            if (artifacts != null) {
+                groupArtifacts.addAll(artifacts);
+            }
+            return (Artifact[]) groupArtifacts.toArray(new Artifact[groupArtifacts.size()]);
+        } else {
+            if (artifacts == null) {
+                return new Artifact[0];
+            } else {
+                return (Artifact[]) artifacts.toArray(new Artifact[artifacts.size()]);
+            }
+        }
+    }
+
+    private Collection/* <Artifact> */getArtifactsIncludingExtending(String conf) {
+        Collection extendingConfs = Configuration.findConfigurationExtending(conf,
+            getConfigurations());
+        Set/* <Artifact> */artifacts = new LinkedHashSet();
+        Collection arts = (Collection) artifactsByConf.get(conf);
+        if (arts != null) {
+            artifacts.addAll(arts);
+        }
+        for (Iterator it = extendingConfs.iterator(); it.hasNext();) {
+            Configuration extendingConf = (Configuration) it.next();
+            arts = (Collection) artifactsByConf.get(extendingConf.getName());
+            if (arts != null) {
+                artifacts.addAll(arts);
+            }
+        }
+        return artifacts;
+    }
+
+    public Artifact[] getAllArtifacts() {
+        return (Artifact[]) artifacts.toArray(new Artifact[artifacts.size()]);
+    }
+
+    public DependencyDescriptor[] getDependencies() {
+        return (DependencyDescriptor[]) dependencies.toArray(new DependencyDescriptor[dependencies
+                .size()]);
+    }
+
+    public boolean dependsOn(VersionMatcher matcher, ModuleDescriptor md) {
+        for (Iterator iter = dependencies.iterator(); iter.hasNext();) {
+            DependencyDescriptor dd = (DependencyDescriptor) iter.next();
+            if (dd.getDependencyId().equals(md.getModuleRevisionId().getModuleId())) {
+                if (md.getResolvedModuleRevisionId().getRevision() == null) {
+                    return true;
+                } else if (matcher.accept(dd.getDependencyRevisionId(), md)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    public void toIvyFile(File destFile) throws ParseException, IOException {
+        if (parser != null && resource != null) {
+            parser.toIvyFile(resource.openStream(), resource, destFile, this);
+        } else {
+            XmlModuleDescriptorWriter.write(this, destFile);
+        }
+    }
+
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((revId == null) ? 0 : revId.hashCode());
+        return result;
+    }
+
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        DefaultModuleDescriptor other = (DefaultModuleDescriptor) obj;
+        if (revId == null) {
+            if (other.revId != null) {
+                return false;
+            }
+        } else if (!revId.equals(other.revId)) {
+            return false;
+        }
+        return true;
+    }
+
+    public String toString() {
+        return "module: " + revId + " status=" + status + " publication=" + publicationDate
+                + " configurations=" + configurations + " artifacts=" + artifactsByConf
+                + " dependencies=" + dependencies;
+    }
+
+    public void setDefault(boolean b) {
+        isDefault = b;
+    }
+
+    /**
+     * regular expressions as explained in Pattern class may be used in ModuleId organisation and
+     * name
+     * 
+     * @param moduleId
+     * @param matcher
+     * @param resolverName
+     */
+    public void addConflictManager(ModuleId moduleId, PatternMatcher matcher,
+            ConflictManager manager) {
+        conflictManagers.defineRule(new MapMatcher(moduleId.getAttributes(), matcher), manager);
+    }
+
+    public ConflictManager getConflictManager(ModuleId moduleId) {
+        return (ConflictManager) conflictManagers.getRule(moduleId);
+    }
+
+    public void addDependencyDescriptorMediator(ModuleId moduleId, PatternMatcher matcher,
+            DependencyDescriptorMediator ddm) {
+        dependencyDescriptorMediators.defineRule(new MapMatcher(moduleId.getAttributes(), matcher),
+            ddm);
+    }
+
+    public DependencyDescriptor mediate(DependencyDescriptor dd) {
+        Object[] mediators = dependencyDescriptorMediators.getRules(dd.getDependencyId());
+        for (int i = 0; i < mediators.length; i++) {
+            dd = ((DependencyDescriptorMediator) mediators[i]).mediate(dd);
+        }
+        return dd;
+    }
+
+    public ModuleRules/* <DependencyDescriptorMediator> */getAllDependencyDescriptorMediators() {
+        return (ModuleRules) dependencyDescriptorMediators.clone();
+    }
+
+    public void addLicense(License license) {
+        licenses.add(license);
+    }
+
+    public License[] getLicenses() {
+        return (License[]) licenses.toArray(new License[licenses.size()]);
+    }
+
+    public String getHomePage() {
+        return homePage;
+    }
+
+    public void setHomePage(String homePage) {
+        this.homePage = homePage;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public long getLastModified() {
+        return lastModified;
+    }
+
+    public void setLastModified(long lastModified) {
+        this.lastModified = lastModified;
+    }
+
+    public Namespace getNamespace() {
+        return namespace;
+    }
+
+    public boolean isNamespaceUseful() {
+        for (Iterator iter = dependencies.iterator(); iter.hasNext();) {
+            DependencyDescriptor dd = (DependencyDescriptor) iter.next();
+            if (dd.getAllExcludeRules().length > 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public void setNamespace(Namespace ns) {
+        namespace = ns;
+    }
+
+    /**
+     * Throws an exception if the module descriptor is inconsistent For the moment, only extended
+     * configurations existence and cycles are checked
+     */
+    public void check() {
+        Stack confs = new Stack();
+        for (Iterator iter = configurations.values().iterator(); iter.hasNext();) {
+            Configuration conf = (Configuration) iter.next();
+            String[] ext = conf.getExtends();
+            for (int i = 0; i < ext.length; i++) {
+                confs.push(conf.getName());
+                checkConf(confs, ext[i].trim());
+                confs.pop();
+            }
+        }
+    }
+
+    private void checkConf(Stack confs, String confName) {
+        int index = confs.indexOf(confName);
+        if (index != -1) {
+            StringBuffer cycle = new StringBuffer();
+            for (; index < confs.size(); index++) {
+                cycle.append(confs.get(index)).append(" => ");
+            }
+            cycle.append(confName);
+            throw new IllegalStateException("illegal cycle detected in configuration extension: "
+                    + cycle);
+        }
+        Configuration conf = getConfiguration(confName);
+        if (conf == null) {
+            throw new IllegalStateException("unknown configuration '" + confName
+                    + "'. It is extended by " + confs.get(confs.size() - 1));
+        }
+        String[] ext = conf.getExtends();
+        for (int i = 0; i < ext.length; i++) {
+            confs.push(conf.getName());
+            checkConf(confs, ext[i].trim());
+            confs.pop();
+        }
+    }
+
+    public String getDefaultConf() {
+        return defaultConf;
+    }
+
+    public void setDefaultConf(String defaultConf) {
+        this.defaultConf = defaultConf;
+    }
+
+    public String getDefaultConfMapping() {
+        return defaultConfMapping;
+    }
+
+    public void setDefaultConfMapping(String defaultConfMapping) {
+        this.defaultConfMapping = defaultConfMapping;
+    }
+
+    public void setMappingOverride(boolean override) {
+        mappingOverride = override;
+    }
+
+    public boolean isMappingOverride() {
+        return mappingOverride;
+    }
+
+    public String getAttribute(String attName) {
+        return resolvedRevId.getAttribute(attName);
+    }
+
+    public Map getAttributes() {
+        return resolvedRevId.getAttributes();
+    }
+
+    public String getExtraAttribute(String attName) {
+        return resolvedRevId.getExtraAttribute(attName);
+    }
+
+    public Map getExtraAttributes() {
+        return resolvedRevId.getExtraAttributes();
+    }
+
+    public Map getQualifiedExtraAttributes() {
+        return resolvedRevId.getQualifiedExtraAttributes();
+    }
+
+    public ModuleDescriptorParser getParser() {
+        return parser;
+    }
+
+    public Resource getResource() {
+        return resource;
+    }
+
+    public void addExcludeRule(ExcludeRule rule) {
+        excludeRules.add(rule);
+    }
+
+    public boolean canExclude() {
+        return !excludeRules.isEmpty();
+    }
+
+    /**
+     * only works when namespace is properly set. The behaviour is not specified if namespace is not
+     * set
+     */
+    public boolean doesExclude(String[] moduleConfigurations, ArtifactId artifactId) {
+        if (namespace != null) {
+            artifactId = NameSpaceHelper
+                    .transform(artifactId, namespace.getFromSystemTransformer());
+        }
+        ExcludeRule[] rules = getExcludeRules(moduleConfigurations);
+        for (int i = 0; i < rules.length; i++) {
+            if (MatcherHelper.matches(rules[i].getMatcher(), rules[i].getId(), artifactId)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public ExcludeRule[] getAllExcludeRules() {
+        return (ExcludeRule[]) excludeRules.toArray(new ExcludeRule[excludeRules.size()]);
+    }
+
+    public ExcludeRule[] getExcludeRules(String[] moduleConfigurations) {
+        Set rules = new LinkedHashSet();
+        for (Iterator iter = excludeRules.iterator(); iter.hasNext();) {
+            ExcludeRule rule = (ExcludeRule) iter.next();
+            String[] ruleConfs = rule.getConfigurations();
+            if (containsAny(ruleConfs, moduleConfigurations)) {
+                rules.add(rule);
+            }
+        }
+        return (ExcludeRule[]) rules.toArray(new ExcludeRule[rules.size()]);
+    }
+
+    private boolean containsAny(String[] arr1, String[] arr2) {
+        return new ArrayList(Arrays.asList(arr1)).removeAll(Arrays.asList(arr2));
+    }
+
+    public Map getExtraAttributesNamespaces() {
+        return extraAttributesNamespaces;
+    }
+
+    public void addExtraAttributeNamespace(String prefix, String namespace) {
+        extraAttributesNamespaces.put(prefix, namespace);
+    }
+
+    @Deprecated
+    public void addExtraInfo(String infoKey, String value) {
+        extraInfos.add(new ExtraInfoHolder(infoKey, value));
+    }
+
+    @Deprecated
+    public Map<String, String> getExtraInfo() {
+        Map<String, String> map = new HashMap<String, String>();
+        for (ExtraInfoHolder extraInfo : extraInfos) {
+            populateExtraInfoMap(map, extraInfo);
+        }
+        return map;
+    }
+
+    private void populateExtraInfoMap(Map<String, String> map, ExtraInfoHolder extraInfo) {
+        map.put(extraInfo.getName(), extraInfo.getContent());
+        for (ExtraInfoHolder nested : extraInfo.getNestedExtraInfoHolder()) {
+            populateExtraInfoMap(map, nested);
+        }
+    }
+
+    public List<ExtraInfoHolder> getExtraInfos() {
+        return extraInfos;
+    }
+
+    public void addExtraInfo(ExtraInfoHolder extraInfo) {
+        extraInfos.add(extraInfo);
+    }
+
+    public String getExtraInfoContentByTagName(String tagName) {
+        ExtraInfoHolder extraInfoByTagName = getExtraInfoByTagName(tagName);
+        if (extraInfoByTagName != null) {
+            return extraInfoByTagName.getContent();
+        }
+        return null;
+    }
+
+    public ExtraInfoHolder getExtraInfoByTagName(String tagName) {
+        for (ExtraInfoHolder extraInfoHolder : extraInfos) {
+            if (extraInfoHolder.getName().equals(tagName)) {
+                return extraInfoHolder;
+            }
+        }
+        return null;
+
+    }
+}
diff --git a/src/java/org/apache/ivy/core/module/descriptor/DependencyArtifactDescriptor.java b/src/java/org/apache/ivy/core/module/descriptor/DependencyArtifactDescriptor.java
new file mode 100644
index 0000000..1ea78da
--- /dev/null
+++ b/src/java/org/apache/ivy/core/module/descriptor/DependencyArtifactDescriptor.java
@@ -0,0 +1,70 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.module.descriptor;
+
+import java.net.URL;
+
+import org.apache.ivy.util.extendable.ExtendableItem;
+
+/**
+ * This describes an artifact that is asked for a dependency. It is used to define an (additional)
+ * artifact not declared by a dependency module descriptor.
+ */
+public interface DependencyArtifactDescriptor extends ExtendableItem {
+    /**
+     * Returns the dependency descriptor in which this dependency artifact descriptor is declared.
+     * 
+     * @return the dependency descriptor in which this dependency artifact descriptor is declared.
+     */
+    public DependencyDescriptor getDependencyDescriptor();
+
+    /**
+     * Returns the name of the artifact asked
+     * 
+     * @return
+     */
+    public String getName();
+
+    /**
+     * Returns the type of the artifact asked
+     * 
+     * @return
+     */
+    public String getType();
+
+    /**
+     * Returns the ext of the artifact asked
+     * 
+     * @return
+     */
+    public String getExt();
+
+    /**
+     * Returns the url to look this artifact at
+     * 
+     * @return
+     */
+    public URL getUrl();
+
+    /**
+     * Returns the configurations of the module in which the artifact is asked
+     * 
+     * @return an array of configuration names in which the artifact is asked
+     */
+    public String[] getConfigurations();
+}
diff --git a/src/java/org/apache/ivy/core/module/descriptor/DependencyDescriptor.java b/src/java/org/apache/ivy/core/module/descriptor/DependencyDescriptor.java
new file mode 100644
index 0000000..1780d1f
--- /dev/null
+++ b/src/java/org/apache/ivy/core/module/descriptor/DependencyDescriptor.java
@@ -0,0 +1,141 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.module.descriptor;
+
+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.namespace.Namespace;
+import org.apache.ivy.util.extendable.ExtendableItem;
+
+/**
+ * Describes a dependency from a depender to a dependee.
+ * <p>
+ * The main information this descriptor contains is the constraint on the dependency. There is
+ * actually two kind of dependency constraints: the default dependency constraint, which can be
+ * obtained with {@link #getDependencyRevisionId()}, and corresponds to the <code>rev</code>
+ * attribute in Ivy files. This is the constraint as it should be honored by Ivy in default resolve
+ * mode.
+ * </p>
+ * <p>
+ * Then there is the dynamic constraint, which can either be the same as the default constraint, or
+ * the original dependency constraint when an Ivy file is delivered an published to a repository.
+ * This dynamic constraint is returned by {@link #getDynamicConstraintDependencyRevisionId()}, and
+ * corresponds to the <code>revconstraint</code> attribute in the Ivy file. In some resolve mode,
+ * this constraint can be used instead of the default dependency constraint when performing
+ * dependency resolution.
+ * </p>
+ */
+public interface DependencyDescriptor extends ExtendableItem, InheritableItem {
+    ModuleId getDependencyId();
+
+    /**
+     * Used to indicate that this revision must be used in case of conflicts, independently of
+     * conflicts manager. This only works for direct dependencies, and not transitive ones.
+     * 
+     * @return true if this dependency should be used, false if conflicts manager can do its work.
+     */
+    boolean isForce();
+
+    /**
+     * Used to indicate that this dependency is a changing one. A changing dependency in ivy means
+     * that the revision may have its artifacts modified without revision change. When new artifacts
+     * are published a new ivy file should also be published with a new publication date to indicate
+     * to ivy that artifacts have changed and that they should be downloaded again.
+     * 
+     * @return true if this dependency is a changing one
+     */
+    boolean isChanging();
+
+    boolean isTransitive();
+
+    ModuleRevisionId getParentRevisionId();
+
+    /**
+     * Returns the constraint on dependency this descriptor represents.
+     * 
+     * @return the constraint on dependency.
+     */
+    ModuleRevisionId getDependencyRevisionId();
+
+    /**
+     * Returns the dynamic constraint on dependency this descriptor represents.
+     * 
+     * @return the dynamic constraint on dependency, or exact constraint if no dynamic constraint is
+     *         specified.
+     */
+    ModuleRevisionId getDynamicConstraintDependencyRevisionId();
+
+    String[] getModuleConfigurations();
+
+    String[] getDependencyConfigurations(String moduleConfiguration, String requestedConfiguration);
+
+    String[] getDependencyConfigurations(String moduleConfiguration);
+
+    String[] getDependencyConfigurations(String[] moduleConfigurations);
+
+    Namespace getNamespace();
+
+    DependencyArtifactDescriptor[] getAllDependencyArtifacts();
+
+    DependencyArtifactDescriptor[] getDependencyArtifacts(String moduleConfigurations);
+
+    DependencyArtifactDescriptor[] getDependencyArtifacts(String[] moduleConfigurations);
+
+    IncludeRule[] getAllIncludeRules();
+
+    IncludeRule[] getIncludeRules(String moduleConfigurations);
+
+    IncludeRule[] getIncludeRules(String[] moduleConfigurations);
+
+    ExcludeRule[] getAllExcludeRules();
+
+    ExcludeRule[] getExcludeRules(String moduleConfigurations);
+
+    ExcludeRule[] getExcludeRules(String[] moduleConfigurations);
+
+    /**
+     * Returns true if
+     * 
+     * @param moduleConfigurations
+     * @param artifactId
+     * @return
+     */
+    boolean doesExclude(String[] moduleConfigurations, ArtifactId artifactId);
+
+    /**
+     * Returns true if this descriptor contains any exclusion rule
+     * 
+     * @return true if this descriptor contains any exclusion rule
+     */
+    public boolean canExclude();
+
+    DependencyDescriptor asSystem();
+
+    /**
+     * Clones current dependency descriptor with another revision.
+     * 
+     * @param revision
+     *            the revision of the cloned dependency descriptor
+     * @return the cloned dependency descriptor
+     * @throws IllegalArgumentException
+     *             if the given {@link ModuleRevisionId} has not the same {@link ModuleId} as the
+     *             {@link ModuleRevisionId} of this descriptor.
+     */
+    DependencyDescriptor clone(ModuleRevisionId revision);
+}
diff --git a/src/java/org/apache/ivy/core/module/descriptor/DependencyDescriptorMediator.java b/src/java/org/apache/ivy/core/module/descriptor/DependencyDescriptorMediator.java
new file mode 100644
index 0000000..611f458
--- /dev/null
+++ b/src/java/org/apache/ivy/core/module/descriptor/DependencyDescriptorMediator.java
@@ -0,0 +1,44 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.module.descriptor;
+
+/**
+ * A DependencyDescriptorMediator is responsible for dependency descriptor mediation.
+ * <p>
+ * Dependency descriptor mediation consists in adjusting dependency descriptors according to a
+ * context, environment, the stack of dependers, ...
+ * </p>
+ */
+public interface DependencyDescriptorMediator {
+
+    /**
+     * Mediates the given {@link DependencyDescriptor} according to this {@link ModuleDescriptor}.
+     * <p>
+     * This method gives the opportunity to a ModuleDescriptor to override dependency version
+     * information of any of its transitive dependencies, since it is called by dependency resolvers
+     * before actually resolving a dependency.
+     * </p>
+     * 
+     * @param dd
+     *            the dependency descriptor which should be mediated.
+     * @return the mediated {@link DependencyDescriptor}, or the original
+     *         {@link DependencyDescriptor} if no mediation is required by this ModuleDescriptor.
+     */
+    DependencyDescriptor mediate(DependencyDescriptor dd);
+
+}
diff --git a/src/java/org/apache/ivy/core/module/descriptor/ExcludeRule.java b/src/java/org/apache/ivy/core/module/descriptor/ExcludeRule.java
new file mode 100644
index 0000000..33c82c2
--- /dev/null
+++ b/src/java/org/apache/ivy/core/module/descriptor/ExcludeRule.java
@@ -0,0 +1,50 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.module.descriptor;
+
+import org.apache.ivy.core.module.id.ArtifactId;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+import org.apache.ivy.util.extendable.ExtendableItem;
+
+/**
+ * This describes a rule of exclusion. It is used to restrict the artifacts asked for a dependency,
+ * by excluding a whole module or some artifacts of a dependency.
+ */
+public interface ExcludeRule extends ExtendableItem {
+
+    /**
+     * Returns the id of the described artifact, without revision information
+     * 
+     * @return
+     */
+    public ArtifactId getId();
+
+    /**
+     * Returns the configurations of the module in which the artifact is asked
+     * 
+     * @return an array of configuration names in which the artifact is asked
+     */
+    public String[] getConfigurations();
+
+    /**
+     * Returns the matcher to use to know if an artifact match the current descriptor
+     * 
+     * @return
+     */
+    public PatternMatcher getMatcher();
+}
diff --git a/src/java/org/apache/ivy/core/module/descriptor/ExtendsDescriptor.java b/src/java/org/apache/ivy/core/module/descriptor/ExtendsDescriptor.java
new file mode 100644
index 0000000..8b7456d
--- /dev/null
+++ b/src/java/org/apache/ivy/core/module/descriptor/ExtendsDescriptor.java
@@ -0,0 +1,68 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.module.descriptor;
+
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+
+/**
+ * Describes parent descriptor information for a module descriptor.
+ */
+public interface ExtendsDescriptor {
+
+    /** get the module revision id of the declared parent descriptor */
+    public ModuleRevisionId getParentRevisionId();
+
+    /**
+     * get the resolved revision id for {@link #getParentRevisionId}, see
+     * {@link org.apache.ivy.core.module.descriptor.ModuleDescriptor#getResolvedModuleRevisionId()}
+     */
+    public ModuleRevisionId getResolvedParentRevisionId();
+
+    public ModuleDescriptor getParentMd();
+
+    /**
+     * If there is an explicit path to check for the parent descriptor, return it. Otherwise returns
+     * null.
+     */
+    public String getLocation();
+
+    /**
+     * Get the parts of the parent descriptor that are inherited. Default supported types are
+     * <code>info</code>, <code>description</code>, <code>configurations</code>,
+     * <code>dependencies</code>, and/or <code>all</code>. Ivy extensions may add support for
+     * additional extends types.
+     */
+    public String[] getExtendsTypes();
+
+    /** @return true if the <code>all</code> extend type is specified, implying all other types */
+    public boolean isAllInherited();
+
+    /** @return true if parent info attributes are inherited (organisation, branch, revision, etc) */
+    public boolean isInfoInherited();
+
+    /** @return true if parent description is inherited */
+    public boolean isDescriptionInherited();
+
+    /** @return true if parent configurations are inherited */
+    public boolean areConfigurationsInherited();
+
+    /** @return true if parent dependencies are inherited */
+    public boolean areDependenciesInherited();
+
+    public boolean isLocal();
+}
diff --git a/src/java/org/apache/ivy/core/module/descriptor/ExtraInfoHolder.java b/src/java/org/apache/ivy/core/module/descriptor/ExtraInfoHolder.java
new file mode 100644
index 0000000..1422378
--- /dev/null
+++ b/src/java/org/apache/ivy/core/module/descriptor/ExtraInfoHolder.java
@@ -0,0 +1,76 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.module.descriptor;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ExtraInfoHolder {
+
+    private String name;
+
+    private Map<String, String> attributes = new LinkedHashMap<String, String>();
+
+    private String content;
+
+    private List<ExtraInfoHolder> nestedExtraInfoHolder = new ArrayList<ExtraInfoHolder>();
+
+    public ExtraInfoHolder() {
+
+    }
+
+    public ExtraInfoHolder(String name, String content) {
+        this.name = name;
+        this.content = content;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public Map<String, String> getAttributes() {
+        return attributes;
+    }
+
+    public void setAttributes(Map<String, String> attributes) {
+        this.attributes = attributes;
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+    public List<ExtraInfoHolder> getNestedExtraInfoHolder() {
+        return nestedExtraInfoHolder;
+    }
+
+    public void setNestedExtraInfoHolder(List<ExtraInfoHolder> nestedExtraInfoHolder) {
+        this.nestedExtraInfoHolder = nestedExtraInfoHolder;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/module/descriptor/IncludeRule.java b/src/java/org/apache/ivy/core/module/descriptor/IncludeRule.java
new file mode 100644
index 0000000..7cbeada
--- /dev/null
+++ b/src/java/org/apache/ivy/core/module/descriptor/IncludeRule.java
@@ -0,0 +1,51 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.module.descriptor;
+
+import org.apache.ivy.core.module.id.ArtifactId;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+import org.apache.ivy.util.extendable.ExtendableItem;
+
+/**
+ * This describes a rule of inclusion. It is used to resctrict the artifacts and modules asked for a
+ * dependency, by including only modules and artifacts matching the rule
+ */
+public interface IncludeRule extends ExtendableItem {
+
+    /**
+     * Returns the id of the described artifact, without revision information
+     * 
+     * @return
+     */
+    public ArtifactId getId();
+
+    /**
+     * Returns the configurations of the module in which the artifact is asked
+     * 
+     * @return an array of configuration names in which the artifact is asked
+     */
+    public String[] getConfigurations();
+
+    /**
+     * Returns the matcher to use to know if an artifact match the current descriptor
+     * 
+     * @return
+     */
+    public PatternMatcher getMatcher();
+
+}
diff --git a/src/java/org/apache/ivy/core/module/descriptor/InheritableItem.java b/src/java/org/apache/ivy/core/module/descriptor/InheritableItem.java
new file mode 100644
index 0000000..07eae0c
--- /dev/null
+++ b/src/java/org/apache/ivy/core/module/descriptor/InheritableItem.java
@@ -0,0 +1,34 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.module.descriptor;
+
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+
+/**
+ * Interface for elements that can be inherited from a parent descriptor by a child descriptor.
+ * 
+ * @see Configuration
+ * @see DependencyDescriptor
+ */
+public interface InheritableItem {
+    /**
+     * @return the module in which this item was actually defined, if different from the module in
+     *         which the item appears. May be null.
+     */
+    public ModuleRevisionId getSourceModule();
+}
diff --git a/src/java/org/apache/ivy/core/module/descriptor/License.java b/src/java/org/apache/ivy/core/module/descriptor/License.java
new file mode 100644
index 0000000..db1c823
--- /dev/null
+++ b/src/java/org/apache/ivy/core/module/descriptor/License.java
@@ -0,0 +1,38 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.module.descriptor;
+
+public class License {
+    private String name;
+
+    private String url;
+
+    public License(String name, String url) {
+        this.name = name;
+        this.url = url;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getUrl() {
+        return url;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/module/descriptor/MDArtifact.java b/src/java/org/apache/ivy/core/module/descriptor/MDArtifact.java
new file mode 100644
index 0000000..0efabbd
--- /dev/null
+++ b/src/java/org/apache/ivy/core/module/descriptor/MDArtifact.java
@@ -0,0 +1,126 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.module.descriptor;
+
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.ivy.core.module.id.ArtifactRevisionId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+
+/**
+ *
+ */
+public class MDArtifact extends AbstractArtifact {
+
+    public static Artifact newIvyArtifact(ModuleDescriptor md) {
+        return new MDArtifact(md, "ivy", "ivy", "xml", true);
+    }
+
+    private ModuleDescriptor md;
+
+    private String name;
+
+    private String type;
+
+    private String ext;
+
+    private List/* <String> */confs = new ArrayList();
+
+    private Map extraAttributes = null;
+
+    private URL url;
+
+    private boolean isMetadata = false;
+
+    public MDArtifact(ModuleDescriptor md, String name, String type, String ext) {
+        this(md, name, type, ext, null, null);
+    }
+
+    public MDArtifact(ModuleDescriptor md, String name, String type, String ext, boolean isMetadata) {
+        this(md, name, type, ext, null, null);
+        this.isMetadata = isMetadata;
+    }
+
+    public MDArtifact(ModuleDescriptor md, String name, String type, String ext, URL url,
+            Map extraAttributes) {
+        if (md == null) {
+            throw new NullPointerException("null module descriptor not allowed");
+        }
+        if (name == null) {
+            throw new NullPointerException("null name not allowed");
+        }
+        if (type == null) {
+            throw new NullPointerException("null type not allowed");
+        }
+        if (ext == null) {
+            throw new NullPointerException("null ext not allowed");
+        }
+        this.md = md;
+        this.name = name;
+        this.type = type;
+        this.ext = ext;
+        this.url = url;
+        this.extraAttributes = extraAttributes;
+    }
+
+    public ModuleRevisionId getModuleRevisionId() {
+        return md.getResolvedModuleRevisionId();
+    }
+
+    public Date getPublicationDate() {
+        return md.getResolvedPublicationDate();
+    }
+
+    public ArtifactRevisionId getId() {
+        // do not cache the result because the resolvedModuleRevisionId can change!
+        return ArtifactRevisionId.newInstance(md.getResolvedModuleRevisionId(), name, type, ext,
+            extraAttributes);
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public String getExt() {
+        return ext;
+    }
+
+    public String[] getConfigurations() {
+        return (String[]) confs.toArray(new String[confs.size()]);
+    }
+
+    public void addConfiguration(String conf) {
+        confs.add(conf);
+    }
+
+    public URL getUrl() {
+        return url;
+    }
+
+    public boolean isMetadata() {
+        return isMetadata;
+    }
+}
diff --git a/src/java/org/apache/ivy/core/module/descriptor/ModuleDescriptor.java b/src/java/org/apache/ivy/core/module/descriptor/ModuleDescriptor.java
new file mode 100644
index 0000000..4eb4a1e
--- /dev/null
+++ b/src/java/org/apache/ivy/core/module/descriptor/ModuleDescriptor.java
@@ -0,0 +1,293 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.module.descriptor;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.ParseException;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+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.core.module.id.ModuleRules;
+import org.apache.ivy.plugins.conflict.ConflictManager;
+import org.apache.ivy.plugins.latest.ArtifactInfo;
+import org.apache.ivy.plugins.parser.ModuleDescriptorParser;
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.plugins.version.VersionMatcher;
+import org.apache.ivy.util.extendable.ExtendableItem;
+
+/**
+ * Descriptor of a module. This is the Java representation of an ivy.xml
+ */
+public interface ModuleDescriptor extends ExtendableItem, ArtifactInfo,
+        DependencyDescriptorMediator {
+
+    public static final String DEFAULT_CONFIGURATION = "default";
+
+    public static final String CALLER_ALL_CONFIGURATION = "all";
+
+    /**
+     * Returns true if this descriptor is a default one, i.e. one generated for a module not
+     * actually having one.
+     * 
+     * @return
+     */
+    boolean isDefault();
+
+    ModuleRevisionId getModuleRevisionId();
+
+    /**
+     * The module revision id returned here is the resolved one, i.e. it is never a latest one. If
+     * the revision has not been resolved, a null revision should be returned by getRevision() of
+     * the returned ModuleRevisionId. This revision must be the same as the module descriptor
+     * resolved revision id unless no module descriptor is defined
+     * 
+     * @return
+     */
+    ModuleRevisionId getResolvedModuleRevisionId();
+
+    /**
+     * This method update the resolved module revision id
+     * 
+     * @param revId
+     */
+    void setResolvedModuleRevisionId(ModuleRevisionId revId);
+
+    /**
+     * Get the list of parent descriptors imported via an <extends> element. Only directly
+     * imported descriptors are included; the parent's parents are not included.
+     */
+    ExtendsDescriptor[] getInheritedDescriptors();
+
+    /**
+     * This method update the resolved publication date
+     * 
+     * @param publicationDate
+     */
+    void setResolvedPublicationDate(Date publicationDate);
+
+    String getStatus();
+
+    /**
+     * May be <code>null</code> if unknown in the descriptor itself.
+     * 
+     * @return The publication date or <code>null</code> when not knwon.
+     */
+    Date getPublicationDate();
+
+    /**
+     * The publication date of the module revision should be the date at which it has been
+     * published, i.e. in general the date of any of its published artifacts, since all published
+     * artifact of a module should follow the same publishing cycle.
+     */
+    Date getResolvedPublicationDate();
+
+    /**
+     * Returns all the configurations declared by this module as an array. This array is never empty
+     * (a 'default' conf is assumed when none is declared in the ivy file)
+     * 
+     * @return all the configurations declared by this module as an array.
+     */
+    Configuration[] getConfigurations();
+
+    String[] getConfigurationsNames();
+
+    String[] getPublicConfigurationsNames();
+
+    Artifact[] getArtifacts(String conf);
+
+    /**
+     * Returns all artifacts of this module, excluding the artifact corresponding to the module
+     * descriptor.
+     * 
+     * @return all published artifacts of this module
+     * @see #getMetadataArtifact()
+     */
+    Artifact[] getAllArtifacts();
+
+    /**
+     * @retun The dependencies of the module. If there is no dependencies return an empty array (non
+     *        null)
+     */
+    DependencyDescriptor[] getDependencies();
+
+    /**
+     * Returns true if the module described by this descriptor dependes directly upon the given
+     * module descriptor
+     * 
+     * @param md
+     * @return
+     */
+    boolean dependsOn(VersionMatcher matcher, ModuleDescriptor md);
+
+    /**
+     * @param confName
+     * @return
+     */
+    Configuration getConfiguration(String confName);
+
+    /**
+     * Returns the conflict manager to use for the given ModuleId, or <code>null</code> if no
+     * specific conflict manager is associated with the given module id in this module descriptor.
+     * 
+     * @param id
+     * @return
+     */
+    ConflictManager getConflictManager(ModuleId id);
+
+    /**
+     * Returns the licenses of the module described by this descriptor
+     * 
+     * @return
+     */
+    License[] getLicenses();
+
+    String getHomePage();
+
+    String getDescription();
+
+    long getLastModified();
+
+    /**
+     * Writes this module descriptor as an ivy file. If this descriptor was obtained through the
+     * parsing of an ivy file, it should keep the layout of the file the most possible similar to
+     * the original one.
+     * 
+     * @param ivyFile
+     *            the destination ivy file
+     */
+    void toIvyFile(File ivyFile) throws ParseException, IOException;
+
+    /**
+     * The ModuleDescriptorParser used to parse this module descriptor, null is no parser was used.
+     * 
+     * @return
+     */
+    ModuleDescriptorParser getParser();
+
+    /**
+     * The resource being the source of this module descriptor, null if no resource corresponds to
+     * this module descriptor
+     * 
+     * @return
+     */
+    Resource getResource();
+
+    /**
+     * Returns the Artifact representing this module descriptor itself.
+     * <p>
+     * Even though the module descriptor is never described as a published artifact of a module in
+     * the module descriptor itself, it is often useful to consider it as any other artifact of the
+     * module. This method allows to access to the Artifact object representing this module
+     * descriptor for this purpose.
+     * </p>
+     * 
+     * @return the Artifact representing this module descriptor itself.
+     */
+    Artifact getMetadataArtifact();
+
+    /**
+     * Returns true if this descriptor contains any exclusion rule
+     * 
+     * @return true if this descriptor contains any exclusion rule
+     */
+    boolean canExclude();
+
+    /**
+     * Returns true if an exclude rule of this module attached to any of the given configurations
+     * matches the given artifact id, and thus exclude it
+     * 
+     * @param moduleConfs
+     * @param artifactId
+     * @return
+     */
+    boolean doesExclude(String[] moduleConfs, ArtifactId artifactId);
+
+    /**
+     * Returns an array of all the exclude rules this module descriptor currently holds. Module
+     * Descriptor exclude rules are used to exclude (usually transitive) dependencies for the whole
+     * module.
+     * 
+     * @return an array of {@link ExcludeRule} this module descriptor holds
+     */
+    public ExcludeRule[] getAllExcludeRules();
+
+    /**
+     * Returns all the dependency descriptor mediators used by this {@link ModuleDescriptor}, as an
+     * instance of {@link ModuleRules}.
+     * <p>
+     * All rules in the {@link ModuleRules} object returned are {@link DependencyDescriptorMediator}
+     * .
+     * </p>
+     * 
+     * @return all the dependency descriptor mediators used by this {@link ModuleDescriptor}.
+     */
+    public ModuleRules/* <DependencyDescriptorMediator> */getAllDependencyDescriptorMediators();
+
+    /**
+     * Returns the list of xml namespaces used by extra attributes, as Map from prefix to namespace
+     * URIs.
+     * <p>
+     * The returned list is never <code>null</code>, it is empty when no extra attribute is used or
+     * if extra attributes are used without xml namespaces
+     * </p>
+     * 
+     * @return the list of xml namespaces used by extra attributes, as Map from prefix to namespace
+     *         URIs.
+     */
+    Map<String, String> getExtraAttributesNamespaces();
+
+    /**
+     * Returns the custom info provided in the info tag. All the tags except the description are
+     * given. The key is the name of the tag, the value is its content. <br />
+     * 
+     * @deprecated this method is not returning the full content of the extra info: to get the full
+     *             structure of the extra infos, use getExtraInfos()
+     */
+    @Deprecated
+    Map<String, String> getExtraInfo();
+
+    /**
+     * Returns a list of extras infos (tag name, attributes and content). All the tags except the
+     * description are given.
+     * 
+     * @since 2.4.0
+     * @return
+     */
+    List<ExtraInfoHolder> getExtraInfos();
+
+    /**
+     * Returns content from first extrainfo matching with given tag name
+     * 
+     * @since 2.4.0
+     * @return
+     */
+    String getExtraInfoContentByTagName(String tagName);
+
+    /**
+     * Returns first extrainfo matching with given tag name
+     * 
+     * @since 2.4.0
+     * @return
+     */
+    ExtraInfoHolder getExtraInfoByTagName(String tagName);
+}
diff --git a/src/java/org/apache/ivy/core/module/descriptor/OverrideDependencyDescriptorMediator.java b/src/java/org/apache/ivy/core/module/descriptor/OverrideDependencyDescriptorMediator.java
new file mode 100644
index 0000000..cbf7482
--- /dev/null
+++ b/src/java/org/apache/ivy/core/module/descriptor/OverrideDependencyDescriptorMediator.java
@@ -0,0 +1,85 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.module.descriptor;
+
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+
+/**
+ * DependencyDescriptorMediator used to override some dependency descriptors values, such as the
+ * branch or version of the dependency.
+ */
+public class OverrideDependencyDescriptorMediator implements DependencyDescriptorMediator {
+    private String version;
+
+    private String branch;
+
+    /**
+     * Constructs a new instance.
+     * 
+     * @param branch
+     *            the branch to give to mediated dependency descriptors, <code>null</code> to keep
+     *            the original branch.
+     * @param version
+     *            the version to give to mediated dependency descriptors, <code>null</code> to keep
+     *            the original one.
+     */
+    public OverrideDependencyDescriptorMediator(String branch, String version) {
+        this.branch = branch;
+        this.version = version;
+    }
+
+    /**
+     * Returns the version this mediator will give to mediated descriptors, or <code>null</code> if
+     * this mediator does not override version.
+     * 
+     * @return the version this mediator will give to mediated descriptors.
+     */
+    public String getVersion() {
+        return version;
+    }
+
+    /**
+     * Returns the branch this mediator will give to mediated descriptors, or <code>null</code> if
+     * this mediator does not override branch.
+     * 
+     * @return the branch this mediator will give to mediated descriptors.
+     */
+    public String getBranch() {
+        return branch;
+    }
+
+    public DependencyDescriptor mediate(DependencyDescriptor dd) {
+        ModuleRevisionId mrid = dd.getDependencyRevisionId();
+        if ((version == null || version.equals(mrid.getRevision()))
+                && (branch == null || branch.equals(mrid.getBranch()))) {
+            return dd;
+        }
+
+        String version = this.version == null ? mrid.getRevision() : this.version;
+        String branch = this.branch == null ? mrid.getBranch() : this.branch;
+
+        // if this is a noop, do not construct any new object
+        if (version.equals(dd.getDependencyRevisionId().getRevision())
+                && branch.equals(dd.getDependencyRevisionId().getBranch())) {
+            return dd;
+        }
+
+        return dd.clone(ModuleRevisionId.newInstance(mrid.getOrganisation(), mrid.getName(),
+            branch, version, mrid.getQualifiedExtraAttributes()));
+    }
+}
diff --git a/src/java/org/apache/ivy/core/module/id/ArtifactId.java b/src/java/org/apache/ivy/core/module/id/ArtifactId.java
new file mode 100644
index 0000000..0991c1f
--- /dev/null
+++ b/src/java/org/apache/ivy/core/module/id/ArtifactId.java
@@ -0,0 +1,107 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.module.id;
+
+/**
+ * Identifies an artifact in a module, without revision information
+ * 
+ * @see <a href="package-summary.html">org.apache.ivy.core.module.id</a>
+ */
+public class ArtifactId {
+    private ModuleId mid;
+
+    private String name;
+
+    private String type;
+
+    private String ext;
+
+    /**
+     * @param mid
+     *            The ModuleId, which is the base of this artifact.
+     * @param name
+     *            The name of the artifact.
+     * @param type
+     *            The type of the artifact.
+     */
+    public ArtifactId(ModuleId mid, String name, String type, String ext) {
+        this.mid = mid;
+        this.name = name;
+        this.type = type;
+        this.ext = ext;
+    }
+
+    /** {@inheritDoc} */
+    public boolean equals(Object obj) {
+        if (!(obj instanceof ArtifactId)) {
+            return false;
+        }
+        ArtifactId aid = (ArtifactId) obj;
+        return getModuleId().equals(aid.getModuleId()) && getName().equals(aid.getName())
+                && getExt().equals(aid.getExt()) && getType().equals(aid.getType());
+    }
+
+    /** {@inheritDoc} */
+    public int hashCode() {
+        // CheckStyle:MagicNumber| OFF
+        int hash = 17;
+        hash += getModuleId().hashCode() * 37;
+        hash += getName().hashCode() * 37;
+        hash += getType().hashCode() * 37;
+        // CheckStyle:MagicNumber| OFF
+        return hash;
+    }
+
+    /** {@inheritDoc} */
+    public String toString() {
+        return getModuleId() + "!" + getShortDescription();
+    }
+
+    public String getShortDescription() {
+        return getName() + "." + getExt()
+                + (getType().equals(getExt()) ? "" : "(" + getType() + ")");
+    }
+
+    /**
+     * @return Returns the module id.
+     */
+    public ModuleId getModuleId() {
+        return mid;
+    }
+
+    /**
+     * @return Returns the name.
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * @return Returns the type.
+     */
+    public String getType() {
+        return type;
+    }
+
+    /**
+     * @return Returns the ext.
+     */
+    public String getExt() {
+        return ext;
+    }
+}
diff --git a/src/java/org/apache/ivy/core/module/id/ArtifactRevisionId.java b/src/java/org/apache/ivy/core/module/id/ArtifactRevisionId.java
new file mode 100644
index 0000000..ca34585
--- /dev/null
+++ b/src/java/org/apache/ivy/core/module/id/ArtifactRevisionId.java
@@ -0,0 +1,121 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.module.id;
+
+import java.util.Map;
+
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.util.extendable.UnmodifiableExtendableItem;
+
+/**
+ * Identifies an artifact in a particular module revision
+ * 
+ * @see <a href="package-summary.html">org.apache.ivy.core.module.id</a>
+ */
+public class ArtifactRevisionId extends UnmodifiableExtendableItem {
+    public static ArtifactRevisionId newInstance(ModuleRevisionId mrid, String name, String type,
+            String ext) {
+        return newInstance(mrid, name, type, ext, null);
+    }
+
+    public static ArtifactRevisionId newInstance(ModuleRevisionId mrid, String name, String type,
+            String ext, Map extraAttributes) {
+        return new ArtifactRevisionId(new ArtifactId(mrid.getModuleId(), name, type, ext), mrid,
+                extraAttributes);
+    }
+
+    private ArtifactId artifactId;
+
+    private ModuleRevisionId mrid;
+
+    public ArtifactRevisionId(ArtifactId artifactId, ModuleRevisionId mrid) {
+        this(artifactId, mrid, null);
+    }
+
+    public ArtifactRevisionId(ArtifactId artfId, ModuleRevisionId mdlRevId, Map extraAttributes) {
+        super(null, extraAttributes);
+        artifactId = artfId;
+        mrid = mdlRevId;
+
+        setStandardAttribute(IvyPatternHelper.ORGANISATION_KEY, getModuleRevisionId()
+                .getOrganisation());
+        setStandardAttribute(IvyPatternHelper.MODULE_KEY, getModuleRevisionId().getName());
+        setStandardAttribute(IvyPatternHelper.REVISION_KEY, getModuleRevisionId().getRevision());
+        setStandardAttribute(IvyPatternHelper.ARTIFACT_KEY, getName());
+        setStandardAttribute(IvyPatternHelper.TYPE_KEY, getType());
+        setStandardAttribute(IvyPatternHelper.EXT_KEY, getExt());
+    }
+
+    public boolean equals(Object obj) {
+        if (!(obj instanceof ArtifactRevisionId)) {
+            return false;
+        }
+        ArtifactRevisionId arid = (ArtifactRevisionId) obj;
+        return getArtifactId().equals(arid.getArtifactId())
+                && getModuleRevisionId().equals(arid.getModuleRevisionId())
+                && getQualifiedExtraAttributes().equals(arid.getQualifiedExtraAttributes());
+    }
+
+    public int hashCode() {
+        // WARN: uniqueness needs to be relatively strong here
+        // CheckStyle:MagicNumber| OFF
+        int hash = 17;
+        hash += getArtifactId().hashCode() * 37;
+        hash += getModuleRevisionId().hashCode() * 37;
+        hash += getQualifiedExtraAttributes().hashCode() * 37;
+        // CheckStyle:MagicNumber| ON
+
+        return hash;
+    }
+
+    public String toString() {
+        return getModuleRevisionId() + "!" + getName() + "." + getExt()
+                + (getType().equals(getExt()) ? "" : "(" + getType() + ")");
+    }
+
+    /**
+     * @return Returns the artifactId.
+     */
+    public ArtifactId getArtifactId() {
+        return artifactId;
+    }
+
+    public ModuleRevisionId getModuleRevisionId() {
+        return mrid;
+    }
+
+    public String getName() {
+        return artifactId.getName();
+    }
+
+    public String getType() {
+        return artifactId.getType();
+    }
+
+    public String getExt() {
+        return artifactId.getExt();
+    }
+
+    /**
+     * @return Returns the revision.
+     */
+    public String getRevision() {
+        return mrid.getRevision();
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/module/id/MatcherLookup.java b/src/java/org/apache/ivy/core/module/id/MatcherLookup.java
new file mode 100644
index 0000000..76a44fc
--- /dev/null
+++ b/src/java/org/apache/ivy/core/module/id/MatcherLookup.java
@@ -0,0 +1,153 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.module.id;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.plugins.matcher.ExactPatternMatcher;
+import org.apache.ivy.plugins.matcher.MapMatcher;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+
+/**
+ * This class targets to speed up lookup for exact pattern matcher by keys, which are created with
+ * (organization, module) information. When exact pattern matcher is added, the key is created from
+ * matcher's attributes. When matcher is looked up against specific module, the key is recreated
+ * from module's attributes.
+ * <p>
+ * </p>
+ * The lookup doesn't target to speed up lookup for non exact pattern matcher. All non exact
+ * matchers are placed in non-keyed collection.
+ * <p>
+ * </p>
+ * At lookup for matchers against specific module, all non exact pattern matchers are iterated to
+ * match with module attributes, and exact pattern matchers binding to the same key will also
+ * iterated to match with module attributes.
+ * <p>
+ * </p>
+ * If there are much more exact pattern matchers than non exact pattern matchers, the matcher lookup
+ * speed can benefit from this class significantly. A quick example could be user declares lots of
+ * dependencyOverrides which are typically exact pattern matchers.
+ * <p>
+ * </p>
+ * If there are balanced exact and non exact pattern matchers, the matcher lookup speed doesn't hurt
+ * by this class.
+ * <p>
+ * </p>
+ */
+public class MatcherLookup {
+
+    //private static final String FORMAT = "{org:%s, module:%s}";
+
+    private static final String DEFAULT = "{org:" + "default" + ", module:" + "default" + "}";
+
+    private Map/* <String, List<MapMatcher>> */lookup = new HashMap();
+
+    private List/* <MapMatcher> */non_exact_matchers = new ArrayList();
+
+    /**
+     * Add matcher.
+     * 
+     * If matcher is exact pattern matcher, it will be associated with a key and placed in keyed
+     * collection.
+     * 
+     * If matcher is not exact pattern matcher, it will be placed into non-keyed collection
+     * 
+     * @param matcher
+     */
+    public void add(MapMatcher matcher) {
+        if (!(matcher.getPatternMatcher() instanceof ExactPatternMatcher)) {
+            non_exact_matchers.add(matcher);
+            return;
+        }
+        Object key = key(matcher.getAttributes());
+        List exact_matchers = (List) lookup.get(key);
+        if (exact_matchers == null) {
+            exact_matchers = new ArrayList();
+            lookup.put(key, exact_matchers);
+        }
+        exact_matchers.add(matcher);
+    }
+
+    /**
+     * Get a list of matchers which can apply to module with specified attributes
+     * 
+     * @param attrs
+     *            A map of attributes that matcher should match.
+     * 
+     * @return list A list of candidate matchers that matches specified attributes
+     */
+    public List get(Map attrs) {
+        List matchers = new ArrayList();
+        // Step 1: find matchers from non_exact_matchers list
+        if (!non_exact_matchers.isEmpty()) {
+            for (Iterator iter = non_exact_matchers.iterator(); iter.hasNext();) {
+                MapMatcher matcher = (MapMatcher) iter.next();
+                if (matcher.matches(attrs)) {
+                    matchers.add(matcher);
+                }
+            }
+        }
+        // Step 2: find matchers from exact_matchers list of key
+        Object key = key(attrs);
+        List exact_matchers = (List) lookup.get(key);
+        if (exact_matchers != null) {
+            for (Iterator iter = exact_matchers.iterator(); iter.hasNext();) {
+                MapMatcher matcher = (MapMatcher) iter.next();
+                if (matcher.matches(attrs)) {
+                    matchers.add(matcher);
+                }
+            }
+        }
+        // Step 3: (iff key != DEFAULT) find matchers from exact_matchers of DEFAULT
+        if (key != DEFAULT) {
+            List default_exact_matchers = (List) lookup.get(DEFAULT);
+            if (default_exact_matchers != null) {
+                for (Iterator iter = default_exact_matchers.iterator(); iter.hasNext();) {
+                    MapMatcher matcher = (MapMatcher) iter.next();
+                    if (matcher.matches(attrs)) {
+                        matchers.add(matcher);
+                    }
+                }
+            }
+        }
+        return matchers;
+    }
+
+    /**
+     * Create a key from specified attributes
+     * 
+     * @param attrs
+     *            A map of attributes
+     * @return key object
+     */
+    private Object key(Map attrs) {
+        Object org = attrs.get(IvyPatternHelper.ORGANISATION_KEY);
+        Object module = attrs.get(IvyPatternHelper.MODULE_KEY);
+        if (org == null || PatternMatcher.ANY_EXPRESSION.equals(org) || module == null
+                || PatternMatcher.ANY_EXPRESSION.equals(module)) {
+            return DEFAULT;
+        }
+        return "{org:" + org + ", module:" + module + "}";
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/module/id/ModuleId.java b/src/java/org/apache/ivy/core/module/id/ModuleId.java
new file mode 100644
index 0000000..4492827
--- /dev/null
+++ b/src/java/org/apache/ivy/core/module/id/ModuleId.java
@@ -0,0 +1,230 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.module.id;
+
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.WeakHashMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.ivy.core.IvyPatternHelper;
+
+/**
+ * Identifies a module, without revision information
+ * 
+ * @see <a href="package-summary.html">org.apache.ivy.core.module.id</a>
+ */
+public class ModuleId implements Comparable {
+    static final String ENCODE_SEPARATOR = ":#@#:";
+
+    private static final Map/* <ModuleId, WeakReference<ModuleId>> */CACHE = new WeakHashMap();
+
+    /**
+     * Returns a ModuleId for the given organization and module name.
+     * 
+     * @param org
+     *            the module's organization, can be <code>null</code>
+     * @param name
+     *            the module's name, must not be <code>null</code>
+     * @return a ModuleId instance
+     */
+    public static ModuleId newInstance(String org, String name) {
+        return intern(new ModuleId(org, name));
+    }
+
+    /**
+     * Returns an intern instance of a ModuleId equals to the given ModuleId if any, or the given
+     * ModuleId.
+     * <p>
+     * This is useful to reduce the number of instances of ModuleId kept in memory, and thus reduce
+     * memory footprint.
+     * </p>
+     * 
+     * @param moduleId
+     *            the module id to return
+     * @return a unit instance of the given module id.
+     */
+    public static ModuleId intern(ModuleId moduleId) {
+        ModuleId r = null;
+
+        synchronized (CACHE) {
+            WeakReference ref = (WeakReference) CACHE.get(moduleId);
+            if (ref != null) {
+                r = (ModuleId) ref.get();
+            }
+            if (r == null) {
+                r = moduleId;
+                CACHE.put(r, new WeakReference(r));
+            }
+        }
+
+        return r;
+    }
+
+    private String organisation;
+
+    private String name;
+
+    private int hash;
+
+    private Map/* <String, String> */attributes = new HashMap();
+
+    /**
+     * Constructor.
+     * 
+     * @param organisation
+     *            The organisation which creates the module.
+     * @param name
+     *            The name of the module.
+     */
+    public ModuleId(String organisation, String name) {
+        if (name == null) {
+            throw new IllegalArgumentException("null name not allowed");
+        }
+        this.organisation = organisation;
+        this.name = name;
+        attributes.put(IvyPatternHelper.ORGANISATION_KEY, organisation);
+        attributes.put(IvyPatternHelper.MODULE_KEY, name);
+    }
+
+    /**
+     * Returns the name of the module.
+     * 
+     * @return The name of the module.
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Returns the name of the organisation.
+     * 
+     * @return The name of the organisation.
+     */
+    public String getOrganisation() {
+        return organisation;
+    }
+
+    /** {@inheritDoc} */
+    public boolean equals(Object obj) {
+        if (!(obj instanceof ModuleId)) {
+            return false;
+        }
+        ModuleId other = (ModuleId) obj;
+        if (other.organisation == null) {
+            return organisation == null && other.name.equals(name);
+        } else {
+            return other.organisation.equals(organisation) && other.name.equals(name);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public int hashCode() {
+        if (hash == 0) {
+            // CheckStyle:MagicNumber| OFF
+            hash = 31;
+            hash = hash * 13 + (organisation == null ? 0 : organisation.hashCode());
+            hash = hash * 13 + name.hashCode();
+            // CheckStyle:MagicNumber| ON
+        }
+        return hash;
+    }
+
+    /** {@inheritDoc} */
+    public String toString() {
+        return organisation + "#" + name;
+    }
+
+    /** {@inheritDoc} */
+    public int compareTo(Object obj) {
+        ModuleId that = (ModuleId) obj;
+        int result = organisation.compareTo(that.organisation);
+        if (result == 0) {
+            result = name.compareTo(that.name);
+        }
+        return result;
+    }
+
+    /**
+     * Returns the encoded String representing this ModuleId.
+     * 
+     * @return The ModuleId encoded as String.
+     */
+    public String encodeToString() {
+        return getOrganisation() + ENCODE_SEPARATOR + getName();
+    }
+
+    /**
+     * Returns a Map of all attributes of this module id. The Map keys are attribute names as
+     * Strings, and values are corresponding attribute values (as String too).
+     * 
+     * @return A Map instance containing all the attributes and their values.
+     */
+    public Map getAttributes() {
+        return attributes;
+    }
+
+    /**
+     * Returns a ModuleId
+     * 
+     * @param encoded
+     * @return The new ModuleId.
+     * @throws IllegalArgumentException
+     *             If the given String could not be decoded.
+     */
+    public static ModuleId decode(String encoded) {
+        String[] parts = encoded.split(ENCODE_SEPARATOR);
+        if (parts.length != 2) {
+            throw new IllegalArgumentException("badly encoded module id: '" + encoded + "'");
+        }
+        return new ModuleId(parts[0], parts[1]);
+    }
+
+    /**
+     * Pattern to use to matched mid text representation.
+     * 
+     * @see #parse(String)
+     */
+    public static final Pattern MID_PATTERN = Pattern.compile("("
+            + ModuleRevisionId.STRICT_CHARS_PATTERN + "*)" + "#("
+            + ModuleRevisionId.STRICT_CHARS_PATTERN + "+)");
+
+    /**
+     * Parses the module id text representation and returns it as a {@link ModuleId} instance.
+     * 
+     * @param mid
+     *            the module id text representation to parse
+     * @return the ModuleId instance corresponding to the representation
+     * @throws IllegalArgumentException
+     *             if the given text representation cannot be parsed
+     */
+    public static ModuleId parse(String mid) {
+        Matcher m = MID_PATTERN.matcher(mid);
+        if (!m.matches()) {
+            throw new IllegalArgumentException(
+                    "module text representation do not match expected pattern." + " given mid='"
+                            + mid + "' expected form=" + MID_PATTERN.pattern());
+        }
+
+        // CheckStyle:MagicNumber| OFF
+        return newInstance(m.group(1), m.group(2));
+        // CheckStyle:MagicNumber| ON
+    }
+}
diff --git a/src/java/org/apache/ivy/core/module/id/ModuleRevisionId.java b/src/java/org/apache/ivy/core/module/id/ModuleRevisionId.java
new file mode 100644
index 0000000..6c36f5c
--- /dev/null
+++ b/src/java/org/apache/ivy/core/module/id/ModuleRevisionId.java
@@ -0,0 +1,343 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.module.id;
+
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.WeakHashMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.ivy.Ivy;
+import org.apache.ivy.core.IvyContext;
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.util.extendable.UnmodifiableExtendableItem;
+
+/**
+ * Identifies a module in a particular version
+ * 
+ * @see <a href="package-summary.html">org.apache.ivy.core.module.id</a>
+ */
+public class ModuleRevisionId extends UnmodifiableExtendableItem {
+    private static final String ENCODE_SEPARATOR = ModuleId.ENCODE_SEPARATOR;
+
+    private static final String ENCODE_PREFIX = "+";
+
+    private static final String NULL_ENCODE = "@#:NULL:#@";
+
+    static final String STRICT_CHARS_PATTERN = "[a-zA-Z0-9\\-/\\._+=]";
+
+    private static final String REV_STRICT_CHARS_PATTERN = "[a-zA-Z0-9\\-/\\._+=,\\[\\]\\{\\}\\(\\):@]";
+
+    private static final Map/* <ModuleRevisionId, ModuleRevisionId> */CACHE = new WeakHashMap();
+
+    /**
+     * Pattern to use to matched mrid text representation.
+     * 
+     * @see #parse(String)
+     */
+    public static final Pattern MRID_PATTERN = Pattern.compile("(" + STRICT_CHARS_PATTERN + "*)"
+            + "#(" + STRICT_CHARS_PATTERN + "+)" + "(?:#(" + STRICT_CHARS_PATTERN + "+))?" + ";("
+            + REV_STRICT_CHARS_PATTERN + "+)");
+
+    /**
+     * Same as MRID_PATTERN but using non capturing groups, useful to build larger regexp
+     */
+    public static final Pattern NON_CAPTURING_PATTERN = Pattern.compile("(?:"
+            + STRICT_CHARS_PATTERN + "*)" + "#(?:" + STRICT_CHARS_PATTERN + "+)" + "(?:#(?:"
+            + STRICT_CHARS_PATTERN + "+))?" + ";(?:" + REV_STRICT_CHARS_PATTERN + "+)");
+
+    /**
+     * Parses a module revision id text representation and returns a new {@link ModuleRevisionId}
+     * instance corresponding to the parsed String.
+     * <p>
+     * The result is unspecified if the module doesn't respect strict name conventions.
+     * </p>
+     * 
+     * @param mrid
+     *            the text representation of the module (as returned by {@link #toString()}). Must
+     *            not be <code>null</code>.
+     * @return a {@link ModuleRevisionId} corresponding to the given text representation
+     * @throws IllegalArgumentException
+     *             if the given text representation does not match the {@link ModuleRevisionId} text
+     *             representation rules.
+     */
+    public static ModuleRevisionId parse(String mrid) {
+        Matcher m = MRID_PATTERN.matcher(mrid.trim());
+        if (!m.matches()) {
+            throw new IllegalArgumentException(
+                    "module revision text representation do not match expected pattern."
+                            + " given mrid='" + mrid + "' expected form=" + MRID_PATTERN.pattern());
+        }
+
+        // CheckStyle:MagicNumber| OFF
+        return newInstance(m.group(1), m.group(2), m.group(3), m.group(4));
+        // CheckStyle:MagicNumber| ON
+    }
+
+    public static ModuleRevisionId newInstance(String organisation, String name, String revision) {
+        return intern(new ModuleRevisionId(ModuleId.newInstance(organisation, name), revision));
+    }
+
+    public static ModuleRevisionId newInstance(String organisation, String name, String revision,
+            Map extraAttributes) {
+        return intern(new ModuleRevisionId(ModuleId.newInstance(organisation, name), revision,
+                extraAttributes));
+    }
+
+    public static ModuleRevisionId newInstance(String organisation, String name, String branch,
+            String revision) {
+        return intern(new ModuleRevisionId(ModuleId.newInstance(organisation, name), branch,
+                revision));
+    }
+
+    public static ModuleRevisionId newInstance(String organisation, String name, String branch,
+            String revision, Map extraAttributes) {
+        return intern(new ModuleRevisionId(ModuleId.newInstance(organisation, name), branch,
+                revision, extraAttributes));
+    }
+
+    public static ModuleRevisionId newInstance(String organisation, String name, String branch,
+            String revision, Map extraAttributes, boolean replaceNullBranchWithDefault) {
+        return intern(new ModuleRevisionId(ModuleId.newInstance(organisation, name), branch,
+                revision, extraAttributes, replaceNullBranchWithDefault));
+    }
+
+    public static ModuleRevisionId newInstance(ModuleRevisionId mrid, String rev) {
+        return intern(new ModuleRevisionId(mrid.getModuleId(), mrid.getBranch(), rev,
+                mrid.getQualifiedExtraAttributes()));
+    }
+
+    public static ModuleRevisionId newInstance(ModuleRevisionId mrid, String branch, String rev) {
+        return intern(new ModuleRevisionId(mrid.getModuleId(), branch, rev,
+                mrid.getQualifiedExtraAttributes()));
+    }
+
+    /**
+     * Returns an intern instance of the given ModuleRevisionId if any, or put the given
+     * ModuleRevisionId in a cache of intern instances and returns it.
+     * <p>
+     * This method should be called on ModuleRevisionId created with one of the constructor to
+     * decrease memory footprint.
+     * </p>
+     * <p>
+     * When using static newInstances methods, this method is already called.
+     * </p>
+     * 
+     * @param moduleRevisionId
+     *            the module revision id to intern
+     * @return an interned ModuleRevisionId
+     */
+    public static ModuleRevisionId intern(ModuleRevisionId moduleRevisionId) {
+        ModuleRevisionId r = null;
+
+        synchronized (CACHE) {
+            WeakReference ref = (WeakReference) CACHE.get(moduleRevisionId);
+            if (ref != null) {
+                r = (ModuleRevisionId) ref.get();
+            }
+            if (r == null) {
+                r = moduleRevisionId;
+                CACHE.put(r, new WeakReference(r));
+            }
+        }
+
+        return r;
+    }
+
+    private final ModuleId moduleId;
+
+    private final String branch;
+
+    private final String revision;
+
+    private int hash;
+
+    // TODO: make these constructors private and use only static factory methods
+
+    public ModuleRevisionId(ModuleId moduleId, String revision) {
+        this(moduleId, null, revision, null);
+    }
+
+    public ModuleRevisionId(ModuleId moduleId, String branch, String revision) {
+        this(moduleId, branch, revision, null);
+    }
+
+    private ModuleRevisionId(ModuleId moduleId, String revision, Map extraAttributes) {
+        this(moduleId, null, revision, extraAttributes);
+    }
+
+    private ModuleRevisionId(ModuleId moduleId, String branch, String revision, Map extraAttributes) {
+        this(moduleId, branch, revision, extraAttributes, true);
+    }
+
+    private ModuleRevisionId(ModuleId moduleId, String branch, String revision,
+            Map extraAttributes, boolean replaceNullBranchWithDefault) {
+        super(null, extraAttributes);
+        this.moduleId = moduleId;
+        IvyContext context = IvyContext.getContext();
+        this.branch = (replaceNullBranchWithDefault && branch == null)
+        // we test if there's already an Ivy instance loaded, to avoid loading a default one
+        // just to get the default branch
+        ? (context.peekIvy() == null ? null : context.getSettings().getDefaultBranch(moduleId))
+                : branch;
+        this.revision = revision == null ? Ivy.getWorkingRevision() : normalizeRevision(revision);
+        setStandardAttribute(IvyPatternHelper.ORGANISATION_KEY, this.moduleId.getOrganisation());
+        setStandardAttribute(IvyPatternHelper.MODULE_KEY, this.moduleId.getName());
+        setStandardAttribute(IvyPatternHelper.BRANCH_KEY, this.branch);
+        setStandardAttribute(IvyPatternHelper.REVISION_KEY, this.revision);
+    }
+
+    public ModuleId getModuleId() {
+        return moduleId;
+    }
+
+    public String getName() {
+        return getModuleId().getName();
+    }
+
+    public String getOrganisation() {
+        return getModuleId().getOrganisation();
+    }
+
+    public String getRevision() {
+        return revision;
+    }
+
+    public boolean equals(Object obj) {
+        if (!(obj instanceof ModuleRevisionId)) {
+            return false;
+        }
+        ModuleRevisionId other = (ModuleRevisionId) obj;
+
+        if (!other.getRevision().equals(getRevision())) {
+            return false;
+        } else if (other.getBranch() == null && getBranch() != null) {
+            return false;
+        } else if (other.getBranch() != null && !other.getBranch().equals(getBranch())) {
+            return false;
+        } else if (!other.getModuleId().equals(getModuleId())) {
+            return false;
+        } else {
+            return other.getQualifiedExtraAttributes().equals(getQualifiedExtraAttributes());
+        }
+    }
+
+    public int hashCode() {
+        if (hash == 0) {
+            // CheckStyle:MagicNumber| OFF
+            hash = 31;
+            hash = hash * 13 + (getBranch() == null ? 0 : getBranch().hashCode());
+            hash = hash * 13 + getRevision().hashCode();
+            hash = hash * 13 + getModuleId().hashCode();
+            hash = hash * 13 + getQualifiedExtraAttributes().hashCode();
+            // CheckStyle:MagicNumber| ON
+        }
+        return hash;
+    }
+
+    public String toString() {
+        return moduleId + (branch == null || branch.length() == 0 ? "" : "#" + branch) + ";"
+                + (revision == null ? "NONE" : revision);
+    }
+
+    public String encodeToString() {
+        StringBuffer buf = new StringBuffer();
+        Map attributes = new HashMap(getAttributes());
+        attributes.keySet().removeAll(getExtraAttributes().keySet());
+        attributes.putAll(getQualifiedExtraAttributes());
+
+        for (Iterator iter = attributes.keySet().iterator(); iter.hasNext();) {
+            String attName = (String) iter.next();
+            String value = (String) attributes.get(attName);
+            value = value == null ? NULL_ENCODE : value;
+            buf.append(ENCODE_PREFIX).append(attName).append(ENCODE_SEPARATOR)
+                    .append(ENCODE_PREFIX).append(value).append(ENCODE_SEPARATOR);
+        }
+        return buf.toString();
+    }
+
+    public static ModuleRevisionId decode(String encoded) {
+        String[] parts = encoded.split(ENCODE_SEPARATOR);
+        if (parts.length % 2 != 0) {
+            throw new IllegalArgumentException("badly encoded module revision id: '" + encoded
+                    + "'");
+        }
+        Map attributes = new HashMap();
+        for (int i = 0; i < parts.length; i += 2) {
+            String attName = parts[i];
+            if (!attName.startsWith(ENCODE_PREFIX)) {
+                throw new IllegalArgumentException("badly encoded module revision id: '" + encoded
+                        + "': " + attName + " doesn't start with " + ENCODE_PREFIX);
+            } else {
+                attName = attName.substring(1);
+            }
+            String attValue = parts[i + 1];
+            if (!attValue.startsWith(ENCODE_PREFIX)) {
+                throw new IllegalArgumentException("badly encoded module revision id: '" + encoded
+                        + "': " + attValue + " doesn't start with " + ENCODE_PREFIX);
+            } else {
+                attValue = attValue.substring(1);
+            }
+            if (NULL_ENCODE.equals(attValue)) {
+                attValue = null;
+            }
+            attributes.put(attName, attValue);
+        }
+        String org = (String) attributes.remove(IvyPatternHelper.ORGANISATION_KEY);
+        String mod = (String) attributes.remove(IvyPatternHelper.MODULE_KEY);
+        String rev = (String) attributes.remove(IvyPatternHelper.REVISION_KEY);
+        String branch = (String) attributes.remove(IvyPatternHelper.BRANCH_KEY);
+        if (org == null) {
+            throw new IllegalArgumentException("badly encoded module revision id: '" + encoded
+                    + "': no organisation");
+        }
+        if (mod == null) {
+            throw new IllegalArgumentException("badly encoded module revision id: '" + encoded
+                    + "': no module name");
+        }
+        if (rev == null) {
+            throw new IllegalArgumentException("badly encoded module revision id: '" + encoded
+                    + "': no revision");
+        }
+        return newInstance(org, mod, branch, rev, attributes);
+    }
+
+    public String getBranch() {
+        return branch;
+    }
+
+    /**
+     * [revision] is a valid revision in maven. This method strips the '[' and ']' characters. Cfr.
+     * http://docs.codehaus.org/x/IGU
+     */
+    private static String normalizeRevision(String revision) {
+        if (revision.startsWith("[") && revision.endsWith("]") && revision.indexOf(',') == -1) {
+            if (IvyPatternHelper.getTokenString(IvyPatternHelper.REVISION_KEY).equals(revision)) {
+                // this is the case when listing dynamic revions
+                return revision;
+            }
+
+            return revision.substring(1, revision.length() - 1);
+        } else {
+            return revision;
+        }
+    }
+}
diff --git a/src/java/org/apache/ivy/core/module/id/ModuleRules.java b/src/java/org/apache/ivy/core/module/id/ModuleRules.java
new file mode 100644
index 0000000..f16981d
--- /dev/null
+++ b/src/java/org/apache/ivy/core/module/id/ModuleRules.java
@@ -0,0 +1,250 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.module.id;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.ivy.plugins.matcher.MapMatcher;
+import org.apache.ivy.util.Checks;
+import org.apache.ivy.util.Message;
+import org.apache.ivy.util.filter.Filter;
+import org.apache.ivy.util.filter.NoFilter;
+
+/**
+ * A list of module specific rules.
+ * <p>
+ * This class defines a list of module specific rules. For each module only one rule apply,
+ * sometimes none.
+ * </p>
+ * <p>
+ * To know which rule to apply, they are configured using matchers. So you can define a rule
+ * applying to all module from one particular organization, or to all modules with a revisions
+ * matching a pattern, and so on.
+ * </p>
+ * <p>
+ * Rules condition are evaluated in order, so the first matching rule is returned.
+ * </p>
+ * <p>
+ * Rules themselves can be represented by any object, depending on the purpose of the rule (define
+ * which resolver to use, which TTL in cache, ...)
+ * </p>
+ */
+public class ModuleRules {
+    private Map/* <MapMatcher,Object> */rules = new LinkedHashMap();
+
+    private MatcherLookup matcher_lookup = new MatcherLookup();
+
+    /**
+     * Constructs an empty ModuleRules.
+     */
+    public ModuleRules() {
+    }
+
+    private ModuleRules(Map/* <MapMatcher,Object> */rules) {
+        this.rules = new LinkedHashMap(rules);
+        for (Iterator iter = rules.keySet().iterator(); iter.hasNext();) {
+            matcher_lookup.add((MapMatcher) iter.next());
+        }
+    }
+
+    /**
+     * Defines a new rule for the given condition.
+     * 
+     * @param condition
+     *            the condition for which the rule should be applied. Must not be <code>null</code>.
+     * @param rule
+     *            the rule to apply. Must not be <code>null</code>.
+     */
+    public void defineRule(MapMatcher condition, Object rule) {
+        Checks.checkNotNull(condition, "condition");
+        Checks.checkNotNull(rule, "rule");
+
+        rules.put(condition, rule);
+        matcher_lookup.add(condition);
+    }
+
+    /**
+     * Returns the rule object matching the given {@link ModuleId}, or <code>null</code> if no rule
+     * applies.
+     * 
+     * @param mid
+     *            the {@link ModuleId} to search the rule for. Must not be <code>null</code>.
+     * @return the rule object matching the given {@link ModuleId}, or <code>null</code> if no rule
+     *         applies.
+     * @see #getRule(ModuleId, Filter)
+     */
+    public Object getRule(ModuleId mid) {
+        return getRule(mid, NoFilter.INSTANCE);
+    }
+
+    /**
+     * Returns the rules objects matching the given {@link ModuleId}, or an empty array if no rule
+     * applies.
+     * 
+     * @param mid
+     *            the {@link ModuleId} to search the rule for. Must not be <code>null</code>.
+     * @return an array of rule objects matching the given {@link ModuleId}.
+     */
+    public Object[] getRules(ModuleId mid) {
+        return getRules(mid.getAttributes(), NoFilter.INSTANCE);
+    }
+
+    /**
+     * Returns the rule object matching the given {@link ModuleRevisionId}, or <code>null</code> if
+     * no rule applies.
+     * 
+     * @param mrid
+     *            the {@link ModuleRevisionId} to search the rule for. Must not be <code>null</code>
+     *            .
+     * @return the rule object matching the given {@link ModuleRevisionId}, or <code>null</code> if
+     *         no rule applies.
+     * @see #getRule(ModuleRevisionId, Filter)
+     */
+    public Object getRule(ModuleRevisionId mrid) {
+        return getRule(mrid, NoFilter.INSTANCE);
+    }
+
+    /**
+     * Returns the rule object matching the given {@link ModuleId} and accepted by the given
+     * {@link Filter}, or <code>null</code> if no rule applies.
+     * 
+     * @param mid
+     *            the {@link ModuleRevisionId} to search the rule for. Must not be <code>null</code>
+     *            .
+     * @param filter
+     *            the filter to use to filter the rule to return. The {@link Filter#accept(Object)}
+     *            method will be called only with rule objects matching the given {@link ModuleId},
+     *            and the first rule object accepted by the filter will be returned. Must not be
+     *            <code>null</code>.
+     * @return the rule object matching the given {@link ModuleId}, or <code>null</code> if no rule
+     *         applies.
+     * @see #getRule(ModuleRevisionId, Filter)
+     */
+    public Object getRule(ModuleId mid, Filter filter) {
+        Checks.checkNotNull(mid, "mid");
+        return getRule(mid.getAttributes(), filter);
+    }
+
+    /**
+     * Returns the rule object matching the given {@link ModuleRevisionId} and accepted by the given
+     * {@link Filter}, or <code>null</code> if no rule applies.
+     * 
+     * @param mrid
+     *            the {@link ModuleRevisionId} to search the rule for. Must not be <code>null</code>
+     *            .
+     * @param filter
+     *            the filter to use to filter the rule to return. The {@link Filter#accept(Object)}
+     *            method will be called only with rule objects matching the given
+     *            {@link ModuleRevisionId}, and the first rule object accepted by the filter will be
+     *            returned. Must not be <code>null</code>.
+     * @return the rule object matching the given {@link ModuleRevisionId}, or <code>null</code> if
+     *         no rule applies.
+     * @see #getRule(ModuleRevisionId)
+     */
+    public Object getRule(ModuleRevisionId mrid, Filter filter) {
+        Checks.checkNotNull(mrid, "mrid");
+        Checks.checkNotNull(filter, "filter");
+        Map moduleAttributes = mrid.getAttributes();
+        return getRule(moduleAttributes, filter);
+    }
+
+    private Object getRule(Map moduleAttributes, Filter filter) {
+        List matchers = matcher_lookup.get(moduleAttributes);
+        for (Iterator iter = matchers.iterator(); iter.hasNext();) {
+            MapMatcher midm = (MapMatcher) iter.next();
+            Object rule = rules.get(midm);
+            if (filter.accept(rule)) {
+                return rule;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns the rules object matching the given {@link ModuleRevisionId} and accepted by the
+     * given {@link Filter}, or an empty array if no rule applies.
+     * 
+     * @param mrid
+     *            the {@link ModuleRevisionId} to search the rule for. Must not be <code>null</code>
+     *            .
+     * @param filter
+     *            the filter to use to filter the rule to return. The {@link Filter#accept(Object)}
+     *            method will be called only with rule objects matching the given
+     *            {@link ModuleRevisionId}. Must not be <code>null</code>.
+     * @return an array of rule objects matching the given {@link ModuleRevisionId}.
+     */
+    public Object[] getRules(ModuleRevisionId mrid, Filter filter) {
+        Checks.checkNotNull(mrid, "mrid");
+        Checks.checkNotNull(filter, "filter");
+        Map moduleAttributes = mrid.getAttributes();
+        return getRules(moduleAttributes, filter);
+    }
+
+    private Object[] getRules(Map moduleAttributes, Filter filter) {
+        List matchers = matcher_lookup.get(moduleAttributes);
+        List matchingRules = new ArrayList();
+        for (Iterator iter = matchers.iterator(); iter.hasNext();) {
+            MapMatcher midm = (MapMatcher) iter.next();
+            Object rule = rules.get(midm);
+            if (filter.accept(rule)) {
+                matchingRules.add(rule);
+            }
+        }
+        return matchingRules.toArray();
+    }
+
+    /**
+     * Dump the list of rules to {@link Message#debug(String)}
+     * 
+     * @param prefix
+     *            the prefix to use for each line dumped
+     */
+    public void dump(String prefix) {
+        if (rules.isEmpty()) {
+            Message.debug(prefix + "NONE");
+        } else {
+            for (Iterator iter = rules.keySet().iterator(); iter.hasNext();) {
+                MapMatcher midm = (MapMatcher) iter.next();
+                Object rule = rules.get(midm);
+                Message.debug(prefix + midm + " -> " + rule);
+            }
+        }
+    }
+
+    /**
+     * Returns an unmodifiable view of all the rules defined on this ModuleRules.
+     * <p>
+     * The rules are returned in a Map where they keys are the MapMatchers matching the rules
+     * object, and the values are the rules object themselves.
+     * </p>
+     * 
+     * @return an unmodifiable view of all the rules defined on this ModuleRules.
+     */
+    public Map/* <MapMatcher,Object> */getAllRules() {
+        return Collections.unmodifiableMap(rules);
+    }
+
+    public Object clone() {
+        return new ModuleRules(rules);
+    }
+}
diff --git a/src/java/org/apache/ivy/core/module/id/package.html b/src/java/org/apache/ivy/core/module/id/package.html
new file mode 100644
index 0000000..9e57242
--- /dev/null
+++ b/src/java/org/apache/ivy/core/module/id/package.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT 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>
+<head>
+</head>
+<body>
+<p>
+This packages gathers classes used to represent identifiers of objects involved in dependency management.
+</p>
+<p>
+As identifiers, they do not store any information on the module content and detailed metadata (like dependencies).
+</p>
+<h2>Classes</h2>
+<p>
+<ul>
+<li>ModuleId</li> is used as the identifier of module, without considering its version. It's basically an organization and module name couple.
+<li>ModuleRevisionId</li> is used as the identifier of a module in a particular version or version constraint. It is composed of a ModuleId, a revision, and optionally a branch and extra attributes. 
+<li>ArtifactId</li> identifies an artifact without considering its version. It's basically a ModuleId and an artifact name, type and extension, plus optional extra attributes.
+<li>ArtifactRevisionId</li> identifies an artifact of a module in a particular version. It is composed of a ModuleRevisionId along with artifact identifier like the name type and extension. 
+</ul>
+</p>
+<h2>Text Representation</h2>
+<p>
+These classes share an homogeneous text representation, which can be easily obtained through the toString() method.
+</p>
+<p>
+The general pattern is: <code>[organisation]#[module]#[branch];[revision]![artifact].[ext]([type])</code>
+The # before the branch is present only if the branch is not empty.<br/>
+The type and surrounding parenthesis are present only if different from the extension.
+</p>
+<p>
+A textual representation can be parsed into an object (supported for ModuleRevisionId only yet), as long as a strict set of characters is used for each field (which is recommended).<br/>
+Allowed characters are:
+<ul>
+<li>organisation</li> a-z A-Z 0-9 - / . _ + =
+<li>module</li> a-z A-Z 0-9 - / . _ + =
+<li>branch</li> a-z A-Z 0-9 - / . _ + =
+<li>revision</li> a-z A-Z 0-9 - / . _ + = , [ ] { } ( ) : @
+<li>artifact</li> a-z A-Z 0-9 - / . _ + =
+<li>extension</li> a-z A-Z 0-9 - / . _ + =
+<li>type</li> a-z A-Z 0-9 - / . _ + =
+</ul>
+
+</body>
\ No newline at end of file
diff --git a/src/java/org/apache/ivy/core/module/status/Status.java b/src/java/org/apache/ivy/core/module/status/Status.java
new file mode 100644
index 0000000..d94d94a
--- /dev/null
+++ b/src/java/org/apache/ivy/core/module/status/Status.java
@@ -0,0 +1,48 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.module.status;
+
+public class Status {
+    private String name;
+
+    private boolean integration;
+
+    public Status() {
+    }
+
+    public Status(String name, boolean integration) {
+        this.name = name;
+        this.integration = integration;
+    }
+
+    public boolean isIntegration() {
+        return integration;
+    }
+
+    public void setIntegration(boolean integration) {
+        this.integration = integration;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+}
diff --git a/src/java/org/apache/ivy/core/module/status/StatusManager.java b/src/java/org/apache/ivy/core/module/status/StatusManager.java
new file mode 100644
index 0000000..cb45b7f
--- /dev/null
+++ b/src/java/org/apache/ivy/core/module/status/StatusManager.java
@@ -0,0 +1,151 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.module.status;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+
+import org.apache.ivy.core.IvyContext;
+import org.apache.ivy.util.Message;
+
+/**
+ * Note: update methods (such as addStatus) should only be called BEFORE any call to accessor
+ * methods
+ */
+public class StatusManager {
+    public static StatusManager newDefaultInstance() {
+        return new StatusManager(new Status[] {new Status("release", false),
+                new Status("milestone", false), new Status("integration", true)}, "integration");
+    }
+
+    public static StatusManager getCurrent() {
+        return IvyContext.getContext().getSettings().getStatusManager();
+    }
+
+    private List status = new ArrayList();
+
+    private String defaultStatus;
+
+    // for easier querying only
+    private Map statusPriorityMap;
+
+    private Map statusIntegrationMap;
+
+    private String deliveryStatusListString;
+
+    public StatusManager(Status[] status, String defaultStatus) {
+        this.status.addAll(Arrays.asList(status));
+        this.defaultStatus = defaultStatus;
+
+        computeMaps();
+    }
+
+    public StatusManager() {
+    }
+
+    public void addStatus(Status status) {
+        this.status.add(status);
+    }
+
+    public void setDefaultStatus(String defaultStatus) {
+        this.defaultStatus = defaultStatus;
+    }
+
+    public List getStatuses() {
+        return status;
+    }
+
+    private void computeMaps() {
+        if (status.isEmpty()) {
+            throw new IllegalStateException("badly configured statuses: no status found");
+        }
+        statusPriorityMap = new HashMap();
+        for (ListIterator iter = status.listIterator(); iter.hasNext();) {
+            Status status = (Status) iter.next();
+            statusPriorityMap.put(status.getName(), new Integer(iter.previousIndex()));
+        }
+        statusIntegrationMap = new HashMap();
+        for (Iterator iter = status.iterator(); iter.hasNext();) {
+            Status status = (Status) iter.next();
+            statusIntegrationMap.put(status.getName(), Boolean.valueOf(status.isIntegration()));
+        }
+    }
+
+    public boolean isStatus(String status) {
+        if (statusPriorityMap == null) {
+            computeMaps();
+        }
+        return statusPriorityMap.containsKey(status);
+    }
+
+    public int getPriority(String status) {
+        if (statusPriorityMap == null) {
+            computeMaps();
+        }
+        Integer priority = (Integer) statusPriorityMap.get(status);
+        if (priority == null) {
+            Message.debug("unknown status " + status + ": assuming lowest priority");
+            return Integer.MAX_VALUE;
+        }
+        return priority.intValue();
+    }
+
+    public boolean isIntegration(String status) {
+        if (statusIntegrationMap == null) {
+            computeMaps();
+        }
+        Boolean isIntegration = (Boolean) statusIntegrationMap.get(status);
+        if (isIntegration == null) {
+            Message.debug("unknown status " + status + ": assuming integration");
+            return true;
+        }
+        return isIntegration.booleanValue();
+    }
+
+    public String getDeliveryStatusListString() {
+        if (deliveryStatusListString == null) {
+            StringBuffer ret = new StringBuffer();
+            for (Iterator iter = status.iterator(); iter.hasNext();) {
+                Status status = (Status) iter.next();
+                if (!status.isIntegration()) {
+                    ret.append(status.getName()).append(",");
+                }
+            }
+            if (ret.length() > 0) {
+                ret.deleteCharAt(ret.length() - 1);
+            }
+            deliveryStatusListString = ret.toString();
+        }
+        return deliveryStatusListString;
+    }
+
+    public String getDefaultStatus() {
+        if (defaultStatus == null) {
+            if (status.isEmpty()) {
+                throw new IllegalStateException("badly configured statuses: no status found");
+            }
+            defaultStatus = ((Status) status.get(status.size() - 1)).getName();
+        }
+        return defaultStatus;
+    }
+}
diff --git a/src/java/org/apache/ivy/core/pack/ArchivePacking.java b/src/java/org/apache/ivy/core/pack/ArchivePacking.java
new file mode 100644
index 0000000..2802ca5
--- /dev/null
+++ b/src/java/org/apache/ivy/core/pack/ArchivePacking.java
@@ -0,0 +1,32 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.pack;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+public abstract class ArchivePacking {
+
+    public abstract String[] getNames();
+
+    public abstract void unpack(InputStream packed, File dest) throws IOException;
+
+    public abstract String getUnpackedExtension(String ext);
+
+}
diff --git a/src/java/org/apache/ivy/core/pack/OsgiBundlePacking.java b/src/java/org/apache/ivy/core/pack/OsgiBundlePacking.java
new file mode 100644
index 0000000..a9f9344
--- /dev/null
+++ b/src/java/org/apache/ivy/core/pack/OsgiBundlePacking.java
@@ -0,0 +1,48 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.pack;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.ivy.util.FileUtil;
+
+/**
+ * Packaging which handle OSGi bundles with inner packed jar
+ */
+public class OsgiBundlePacking extends ZipPacking {
+
+    private static final String[] NAMES = {"bundle"};
+
+    @Override
+    public String[] getNames() {
+        return NAMES;
+    }
+
+    @Override
+    protected void writeFile(InputStream zip, File f) throws FileNotFoundException, IOException {
+        // XXX maybe we should only unpack file listed by the 'Bundle-ClassPath' MANIFEST header ?
+        if (f.getName().endsWith(".jar.pack.gz")) {
+            zip = FileUtil.unwrapPack200(zip);
+            f = new File(f.getParentFile(), f.getName().substring(0, f.getName().length() - 8));
+        }
+        super.writeFile(zip, f);
+    }
+}
diff --git a/src/java/org/apache/ivy/core/pack/Pack200Packing.java b/src/java/org/apache/ivy/core/pack/Pack200Packing.java
new file mode 100644
index 0000000..f2039d1
--- /dev/null
+++ b/src/java/org/apache/ivy/core/pack/Pack200Packing.java
@@ -0,0 +1,55 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.pack;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.ivy.util.FileUtil;
+
+public class Pack200Packing extends StreamPacking {
+
+    private static final String[] NAMES = {"pack200"};
+
+    @Override
+    public String[] getNames() {
+        return NAMES;
+    }
+
+    @Override
+    public String getUnpackedExtension(String ext) {
+        if (ext.endsWith("pack.gz")) {
+            ext = ext.substring(0, ext.length() - 7);
+            if (ext.endsWith(".")) {
+                ext = ext.substring(0, ext.length() - 1);
+            }
+        } else if (ext.endsWith("pack")) {
+            ext = ext.substring(0, ext.length() - 4);
+            if (ext.endsWith(".")) {
+                ext = ext.substring(0, ext.length() - 1);
+            }
+        }
+        return ext;
+    }
+
+    @Override
+    public InputStream unpack(InputStream packed) throws IOException {
+        return FileUtil.unwrapPack200(packed);
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/pack/PackagingManager.java b/src/java/org/apache/ivy/core/pack/PackagingManager.java
new file mode 100644
index 0000000..6b3caf0
--- /dev/null
+++ b/src/java/org/apache/ivy/core/pack/PackagingManager.java
@@ -0,0 +1,115 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.pack;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DefaultArtifact;
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.ivy.plugins.IvySettingsAware;
+
+public class PackagingManager implements IvySettingsAware {
+
+    private IvySettings settings;
+
+    public void setSettings(IvySettings settings) {
+        this.settings = settings;
+    }
+
+    public Artifact getUnpackedArtifact(Artifact artifact) {
+        String packaging = artifact.getExtraAttribute("packaging");
+        if (packaging == null) {
+            // not declared as packed, nothing to do
+            return null;
+        }
+
+        String ext = artifact.getExt();
+
+        String[] packings = packaging.split(",");
+        for (int i = packings.length - 1; i >= 1; i--) {
+            ArchivePacking packing = settings.getPackingRegistry().get(packings[i]);
+            if (packing == null) {
+                throw new IllegalStateException("Unknown packing type '" + packings[i]
+                        + "' in the packing chain: " + packaging);
+            }
+            if (!(packing instanceof StreamPacking)) {
+                throw new IllegalStateException("Unsupported archive only packing type '"
+                        + packings[i] + "' in the streamed chain: " + packaging);
+            }
+            ext = ((StreamPacking) packing).getUnpackedExtension(ext);
+        }
+        ArchivePacking packing = settings.getPackingRegistry().get(packings[0]);
+        if (packing == null) {
+            throw new IllegalStateException("Unknown packing type '" + packings[0]
+                    + "' in the packing chain: " + packaging);
+        }
+        ext = packing.getUnpackedExtension(ext);
+
+        DefaultArtifact unpacked = new DefaultArtifact(artifact.getModuleRevisionId(),
+                artifact.getPublicationDate(), artifact.getName(),
+                artifact.getType() + "_unpacked", ext);
+
+        return unpacked;
+    }
+
+    public void unpackArtifact(Artifact artifact, File localFile, File archiveFile)
+            throws IOException {
+        String packaging = artifact.getExtraAttribute("packaging");
+        if (packaging == null) {
+            // not declared as packed, nothing to do
+            return;
+        }
+
+        String[] packings = packaging.split(",");
+        InputStream in = null;
+        try {
+            in = new FileInputStream(localFile);
+            for (int i = packings.length - 1; i >= 1; i--) {
+                ArchivePacking packing = settings.getPackingRegistry().get(packings[i]);
+                if (packing == null) {
+                    throw new IllegalStateException("Unknown packing type '" + packings[i]
+                            + "' in the packing chain: " + packaging);
+                }
+                if (!(packing instanceof StreamPacking)) {
+                    throw new IllegalStateException("Unsupported archive only packing type '"
+                            + packings[i] + "' in the streamed chain: " + packaging);
+                }
+                in = ((StreamPacking) packing).unpack(in);
+            }
+            ArchivePacking packing = settings.getPackingRegistry().get(packings[0]);
+            if (packing == null) {
+                throw new IllegalStateException("Unknown packing type '" + packings[0]
+                        + "' in the packing chain: " + packaging);
+            }
+            packing.unpack(in, archiveFile);
+        } finally {
+            if (in != null) {
+                try {
+                    in.close();
+                } catch (IOException e) {
+                    // ignore
+                }
+            }
+        }
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/pack/PackingRegistry.java b/src/java/org/apache/ivy/core/pack/PackingRegistry.java
new file mode 100644
index 0000000..6688677
--- /dev/null
+++ b/src/java/org/apache/ivy/core/pack/PackingRegistry.java
@@ -0,0 +1,43 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.pack;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class PackingRegistry {
+
+    private Map<String, ArchivePacking> packings = new HashMap<String, ArchivePacking>();
+
+    public PackingRegistry() {
+        // register defaults
+        register(new ZipPacking());
+        register(new Pack200Packing());
+        register(new OsgiBundlePacking());
+    }
+
+    public void register(ArchivePacking packing) {
+        for (String name : packing.getNames()) {
+            packings.put(name, packing);
+        }
+    }
+
+    public ArchivePacking get(String type) {
+        return packings.get(type);
+    }
+}
diff --git a/src/java/org/apache/ivy/core/pack/StreamPacking.java b/src/java/org/apache/ivy/core/pack/StreamPacking.java
new file mode 100644
index 0000000..b4b98d0
--- /dev/null
+++ b/src/java/org/apache/ivy/core/pack/StreamPacking.java
@@ -0,0 +1,35 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.pack;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.ivy.util.FileUtil;
+
+public abstract class StreamPacking extends ArchivePacking {
+
+    public abstract InputStream unpack(InputStream packed) throws IOException;
+
+    @Override
+    public void unpack(InputStream packed, File dest) throws IOException {
+        FileUtil.copy(unpack(packed), dest, null);
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/pack/ZipPacking.java b/src/java/org/apache/ivy/core/pack/ZipPacking.java
new file mode 100644
index 0000000..87cfbdf
--- /dev/null
+++ b/src/java/org/apache/ivy/core/pack/ZipPacking.java
@@ -0,0 +1,99 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.pack;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+import org.apache.ivy.util.FileUtil;
+import org.apache.ivy.util.Message;
+
+public class ZipPacking extends ArchivePacking {
+
+    private static final String[] NAMES = {"zip", "jar", "war"};
+
+    @Override
+    public String[] getNames() {
+        return NAMES;
+    }
+
+    @Override
+    public String getUnpackedExtension(String ext) {
+        if (ext.endsWith("zip") || ext.endsWith("jar") || ext.endsWith("war")) {
+            ext = ext.substring(0, ext.length() - 3);
+            if (ext.endsWith(".")) {
+                ext = ext.substring(0, ext.length() - 1);
+            }
+        }
+        return ext;
+    }
+
+    @Override
+    public void unpack(InputStream packed, File dest) throws IOException {
+        ZipInputStream zip = null;
+        try {
+            zip = new ZipInputStream(packed);
+            ZipEntry entry = null;
+            while (((entry = zip.getNextEntry()) != null)) {
+                File f = new File(dest, entry.getName());
+                Message.verbose("\t\texpanding " + entry.getName() + " to " + f);
+
+                // create intermediary directories - sometimes zip don't add them
+                File dirF = f.getParentFile();
+                if (dirF != null) {
+                    dirF.mkdirs();
+                }
+
+                if (entry.isDirectory()) {
+                    f.mkdirs();
+                } else {
+                    writeFile(zip, f);
+                }
+
+                f.setLastModified(entry.getTime());
+            }
+        } finally {
+            if (zip != null) {
+                try {
+                    zip.close();
+                } catch (IOException e) {
+                    // ignore
+                }
+            }
+        }
+    }
+
+    protected void writeFile(InputStream zip, File f) throws FileNotFoundException, IOException {
+        FileOutputStream out = new FileOutputStream(f);
+        try {
+            FileUtil.copy(zip, out, null, false);
+        } finally {
+            try {
+                out.close();
+            } catch (IOException e) {
+                // ignore
+            }
+        }
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/publish/PublishEngine.java b/src/java/org/apache/ivy/core/publish/PublishEngine.java
new file mode 100644
index 0000000..4fa43a2
--- /dev/null
+++ b/src/java/org/apache/ivy/core/publish/PublishEngine.java
@@ -0,0 +1,290 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.publish;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import org.apache.ivy.core.IvyContext;
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.core.cache.ResolutionCacheManager;
+import org.apache.ivy.core.event.EventManager;
+import org.apache.ivy.core.event.publish.EndArtifactPublishEvent;
+import org.apache.ivy.core.event.publish.StartArtifactPublishEvent;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DefaultArtifact;
+import org.apache.ivy.core.module.descriptor.MDArtifact;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.plugins.parser.xml.UpdateOptions;
+import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorParser;
+import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorUpdater;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.apache.ivy.util.ConfigurationUtils;
+import org.apache.ivy.util.Message;
+import org.xml.sax.SAXException;
+
+public class PublishEngine {
+    private PublishEngineSettings settings;
+
+    private EventManager eventManager;
+
+    public PublishEngine(PublishEngineSettings settings, EventManager eventManager) {
+        this.settings = settings;
+        this.eventManager = eventManager;
+    }
+
+    /**
+     * Publishes a module to the repository. The publish can update the ivy file to publish if
+     * update is set to true. In this case it will use the given pubrevision, pubdate and status. If
+     * pubdate is null it will default to the current date. If status is null it will default to the
+     * current ivy file status (which itself defaults to integration if none is found). If update is
+     * false, then if the revision is not the same in the ivy file than the one expected (given as
+     * parameter), this method will fail with an IllegalArgumentException. pubdate and status are
+     * not used if update is false. extra artifacts can be used to publish more artifacts than
+     * actually declared in the ivy file. This can be useful to publish additional metadata or
+     * reports. The extra artifacts array can be null (= no extra artifacts), and if non null only
+     * the name, type, ext url and extra attributes of the artifacts are really used. Other methods
+     * can return null safely.
+     */
+    public Collection publish(ModuleRevisionId mrid, Collection srcArtifactPattern,
+            String resolverName, PublishOptions options) throws IOException {
+        Message.info(":: publishing :: " + mrid.getModuleId());
+        Message.verbose("\tvalidate = " + options.isValidate());
+        long start = System.currentTimeMillis();
+
+        options.setSrcIvyPattern(settings.substitute(options.getSrcIvyPattern()));
+        if (options.getPubBranch() == null) {
+            options.setPubbranch(mrid.getBranch());
+        }
+        if (options.getPubrevision() == null) {
+            options.setPubrevision(mrid.getRevision());
+        }
+        ModuleRevisionId pubmrid = ModuleRevisionId.newInstance(mrid, options.getPubBranch(),
+            options.getPubrevision());
+
+        // let's find the resolved module descriptor
+        ModuleDescriptor md = null;
+        if (options.getSrcIvyPattern() != null) {
+            File ivyFile = settings.resolveFile(IvyPatternHelper.substitute(
+                options.getSrcIvyPattern(), DefaultArtifact.newIvyArtifact(pubmrid, new Date())));
+            if (!ivyFile.exists()) {
+                throw new IllegalArgumentException("ivy file to publish not found for " + mrid
+                        + ": call deliver before (" + ivyFile + ")");
+            }
+
+            URL ivyFileURL = ivyFile.toURI().toURL();
+            try {
+                md = XmlModuleDescriptorParser.getInstance().parseDescriptor(settings, ivyFileURL,
+                    false);
+
+                if (options.isUpdate()) {
+                    File tmp = File.createTempFile("ivy", ".xml");
+                    tmp.deleteOnExit();
+
+                    String[] confs = ConfigurationUtils.replaceWildcards(options.getConfs(), md);
+                    Set confsToRemove = new HashSet(Arrays.asList(md.getConfigurationsNames()));
+                    confsToRemove.removeAll(Arrays.asList(confs));
+
+                    try {
+                        XmlModuleDescriptorUpdater.update(
+                            ivyFileURL,
+                            tmp,
+                            new UpdateOptions()
+                                    .setSettings(settings)
+                                    .setStatus(
+                                        options.getStatus() == null ? md.getStatus() : options
+                                                .getStatus())
+                                    .setRevision(options.getPubrevision())
+                                    .setBranch(options.getPubBranch())
+                                    .setPubdate(
+                                        options.getPubdate() == null ? new Date() : options
+                                                .getPubdate())
+                                    .setMerge(options.isMerge())
+                                    .setMergedDescriptor(md)
+                                    .setConfsToExclude(
+                                        (String[]) confsToRemove.toArray(new String[confsToRemove
+                                                .size()])));
+                        ivyFile = tmp;
+                        // we parse the new file to get updated module descriptor
+                        md = XmlModuleDescriptorParser.getInstance().parseDescriptor(settings,
+                            ivyFile.toURI().toURL(), false);
+                        options.setSrcIvyPattern(ivyFile.getAbsolutePath());
+                    } catch (SAXException e) {
+                        throw new IllegalStateException("bad ivy file for " + mrid + ": " + ivyFile
+                                + ": " + e);
+                    }
+                } else if (!options.getPubrevision().equals(md.getModuleRevisionId().getRevision())) {
+                    throw new IllegalArgumentException("cannot publish " + ivyFile + " as "
+                            + options.getPubrevision()
+                            + ": bad revision found in ivy file (Revision: "
+                            + md.getModuleRevisionId().getRevision()
+                            + "). Use forcedeliver or update.");
+                }
+            } catch (ParseException e) {
+                throw new IllegalStateException("bad ivy file for " + mrid + ": " + ivyFile + ": "
+                        + e);
+            }
+        } else {
+            ResolutionCacheManager cacheManager = settings.getResolutionCacheManager();
+            try {
+                md = cacheManager.getResolvedModuleDescriptor(mrid);
+            } catch (ParseException e) {
+                throw new IllegalStateException("bad ivy file in cache for " + mrid + ": " + e);
+            }
+            md.setResolvedModuleRevisionId(pubmrid);
+        }
+
+        DependencyResolver resolver = settings.getResolver(resolverName);
+        if (resolver == null) {
+            throw new IllegalArgumentException("unknown resolver " + resolverName);
+        }
+
+        // collect all declared artifacts of this module
+        Collection missing = publish(md, srcArtifactPattern, resolver, options);
+        Message.verbose("\tpublish done (" + (System.currentTimeMillis() - start) + "ms)");
+        return missing;
+    }
+
+    public Collection publish(ModuleDescriptor md, Collection srcArtifactPattern,
+            DependencyResolver resolver, PublishOptions options) throws IOException {
+        Collection missing = new ArrayList();
+        Set artifactsSet = new LinkedHashSet();
+        String[] confs = ConfigurationUtils.replaceWildcards(options.getConfs(), md);
+
+        for (int i = 0; i < confs.length; i++) {
+            Artifact[] artifacts = md.getArtifacts(confs[i]);
+            for (int j = 0; j < artifacts.length; j++) {
+                artifactsSet.add(artifacts[j]);
+            }
+        }
+        Artifact[] extraArtifacts = options.getExtraArtifacts();
+        if (extraArtifacts != null) {
+            for (int i = 0; i < extraArtifacts.length; i++) {
+                artifactsSet.add(new MDArtifact(md, extraArtifacts[i].getName(), extraArtifacts[i]
+                        .getType(), extraArtifacts[i].getExt(), extraArtifacts[i].getUrl(),
+                        extraArtifacts[i].getQualifiedExtraAttributes()));
+            }
+        }
+        // now collects artifacts files
+        Map/* <Artifact,File> */artifactsFiles = new LinkedHashMap();
+        for (Iterator iter = artifactsSet.iterator(); iter.hasNext();) {
+            Artifact artifact = (Artifact) iter.next();
+            for (Iterator iterator = srcArtifactPattern.iterator(); iterator.hasNext();) {
+                String pattern = (String) iterator.next();
+                File artifactFile = settings.resolveFile(IvyPatternHelper.substitute(
+                    settings.substitute(pattern), artifact));
+                if (artifactFile.exists()) {
+                    artifactsFiles.put(artifact, artifactFile);
+                    break;
+                }
+            }
+            if (!artifactsFiles.containsKey(artifact)) {
+                StringBuffer sb = new StringBuffer();
+                sb.append("missing artifact " + artifact + ":\n");
+                for (Iterator iterator = srcArtifactPattern.iterator(); iterator.hasNext();) {
+                    String pattern = (String) iterator.next();
+                    sb.append("\t"
+                            + settings.resolveFile(IvyPatternHelper.substitute(pattern, artifact))
+                            + " file does not exist\n");
+                }
+                if (options.isWarnOnMissing() || options.isHaltOnMissing()) {
+                    Message.warn(sb.toString());
+                } else {
+                    Message.verbose(sb.toString());
+                }
+                if (options.isHaltOnMissing()) {
+                    throw new IOException("missing artifact " + artifact);
+                }
+                missing.add(artifact);
+            }
+        }
+        if (options.getSrcIvyPattern() != null) {
+            Artifact artifact = MDArtifact.newIvyArtifact(md);
+            File artifactFile = settings.resolveFile(IvyPatternHelper.substitute(
+                options.getSrcIvyPattern(), artifact));
+            if (!artifactFile.exists()) {
+                String msg = "missing ivy file for " + md.getModuleRevisionId() + ": \n"
+                        + artifactFile + " file does not exist";
+                if (options.isWarnOnMissing() || options.isHaltOnMissing()) {
+                    Message.warn(msg);
+                } else {
+                    Message.verbose(msg);
+                }
+                if (options.isHaltOnMissing()) {
+                    throw new IOException("missing ivy artifact " + artifact);
+                }
+                missing.add(artifact);
+            } else {
+                artifactsFiles.put(artifact, artifactFile);
+            }
+        }
+
+        // and now do actual publishing
+        boolean successfullyPublished = false;
+        try {
+            resolver.beginPublishTransaction(md.getModuleRevisionId(), options.isOverwrite());
+            // for each declared published artifact in this descriptor, do:
+            for (Iterator iter = artifactsFiles.entrySet().iterator(); iter.hasNext();) {
+                Map.Entry entry = (Entry) iter.next();
+                Artifact artifact = (Artifact) entry.getKey();
+                File artifactFile = (File) entry.getValue();
+                publish(artifact, artifactFile, resolver, options.isOverwrite());
+            }
+            resolver.commitPublishTransaction();
+            successfullyPublished = true;
+        } finally {
+            if (!successfullyPublished) {
+                resolver.abortPublishTransaction();
+            }
+        }
+        return missing;
+    }
+
+    private void publish(Artifact artifact, File src, DependencyResolver resolver, boolean overwrite)
+            throws IOException {
+        IvyContext.getContext().checkInterrupted();
+        // notify triggers that an artifact is about to be published
+        eventManager
+                .fireIvyEvent(new StartArtifactPublishEvent(resolver, artifact, src, overwrite));
+        boolean successful = false; // set to true once the publish succeeds
+        try {
+            if (src.exists()) {
+                resolver.publish(artifact, src, overwrite);
+                successful = true;
+            }
+        } finally {
+            // notify triggers that the publish is finished, successfully or not.
+            eventManager.fireIvyEvent(new EndArtifactPublishEvent(resolver, artifact, src,
+                    overwrite, successful));
+        }
+    }
+}
diff --git a/src/java/org/apache/ivy/core/publish/PublishEngineSettings.java b/src/java/org/apache/ivy/core/publish/PublishEngineSettings.java
new file mode 100644
index 0000000..8105b2f
--- /dev/null
+++ b/src/java/org/apache/ivy/core/publish/PublishEngineSettings.java
@@ -0,0 +1,32 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.publish;
+
+import org.apache.ivy.plugins.parser.ParserSettings;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+
+/**
+ * Settings specific to the publish engine
+ */
+public interface PublishEngineSettings extends ParserSettings {
+
+    String substitute(String srcIvyPattern);
+
+    DependencyResolver getResolver(String resolverName);
+
+}
diff --git a/src/java/org/apache/ivy/core/publish/PublishOptions.java b/src/java/org/apache/ivy/core/publish/PublishOptions.java
new file mode 100644
index 0000000..3c604cd
--- /dev/null
+++ b/src/java/org/apache/ivy/core/publish/PublishOptions.java
@@ -0,0 +1,183 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.publish;
+
+import java.util.Date;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+
+/**
+ * A set of options used during publish related tasks The publish can update the ivy file to publish
+ * if update is set to true. In this case it will use the given pubrevision, pubdate and status. If
+ * pudate is null it will default to the current date. If status is null it will default to the
+ * current ivy file status (which itself defaults to integration if none is found). If update is
+ * false, then if the revision is not the same in the ivy file than the one expected (given as
+ * parameter), this method will fail with an IllegalArgumentException. pubdate and status are not
+ * used if update is false. extra artifacts can be used to publish more artifacts than actually
+ * declared in the ivy file. This can be useful to publish additional metadata or reports. The extra
+ * artifacts array can be null (= no extra artifacts), and if non null only the name, type, ext url
+ * and extra attributes of the artifacts are really used. Other methods (on the artifacts) can
+ * return null safely.
+ * 
+ * @see PublishEngine
+ */
+public class PublishOptions {
+    private String srcIvyPattern;
+
+    private String pubrevision;
+
+    private String status;
+
+    private Date pubdate;
+
+    private Artifact[] extraArtifacts;
+
+    private boolean validate;
+
+    private boolean overwrite;
+
+    private boolean update;
+
+    private boolean merge = true;
+
+    private String[] confs;
+
+    private boolean haltonmissing;
+
+    private String pubBranch;
+
+    private boolean warnonmissing;
+
+    public String[] getConfs() {
+        return confs;
+    }
+
+    public PublishOptions setConfs(String[] confs) {
+        this.confs = confs;
+        return this;
+    }
+
+    public Artifact[] getExtraArtifacts() {
+        return extraArtifacts;
+    }
+
+    public PublishOptions setExtraArtifacts(Artifact[] extraArtifacts) {
+        this.extraArtifacts = extraArtifacts;
+        return this;
+    }
+
+    public boolean isOverwrite() {
+        return overwrite;
+    }
+
+    public PublishOptions setOverwrite(boolean overwrite) {
+        this.overwrite = overwrite;
+        return this;
+    }
+
+    public Date getPubdate() {
+        return pubdate;
+    }
+
+    public PublishOptions setPubdate(Date pubdate) {
+        this.pubdate = pubdate;
+        return this;
+    }
+
+    public String getPubrevision() {
+        return pubrevision;
+    }
+
+    public PublishOptions setPubrevision(String pubrevision) {
+        this.pubrevision = pubrevision;
+        return this;
+    }
+
+    public String getSrcIvyPattern() {
+        return srcIvyPattern;
+    }
+
+    public PublishOptions setSrcIvyPattern(String srcIvyPattern) {
+        this.srcIvyPattern = srcIvyPattern;
+        return this;
+    }
+
+    public String getStatus() {
+        return status;
+    }
+
+    public PublishOptions setStatus(String status) {
+        this.status = status;
+        return this;
+    }
+
+    public boolean isUpdate() {
+        return update;
+    }
+
+    public PublishOptions setUpdate(boolean update) {
+        this.update = update;
+        return this;
+    }
+
+    public boolean isMerge() {
+        return merge;
+    }
+
+    public PublishOptions setMerge(boolean merge) {
+        this.merge = merge;
+        return this;
+    }
+
+    public boolean isValidate() {
+        return validate;
+    }
+
+    public PublishOptions setValidate(boolean validate) {
+        this.validate = validate;
+        return this;
+    }
+
+    public boolean isHaltOnMissing() {
+        return haltonmissing;
+    }
+
+    public PublishOptions setHaltOnMissing(boolean haltonmissing) {
+        this.haltonmissing = haltonmissing;
+        return this;
+    }
+
+    public String getPubBranch() {
+        return pubBranch;
+    }
+
+    public PublishOptions setPubbranch(String pubbranch) {
+        this.pubBranch = pubbranch;
+        return this;
+    }
+
+    public boolean isWarnOnMissing() {
+        return warnonmissing;
+    }
+
+    public PublishOptions setWarnOnMissing(boolean warnonmissing) {
+        this.warnonmissing = warnonmissing;
+        return this;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/report/ArtifactDownloadReport.java b/src/java/org/apache/ivy/core/report/ArtifactDownloadReport.java
new file mode 100644
index 0000000..76547f7
--- /dev/null
+++ b/src/java/org/apache/ivy/core/report/ArtifactDownloadReport.java
@@ -0,0 +1,195 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.report;
+
+import java.io.File;
+
+import org.apache.ivy.core.cache.ArtifactOrigin;
+import org.apache.ivy.core.module.descriptor.Artifact;
+
+/**
+ * Report on the download of an artifact from a repository to a local (cached) file.
+ * <p>
+ * Note that depending on cache implementation, the artifact may not be actually downloaded, but
+ * used directly from its original location.
+ * </p>
+ */
+public class ArtifactDownloadReport {
+    /**
+     * download details used when the download "fails" when the artifact is simply missing on the
+     * remote repository.
+     * <p>
+     * For historical reason the status can't be used to distinguish a real failure from a missing
+     * artifact by using the status, in both cases it's DownloadStatus.FAILED. The details message
+     * can be used for this purpose though.
+     * </p>
+     */
+    public static final String MISSING_ARTIFACT = "missing artifact";
+
+    private Artifact artifact;
+
+    private ArtifactOrigin origin;
+
+    private File localFile;
+
+    private DownloadStatus downloadStatus;
+
+    private long size;
+
+    private String downloadDetails = "";
+
+    private long downloadTimeMillis;
+
+    private File unpackedLocalFile;
+
+    public ArtifactDownloadReport(Artifact artifact) {
+        this.artifact = artifact;
+    }
+
+    public DownloadStatus getDownloadStatus() {
+        return downloadStatus;
+    }
+
+    public void setDownloadStatus(DownloadStatus downloadStatus) {
+        this.downloadStatus = downloadStatus;
+    }
+
+    public String getName() {
+        return artifact.getName();
+    }
+
+    /**
+     * 
+     * @return the type of the downloaded artifact
+     */
+    public String getType() {
+        return artifact.getType();
+    }
+
+    public Artifact getArtifact() {
+        return artifact;
+    }
+
+    public String getExt() {
+        return artifact.getExt();
+    }
+
+    public long getSize() {
+        return size;
+    }
+
+    public void setSize(long size) {
+        this.size = size;
+    }
+
+    public void setArtifactOrigin(ArtifactOrigin origin) {
+        this.origin = origin;
+    }
+
+    public ArtifactOrigin getArtifactOrigin() {
+        return origin;
+    }
+
+    public void setDownloadDetails(String message) {
+        downloadDetails = message;
+    }
+
+    public String getDownloadDetails() {
+        return downloadDetails;
+    }
+
+    public void setDownloadTimeMillis(long l) {
+        downloadTimeMillis = l;
+    }
+
+    public long getDownloadTimeMillis() {
+        return downloadTimeMillis;
+    }
+
+    public String toString() {
+        if (downloadStatus == DownloadStatus.SUCCESSFUL) {
+            return "[SUCCESSFUL ] " + artifact + " (" + downloadTimeMillis + "ms)";
+        } else if (downloadStatus == DownloadStatus.FAILED) {
+            if (downloadDetails == MISSING_ARTIFACT) {
+                return "[NOT FOUND  ] " + artifact + " (" + downloadTimeMillis + "ms)";
+            } else {
+                return "[FAILED     ] " + artifact + ": " + downloadDetails + " ("
+                        + downloadTimeMillis + "ms)";
+            }
+        } else if (downloadStatus == DownloadStatus.NO) {
+            return "[NOT REQUIRED] " + artifact;
+        } else {
+            return super.toString();
+        }
+    }
+
+    /**
+     * Returns the File where the artifact is available on the local filesystem, or
+     * <code>null</code> if and only if the artifact caching failed.
+     * 
+     * @return the file where the artifact is now available on the local filesystem.
+     */
+    public File getLocalFile() {
+        return localFile;
+    }
+
+    public void setLocalFile(File localFile) {
+        this.localFile = localFile;
+    }
+
+    public boolean isDownloaded() {
+        return DownloadStatus.SUCCESSFUL == downloadStatus;
+    }
+
+    public void setUnpackedLocalFile(File unpackedLocalFile) {
+        this.unpackedLocalFile = unpackedLocalFile;
+    }
+
+    public File getUnpackedLocalFile() {
+        return unpackedLocalFile;
+    }
+
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((artifact == null) ? 0 : artifact.hashCode());
+        return result;
+    }
+
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        ArtifactDownloadReport other = (ArtifactDownloadReport) obj;
+        if (artifact == null) {
+            if (other.artifact != null) {
+                return false;
+            }
+        } else if (!artifact.equals(other.artifact)) {
+            return false;
+        }
+        return true;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/report/ConfigurationResolveReport.java b/src/java/org/apache/ivy/core/report/ConfigurationResolveReport.java
new file mode 100644
index 0000000..a395783
--- /dev/null
+++ b/src/java/org/apache/ivy/core/report/ConfigurationResolveReport.java
@@ -0,0 +1,365 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.report;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.ivy.core.cache.ResolutionCacheManager;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.resolve.IvyNode;
+import org.apache.ivy.core.resolve.ResolveEngine;
+import org.apache.ivy.core.resolve.ResolveOptions;
+import org.apache.ivy.core.sort.SortOptions;
+import org.apache.ivy.plugins.report.XmlReportParser;
+import org.apache.ivy.util.Message;
+
+/**
+ * Represents a whole resolution report for a module but for a specific configuration
+ */
+public class ConfigurationResolveReport {
+
+    private final ModuleDescriptor md;
+
+    private final String conf;
+
+    private final Date date;
+
+    private final ResolveOptions options;
+
+    private Map<IvyNode, List<ArtifactDownloadReport>> dependencyReports = new LinkedHashMap<IvyNode, List<ArtifactDownloadReport>>();
+
+    private Map<ModuleRevisionId, IvyNode> dependencies = new LinkedHashMap<ModuleRevisionId, IvyNode>();
+
+    private final ResolveEngine resolveEngine;
+
+    private Map<ModuleId, Collection<IvyNode>> modulesIdsMap = new LinkedHashMap<ModuleId, Collection<IvyNode>>();
+
+    private List<ModuleId> modulesIds;
+
+    private Boolean hasChanged = null;
+
+    public ConfigurationResolveReport(ResolveEngine resolveEngine, ModuleDescriptor md,
+            String conf, Date date, ResolveOptions options) {
+        this.resolveEngine = resolveEngine;
+        this.md = md;
+        this.conf = conf;
+        this.date = date;
+        this.options = options;
+    }
+
+    /**
+     * Check if the set of dependencies has changed since the previous execution of a resolution.<br/>
+     * This function use the report file found in the cache. So the function must be called before
+     * the new report is serialized there.</br> This function also use the internal dependencies
+     * that must already be filled. This function might be 'heavy' because it may have to parse the
+     * previous report.
+     * 
+     * @return
+     */
+    public void checkIfChanged() {
+        ResolutionCacheManager cache = resolveEngine.getSettings().getResolutionCacheManager();
+        String resolveId = options.getResolveId();
+        File previousReportFile = cache.getConfigurationResolveReportInCache(resolveId, conf);
+        if (previousReportFile.exists()) {
+            try {
+                XmlReportParser parser = new XmlReportParser();
+                parser.parse(previousReportFile);
+                List<ModuleRevisionId> previousDeps = Arrays.asList(parser
+                        .getDependencyRevisionIds());
+                HashSet<ModuleRevisionId> previousDepSet = new HashSet<ModuleRevisionId>(
+                        previousDeps);
+                hasChanged = Boolean.valueOf(!previousDepSet.equals(getModuleRevisionIds()));
+            } catch (Exception e) {
+                Message.warn("Error while parsing configuration resolve report "
+                        + previousReportFile.getAbsolutePath(), e);
+                hasChanged = Boolean.TRUE;
+            }
+        } else {
+            hasChanged = Boolean.TRUE;
+        }
+    }
+
+    /**
+     * @pre checkIfChanged has been called.
+     */
+    public boolean hasChanged() {
+        return hasChanged.booleanValue();
+    }
+
+    /**
+     * Returns all non evicted and non error dependency mrids The returned set is ordered so that a
+     * dependency will always be found before their own dependencies
+     * 
+     * @return all non evicted and non error dependency mrids
+     */
+    public Set<ModuleRevisionId> getModuleRevisionIds() {
+        Set<ModuleRevisionId> mrids = new LinkedHashSet<ModuleRevisionId>();
+        for (IvyNode node : getDependencies()) {
+            if (!node.isEvicted(getConfiguration()) && !node.hasProblem()) {
+                mrids.add(node.getResolvedId());
+            }
+        }
+        return mrids;
+    }
+
+    public void addDependency(IvyNode node) {
+        dependencies.put(node.getId(), node);
+        dependencies.put(node.getResolvedId(), node);
+        dependencyReports.put(node, Collections.<ArtifactDownloadReport> emptyList());
+    }
+
+    public void updateDependency(ModuleRevisionId mrid, IvyNode node) {
+        dependencies.put(mrid, node);
+    }
+
+    public void addDependency(IvyNode node, DownloadReport report) {
+        dependencies.put(node.getId(), node);
+        dependencies.put(node.getResolvedId(), node);
+        List<ArtifactDownloadReport> adrs = new ArrayList<ArtifactDownloadReport>();
+        Artifact[] artifacts = node.getArtifacts(conf);
+        for (Artifact artifact : artifacts) {
+            ArtifactDownloadReport artifactReport = report.getArtifactReport(artifact);
+            if (artifactReport != null) {
+                adrs.add(artifactReport);
+            } else {
+                Message.debug("no report found for " + artifact);
+            }
+        }
+        dependencyReports.put(node, adrs);
+    }
+
+    public String getConfiguration() {
+        return conf;
+    }
+
+    public Date getDate() {
+        return date;
+    }
+
+    public ModuleDescriptor getModuleDescriptor() {
+        return md;
+    }
+
+    public IvyNode[] getUnresolvedDependencies() {
+        List<IvyNode> unresolved = new ArrayList<IvyNode>();
+        for (IvyNode node : getDependencies()) {
+            if (node.hasProblem()) {
+                unresolved.add(node);
+            }
+        }
+        return unresolved.toArray(new IvyNode[unresolved.size()]);
+    }
+
+    private Collection<IvyNode> getDependencies() {
+        return new LinkedHashSet<IvyNode>(dependencies.values());
+    }
+
+    public IvyNode[] getEvictedNodes() {
+        List<IvyNode> evicted = new ArrayList<IvyNode>();
+        for (IvyNode node : getDependencies()) {
+            if (node.isEvicted(conf)) {
+                evicted.add(node);
+            }
+        }
+        return evicted.toArray(new IvyNode[evicted.size()]);
+    }
+
+    private Set<ModuleRevisionId> getEvictedMrids() {
+        Set<ModuleRevisionId> evicted = new LinkedHashSet<ModuleRevisionId>();
+        IvyNode[] evictedNodes = getEvictedNodes();
+        for (IvyNode node : evictedNodes) {
+            evicted.add(node.getId());
+        }
+        return evicted;
+    }
+
+    public IvyNode[] getDownloadedNodes() {
+        List<IvyNode> downloaded = new ArrayList<IvyNode>();
+        for (IvyNode node : getDependencies()) {
+            if (node.isDownloaded() && node.getRealNode() == node) {
+                downloaded.add(node);
+            }
+        }
+        return downloaded.toArray(new IvyNode[downloaded.size()]);
+    }
+
+    public IvyNode[] getSearchedNodes() {
+        List<IvyNode> downloaded = new ArrayList<IvyNode>();
+        for (IvyNode node : getDependencies()) {
+            if (node.isSearched() && node.getRealNode() == node) {
+                downloaded.add(node);
+            }
+        }
+        return downloaded.toArray(new IvyNode[downloaded.size()]);
+    }
+
+    public ArtifactDownloadReport[] getDownloadReports(ModuleRevisionId mrid) {
+        Collection<ArtifactDownloadReport> col = dependencyReports.get(getDependency(mrid));
+        if (col == null) {
+            return new ArtifactDownloadReport[0];
+        }
+        return col.toArray(new ArtifactDownloadReport[col.size()]);
+    }
+
+    public IvyNode getDependency(ModuleRevisionId mrid) {
+        return dependencies.get(mrid);
+    }
+
+    /**
+     * gives all the modules ids concerned by this report, from the most dependent to the least one
+     * 
+     * @return a list of ModuleId
+     */
+    public List<ModuleId> getModuleIds() {
+        if (modulesIds == null) {
+            List<IvyNode> sortedDependencies = resolveEngine.getSortEngine().sortNodes(
+                getDependencies(), SortOptions.SILENT);
+            Collections.reverse(sortedDependencies);
+            for (IvyNode dependency : sortedDependencies) {
+                ModuleId mid = dependency.getResolvedId().getModuleId();
+                Collection<IvyNode> deps = modulesIdsMap.get(mid);
+                if (deps == null) {
+                    deps = new LinkedHashSet<IvyNode>();
+                    modulesIdsMap.put(mid, deps);
+                }
+                deps.add(dependency);
+            }
+            modulesIds = new ArrayList<ModuleId>(modulesIdsMap.keySet());
+        }
+        return Collections.unmodifiableList(modulesIds);
+    }
+
+    public Collection<IvyNode> getNodes(ModuleId mid) {
+        if (modulesIds == null) {
+            getModuleIds();
+        }
+        return modulesIdsMap.get(mid);
+    }
+
+    public ResolveEngine getResolveEngine() {
+        return resolveEngine;
+    }
+
+    public int getArtifactsNumber() {
+        int total = 0;
+        for (Collection<ArtifactDownloadReport> reports : dependencyReports.values()) {
+            total += reports == null ? 0 : reports.size();
+        }
+        return total;
+    }
+
+    /**
+     * Get every report on the download requests.
+     * 
+     * @return the list of reports, never <code>null</code>
+     */
+    public ArtifactDownloadReport[] getAllArtifactsReports() {
+        return getArtifactsReports(null, true);
+    }
+
+    /**
+     * Get the report on the download requests. The list of download report can be restricted to a
+     * specific download status, and also remove the download report for the evicted modules.
+     * 
+     * @param downloadStatus
+     *            the status of download to retreive. Set it to <code>null</code> for no restriction
+     *            on the download status
+     * @param withEvicted
+     *            set it to <code>true</code> if the report for the evicted modules have to be
+     *            retrieved.
+     * @return the list of reports, never <code>null</code>
+     * @see ArtifactDownloadReport
+     */
+    public ArtifactDownloadReport[] getArtifactsReports(DownloadStatus downloadStatus,
+            boolean withEvicted) {
+        Collection<ArtifactDownloadReport> all = new LinkedHashSet<ArtifactDownloadReport>();
+        Collection<ModuleRevisionId> evictedMrids = null;
+        if (!withEvicted) {
+            evictedMrids = getEvictedMrids();
+        }
+        for (Collection<ArtifactDownloadReport> reports : dependencyReports.values()) {
+            for (ArtifactDownloadReport report : reports) {
+                if (downloadStatus != null && report.getDownloadStatus() != downloadStatus) {
+                    continue;
+                }
+                if (withEvicted
+                        || !evictedMrids.contains(report.getArtifact().getModuleRevisionId())) {
+                    all.add(report);
+                }
+            }
+        }
+        return all.toArray(new ArtifactDownloadReport[all.size()]);
+    }
+
+    /**
+     * Get the report on the sucessfull download requests with the evicted modules
+     * 
+     * @return the list of reports, never <code>null</code>
+     */
+    public ArtifactDownloadReport[] getDownloadedArtifactsReports() {
+        return getArtifactsReports(DownloadStatus.SUCCESSFUL, true);
+    }
+
+    /**
+     * Get the report on the failed download requests with the evicted modules
+     * 
+     * @return the list of reports, never <code>null</code>
+     */
+    public ArtifactDownloadReport[] getFailedArtifactsReports() {
+        ArtifactDownloadReport[] allFailedReports = getArtifactsReports(DownloadStatus.FAILED, true);
+        return filterOutMergedArtifacts(allFailedReports);
+    }
+
+    public boolean hasError() {
+        return getUnresolvedDependencies().length > 0 || getFailedArtifactsReports().length > 0;
+    }
+
+    public int getNodesNumber() {
+        return getDependencies().size();
+    }
+
+    public static ArtifactDownloadReport[] filterOutMergedArtifacts(
+            ArtifactDownloadReport[] allFailedReports) {
+        Collection<ArtifactDownloadReport> adrs = new ArrayList<ArtifactDownloadReport>(
+                Arrays.asList(allFailedReports));
+        for (Iterator<ArtifactDownloadReport> iterator = adrs.iterator(); iterator.hasNext();) {
+            ArtifactDownloadReport adr = iterator.next();
+
+            if (adr.getArtifact().getExtraAttribute("ivy:merged") != null) {
+                iterator.remove();
+            }
+        }
+        return adrs.toArray(new ArtifactDownloadReport[adrs.size()]);
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/report/DownloadReport.java b/src/java/org/apache/ivy/core/report/DownloadReport.java
new file mode 100644
index 0000000..f2dfcb6
--- /dev/null
+++ b/src/java/org/apache/ivy/core/report/DownloadReport.java
@@ -0,0 +1,57 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.report;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+
+/**
+ *
+ */
+public class DownloadReport {
+    private Map artifacts = new HashMap();
+
+    public void addArtifactReport(ArtifactDownloadReport adr) {
+        artifacts.put(adr.getArtifact(), adr);
+    }
+
+    public ArtifactDownloadReport[] getArtifactsReports() {
+        return (ArtifactDownloadReport[]) artifacts.values().toArray(
+            new ArtifactDownloadReport[artifacts.size()]);
+    }
+
+    public ArtifactDownloadReport[] getArtifactsReports(DownloadStatus status) {
+        List ret = new ArrayList(artifacts.size());
+        for (Iterator iter = artifacts.values().iterator(); iter.hasNext();) {
+            ArtifactDownloadReport adr = (ArtifactDownloadReport) iter.next();
+            if (adr.getDownloadStatus() == status) {
+                ret.add(adr);
+            }
+        }
+        return (ArtifactDownloadReport[]) ret.toArray(new ArtifactDownloadReport[ret.size()]);
+    }
+
+    public ArtifactDownloadReport getArtifactReport(Artifact artifact) {
+        return (ArtifactDownloadReport) artifacts.get(artifact);
+    }
+}
diff --git a/src/java/org/apache/ivy/core/report/DownloadStatus.java b/src/java/org/apache/ivy/core/report/DownloadStatus.java
new file mode 100644
index 0000000..f0364c2
--- /dev/null
+++ b/src/java/org/apache/ivy/core/report/DownloadStatus.java
@@ -0,0 +1,62 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.report;
+
+/**
+ *
+ */
+public final class DownloadStatus {
+    private String name;
+
+    private DownloadStatus(String name) {
+        this.name = name;
+    }
+
+    /**
+     * means that download was not required
+     */
+    public static final DownloadStatus NO = new DownloadStatus("no");
+
+    public static final DownloadStatus SUCCESSFUL = new DownloadStatus("successful");
+
+    public static final DownloadStatus FAILED = new DownloadStatus("failed");
+
+    /**
+     * Returns the {@link DownloadStatus} corresponding to the given String representation.
+     * 
+     * @return the {@link DownloadStatus} corresponding to the given String representation.
+     * @throws IllegalArgumentException
+     *             if the given String does not correspond to any {@link DownloadStatus}.
+     */
+    public static final DownloadStatus fromString(String status) {
+        if (NO.name.equals(status)) {
+            return NO;
+        }
+        if (SUCCESSFUL.name.equals(status)) {
+            return SUCCESSFUL;
+        }
+        if (FAILED.name.equals(status)) {
+            return FAILED;
+        }
+        throw new IllegalArgumentException("unknown download status '" + status + "'");
+    }
+
+    public String toString() {
+        return name;
+    }
+}
diff --git a/src/java/org/apache/ivy/core/report/MetadataArtifactDownloadReport.java b/src/java/org/apache/ivy/core/report/MetadataArtifactDownloadReport.java
new file mode 100644
index 0000000..be3181f
--- /dev/null
+++ b/src/java/org/apache/ivy/core/report/MetadataArtifactDownloadReport.java
@@ -0,0 +1,63 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.report;
+
+import java.io.File;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+
+public class MetadataArtifactDownloadReport extends ArtifactDownloadReport {
+    private boolean isSearched;
+
+    private File originalLocalFile;
+
+    public MetadataArtifactDownloadReport(Artifact artifact) {
+        super(artifact);
+    }
+
+    /**
+     * Returns <code>true</code> if the resolution of this metadata artifact required at least one
+     * access to the repository, or <code>false</code> if only provisioned data was used.
+     * 
+     * @return <code>true</code> if the resolution of this metadata artifact required at least one
+     *         access to the repository
+     */
+    public boolean isSearched() {
+        return isSearched;
+    }
+
+    public void setSearched(boolean isSearched) {
+        this.isSearched = isSearched;
+    }
+
+    /**
+     * Returns the location on the local filesystem where the original metadata artifact is
+     * provisioned, or <code>null</code> if the provisioning failed.
+     * 
+     * @return the location on the local filesystem where the original metadata artifact is
+     *         provisioned.
+     */
+    public File getOriginalLocalFile() {
+        return originalLocalFile;
+    }
+
+    public void setOriginalLocalFile(File originalLocalFile) {
+        this.originalLocalFile = originalLocalFile;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/report/ResolveReport.java b/src/java/org/apache/ivy/core/report/ResolveReport.java
new file mode 100644
index 0000000..0f12488
--- /dev/null
+++ b/src/java/org/apache/ivy/core/report/ResolveReport.java
@@ -0,0 +1,436 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.report;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.ivy.core.cache.ResolutionCacheManager;
+import org.apache.ivy.core.module.descriptor.Configuration;
+import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
+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.ModuleId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.resolve.IvyNode;
+import org.apache.ivy.core.resolve.ResolveOptions;
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.ivy.plugins.report.ReportOutputter;
+import org.apache.ivy.util.filter.Filter;
+
+/**
+ * Represents a whole resolution report for a module
+ */
+public class ResolveReport {
+    private ModuleDescriptor md;
+
+    /** String conf -> ConfigurationResolveReport report */
+    private Map confReports = new LinkedHashMap();
+
+    private List problemMessages = new ArrayList();
+
+    /**
+     * the list of all dependencies resolved, ordered from the more dependent to the less dependent
+     */
+    private List/* <IvyNode> */dependencies = new ArrayList();
+
+    private List/* <Artifact> */artifacts = new ArrayList();
+
+    private long resolveTime;
+
+    private long downloadTime;
+
+    private String resolveId;
+
+    private long downloadSize;
+
+    public ResolveReport(ModuleDescriptor md) {
+        this(md, ResolveOptions.getDefaultResolveId(md));
+    }
+
+    public ResolveReport(ModuleDescriptor md, String resolveId) {
+        this.md = md;
+        this.resolveId = resolveId;
+    }
+
+    public void addReport(String conf, ConfigurationResolveReport report) {
+        confReports.put(conf, report);
+    }
+
+    public ConfigurationResolveReport getConfigurationReport(String conf) {
+        return (ConfigurationResolveReport) confReports.get(conf);
+    }
+
+    public String[] getConfigurations() {
+        return (String[]) confReports.keySet().toArray(new String[confReports.size()]);
+    }
+
+    public boolean hasError() {
+        boolean hasError = false;
+        for (Iterator it = confReports.values().iterator(); it.hasNext() && !hasError;) {
+            ConfigurationResolveReport report = (ConfigurationResolveReport) it.next();
+            hasError |= report.hasError();
+        }
+        return hasError;
+    }
+
+    public void output(ReportOutputter[] outputters, ResolutionCacheManager cacheMgr,
+            ResolveOptions options) throws IOException {
+        for (int i = 0; i < outputters.length; i++) {
+            outputters[i].output(this, cacheMgr, options);
+        }
+    }
+
+    public ModuleDescriptor getModuleDescriptor() {
+        return md;
+    }
+
+    public IvyNode[] getEvictedNodes() {
+        Collection all = new LinkedHashSet();
+        for (Iterator iter = confReports.values().iterator(); iter.hasNext();) {
+            ConfigurationResolveReport report = (ConfigurationResolveReport) iter.next();
+            all.addAll(Arrays.asList(report.getEvictedNodes()));
+        }
+        return (IvyNode[]) all.toArray(new IvyNode[all.size()]);
+    }
+
+    public IvyNode[] getUnresolvedDependencies() {
+        Collection all = new LinkedHashSet();
+        for (Iterator iter = confReports.values().iterator(); iter.hasNext();) {
+            ConfigurationResolveReport report = (ConfigurationResolveReport) iter.next();
+            all.addAll(Arrays.asList(report.getUnresolvedDependencies()));
+        }
+        return (IvyNode[]) all.toArray(new IvyNode[all.size()]);
+    }
+
+    /**
+     * Get every report on the download requests.
+     * 
+     * @return the list of reports, never <code>null</code>
+     */
+    public ArtifactDownloadReport[] getFailedArtifactsReports() {
+        return ConfigurationResolveReport.filterOutMergedArtifacts(getArtifactsReports(
+            DownloadStatus.FAILED, true));
+    }
+
+    /**
+     * Get every report on the download requests.
+     * 
+     * @return the list of reports, never <code>null</code>
+     */
+    public ArtifactDownloadReport[] getAllArtifactsReports() {
+        return getArtifactsReports(null, true);
+    }
+
+    /**
+     * Get the report on the download requests. The list of download report can be restricted to a
+     * specific download status, and also remove the download report for the evicted modules.
+     * 
+     * @param downloadStatus
+     *            the status of download to retreive. Set it to <code>null</code> for no restriction
+     *            on the download status
+     * @param withEvicted
+     *            set it to <code>true</code> if the report for the evicted modules have to be
+     *            retrieved, <code>false</code> to exclude reports from modules evicted in all
+     *            configurations.
+     * @return the list of reports, never <code>null</code>
+     * @see ConfigurationResolveReport#getArtifactsReports(DownloadStatus, boolean)
+     */
+    public ArtifactDownloadReport[] getArtifactsReports(DownloadStatus downloadStatus,
+            boolean withEvicted) {
+        Collection all = new LinkedHashSet();
+        for (Iterator iter = confReports.values().iterator(); iter.hasNext();) {
+            ConfigurationResolveReport report = (ConfigurationResolveReport) iter.next();
+            ArtifactDownloadReport[] reports = report.getArtifactsReports(downloadStatus,
+                withEvicted);
+            all.addAll(Arrays.asList(reports));
+        }
+        return (ArtifactDownloadReport[]) all.toArray(new ArtifactDownloadReport[all.size()]);
+    }
+
+    public ArtifactDownloadReport[] getArtifactsReports(ModuleRevisionId mrid) {
+        Collection all = new LinkedHashSet();
+        for (Iterator iter = confReports.values().iterator(); iter.hasNext();) {
+            ConfigurationResolveReport report = (ConfigurationResolveReport) iter.next();
+            all.addAll(Arrays.asList(report.getDownloadReports(mrid)));
+        }
+        return (ArtifactDownloadReport[]) all.toArray(new ArtifactDownloadReport[all.size()]);
+    }
+
+    public void checkIfChanged() {
+        for (Iterator iter = confReports.values().iterator(); iter.hasNext();) {
+            ConfigurationResolveReport report = (ConfigurationResolveReport) iter.next();
+            report.checkIfChanged();
+        }
+    }
+
+    /** Can only be called if checkIfChanged has been called */
+    public boolean hasChanged() {
+        for (Iterator iter = confReports.values().iterator(); iter.hasNext();) {
+            ConfigurationResolveReport report = (ConfigurationResolveReport) iter.next();
+            if (report.hasChanged()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public void setProblemMessages(List problems) {
+        problemMessages = problems;
+    }
+
+    public List getProblemMessages() {
+        return problemMessages;
+    }
+
+    public List getAllProblemMessages() {
+        List ret = new ArrayList(problemMessages);
+        for (Iterator iter = confReports.values().iterator(); iter.hasNext();) {
+            ConfigurationResolveReport r = (ConfigurationResolveReport) iter.next();
+            IvyNode[] unresolved = r.getUnresolvedDependencies();
+            for (int i = 0; i < unresolved.length; i++) {
+                String errMsg = unresolved[i].getProblemMessage();
+                if (errMsg.length() > 0) {
+                    ret.add("unresolved dependency: " + unresolved[i].getId() + ": " + errMsg);
+                } else {
+                    ret.add("unresolved dependency: " + unresolved[i].getId());
+                }
+            }
+            ArtifactDownloadReport[] adrs = r.getFailedArtifactsReports();
+            for (int i = 0; i < adrs.length; i++) {
+                ret.add("download failed: " + adrs[i].getArtifact());
+            }
+        }
+        return ret;
+    }
+
+    public void setDependencies(List dependencies, Filter artifactFilter) {
+        this.dependencies = dependencies;
+        // collect list of artifacts
+        artifacts = new ArrayList();
+        for (Iterator iter = dependencies.iterator(); iter.hasNext();) {
+            IvyNode dependency = (IvyNode) iter.next();
+            if (!dependency.isCompletelyEvicted() && !dependency.hasProblem()) {
+                artifacts.addAll(Arrays.asList(dependency.getSelectedArtifacts(artifactFilter)));
+            }
+            // update the configurations reports with the dependencies
+            // these reports will be completed later with download information, if any
+            String[] dconfs = dependency.getRootModuleConfigurations();
+            for (int j = 0; j < dconfs.length; j++) {
+                ConfigurationResolveReport configurationReport = getConfigurationReport(dconfs[j]);
+                if (configurationReport != null) {
+                    configurationReport.addDependency(dependency);
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns the list of all dependencies concerned by this report as a List of IvyNode ordered
+     * from the more dependent to the least one
+     * 
+     * @return The list of all dependencies.
+     */
+    public List/* <IvyNode> */getDependencies() {
+        return dependencies;
+    }
+
+    /**
+     * Returns the list of all artifacts which should be downloaded per this resolve To know if the
+     * artifact have actually been downloaded use information found in ConfigurationResolveReport.
+     * 
+     * @return The list of all artifacts.
+     */
+    public List/* <Artifact> */getArtifacts() {
+        return artifacts;
+    }
+
+    /**
+     * gives all the modules ids concerned by this report, from the most dependent to the least one
+     * 
+     * @return a list of ModuleId
+     */
+    public List getModuleIds() {
+        List ret = new ArrayList();
+        List sortedDependencies = new ArrayList(dependencies);
+        for (Iterator iter = sortedDependencies.iterator(); iter.hasNext();) {
+            IvyNode dependency = (IvyNode) iter.next();
+            ModuleId mid = dependency.getResolvedId().getModuleId();
+            if (!ret.contains(mid)) {
+                ret.add(mid);
+            }
+        }
+        return ret;
+    }
+
+    public void setResolveTime(long elapsedTime) {
+        resolveTime = elapsedTime;
+    }
+
+    public long getResolveTime() {
+        return resolveTime;
+    }
+
+    public void setDownloadTime(long elapsedTime) {
+        downloadTime = elapsedTime;
+    }
+
+    public long getDownloadTime() {
+        return downloadTime;
+    }
+
+    public void setDownloadSize(long size) {
+        this.downloadSize = size;
+    }
+
+    /**
+     * The total size of downloaded artifacts, in bytes.
+     * <p>
+     * This only includes artifacts actually downloaded to cache (DownloadStatus.SUCCESSFUL), and
+     * not artifacts already in cache or used at their original location.
+     * </p>
+     * 
+     * @return The total size of downloaded artifacts, in bytes.
+     */
+    public long getDownloadSize() {
+        return downloadSize;
+    }
+
+    public String getResolveId() {
+        return resolveId;
+    }
+
+    /**
+     * Get every configuration which extends the specified one. The returned list also includes the
+     * specified one.
+     * 
+     * @param extended
+     * @return
+     */
+    private String[] getExtendingConfs(String extended) {
+        String[] allConfs = md.getConfigurationsNames();
+        Set<String> extendingConfs = new HashSet<String>();
+        extendingConfs.add(extended);
+        for (int i = 0; i < allConfs.length; i++) {
+            gatherExtendingConfs(extendingConfs, allConfs[i], extended);
+        }
+        return extendingConfs.toArray(new String[extendingConfs.size()]);
+    }
+
+    private boolean gatherExtendingConfs(Set<String> extendingConfs, String conf, String extended) {
+        if (extendingConfs.contains(conf)) {
+            return true;
+        }
+        String[] ext = md.getConfiguration(conf).getExtends();
+        if (ext == null || ext.length == 0) {
+            return false;
+        }
+        for (int i = 0; i < ext.length; i++) {
+            if (extendingConfs.contains(ext[i])) {
+                extendingConfs.add(conf);
+                return true;
+            }
+            if (ext[i].equals(extended)) {
+                extendingConfs.add(conf);
+                return true;
+            }
+            if (gatherExtendingConfs(extendingConfs, ext[i], extended)) {
+                extendingConfs.add(conf);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public ModuleDescriptor toFixedModuleDescriptor(IvySettings settings, List<ModuleId> midToKeep) {
+        DefaultModuleDescriptor fixedmd = new DefaultModuleDescriptor(md.getModuleRevisionId(),
+                md.getStatus(), new Date());
+        fixedmd.getExtraInfos().addAll(md.getExtraInfos());
+
+        // copy configurations
+        List<String> resolvedConfs = Arrays.asList(getConfigurations());
+        for (String conf : resolvedConfs) {
+            fixedmd.addConfiguration(new Configuration(conf));
+        }
+
+        if (midToKeep != null && !midToKeep.isEmpty()) {
+            // add dependency we want to keep from the original module descriptor
+            DependencyDescriptor[] deps = md.getDependencies();
+            for (int i = 0; i < deps.length; i++) {
+                if (midToKeep.contains(deps[i].getDependencyId())) {
+                    DefaultDependencyDescriptor dep = new DefaultDependencyDescriptor(fixedmd,
+                            deps[i].getDependencyRevisionId(), true, false, false);
+                    List<String> confs = Arrays.asList(deps[i].getModuleConfigurations());
+                    if (confs.size() == 1 && confs.get(0).equals("*")) {
+                        confs = resolvedConfs;
+                    }
+                    for (String conf : confs) {
+                        String[] extendedConfs = getExtendingConfs(conf);
+                        String[] depConfs = deps[i].getDependencyConfigurations(conf);
+                        for (String extendedConf : extendedConfs) {
+                            if (resolvedConfs.contains(extendedConf)) {
+                                for (String depConf : depConfs) {
+                                    dep.addDependencyConfiguration(extendedConf, depConf);
+                                }
+                            }
+                        }
+                    }
+                    fixedmd.addDependency(dep);
+                }
+            }
+        }
+
+        // add resolved dependencies
+        for (int i = 0; i < dependencies.size(); i++) {
+            IvyNode node = (IvyNode) dependencies.get(i);
+            if (midToKeep != null && midToKeep.contains(node.getModuleId())) {
+                continue;
+            }
+            String[] rootConfs = node.getRootModuleConfigurations();
+            for (int j = 0; j < rootConfs.length; j++) {
+                if (node.isEvicted(rootConfs[j])) {
+                    continue;
+                }
+                if (node.getAllArtifacts().length == 0) {
+                    // no artifact: it was probably useful transitively, hence it is useless here
+                    break;
+                }
+                DefaultDependencyDescriptor dep = new DefaultDependencyDescriptor(fixedmd,
+                        node.getResolvedId(), true, false, false);
+                String[] targetConfs = node.getConfigurations(rootConfs[j]);
+                for (int k = 0; k < targetConfs.length; k++) {
+                    dep.addDependencyConfiguration(rootConfs[j], targetConfs[k]);
+                }
+                fixedmd.addDependency(dep);
+            }
+        }
+
+        return fixedmd;
+    }
+}
diff --git a/src/java/org/apache/ivy/core/repository/RepositoryManagementEngine.java b/src/java/org/apache/ivy/core/repository/RepositoryManagementEngine.java
new file mode 100644
index 0000000..fe552ed
--- /dev/null
+++ b/src/java/org/apache/ivy/core/repository/RepositoryManagementEngine.java
@@ -0,0 +1,339 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.repository;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.TreeSet;
+
+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.apache.ivy.core.resolve.ResolveData;
+import org.apache.ivy.core.resolve.ResolveEngine;
+import org.apache.ivy.core.resolve.ResolveOptions;
+import org.apache.ivy.core.resolve.ResolvedModuleRevision;
+import org.apache.ivy.core.search.SearchEngine;
+import org.apache.ivy.plugins.latest.ArtifactInfo;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+import org.apache.ivy.plugins.matcher.RegexpPatternMatcher;
+import org.apache.ivy.plugins.version.VersionMatcher;
+import org.apache.ivy.util.MemoryUtil;
+import org.apache.ivy.util.Message;
+
+/**
+ * The repository management can be used to load all metadata from a repository, analyze them, and
+ * provide a bunch of information about the whole repository state.
+ * <p>
+ * Since loading all metadata from a repository is not a light task, this engine should only be used
+ * on a machine having good access to the repository (on the same filesystem being usually the best
+ * suited).
+ * </p>
+ * <p>
+ * To access information, you usually have before to call a method to init the data: {@link #load()}
+ * is used to load repository metadata, {@link #analyze()} is used to analyze them. These methods
+ * being very time consuming, they must always be called explicitly.
+ * </p>
+ * <p>
+ * On a large repository, this engine can be very memory consuming to use, it is not suited to be
+ * used in a long running process, but rather in short process loading data and taking action about
+ * the current state of the repository.
+ * </p>
+ * <p>
+ * This engine is not intended to be used concurrently with publish, the order of repository loaded
+ * being undeterministic and long, it could end up in having an inconsistent in memory state.
+ * </p>
+ * <p>
+ * For better performance, we strongly suggest using this engine with cache in useOrigin mode.
+ * </p>
+ */
+public class RepositoryManagementEngine {
+    private static final double THOUSAND = 1000.0;
+
+    private static final int KILO = 1024;
+
+    // /////////////////////////////////////////
+    // state loaded on #load()
+    // /////////////////////////////////////////
+
+    /**
+     * True if the repository has already been loaded, false otherwise.
+     */
+    private boolean loaded;
+
+    /**
+     * ModuleDescriptors stored by ModuleRevisionId
+     */
+    private Map/* <ModuleRevisionId,ModuleDescriptor> */revisions = new HashMap();
+
+    /**
+     * ModuleRevisionId for which loading was not possible, with corresponding error message.
+     */
+    private Map/* <ModuleRevisionId,String> */errors = new HashMap();
+
+    /**
+     * List of ModuleRevisionId per ModuleId.
+     */
+    private Map/* <ModuleId,Collection<ModuleRevisionId>> */modules = new HashMap();
+
+    // /////////////////////////////////////////
+    // state loaded on #analyze()
+    // /////////////////////////////////////////
+
+    /**
+     * True when the repository has been analyzed, false otherwise
+     */
+    private boolean analyzed;
+
+    /**
+     * Cache from requested module revision id to actual module revision id.
+     */
+    private Map/* <ModuleRevisionId,ModuleRevisionId> */cache = new HashMap();
+
+    /**
+     * list of dependers per ModuleRevisionId.
+     */
+    private Map/* <ModuleRevisionId,List<ModuleRevisionId>> */dependers = new HashMap();
+
+    // /////////////////////////////////////////
+    // dependencies
+    // /////////////////////////////////////////
+    private SearchEngine searchEngine;
+
+    private ResolveEngine resolveEngine;
+
+    private RepositoryManagementEngineSettings settings;
+
+    public RepositoryManagementEngine(RepositoryManagementEngineSettings settings,
+            SearchEngine searchEngine, ResolveEngine resolveEngine) {
+        this.settings = settings;
+        this.searchEngine = searchEngine;
+        this.resolveEngine = resolveEngine;
+    }
+
+    /**
+     * Loads data from the repository.
+     * <p>
+     * This method usually takes a long time to proceed. It should never be called from event
+     * dispatch thread in a GUI.
+     * </p>
+     */
+    public void load() {
+        long startingMemoryUse = 0;
+        if (settings.dumpMemoryUsage()) {
+            startingMemoryUse = MemoryUtil.getUsedMemory();
+        }
+        long startTime = System.currentTimeMillis();
+        Message.rawinfo("searching modules... ");
+        ModuleRevisionId[] mrids = searchModules();
+        Message.info("loading repository metadata...");
+        for (int i = 0; i < mrids.length; i++) {
+            try {
+                loadModuleRevision(mrids[i]);
+            } catch (Exception e) {
+                Message.debug(e);
+                errors.put(mrids[i], e.getMessage());
+            }
+        }
+        long endTime = System.currentTimeMillis();
+        Message.info("\nrepository loaded: "
+                + modules.size()
+                + " modules; "
+                + revisions.size()
+                + " revisions; "
+                + (settings.dumpMemoryUsage() ? (MemoryUtil.getUsedMemory() - startingMemoryUse)
+                        / KILO + "kB; " : "") + (endTime - startTime) / THOUSAND + "s");
+        loaded = true;
+    }
+
+    /**
+     * Analyze data in the repository.
+     * <p>
+     * This method may take a long time to proceed. It should never be called from event dispatch
+     * thread in a GUI.
+     * </p>
+     * 
+     * @throws IllegalStateException
+     *             if the repository has not been loaded yet
+     * @see #load()
+     */
+    public void analyze() {
+        ensureLoaded();
+        Message.info("\nanalyzing dependencies...");
+        for (Iterator iterator = revisions.values().iterator(); iterator.hasNext();) {
+            ModuleDescriptor md = (ModuleDescriptor) iterator.next();
+            DependencyDescriptor[] dds = md.getDependencies();
+            for (int i = 0; i < dds.length; i++) {
+                ModuleRevisionId dep = getDependency(dds[i]);
+                if (dep == null) {
+                    Message.warn("inconsistent repository: declared dependency not found: "
+                            + dds[i]);
+                } else {
+                    getDependers(dep).add(md.getModuleRevisionId());
+                }
+            }
+            Message.progress();
+        }
+        analyzed = true;
+    }
+
+    /**
+     * Returns the number of Module Revision in the repository.
+     * 
+     * @return the number of module revisions in the repository.
+     * @throws IllegalStateException
+     *             if the repository has not been loaded yet
+     * @see #load()
+     */
+    public int getRevisionsNumber() {
+        ensureLoaded();
+        return revisions.size();
+    }
+
+    /**
+     * Returns the number of ModuleId in the repository.
+     * 
+     * @return the number of ModuleId in the repository.
+     * @throws IllegalStateException
+     *             if the repository has not been loaded yet
+     * @see #load()
+     */
+    public int getModuleIdsNumber() {
+        ensureLoaded();
+        return modules.size();
+    }
+
+    /**
+     * Returns Module Revisions which have no dependers.
+     * 
+     * @return a Collection of the {@link ModuleRevisionId} of module revisions which have no
+     *         dependers in the repository.
+     * @throws IllegalStateException
+     *             if the repository has not been analyzed yet
+     * @see #analyze()
+     */
+    public Collection getOrphans() {
+        ensureAnalyzed();
+        Collection orphans = new HashSet(revisions.keySet());
+        orphans.removeAll(dependers.keySet());
+        return orphans;
+    }
+
+    private ModuleRevisionId[] searchModules() {
+        ModuleRevisionId[] mrids = searchEngine.listModules(ModuleRevisionId.newInstance(
+            PatternMatcher.ANY_EXPRESSION, PatternMatcher.ANY_EXPRESSION,
+            PatternMatcher.ANY_EXPRESSION, PatternMatcher.ANY_EXPRESSION),
+            RegexpPatternMatcher.INSTANCE);
+        return mrids;
+    }
+
+    private ModuleRevisionId getDependency(DependencyDescriptor dd) {
+        ModuleRevisionId askedMrid = dd.getDependencyRevisionId();
+        VersionMatcher vmatcher = settings.getVersionMatcher();
+        if (vmatcher.isDynamic(askedMrid)) {
+            ModuleRevisionId mrid = (ModuleRevisionId) cache.get(askedMrid);
+            if (mrid == null) {
+                Collection revs = getAllRevisions(askedMrid);
+                for (Iterator iterator = revs.iterator(); iterator.hasNext();) {
+                    ModuleDescriptor md = (ModuleDescriptor) iterator.next();
+                    if (vmatcher.needModuleDescriptor(askedMrid, md.getResolvedModuleRevisionId())) {
+                        if (vmatcher.accept(askedMrid, md)) {
+                            mrid = md.getResolvedModuleRevisionId();
+                            break;
+                        }
+                    } else {
+                        if (vmatcher.accept(askedMrid, md.getResolvedModuleRevisionId())) {
+                            mrid = md.getResolvedModuleRevisionId();
+                            break;
+                        }
+                    }
+                }
+                if (mrid == null) {
+                    return null;
+                } else {
+                    cache.put(askedMrid, mrid);
+                }
+            }
+            return mrid;
+        } else {
+            return askedMrid;
+        }
+    }
+
+    private Collection getDependers(ModuleRevisionId id) {
+        Collection depders = (Collection) dependers.get(id);
+        if (depders == null) {
+            depders = new ArrayList();
+            dependers.put(id, depders);
+        }
+        return depders;
+    }
+
+    private void loadModuleRevision(ModuleRevisionId mrid) throws Exception {
+        ResolvedModuleRevision module = settings.getResolver(mrid).getDependency(
+            new DefaultDependencyDescriptor(mrid, false), newResolveData());
+        if (module == null) {
+            Message.warn("module not found while listed: " + mrid);
+        } else {
+            revisions.put(module.getId(), module.getDescriptor());
+            getAllRevisions(module.getId()).add(module.getDescriptor());
+        }
+        Message.progress();
+    }
+
+    private Collection getAllRevisions(ModuleRevisionId id) {
+        Collection revisions = (Collection) modules.get(id.getModuleId());
+        if (revisions == null) {
+            revisions = new TreeSet(new Comparator() {
+                public int compare(Object o1, Object o2) {
+                    ModuleDescriptor md1 = (ModuleDescriptor) o1;
+                    ModuleDescriptor md2 = (ModuleDescriptor) o2;
+                    // we use reverse order compared to latest revision, to have latest revision
+                    // first
+                    return settings.getDefaultLatestStrategy().sort(new ArtifactInfo[] {md1, md2})
+                            .get(0).equals(md1) ? 1 : -1;
+                }
+            });
+            modules.put(id.getModuleId(), revisions);
+        }
+        return revisions;
+    }
+
+    private ResolveData newResolveData() {
+        return new ResolveData(resolveEngine, new ResolveOptions());
+    }
+
+    private void ensureAnalyzed() {
+        if (!analyzed) {
+            throw new IllegalStateException(
+                    "repository must have been analyzed to perform this method");
+        }
+    }
+
+    private void ensureLoaded() {
+        if (!loaded) {
+            throw new IllegalStateException("repository must have be loaded to perform this method");
+        }
+    }
+}
diff --git a/src/java/org/apache/ivy/core/repository/RepositoryManagementEngineSettings.java b/src/java/org/apache/ivy/core/repository/RepositoryManagementEngineSettings.java
new file mode 100644
index 0000000..ff946f6
--- /dev/null
+++ b/src/java/org/apache/ivy/core/repository/RepositoryManagementEngineSettings.java
@@ -0,0 +1,24 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.repository;
+
+import org.apache.ivy.core.resolve.ResolveEngineSettings;
+
+public interface RepositoryManagementEngineSettings extends ResolveEngineSettings {
+    public boolean dumpMemoryUsage();
+}
diff --git a/src/java/org/apache/ivy/core/resolve/DownloadOptions.java b/src/java/org/apache/ivy/core/resolve/DownloadOptions.java
new file mode 100644
index 0000000..e3f8555
--- /dev/null
+++ b/src/java/org/apache/ivy/core/resolve/DownloadOptions.java
@@ -0,0 +1,27 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.resolve;
+
+import org.apache.ivy.core.LogOptions;
+
+public class DownloadOptions extends LogOptions {
+
+    public DownloadOptions() {
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/resolve/IvyNode.java b/src/java/org/apache/ivy/core/resolve/IvyNode.java
new file mode 100644
index 0000000..dec51d5
--- /dev/null
+++ b/src/java/org/apache/ivy/core/resolve/IvyNode.java
@@ -0,0 +1,1344 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.resolve;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.ivy.core.IvyContext;
+import org.apache.ivy.core.LogOptions;
+import org.apache.ivy.core.event.resolve.EndResolveDependencyEvent;
+import org.apache.ivy.core.event.resolve.StartResolveDependencyEvent;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.Configuration;
+import org.apache.ivy.core.module.descriptor.DefaultArtifact;
+import org.apache.ivy.core.module.descriptor.DependencyArtifactDescriptor;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.IncludeRule;
+import org.apache.ivy.core.module.descriptor.MDArtifact;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+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.core.resolve.IvyNodeCallers.Caller;
+import org.apache.ivy.core.resolve.IvyNodeEviction.EvictionData;
+import org.apache.ivy.plugins.conflict.ConflictManager;
+import org.apache.ivy.plugins.conflict.LatestCompatibleConflictManager;
+import org.apache.ivy.plugins.matcher.MatcherHelper;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.apache.ivy.util.Message;
+import org.apache.ivy.util.StringUtils;
+import org.apache.ivy.util.filter.Filter;
+import org.apache.ivy.util.filter.FilterHelper;
+
+public class IvyNode implements Comparable {
+    private static final Pattern FALLBACK_CONF_PATTERN = Pattern.compile("(.+)\\((.*)\\)");
+
+    // //////// CONTEXT
+    private ResolveData data;
+
+    private ResolveEngineSettings settings;
+
+    // //////// DELEGATES
+    private IvyNodeCallers callers;
+
+    private IvyNodeEviction eviction;
+
+    // //////// MAIN DATA
+
+    private IvyNode root;
+
+    // id as requested, i.e. may be with latest rev
+    private ModuleRevisionId id;
+
+    // set only when node has been built or updated from a DependencyDescriptor
+    // Map(IvyNode parent -> DependencyDescriptor)
+    private Map dds = new HashMap();
+
+    // Set when data has been loaded only, or when constructed from a module descriptor
+    private ModuleDescriptor md;
+
+    private ResolvedModuleRevision module;
+
+    // //////// LOADING METADATA
+    private Exception problem = null;
+
+    private boolean downloaded = false;
+
+    private boolean searched = false;
+
+    private Collection confsToFetch = new HashSet();
+
+    private Collection fetchedConfigurations = new HashSet();
+
+    private Collection loadedRootModuleConfs = new HashSet();
+
+    // //////// USAGE DATA
+
+    private IvyNodeUsage usage = new IvyNodeUsage(this);
+
+    // usage information merged from evicted nodes this node is "replacing"
+    private Map/* <ModuleRevisionId, IvyNodeUsage> */mergedUsages = new LinkedHashMap();
+
+    public IvyNode(ResolveData data, IvyNode parent, DependencyDescriptor dd) {
+        id = dd.getDependencyRevisionId();
+        dds.put(parent, dd);
+        root = parent.getRoot();
+        init(data);
+    }
+
+    public IvyNode(ResolveData data, ModuleDescriptor md) {
+        id = md.getModuleRevisionId();
+        this.md = md;
+        root = this;
+        init(data);
+    }
+
+    private void init(ResolveData data) {
+        this.data = data;
+        settings = data.getSettings();
+        eviction = new IvyNodeEviction(this);
+        callers = new IvyNodeCallers(this);
+    }
+
+    /**
+     * After the call node may be discarded. To avoid using discarded node, make sure to get the
+     * real node after the call IvyNode node = ... node.loadData(); node = node.getRealNode(); ...
+     */
+    public boolean loadData(String rootModuleConf, IvyNode parent, String parentConf, String conf,
+            boolean shouldBePublic, IvyNodeUsage usage) {
+        Message.debug("loadData of " + this.toString() + " of rootConf=" + rootModuleConf);
+        if (!isRoot() && (data.getReport() != null)) {
+            data.getReport().addDependency(this);
+        }
+
+        boolean loaded = false;
+        if (hasProblem()) {
+            Message.debug("Node has problem.  Skip loading");
+        } else if (isEvicted(rootModuleConf)) {
+            Message.debug(rootModuleConf + " is evicted.  Skip loading");
+        } else if (!hasConfigurationsToLoad() && isRootModuleConfLoaded(rootModuleConf)) {
+            Message.debug(rootModuleConf + " is loaded and no conf to load.  Skip loading");
+        } else {
+            markRootModuleConfLoaded(rootModuleConf);
+            if (md == null) {
+                DependencyResolver resolver = data.getSettings().getResolver(getId());
+                if (resolver == null) {
+                    Message.error("no resolver found for " + getModuleId()
+                            + ": check your configuration");
+                    problem = new RuntimeException("no resolver found for " + getModuleId()
+                            + ": check your configuration");
+                    return false;
+                }
+                try {
+                    Message.debug("\tusing " + resolver + " to resolve " + getId());
+                    DependencyDescriptor dependencyDescriptor = getDependencyDescriptor(parent);
+                    long start = System.currentTimeMillis();
+                    ModuleRevisionId requestedRevisionId = dependencyDescriptor
+                            .getDependencyRevisionId();
+                    data.getEventManager().fireIvyEvent(
+                        new StartResolveDependencyEvent(resolver, dependencyDescriptor,
+                                requestedRevisionId));
+                    module = resolver.getDependency(dependencyDescriptor, data);
+                    data.getEventManager().fireIvyEvent(
+                        new EndResolveDependencyEvent(resolver, dependencyDescriptor,
+                                requestedRevisionId, module, System.currentTimeMillis() - start));
+
+                    if (module != null) {
+                        module.getResolver()
+                                .getRepositoryCacheManager()
+                                .saveResolvers(module.getDescriptor(),
+                                    module.getResolver().getName(),
+                                    module.getArtifactResolver().getName());
+                        if (settings.logModuleWhenFound()
+                                && LogOptions.LOG_DEFAULT.equals(getData().getOptions().getLog())) {
+                            Message.info("\tfound " + module.getId() + " in "
+                                    + module.getResolver().getName());
+                        } else {
+                            Message.verbose("\tfound " + module.getId() + " in "
+                                    + module.getResolver().getName());
+                        }
+
+                        // IVY-56: check if revision has actually been resolved
+                        if (settings.getVersionMatcher().isDynamic(getId())
+                                && settings.getVersionMatcher().isDynamic(module.getId())) {
+                            Message.error("impossible to resolve dynamic revision for " + getId()
+                                    + ": check your configuration and "
+                                    + "make sure revision is part of your pattern");
+                            problem = new RuntimeException("impossible to resolve dynamic revision");
+                            return false;
+                        }
+                        if (!getId().equals(module.getId())) {
+                            IvyNode resolved = data.getNode(module.getId());
+                            if (resolved != null) {
+                                // found revision has already been resolved
+                                // => update it and discard this node
+                                md = module.getDescriptor(); // needed for handleConfiguration
+                                if (!handleConfiguration(loaded, rootModuleConf, parent,
+                                    parentConf, conf, shouldBePublic, usage)) {
+                                    return false;
+                                }
+
+                                moveToRealNode(rootModuleConf, parent, parentConf, conf,
+                                    shouldBePublic, resolved);
+
+                                return true;
+                            }
+                            String log = "\t[" + module.getId().getRevision() + "] " + getId();
+                            if (!settings.getVersionMatcher().isDynamic(getId())) {
+                                log += " (forced)";
+                            }
+                            if (settings.logResolvedRevision()
+                                    && LogOptions.LOG_DEFAULT.equals(getData().getOptions()
+                                            .getLog())) {
+                                Message.info(log);
+                            } else {
+                                Message.verbose(log);
+                            }
+                        }
+                        downloaded = module.getReport().isDownloaded();
+                        searched = module.getReport().isSearched();
+                        loaded = true;
+
+                        md = module.getDescriptor();
+                        confsToFetch.remove("*");
+                        updateConfsToFetch(Arrays
+                                .asList(resolveSpecialConfigurations(getRequiredConfigurations(
+                                    parent, parentConf))));
+                    } else {
+                        Message.warn("\tmodule not found: " + getId());
+                        resolver.reportFailure();
+                        problem = new RuntimeException("not found");
+                        return false;
+                    }
+                } catch (ResolveProcessException e) {
+                    throw e;
+                } catch (Exception e) {
+                    problem = e;
+                    Message.debug("Unexpected error: " + problem.getMessage(), problem);
+                    return false;
+                }
+            } else {
+                loaded = true;
+            }
+        }
+        handleConfiguration(loaded, rootModuleConf, parent, parentConf, conf, shouldBePublic, usage);
+        if (hasProblem()) {
+            Message.debug("problem : " + problem.getMessage());
+            return false;
+        } else {
+            DependencyDescriptor dd = getDependencyDescriptor(parent);
+            if (dd != null) {
+                usage.addUsage(rootModuleConf, dd, parentConf);
+            }
+            return loaded;
+        }
+    }
+
+    private void moveToRealNode(String rootModuleConf, IvyNode parent, String parentConf,
+            String conf, boolean shouldBePublic, IvyNode resolved) {
+        if (resolved.md == null) {
+            resolved.md = md;
+        }
+        if (resolved.module == null) {
+            resolved.module = module;
+        }
+        resolved.downloaded |= module.getReport().isDownloaded();
+        resolved.searched |= module.getReport().isSearched();
+        resolved.dds.putAll(dds);
+        resolved.updateDataFrom(this, rootModuleConf, true);
+        resolved.loadData(rootModuleConf, parent, parentConf, conf, shouldBePublic, usage);
+        resolved.usage.updateDataFrom(getAllUsages(), rootModuleConf);
+        usage = resolved.usage;
+
+        data.replaceNode(getId(), resolved, rootModuleConf); // this actually discards the node
+
+        if (settings.logResolvedRevision()
+                && LogOptions.LOG_DEFAULT.equals(getData().getOptions().getLog())) {
+            Message.info("\t[" + module.getId().getRevision() + "] " + getId());
+        } else {
+            Message.verbose("\t[" + module.getId().getRevision() + "] " + getId());
+        }
+    }
+
+    public Collection getDependencies(String rootModuleConf, String[] confs, String requestedConf) {
+        if (md == null) {
+            throw new IllegalStateException(
+                    "impossible to get dependencies when data has not been loaded");
+        }
+        if (Arrays.asList(confs).contains("*")) {
+            if (isRoot()) {
+                confs = md.getConfigurationsNames();
+            } else {
+                confs = md.getPublicConfigurationsNames();
+            }
+        }
+        Collection deps = new HashSet();
+        for (int i = 0; i < confs.length; i++) {
+            deps.addAll(getDependencies(rootModuleConf, confs[i], requestedConf));
+        }
+        return deps;
+    }
+
+    /**
+     * Load the dependencies of the current node
+     * <p>
+     * The resulting collection of nodes may have some configuration to load
+     * 
+     * @param rootModuleConf
+     *            the requested configuration of the root module
+     * @param conf
+     *            the configuration to load of this node
+     * @param requestedConf
+     *            the actual node conf requested, possibly extending the <code>conf</code> one.
+     * @return
+     */
+    public Collection/* <IvyNode> */getDependencies(String rootModuleConf, String conf,
+            String requestedConf) {
+        if (md == null) {
+            throw new IllegalStateException(
+                    "impossible to get dependencies when data has not been loaded");
+        }
+        DependencyDescriptor[] dds = md.getDependencies();
+        Map/* <ModuleRevisionId, IvyNode> */dependencies = new LinkedHashMap(); // it's important to
+                                                                                // respect order
+        for (int i = 0; i < dds.length; i++) {
+            DependencyDescriptor dd = data.mediate(dds[i]);
+            String[] dependencyConfigurations = dd.getDependencyConfigurations(conf, requestedConf);
+            if (dependencyConfigurations.length == 0) {
+                // no configuration of the dependency is required for current confs :
+                // it is exactly the same as if there was no dependency at all on it
+                continue;
+            }
+            ModuleRevisionId requestedDependencyRevisionId = dd.getDependencyRevisionId();
+            if (isDependencyModuleExcluded(dd, rootModuleConf, requestedDependencyRevisionId, conf)) {
+                // the whole module is excluded, it is considered as not being part of dependencies
+                // at all
+                Message.verbose("excluding " + dd + " in " + conf);
+                continue;
+            }
+
+            // check if not already loaded here
+            IvyNode depNode = (IvyNode) dependencies.get(requestedDependencyRevisionId);
+            if (depNode == null) {
+                // check if not already loaded during the resolve session
+                depNode = data.getNode(requestedDependencyRevisionId);
+            }
+
+            if (depNode == null) {
+                depNode = new IvyNode(data, this, dd);
+            } else {
+                depNode.addDependencyDescriptor(this, dd);
+                if (depNode.hasProblem()) {
+                    // dependency already tried to be resolved, but unsuccessfully
+                    // nothing special to do
+                }
+
+            }
+            String[] confsArray = depNode.resolveSpecialConfigurations(dependencyConfigurations);
+            Collection confs = Arrays.asList(confsArray);
+            depNode.updateConfsToFetch(confs);
+            depNode.addRootModuleConfigurations(depNode.usage, rootModuleConf, confsArray);
+            depNode.usage.setRequiredConfs(this, conf, confs);
+
+            depNode.addCaller(rootModuleConf, this, conf, requestedConf, dependencyConfigurations,
+                dd);
+            dependencies.put(requestedDependencyRevisionId, depNode);
+        }
+        return dependencies.values();
+    }
+
+    private void addDependencyDescriptor(IvyNode parent, DependencyDescriptor dd) {
+        dds.put(parent, dd);
+    }
+
+    public DependencyDescriptor getDependencyDescriptor(IvyNode parent) {
+        return (DependencyDescriptor) dds.get(parent);
+    }
+
+    private boolean isDependencyModuleExcluded(DependencyDescriptor dd, String rootModuleConf,
+            ModuleRevisionId dependencyRevisionId, String conf) {
+        Artifact a = DefaultArtifact.newIvyArtifact(dependencyRevisionId, null);
+        if (isRoot()) {
+            // no callers, but maybe some exclude
+            Boolean exclude = doesExclude(md, rootModuleConf, new String[] {rootModuleConf}, dd, a,
+                new Stack());
+            return exclude == null ? false : exclude.booleanValue();
+        }
+        return callers.doesCallersExclude(rootModuleConf, a);
+    }
+
+    Boolean doesExclude(ModuleDescriptor md, String rootModuleConf, String[] moduleConfs,
+            DependencyDescriptor dd, Artifact artifact, Stack callersStack) {
+        // artifact is excluded if it match any of the exclude pattern for this dependency...
+        if (dd != null) {
+            if (dd.doesExclude(moduleConfs, artifact.getId().getArtifactId())) {
+                return Boolean.TRUE;
+            }
+        }
+        if (md.doesExclude(moduleConfs, artifact.getId().getArtifactId())) {
+            return Boolean.TRUE;
+        }
+        // ... or if it is excluded by all its callers
+        IvyNode c = getData().getNode(md.getModuleRevisionId());
+        if (c != null) {
+            if (callersStack.contains(c.getId())) {
+                // a circular dependency, we cannot be conclusive here
+                return null;
+            }
+            return Boolean.valueOf(c.doesCallersExclude(rootModuleConf, artifact, callersStack));
+        } else {
+            return Boolean.FALSE;
+        }
+    }
+
+    public boolean hasConfigurationsToLoad() {
+        return !confsToFetch.isEmpty();
+    }
+
+    private boolean markRootModuleConfLoaded(String rootModuleConf) {
+        return loadedRootModuleConfs.add(rootModuleConf);
+    }
+
+    private boolean isRootModuleConfLoaded(String rootModuleConf) {
+        return loadedRootModuleConfs.contains(rootModuleConf);
+    }
+
+    private boolean handleConfiguration(boolean loaded, String rootModuleConf, IvyNode parent,
+            String parentConf, String conf, boolean shouldBePublic, IvyNodeUsage usage) {
+        if (md != null) {
+            String[] confs = getRealConfs(conf);
+            addRootModuleConfigurations(usage, rootModuleConf, confs);
+            for (int i = 0; i < confs.length; i++) {
+                Configuration c = md.getConfiguration(confs[i]);
+                if (c == null) {
+                    confsToFetch.remove(conf);
+                    if (isConfRequiredByMergedUsageOnly(rootModuleConf, conf)) {
+                        Message.verbose("configuration required by evicted revision is not available in "
+                                + "selected revision. skipping " + conf + " in " + this);
+                    } else if (!conf.equals(confs[i])) {
+                        problem = new RuntimeException("configuration not found in " + this + ": '"
+                                + conf + "'. Missing configuration: '" + confs[i]
+                                + "'. It was required from " + parent + " " + parentConf);
+                    } else {
+                        problem = new RuntimeException("configuration not found in " + this + ": '"
+                                + confs[i] + "'. It was required from " + parent + " " + parentConf);
+                    }
+                    return false;
+                } else if (shouldBePublic && !isRoot()
+                        && c.getVisibility() != Configuration.Visibility.PUBLIC) {
+                    confsToFetch.remove(conf);
+                    if (isConfRequiredByMergedUsageOnly(rootModuleConf, conf)) {
+                        Message.verbose("configuration required by evicted revision is not visible in "
+                                + "selected revision. skipping " + conf + " in " + this);
+                    } else {
+                        problem = new RuntimeException("configuration not public in " + this
+                                + ": '" + c + "'. It was required from " + parent + " "
+                                + parentConf);
+                    }
+                    return false;
+                }
+            }
+            if (loaded) {
+                fetchedConfigurations.add(conf);
+                confsToFetch.removeAll(Arrays.asList(confs));
+                confsToFetch.remove(conf);
+            }
+        }
+        return true;
+    }
+
+    private String getDefaultConf(String conf) {
+        Matcher m = FALLBACK_CONF_PATTERN.matcher(conf);
+        if (m.matches()) {
+            return m.group(2);
+        } else {
+            return conf;
+        }
+    }
+
+    private String getMainConf(String conf) {
+        Matcher m = FALLBACK_CONF_PATTERN.matcher(conf);
+        if (m.matches()) {
+            return m.group(1);
+        } else {
+            return null;
+        }
+    }
+
+    public void updateConfsToFetch(Collection confs) {
+        confsToFetch.addAll(confs);
+        confsToFetch.removeAll(fetchedConfigurations);
+    }
+
+    /**
+     * resolve the '*' special configurations if necessary and possible
+     */
+    private String[] resolveSpecialConfigurations(String[] dependencyConfigurations) {
+        if (dependencyConfigurations.length == 1 && dependencyConfigurations[0].startsWith("*")
+                && isLoaded()) {
+            String conf = dependencyConfigurations[0];
+            if ("*".equals(conf)) {
+                return getDescriptor().getPublicConfigurationsNames();
+            }
+            // there are exclusions in the configuration
+            List exclusions = Arrays.asList(conf.substring(2).split("\\!"));
+
+            List ret = new ArrayList(Arrays.asList(getDescriptor().getPublicConfigurationsNames()));
+            ret.removeAll(exclusions);
+
+            return (String[]) ret.toArray(new String[ret.size()]);
+        }
+        return dependencyConfigurations;
+    }
+
+    /**
+     * returns the required configurations from the given node
+     * 
+     * @param in
+     * @return
+     */
+    public String[] getRequiredConfigurations(IvyNode in, String inConf) {
+        Collection req = new LinkedHashSet();
+        addAllIfNotNull(req, usage.getRequiredConfigurations(in, inConf));
+        for (Iterator iterator = mergedUsages.values().iterator(); iterator.hasNext();) {
+            IvyNodeUsage usage = (IvyNodeUsage) iterator.next();
+            addAllIfNotNull(req, usage.getRequiredConfigurations(in, inConf));
+        }
+        return req == null ? new String[0] : (String[]) req.toArray(new String[req.size()]);
+    }
+
+    private void addAllIfNotNull(Collection into, Collection col) {
+        if (col != null) {
+            into.addAll(col);
+        }
+    }
+
+    /**
+     * returns all the current required configurations of the node
+     * 
+     * @return
+     */
+    public String[] getRequiredConfigurations() {
+        Collection required = new ArrayList(confsToFetch.size() + fetchedConfigurations.size());
+        required.addAll(fetchedConfigurations);
+        required.addAll(confsToFetch);
+        return (String[]) required.toArray(new String[required.size()]);
+    }
+
+    public Configuration getConfiguration(String conf) {
+        if (md == null) {
+            throw new IllegalStateException(
+                    "impossible to get configuration when data has not been loaded");
+        }
+        String defaultConf = getDefaultConf(conf);
+        conf = getMainConf(conf);
+        Configuration configuration = md.getConfiguration(conf);
+        if (configuration == null) {
+            configuration = md.getConfiguration(defaultConf);
+        }
+        return configuration;
+    }
+
+    /**
+     * Returns the configurations of the dependency required in a given root module configuration.
+     * 
+     * @param rootModuleConf
+     * @return
+     */
+    public String[] getConfigurations(String rootModuleConf) {
+        Set depConfs = new LinkedHashSet();
+        addAllIfNotNull(depConfs, usage.getConfigurations(rootModuleConf));
+        for (Iterator iterator = mergedUsages.values().iterator(); iterator.hasNext();) {
+            IvyNodeUsage usage = (IvyNodeUsage) iterator.next();
+            addAllIfNotNull(depConfs, usage.getConfigurations(rootModuleConf));
+        }
+        return (String[]) depConfs.toArray(new String[depConfs.size()]);
+    }
+
+    protected boolean isConfRequiredByMergedUsageOnly(String rootModuleConf, String conf) {
+        Set confs = usage.getConfigurations(rootModuleConf);
+        return confs == null || !confs.contains(conf);
+    }
+
+    // This is never called. Could we remove it?
+    public void discardConf(String rootModuleConf, String conf) {
+        Set depConfs = usage.addAndGetConfigurations(rootModuleConf);
+        if (md != null) {
+            // remove all given dependency configurations to the set + extended ones
+            Configuration c = md.getConfiguration(conf);
+            if (conf != null) {
+                String[] exts = c.getExtends();
+                for (int i = 0; i < exts.length; i++) {
+                    discardConf(rootModuleConf, exts[i]); // recursive remove of extended
+                    // configurations
+                }
+                depConfs.remove(c.getName());
+            } else {
+                Message.warn("unknown configuration in " + getId() + ": " + conf);
+            }
+        } else {
+            depConfs.remove(conf);
+        }
+    }
+
+    private void addRootModuleConfigurations(IvyNodeUsage usage, String rootModuleConf,
+            String[] dependencyConfs) {
+        Set depConfs = usage.addAndGetConfigurations(rootModuleConf);
+        if (md != null) {
+            // add all given dependency configurations to the set + extended ones
+            for (int i = 0; i < dependencyConfs.length; i++) {
+                depConfs.add(dependencyConfs[i]);
+                Configuration conf = md.getConfiguration(dependencyConfs[i]);
+                if (conf != null) {
+                    String[] exts = conf.getExtends();
+                    // recursive add of extended
+                    addRootModuleConfigurations(usage, rootModuleConf, exts);
+                }
+            }
+        } else {
+            for (int i = 0; i < dependencyConfs.length; i++) {
+                depConfs.add(dependencyConfs[i]);
+            }
+        }
+    }
+
+    /**
+     * Returns the root module configurations in which this dependency is required
+     * 
+     * @return
+     */
+    public String[] getRootModuleConfigurations() {
+        Set confs = getRootModuleConfigurationsSet();
+        return (String[]) confs.toArray(new String[confs.size()]);
+    }
+
+    /**
+     * Returns the root module configurations in which this dependency is required
+     * 
+     * @return
+     */
+    public Set getRootModuleConfigurationsSet() {
+        Set confs = new LinkedHashSet();
+        addAllIfNotNull(confs, usage.getRootModuleConfigurations());
+        for (Iterator iterator = mergedUsages.values().iterator(); iterator.hasNext();) {
+            IvyNodeUsage usage = (IvyNodeUsage) iterator.next();
+            addAllIfNotNull(confs, usage.getRootModuleConfigurations());
+        }
+        return confs;
+    }
+
+    public String[] getConfsToFetch() {
+        return (String[]) confsToFetch.toArray(new String[confsToFetch.size()]);
+    }
+
+    public String[] getRealConfs(String conf) {
+        if (md == null) {
+            return new String[] {conf};
+        }
+        String defaultConf = getDefaultConf(conf);
+        conf = getMainConf(conf);
+        if ((md.getConfiguration(conf) == null)
+                || Configuration.Visibility.PRIVATE.equals(md.getConfiguration(conf)
+                        .getVisibility())) {
+            if ("".equals(defaultConf)) {
+                return new String[0];
+            }
+            conf = defaultConf;
+        }
+        if (conf.startsWith("*")) {
+            return resolveSpecialConfigurations(new String[] {conf});
+        } else if (conf.indexOf(',') != -1) {
+            String[] confs = conf.split(",");
+            for (int i = 0; i < confs.length; i++) {
+                confs[i] = confs[i].trim();
+            }
+        }
+        return new String[] {conf};
+
+    }
+
+    /**
+     * Finds and returns a path in callers from the given module id to the current node
+     * 
+     * @param from
+     *            the module id to start the path from
+     * @return a collection representing the path, starting with the from node, followed by the list
+     *         of nodes being one path to the current node, excluded
+     */
+    private Collection findPath(ModuleId from) {
+        return findPath(from, this, new LinkedList());
+    }
+
+    private Collection findPath(ModuleId from, IvyNode node, List path) {
+        IvyNode parent = node.getDirectCallerFor(from);
+        if (parent == null) {
+            throw new IllegalArgumentException("no path from " + from + " to " + getId() + " found");
+        }
+        if (path.contains(parent)) {
+            path.add(0, parent);
+            Message.verbose("circular dependency found while looking for the path for another one: "
+                    + "was looking for " + from + " as a caller of " + path.get(path.size() - 1));
+            return path;
+        }
+        path.add(0, parent);
+        if (parent.getId().getModuleId().equals(from)) {
+            return path;
+        }
+        return findPath(from, parent, path);
+    }
+
+    /**
+     * Update data in this node from data of the given node, for the given root module
+     * configuration.
+     * 
+     * @param node
+     *            the source node from which data should be copied
+     * @param rootModuleConf
+     *            the root module configuration for which data should be updated
+     * @param real
+     *            true if the node to update from actually corresponds to the same real node
+     *            (usually updated because of dynamic revision resolution), false if it's not the
+     *            same real node (usually updated because of node eviction)
+     */
+    private void updateDataFrom(IvyNode node, String rootModuleConf, boolean real) {
+        // update callers
+        callers.updateFrom(node.callers, rootModuleConf, real);
+
+        if (real) {
+            usage.updateDataFrom(node.getAllUsages(), rootModuleConf);
+        } else {
+            // let's copy usage information for the given rootModuleConf, into a separate usage
+            // object to keep detailed data about where usage comes from
+            IvyNodeUsage mergedUsage = (IvyNodeUsage) mergedUsages.get(node.getId());
+            if (mergedUsage == null) {
+                mergedUsage = new IvyNodeUsage(node);
+                mergedUsages.put(node.getId(), mergedUsage);
+            }
+            mergedUsage.updateDataFrom(node.getAllUsages(), rootModuleConf);
+        }
+
+        // update confsToFetch
+        updateConfsToFetch(node.fetchedConfigurations);
+        updateConfsToFetch(node.confsToFetch);
+    }
+
+    private Collection/* <IvyNodeUsage> */getAllUsages() {
+        Collection usages = new ArrayList();
+        usages.add(usage);
+        usages.addAll(mergedUsages.values());
+        return usages;
+    }
+
+    /**
+     * Returns all the artifacts of this dependency required in all the root module configurations
+     * 
+     * @return
+     */
+    public Artifact[] getAllArtifacts() {
+        Set ret = new HashSet();
+
+        for (Iterator it = getRootModuleConfigurationsSet().iterator(); it.hasNext();) {
+            String rootModuleConf = (String) it.next();
+            ret.addAll(Arrays.asList(getArtifacts(rootModuleConf)));
+        }
+        return (Artifact[]) ret.toArray(new Artifact[ret.size()]);
+    }
+
+    /**
+     * Returns all the artifacts of this dependency required in the root module configurations in
+     * which the node is not evicted nor blacklisted
+     * 
+     * @param artifactFilter
+     * @return
+     */
+    public Artifact[] getSelectedArtifacts(Filter artifactFilter) {
+        Collection ret = new HashSet();
+        for (Iterator it = getRootModuleConfigurationsSet().iterator(); it.hasNext();) {
+            String rootModuleConf = (String) it.next();
+            if (!isEvicted(rootModuleConf) && !isBlacklisted(rootModuleConf)) {
+                ret.addAll(Arrays.asList(getArtifacts(rootModuleConf)));
+            }
+        }
+        ret = FilterHelper.filter(ret, artifactFilter);
+        return (Artifact[]) ret.toArray(new Artifact[ret.size()]);
+    }
+
+    /**
+     * Returns the artifacts of this dependency required in the configurations themselves required
+     * in the given root module configuration
+     * 
+     * @param rootModuleConf
+     * @return
+     */
+    public Artifact[] getArtifacts(String rootModuleConf) {
+        // first we look for the dependency configurations required
+        // in the given root module configuration
+        String[] confs = getConfigurations(rootModuleConf);
+        if (confs == null || confs.length == 0) {
+            // no configuration required => no artifact required
+            return new Artifact[0];
+        }
+        if (md == null) {
+            throw new IllegalStateException(
+                    "impossible to get artifacts when data has not been loaded. IvyNode = "
+                            + this.toString());
+        }
+
+        Set artifacts = new HashSet(); // the set we fill before returning
+
+        // we check if we have dependencyArtifacts includes description for this rootModuleConf
+        Set dependencyArtifacts = usage.getDependencyArtifactsSet(rootModuleConf);
+
+        if (md.isDefault() && dependencyArtifacts != null && !dependencyArtifacts.isEmpty()) {
+            addArtifactsFromOwnUsage(artifacts, dependencyArtifacts);
+            addArtifactsFromMergedUsage(rootModuleConf, artifacts);
+        } else {
+            Set includes = new LinkedHashSet();
+            addAllIfNotNull(includes, usage.getDependencyIncludesSet(rootModuleConf));
+            for (Iterator iterator = mergedUsages.values().iterator(); iterator.hasNext();) {
+                IvyNodeUsage usage = (IvyNodeUsage) iterator.next();
+                addAllIfNotNull(includes, usage.getDependencyIncludesSet(rootModuleConf));
+            }
+
+            if ((dependencyArtifacts == null || dependencyArtifacts.isEmpty())
+                    && (includes.isEmpty())) {
+                // no artifacts / includes: we get all artifacts as defined by the descriptor
+                for (int i = 0; i < confs.length; i++) {
+                    artifacts.addAll(Arrays.asList(md.getArtifacts(confs[i])));
+                }
+            } else {
+                // we have to get only artifacts listed as "includes"
+
+                // first we get all artifacts as defined by the module descriptor
+                // and classify them by artifact id
+                Map allArtifacts = new HashMap();
+                for (int i = 0; i < confs.length; i++) {
+                    Artifact[] arts = md.getArtifacts(confs[i]);
+                    for (int j = 0; j < arts.length; j++) {
+                        allArtifacts.put(arts[j].getId().getArtifactId(), arts[j]);
+                    }
+                }
+
+                // now we add caller defined ones
+                if (dependencyArtifacts != null) {
+                    addArtifactsFromOwnUsage(artifacts, dependencyArtifacts);
+                }
+                addArtifactsFromMergedUsage(rootModuleConf, artifacts);
+
+                // and now we filter according to include rules
+                for (Iterator it = includes.iterator(); it.hasNext();) {
+                    IncludeRule dad = (IncludeRule) it.next();
+                    Collection arts = findArtifactsMatching(dad, allArtifacts);
+                    if (arts.isEmpty()) {
+                        Message.error("a required artifact is not listed by module descriptor: "
+                                + dad.getId());
+                        // we remove it from required list to prevent message to be displayed more
+                        // than once
+                        it.remove();
+                    } else {
+                        Message.debug(this + " in " + rootModuleConf + ": including " + arts);
+                        artifacts.addAll(arts);
+                    }
+                }
+            }
+        }
+
+        // now excludes artifacts that aren't accepted by any caller
+        for (Iterator iter = artifacts.iterator(); iter.hasNext();) {
+            Artifact artifact = (Artifact) iter.next();
+            boolean excluded = callers.doesCallersExclude(rootModuleConf, artifact);
+            if (excluded) {
+                Message.debug(this + " in " + rootModuleConf + ": excluding " + artifact);
+                iter.remove();
+            }
+        }
+        return (Artifact[]) artifacts.toArray(new Artifact[artifacts.size()]);
+    }
+
+    private void addArtifactsFromOwnUsage(Set artifacts, Set dependencyArtifacts) {
+        for (Iterator it = dependencyArtifacts.iterator(); it.hasNext();) {
+            DependencyArtifactDescriptor dad = (DependencyArtifactDescriptor) it.next();
+            artifacts.add(new MDArtifact(md, dad.getName(), dad.getType(), dad.getExt(), dad
+                    .getUrl(), dad.getQualifiedExtraAttributes()));
+        }
+    }
+
+    private void addArtifactsFromMergedUsage(String rootModuleConf, Set artifacts) {
+        for (Iterator iterator = mergedUsages.values().iterator(); iterator.hasNext();) {
+            IvyNodeUsage usage = (IvyNodeUsage) iterator.next();
+            Set mergedDependencyArtifacts = usage.getDependencyArtifactsSet(rootModuleConf);
+            if (mergedDependencyArtifacts != null) {
+                for (Iterator it = mergedDependencyArtifacts.iterator(); it.hasNext();) {
+                    DependencyArtifactDescriptor dad = (DependencyArtifactDescriptor) it.next();
+                    Map extraAttributes = new HashMap(dad.getQualifiedExtraAttributes());
+                    MDArtifact artifact = new MDArtifact(md, dad.getName(), dad.getType(),
+                            dad.getExt(), dad.getUrl(), extraAttributes);
+
+                    if (!artifacts.contains(artifact)) {
+                        // this is later used to know that this is a merged artifact
+                        extraAttributes.put("ivy:merged", dad.getDependencyDescriptor()
+                                .getParentRevisionId() + " -> " + usage.getNode().getId());
+                        artifacts.add(artifact);
+                    }
+                }
+            }
+        }
+    }
+
+    private static Collection findArtifactsMatching(IncludeRule rule, Map allArtifacts) {
+        Collection ret = new ArrayList();
+        for (Iterator iter = allArtifacts.keySet().iterator(); iter.hasNext();) {
+            ArtifactId aid = (ArtifactId) iter.next();
+            if (MatcherHelper.matches(rule.getMatcher(), rule.getId(), aid)) {
+                ret.add(allArtifacts.get(aid));
+            }
+        }
+        return ret;
+    }
+
+    public boolean hasProblem() {
+        return problem != null;
+    }
+
+    public Exception getProblem() {
+        return problem;
+    }
+
+    public String getProblemMessage() {
+        return StringUtils.getErrorMessage(problem);
+    }
+
+    public boolean isDownloaded() {
+        return downloaded;
+    }
+
+    public boolean isSearched() {
+        return searched;
+    }
+
+    public boolean isLoaded() {
+        return md != null;
+    }
+
+    public boolean isFetched(String conf) {
+        return fetchedConfigurations.contains(conf);
+    }
+
+    public IvyNode findNode(ModuleRevisionId mrid) {
+        return data.getNode(mrid);
+    }
+
+    boolean isRoot() {
+        return root == this;
+    }
+
+    public IvyNode getRoot() {
+        return root;
+    }
+
+    public ConflictManager getConflictManager(ModuleId mid) {
+        if (md == null) {
+            throw new IllegalStateException(
+                    "impossible to get conflict manager when data has not been loaded. IvyNode = "
+                            + this.toString());
+        }
+        ConflictManager cm = md.getConflictManager(mid);
+        return cm == null ? settings.getConflictManager(mid) : cm;
+    }
+
+    public IvyNode getRealNode() {
+        IvyNode real = data.getNode(getId());
+        return real != null ? real : this;
+    }
+
+    public ModuleRevisionId getId() {
+        return id;
+    }
+
+    public ModuleId getModuleId() {
+        return id.getModuleId();
+    }
+
+    public ModuleDescriptor getDescriptor() {
+        return md;
+    }
+
+    public ResolveData getData() {
+        return data;
+    }
+
+    public ResolvedModuleRevision getModuleRevision() {
+        return module;
+    }
+
+    public long getPublication() {
+        if (module != null) {
+            return module.getPublicationDate().getTime();
+        }
+        return 0;
+    }
+
+    /**
+     * Returns the last modified timestamp of the module represented by this Node, or 0 if the last
+     * modified timestamp is currently unkwown (module not loaded)
+     * 
+     * @return the last modified timestamp of the module represented by this Node
+     */
+    public long getLastModified() {
+        if (md != null) {
+            return md.getLastModified();
+        }
+        return 0;
+    }
+
+    public ModuleRevisionId getResolvedId() {
+        if (md != null && md.getResolvedModuleRevisionId().getRevision() != null) {
+            return md.getResolvedModuleRevisionId();
+        } else if (module != null) {
+            return module.getId();
+        } else {
+            return getId();
+        }
+    }
+
+    /**
+     * Clean data related to one root module configuration only
+     */
+    public void clean() {
+        confsToFetch.clear();
+    }
+
+    // /////////////////////////////////////////////////////////////////////////////
+    // CALLERS MANAGEMENT
+    // /////////////////////////////////////////////////////////////////////////////
+
+    boolean canExclude(String rootModuleConf) {
+        Caller[] callers = getCallers(rootModuleConf);
+        for (int i = 0; i < callers.length; i++) {
+            if (callers[i].canExclude()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private IvyNode getDirectCallerFor(ModuleId from) {
+        return callers.getDirectCallerFor(from);
+    }
+
+    public Caller[] getCallers(String rootModuleConf) {
+        return callers.getCallers(rootModuleConf);
+    }
+
+    public Collection getAllCallersModuleIds() {
+        return callers.getAllCallersModuleIds();
+    }
+
+    public Caller[] getAllCallers() {
+        return callers.getAllCallers();
+    }
+
+    public Caller[] getAllRealCallers() {
+        return callers.getAllRealCallers();
+    }
+
+    public void addCaller(String rootModuleConf, IvyNode callerNode, String callerConf,
+            String requestedConf, String[] dependencyConfs, DependencyDescriptor dd) {
+        callers.addCaller(rootModuleConf, callerNode, callerConf, requestedConf, dependencyConfs,
+            dd);
+        boolean isCircular = callers.getAllCallersModuleIds().contains(getId().getModuleId());
+        if (isCircular) {
+            IvyContext.getContext().getCircularDependencyStrategy()
+                    .handleCircularDependency(toMrids(findPath(getId().getModuleId()), this));
+        }
+    }
+
+    public boolean doesCallersExclude(String rootModuleConf, Artifact artifact, Stack callersStack) {
+        return callers.doesCallersExclude(rootModuleConf, artifact, callersStack);
+    }
+
+    private ModuleRevisionId[] toMrids(Collection path, IvyNode depNode) {
+        ModuleRevisionId[] ret = new ModuleRevisionId[path.size() + 1];
+        int i = 0;
+        for (Iterator iter = path.iterator(); iter.hasNext(); i++) {
+            IvyNode node = (IvyNode) iter.next();
+            ret[i] = node.getId();
+        }
+        ret[ret.length - 1] = depNode.getId();
+        return ret;
+    }
+
+    // /////////////////////////////////////////////////////////////////////////////
+    // EVICTION MANAGEMENT
+    // /////////////////////////////////////////////////////////////////////////////
+
+    /** A copy of the set of resolved nodes (real nodes) */
+    public Set getResolvedNodes(ModuleId moduleId, String rootModuleConf) {
+        return eviction.getResolvedNodes(moduleId, rootModuleConf);
+    }
+
+    public Collection getResolvedRevisions(ModuleId moduleId, String rootModuleConf) {
+        return eviction.getResolvedRevisions(moduleId, rootModuleConf);
+    }
+
+    public void markEvicted(EvictionData evictionData) {
+        eviction.markEvicted(evictionData);
+        String rootModuleConf = evictionData.getRootModuleConf();
+
+        // bug 105: update selected data with evicted one
+        if (evictionData.getSelected() != null) {
+            for (Iterator iter = evictionData.getSelected().iterator(); iter.hasNext();) {
+                IvyNode selected = (IvyNode) iter.next();
+                selected.updateDataFrom(this, rootModuleConf, false);
+            }
+        }
+    }
+
+    public Collection getAllEvictingConflictManagers() {
+        return eviction.getAllEvictingConflictManagers();
+    }
+
+    public Collection getAllEvictingNodes() {
+        return eviction.getAllEvictingNodes();
+    }
+
+    public Collection/* <String> */getAllEvictingNodesDetails() {
+        return eviction.getAllEvictingNodesDetails();
+    }
+
+    public String[] getEvictedConfs() {
+        return eviction.getEvictedConfs();
+    }
+
+    public EvictionData getEvictedData(String rootModuleConf) {
+        return eviction.getEvictedData(rootModuleConf);
+    }
+
+    public Collection getEvictedNodes(ModuleId mid, String rootModuleConf) {
+        return eviction.getEvictedNodes(mid, rootModuleConf);
+    }
+
+    public Collection getEvictedRevisions(ModuleId mid, String rootModuleConf) {
+        return eviction.getEvictedRevisions(mid, rootModuleConf);
+    }
+
+    public EvictionData getEvictionDataInRoot(String rootModuleConf, IvyNode ancestor) {
+        return eviction.getEvictionDataInRoot(rootModuleConf, ancestor);
+    }
+
+    public boolean isCompletelyEvicted() {
+        return eviction.isCompletelyEvicted();
+    }
+
+    public boolean isEvicted(String rootModuleConf) {
+        return eviction.isEvicted(rootModuleConf);
+    }
+
+    public void markEvicted(String rootModuleConf, IvyNode node, ConflictManager conflictManager,
+            Collection resolved) {
+        EvictionData evictionData = new EvictionData(rootModuleConf, node, conflictManager,
+                resolved);
+        markEvicted(evictionData);
+    }
+
+    public void setEvictedNodes(ModuleId moduleId, String rootModuleConf, Collection evicted) {
+        eviction.setEvictedNodes(moduleId, rootModuleConf, evicted);
+    }
+
+    public void setResolvedNodes(ModuleId moduleId, String rootModuleConf, Collection resolved) {
+        eviction.setResolvedNodes(moduleId, rootModuleConf, resolved);
+    }
+
+    public String toString() {
+        return getResolvedId().toString();
+    }
+
+    public boolean equals(Object obj) {
+        if (!(obj instanceof IvyNode)) {
+            return false;
+        }
+        IvyNode node = (IvyNode) obj;
+        return node.getId().equals(getId());
+    }
+
+    public int compareTo(Object obj) {
+        IvyNode that = (IvyNode) obj;
+        return this.getModuleId().compareTo(that.getModuleId());
+    }
+
+    public int hashCode() {
+        return getId().hashCode();
+    }
+
+    /**
+     * Returns a collection of Nodes in conflict for which conflict has been detected but conflict
+     * resolution hasn't been done yet
+     * 
+     * @param rootModuleConf
+     * @param mid
+     *            the module id for which pending conflicts should be found
+     * @return a Collection of IvyNode in pending conflict
+     */
+    public Collection getPendingConflicts(String rootModuleConf, ModuleId mid) {
+        return eviction.getPendingConflicts(rootModuleConf, mid);
+    }
+
+    public void setPendingConflicts(ModuleId moduleId, String rootModuleConf, Collection conflicts) {
+        eviction.setPendingConflicts(moduleId, rootModuleConf, conflicts);
+    }
+
+    // /////////////////////////////////////////////////////////////////////////////
+    // BLACKLISTING MANAGEMENT
+    // /////////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Blacklists the current node, so that a new resolve process won't ever consider this node as
+     * available in the repository.
+     * <p>
+     * This is useful in combination with {@link RestartResolveProcess} for conflict manager
+     * implementation which use a best effort strategy to find compatible dependency set, like
+     * {@link LatestCompatibleConflictManager}
+     * </p>
+     * 
+     * @param rootModuleConf
+     *            the root module configuration in which the node should be blacklisted
+     */
+    public void blacklist(IvyNodeBlacklist bdata) {
+        if (data.getSettings().logResolvedRevision()) {
+            Message.info("BLACKLISTING " + bdata);
+        } else {
+            Message.verbose("BLACKLISTING " + bdata);
+        }
+
+        Stack callerStack = new Stack();
+        callerStack.push(this);
+        clearEvictionDataInAllCallers(bdata.getRootModuleConf(), callerStack);
+
+        usage.blacklist(bdata);
+        data.blacklist(this);
+    }
+
+    private void clearEvictionDataInAllCallers(String rootModuleConf,
+            Stack/* <IvyNode> */callerStack) {
+        IvyNode node = (IvyNode) callerStack.peek();
+        Caller[] callers = node.getCallers(rootModuleConf);
+        for (int i = 0; i < callers.length; i++) {
+            IvyNode callerNode = findNode(callers[i].getModuleRevisionId());
+            if (callerNode != null) {
+                callerNode.eviction = new IvyNodeEviction(callerNode);
+                if (!callerStack.contains(callerNode)) {
+                    callerStack.push(callerNode);
+                    clearEvictionDataInAllCallers(rootModuleConf, callerStack);
+                    callerStack.pop();
+                }
+            }
+        }
+    }
+
+    /**
+     * Indicates if this node has been blacklisted in the given root module conf.
+     * <p>
+     * A blacklisted node should be considered as if it doesn't even exist on the repository.
+     * </p>
+     * 
+     * @param rootModuleConf
+     *            the root module conf for which we'd like to know if the node is blacklisted
+     * 
+     * @return true if this node is blacklisted int he given root module conf, false otherwise
+     * @see #blacklist(String)
+     */
+    public boolean isBlacklisted(String rootModuleConf) {
+        return usage.isBlacklisted(rootModuleConf);
+    }
+
+    /**
+     * Indicates if this node has been blacklisted in all root module configurations.
+     * 
+     * @return true if this node is blacklisted in all root module configurations, false otherwise
+     * @see #blacklist(String)
+     */
+    public boolean isCompletelyBlacklisted() {
+        if (isRoot()) {
+            return false;
+        }
+        String[] rootModuleConfigurations = getRootModuleConfigurations();
+        for (int i = 0; i < rootModuleConfigurations.length; i++) {
+            if (!isBlacklisted(rootModuleConfigurations[i])) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Returns the blacklist data of this node in the given root module conf, or <code>null</code>
+     * if this node is not blacklisted in this root module conf.
+     * 
+     * @param rootModuleConf
+     *            the root module configuration to consider
+     * @return the blacklist data if any
+     */
+    public IvyNodeBlacklist getBlacklistData(String rootModuleConf) {
+        return usage.getBlacklistData(rootModuleConf);
+    }
+
+    public IvyNodeUsage getMainUsage() {
+        return usage;
+    }
+
+    /**
+     * Indicates if there is any of the merged usages of this node which has a depender with
+     * transitive dependency descriptor.
+     * <p>
+     * If at there is at least one usage from the merged usages for which there is a depender in the
+     * given root module conf which has a dependency descriptor with transitive == true, then it
+     * returns true. Otherwise it returns false.
+     * </p>
+     * 
+     * @param rootModuleConf
+     *            the root module configuration to consider
+     * @return true if there is any merged usage with transitive dd, false otherwise.
+     */
+    public boolean hasAnyMergedUsageWithTransitiveDependency(String rootModuleConf) {
+        if (mergedUsages == null) {
+            return false;
+        }
+        for (Iterator iterator = mergedUsages.values().iterator(); iterator.hasNext();) {
+            IvyNodeUsage usage = (IvyNodeUsage) iterator.next();
+            if (usage.hasTransitiveDepender(rootModuleConf)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/resolve/IvyNodeBlacklist.java b/src/java/org/apache/ivy/core/resolve/IvyNodeBlacklist.java
new file mode 100644
index 0000000..466d2b6
--- /dev/null
+++ b/src/java/org/apache/ivy/core/resolve/IvyNodeBlacklist.java
@@ -0,0 +1,68 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.resolve;
+
+/**
+ * Information about a blacklisted module, providing context information in which it has been
+ * blacklisted
+ */
+public class IvyNodeBlacklist {
+    private IvyNode conflictParent;
+
+    private IvyNode selectedNode;
+
+    private IvyNode evictedNode;
+
+    private IvyNode blacklistedNode;
+
+    private String rootModuleConf;
+
+    public IvyNodeBlacklist(IvyNode conflictParent, IvyNode selectedNode, IvyNode evictedNode,
+            IvyNode blacklistedNode, String rootModuleConf) {
+        this.conflictParent = conflictParent;
+        this.selectedNode = selectedNode;
+        this.evictedNode = evictedNode;
+        this.blacklistedNode = blacklistedNode;
+        this.rootModuleConf = rootModuleConf;
+    }
+
+    public IvyNode getConflictParent() {
+        return conflictParent;
+    }
+
+    public IvyNode getSelectedNode() {
+        return selectedNode;
+    }
+
+    public IvyNode getEvictedNode() {
+        return evictedNode;
+    }
+
+    public IvyNode getBlacklistedNode() {
+        return blacklistedNode;
+    }
+
+    public String getRootModuleConf() {
+        return rootModuleConf;
+    }
+
+    public String toString() {
+        return "[" + blacklistedNode + " blacklisted to evict " + evictedNode + " in favor of "
+                + selectedNode + " in " + conflictParent + " for " + rootModuleConf + "]";
+    }
+}
diff --git a/src/java/org/apache/ivy/core/resolve/IvyNodeCallers.java b/src/java/org/apache/ivy/core/resolve/IvyNodeCallers.java
new file mode 100644
index 0000000..399a60a
--- /dev/null
+++ b/src/java/org/apache/ivy/core/resolve/IvyNodeCallers.java
@@ -0,0 +1,296 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.resolve;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.Configuration;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+
+public class IvyNodeCallers {
+    public static class Caller {
+        private ModuleDescriptor md;
+
+        private ModuleRevisionId mrid;
+
+        private Map confs = new HashMap(); // Map (String callerConf -> String[] dependencyConfs)
+
+        private DependencyDescriptor dd;
+
+        private boolean callerCanExclude;
+
+        private boolean real = true;
+
+        public Caller(ModuleDescriptor md, ModuleRevisionId mrid, DependencyDescriptor dd,
+                boolean callerCanExclude) {
+            this.md = md;
+            this.mrid = mrid;
+            this.dd = dd;
+            this.callerCanExclude = callerCanExclude;
+        }
+
+        public void addConfiguration(String callerConf, String[] dependencyConfs) {
+            updateConfs(callerConf, dependencyConfs);
+            Configuration conf = md.getConfiguration(callerConf);
+            if (conf != null) {
+                String[] confExtends = conf.getExtends();
+                if (confExtends != null) {
+                    for (int i = 0; i < confExtends.length; i++) {
+                        addConfiguration(confExtends[i], dependencyConfs);
+                    }
+                }
+            }
+        }
+
+        private void updateConfs(String callerConf, String[] dependencyConfs) {
+            String[] prevDepConfs = (String[]) confs.get(callerConf);
+            if (prevDepConfs != null) {
+                Set newDepConfs = new HashSet(Arrays.asList(prevDepConfs));
+                newDepConfs.addAll(Arrays.asList(dependencyConfs));
+                confs.put(callerConf, newDepConfs.toArray(new String[newDepConfs.size()]));
+            } else {
+                confs.put(callerConf, dependencyConfs);
+            }
+        }
+
+        public String[] getCallerConfigurations() {
+            return (String[]) confs.keySet().toArray(new String[confs.keySet().size()]);
+        }
+
+        public ModuleRevisionId getModuleRevisionId() {
+            return mrid;
+        }
+
+        public boolean equals(Object obj) {
+            if (!(obj instanceof Caller)) {
+                return false;
+            }
+            Caller other = (Caller) obj;
+            return other.confs.equals(confs) && mrid.equals(other.mrid);
+        }
+
+        public int hashCode() {
+            // CheckStyle:MagicNumber| OFF
+            int hash = 31;
+            hash = hash * 13 + confs.hashCode();
+            hash = hash * 13 + mrid.hashCode();
+            // CheckStyle:MagicNumber| ON
+            return hash;
+        }
+
+        public String toString() {
+            return mrid.toString();
+        }
+
+        public ModuleRevisionId getAskedDependencyId(ResolveData resolveData) {
+            return dd.getDependencyRevisionId();
+        }
+
+        public ModuleDescriptor getModuleDescriptor() {
+            return md;
+        }
+
+        public boolean canExclude() {
+            return callerCanExclude || md.canExclude() || dd.canExclude();
+        }
+
+        public DependencyDescriptor getDependencyDescriptor() {
+            return dd;
+        }
+
+        public void setRealCaller(boolean b) {
+            this.real = b;
+        }
+
+        public boolean isRealCaller() {
+            return real;
+        }
+    }
+
+    // Map (String rootModuleConf -> Map (ModuleRevisionId -> Caller)): key in second map is used to
+    // easily get a caller by its mrid
+    private Map callersByRootConf = new HashMap();
+
+    // this map contains all the module ids calling this one (including transitively) as keys.
+    // the mapped nodes (values) correspond to a direct caller from which the transitive caller
+    // comes
+
+    private Map allCallers = new HashMap(); // Map (ModuleId -> IvyNode)
+
+    private IvyNode node;
+
+    public IvyNodeCallers(IvyNode node) {
+        this.node = node;
+    }
+
+    /**
+     * @param rootModuleConf
+     * @param mrid
+     * @param callerConf
+     * @param dependencyConfs
+     *            '*' must have been resolved
+     * @param dd
+     *            the dependency revision id asked by the caller
+     */
+    public void addCaller(String rootModuleConf, IvyNode callerNode, String callerConf,
+            String requestedConf, String[] dependencyConfs, DependencyDescriptor dd) {
+        ModuleDescriptor md = callerNode.getDescriptor();
+        ModuleRevisionId mrid = callerNode.getResolvedId();
+        if (mrid.getModuleId().equals(node.getId().getModuleId())) {
+            throw new IllegalArgumentException("a module is not authorized to depend on itself: "
+                    + node.getId());
+        }
+        Map callers = (Map) callersByRootConf.get(rootModuleConf);
+        if (callers == null) {
+            callers = new HashMap();
+            callersByRootConf.put(rootModuleConf, callers);
+        }
+        Caller caller = (Caller) callers.get(mrid);
+        if (caller == null) {
+            caller = new Caller(md, mrid, dd, callerNode.canExclude(rootModuleConf));
+            callers.put(mrid, caller);
+        }
+        caller.addConfiguration(requestedConf, dependencyConfs);
+
+        IvyNode parent = callerNode.getRealNode();
+        for (Iterator iter = parent.getAllCallersModuleIds().iterator(); iter.hasNext();) {
+            ModuleId mid = (ModuleId) iter.next();
+            allCallers.put(mid, parent);
+        }
+        allCallers.put(mrid.getModuleId(), callerNode);
+    }
+
+    void removeCaller(String rootModuleConf, ModuleRevisionId callerMrid) {
+        allCallers.remove(callerMrid.getModuleId());
+        Map callers = (Map) callersByRootConf.get(rootModuleConf);
+        if (callers != null) {
+            callers.remove(callerMrid);
+        }
+    }
+
+    public Caller[] getCallers(String rootModuleConf) {
+        Map callers = (Map) callersByRootConf.get(rootModuleConf);
+        if (callers == null) {
+            return new Caller[0];
+        }
+        return (Caller[]) callers.values().toArray(new Caller[callers.values().size()]);
+    }
+
+    public Caller[] getAllCallers() {
+        Set all = new HashSet();
+        for (Iterator iter = callersByRootConf.values().iterator(); iter.hasNext();) {
+            Map callers = (Map) iter.next();
+            all.addAll(callers.values());
+        }
+        return (Caller[]) all.toArray(new Caller[all.size()]);
+    }
+
+    public Caller[] getAllRealCallers() {
+        Set all = new HashSet();
+        for (Iterator iter = callersByRootConf.values().iterator(); iter.hasNext();) {
+            Map callers = (Map) iter.next();
+            for (Iterator iterator = callers.values().iterator(); iterator.hasNext();) {
+                Caller c = (Caller) iterator.next();
+                if (c.isRealCaller()) {
+                    all.add(c);
+                }
+            }
+        }
+        return (Caller[]) all.toArray(new Caller[all.size()]);
+    }
+
+    public Collection getAllCallersModuleIds() {
+        return allCallers.keySet();
+    }
+
+    void updateFrom(IvyNodeCallers callers, String rootModuleConf, boolean real) {
+        Map nodecallers = (Map) callers.callersByRootConf.get(rootModuleConf);
+        if (nodecallers != null) {
+            Map thiscallers = (Map) callersByRootConf.get(rootModuleConf);
+            if (thiscallers == null) {
+                thiscallers = new HashMap();
+                callersByRootConf.put(rootModuleConf, thiscallers);
+            }
+            for (Iterator iter = nodecallers.values().iterator(); iter.hasNext();) {
+                Caller caller = (Caller) iter.next();
+                if (!thiscallers.containsKey(caller.getModuleRevisionId())) {
+                    if (!real) {
+                        caller.setRealCaller(false);
+                    }
+                    thiscallers.put(caller.getModuleRevisionId(), caller);
+                }
+            }
+        }
+    }
+
+    public IvyNode getDirectCallerFor(ModuleId from) {
+        return (IvyNode) allCallers.get(from);
+    }
+
+    /**
+     * Returns true if ALL callers exclude the given artifact in the given root module conf
+     * 
+     * @param rootModuleConf
+     * @param artifact
+     * @return
+     */
+    boolean doesCallersExclude(String rootModuleConf, Artifact artifact) {
+        return doesCallersExclude(rootModuleConf, artifact, new Stack());
+    }
+
+    boolean doesCallersExclude(String rootModuleConf, Artifact artifact, Stack callersStack) {
+        callersStack.push(node.getId());
+        try {
+            Caller[] callers = getCallers(rootModuleConf);
+            if (callers.length == 0) {
+                return false;
+            }
+            boolean allUnconclusive = true;
+            for (int i = 0; i < callers.length; i++) {
+                if (!callers[i].canExclude()) {
+                    return false;
+                }
+                ModuleDescriptor md = callers[i].getModuleDescriptor();
+                Boolean doesExclude = node.doesExclude(md, rootModuleConf,
+                    callers[i].getCallerConfigurations(), callers[i].getDependencyDescriptor(),
+                    artifact, callersStack);
+                if (doesExclude != null) {
+                    if (!doesExclude.booleanValue()) {
+                        return false;
+                    }
+                    allUnconclusive = false;
+                }
+            }
+            return allUnconclusive ? false : true;
+        } finally {
+            callersStack.pop();
+        }
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/resolve/IvyNodeEviction.java b/src/java/org/apache/ivy/core/resolve/IvyNodeEviction.java
new file mode 100644
index 0000000..5c757ee
--- /dev/null
+++ b/src/java/org/apache/ivy/core/resolve/IvyNodeEviction.java
@@ -0,0 +1,439 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.resolve;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.plugins.conflict.ConflictManager;
+
+public class IvyNodeEviction {
+    /**
+     * This class contains data about the eviction of an {@link IvyNode}.
+     */
+    public static class EvictionData {
+        /**
+         * Can be null in case of transitive eviction.
+         */
+        private IvyNode parent;
+
+        /**
+         * Can be null in case of transitive eviction.
+         */
+        private ConflictManager conflictManager;
+
+        /**
+         * Can be null in case of transitive eviction.
+         */
+        private Collection selected; // Collection(IvyNode)
+
+        private String rootModuleConf;
+
+        private String detail;
+
+        /**
+         * Creates a new object containing the eviction data of an {@link IvyNode}.
+         * 
+         * @param rootModuleConf
+         *            the root module configuration
+         * @param parent
+         *            the parent node (or <tt>null</tt> in case of transitive eviction)
+         * @param conflictManager
+         *            the conflict manager which evicted the node (or <tt>null</tt> in case of
+         *            transitive eviction)
+         * @param selected
+         *            a collection of {@link IvyNode}s which evict the evicted node (or
+         *            <tt>null</tt> in case of transitive eviction)
+         */
+        public EvictionData(String rootModuleConf, IvyNode parent, ConflictManager conflictManager,
+                Collection selected) {
+            this(rootModuleConf, parent, conflictManager, selected, null);
+        }
+
+        /**
+         * Creates a new object containing the eviction data of an {@link IvyNode}.
+         * 
+         * @param rootModuleConf
+         *            the root module configuration
+         * @param parent
+         *            the parent node (or <tt>null</tt> in case of transitive eviction)
+         * @param conflictManager
+         *            the conflict manager which evicted the node (or <tt>null</tt> in case of
+         *            transitive eviction)
+         * @param selected
+         *            a collection of {@link IvyNode}s which evict the evicted node (or
+         *            <tt>null</tt> in case of transitive eviction)
+         * @param detail
+         *            a String detailing the reason why the node was evicted
+         */
+        public EvictionData(String rootModuleConf, IvyNode parent, ConflictManager conflictManager,
+                Collection selected, String detail) {
+            this.rootModuleConf = rootModuleConf;
+            this.parent = parent;
+            this.conflictManager = conflictManager;
+            this.selected = selected;
+            this.detail = detail;
+        }
+
+        public String toString() {
+            if (selected != null) {
+                return selected + " in " + parent + (detail == null ? "" : " " + detail) + " ("
+                        + conflictManager + ") [" + rootModuleConf + "]";
+            } else {
+                return "transitively [" + rootModuleConf + "]";
+            }
+        }
+
+        public ConflictManager getConflictManager() {
+            return conflictManager;
+        }
+
+        public IvyNode getParent() {
+            return parent;
+        }
+
+        public Collection getSelected() {
+            return selected;
+        }
+
+        public String getRootModuleConf() {
+            return rootModuleConf;
+        }
+
+        public boolean isTransitivelyEvicted() {
+            return parent == null;
+        }
+
+        public String getDetail() {
+            return detail;
+        }
+    }
+
+    private static final class ModuleIdConf {
+        private ModuleId moduleId;
+
+        private String conf;
+
+        public ModuleIdConf(ModuleId mid, String conf) {
+            if (mid == null) {
+                throw new NullPointerException("mid cannot be null");
+            }
+            if (conf == null) {
+                throw new NullPointerException("conf cannot be null");
+            }
+            moduleId = mid;
+            this.conf = conf;
+        }
+
+        public final String getConf() {
+            return conf;
+        }
+
+        public final ModuleId getModuleId() {
+            return moduleId;
+        }
+
+        public boolean equals(Object obj) {
+            if (!(obj instanceof ModuleIdConf)) {
+                return false;
+            }
+            return getModuleId().equals(((ModuleIdConf) obj).getModuleId())
+                    && getConf().equals(((ModuleIdConf) obj).getConf());
+        }
+
+        public int hashCode() {
+            // CheckStyle:MagicNumber| OFF
+            int hash = 33;
+            hash += getModuleId().hashCode() * 17;
+            hash += getConf().hashCode() * 17;
+            // CheckStyle:MagicNumber| ON
+            return hash;
+        }
+    }
+
+    private IvyNode node;
+
+    private Map selectedDeps = new HashMap(); // Map (ModuleIdConf -> Set(Node)) // map indicating
+
+    // for each dependency which node has been selected
+
+    private Map pendingConflicts = new HashMap(); // Map (ModuleIdConf -> Set(Node)) // map
+
+    // indicating for each dependency which nodes
+    // are in pending conflict (conflict detected
+    // but not yet resolved)
+
+    private Map evictedDeps = new HashMap(); // Map (ModuleIdConf -> Set(Node)) // map indicating
+
+    // for each dependency which node has been evicted
+
+    private Map evictedRevs = new HashMap(); // Map (ModuleIdConf -> Set(ModuleRevisionId)) //
+
+    // map indicating for each dependency which revision
+    // has been evicted
+
+    private Map evicted = new HashMap(); // Map (root module conf -> EvictionData) // indicates
+
+    // if the node is evicted in each root module conf
+
+    public IvyNodeEviction(IvyNode node) {
+        if (node == null) {
+            throw new NullPointerException("node must not be null");
+        }
+        this.node = node;
+    }
+
+    /**
+     * @return A copy of the set of resolved nodes (real nodes)
+     */
+    public Set getResolvedNodes(ModuleId mid, String rootModuleConf) {
+        Collection resolved = (Collection) selectedDeps.get(new ModuleIdConf(mid, rootModuleConf));
+        Set ret = new HashSet();
+        if (resolved != null) {
+            for (Iterator iter = resolved.iterator(); iter.hasNext();) {
+                IvyNode node = (IvyNode) iter.next();
+                ret.add(node.getRealNode());
+            }
+        }
+        return ret;
+    }
+
+    public Collection getResolvedRevisions(ModuleId mid, String rootModuleConf) {
+        Collection resolved = (Collection) selectedDeps.get(new ModuleIdConf(mid, rootModuleConf));
+        if (resolved == null) {
+            return new HashSet();
+        } else {
+            Collection resolvedRevs = new HashSet();
+            for (Iterator iter = resolved.iterator(); iter.hasNext();) {
+                IvyNode node = (IvyNode) iter.next();
+                ModuleRevisionId resolvedId = node.getResolvedId();
+                resolvedRevs.add(node.getId());
+                resolvedRevs.add(resolvedId);
+
+                // in case there are extra attributes on the resolved module we also add the
+                // the module without these extra attributes (cfr. IVY-1236)
+                if (!resolvedId.getExtraAttributes().isEmpty()) {
+                    resolvedRevs.add(ModuleRevisionId.newInstance(resolvedId.getOrganisation(),
+                        resolvedId.getName(), resolvedId.getBranch(), resolvedId.getRevision()));
+                }
+            }
+            return resolvedRevs;
+        }
+    }
+
+    public void setResolvedNodes(ModuleId moduleId, String rootModuleConf, Collection resolved) {
+        ModuleIdConf moduleIdConf = new ModuleIdConf(moduleId, rootModuleConf);
+        selectedDeps.put(moduleIdConf, new HashSet(resolved));
+    }
+
+    public Collection getEvictedNodes(ModuleId mid, String rootModuleConf) {
+        Collection resolved = (Collection) evictedDeps.get(new ModuleIdConf(mid, rootModuleConf));
+        Set ret = new HashSet();
+        if (resolved != null) {
+            for (Iterator iter = resolved.iterator(); iter.hasNext();) {
+                IvyNode node = (IvyNode) iter.next();
+                ret.add(node.getRealNode());
+            }
+        }
+        return ret;
+    }
+
+    public Collection getEvictedRevisions(ModuleId mid, String rootModuleConf) {
+        Collection evicted = (Collection) evictedRevs.get(new ModuleIdConf(mid, rootModuleConf));
+        if (evicted == null) {
+            return new HashSet();
+        } else {
+            return new HashSet(evicted);
+        }
+    }
+
+    public void setEvictedNodes(ModuleId moduleId, String rootModuleConf, Collection evicted) {
+        ModuleIdConf moduleIdConf = new ModuleIdConf(moduleId, rootModuleConf);
+        evictedDeps.put(moduleIdConf, new HashSet(evicted));
+        Collection evictedRevs = new HashSet();
+        for (Iterator iter = evicted.iterator(); iter.hasNext();) {
+            IvyNode node = (IvyNode) iter.next();
+            evictedRevs.add(node.getId());
+            evictedRevs.add(node.getResolvedId());
+        }
+        this.evictedRevs.put(moduleIdConf, evictedRevs);
+    }
+
+    public boolean isEvicted(String rootModuleConf) {
+        cleanEvicted();
+        if (node.isRoot()) {
+            return false;
+        }
+        EvictionData evictedData = getEvictedData(rootModuleConf);
+        if (evictedData == null) {
+            return false;
+        }
+        IvyNode root = node.getRoot();
+        ModuleId moduleId = node.getId().getModuleId();
+        Collection resolvedRevisions = root.getResolvedRevisions(moduleId, rootModuleConf);
+        return !resolvedRevisions.contains(node.getResolvedId())
+                || evictedData.isTransitivelyEvicted();
+    }
+
+    public boolean isCompletelyEvicted() {
+        cleanEvicted();
+        if (node.isRoot()) {
+            return false;
+        }
+        String[] rootModuleConfigurations = node.getRootModuleConfigurations();
+        for (int i = 0; i < rootModuleConfigurations.length; i++) {
+            if (!isEvicted(rootModuleConfigurations[i])) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private void cleanEvicted() {
+        // check if it was evicted by a node that we are now the real node for
+        for (Iterator iter = evicted.keySet().iterator(); iter.hasNext();) {
+            String rootModuleConf = (String) iter.next();
+            EvictionData ed = (EvictionData) evicted.get(rootModuleConf);
+            Collection sel = ed.getSelected();
+            if (sel != null) {
+                for (Iterator iterator = sel.iterator(); iterator.hasNext();) {
+                    IvyNode n = (IvyNode) iterator.next();
+                    if (n.getRealNode().equals(node)) {
+                        // yes, we are the real node for a selected one !
+                        // we are no more evicted in this conf !
+                        iter.remove();
+                    }
+                }
+            }
+        }
+    }
+
+    public void markEvicted(EvictionData evictionData) {
+        evicted.put(evictionData.getRootModuleConf(), evictionData);
+    }
+
+    public EvictionData getEvictedData(String rootModuleConf) {
+        cleanEvicted();
+        return (EvictionData) evicted.get(rootModuleConf);
+    }
+
+    public String[] getEvictedConfs() {
+        cleanEvicted();
+        return (String[]) evicted.keySet().toArray(new String[evicted.keySet().size()]);
+    }
+
+    /**
+     * Returns null if this node has only be evicted transitively, or the the collection of selected
+     * nodes if it has been evicted by other selected nodes
+     * 
+     * @return
+     */
+    public Collection getAllEvictingNodes() {
+        Collection allEvictingNodes = null;
+        for (Iterator iter = evicted.values().iterator(); iter.hasNext();) {
+            EvictionData ed = (EvictionData) iter.next();
+            Collection selected = ed.getSelected();
+            if (selected != null) {
+                if (allEvictingNodes == null) {
+                    allEvictingNodes = new HashSet();
+                }
+                allEvictingNodes.addAll(selected);
+            }
+        }
+        return allEvictingNodes;
+    }
+
+    public Collection/* <String> */getAllEvictingNodesDetails() {
+        Collection ret = null;
+        for (Iterator iter = evicted.values().iterator(); iter.hasNext();) {
+            EvictionData ed = (EvictionData) iter.next();
+            Collection selected = ed.getSelected();
+            if (selected != null) {
+                if (ret == null) {
+                    ret = new HashSet();
+                }
+                if (selected.size() == 1) {
+                    ret.add(selected.iterator().next()
+                            + (ed.getDetail() == null ? "" : " " + ed.getDetail()));
+                } else if (selected.size() > 1) {
+                    ret.add(selected + (ed.getDetail() == null ? "" : " " + ed.getDetail()));
+                }
+            }
+        }
+        return ret;
+    }
+
+    public Collection getAllEvictingConflictManagers() {
+        Collection ret = new HashSet();
+        for (Iterator iter = evicted.values().iterator(); iter.hasNext();) {
+            EvictionData ed = (EvictionData) iter.next();
+            ret.add(ed.getConflictManager());
+        }
+        return ret;
+    }
+
+    /**
+     * Returns the eviction data for this node if it has been previously evicted in the root, null
+     * otherwise (if it hasn't been evicted in root) for the given rootModuleConf. Note that this
+     * method only works if conflict resolution has already be done in all the ancestors.
+     * 
+     * @param rootModuleConf
+     * @param ancestor
+     * @return
+     */
+    public EvictionData getEvictionDataInRoot(String rootModuleConf, IvyNode ancestor) {
+        Collection selectedNodes = node.getRoot().getResolvedNodes(node.getModuleId(),
+            rootModuleConf);
+        for (Iterator iter = selectedNodes.iterator(); iter.hasNext();) {
+            IvyNode node = (IvyNode) iter.next();
+            if (node.getResolvedId().equals(this.node.getResolvedId())) {
+                // the node is part of the selected ones for the root: no eviction data to return
+                return null;
+            }
+        }
+        // we didn't find this mrid in the selected ones for the root: it has been previously
+        // evicted
+        return new EvictionData(rootModuleConf, ancestor, node.getRoot().getConflictManager(
+            node.getModuleId()), selectedNodes);
+    }
+
+    public Collection getPendingConflicts(String rootModuleConf, ModuleId mid) {
+        Collection resolved = (Collection) pendingConflicts.get(new ModuleIdConf(mid,
+                rootModuleConf));
+        Set ret = new HashSet();
+        if (resolved != null) {
+            for (Iterator iter = resolved.iterator(); iter.hasNext();) {
+                IvyNode node = (IvyNode) iter.next();
+                ret.add(node.getRealNode());
+            }
+        }
+        return ret;
+    }
+
+    public void setPendingConflicts(ModuleId moduleId, String rootModuleConf, Collection conflicts) {
+        ModuleIdConf moduleIdConf = new ModuleIdConf(moduleId, rootModuleConf);
+        pendingConflicts.put(moduleIdConf, new HashSet(conflicts));
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/resolve/IvyNodeUsage.java b/src/java/org/apache/ivy/core/resolve/IvyNodeUsage.java
new file mode 100644
index 0000000..5269558
--- /dev/null
+++ b/src/java/org/apache/ivy/core/resolve/IvyNodeUsage.java
@@ -0,0 +1,326 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.resolve;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.ivy.core.module.descriptor.DependencyArtifactDescriptor;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.IncludeRule;
+
+/**
+ * Class collecting usage data for an IvyNode.
+ * <p>
+ * Usage data contains the configurations required by callers for each root module configuration,
+ * the configurations required by caller node and caller configuration, dependency artifacts
+ * descriptors declared by callers, include rules declared by callers, and blacklisted data by root
+ * module conf.
+ * </p>
+ */
+public class IvyNodeUsage {
+
+    private static final class NodeConf {
+        private IvyNode node;
+
+        private String conf;
+
+        public NodeConf(IvyNode node, String conf) {
+            if (node == null) {
+                throw new NullPointerException("node must not null");
+            }
+            if (conf == null) {
+                throw new NullPointerException("conf must not null");
+            }
+            this.node = node;
+            this.conf = conf;
+        }
+
+        public final String getConf() {
+            return conf;
+        }
+
+        public final IvyNode getNode() {
+            return node;
+        }
+
+        public boolean equals(Object obj) {
+            if (!(obj instanceof NodeConf)) {
+                return false;
+            }
+            return getNode().equals(((NodeConf) obj).getNode())
+                    && getConf().equals(((NodeConf) obj).getConf());
+        }
+
+        public int hashCode() {
+            // CheckStyle:MagicNumber| OFF
+            int hash = 33;
+            hash += getNode().hashCode() * 17;
+            hash += getConf().hashCode() * 17;
+            // CheckStyle:MagicNumber| OFF
+            return hash;
+        }
+
+        public String toString() {
+            return "NodeConf(" + conf + ")";
+        }
+    }
+
+    private static final class Depender {
+        private DependencyDescriptor dd;
+
+        private String dependerConf;
+
+        public Depender(DependencyDescriptor dd, String dependerConf) {
+            this.dd = dd;
+            this.dependerConf = dependerConf;
+        }
+
+        public String toString() {
+            return dd + " [" + dependerConf + "]";
+        }
+
+        public boolean equals(Object obj) {
+            if (!(obj instanceof Depender)) {
+                return false;
+            }
+            Depender other = (Depender) obj;
+            return other.dd == dd && other.dependerConf.equals(dependerConf);
+        }
+
+        public int hashCode() {
+            int hash = 33;
+            hash += dd.hashCode() * 13;
+            hash += dependerConf.hashCode() * 13;
+            return hash;
+        }
+    }
+
+    private IvyNode node;
+
+    // Map (String rootConfName -> Set(String confName))
+    // used to know which configurations of the dependency are required
+    // for each root module configuration
+    private Map rootModuleConfs = new HashMap();
+
+    // Map (NodeConf in -> Set(String conf))
+    private Map requiredConfs = new HashMap();
+
+    private Map /* <String, Set<Depender>> */dependers = new HashMap();
+
+    // Map (String rootModuleConf -> IvyNodeBlacklist)
+    private Map blacklisted = new HashMap();
+
+    public IvyNodeUsage(IvyNode node) {
+        this.node = node;
+    }
+
+    protected Collection getRequiredConfigurations(IvyNode in, String inConf) {
+        return (Collection) requiredConfs.get(new NodeConf(in, inConf));
+    }
+
+    protected void setRequiredConfs(IvyNode parent, String parentConf, Collection confs) {
+        requiredConfs.put(new NodeConf(parent, parentConf), new HashSet(confs));
+    }
+
+    /**
+     * Returns the configurations of the dependency required in a given root module configuration.
+     * 
+     * @param rootModuleConf
+     * @return
+     */
+    protected Set getConfigurations(String rootModuleConf) {
+        return (Set) rootModuleConfs.get(rootModuleConf);
+    }
+
+    protected Set addAndGetConfigurations(String rootModuleConf) {
+        Set depConfs = (Set) rootModuleConfs.get(rootModuleConf);
+        if (depConfs == null) {
+            depConfs = new HashSet();
+            rootModuleConfs.put(rootModuleConf, depConfs);
+        }
+        return depConfs;
+    }
+
+    protected Set /* <String> */getRootModuleConfigurations() {
+        return rootModuleConfs.keySet();
+    }
+
+    public void updateDataFrom(Collection/* <IvyNodeUsage> */usages, String rootModuleConf) {
+        for (Iterator iterator = usages.iterator(); iterator.hasNext();) {
+            IvyNodeUsage usage = (IvyNodeUsage) iterator.next();
+            updateDataFrom(usage, rootModuleConf);
+        }
+    }
+
+    private void updateDataFrom(IvyNodeUsage usage, String rootModuleConf) {
+        // update requiredConfs
+        updateMapOfSet(usage.requiredConfs, requiredConfs);
+
+        // update rootModuleConfs
+        updateMapOfSetForKey(usage.rootModuleConfs, rootModuleConfs, rootModuleConf);
+
+        // update dependencyArtifacts
+        updateMapOfSetForKey(usage.dependers, dependers, rootModuleConf);
+    }
+
+    private void updateMapOfSet(Map from, Map to) {
+        for (Iterator iter = from.keySet().iterator(); iter.hasNext();) {
+            Object key = iter.next();
+            updateMapOfSetForKey(from, to, key);
+        }
+    }
+
+    private void updateMapOfSetForKey(Map from, Map to, Object key) {
+        Set set = (Set) from.get(key);
+        if (set != null) {
+            Set toupdate = (Set) to.get(key);
+            if (toupdate != null) {
+                toupdate.addAll(set);
+            } else {
+                to.put(key, new HashSet(set));
+            }
+        }
+    }
+
+    // protected void addDependencyArtifacts(String rootModuleConf,
+    // DependencyArtifactDescriptor[] dependencyArtifacts) {
+    // addObjectsForConf(rootModuleConf, Arrays.asList(dependencyArtifacts),
+    // this.dependencyArtifacts);
+    // }
+    //
+    // protected void addDependencyIncludes(String rootModuleConf, IncludeRule[] rules) {
+    // addObjectsForConf(rootModuleConf, Arrays.asList(rules), dependencyIncludes);
+    // }
+    //
+    private void addObjectsForConf(String rootModuleConf, Object objectToAdd, Map map) {
+        Set set = (Set) map.get(rootModuleConf);
+        if (set == null) {
+            set = new HashSet();
+            map.put(rootModuleConf, set);
+        }
+        set.add(objectToAdd);
+    }
+
+    public void addUsage(String rootModuleConf, DependencyDescriptor dd, String parentConf) {
+        addObjectsForConf(rootModuleConf, new Depender(dd, parentConf), dependers);
+    }
+
+    protected Set getDependencyArtifactsSet(String rootModuleConf) {
+        Collection dependersInConf = (Collection) dependers.get(rootModuleConf);
+        if (dependersInConf == null) {
+            return null;
+        }
+        Set dependencyArtifacts = new HashSet();
+        for (Iterator iterator = dependersInConf.iterator(); iterator.hasNext();) {
+            Depender depender = (Depender) iterator.next();
+            DependencyArtifactDescriptor[] dads = depender.dd
+                    .getDependencyArtifacts(depender.dependerConf);
+            dependencyArtifacts.addAll(Arrays.asList(dads));
+        }
+        return dependencyArtifacts;
+    }
+
+    protected Set getDependencyIncludesSet(String rootModuleConf) {
+        Collection dependersInConf = (Collection) dependers.get(rootModuleConf);
+        if (dependersInConf == null) {
+            return null;
+        }
+        Set dependencyIncludes = new HashSet();
+        for (Iterator iterator = dependersInConf.iterator(); iterator.hasNext();) {
+            Depender depender = (Depender) iterator.next();
+            IncludeRule[] rules = depender.dd.getIncludeRules(depender.dependerConf);
+            if (rules == null || rules.length == 0) {
+                // no include rule in at least one depender -> we must include everything,
+                // and so return no include rule at all
+                return null;
+            }
+            dependencyIncludes.addAll(Arrays.asList(rules));
+        }
+        return dependencyIncludes;
+    }
+
+    protected void removeRootModuleConf(String rootModuleConf) {
+        rootModuleConfs.remove(rootModuleConf);
+    }
+
+    protected void blacklist(IvyNodeBlacklist bdata) {
+        blacklisted.put(bdata.getRootModuleConf(), bdata);
+    }
+
+    /**
+     * Indicates if this node has been blacklisted in the given root module conf.
+     * <p>
+     * A blacklisted node should be considered as if it doesn't even exist on the repository.
+     * </p>
+     * 
+     * @param rootModuleConf
+     *            the root module conf for which we'd like to know if the node is blacklisted
+     * 
+     * @return true if this node is blacklisted int he given root module conf, false otherwise
+     * @see #blacklist(String)
+     */
+    protected boolean isBlacklisted(String rootModuleConf) {
+        return blacklisted.containsKey(rootModuleConf);
+    }
+
+    /**
+     * Returns the blacklist data of this node in the given root module conf, or <code>null</code>
+     * if this node is not blacklisted in this root module conf.
+     * 
+     * @param rootModuleConf
+     *            the root module configuration to consider
+     * @return the blacklist data if any
+     */
+    protected IvyNodeBlacklist getBlacklistData(String rootModuleConf) {
+        return (IvyNodeBlacklist) blacklisted.get(rootModuleConf);
+    }
+
+    protected IvyNode getNode() {
+        return node;
+    }
+
+    /**
+     * Indicates if at least one depender has a transitive dependency descriptor for the given root
+     * module conf.
+     * 
+     * @param rootModuleConf
+     *            the root module conf to consider
+     * @return <code>true</code> if at least one depender has a transitive dependency descriptor for
+     *         the given root module conf, <code>false</code> otherwise.
+     */
+    public boolean hasTransitiveDepender(String rootModuleConf) {
+        Set/* <Depender> */dependersSet = (Set) dependers.get(rootModuleConf);
+        if (dependersSet == null) {
+            return false;
+        }
+        for (Iterator iterator = dependersSet.iterator(); iterator.hasNext();) {
+            Depender depender = (Depender) iterator.next();
+            if (depender.dd.isTransitive()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/resolve/ResolveData.java b/src/java/org/apache/ivy/core/resolve/ResolveData.java
new file mode 100644
index 0000000..54cb661
--- /dev/null
+++ b/src/java/org/apache/ivy/core/resolve/ResolveData.java
@@ -0,0 +1,351 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.resolve;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.ivy.core.event.EventManager;
+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.apache.ivy.core.report.ConfigurationResolveReport;
+import org.apache.ivy.util.Message;
+
+public class ResolveData {
+    private ResolveEngine engine;
+
+    private Map visitData; // shared map of all visit data: Map (ModuleRevisionId -> VisitData)
+
+    private ConfigurationResolveReport report;
+
+    private ResolveOptions options;
+
+    private VisitNode currentVisitNode = null;
+
+    private ResolvedModuleRevision currentResolvedModuleRevision;
+
+    public ResolveData(ResolveData data, boolean validate) {
+        this(data.engine, new ResolveOptions(data.options).setValidate(validate), data.report,
+                data.visitData);
+        setCurrentVisitNode(data.currentVisitNode);
+        setCurrentResolvedModuleRevision(data.currentResolvedModuleRevision);
+    }
+
+    public ResolveData(ResolveEngine engine, ResolveOptions options) {
+        this(engine, options, null, new LinkedHashMap());
+    }
+
+    public ResolveData(ResolveEngine engine, ResolveOptions options,
+            ConfigurationResolveReport report) {
+        this(engine, options, report, new LinkedHashMap());
+    }
+
+    public ResolveData(ResolveEngine engine, ResolveOptions options,
+            ConfigurationResolveReport report, Map visitData) {
+        this.engine = engine;
+        this.report = report;
+        this.visitData = visitData;
+        this.options = options;
+    }
+
+    public ConfigurationResolveReport getReport() {
+        return report;
+    }
+
+    public IvyNode getNode(ModuleRevisionId mrid) {
+        VisitData visitData = getVisitData(mrid);
+        return visitData == null ? null : visitData.getNode();
+    }
+
+    public Collection getNodes() {
+        Collection nodes = new ArrayList();
+        for (Iterator iter = visitData.values().iterator(); iter.hasNext();) {
+            VisitData vdata = (VisitData) iter.next();
+            nodes.add(vdata.getNode());
+        }
+        return nodes;
+    }
+
+    public Collection getNodeIds() {
+        return visitData.keySet();
+    }
+
+    public VisitData getVisitData(ModuleRevisionId mrid) {
+        VisitData result = (VisitData) visitData.get(mrid);
+
+        if (result == null) {
+            // search again, now ignore the missing extra attributes
+            for (Iterator it = visitData.entrySet().iterator(); it.hasNext();) {
+                Map.Entry entry = (Entry) it.next();
+                ModuleRevisionId current = (ModuleRevisionId) entry.getKey();
+
+                if (isSubMap(mrid.getAttributes(), current.getAttributes())) {
+                    result = (VisitData) entry.getValue();
+                    break;
+                }
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Checks whether one map is a sub-map of the other.
+     */
+    private static boolean isSubMap(Map map1, Map map2) {
+        int map1Size = map1.size();
+        int map2Size = map2.size();
+
+        if (map1Size == map2Size) {
+            return map1.equals(map2);
+        }
+
+        Map smallest = map1Size < map2Size ? map1 : map2;
+        Map largest = map1Size < map2Size ? map2 : map1;
+
+        for (Iterator it = smallest.entrySet().iterator(); it.hasNext();) {
+            Map.Entry entry = (Entry) it.next();
+
+            if (!largest.containsKey(entry.getKey())) {
+                return false;
+            }
+
+            Object map1Value = smallest.get(entry.getKey());
+            Object map2Value = largest.get(entry.getKey());
+            if (!isEqual(map1Value, map2Value)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    private static boolean isEqual(Object obj1, Object obj2) {
+        if (obj1 == obj2) {
+            return true;
+        }
+
+        if (obj1 == null) {
+            return obj2 == null;
+        }
+
+        if (obj2 == null) {
+            return obj1 == null;
+        }
+
+        return obj1.equals(obj2);
+    }
+
+    /**
+     * Returns the VisitNode currently visited, or <code>null</code> if there is no node currently
+     * visited in this context.
+     * 
+     * @return the VisitNode currently visited
+     */
+    public VisitNode getCurrentVisitNode() {
+        return currentVisitNode;
+    }
+
+    /**
+     * Sets the currently visited node. WARNING: This should only be called by Ivy core
+     * ResolveEngine!
+     * 
+     * @param currentVisitNode
+     */
+    void setCurrentVisitNode(VisitNode currentVisitNode) {
+        this.currentVisitNode = currentVisitNode;
+    }
+
+    public void register(VisitNode node) {
+        register(node.getId(), node);
+    }
+
+    public void register(ModuleRevisionId mrid, VisitNode node) {
+        VisitData visitData = getVisitData(mrid);
+        if (visitData == null) {
+            visitData = new VisitData(node.getNode());
+            visitData.addVisitNode(node);
+            this.visitData.put(mrid, visitData);
+        } else {
+            visitData.setNode(node.getNode());
+            visitData.addVisitNode(node);
+        }
+    }
+
+    /**
+     * Updates the visit data currently associated with the given mrid with the given node and the
+     * visit nodes of the old visitData for the given rootModuleConf
+     * 
+     * @param mrid
+     *            the module revision id for which the update should be done
+     * @param node
+     *            the IvyNode to associate with the visit data to update
+     * @param rootModuleConf
+     *            the root module configuration in which the update is made
+     */
+    void replaceNode(ModuleRevisionId mrid, IvyNode node, String rootModuleConf) {
+        VisitData visitData = getVisitData(mrid);
+        if (visitData == null) {
+            throw new IllegalArgumentException("impossible to replace node for id " + mrid
+                    + ". No registered node found.");
+        }
+        VisitData keptVisitData = getVisitData(node.getId());
+        if (keptVisitData == null) {
+            throw new IllegalArgumentException("impossible to replace node with " + node
+                    + ". No registered node found for " + node.getId() + ".");
+        }
+        // replace visit data in Map (discards old one)
+        this.visitData.put(mrid, keptVisitData);
+        // update visit data with discarde visit nodes
+        keptVisitData.addVisitNodes(rootModuleConf, visitData.getVisitNodes(rootModuleConf));
+
+        report.updateDependency(mrid, node);
+    }
+
+    public void setReport(ConfigurationResolveReport report) {
+        this.report = report;
+    }
+
+    public Date getDate() {
+        return options.getDate();
+    }
+
+    public boolean isValidate() {
+        return options.isValidate();
+    }
+
+    public boolean isTransitive() {
+        return options.isTransitive();
+    }
+
+    public ResolveOptions getOptions() {
+        return options;
+    }
+
+    public ResolveEngineSettings getSettings() {
+        return engine.getSettings();
+    }
+
+    public EventManager getEventManager() {
+        return engine.getEventManager();
+    }
+
+    public ResolveEngine getEngine() {
+        return engine;
+    }
+
+    void blacklist(IvyNode node) {
+        for (Iterator iter = visitData.entrySet().iterator(); iter.hasNext();) {
+            Entry entry = (Entry) iter.next();
+            VisitData vdata = (VisitData) entry.getValue();
+            if (vdata.getNode() == node && !node.getResolvedId().equals(entry.getKey())) {
+                // this visit data was associated with the blacklisted node,
+                // we discard this association
+                iter.remove();
+            }
+        }
+    }
+
+    public boolean isBlacklisted(String rootModuleConf, ModuleRevisionId mrid) {
+        IvyNode node = getNode(mrid);
+
+        // if (node == null) {
+        // // search again, now ignore the extra attributes
+        // // TODO: maybe we should search the node that has at least the
+        // // same attributes as mrid
+        // for (Iterator it = visitData.entrySet().iterator(); it.hasNext();) {
+        // Map.Entry entry = (Entry) it.next();
+        // ModuleRevisionId current = (ModuleRevisionId) entry.getKey();
+        // if (current.getModuleId().equals(mrid.getModuleId())
+        // && current.getRevision().equals(mrid.getRevision())) {
+        // VisitData data = (VisitData) entry.getValue();
+        // node = data.getNode();
+        // break;
+        // }
+        // }
+        // }
+        //
+        return node != null && node.isBlacklisted(rootModuleConf);
+    }
+
+    public DependencyDescriptor mediate(DependencyDescriptor dd) {
+        DependencyDescriptor originalDD = dd;
+        dd = getEngine().mediate(dd, getOptions());
+
+        VisitNode current = getCurrentVisitNode();
+        if (current != null) {
+            // mediating dd through dependers stack
+            List dependers = new ArrayList(current.getPath());
+            // the returned path contains the currently visited node, we are only interested in
+            // the dependers, so we remove the currently visted node from the end
+            dependers.remove(dependers.size() - 1);
+            // we want to apply mediation going up in the dependers stack, not the opposite
+            Collections.reverse(dependers);
+            for (Iterator iterator = dependers.iterator(); iterator.hasNext();) {
+                VisitNode n = (VisitNode) iterator.next();
+                ModuleDescriptor md = n.getDescriptor();
+                if (md != null) {
+                    dd = md.mediate(dd);
+                }
+            }
+        }
+
+        if (originalDD != dd) {
+            Message.verbose("dependency descriptor has been mediated: " + originalDD + " => " + dd);
+        }
+
+        return dd;
+    }
+
+    /**
+     * Sets the last {@link ResolvedModuleRevision} which has been currently resolved.
+     * <p>
+     * This can be used especially in dependency resolvers, to know if another dependency resolver
+     * has already resolved the requested dependency, to take a decision if the resolver should try
+     * to resolve it by itself or not. Indeed, the dependency resolver is responsible for taking
+     * this decision, even when included in a chain. The chain responsibility is only to set this
+     * current resolved module revision to enable the resolver to take the decision.
+     * </p>
+     * 
+     * @param mr
+     *            the last {@link ResolvedModuleRevision} which has been currently resolved.
+     */
+    public void setCurrentResolvedModuleRevision(ResolvedModuleRevision mr) {
+        this.currentResolvedModuleRevision = mr;
+    }
+
+    /**
+     * Returns the last {@link ResolvedModuleRevision} which has been currently resolved.
+     * <p>
+     * It can be <code>null</code>.
+     * </p>
+     * 
+     * @return the last {@link ResolvedModuleRevision} which has been currently resolved.
+     */
+    public ResolvedModuleRevision getCurrentResolvedModuleRevision() {
+        return currentResolvedModuleRevision;
+    }
+}
diff --git a/src/java/org/apache/ivy/core/resolve/ResolveEngine.java b/src/java/org/apache/ivy/core/resolve/ResolveEngine.java
new file mode 100644
index 0000000..bb3bc95
--- /dev/null
+++ b/src/java/org/apache/ivy/core/resolve/ResolveEngine.java
@@ -0,0 +1,1219 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.resolve;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import org.apache.ivy.Ivy;
+import org.apache.ivy.core.IvyContext;
+import org.apache.ivy.core.LogOptions;
+import org.apache.ivy.core.cache.ArtifactOrigin;
+import org.apache.ivy.core.cache.ResolutionCacheManager;
+import org.apache.ivy.core.event.EventManager;
+import org.apache.ivy.core.event.download.PrepareDownloadEvent;
+import org.apache.ivy.core.event.resolve.EndResolveEvent;
+import org.apache.ivy.core.event.resolve.StartResolveEvent;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.Configuration;
+import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
+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.ModuleId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.report.ArtifactDownloadReport;
+import org.apache.ivy.core.report.ConfigurationResolveReport;
+import org.apache.ivy.core.report.DownloadReport;
+import org.apache.ivy.core.report.DownloadStatus;
+import org.apache.ivy.core.report.ResolveReport;
+import org.apache.ivy.core.resolve.IvyNodeEviction.EvictionData;
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.ivy.core.sort.SortEngine;
+import org.apache.ivy.core.sort.SortOptions;
+import org.apache.ivy.plugins.conflict.ConflictManager;
+import org.apache.ivy.plugins.parser.ModuleDescriptorParser;
+import org.apache.ivy.plugins.parser.ModuleDescriptorParserRegistry;
+import org.apache.ivy.plugins.repository.url.URLResource;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.apache.ivy.plugins.version.VersionMatcher;
+import org.apache.ivy.util.Message;
+import org.apache.ivy.util.filter.Filter;
+
+/**
+ * The resolve engine which is the core of the dependency resolution mechanism used in Ivy. It
+ * features several resolve methods, some very simple, like {@link #resolve(File)} and
+ * {@link #resolve(URL)} which allow to simply resolve dependencies of a single module descriptor,
+ * or more complete one, like the {@link #resolve(ModuleDescriptor, ResolveOptions)} which allows to
+ * provide options to the resolution engine.
+ * 
+ * @see ResolveOptions
+ */
+public class ResolveEngine {
+    private ResolveEngineSettings settings;
+
+    private EventManager eventManager;
+
+    private SortEngine sortEngine;
+
+    private Set fetchedSet = new HashSet();
+
+    private DependencyResolver dictatorResolver;
+
+    /**
+     * Constructs a ResolveEngine.
+     * 
+     * @param settings
+     *            the settings to use to configure the engine. Must not be null.
+     * @param eventManager
+     *            the event manager to use to send events about the resolution process. Must not be
+     *            null.
+     * @param sortEngine
+     *            the sort engine to use to sort modules before producing the dependency resolution
+     *            report. Must not be null.
+     */
+    public ResolveEngine(ResolveEngineSettings settings, EventManager eventManager,
+            SortEngine sortEngine) {
+        this.settings = settings;
+        this.eventManager = eventManager;
+        this.sortEngine = sortEngine;
+    }
+
+    /**
+     * Returns the currently configured dictator resolver, which when non null is used in place of
+     * any specified resolver in the {@link IvySettings}
+     * 
+     * @return the currently configured dictator resolver, may be null.
+     */
+    public DependencyResolver getDictatorResolver() {
+        return dictatorResolver;
+    }
+
+    /**
+     * Sets a dictator resolver, which is used in place of regular dependency resolver for
+     * subsequent dependency resolution by this engine.
+     * 
+     * @param dictatorResolver
+     *            the dictator resolver to use in this engine, null if regular settings should used
+     */
+    public void setDictatorResolver(DependencyResolver dictatorResolver) {
+        this.dictatorResolver = dictatorResolver;
+        settings.setDictatorResolver(dictatorResolver);
+    }
+
+    public ResolveReport resolve(File ivySource) throws ParseException, IOException {
+        return resolve(ivySource.toURI().toURL());
+    }
+
+    public ResolveReport resolve(URL ivySource) throws ParseException, IOException {
+        return resolve(ivySource, new ResolveOptions());
+    }
+
+    /**
+     * Resolves the module identified by the given mrid with its dependencies if transitive is set
+     * to true.
+     */
+    public ResolveReport resolve(final ModuleRevisionId mrid, ResolveOptions options,
+            boolean changing) throws ParseException, IOException {
+        DefaultModuleDescriptor md;
+        ResolveOptions optionsToUse = new ResolveOptions(options);
+
+        if (options.useSpecialConfs()) {
+            // create new resolve options because this is a different resolve than the real resolve
+            // (which will be a resolve of a newCallerInstance module)
+            ResolvedModuleRevision rmr = findModule(mrid, new ResolveOptions(options));
+            if (rmr == null) {
+                Message.verbose("module not found " + mrid);
+
+                // we will continue the resolve anyway to get a nice error message back
+                // to the user, however reduce the amount of logging in this case
+                optionsToUse.setLog(LogOptions.LOG_DOWNLOAD_ONLY);
+                md = DefaultModuleDescriptor.newCallerInstance(mrid, new String[] {"default"},
+                    options.isTransitive(), changing);
+            } else {
+                String[] confs = options.getConfs(rmr.getDescriptor());
+                md = DefaultModuleDescriptor.newCallerInstance(
+                    ModuleRevisionId.newInstance(mrid, rmr.getId().getRevision()), confs,
+                    options.isTransitive(), changing);
+            }
+        } else {
+            md = DefaultModuleDescriptor.newCallerInstance(mrid, options.getConfs(),
+                options.isTransitive(), changing);
+        }
+
+        return resolve(md, optionsToUse);
+    }
+
+    /**
+     * Resolve dependencies of a module described by an ivy file.
+     */
+    public ResolveReport resolve(URL ivySource, ResolveOptions options) throws ParseException,
+            IOException {
+        URLResource res = new URLResource(ivySource);
+        ModuleDescriptorParser parser = ModuleDescriptorParserRegistry.getInstance().getParser(res);
+        Message.verbose("using " + parser + " to parse " + ivySource);
+        ModuleDescriptor md = parser.parseDescriptor(settings, ivySource, options.isValidate());
+        String revision = options.getRevision();
+        if (revision == null && md.getResolvedModuleRevisionId().getRevision() == null) {
+            revision = Ivy.getWorkingRevision();
+        }
+        if (revision != null) {
+            md.setResolvedModuleRevisionId(ModuleRevisionId.newInstance(md.getModuleRevisionId(),
+                revision));
+        }
+
+        return resolve(md, options);
+    }
+
+    /**
+     * Resolve dependencies of a module described by a module descriptor.
+     */
+    public ResolveReport resolve(ModuleDescriptor md, ResolveOptions options)
+            throws ParseException, IOException {
+        DependencyResolver oldDictator = getDictatorResolver();
+        IvyContext context = IvyContext.getContext();
+        try {
+            String[] confs = options.getConfs(md);
+            options.setConfs(confs);
+
+            if (options.getResolveId() == null) {
+                options.setResolveId(ResolveOptions.getDefaultResolveId(md));
+            }
+
+            eventManager.fireIvyEvent(new StartResolveEvent(md, confs));
+
+            long start = System.currentTimeMillis();
+            if (ResolveOptions.LOG_DEFAULT.equals(options.getLog())) {
+                Message.info(":: resolving dependencies :: " + md.getResolvedModuleRevisionId()
+                        + (options.isTransitive() ? "" : " [not transitive]"));
+                Message.info("\tconfs: " + Arrays.asList(confs));
+            } else {
+                Message.verbose(":: resolving dependencies :: " + md.getResolvedModuleRevisionId()
+                        + (options.isTransitive() ? "" : " [not transitive]"));
+                Message.verbose("\tconfs: " + Arrays.asList(confs));
+            }
+            Message.verbose("\tvalidate = " + options.isValidate());
+            Message.verbose("\trefresh = " + options.isRefresh());
+
+            ResolveReport report = new ResolveReport(md, options.getResolveId());
+
+            ResolveData data = new ResolveData(this, options);
+            context.setResolveData(data);
+
+            // resolve dependencies
+            IvyNode[] dependencies = getDependencies(md, options, report);
+            report.setDependencies(Arrays.asList(dependencies), options.getArtifactFilter());
+
+            if (options.getCheckIfChanged()) {
+                report.checkIfChanged();
+            }
+
+            // produce resolved ivy file and ivy properties in cache
+            ResolutionCacheManager cacheManager = settings.getResolutionCacheManager();
+            cacheManager.saveResolvedModuleDescriptor(md);
+
+            // we store the resolved dependencies revisions and statuses per asked dependency
+            // revision id, for direct dependencies only.
+            // this is used by the deliver task to resolve dynamic revisions to static ones
+            File ivyPropertiesInCache = cacheManager.getResolvedIvyPropertiesInCache(md
+                    .getResolvedModuleRevisionId());
+            Properties props = new Properties();
+            if (dependencies.length > 0) {
+                Map forcedRevisions = new HashMap();
+                for (int i = 0; i < dependencies.length; i++) {
+                    if (dependencies[i].getModuleRevision() != null
+                            && dependencies[i].getModuleRevision().isForce()) {
+                        forcedRevisions.put(dependencies[i].getModuleId(),
+                            dependencies[i].getResolvedId());
+                    }
+                }
+
+                IvyNode root = dependencies[0].getRoot();
+
+                // <ModuleId,IvyNode>();
+                Map topLevelDeps = new HashMap(); //
+                for (int i = 0; i < dependencies.length; i++) {
+                    if (!dependencies[i].hasProblem()) {
+                        DependencyDescriptor dd = dependencies[i].getDependencyDescriptor(root);
+                        if (dd != null) {
+                            ModuleId orgMod = dependencies[i].getModuleId();
+                            topLevelDeps.put(orgMod, dependencies[i]);
+                        }
+                    }
+                }
+
+                for (int i = 0; i < dependencies.length; i++) {
+                    if (!dependencies[i].hasProblem() && !dependencies[i].isCompletelyEvicted()) {
+                        DependencyDescriptor dd = dependencies[i].getDependencyDescriptor(root);
+                        if (dd == null) {
+                            ModuleId mid = dependencies[i].getModuleId();
+                            IvyNode tlDep = (IvyNode) topLevelDeps.get(mid);
+                            if (tlDep != null) {
+                                dd = tlDep.getDependencyDescriptor(root);
+                            }
+                        }
+                        if (dd != null) {
+                            ModuleRevisionId depResolvedId = dependencies[i].getResolvedId();
+                            ModuleDescriptor depDescriptor = dependencies[i].getDescriptor();
+                            ModuleRevisionId depRevisionId = dd.getDependencyRevisionId();
+                            ModuleRevisionId forcedRevisionId = (ModuleRevisionId) forcedRevisions
+                                    .get(dependencies[i].getModuleId());
+
+                            if (dependencies[i].getModuleRevision() != null
+                                    && dependencies[i].getModuleRevision().isForce()
+                                    && !depResolvedId.equals(depRevisionId)
+                                    && !settings.getVersionMatcher().isDynamic(depRevisionId)) {
+                                // if we were forced to this revision and we
+                                // are not a dynamic revision, reset to the
+                                // asked revision
+                                depResolvedId = depRevisionId;
+                                depDescriptor = null;
+                            }
+
+                            if (depResolvedId == null) {
+                                throw new NullPointerException("getResolvedId() is null for "
+                                        + dependencies[i].toString());
+                            }
+                            if (depRevisionId == null) {
+                                throw new NullPointerException("getDependencyRevisionId() "
+                                        + "is null for " + dd.toString());
+                            }
+                            String rev = depResolvedId.getRevision();
+                            String forcedRev = forcedRevisionId == null ? rev : forcedRevisionId
+                                    .getRevision();
+
+                            // The evicted modules have no description, so we can't put the status
+                            String status = depDescriptor == null ? "?" : depDescriptor.getStatus();
+                            Message.debug("storing dependency " + depResolvedId + " in props");
+                            props.put(depRevisionId.encodeToString(), rev + " " + status + " "
+                                    + forcedRev + " " + depResolvedId.getBranch());
+                        }
+                    }
+                }
+            }
+            FileOutputStream out = new FileOutputStream(ivyPropertiesInCache);
+            props.store(out, md.getResolvedModuleRevisionId() + " resolved revisions");
+            out.close();
+            Message.verbose("\tresolved ivy file produced in cache");
+
+            report.setResolveTime(System.currentTimeMillis() - start);
+
+            if (options.isDownload()) {
+                Message.verbose(":: downloading artifacts ::");
+
+                DownloadOptions downloadOptions = new DownloadOptions();
+                downloadOptions.setLog(options.getLog());
+                downloadArtifacts(report, options.getArtifactFilter(), downloadOptions);
+            }
+
+            if (options.isOutputReport()) {
+                outputReport(report, cacheManager, options);
+            }
+
+            Message.verbose("\tresolve done (" + report.getResolveTime() + "ms resolve - "
+                    + report.getDownloadTime() + "ms download)");
+            Message.sumupProblems();
+
+            eventManager.fireIvyEvent(new EndResolveEvent(md, confs, report));
+            return report;
+        } catch (RuntimeException ex) {
+            Message.debug(ex);
+            Message.error(ex.getMessage());
+            Message.sumupProblems();
+            throw ex;
+        } finally {
+            context.setResolveData(null);
+            setDictatorResolver(oldDictator);
+        }
+    }
+
+    public void outputReport(ResolveReport report, ResolutionCacheManager cacheMgr,
+            ResolveOptions options) throws IOException {
+        if (ResolveOptions.LOG_DEFAULT.equals(options.getLog())) {
+            Message.info(":: resolution report :: resolve " + report.getResolveTime() + "ms"
+                    + " :: artifacts dl " + report.getDownloadTime() + "ms");
+        } else {
+            Message.verbose(":: resolution report :: resolve " + report.getResolveTime() + "ms"
+                    + " :: artifacts dl " + report.getDownloadTime() + "ms");
+        }
+        report.setProblemMessages(Message.getProblems());
+        // output report
+        report.output(settings.getReportOutputters(), cacheMgr, options);
+    }
+
+    public void downloadArtifacts(ResolveReport report, Filter artifactFilter,
+            DownloadOptions options) {
+        long start = System.currentTimeMillis();
+        IvyNode[] dependencies = (IvyNode[]) report.getDependencies().toArray(
+            new IvyNode[report.getDependencies().size()]);
+
+        eventManager.fireIvyEvent(new PrepareDownloadEvent((Artifact[]) report.getArtifacts()
+                .toArray(new Artifact[report.getArtifacts().size()])));
+
+        long totalSize = 0;
+        for (int i = 0; i < dependencies.length; i++) {
+            checkInterrupted();
+            // download artifacts required in all asked configurations
+            if (!dependencies[i].isCompletelyEvicted() && !dependencies[i].hasProblem()
+                    && dependencies[i].getModuleRevision() != null) {
+                DependencyResolver resolver = dependencies[i].getModuleRevision()
+                        .getArtifactResolver();
+                Artifact[] selectedArtifacts = dependencies[i].getSelectedArtifacts(artifactFilter);
+                DownloadReport dReport = resolver.download(selectedArtifacts, options);
+                ArtifactDownloadReport[] adrs = dReport.getArtifactsReports();
+                for (int j = 0; j < adrs.length; j++) {
+                    if (adrs[j].getDownloadStatus() == DownloadStatus.FAILED) {
+                        if (adrs[j].getArtifact().getExtraAttribute("ivy:merged") != null) {
+                            Message.warn("\tmerged artifact not found: " + adrs[j].getArtifact()
+                                    + ". It was required in "
+                                    + adrs[j].getArtifact().getExtraAttribute("ivy:merged"));
+                        } else {
+                            Message.warn("\t" + adrs[j]);
+                            resolver.reportFailure(adrs[j].getArtifact());
+                        }
+                    } else if (adrs[j].getDownloadStatus() == DownloadStatus.SUCCESSFUL) {
+                        totalSize += adrs[j].getSize();
+                    }
+                }
+                // update concerned reports
+                String[] dconfs = dependencies[i].getRootModuleConfigurations();
+                for (int j = 0; j < dconfs.length; j++) {
+                    // the report itself is responsible to take into account only
+                    // artifacts required in its corresponding configuration
+                    // (as described by the Dependency object)
+                    if (dependencies[i].isEvicted(dconfs[j])
+                            || dependencies[i].isBlacklisted(dconfs[j])) {
+                        report.getConfigurationReport(dconfs[j]).addDependency(dependencies[i]);
+                    } else {
+                        report.getConfigurationReport(dconfs[j]).addDependency(dependencies[i],
+                            dReport);
+                    }
+                }
+            }
+        }
+        report.setDownloadTime(System.currentTimeMillis() - start);
+        report.setDownloadSize(totalSize);
+    }
+
+    /**
+     * Download an artifact to the cache. Not used internally, useful especially for IDE plugins
+     * needing to download artifact one by one (for source or javadoc artifact, for instance).
+     * <p>
+     * Downloaded artifact file can be accessed using {@link ArtifactDownloadReport#getLocalFile()}.
+     * </p>
+     * <p>
+     * It is possible to track the progression of the download using classical ivy progress
+     * monitoring feature (see addTransferListener).
+     * </p>
+     * 
+     * @param artifact
+     *            the artifact to download
+     * @return a report concerning the download
+     * @see #download(ArtifactOrigin, DownloadOptions)
+     */
+    public ArtifactDownloadReport download(Artifact artifact, DownloadOptions options) {
+        DependencyResolver resolver = settings.getResolver(artifact.getModuleRevisionId());
+        DownloadReport r = resolver.download(new Artifact[] {artifact}, options);
+        return r.getArtifactReport(artifact);
+    }
+
+    /**
+     * Locates an artifact in dependency resolvers, and return its location if it can be located and
+     * actually exists, or an unknown {@link ArtifactOrigin} in other cases.
+     * 
+     * @param artifact
+     *            the artifact to locate.
+     * @return the artifact location, should be tested with
+     *         {@link ArtifactOrigin#isUnknown(ArtifactOrigin)} to check if the artifact has
+     *         actually been located.
+     */
+    public ArtifactOrigin locate(Artifact artifact) {
+        DependencyResolver resolver = settings.getResolver(artifact.getModuleRevisionId());
+        return resolver.locate(artifact);
+    }
+
+    /**
+     * Materialize an artifact already located.
+     * <p>
+     * Not used internally, useful especially for IDE plugins needing to download artifact one by
+     * one (for source or javadoc artifact, for instance).
+     * </p>
+     * <p>
+     * Materialized artifact file can be accessed using
+     * {@link ArtifactDownloadReport#getLocalFile()}.
+     * </p>
+     * <p>
+     * It is possible to track the progression of the download using classical ivy progress
+     * monitoring feature (see addTransferListener).
+     * </p>
+     * 
+     * @param origin
+     *            the artifact origin to materialize
+     * @return a report concerning the download
+     * @see #download(Artifact, DownloadOptions)
+     * @see #locate(Artifact)
+     */
+    public ArtifactDownloadReport download(ArtifactOrigin origin, DownloadOptions options) {
+        DependencyResolver resolver = settings.getResolver(origin.getArtifact()
+                .getModuleRevisionId());
+        return resolver.download(origin, options);
+    }
+
+    /**
+     * Resolve the dependencies of a module without downloading corresponding artifacts. The module
+     * to resolve is given by its ivy file URL. This method requires appropriate configuration of
+     * the ivy instance, especially resolvers.
+     * 
+     * @param ivySource
+     *            url of the ivy file to use for dependency resolving
+     * @param confs
+     *            an array of configuration names to resolve - must not be null nor empty
+     * @param getCache
+     *            the cache to use - default cache is used if null
+     * @param date
+     *            the date to which resolution must be done - may be null
+     * @return an array of the resolved dependencies
+     * @throws ParseException
+     *             if a parsing problem occurred in the ivy file
+     * @throws IOException
+     *             if an IO problem was raised during ivy file parsing
+     */
+    public IvyNode[] getDependencies(URL ivySource, ResolveOptions options) throws ParseException,
+            IOException {
+        return getDependencies(
+            ModuleDescriptorParserRegistry.getInstance().parseDescriptor(settings, ivySource,
+                options.isValidate()), options, null);
+    }
+
+    /**
+     * Resolve the dependencies of a module without downloading corresponding artifacts. The module
+     * to resolve is given by its module descriptor. This method requires appropriate configuration
+     * of the ivy instance, especially resolvers.
+     * <p>
+     * The <code>IvyNode</code>s are ordered from the most dependent to the less dependent, so that
+     * an IvyNode is always found in the list after all IvyNode depending directly on it.
+     * 
+     * @param md
+     *            the descriptor of the module for which we want to get dependencies - must not be
+     *            null
+     * @param options
+     *            the resolve options to use to resolve the dependencies
+     * @param report
+     *            a resolve report to fill during resolution - may be null
+     * @return an array of the resolved Dependencies
+     */
+    public IvyNode[] getDependencies(ModuleDescriptor md, ResolveOptions options,
+            ResolveReport report) {
+        // check parameters
+        if (md == null) {
+            throw new NullPointerException("module descriptor must not be null");
+        }
+        String[] confs = options.getConfs(md);
+        Collection missingConfs = new ArrayList();
+        for (int i = 0; i < confs.length; i++) {
+            if (confs[i] == null) {
+                throw new NullPointerException("null conf not allowed: confs where: "
+                        + Arrays.asList(confs));
+            }
+
+            if (md.getConfiguration(confs[i]) == null) {
+                missingConfs.add(" '" + confs[i] + "' ");
+            }
+        }
+        if (!missingConfs.isEmpty()) {
+            throw new IllegalArgumentException("requested configuration"
+                    + (missingConfs.size() > 1 ? "s" : "") + " not found in "
+                    + md.getModuleRevisionId() + ": " + missingConfs);
+        }
+
+        IvyContext context = IvyContext.pushNewCopyContext();
+        try {
+            options.setConfs(confs);
+
+            Date reportDate = new Date();
+            ResolveData data = context.getResolveData();
+            if (data == null) {
+                data = new ResolveData(this, options);
+                context.setResolveData(data);
+            }
+            IvyNode rootNode = new IvyNode(data, md);
+
+            for (int i = 0; i < confs.length; i++) {
+                Message.verbose("resolving dependencies for configuration '" + confs[i] + "'");
+                // for each configuration we clear the cache of what's been fetched
+                fetchedSet.clear();
+
+                ConfigurationResolveReport confReport = null;
+                if (report != null) {
+                    confReport = report.getConfigurationReport(confs[i]);
+                    if (confReport == null) {
+                        confReport = new ConfigurationResolveReport(this, md, confs[i], reportDate,
+                                options);
+                        report.addReport(confs[i], confReport);
+                    }
+                }
+                // we reuse the same resolve data with a new report for each conf
+                data.setReport(confReport);
+
+                // update the root module conf we are about to fetch
+                VisitNode root = new VisitNode(data, rootNode, null, confs[i], null);
+                root.setRequestedConf(confs[i]);
+                rootNode.updateConfsToFetch(Collections.singleton(confs[i]));
+
+                // go fetch !
+                boolean fetched = false;
+                while (!fetched) {
+                    try {
+                        fetchDependencies(root, confs[i], false);
+                        fetched = true;
+                    } catch (RestartResolveProcess restart) {
+                        Message.verbose("====================================================");
+                        Message.verbose("=           RESTARTING RESOLVE PROCESS");
+                        Message.verbose("= " + restart.getMessage());
+                        Message.verbose("====================================================");
+                        fetchedSet.clear();
+                    }
+                }
+
+                // clean data
+                for (Iterator iter = data.getNodes().iterator(); iter.hasNext();) {
+                    IvyNode dep = (IvyNode) iter.next();
+                    dep.clean();
+                }
+            }
+
+            // prune and reverse sort fectched dependencies
+            Collection nodes = data.getNodes();
+            // use a Set to avoid duplicates, linked to preserve order
+            Collection dependencies = new LinkedHashSet(nodes.size());
+            for (Iterator iter = nodes.iterator(); iter.hasNext();) {
+                IvyNode node = (IvyNode) iter.next();
+                if (node != null && !node.isRoot() && !node.isCompletelyBlacklisted()) {
+                    dependencies.add(node);
+                }
+            }
+            List sortedDependencies = sortEngine.sortNodes(dependencies, SortOptions.SILENT);
+            Collections.reverse(sortedDependencies);
+
+            handleTransiviteEviction(md, confs, data, sortedDependencies);
+
+            return (IvyNode[]) dependencies.toArray(new IvyNode[dependencies.size()]);
+        } finally {
+            IvyContext.popContext();
+        }
+    }
+
+    private void handleTransiviteEviction(ModuleDescriptor md, String[] confs, ResolveData data,
+            List sortedDependencies) {
+        // handle transitive eviction now:
+        // if a module has been evicted then all its dependencies required only by it should be
+        // evicted too. Since nodes are now sorted from the more dependent to the less one, we
+        // can traverse the list and check only the direct parent and not all the ancestors
+        for (ListIterator iter = sortedDependencies.listIterator(); iter.hasNext();) {
+            IvyNode node = (IvyNode) iter.next();
+            if (!node.isCompletelyEvicted()) {
+                for (int i = 0; i < confs.length; i++) {
+                    IvyNodeCallers.Caller[] callers = node.getCallers(confs[i]);
+                    if (settings.debugConflictResolution()) {
+                        Message.debug("checking if " + node.getId()
+                                + " is transitively evicted in " + confs[i]);
+                    }
+                    boolean allEvicted = callers.length > 0;
+                    for (int j = 0; j < callers.length; j++) {
+                        if (callers[j].getModuleRevisionId().equals(md.getModuleRevisionId())) {
+                            // the caller is the root module itself, it can't be evicted
+                            allEvicted = false;
+                            break;
+                        } else {
+                            IvyNode callerNode = data.getNode(callers[j].getModuleRevisionId());
+                            if (callerNode == null) {
+                                Message.warn("ivy internal error: no node found for "
+                                        + callers[j].getModuleRevisionId() + ": looked in "
+                                        + data.getNodeIds() + " and root module id was "
+                                        + md.getModuleRevisionId());
+                            } else if (!callerNode.isEvicted(confs[i])) {
+                                allEvicted = false;
+                                break;
+                            } else {
+                                if (settings.debugConflictResolution()) {
+                                    Message.debug("caller " + callerNode.getId() + " of "
+                                            + node.getId() + " is evicted");
+                                }
+                            }
+                        }
+                    }
+                    if (allEvicted) {
+                        Message.verbose("all callers are evicted for " + node + ": evicting too");
+                        node.markEvicted(confs[i], null, null, null);
+                    } else {
+                        if (settings.debugConflictResolution()) {
+                            Message.debug(node.getId()
+                                    + " isn't transitively evicted, at least one caller was"
+                                    + " not evicted");
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private void fetchDependencies(VisitNode node, String conf, boolean shouldBePublic) {
+        checkInterrupted();
+        long start = System.currentTimeMillis();
+        if (node.getParent() != null) {
+            Message.verbose("== resolving dependencies " + node.getParent().getId() + "->"
+                    + node.getId() + " [" + node.getParentConf() + "->" + conf + "]");
+        } else {
+            Message.verbose("== resolving dependencies for " + node.getId() + " [" + conf + "]");
+        }
+        ResolveData data = node.getNode().getData();
+        VisitNode parentVisitNode = data.getCurrentVisitNode();
+
+        data.setCurrentVisitNode(node);
+        DependencyDescriptor dd = node.getDependencyDescriptor();
+        VersionMatcher versionMatcher = node.getNode().getData().getSettings().getVersionMatcher();
+        if (dd != null
+                && !(node.getRoot() == node.getParent() && versionMatcher.isDynamic(dd
+                        .getDependencyRevisionId()))) {
+            /*
+             * we don't resolve conflicts before loading data for direct dependencies on dynamic
+             * revisions, so that direct dynamic revisions are always resolved, which is mandatory
+             * for proper replacement of dynamic revisions during 'deliver'
+             */
+            resolveConflict(node, conf);
+        }
+
+        if (node.loadData(conf, shouldBePublic)) {
+            // we resolve conflict again now that we have all information loaded
+            // indeed in some cases conflict manager need more information than just asked
+            // dependency to take the decision
+            resolveConflict(node, conf);
+            if (!node.isEvicted() && !node.isCircular()) {
+                String[] confs = node.getRealConfs(conf);
+                for (int i = 0; i < confs.length; i++) {
+                    doFetchDependencies(node, confs[i]);
+                }
+            }
+        } else if (!node.hasProblem()) {
+            // the node has not been loaded but hasn't problem: it was already loaded
+            // => we just have to update its dependencies data
+            if (!node.isEvicted() && !node.isCircular()) {
+                String[] confs = node.getRealConfs(conf);
+                for (int i = 0; i < confs.length; i++) {
+                    doFetchDependencies(node, confs[i]);
+                }
+            }
+        }
+        if (node.isEvicted()) {
+            // update selected nodes with confs asked in evicted one
+            EvictionData ed = node.getEvictedData();
+            if (ed.getSelected() != null) {
+                for (Iterator iter = ed.getSelected().iterator(); iter.hasNext();) {
+                    IvyNode selected = (IvyNode) iter.next();
+                    if (!selected.isLoaded()) {
+                        // the node is not yet loaded, we can simply update its set of
+                        // configurations to fetch
+                        selected.updateConfsToFetch(Collections.singleton(conf));
+                    } else {
+                        // the node has already been loaded, we must fetch its dependencies in the
+                        // required conf
+                        fetchDependencies(node.gotoNode(selected), conf, true);
+                    }
+                }
+            }
+        }
+        if (settings.debugConflictResolution()) {
+            Message.debug(node.getId() + " => dependencies resolved in " + conf + " ("
+                    + (System.currentTimeMillis() - start) + "ms)");
+        }
+        data.setCurrentVisitNode(parentVisitNode);
+    }
+
+    private void doFetchDependencies(VisitNode node, String conf) {
+        Configuration c = node.getConfiguration(conf);
+        if (c == null) {
+            if (!node.isConfRequiredByMergedUsageOnly(conf)) {
+                Message.warn("configuration not found '" + conf + "' in " + node.getResolvedId()
+                        + ": ignoring");
+                if (node.getParent() != null) {
+                    Message.warn("it was required from " + node.getParent().getResolvedId());
+                }
+            }
+            return;
+        }
+        // we handle the case where the asked configuration extends others:
+        // we have to first fetch the extended configurations
+
+        // first we check if this is the actual requested conf (not an extended one)
+        boolean requestedConfSet = false;
+        if (node.getRequestedConf() == null) {
+            node.setRequestedConf(conf);
+            requestedConfSet = true;
+        }
+        // now let's recurse in extended confs
+        String[] extendedConfs = c.getExtends();
+        if (extendedConfs.length > 0) {
+            node.updateConfsToFetch(Arrays.asList(extendedConfs));
+        }
+        for (int i = 0; i < extendedConfs.length; i++) {
+            fetchDependencies(node, extendedConfs[i], false);
+        }
+
+        // now we can actually resolve this configuration dependencies
+        if (!isDependenciesFetched(node.getNode(), conf) && node.isTransitive()) {
+            Collection/* <VisitNode> */dependencies = node.getDependencies(conf);
+            for (Iterator iter = dependencies.iterator(); iter.hasNext();) {
+                VisitNode dep = (VisitNode) iter.next();
+                dep.useRealNode(); // the node may have been resolved to another real one while
+                // resolving other deps
+                String[] confs = dep.getRequiredConfigurations(node, conf);
+                for (int i = 0; i < confs.length; i++) {
+                    fetchDependencies(dep, confs[i], true);
+                }
+                if (!dep.isEvicted() && !dep.hasProblem()) {
+                    // if there are still confs to fetch (usually because they have
+                    // been updated when evicting another module), we fetch them now
+                    confs = dep.getConfsToFetch();
+                    for (int i = 0; i < confs.length; i++) {
+                        // shouldBeFixed=false to because some of those dependencies might
+                        // be private when they were actually extending public conf.
+                        // Should we keep two list of confs to fetch (private&public)?
+                        // I don't think, visibility is already checked, and a change in the
+                        // configuration between version might anyway have worse problems.
+                        fetchDependencies(dep, confs[i], false);
+                    }
+                }
+            }
+            markDependenciesFetched(node.getNode(), conf);
+        }
+        // we have finiched with this configuration, if it was the original requested conf
+        // we can clean it now
+        if (requestedConfSet) {
+            node.setRequestedConf(null);
+        }
+
+    }
+
+    /**
+     * Returns true if we've already fetched the dependencies for this node and configuration
+     * 
+     * @param node
+     *            node to check
+     * @param conf
+     *            configuration to check
+     * @return true if we've already fetched this dependency
+     */
+    private boolean isDependenciesFetched(IvyNode node, String conf) {
+        String key = getDependenciesFetchedKey(node, conf);
+        return fetchedSet.contains(key);
+    }
+
+    private void markDependenciesFetched(IvyNode node, String conf) {
+        String key = getDependenciesFetchedKey(node, conf);
+        fetchedSet.add(key);
+    }
+
+    private String getDependenciesFetchedKey(IvyNode node, String conf) {
+        ModuleRevisionId moduleRevisionId = node.getResolvedId();
+        String key = moduleRevisionId.getOrganisation() + "|" + moduleRevisionId.getName() + "|"
+                + moduleRevisionId.getRevision() + "|" + conf;
+        return key;
+    }
+
+    private void resolveConflict(VisitNode node, String conf) {
+        resolveConflict(node, node.getParent(), conf, Collections.EMPTY_SET);
+    }
+
+    /**
+     * Resolves conflict for the given node in the given ancestor. This method do conflict
+     * resolution in ancestor parents recursively, unless not necessary.
+     * 
+     * @param node
+     *            the node for which conflict resolution should be done
+     * @param ancestor
+     *            the ancestor in which the conflict resolution should be done
+     * @param toevict
+     *            a collection of IvyNode to evict (as computed by conflict resolution in
+     *            descendants of ancestor)
+     * @return true if conflict resolution has been done, false it can't be done yet
+     */
+    private boolean resolveConflict(VisitNode node, VisitNode ancestor, String conf,
+            Collection toevict) {
+        if (ancestor == null || node == ancestor) {
+            return true;
+        }
+        // check if job is not already done
+        if (checkConflictSolvedEvicted(node, ancestor)) {
+            // job is done and node is evicted, nothing to do
+            return true;
+        }
+        boolean debugConflictResolution = settings.debugConflictResolution();
+        if (checkConflictSolvedSelected(node, ancestor)) {
+            // job is done and node is selected, nothing to do for this ancestor, but we still have
+            // to check higher levels, for which conflict resolution might have been impossible
+            // before
+            if (resolveConflict(node, ancestor.getParent(), conf, toevict)) {
+                // now that conflict resolution is ok in ancestors
+                // we just have to check if the node wasn't previously evicted in root ancestor
+                EvictionData evictionData = node.getEvictionDataInRoot(node.getRootModuleConf(),
+                    ancestor);
+                if (evictionData != null) {
+                    // node has been previously evicted in an ancestor: we mark it as evicted
+                    if (debugConflictResolution) {
+                        Message.debug(node + " was previously evicted in root module conf "
+                                + node.getRootModuleConf());
+                    }
+                    node.markEvicted(evictionData);
+                    if (debugConflictResolution) {
+                        Message.debug("evicting " + node + " by " + evictionData);
+                    }
+                }
+                return true;
+            } else {
+                return false;
+            }
+        }
+
+        // compute conflicts
+        Set resolvedNodes = ancestor.getNode().getResolvedNodes(node.getModuleId(),
+            node.getRootModuleConf());
+        resolvedNodes.addAll(ancestor.getNode().getPendingConflicts(node.getRootModuleConf(),
+            node.getModuleId()));
+        Collection conflicts = computeConflicts(node, ancestor, conf, toevict, resolvedNodes);
+
+        ConflictManager conflictManager = ancestor.getNode().getConflictManager(node.getModuleId());
+
+        Collection resolved = resolveConflicts(node, ancestor, conflicts, conflictManager);
+
+        if (resolved == null) {
+            if (debugConflictResolution) {
+                Message.debug("impossible to resolve conflicts for " + node + " in " + ancestor
+                        + " yet");
+                Message.debug("setting all nodes as pending conflicts for later conflict"
+                        + " resolution: " + conflicts);
+            }
+            ancestor.getNode().setPendingConflicts(node.getModuleId(), node.getRootModuleConf(),
+                conflicts);
+            return false;
+        }
+
+        if (debugConflictResolution) {
+            Message.debug("selected revisions for " + node + " in " + ancestor + ": " + resolved);
+        }
+        if (resolved.contains(node.getNode())) {
+            // node has been selected for the current parent
+
+            // handle previously selected nodes that are now evicted by this new node
+            toevict = resolvedNodes;
+            toevict.removeAll(resolved);
+
+            for (Iterator iter = toevict.iterator(); iter.hasNext();) {
+                IvyNode te = (IvyNode) iter.next();
+                te.markEvicted(node.getRootModuleConf(), ancestor.getNode(), conflictManager,
+                    resolved);
+
+                if (debugConflictResolution) {
+                    Message.debug("evicting " + te + " by "
+                            + te.getEvictedData(node.getRootModuleConf()));
+                }
+            }
+
+            // it's very important to update resolved and evicted nodes BEFORE recompute parent call
+            // to allow it to recompute its resolved collection with correct data
+            // if necessary
+            ancestor.getNode().setResolvedNodes(node.getModuleId(), node.getRootModuleConf(),
+                resolved);
+
+            Collection evicted = new HashSet(ancestor.getNode().getEvictedNodes(node.getModuleId(),
+                node.getRootModuleConf()));
+            evicted.removeAll(resolved);
+            evicted.addAll(toevict);
+            ancestor.getNode().setEvictedNodes(node.getModuleId(), node.getRootModuleConf(),
+                evicted);
+            ancestor.getNode().setPendingConflicts(node.getModuleId(), node.getRootModuleConf(),
+                Collections.EMPTY_SET);
+
+            return resolveConflict(node, ancestor.getParent(), conf, toevict);
+        } else {
+            // node has been evicted for the current parent
+            if (resolved.isEmpty()) {
+                if (debugConflictResolution) {
+                    Message.verbose("conflict manager '" + conflictManager
+                            + "' evicted all revisions among " + conflicts);
+                }
+            }
+
+            // it's time to update parent resolved and evicted with what was found
+
+            Collection evicted = new HashSet(ancestor.getNode().getEvictedNodes(node.getModuleId(),
+                node.getRootModuleConf()));
+            toevict.removeAll(resolved);
+            evicted.removeAll(resolved);
+            evicted.addAll(toevict);
+            evicted.add(node.getNode());
+            ancestor.getNode().setEvictedNodes(node.getModuleId(), node.getRootModuleConf(),
+                evicted);
+            ancestor.getNode().setPendingConflicts(node.getModuleId(), node.getRootModuleConf(),
+                Collections.EMPTY_SET);
+
+            node.markEvicted(ancestor, conflictManager, resolved);
+            if (debugConflictResolution) {
+                Message.debug("evicting " + node + " by " + node.getEvictedData());
+            }
+
+            // if resolved changed we have to go up in the graph
+            Collection prevResolved = ancestor.getNode().getResolvedNodes(node.getModuleId(),
+                node.getRootModuleConf());
+            boolean solved = true;
+            if (!prevResolved.equals(resolved)) {
+                ancestor.getNode().setResolvedNodes(node.getModuleId(), node.getRootModuleConf(),
+                    resolved);
+                for (Iterator iter = resolved.iterator(); iter.hasNext();) {
+                    IvyNode sel = (IvyNode) iter.next();
+                    if (!prevResolved.contains(sel)) {
+                        solved &= resolveConflict(node.gotoNode(sel), ancestor.getParent(), conf,
+                            toevict);
+                    }
+                }
+            }
+            return solved;
+        }
+    }
+
+    private Collection resolveConflicts(VisitNode node, VisitNode ancestor, Collection conflicts,
+            ConflictManager conflictManager) {
+        if (node.getParent() != ancestor
+        // we are not handling the direct parent
+
+                && conflictManager == settings.getConflictManager(node.getModuleId())
+                // the conflict manager is the default one
+
+                && node.getParent().getNode()
+                        .getResolvedNodes(node.getModuleId(), node.getRootModuleConf())
+                        .equals(conflicts)
+        // there is no new conflict in this ancestor
+
+        ) {
+            // IVY-465 case
+            if (settings.debugConflictResolution()) {
+                Message.debug("no new conflicting revisions for " + node + " in " + ancestor + ": "
+                        + conflicts);
+            }
+
+            return conflicts;
+        } else {
+            if (settings.debugConflictResolution()) {
+                Message.debug("found conflicting revisions for " + node + " in " + ancestor + ": "
+                        + conflicts);
+            }
+
+            return conflictManager.resolveConflicts(ancestor.getNode(), conflicts);
+        }
+    }
+
+    /**
+     * Compute possible conflicts for a node, in the context of an ancestor (a node which has a
+     * dependency - direct or indirect - on the node for which conflicts should be computed.
+     * 
+     * @param node
+     *            the node for which conflicts should be computed
+     * @param ancestor
+     *            the ancestor in which conflicts should be computed
+     * @param conf
+     *            the configuration of the node in which conflicts should be computed
+     * @param toevict
+     *            a collection of nodes which have been evicted during conflict resolution at lower
+     *            level. It may be empty if no conflict resolution has occurred for this node yet,
+     *            or if no node has been evicted.
+     * @param selectedNodes
+     *            a collection of nodes selected during previous conflict resolution for the given
+     *            node and ancestor. This collection is updated by this call, removing nodes which
+     *            should be evicted.
+     * @return a collection of IvyNode which may be in conflict with the given node in the given
+     *         ancestor. This collection always contain at least the given node.
+     */
+    private Collection computeConflicts(VisitNode node, VisitNode ancestor, String conf,
+            Collection toevict, Collection selectedNodes) {
+        Collection conflicts = new LinkedHashSet();
+        conflicts.add(node.getNode());
+        /*
+         * We first try to remove all evicted nodes from the collection of selected nodes to update
+         * this collection. If the collection changes, it means that it contained evicted nodes, and
+         * thus is not up to date.
+         */
+        boolean evictedInSelected = selectedNodes.removeAll(toevict);
+        /*
+         * Another case where we need to deeply compute selected nodes is when selectedNodes is
+         * empty (not computed yet) and we aren't in the context of the direct parent of the node.
+         */
+        if (evictedInSelected
+                || (selectedNodes.isEmpty() && !node.getParent().getNode()
+                        .equals(ancestor.getNode()))) {
+            IvyContext context = IvyContext.getContext();
+            ResolveData data = context.getResolveData();
+            VisitNode oldVisitNode = data.getCurrentVisitNode();
+            data.setCurrentVisitNode(ancestor);
+            try {
+                // In this case we need to compute selected nodes again.
+                Collection deps = ancestor.getNode().getDependencies(node.getRootModuleConf(),
+                    ancestor.getNode().getConfigurations(node.getRootModuleConf()),
+                    ancestor.getRequestedConf());
+                for (Iterator iter = deps.iterator(); iter.hasNext();) {
+                    IvyNode dep = (IvyNode) iter.next();
+                    if (dep.getModuleId().equals(node.getModuleId())) {
+                        conflicts.add(dep);
+                    }
+                    conflicts.addAll(dep.getResolvedNodes(node.getModuleId(),
+                        node.getRootModuleConf()));
+                }
+            } finally {
+                data.setCurrentVisitNode(oldVisitNode);
+            }
+        } else if (selectedNodes.isEmpty()) {
+            /*
+             * No selected nodes at all yet, and we are in the context of the direct parent
+             * (otherwise previous block would have been reached). We can compute conflicts based on
+             * the parent direct dependencies in current root module conf.
+             */
+            VisitNode parent = node.getParent();
+            Collection parentDepIvyNodes = parent.getNode().getDependencies(
+                node.getRootModuleConf(),
+                parent.getNode().getConfigurations(node.getRootModuleConf()),
+                parent.getRequestedConf());
+            for (Iterator it = parentDepIvyNodes.iterator(); it.hasNext();) {
+                IvyNode parentDep = (IvyNode) it.next();
+                if (parentDep.getModuleId().equals(node.getModuleId())) {
+                    conflicts.add(parentDep);
+                }
+            }
+        } else {
+            conflicts.addAll(selectedNodes);
+        }
+        return conflicts;
+    }
+
+    private boolean checkConflictSolvedSelected(VisitNode node, VisitNode ancestor) {
+        if (ancestor.getResolvedRevisions(node.getModuleId()).contains(node.getResolvedId())) {
+            // resolve conflict has already be done with node with the same id
+            if (settings.debugConflictResolution()) {
+                Message.debug("conflict resolution already done for " + node + " in " + ancestor);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    private boolean checkConflictSolvedEvicted(VisitNode node, VisitNode ancestor) {
+        if (ancestor.getEvictedRevisions(node.getModuleId()).contains(node.getResolvedId())) {
+            // resolve conflict has already be done with node with the same id
+            if (settings.debugConflictResolution()) {
+                Message.debug("conflict resolution already done for " + node + " in " + ancestor);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    public ResolvedModuleRevision findModule(ModuleRevisionId id, ResolveOptions options) {
+        DependencyResolver r = settings.getResolver(id);
+        if (r == null) {
+            throw new IllegalStateException("no resolver found for " + id.getModuleId());
+        }
+        DefaultModuleDescriptor md = DefaultModuleDescriptor.newCallerInstance(id,
+            new String[] {"*"}, false, false);
+
+        if (options.getResolveId() == null) {
+            options.setResolveId(ResolveOptions.getDefaultResolveId(md));
+        }
+
+        try {
+            return r.getDependency(new DefaultDependencyDescriptor(id, true), new ResolveData(this,
+                    options, new ConfigurationResolveReport(this, md, "default", null, options)));
+        } catch (ParseException e) {
+            throw new RuntimeException("problem while parsing repository module descriptor for "
+                    + id + ": " + e, e);
+        }
+    }
+
+    /**
+     * Mediates the given dependency descriptor according to given options.
+     * <p>
+     * The mediated dependency descriptor must return the actually requested module revision id when
+     * the method {@link DependencyDescriptor#getDependencyRevisionId()} is called.
+     * </p>
+     * 
+     * @param dd
+     *            the dependency descriptor for which the requested module revision id should be
+     *            returned
+     * @param options
+     *            the resolve options to use
+     * @return the mediated {@link DependencyDescriptor}.
+     */
+    public DependencyDescriptor mediate(DependencyDescriptor dd, ResolveOptions options) {
+        if (dd == null) {
+            return null;
+        }
+        String resolveMode = options.getResolveMode() == null ? settings.getResolveMode(dd
+                .getDependencyId()) : options.getResolveMode();
+        if (ResolveOptions.RESOLVEMODE_DYNAMIC.equals(resolveMode)
+                && !dd.getDynamicConstraintDependencyRevisionId().equals(
+                    dd.getDependencyRevisionId())) {
+            // the dynamicRevId can contain a null branch, so make sure this
+            // has been replaced by the default branch (if any!)
+            return dd.clone(ModuleRevisionId.newInstance(dd
+                    .getDynamicConstraintDependencyRevisionId(), dd
+                    .getDynamicConstraintDependencyRevisionId().getRevision()));
+        } else {
+            return dd;
+        }
+    }
+
+    public EventManager getEventManager() {
+        return eventManager;
+    }
+
+    public ResolveEngineSettings getSettings() {
+        return settings;
+    }
+
+    public SortEngine getSortEngine() {
+        return sortEngine;
+    }
+
+    private void checkInterrupted() {
+        IvyContext.getContext().getIvy().checkInterrupted();
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/resolve/ResolveEngineSettings.java b/src/java/org/apache/ivy/core/resolve/ResolveEngineSettings.java
new file mode 100644
index 0000000..34ff9fa
--- /dev/null
+++ b/src/java/org/apache/ivy/core/resolve/ResolveEngineSettings.java
@@ -0,0 +1,45 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.resolve;
+
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.plugins.conflict.ConflictManager;
+import org.apache.ivy.plugins.report.ReportOutputter;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.apache.ivy.plugins.resolver.ResolverSettings;
+
+public interface ResolveEngineSettings extends ResolverSettings {
+
+    void setDictatorResolver(DependencyResolver dictatorResolver);
+
+    boolean debugConflictResolution();
+
+    ReportOutputter[] getReportOutputters();
+
+    String getResolverName(ModuleRevisionId mid);
+
+    boolean logNotConvertedExclusionRule();
+
+    ConflictManager getConflictManager(ModuleId mid);
+
+    boolean logModuleWhenFound();
+
+    boolean logResolvedRevision();
+
+}
diff --git a/src/java/org/apache/ivy/core/resolve/ResolveOptions.java b/src/java/org/apache/ivy/core/resolve/ResolveOptions.java
new file mode 100644
index 0000000..9ffe43f
--- /dev/null
+++ b/src/java/org/apache/ivy/core/resolve/ResolveOptions.java
@@ -0,0 +1,312 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.resolve;
+
+import java.util.Date;
+
+import org.apache.ivy.core.LogOptions;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.util.ConfigurationUtils;
+import org.apache.ivy.util.filter.Filter;
+import org.apache.ivy.util.filter.FilterHelper;
+
+/**
+ * A set of options used during resolve related tasks
+ * 
+ * @see ResolveEngine
+ */
+public class ResolveOptions extends LogOptions {
+
+    /**
+     * Default resolve mode, using default revision constraints in dependency descriptors.
+     */
+    public static final String RESOLVEMODE_DEFAULT = "default";
+
+    /**
+     * Dynamic resolve mode, using dynamic revision constraints in dependency descriptors.
+     */
+    public static final String RESOLVEMODE_DYNAMIC = "dynamic";
+
+    /**
+     * Array of all available resolve modes.
+     */
+    public static final String[] RESOLVEMODES = new String[] {RESOLVEMODE_DEFAULT,
+            RESOLVEMODE_DYNAMIC};
+
+    /**
+     * an array of configuration names to resolve - must not be null nor empty
+     */
+    private String[] confs = new String[] {"*"};
+
+    /**
+     * the revision of the module for which dependencies should be resolved. This revision is
+     * considered as the resolved revision of the module, unless it is null. If it is null, then a
+     * default revision is given if necessary (no revision found in ivy file)
+     */
+    private String revision = null;
+
+    /**
+     * the date for which the dependencies should be resolved. All obtained artifacts should have a
+     * publication date which is before or equal to the given date. The date can be null, in which
+     * case all artifacts will be considered
+     */
+    private Date date = null;
+
+    /**
+     * True if validation of module descriptors should done, false otherwise
+     */
+    private boolean validate = true;
+
+    /**
+     * True if only the cache should be used for resolve, false if a real resolve with dependency
+     * resolvers should be done
+     */
+    private boolean useCacheOnly = false;
+
+    /**
+     * True if the dependencies should be resolved transitively, false if only direct dependencies
+     * should be resolved
+     */
+    private boolean transitive = true;
+
+    /**
+     * True if the resolve should also download artifacts, false if only dependency resolution with
+     * module descriptors should be done
+     */
+    private boolean download = true;
+
+    /**
+     * True if a report of the resolve process should be output at the end of the process, false
+     * otherwise
+     */
+    private boolean outputReport = true;
+
+    /**
+     * A filter to use to avoid downloading all artifacts.
+     */
+    private Filter artifactFilter = FilterHelper.NO_FILTER;
+
+    /**
+     * The resolve mode to use. Should be one of {@link #RESOLVEMODES}, or <code>null</code> to use
+     * settings configured resolve mode.
+     */
+    private String resolveMode;
+
+    /**
+     * The id used to store the resolve information.
+     */
+    private String resolveId;
+
+    private boolean refresh;
+
+    /**
+     * True if the resolve should compare the new resolution against the previous report
+     **/
+    private boolean checkIfChanged = false;
+
+    public ResolveOptions() {
+    }
+
+    public ResolveOptions(ResolveOptions options) {
+        super(options);
+        confs = options.confs;
+        revision = options.revision;
+        date = options.date;
+        validate = options.validate;
+        refresh = options.refresh;
+        useCacheOnly = options.useCacheOnly;
+        transitive = options.transitive;
+        download = options.download;
+        outputReport = options.outputReport;
+        resolveMode = options.resolveMode;
+        artifactFilter = options.artifactFilter;
+        resolveId = options.resolveId;
+        checkIfChanged = options.checkIfChanged;
+    }
+
+    public Filter getArtifactFilter() {
+        return artifactFilter;
+    }
+
+    public ResolveOptions setArtifactFilter(Filter artifactFilter) {
+        this.artifactFilter = artifactFilter;
+        return this;
+    }
+
+    /**
+     * Returns the resolve mode to use, or <code>null</code> to use settings configured resolve
+     * mode.
+     * 
+     * @return the resolve mode to use.
+     */
+    public String getResolveMode() {
+        return resolveMode;
+    }
+
+    public ResolveOptions setResolveMode(String resolveMode) {
+        this.resolveMode = resolveMode;
+        return this;
+    }
+
+    /**
+     * Indicates if the configurations use a special configuration * , *(private) or *(public). When
+     * special configurations are used, you must have the module descriptor in order to get the list
+     * of configurations.
+     * 
+     * @see #getConfs()
+     * @see #getConfs(ModuleDescriptor)
+     */
+    public boolean useSpecialConfs() {
+        for (int i = 0; confs != null && i < confs.length; i++) {
+            if (confs[0].startsWith("*")) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @pre can only be called if useSpecialConfs()==false. When it is true, you have to provide a
+     *      module desciptor so that configurations can be resolved.
+     * @see #getConfs(ModuleDescriptor)
+     */
+    public String[] getConfs() {
+        if (useSpecialConfs()) {
+            throw new AssertionError("ResolveOptions.getConfs() "
+                    + "can not be used for options used special confs.");
+        }
+        return confs;
+    }
+
+    /**
+     * Get the aksed confs. Special confs (like *) use the moduleDescriptor to find the values *
+     * 
+     * @param md
+     *            Used to get the exact values for special confs.
+     * */
+    public String[] getConfs(ModuleDescriptor md) {
+        // TODO add isInline, in that case, replace * by *(public).
+        return ConfigurationUtils.replaceWildcards(confs, md);
+    }
+
+    public ResolveOptions setConfs(String[] confs) {
+        this.confs = confs;
+        return this;
+    }
+
+    public Date getDate() {
+        return date;
+    }
+
+    public ResolveOptions setDate(Date date) {
+        this.date = date;
+        return this;
+    }
+
+    public boolean isDownload() {
+        return download;
+    }
+
+    public ResolveOptions setDownload(boolean download) {
+        this.download = download;
+        return this;
+    }
+
+    public boolean isOutputReport() {
+        return outputReport;
+    }
+
+    public ResolveOptions setOutputReport(boolean outputReport) {
+        this.outputReport = outputReport;
+        return this;
+    }
+
+    public boolean isTransitive() {
+        return transitive;
+    }
+
+    public ResolveOptions setTransitive(boolean transitive) {
+        this.transitive = transitive;
+        return this;
+    }
+
+    public boolean isUseCacheOnly() {
+        return useCacheOnly;
+    }
+
+    public ResolveOptions setUseCacheOnly(boolean useCacheOnly) {
+        this.useCacheOnly = useCacheOnly;
+        return this;
+    }
+
+    public boolean isValidate() {
+        return validate;
+    }
+
+    public ResolveOptions setValidate(boolean validate) {
+        this.validate = validate;
+        return this;
+    }
+
+    public String getRevision() {
+        return revision;
+    }
+
+    public ResolveOptions setRevision(String revision) {
+        this.revision = revision;
+        return this;
+    }
+
+    public String getResolveId() {
+        return resolveId;
+    }
+
+    public ResolveOptions setResolveId(String resolveId) {
+        this.resolveId = resolveId;
+        return this;
+    }
+
+    public ResolveOptions setRefresh(boolean refresh) {
+        this.refresh = refresh;
+        return this;
+    }
+
+    public boolean isRefresh() {
+        return refresh;
+    }
+
+    public ResolveOptions setCheckIfChanged(boolean checkIfChanged) {
+        this.checkIfChanged = checkIfChanged;
+        return this;
+    }
+
+    public boolean getCheckIfChanged() {
+        return checkIfChanged;
+    }
+
+    public static String getDefaultResolveId(ModuleDescriptor md) {
+        ModuleId module = md.getModuleRevisionId().getModuleId();
+        return getDefaultResolveId(module);
+    }
+
+    public static String getDefaultResolveId(ModuleId moduleId) {
+        return moduleId.getOrganisation() + "-" + moduleId.getName();
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/resolve/ResolveProcessException.java b/src/java/org/apache/ivy/core/resolve/ResolveProcessException.java
new file mode 100644
index 0000000..07ccea8
--- /dev/null
+++ b/src/java/org/apache/ivy/core/resolve/ResolveProcessException.java
@@ -0,0 +1,46 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.resolve;
+
+/**
+ * ResolveProcessException is an exception which is used to control the resolve process.
+ * <p>
+ * All {@link ResolveProcessException} subclasses have the ability to interrupt the resolve process
+ * when thrown while resolving dependencies, instead of only marking the module with a problem and
+ * continuing the resolve process as part of the best effort strategy during resolve process.
+ * </p>
+ * Some subclasses have even a stronger power over the resolve process, like
+ * {@link RestartResolveProcess} which orders to restart the resolve process at the start.
+ */
+public class ResolveProcessException extends RuntimeException {
+
+    public ResolveProcessException() {
+    }
+
+    public ResolveProcessException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public ResolveProcessException(String message) {
+        super(message);
+    }
+
+    public ResolveProcessException(Throwable cause) {
+        super(cause);
+    }
+}
diff --git a/src/java/org/apache/ivy/core/resolve/ResolvedModuleRevision.java b/src/java/org/apache/ivy/core/resolve/ResolvedModuleRevision.java
new file mode 100644
index 0000000..e25b21f
--- /dev/null
+++ b/src/java/org/apache/ivy/core/resolve/ResolvedModuleRevision.java
@@ -0,0 +1,143 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.resolve;
+
+import java.util.Date;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.report.MetadataArtifactDownloadReport;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+
+/**
+ * Represents a module revision provisioned on the local filesystem.
+ */
+public class ResolvedModuleRevision {
+
+    private DependencyResolver resolver;
+
+    private DependencyResolver artifactResolver;
+
+    private ModuleDescriptor descriptor;
+
+    private MetadataArtifactDownloadReport report;
+
+    private boolean force = false;
+
+    public ResolvedModuleRevision(DependencyResolver resolver, DependencyResolver artifactResolver,
+            ModuleDescriptor descriptor, MetadataArtifactDownloadReport report) {
+        this.resolver = resolver;
+        this.artifactResolver = artifactResolver;
+        this.descriptor = descriptor;
+        this.report = report;
+    }
+
+    public ResolvedModuleRevision(DependencyResolver resolver, DependencyResolver artifactResolver,
+            ModuleDescriptor descriptor, MetadataArtifactDownloadReport report, boolean force) {
+        this.resolver = resolver;
+        this.artifactResolver = artifactResolver;
+        this.descriptor = descriptor;
+        this.report = report;
+        this.force = force;
+    }
+
+    /**
+     * Returns the identifier of the resolved module.
+     * 
+     * @return the identifier of the resolved module.
+     */
+    public ModuleRevisionId getId() {
+        return descriptor.getResolvedModuleRevisionId();
+    }
+
+    /**
+     * Returns the date of publication of the resolved module.
+     * 
+     * @return the date of publication of the resolved module.
+     */
+    public Date getPublicationDate() {
+        return descriptor.getResolvedPublicationDate();
+    }
+
+    /**
+     * Returns the descriptor of the resolved module.
+     * 
+     * @return the descriptor of the resolved module.
+     */
+    public ModuleDescriptor getDescriptor() {
+        return descriptor;
+    }
+
+    /**
+     * The resolver which resolved this ResolvedModuleRevision
+     * 
+     * @return The resolver which resolved this ResolvedModuleRevision
+     */
+    public DependencyResolver getResolver() {
+        return resolver;
+    }
+
+    /**
+     * The resolver to use to download artifacts
+     * 
+     * @return The resolver to use to download artifacts
+     */
+    public DependencyResolver getArtifactResolver() {
+        return artifactResolver;
+    }
+
+    /**
+     * Returns a report of the resolved module metadata artifact provisioning.
+     * 
+     * @return a report of the resolved module metadata artifact provisioning.
+     */
+    public MetadataArtifactDownloadReport getReport() {
+        return report;
+    }
+
+    /**
+     * Returns <code>true</code> if this resolved module revision should be forced as the one being
+     * returned.
+     * <p>
+     * This is used as an indication for CompositeResolver, to know if they should continue to look
+     * for a better ResolvedModuleRevision if possible, or stop with this instance.
+     * </p>
+     * 
+     * @return <code>true</code> if this resolved module revision should be forced as the one being
+     *         returned.
+     */
+    public boolean isForce() {
+        return force;
+    }
+
+    public boolean equals(Object obj) {
+        if (!(obj instanceof ResolvedModuleRevision)) {
+            return false;
+        }
+        return ((ResolvedModuleRevision) obj).getId().equals(getId());
+    }
+
+    public int hashCode() {
+        return getId().hashCode();
+    }
+
+    public String toString() {
+        return getId().toString();
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/resolve/RestartResolveProcess.java b/src/java/org/apache/ivy/core/resolve/RestartResolveProcess.java
new file mode 100644
index 0000000..c074067
--- /dev/null
+++ b/src/java/org/apache/ivy/core/resolve/RestartResolveProcess.java
@@ -0,0 +1,34 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.resolve;
+
+/**
+ * This RuntimeException is used during the resolve process to ask the engine to restart the resolve
+ * process.
+ * <p>
+ * Users of this feature should be very careful to make sure they handle a termination condition,
+ * since the resolve engine itself won't check the same exception is not thrown ad libitum
+ * </p>
+ */
+public class RestartResolveProcess extends ResolveProcessException {
+
+    public RestartResolveProcess(String message) {
+        super(message);
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/resolve/VisitData.java b/src/java/org/apache/ivy/core/resolve/VisitData.java
new file mode 100644
index 0000000..16a10ab
--- /dev/null
+++ b/src/java/org/apache/ivy/core/resolve/VisitData.java
@@ -0,0 +1,72 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.resolve;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This class is used to store data related to one node of the dependency graph visit. It stores
+ * both an {@link IvyNode} and related {@link VisitNode} objects. Indeed, during the visit of the
+ * graph, the algorithm can visit the same node from several parents, thus requiring several
+ * VisitNode.
+ */
+public class VisitData {
+    /**
+     * A node in the graph of module dependencies resolution
+     */
+    private IvyNode node;
+
+    /**
+     * The associated visit nodes, per rootModuleConf Note that the value is a List, because a node
+     * can be visited from several parents during the resolution process
+     */
+    private Map visitNodes = new HashMap(); // Map (String rootModuleConf -> List(VisitNode))
+
+    public VisitData(IvyNode node) {
+        this.node = node;
+    }
+
+    public void addVisitNode(VisitNode node) {
+        String rootModuleConf = node.getRootModuleConf();
+        getVisitNodes(rootModuleConf).add(node);
+    }
+
+    public List getVisitNodes(String rootModuleConf) {
+        List visits = (List) visitNodes.get(rootModuleConf);
+        if (visits == null) {
+            visits = new ArrayList();
+            visitNodes.put(rootModuleConf, visits);
+        }
+        return visits;
+    }
+
+    public IvyNode getNode() {
+        return node;
+    }
+
+    public void setNode(IvyNode node) {
+        this.node = node;
+    }
+
+    public void addVisitNodes(String rootModuleConf, List visitNodes) {
+        getVisitNodes(rootModuleConf).addAll(visitNodes);
+    }
+}
diff --git a/src/java/org/apache/ivy/core/resolve/VisitNode.java b/src/java/org/apache/ivy/core/resolve/VisitNode.java
new file mode 100644
index 0000000..1547a40
--- /dev/null
+++ b/src/java/org/apache/ivy/core/resolve/VisitNode.java
@@ -0,0 +1,519 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.resolve;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+
+import org.apache.ivy.core.IvyContext;
+import org.apache.ivy.core.module.descriptor.Configuration;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.resolve.IvyNodeEviction.EvictionData;
+import org.apache.ivy.plugins.conflict.ConflictManager;
+import org.apache.ivy.util.Checks;
+
+/**
+ * A visit node is an object used to represent one visit from one parent on an {@link IvyNode} of
+ * the dependency graph. During dependency resolution, the {@link ResolveEngine} visits nodes of the
+ * depency graph following the dependencies, thus the same node can be visited several times, if it
+ * is requested from several module. In this case you will have one VisitNode per parent and per
+ * root module configuration. Thus VisitNode stores data specific to the visit:
+ * <ul>
+ * <li>parent</li>
+ * the node from which the visit is occuring
+ * <li>parentConf</li>
+ * the configuration of the parent in which this node is visited
+ * <li>rootModuleConf</li>
+ * the configuration of the root module which is currently resolved
+ * </ul>
+ */
+public class VisitNode {
+    /**
+     * The node which is currently visited
+     */
+    private IvyNode node;
+
+    /**
+     * Represents the current parent of the node during ivy visit of dependency graph.
+     */
+    private VisitNode parent = null;
+
+    /**
+     * The root node of the current visit It is null until it is required, see getRoot
+     */
+    private VisitNode root = null;
+
+    /**
+     * Direct path from root to this node. Note that the colleciton is ordered but is not a list
+     * implementation This collection is null until it is required, see getPath
+     */
+    private Collection path = null; // Collection(VisitNode)
+
+    /**
+     * The configuration of the parent module in the current visit
+     */
+    private String parentConf = null;
+
+    /**
+     * The configuration requested by the parent Note that this is the actual conf requested by the
+     * parent, not a configuration extended by the requested conf which actually trigger the node
+     * visit
+     */
+    private String requestedConf;
+
+    /**
+     * The root configuration which is currently visited
+     */
+    private String rootModuleConf;
+
+    /**
+     * Shared ResolveData instance, which can be used to get info on the current resolve process
+     */
+    private ResolveData data;
+
+    /**
+     * Boolean.TRUE if a node with a same module id as the one visited has already been visited in
+     * the current path. null if not computed yet Boolean.FALSE otherwise
+     */
+    private Boolean isCircular;
+
+    /**
+     * IvyNode usage information to update when visiting the underlying IvyNode. This is usually the
+     * main IvyNodeUsage of the underlying node, except when we are visiting it coming from an
+     * evicted node replaced by the other one.
+     */
+    private IvyNodeUsage usage;
+
+    public VisitNode(ResolveData data, IvyNode node, VisitNode parent, String rootModuleConf,
+            String parentConf) {
+        this(data, node, parent, rootModuleConf, parentConf, null);
+    }
+
+    public VisitNode(ResolveData data, IvyNode node, VisitNode parent, String rootModuleConf,
+            String parentConf, IvyNodeUsage usage) {
+        Checks.checkNotNull(data, "data");
+        Checks.checkNotNull(node, "node");
+        Checks.checkNotNull(rootModuleConf, "rootModuleConf");
+
+        this.data = data;
+        this.node = node;
+        this.parent = parent;
+        this.rootModuleConf = rootModuleConf;
+        this.parentConf = parentConf;
+        this.usage = usage;
+
+        this.data.register(this);
+    }
+
+    public IvyNode getNode() {
+        return node;
+    }
+
+    /**
+     * @return Returns the configuration requested by the parent
+     */
+    public String getRequestedConf() {
+        return requestedConf;
+    }
+
+    public void setRequestedConf(String requestedConf) {
+        this.requestedConf = requestedConf;
+    }
+
+    public VisitNode getParent() {
+        return parent;
+    }
+
+    public VisitNode getRoot() {
+        if (root == null) {
+            root = computeRoot();
+        }
+        return root;
+    }
+
+    /**
+     * Get an ordered collection with the nodes from the root to this node
+     * 
+     * @return
+     */
+    public Collection/* <VisitNode> */getPath() {
+        if (path == null) {
+            path = computePath();
+        }
+        return path;
+    }
+
+    private Collection/* <VisitNode> */computePath() {
+        if (parent != null) {
+            Collection p = new LinkedHashSet(parent.getPath());
+            p.add(this);
+            return p;
+        } else {
+            return Collections.singletonList(this);
+        }
+    }
+
+    private VisitNode computeRoot() {
+        if (node.isRoot()) {
+            return this;
+        } else if (parent != null) {
+            return parent.getRoot();
+        } else {
+            return null;
+        }
+    }
+
+    public String getParentConf() {
+        return parentConf;
+    }
+
+    public void setParentConf(String parentConf) {
+        this.parentConf = parentConf;
+    }
+
+    public String getRootModuleConf() {
+        return rootModuleConf;
+    }
+
+    public static VisitNode getRoot(VisitNode parent) {
+        VisitNode root = parent;
+        Collection path = new HashSet();
+        path.add(root);
+        while (root.getParent() != null && !root.getNode().isRoot()) {
+            if (path.contains(root.getParent())) {
+                return root;
+            }
+            root = root.getParent();
+            path.add(root);
+        }
+        return root;
+    }
+
+    /**
+     * Returns true if the current dependency descriptor is transitive and the parent configuration
+     * is transitive. Otherwise returns false.
+     * 
+     * @return true if current node is transitive and the parent configuration is transitive.
+     */
+    public boolean isTransitive() {
+        if (node.isRoot()) {
+            // the root node is always considered transitive!
+            return true;
+        }
+
+        if (!data.isTransitive()) {
+            return false;
+        }
+
+        if (!isParentConfTransitive()) {
+            return false;
+        }
+
+        DependencyDescriptor dd = node.getDependencyDescriptor(getParentNode());
+        if ((dd != null) && dd.isTransitive()) {
+            return true;
+        }
+
+        return node.hasAnyMergedUsageWithTransitiveDependency(rootModuleConf);
+    }
+
+    /**
+     * Checks if the current node's parent configuration is transitive.
+     * 
+     * @param node
+     *            current node
+     * @return true if the node's parent configuration is transitive
+     */
+    protected boolean isParentConfTransitive() {
+        String conf = getParent().getRequestedConf();
+        if (conf == null) {
+            return true;
+        }
+        Configuration parentConf = getParentNode().getConfiguration(conf);
+        return parentConf.isTransitive();
+
+    }
+
+    /**
+     * Returns the 'real' node currently visited. 'Real' means that if we are visiting a node
+     * created originally with only a version constraint, and if this version constraint has been
+     * resolved to an existing node in the graph, we will return the existing node, and not the one
+     * originally used which is about to be discarded, since it's not possible to have in the graph
+     * two nodes for the same ModuleRevisionId
+     * 
+     * @return the 'real' node currently visited.
+     */
+    public IvyNode getRealNode() {
+        IvyNode node = this.node.getRealNode();
+        if (node != null) {
+            return node;
+        } else {
+            return this.node;
+        }
+    }
+
+    /**
+     * Ask to the current visited node to use a real node only, if one exist. See getRealNode for
+     * details about what a 'real' node is.
+     */
+    public void useRealNode() {
+        if (parent != null) { // use real node make sense only for non root module
+            IvyNode node = data.getNode(this.node.getId());
+            if (node != null && node != this.node) {
+                this.node = node;
+            }
+        }
+    }
+
+    public boolean loadData(String conf, boolean shouldBePublic) {
+        boolean loaded = node.loadData(rootModuleConf, getParentNode(), parentConf, conf,
+            shouldBePublic, getUsage());
+        if (loaded) {
+            useRealNode();
+
+            // if the loaded revision is different from original one
+            // we now register this node on the new resolved id
+            // this includes two cases:
+            // - the id refers to a dynamic revision, which has been resolved by loadData
+            // - the loaded module descriptor has extra attributes in his info tag which are not
+            // used when declaring the dependency
+            if (data.getNode(node.getResolvedId()) == null
+                    || !data.getNode(node.getResolvedId()).getId().equals(node.getResolvedId())) {
+                data.register(node.getResolvedId(), this);
+            }
+        }
+
+        return loaded;
+    }
+
+    public Collection/* <VisitNode> */getDependencies(String conf) {
+        Collection/* <IvyNode> */deps = node.getDependencies(rootModuleConf, conf, requestedConf);
+        Collection/* <VisitNode> */ret = new ArrayList(deps.size());
+        for (Iterator iter = deps.iterator(); iter.hasNext();) {
+            IvyNode depNode = (IvyNode) iter.next();
+            ret.add(traverseChild(conf, depNode));
+        }
+        return ret;
+    }
+
+    /**
+     * Returns a VisitNode for the given node. The given node must be a representation of the same
+     * module (usually in another revision) as the one visited by this node.
+     * 
+     * @param node
+     *            the node to visit
+     * @return a VisitNode for the given node
+     */
+    VisitNode gotoNode(IvyNode node) {
+        if (!getModuleId().equals(node.getModuleId())) {
+            throw new IllegalArgumentException(
+                    "You can't use gotoNode for a node which does not represent the same Module "
+                            + "as the one represented by this node.\nCurrent node module id="
+                            + getModuleId() + " Given node module id=" + node.getModuleId());
+        }
+        VisitData visitData = data.getVisitData(node.getId());
+        if (visitData != null) {
+            List visitNodes = visitData.getVisitNodes(rootModuleConf);
+            for (Iterator iter = visitNodes.iterator(); iter.hasNext();) {
+                VisitNode vnode = (VisitNode) iter.next();
+                if ((parent == null && vnode.getParent() == null)
+                        || (parent != null && parent.getId().equals(vnode.getParent().getId()))) {
+                    vnode.parentConf = parentConf;
+                    vnode.usage = getUsage();
+                    return vnode;
+                }
+            }
+        }
+        // the node has not yet been visited from the current parent, we create a new visit node
+        return traverse(parent, parentConf, node, getUsage());
+    }
+
+    private IvyNodeUsage getUsage() {
+        return usage == null ? node.getMainUsage() : usage;
+    }
+
+    private VisitNode traverseChild(String parentConf, IvyNode child) {
+        VisitNode parent = this;
+        return traverse(parent, parentConf, child, null);
+    }
+
+    private VisitNode traverse(VisitNode parent, String parentConf, IvyNode node, IvyNodeUsage usage) {
+        if (getPath().contains(node)) {
+            IvyContext.getContext().getCircularDependencyStrategy()
+                    .handleCircularDependency(toMrids(getPath(), node.getId()));
+            // we do not use the new parent, but the first one, to always be able to go up to the
+            // root
+            // parent = getVisitNode(depNode).getParent();
+        }
+        return new VisitNode(data, node, parent, rootModuleConf, parentConf, usage);
+    }
+
+    private ModuleRevisionId[] toMrids(Collection path, ModuleRevisionId last) {
+        ModuleRevisionId[] ret = new ModuleRevisionId[path.size() + 1];
+        int i = 0;
+        for (Iterator iter = path.iterator(); iter.hasNext(); i++) {
+            VisitNode node = (VisitNode) iter.next();
+            ret[i] = node.getNode().getId();
+        }
+        ret[ret.length - 1] = last;
+        return ret;
+    }
+
+    public ModuleRevisionId getResolvedId() {
+        return node.getResolvedId();
+    }
+
+    public void updateConfsToFetch(Collection confs) {
+        node.updateConfsToFetch(confs);
+    }
+
+    public ModuleRevisionId getId() {
+        return node.getId();
+    }
+
+    public boolean isEvicted() {
+        return node.isEvicted(rootModuleConf);
+    }
+
+    public String[] getRealConfs(String conf) {
+        return node.getRealConfs(conf);
+    }
+
+    public boolean hasProblem() {
+        return node.hasProblem();
+    }
+
+    public Configuration getConfiguration(String conf) {
+        return node.getConfiguration(conf);
+    }
+
+    public EvictionData getEvictedData() {
+        return node.getEvictedData(rootModuleConf);
+    }
+
+    public DependencyDescriptor getDependencyDescriptor() {
+        return node.getDependencyDescriptor(getParentNode());
+    }
+
+    private IvyNode getParentNode() {
+        return parent == null ? null : parent.getNode();
+    }
+
+    /**
+     * Returns true if this node can already be found in the path
+     * 
+     * @return
+     */
+    public boolean isCircular() {
+        if (isCircular == null) {
+            if (parent != null) {
+                isCircular = Boolean.FALSE; // asumme it's false, and see if it isn't by checking
+                // the parent path
+                for (Iterator iter = parent.getPath().iterator(); iter.hasNext();) {
+                    VisitNode ancestor = (VisitNode) iter.next();
+                    if (getId().getModuleId().equals(ancestor.getId().getModuleId())) {
+                        isCircular = Boolean.TRUE;
+                        break;
+                    }
+                }
+            } else {
+                isCircular = Boolean.FALSE;
+            }
+        }
+        return isCircular.booleanValue();
+    }
+
+    public String[] getConfsToFetch() {
+        return node.getConfsToFetch();
+    }
+
+    public String[] getRequiredConfigurations(VisitNode in, String inConf) {
+        return node.getRequiredConfigurations(in.getNode(), inConf);
+    }
+
+    public ModuleId getModuleId() {
+        return node.getModuleId();
+    }
+
+    public Collection getResolvedRevisions(ModuleId mid) {
+        return node.getResolvedRevisions(mid, rootModuleConf);
+    }
+
+    public void markEvicted(EvictionData evictionData) {
+        node.markEvicted(evictionData);
+    }
+
+    public String[] getRequiredConfigurations() {
+        return node.getRequiredConfigurations();
+    }
+
+    /**
+     * Marks the current node as evicted by the the given selected IvyNodes, in the given parent and
+     * root module configuration, with the given {@link ConflictManager}
+     * 
+     * @param parent
+     *            the VisitNode in which eviction has been made
+     * @param conflictMgr
+     *            the conflict manager responsible for the eviction
+     * @param selected
+     *            a Collection of {@link IvyNode} which have been selected
+     */
+    public void markEvicted(VisitNode parent, ConflictManager conflictMgr, Collection selected) {
+        node.markEvicted(rootModuleConf, parent.getNode(), conflictMgr, selected);
+    }
+
+    public ModuleDescriptor getDescriptor() {
+        return node.getDescriptor();
+    }
+
+    public EvictionData getEvictionDataInRoot(String rootModuleConf, VisitNode ancestor) {
+        return node.getEvictionDataInRoot(rootModuleConf, ancestor.getNode());
+    }
+
+    public Collection getEvictedRevisions(ModuleId moduleId) {
+        return node.getEvictedRevisions(moduleId, rootModuleConf);
+    }
+
+    // public void setRootModuleConf(String rootModuleConf) {
+    // if (rootModuleConf != null && !rootModuleConf.equals(rootModuleConf)) {
+    // _confsToFetch.clear(); // we change of root module conf => we discard all confs to fetch
+    // }
+    // if (rootModuleConf != null && rootModuleConf.equals(rootModuleConf)) {
+    // _selectedDeps.put(new ModuleIdConf(_id.getModuleId(), rootModuleConf),
+    // Collections.singleton(this));
+    // }
+    // rootModuleConf = rootModuleConf;
+    // }
+
+    public String toString() {
+        return node.toString();
+    }
+
+    public boolean isConfRequiredByMergedUsageOnly(String conf) {
+        return node.isConfRequiredByMergedUsageOnly(rootModuleConf, conf);
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/retrieve/FileNameMapper.java b/src/java/org/apache/ivy/core/retrieve/FileNameMapper.java
new file mode 100644
index 0000000..0e39a21
--- /dev/null
+++ b/src/java/org/apache/ivy/core/retrieve/FileNameMapper.java
@@ -0,0 +1,24 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.retrieve;
+
+public interface FileNameMapper {
+
+    String[] mapFileName(String fileName);
+
+}
diff --git a/src/java/org/apache/ivy/core/retrieve/RetrieveEngine.java b/src/java/org/apache/ivy/core/retrieve/RetrieveEngine.java
new file mode 100644
index 0000000..d827f78
--- /dev/null
+++ b/src/java/org/apache/ivy/core/retrieve/RetrieveEngine.java
@@ -0,0 +1,496 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.retrieve;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.ivy.core.IvyContext;
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.core.LogOptions;
+import org.apache.ivy.core.cache.ResolutionCacheManager;
+import org.apache.ivy.core.event.EventManager;
+import org.apache.ivy.core.event.retrieve.EndRetrieveArtifactEvent;
+import org.apache.ivy.core.event.retrieve.EndRetrieveEvent;
+import org.apache.ivy.core.event.retrieve.StartRetrieveArtifactEvent;
+import org.apache.ivy.core.event.retrieve.StartRetrieveEvent;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.report.ArtifactDownloadReport;
+import org.apache.ivy.core.resolve.ResolveOptions;
+import org.apache.ivy.plugins.report.XmlReportParser;
+import org.apache.ivy.util.FileUtil;
+import org.apache.ivy.util.Message;
+
+public class RetrieveEngine {
+    private static final int KILO = 1024;
+
+    private RetrieveEngineSettings settings;
+
+    private EventManager eventManager;
+
+    public RetrieveEngine(RetrieveEngineSettings settings, EventManager eventManager) {
+        this.settings = settings;
+        this.eventManager = eventManager;
+    }
+
+    /**
+     * example of destFilePattern : - lib/[organisation]/[module]/[artifact]-[revision].[type] -
+     * lib/[artifact].[type] : flatten with no revision moduleId is used with confs and
+     * localCacheDirectory to determine an ivy report file, used as input for the copy If such a
+     * file does not exist for any conf (resolve has not been called before ?) then an
+     * IllegalStateException is thrown and nothing is copied.
+     * 
+     * @deprecated Use
+     *             {@link #retrieve(org.apache.ivy.core.module.id.ModuleRevisionId, RetrieveOptions)}
+     *             instead
+     */
+    @Deprecated
+    public int retrieve(ModuleRevisionId mrid, String destFilePattern, RetrieveOptions options)
+            throws IOException {
+        RetrieveOptions retieveOptions = new RetrieveOptions(options);
+        retieveOptions.setDestArtifactPattern(destFilePattern);
+
+        RetrieveReport result = retrieve(mrid, retieveOptions);
+        return result.getNbrArtifactsCopied();
+    }
+
+    public RetrieveReport retrieve(ModuleRevisionId mrid, RetrieveOptions options)
+            throws IOException {
+        RetrieveReport report = new RetrieveReport();
+
+        ModuleId moduleId = mrid.getModuleId();
+        if (LogOptions.LOG_DEFAULT.equals(options.getLog())) {
+            Message.info(":: retrieving :: " + moduleId + (options.isSync() ? " [sync]" : ""));
+        } else {
+            Message.verbose(":: retrieving :: " + moduleId + (options.isSync() ? " [sync]" : ""));
+        }
+        Message.verbose("\tcheckUpToDate=" + settings.isCheckUpToDate());
+        long start = System.currentTimeMillis();
+
+        String destFilePattern = IvyPatternHelper.substituteVariables(
+            options.getDestArtifactPattern(), settings.getVariables());
+        String destIvyPattern = IvyPatternHelper.substituteVariables(options.getDestIvyPattern(),
+            settings.getVariables());
+
+        String[] confs = getConfs(mrid, options);
+        if (LogOptions.LOG_DEFAULT.equals(options.getLog())) {
+            Message.info("\tconfs: " + Arrays.asList(confs));
+        } else {
+            Message.verbose("\tconfs: " + Arrays.asList(confs));
+        }
+        if (this.eventManager != null) {
+            this.eventManager.fireIvyEvent(new StartRetrieveEvent(mrid, confs, options));
+        }
+
+        try {
+            Map/* <File, File> */destToSrcMap = null;
+            // Map<ArtifactDownloadReport, Set<String>>
+            Map artifactsToCopy = determineArtifactsToCopy(mrid, destFilePattern, options);
+            File fileRetrieveRoot = settings.resolveFile(IvyPatternHelper
+                    .getTokenRoot(destFilePattern));
+            report.setRetrieveRoot(fileRetrieveRoot);
+            File ivyRetrieveRoot = destIvyPattern == null ? null : settings
+                    .resolveFile(IvyPatternHelper.getTokenRoot(destIvyPattern));
+            Collection targetArtifactsStructure = new HashSet(); // Set(File) set of all paths
+            // which should be present at
+            // then end of retrieve (useful
+            // for sync)
+            Collection targetIvysStructure = new HashSet(); // same for ivy files
+
+            if (options.isMakeSymlinksInMass()) {
+                // The HashMap is of "destToSrc" because src could go two places, but dest can only
+                // come from one
+                destToSrcMap = new HashMap();
+            }
+
+            // do retrieve
+            long totalCopiedSize = 0;
+            for (Iterator iter = artifactsToCopy.keySet().iterator(); iter.hasNext();) {
+                ArtifactDownloadReport artifact = (ArtifactDownloadReport) iter.next();
+                File archive = artifact.getLocalFile();
+                if (artifact.getUnpackedLocalFile() != null) {
+                    archive = artifact.getUnpackedLocalFile();
+                }
+                if (archive == null) {
+                    Message.verbose("\tno local file available for " + artifact + ": skipping");
+                    continue;
+                }
+                Set dest = (Set) artifactsToCopy.get(artifact);
+                Message.verbose("\tretrieving " + archive);
+                for (Iterator it2 = dest.iterator(); it2.hasNext();) {
+                    IvyContext.getContext().checkInterrupted();
+                    File destFile = settings.resolveFile((String) it2.next());
+                    if (!settings.isCheckUpToDate() || !upToDate(archive, destFile, options)) {
+                        Message.verbose("\t\tto " + destFile);
+                        if (this.eventManager != null) {
+                            // There is no unitary event for the mass sym linking.
+                            // skip the event declaration.
+                            if (!options.isMakeSymlinksInMass()) {
+                                this.eventManager.fireIvyEvent(new StartRetrieveArtifactEvent(
+                                        artifact, destFile));
+                            }
+                        }
+                        if (options.isMakeSymlinksInMass()) {
+                            if (FileUtil.prepareCopy(archive, destFile, true)) {
+                                destToSrcMap.put(destFile, archive);
+                            }
+                        } else if (options.isMakeSymlinks()) {
+                            FileUtil.symlink(archive, destFile, null, true);
+                        } else {
+                            FileUtil.copy(archive, destFile, null, true);
+                        }
+                        if (this.eventManager != null) {
+                            // There is no unitary event for the mass sym linking.
+                            // skip the event declaration.
+                            if (!options.isMakeSymlinksInMass()) {
+                                this.eventManager.fireIvyEvent(new EndRetrieveArtifactEvent(
+                                        artifact, destFile));
+                            }
+                        }
+                        totalCopiedSize += FileUtil.getFileLength(destFile);
+                        report.addCopiedFile(destFile, artifact);
+                    } else {
+                        Message.verbose("\t\tto " + destFile + " [NOT REQUIRED]");
+                        report.addUpToDateFile(destFile, artifact);
+                    }
+
+                    if ("ivy".equals(artifact.getType())) {
+                        targetIvysStructure
+                                .addAll(FileUtil.getPathFiles(ivyRetrieveRoot, destFile));
+                    } else {
+                        Iterator destFiles = FileUtil.listAll(destFile, Collections.EMPTY_LIST)
+                                .iterator();
+                        while (destFiles.hasNext()) {
+                            targetArtifactsStructure.addAll(FileUtil.getPathFiles(fileRetrieveRoot,
+                                (File) destFiles.next()));
+                        }
+                    }
+                }
+            }
+
+            if (options.isMakeSymlinksInMass()) {
+                Message.verbose("\tMass symlinking " + destToSrcMap.size() + " files");
+                FileUtil.symlinkInMass(destToSrcMap, true);
+            }
+
+            if (options.isSync()) {
+                Message.verbose("\tsyncing...");
+
+                String[] ignorableFilenames = settings.getIgnorableFilenames();
+                Collection ignoreList = Arrays.asList(ignorableFilenames);
+
+                Collection existingArtifacts = FileUtil.listAll(fileRetrieveRoot, ignoreList);
+                Collection existingIvys = ivyRetrieveRoot == null ? null : FileUtil.listAll(
+                    ivyRetrieveRoot, ignoreList);
+
+                if (fileRetrieveRoot.equals(ivyRetrieveRoot)) {
+                    Collection target = targetArtifactsStructure;
+                    target.addAll(targetIvysStructure);
+                    Collection existing = existingArtifacts;
+                    existing.addAll(existingIvys);
+                    sync(target, existing);
+                } else {
+                    sync(targetArtifactsStructure, existingArtifacts);
+                    if (existingIvys != null) {
+                        sync(targetIvysStructure, existingIvys);
+                    }
+                }
+            }
+            long elapsedTime = System.currentTimeMillis() - start;
+            String msg = "\t"
+                    + report.getNbrArtifactsCopied()
+                    + " artifacts copied"
+                    + (settings.isCheckUpToDate() ? (", " + report.getNbrArtifactsUpToDate() + " already retrieved")
+                            : "") + " (" + (totalCopiedSize / KILO) + "kB/" + elapsedTime + "ms)";
+            if (LogOptions.LOG_DEFAULT.equals(options.getLog())) {
+                Message.info(msg);
+            } else {
+                Message.verbose(msg);
+            }
+            Message.verbose("\tretrieve done (" + (elapsedTime) + "ms)");
+            if (this.eventManager != null) {
+                this.eventManager.fireIvyEvent(new EndRetrieveEvent(mrid, confs, elapsedTime,
+                        report.getNbrArtifactsCopied(), report.getNbrArtifactsUpToDate(),
+                        totalCopiedSize, options));
+            }
+
+            return report;
+        } catch (Exception ex) {
+            throw new RuntimeException("problem during retrieve of " + moduleId + ": " + ex, ex);
+        }
+    }
+
+    private String[] getConfs(ModuleRevisionId mrid, RetrieveOptions options) throws IOException {
+        String[] confs = options.getConfs();
+        if (confs == null || (confs.length == 1 && "*".equals(confs[0]))) {
+            try {
+                ModuleDescriptor md = getCache().getResolvedModuleDescriptor(mrid);
+                Message.verbose("no explicit confs given for retrieve, using ivy file: "
+                        + md.getResource().getName());
+                confs = md.getConfigurationsNames();
+                options.setConfs(confs);
+            } catch (IOException e) {
+                throw e;
+            } catch (Exception e) {
+                IOException ioex = new IOException(e.getMessage());
+                ioex.initCause(e);
+                throw ioex;
+            }
+        }
+        return confs;
+    }
+
+    private ResolutionCacheManager getCache() {
+        return settings.getResolutionCacheManager();
+    }
+
+    private void sync(Collection target, Collection existing) {
+        Collection toRemove = new HashSet();
+        for (Iterator iter = existing.iterator(); iter.hasNext();) {
+            File file = (File) iter.next();
+            toRemove.add(file.getAbsoluteFile());
+        }
+        for (Iterator iter = target.iterator(); iter.hasNext();) {
+            File file = (File) iter.next();
+            toRemove.remove(file.getAbsoluteFile());
+        }
+        for (Iterator iter = toRemove.iterator(); iter.hasNext();) {
+            File file = (File) iter.next();
+            if (file.exists()) {
+                Message.verbose("\t\tdeleting " + file);
+                FileUtil.forceDelete(file);
+            }
+        }
+    }
+
+    /**
+     * @return Map<ArtifactDownloadReport, Set<String>>
+     */
+    public Map determineArtifactsToCopy(ModuleRevisionId mrid, String destFilePattern,
+            RetrieveOptions options) throws ParseException, IOException {
+        ModuleId moduleId = mrid.getModuleId();
+
+        if (options.getResolveId() == null) {
+            options.setResolveId(ResolveOptions.getDefaultResolveId(moduleId));
+        }
+
+        ResolutionCacheManager cacheManager = getCache();
+        String[] confs = getConfs(mrid, options);
+        String destIvyPattern = IvyPatternHelper.substituteVariables(options.getDestIvyPattern(),
+            settings.getVariables());
+
+        // find what we must retrieve where
+
+        // ArtifactDownloadReport source -> Set (String copyDestAbsolutePath)
+        final Map artifactsToCopy = new HashMap();
+        // String copyDestAbsolutePath -> Set (ArtifactRevisionId source)
+        final Map conflictsMap = new HashMap();
+        // String copyDestAbsolutePath -> Set (ArtifactDownloadReport source)
+        final Map conflictsReportsMap = new HashMap();
+        // String copyDestAbsolutePath -> Set (String conf)
+        final Map conflictsConfMap = new HashMap();
+
+        XmlReportParser parser = new XmlReportParser();
+        for (int i = 0; i < confs.length; i++) {
+            final String conf = confs[i];
+
+            File report = cacheManager.getConfigurationResolveReportInCache(options.getResolveId(),
+                conf);
+            parser.parse(report);
+
+            Collection artifacts = new ArrayList(Arrays.asList(parser.getArtifactReports()));
+            if (destIvyPattern != null) {
+                ModuleRevisionId[] mrids = parser.getRealDependencyRevisionIds();
+                for (int j = 0; j < mrids.length; j++) {
+                    artifacts.add(parser.getMetadataArtifactReport(mrids[j]));
+                }
+            }
+            for (Iterator iter = artifacts.iterator(); iter.hasNext();) {
+                ArtifactDownloadReport adr = (ArtifactDownloadReport) iter.next();
+
+                Artifact artifact = adr.getArtifact();
+                String ext = artifact.getExt();
+                if (adr.getUnpackedLocalFile() != null) {
+                    ext = "";
+                }
+
+                String destPattern = "ivy".equals(adr.getType()) ? destIvyPattern : destFilePattern;
+
+                if (!"ivy".equals(adr.getType())
+                        && !options.getArtifactFilter().accept(adr.getArtifact())) {
+                    continue; // skip this artifact, the filter didn't accept it!
+                }
+
+                ModuleRevisionId aMrid = artifact.getModuleRevisionId();
+                String destFileName = IvyPatternHelper.substitute(destPattern,
+                    aMrid.getOrganisation(), aMrid.getName(), aMrid.getBranch(),
+                    aMrid.getRevision(), artifact.getName(), artifact.getType(), ext, conf,
+                    adr.getArtifactOrigin(), aMrid.getQualifiedExtraAttributes(),
+                    artifact.getQualifiedExtraAttributes());
+                Set dest = (Set) artifactsToCopy.get(adr);
+                if (dest == null) {
+                    dest = new HashSet();
+                    artifactsToCopy.put(adr, dest);
+                }
+                String copyDest = settings.resolveFile(destFileName).getAbsolutePath();
+
+                String[] destinations = new String[] {copyDest};
+                if (options.getMapper() != null) {
+                    destinations = options.getMapper().mapFileName(copyDest);
+                }
+
+                for (int j = 0; j < destinations.length; j++) {
+                    dest.add(destinations[j]);
+
+                    Set conflicts = (Set) conflictsMap.get(destinations[j]);
+                    Set conflictsReports = (Set) conflictsReportsMap.get(destinations[j]);
+                    Set conflictsConf = (Set) conflictsConfMap.get(destinations[j]);
+                    if (conflicts == null) {
+                        conflicts = new HashSet();
+                        conflictsMap.put(destinations[j], conflicts);
+                    }
+                    if (conflictsReports == null) {
+                        conflictsReports = new HashSet();
+                        conflictsReportsMap.put(destinations[j], conflictsReports);
+                    }
+                    if (conflictsConf == null) {
+                        conflictsConf = new HashSet();
+                        conflictsConfMap.put(destinations[j], conflictsConf);
+                    }
+                    if (conflicts.add(artifact.getId())) {
+                        conflictsReports.add(adr);
+                        conflictsConf.add(conf);
+                    }
+                }
+            }
+        }
+
+        // resolve conflicts if any
+        for (Iterator iter = conflictsMap.keySet().iterator(); iter.hasNext();) {
+            String copyDest = (String) iter.next();
+            Set artifacts = (Set) conflictsMap.get(copyDest);
+            Set conflictsConfs = (Set) conflictsConfMap.get(copyDest);
+            if (artifacts.size() > 1) {
+                List artifactsList = new ArrayList((Collection) conflictsReportsMap.get(copyDest));
+                // conflicts battle is resolved by a sort using a conflict resolving policy
+                // comparator which consider as greater a winning artifact
+                Collections.sort(artifactsList, getConflictResolvingPolicy());
+
+                // after the sort, the winning artifact is the greatest one, i.e. the last one
+                // we fail if different artifacts of the same module are mapped to the same file
+                ArtifactDownloadReport winner = (ArtifactDownloadReport) artifactsList
+                        .get(artifactsList.size() - 1);
+                ModuleRevisionId winnerMD = winner.getArtifact().getModuleRevisionId();
+                for (int i = artifactsList.size() - 2; i >= 0; i--) {
+                    ArtifactDownloadReport current = (ArtifactDownloadReport) artifactsList.get(i);
+                    if (winnerMD.equals(current.getArtifact().getModuleRevisionId())) {
+                        throw new RuntimeException("Multiple artifacts of the module " + winnerMD
+                                + " are retrieved to the same file! Update the retrieve pattern "
+                                + " to fix this error.");
+                    }
+                }
+
+                Message.info("\tconflict on " + copyDest + " in " + conflictsConfs + ": "
+                        + winnerMD.getRevision() + " won");
+
+                // we now iterate over the list beginning with the artifact preceding the winner,
+                // and going backward to the least artifact
+                for (int i = artifactsList.size() - 2; i >= 0; i--) {
+                    ArtifactDownloadReport looser = (ArtifactDownloadReport) artifactsList.get(i);
+                    Message.verbose("\t\tremoving conflict looser artifact: "
+                            + looser.getArtifact());
+                    // for each loser, we remove the pair (loser - copyDest) in the artifactsToCopy
+                    // map
+                    Set dest = (Set) artifactsToCopy.get(looser);
+                    dest.remove(copyDest);
+                    if (dest.isEmpty()) {
+                        artifactsToCopy.remove(looser);
+                    }
+                }
+            }
+        }
+        return artifactsToCopy;
+    }
+
+    private boolean upToDate(File source, File target, RetrieveOptions options) {
+        if (!target.exists()) {
+            return false;
+        }
+
+        String overwriteMode = options.getOverwriteMode();
+        if (RetrieveOptions.OVERWRITEMODE_ALWAYS.equals(overwriteMode)) {
+            return false;
+        }
+
+        if (RetrieveOptions.OVERWRITEMODE_NEVER.equals(overwriteMode)) {
+            return true;
+        }
+
+        if (RetrieveOptions.OVERWRITEMODE_NEWER.equals(overwriteMode)) {
+            return source.lastModified() <= target.lastModified();
+        }
+
+        if (RetrieveOptions.OVERWRITEMODE_DIFFERENT.equals(overwriteMode)) {
+            return source.lastModified() == target.lastModified();
+        }
+
+        // unknown, so just to be sure
+        return false;
+    }
+
+    /**
+     * The returned comparator should consider greater the artifact which gains the conflict battle.
+     * This is used only during retrieve... prefer resolve conflict manager to resolve conflicts.
+     * 
+     * @return
+     */
+    private Comparator getConflictResolvingPolicy() {
+        return new Comparator() {
+            // younger conflict resolving policy
+            public int compare(Object o1, Object o2) {
+                Artifact a1 = ((ArtifactDownloadReport) o1).getArtifact();
+                Artifact a2 = ((ArtifactDownloadReport) o2).getArtifact();
+                if (a1.getPublicationDate().after(a2.getPublicationDate())) {
+                    // a1 is after a2 <=> a1 is younger than a2 <=> a1 wins the conflict battle
+                    return +1;
+                } else if (a1.getPublicationDate().before(a2.getPublicationDate())) {
+                    // a1 is before a2 <=> a2 is younger than a1 <=> a2 wins the conflict battle
+                    return -1;
+                } else {
+                    return 0;
+                }
+            }
+        };
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/retrieve/RetrieveEngineSettings.java b/src/java/org/apache/ivy/core/retrieve/RetrieveEngineSettings.java
new file mode 100644
index 0000000..c50baee
--- /dev/null
+++ b/src/java/org/apache/ivy/core/retrieve/RetrieveEngineSettings.java
@@ -0,0 +1,31 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.retrieve;
+
+import org.apache.ivy.core.settings.IvyVariableContainer;
+import org.apache.ivy.plugins.parser.ParserSettings;
+
+public interface RetrieveEngineSettings extends ParserSettings {
+
+    boolean isCheckUpToDate();
+
+    IvyVariableContainer getVariables();
+
+    String[] getIgnorableFilenames();
+
+}
diff --git a/src/java/org/apache/ivy/core/retrieve/RetrieveOptions.java b/src/java/org/apache/ivy/core/retrieve/RetrieveOptions.java
new file mode 100644
index 0000000..c80bb71
--- /dev/null
+++ b/src/java/org/apache/ivy/core/retrieve/RetrieveOptions.java
@@ -0,0 +1,210 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.retrieve;
+
+import org.apache.ivy.core.LogOptions;
+import org.apache.ivy.util.filter.Filter;
+import org.apache.ivy.util.filter.FilterHelper;
+
+/**
+ * A set of options used during retrieve related tasks
+ * 
+ * @see RetrieveEngine
+ */
+public class RetrieveOptions extends LogOptions {
+    public static final String OVERWRITEMODE_NEVER = "never";
+
+    public static final String OVERWRITEMODE_ALWAYS = "always";
+
+    public static final String OVERWRITEMODE_NEWER = "newer";
+
+    public static final String OVERWRITEMODE_DIFFERENT = "different";
+
+    /**
+     * The names of configurations to retrieve. If the array consists only of '*', then all
+     * configurations of the module will be retrieved.
+     */
+    private String[] confs = new String[] {"*"};
+
+    /**
+     * The pattern to which ivy files should be retrieved. If destIvyPattern is null no ivy files
+     * will be copied.
+     */
+    private String destIvyPattern = null;
+
+    /**
+     * The pattern to which artifacts should be retrieved.
+     */
+    private String destArtifactPattern = null;
+
+    /**
+     * The filter to apply before retrieving artifacts.
+     */
+    private Filter artifactFilter = FilterHelper.NO_FILTER;
+
+    /**
+     * True if a synchronisation of the destination directory should be done, false if a simple copy
+     * is enough. Synchronisation means that after the retrieve only files which have been retrieved
+     * will be present in the destination directory, which means that some files may be deleted.
+     */
+    private boolean sync = false;
+
+    private String overwriteMode = OVERWRITEMODE_NEWER;
+
+    /**
+     * True if the original files should be used insteaad of their cache copy.
+     */
+    private boolean useOrigin = false;
+
+    /**
+     * True if symbolic links should be created instead of plain copy. Works only on OS supporting
+     * symbolic links.
+     */
+    private boolean makeSymlinks = false;
+
+    /**
+     * True if symbolic links should be created all at once, instead of one at a time. Works only on
+     * OS supporting with both "sh" (a shell) and "ln" (the link command).
+     */
+    private boolean makeSymlinksInMass = false;
+
+    /**
+     * The id used to store the resolve information.
+     */
+    private String resolveId;
+
+    private FileNameMapper mapper;
+
+    public RetrieveOptions() {
+    }
+
+    public RetrieveOptions(RetrieveOptions options) {
+        super(options);
+        this.confs = options.confs;
+        this.destIvyPattern = options.destIvyPattern;
+        this.destArtifactPattern = options.destArtifactPattern;
+        this.artifactFilter = options.artifactFilter;
+        this.sync = options.sync;
+        this.overwriteMode = options.overwriteMode;
+        this.useOrigin = options.useOrigin;
+        this.makeSymlinks = options.makeSymlinks;
+        this.makeSymlinksInMass = options.makeSymlinksInMass;
+        this.resolveId = options.resolveId;
+        this.mapper = options.mapper;
+    }
+
+    public String getDestArtifactPattern() {
+        return destArtifactPattern;
+    }
+
+    public RetrieveOptions setDestArtifactPattern(String destArtifactPattern) {
+        this.destArtifactPattern = destArtifactPattern;
+        return this;
+    }
+
+    public Filter getArtifactFilter() {
+        return artifactFilter;
+    }
+
+    public RetrieveOptions setArtifactFilter(Filter artifactFilter) {
+        this.artifactFilter = artifactFilter;
+        return this;
+    }
+
+    public String[] getConfs() {
+        return confs;
+    }
+
+    public RetrieveOptions setConfs(String[] confs) {
+        this.confs = confs;
+        return this;
+    }
+
+    public String getOverwriteMode() {
+        return overwriteMode == null ? OVERWRITEMODE_NEWER : overwriteMode;
+    }
+
+    public RetrieveOptions setOverwriteMode(String overwriteMode) {
+        this.overwriteMode = overwriteMode;
+        return this;
+    }
+
+    public String getDestIvyPattern() {
+        return destIvyPattern;
+    }
+
+    public RetrieveOptions setDestIvyPattern(String destIvyPattern) {
+        this.destIvyPattern = destIvyPattern;
+        return this;
+    }
+
+    public boolean isMakeSymlinks() {
+        return makeSymlinks;
+    }
+
+    public boolean isMakeSymlinksInMass() {
+        return makeSymlinksInMass;
+    }
+
+    public RetrieveOptions setMakeSymlinks(boolean makeSymlinks) {
+        this.makeSymlinks = makeSymlinks;
+        return this;
+    }
+
+    public RetrieveOptions setMakeSymlinksInMass(boolean makeSymlinksInMass) {
+        this.makeSymlinksInMass = makeSymlinksInMass;
+        return this;
+    }
+
+    public boolean isSync() {
+        return sync;
+    }
+
+    public RetrieveOptions setSync(boolean sync) {
+        this.sync = sync;
+        return this;
+    }
+
+    public boolean isUseOrigin() {
+        return useOrigin;
+    }
+
+    public RetrieveOptions setUseOrigin(boolean useOrigin) {
+        this.useOrigin = useOrigin;
+        return this;
+    }
+
+    public String getResolveId() {
+        return resolveId;
+    }
+
+    public RetrieveOptions setResolveId(String resolveId) {
+        this.resolveId = resolveId;
+        return this;
+    }
+
+    public FileNameMapper getMapper() {
+        return mapper;
+    }
+
+    public RetrieveOptions setMapper(FileNameMapper mapper) {
+        this.mapper = mapper;
+        return this;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/retrieve/RetrieveReport.java b/src/java/org/apache/ivy/core/retrieve/RetrieveReport.java
new file mode 100644
index 0000000..42273d3
--- /dev/null
+++ b/src/java/org/apache/ivy/core/retrieve/RetrieveReport.java
@@ -0,0 +1,101 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.retrieve;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+
+import org.apache.ivy.core.report.ArtifactDownloadReport;
+
+public class RetrieveReport {
+
+    private Collection/* <File> */upToDateFiles = new HashSet();
+
+    private Collection/* <File> */copiedFiles = new HashSet();
+
+    private Map/* <File, ArtifactDownloadReport> */downloadReport = new HashMap();
+
+    private File retrieveRoot;
+
+    /**
+     * Returns the root directory to where the artifacts are retrieved.
+     */
+    public File getRetrieveRoot() {
+        return retrieveRoot;
+    }
+
+    public void setRetrieveRoot(File retrieveRoot) {
+        this.retrieveRoot = retrieveRoot;
+    }
+
+    public int getNbrArtifactsCopied() {
+        return copiedFiles.size();
+    }
+
+    public int getNbrArtifactsUpToDate() {
+        return upToDateFiles.size();
+    }
+
+    public void addCopiedFile(File file, ArtifactDownloadReport report) {
+        copiedFiles.add(file);
+        downloadReport.put(file, report);
+    }
+
+    public void addUpToDateFile(File file, ArtifactDownloadReport report) {
+        upToDateFiles.add(file);
+        downloadReport.put(file, report);
+    }
+
+    /**
+     * Returns a collection of <tt>File</tt> objects who were actually copied during the retrieve
+     * process.
+     */
+    public Collection getCopiedFiles() {
+        return new ArrayList(copiedFiles);
+    }
+
+    /**
+     * Returns a collection of <tt>File</tt> objects who were actually copied during the retrieve
+     * process.
+     */
+    public Collection getUpToDateFiles() {
+        return new ArrayList(upToDateFiles);
+    }
+
+    /**
+     * Returns a collection of <tt>File</tt> objects who were retrieved during the retrieve process.
+     * This is the union of the files being copied and the files that were up-to-date.
+     */
+    public Collection getRetrievedFiles() {
+        Collection result = new ArrayList(upToDateFiles.size() + copiedFiles.size());
+        result.addAll(upToDateFiles);
+        result.addAll(copiedFiles);
+        return result;
+    }
+
+    /**
+     * Get the mapping between the copied files and their corresponding download report
+     */
+    public Map getDownloadReport() {
+        return downloadReport;
+    }
+}
diff --git a/src/java/org/apache/ivy/core/search/ModuleEntry.java b/src/java/org/apache/ivy/core/search/ModuleEntry.java
new file mode 100644
index 0000000..c3062c8
--- /dev/null
+++ b/src/java/org/apache/ivy/core/search/ModuleEntry.java
@@ -0,0 +1,51 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.search;
+
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+
+public class ModuleEntry {
+    private OrganisationEntry organisationEntry;
+
+    private String module;
+
+    public ModuleEntry(OrganisationEntry org, String name) {
+        organisationEntry = org;
+        module = name;
+    }
+
+    public String getOrganisation() {
+        return organisationEntry.getOrganisation();
+    }
+
+    public DependencyResolver getResolver() {
+        return organisationEntry.getResolver();
+    }
+
+    public String getModule() {
+        return module;
+    }
+
+    public OrganisationEntry getOrganisationEntry() {
+        return organisationEntry;
+    }
+
+    public String toString() {
+        return organisationEntry + "#" + module;
+    }
+}
diff --git a/src/java/org/apache/ivy/core/search/OrganisationEntry.java b/src/java/org/apache/ivy/core/search/OrganisationEntry.java
new file mode 100644
index 0000000..d173dd0
--- /dev/null
+++ b/src/java/org/apache/ivy/core/search/OrganisationEntry.java
@@ -0,0 +1,43 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.search;
+
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+
+public class OrganisationEntry {
+    private DependencyResolver resolver;
+
+    private String organisation;
+
+    public OrganisationEntry(DependencyResolver resolver, String organisation) {
+        this.resolver = resolver;
+        this.organisation = organisation;
+    }
+
+    public String getOrganisation() {
+        return organisation;
+    }
+
+    public DependencyResolver getResolver() {
+        return resolver;
+    }
+
+    public String toString() {
+        return organisation;
+    }
+}
diff --git a/src/java/org/apache/ivy/core/search/RevisionEntry.java b/src/java/org/apache/ivy/core/search/RevisionEntry.java
new file mode 100644
index 0000000..74c536d
--- /dev/null
+++ b/src/java/org/apache/ivy/core/search/RevisionEntry.java
@@ -0,0 +1,59 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.search;
+
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+
+public class RevisionEntry {
+    private ModuleEntry moduleEntry;
+
+    private String revision;
+
+    public RevisionEntry(ModuleEntry mod, String name) {
+        moduleEntry = mod;
+        revision = name;
+    }
+
+    public ModuleEntry getModuleEntry() {
+        return moduleEntry;
+    }
+
+    public String getRevision() {
+        return revision;
+    }
+
+    public String getModule() {
+        return moduleEntry.getModule();
+    }
+
+    public String getOrganisation() {
+        return moduleEntry.getOrganisation();
+    }
+
+    public OrganisationEntry getOrganisationEntry() {
+        return moduleEntry.getOrganisationEntry();
+    }
+
+    public DependencyResolver getResolver() {
+        return moduleEntry.getResolver();
+    }
+
+    public String toString() {
+        return moduleEntry + ";" + revision;
+    }
+}
diff --git a/src/java/org/apache/ivy/core/search/SearchEngine.java b/src/java/org/apache/ivy/core/search/SearchEngine.java
new file mode 100644
index 0000000..84c2443
--- /dev/null
+++ b/src/java/org/apache/ivy/core/search/SearchEngine.java
@@ -0,0 +1,437 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.search;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.ivy.plugins.matcher.Matcher;
+import org.apache.ivy.plugins.matcher.MatcherHelper;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+import org.apache.ivy.plugins.namespace.NameSpaceHelper;
+import org.apache.ivy.plugins.namespace.Namespace;
+import org.apache.ivy.plugins.resolver.AbstractResolver;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.apache.ivy.util.Message;
+
+public class SearchEngine {
+    private IvySettings settings;
+
+    public SearchEngine(IvySettings settings) {
+        this.settings = settings;
+    }
+
+    /**
+     * Returns an empty array when no token values are found.
+     * 
+     * @param token
+     * @param otherTokenValues
+     * @return
+     */
+    public String[] listTokenValues(String token, Map otherTokenValues) {
+        Set entries = new LinkedHashSet();
+
+        for (Iterator iter = settings.getResolvers().iterator(); iter.hasNext();) {
+            DependencyResolver resolver = (DependencyResolver) iter.next();
+            Map[] values = resolver.listTokenValues(new String[] {token}, otherTokenValues);
+            for (int i = 0; i < values.length; i++) {
+                entries.add(values[i].get(token));
+            }
+        }
+
+        return (String[]) entries.toArray(new String[entries.size()]);
+    }
+
+    public OrganisationEntry[] listOrganisationEntries() {
+        Set entries = new HashSet();
+
+        for (Iterator iter = settings.getResolvers().iterator(); iter.hasNext();) {
+            DependencyResolver resolver = (DependencyResolver) iter.next();
+            Map[] orgs = resolver.listTokenValues(new String[] {IvyPatternHelper.ORGANISATION_KEY},
+                new HashMap());
+            for (int i = 0; i < orgs.length; i++) {
+                String org = (String) orgs[i].get(IvyPatternHelper.ORGANISATION_KEY);
+                entries.add(new OrganisationEntry(resolver, org));
+            }
+        }
+
+        return (OrganisationEntry[]) entries.toArray(new OrganisationEntry[entries.size()]);
+    }
+
+    public String[] listOrganisations() {
+        Set entries = new HashSet();
+
+        for (Iterator iter = settings.getResolvers().iterator(); iter.hasNext();) {
+            DependencyResolver resolver = (DependencyResolver) iter.next();
+            Map[] orgs = resolver.listTokenValues(new String[] {IvyPatternHelper.ORGANISATION_KEY},
+                new HashMap());
+            for (int i = 0; i < orgs.length; i++) {
+                entries.add(orgs[i].get(IvyPatternHelper.ORGANISATION_KEY));
+            }
+        }
+
+        return (String[]) entries.toArray(new String[entries.size()]);
+    }
+
+    public ModuleEntry[] listModuleEntries(OrganisationEntry org) {
+        Set entries = new HashSet();
+
+        Map tokenValues = new HashMap();
+        tokenValues.put(IvyPatternHelper.ORGANISATION_KEY, org.getOrganisation());
+
+        for (Iterator iter = settings.getResolvers().iterator(); iter.hasNext();) {
+            DependencyResolver resolver = (DependencyResolver) iter.next();
+            Map[] modules = resolver.listTokenValues(new String[] {IvyPatternHelper.MODULE_KEY},
+                tokenValues);
+            for (int i = 0; i < modules.length; i++) {
+                String module = (String) modules[i].get(IvyPatternHelper.MODULE_KEY);
+                entries.add(new ModuleEntry(org, module));
+            }
+        }
+
+        return (ModuleEntry[]) entries.toArray(new ModuleEntry[entries.size()]);
+    }
+
+    public String[] listModules(String org) {
+        Set entries = new HashSet();
+
+        Map tokenValues = new HashMap();
+        tokenValues.put(IvyPatternHelper.ORGANISATION_KEY, org);
+
+        for (Iterator iter = settings.getResolvers().iterator(); iter.hasNext();) {
+            DependencyResolver resolver = (DependencyResolver) iter.next();
+            Map[] modules = resolver.listTokenValues(new String[] {IvyPatternHelper.MODULE_KEY},
+                tokenValues);
+            for (int i = 0; i < modules.length; i++) {
+                entries.add(modules[i].get(IvyPatternHelper.MODULE_KEY));
+            }
+        }
+
+        return (String[]) entries.toArray(new String[entries.size()]);
+    }
+
+    public RevisionEntry[] listRevisionEntries(ModuleEntry module) {
+        Set entries = new HashSet();
+
+        Map tokenValues = new HashMap();
+        tokenValues.put(IvyPatternHelper.ORGANISATION_KEY, module.getOrganisation());
+        tokenValues.put(IvyPatternHelper.MODULE_KEY, module.getModule());
+
+        for (Iterator iter = settings.getResolvers().iterator(); iter.hasNext();) {
+            DependencyResolver resolver = (DependencyResolver) iter.next();
+            Map[] revisions = resolver.listTokenValues(
+                new String[] {IvyPatternHelper.REVISION_KEY}, tokenValues);
+            for (int i = 0; i < revisions.length; i++) {
+                String revision = (String) revisions[i].get(IvyPatternHelper.REVISION_KEY);
+                entries.add(new RevisionEntry(module, revision));
+            }
+        }
+
+        return (RevisionEntry[]) entries.toArray(new RevisionEntry[entries.size()]);
+    }
+
+    public String[] listRevisions(String org, String module) {
+        Set entries = new HashSet();
+
+        Map tokenValues = new HashMap();
+        tokenValues.put(IvyPatternHelper.ORGANISATION_KEY, org);
+        tokenValues.put(IvyPatternHelper.MODULE_KEY, module);
+
+        for (Iterator iter = settings.getResolvers().iterator(); iter.hasNext();) {
+            DependencyResolver resolver = (DependencyResolver) iter.next();
+            Map[] revisions = resolver.listTokenValues(
+                new String[] {IvyPatternHelper.REVISION_KEY}, tokenValues);
+            for (int i = 0; i < revisions.length; i++) {
+                entries.add(revisions[i].get(IvyPatternHelper.REVISION_KEY));
+            }
+        }
+
+        return (String[]) entries.toArray(new String[entries.size()]);
+    }
+
+    /**
+     * List module ids of the module accessible through the current resolvers matching the given mid
+     * criteria according to the given matcher.
+     * <p>
+     * ModuleId are returned in the system namespace.
+     * </p>
+     * 
+     * @param criteria
+     * @param matcher
+     * @return
+     */
+    public ModuleId[] listModules(ModuleId moduleCrit, PatternMatcher matcher) {
+        List ret = new ArrayList();
+
+        Map criteria = new HashMap();
+        addMatcher(matcher, moduleCrit.getOrganisation(), criteria,
+            IvyPatternHelper.ORGANISATION_KEY);
+        addMatcher(matcher, moduleCrit.getName(), criteria, IvyPatternHelper.MODULE_KEY);
+
+        String[] tokensToList = new String[] {IvyPatternHelper.ORGANISATION_KEY,
+                IvyPatternHelper.MODULE_KEY};
+
+        for (Iterator iter = settings.getResolvers().iterator(); iter.hasNext();) {
+            DependencyResolver resolver = (DependencyResolver) iter.next();
+            Map[] moduleIdAsMap = resolver.listTokenValues(tokensToList, criteria);
+            for (int i = 0; i < moduleIdAsMap.length; i++) {
+                String org = (String) moduleIdAsMap[i].get(IvyPatternHelper.ORGANISATION_KEY);
+                String name = (String) moduleIdAsMap[i].get(IvyPatternHelper.MODULE_KEY);
+                ModuleId modId = ModuleId.newInstance(org, name);
+                ret.add(NameSpaceHelper.transform(modId, resolver.getNamespace()
+                        .getToSystemTransformer()));
+            }
+        }
+
+        return (ModuleId[]) ret.toArray(new ModuleId[ret.size()]);
+    }
+
+    /**
+     * List module revision ids of the module accessible through the current resolvers matching the
+     * given mrid criteria according to the given matcher.
+     * <p>
+     * ModuleRevisionId are returned in the system namespace.
+     * </p>
+     * 
+     * @param criteria
+     * @param matcher
+     * @return
+     */
+    public ModuleRevisionId[] listModules(ModuleRevisionId moduleCrit, PatternMatcher matcher) {
+        List ret = new ArrayList();
+
+        Map criteria = new HashMap();
+        for (Iterator it = moduleCrit.getAttributes().entrySet().iterator(); it.hasNext();) {
+            Map.Entry entry = (Entry) it.next();
+            addMatcher(matcher, (String) entry.getValue(), criteria, (String) entry.getKey());
+        }
+
+        String[] tokensToList = (String[]) moduleCrit.getAttributes().keySet()
+                .toArray(new String[moduleCrit.getAttributes().size()]);
+
+        for (Iterator iter = settings.getResolvers().iterator(); iter.hasNext();) {
+            DependencyResolver resolver = (DependencyResolver) iter.next();
+            Map[] moduleIdAsMap = resolver.listTokenValues(tokensToList, criteria);
+            for (int i = 0; i < moduleIdAsMap.length; i++) {
+                String org = (String) moduleIdAsMap[i].get(IvyPatternHelper.ORGANISATION_KEY);
+                String name = (String) moduleIdAsMap[i].get(IvyPatternHelper.MODULE_KEY);
+                String branch = (String) moduleIdAsMap[i].get(IvyPatternHelper.BRANCH_KEY);
+                String rev = (String) moduleIdAsMap[i].get(IvyPatternHelper.REVISION_KEY);
+
+                Map foundExtraAtts = new HashMap();
+                Set qualAttributes = moduleCrit.getQualifiedExtraAttributes().keySet();
+                for (Iterator iter2 = qualAttributes.iterator(); iter2.hasNext();) {
+                    String qualifiedKey = (String) iter2.next();
+                    String value = null;
+                    int colonIndex = qualifiedKey.indexOf(':');
+                    if (colonIndex == -1) {
+                        value = (String) moduleIdAsMap[i].get(qualifiedKey);
+                    } else {
+                        value = (String) moduleIdAsMap[i].get(qualifiedKey
+                                .substring(colonIndex + 1));
+                    }
+
+                    if (value != null) {
+                        foundExtraAtts.put(qualifiedKey, value);
+                    }
+                }
+
+                ModuleRevisionId modRevId = ModuleRevisionId.newInstance(org, name, branch, rev,
+                    foundExtraAtts);
+                ret.add(resolver.getNamespace().getToSystemTransformer().transform(modRevId));
+            }
+        }
+
+        return (ModuleRevisionId[]) ret.toArray(new ModuleRevisionId[ret.size()]);
+    }
+
+    /**
+     * List modules matching a given criteria, available in the given dependency resolver.
+     * <p>
+     * ModuleRevisionId are returned in the system namespace.
+     * </p>
+     * 
+     * @param resolver
+     *            the resolver in which modules should looked up
+     * @param moduleCrit
+     *            the criteria to match
+     * @param matcher
+     *            the matcher to use to match criteria
+     * @return an array of matching module revision ids
+     */
+    public ModuleRevisionId[] listModules(DependencyResolver resolver, ModuleRevisionId moduleCrit,
+            PatternMatcher matcher) {
+        Map criteria = new HashMap();
+        for (Iterator it = moduleCrit.getAttributes().entrySet().iterator(); it.hasNext();) {
+            Map.Entry entry = (Entry) it.next();
+            addMatcher(matcher, (String) entry.getValue(), criteria, (String) entry.getKey());
+        }
+
+        String[] tokensToList = (String[]) moduleCrit.getAttributes().keySet()
+                .toArray(new String[moduleCrit.getAttributes().size()]);
+
+        Map[] moduleIdAsMap = resolver.listTokenValues(tokensToList, criteria);
+        Set result = new LinkedHashSet(); // we use a Set to remove duplicates
+        for (int i = 0; i < moduleIdAsMap.length; i++) {
+            String org = (String) moduleIdAsMap[i].get(IvyPatternHelper.ORGANISATION_KEY);
+            String name = (String) moduleIdAsMap[i].get(IvyPatternHelper.MODULE_KEY);
+            String branch = (String) moduleIdAsMap[i].get(IvyPatternHelper.BRANCH_KEY);
+            String rev = (String) moduleIdAsMap[i].get(IvyPatternHelper.REVISION_KEY);
+
+            Map foundExtraAtts = new HashMap();
+            Set qualExtraAttributes = moduleCrit.getQualifiedExtraAttributes().keySet();
+            for (Iterator iter2 = qualExtraAttributes.iterator(); iter2.hasNext();) {
+                String qualifiedKey = (String) iter2.next();
+                String value = null;
+                int colonIndex = qualifiedKey.indexOf(':');
+                if (colonIndex == -1) {
+                    value = (String) moduleIdAsMap[i].get(qualifiedKey);
+                } else {
+                    value = (String) moduleIdAsMap[i].get(qualifiedKey.substring(colonIndex + 1));
+                }
+
+                if (value != null) {
+                    foundExtraAtts.put(qualifiedKey, value);
+                }
+            }
+
+            ModuleRevisionId modRevId = ModuleRevisionId.newInstance(org, name, branch, rev,
+                foundExtraAtts);
+            result.add(resolver.getNamespace().getToSystemTransformer().transform(modRevId));
+        }
+
+        return (ModuleRevisionId[]) result.toArray(new ModuleRevisionId[result.size()]);
+    }
+
+    private void addMatcher(PatternMatcher patternMatcher, String expression, Map criteria,
+            String key) {
+        if (expression == null) {
+            return;
+        }
+
+        Matcher matcher = patternMatcher.getMatcher(expression);
+        if (matcher.isExact()) {
+            criteria.put(key, expression);
+        } else {
+            criteria.put(key, matcher);
+        }
+    }
+
+    public Collection findModuleRevisionIds(DependencyResolver resolver, ModuleRevisionId pattern,
+            PatternMatcher matcher) {
+        Collection mrids = new ArrayList();
+        String resolverName = resolver.getName();
+
+        Message.verbose("looking for modules matching " + pattern + " using " + matcher.getName());
+        Namespace fromNamespace = null;
+        if (resolver instanceof AbstractResolver) {
+            fromNamespace = ((AbstractResolver) resolver).getNamespace();
+        }
+
+        Collection modules = new ArrayList();
+
+        OrganisationEntry[] orgs = resolver.listOrganisations();
+        if (orgs == null || orgs.length == 0) {
+            // hack for resolvers which are not able to list organisation, we try to see if the
+            // asked organisation is not an exact one:
+            String org = pattern.getOrganisation();
+            if (fromNamespace != null) {
+                org = NameSpaceHelper.transform(pattern.getModuleId(),
+                    fromNamespace.getFromSystemTransformer()).getOrganisation();
+            }
+            modules.addAll(Arrays.asList(resolver.listModules(new OrganisationEntry(resolver, org))));
+        } else {
+            Matcher orgMatcher = matcher.getMatcher(pattern.getOrganisation());
+            for (int i = 0; i < orgs.length; i++) {
+                String org = orgs[i].getOrganisation();
+                String systemOrg = org;
+                if (fromNamespace != null) {
+                    systemOrg = NameSpaceHelper.transformOrganisation(org,
+                        fromNamespace.getToSystemTransformer());
+                }
+                if (orgMatcher.matches(systemOrg)) {
+                    modules.addAll(Arrays.asList(resolver.listModules(new OrganisationEntry(
+                            resolver, org))));
+                }
+            }
+        }
+        Message.debug("found " + modules.size() + " modules for " + pattern.getOrganisation()
+                + " on " + resolverName);
+        boolean foundModule = false;
+        for (Iterator iter = modules.iterator(); iter.hasNext();) {
+            ModuleEntry mEntry = (ModuleEntry) iter.next();
+
+            ModuleId foundMid = new ModuleId(mEntry.getOrganisation(), mEntry.getModule());
+            ModuleId systemMid = foundMid;
+            if (fromNamespace != null) {
+                systemMid = NameSpaceHelper.transform(foundMid,
+                    fromNamespace.getToSystemTransformer());
+            }
+
+            if (MatcherHelper.matches(matcher, pattern.getModuleId(), systemMid)) {
+                // The module corresponds to the searched module pattern
+                foundModule = true;
+                RevisionEntry[] rEntries = resolver.listRevisions(mEntry);
+                Message.debug("found " + rEntries.length + " revisions for ["
+                        + mEntry.getOrganisation() + ", " + mEntry.getModule() + "] on "
+                        + resolverName);
+
+                boolean foundRevision = false;
+                for (int j = 0; j < rEntries.length; j++) {
+                    RevisionEntry rEntry = rEntries[j];
+
+                    ModuleRevisionId foundMrid = ModuleRevisionId.newInstance(
+                        mEntry.getOrganisation(), mEntry.getModule(), rEntry.getRevision());
+                    ModuleRevisionId systemMrid = foundMrid;
+                    if (fromNamespace != null) {
+                        systemMrid = fromNamespace.getToSystemTransformer().transform(foundMrid);
+                    }
+
+                    if (MatcherHelper.matches(matcher, pattern, systemMrid)) {
+                        // We have a matching module revision
+                        foundRevision = true;
+                        mrids.add(systemMrid);
+                    }
+                }
+                if (!foundRevision) {
+                    Message.debug("no revision found matching " + pattern + " in ["
+                            + mEntry.getOrganisation() + "," + mEntry.getModule() + "] using "
+                            + resolverName);
+                }
+            }
+        }
+        if (!foundModule) {
+            Message.debug("no module found matching " + pattern + " using " + resolverName);
+        }
+        return mrids;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/settings/IvyPattern.java b/src/java/org/apache/ivy/core/settings/IvyPattern.java
new file mode 100644
index 0000000..1464714
--- /dev/null
+++ b/src/java/org/apache/ivy/core/settings/IvyPattern.java
@@ -0,0 +1,33 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.settings;
+
+/**
+ *
+ */
+public class IvyPattern {
+    private String pattern;
+
+    public String getPattern() {
+        return pattern;
+    }
+
+    public void setPattern(String pattern) {
+        this.pattern = pattern;
+    }
+}
diff --git a/src/java/org/apache/ivy/core/settings/IvySettings.java b/src/java/org/apache/ivy/core/settings/IvySettings.java
new file mode 100644
index 0000000..1ef8dc7
--- /dev/null
+++ b/src/java/org/apache/ivy/core/settings/IvySettings.java
@@ -0,0 +1,1516 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.settings;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Field;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.AccessControlException;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.ivy.Ivy;
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.core.NormalRelativeUrlResolver;
+import org.apache.ivy.core.RelativeUrlResolver;
+import org.apache.ivy.core.cache.CacheUtil;
+import org.apache.ivy.core.cache.DefaultRepositoryCacheManager;
+import org.apache.ivy.core.cache.DefaultResolutionCacheManager;
+import org.apache.ivy.core.cache.RepositoryCacheManager;
+import org.apache.ivy.core.cache.ResolutionCacheManager;
+import org.apache.ivy.core.check.CheckEngineSettings;
+import org.apache.ivy.core.deliver.DeliverEngineSettings;
+import org.apache.ivy.core.install.InstallEngineSettings;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.module.id.ModuleRules;
+import org.apache.ivy.core.module.status.StatusManager;
+import org.apache.ivy.core.pack.ArchivePacking;
+import org.apache.ivy.core.pack.PackingRegistry;
+import org.apache.ivy.core.publish.PublishEngineSettings;
+import org.apache.ivy.core.repository.RepositoryManagementEngineSettings;
+import org.apache.ivy.core.resolve.ResolveEngineSettings;
+import org.apache.ivy.core.resolve.ResolveOptions;
+import org.apache.ivy.core.retrieve.RetrieveEngineSettings;
+import org.apache.ivy.core.sort.SortEngineSettings;
+import org.apache.ivy.osgi.core.OsgiLatestStrategy;
+import org.apache.ivy.plugins.IvySettingsAware;
+import org.apache.ivy.plugins.circular.CircularDependencyStrategy;
+import org.apache.ivy.plugins.circular.ErrorCircularDependencyStrategy;
+import org.apache.ivy.plugins.circular.IgnoreCircularDependencyStrategy;
+import org.apache.ivy.plugins.circular.WarnCircularDependencyStrategy;
+import org.apache.ivy.plugins.conflict.ConflictManager;
+import org.apache.ivy.plugins.conflict.LatestCompatibleConflictManager;
+import org.apache.ivy.plugins.conflict.LatestConflictManager;
+import org.apache.ivy.plugins.conflict.NoConflictManager;
+import org.apache.ivy.plugins.conflict.StrictConflictManager;
+import org.apache.ivy.plugins.latest.LatestLexicographicStrategy;
+import org.apache.ivy.plugins.latest.LatestRevisionStrategy;
+import org.apache.ivy.plugins.latest.LatestStrategy;
+import org.apache.ivy.plugins.latest.LatestTimeStrategy;
+import org.apache.ivy.plugins.lock.CreateFileLockStrategy;
+import org.apache.ivy.plugins.lock.LockStrategy;
+import org.apache.ivy.plugins.lock.NIOFileLockStrategy;
+import org.apache.ivy.plugins.lock.NoLockStrategy;
+import org.apache.ivy.plugins.matcher.ExactOrRegexpPatternMatcher;
+import org.apache.ivy.plugins.matcher.ExactPatternMatcher;
+import org.apache.ivy.plugins.matcher.MapMatcher;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+import org.apache.ivy.plugins.matcher.RegexpPatternMatcher;
+import org.apache.ivy.plugins.namespace.Namespace;
+import org.apache.ivy.plugins.parser.ModuleDescriptorParser;
+import org.apache.ivy.plugins.parser.ModuleDescriptorParserRegistry;
+import org.apache.ivy.plugins.parser.ParserSettings;
+import org.apache.ivy.plugins.report.LogReportOutputter;
+import org.apache.ivy.plugins.report.ReportOutputter;
+import org.apache.ivy.plugins.report.XmlReportOutputter;
+import org.apache.ivy.plugins.resolver.ChainResolver;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.apache.ivy.plugins.resolver.DualResolver;
+import org.apache.ivy.plugins.resolver.ResolverSettings;
+import org.apache.ivy.plugins.signer.SignatureGenerator;
+import org.apache.ivy.plugins.trigger.Trigger;
+import org.apache.ivy.plugins.version.ChainVersionMatcher;
+import org.apache.ivy.plugins.version.ExactVersionMatcher;
+import org.apache.ivy.plugins.version.LatestVersionMatcher;
+import org.apache.ivy.plugins.version.SubVersionMatcher;
+import org.apache.ivy.plugins.version.VersionMatcher;
+import org.apache.ivy.plugins.version.VersionRangeMatcher;
+import org.apache.ivy.util.Checks;
+import org.apache.ivy.util.FileUtil;
+import org.apache.ivy.util.Message;
+import org.apache.ivy.util.filter.Filter;
+import org.apache.ivy.util.url.URLHandlerRegistry;
+
+public class IvySettings implements SortEngineSettings, PublishEngineSettings, ParserSettings,
+        DeliverEngineSettings, CheckEngineSettings, InstallEngineSettings, ResolverSettings,
+        ResolveEngineSettings, RetrieveEngineSettings, RepositoryManagementEngineSettings {
+    private static final long INTERUPT_TIMEOUT = 2000;
+
+    private Map typeDefs = new HashMap();
+
+    private Map resolversMap = new HashMap();
+
+    private DependencyResolver defaultResolver;
+
+    private DependencyResolver dictatorResolver = null;
+
+    private String defaultResolverName;
+
+    private File defaultCache;
+
+    private String defaultBranch = null;
+
+    private boolean checkUpToDate = true;
+
+    private ModuleRules moduleSettings = new ModuleRules();
+
+    // Map (String conflictManagerName -> ConflictManager)
+    private Map conflictsManager = new HashMap();
+
+    // Map (String latestStrategyName -> LatestStrategy)
+    private Map latestStrategies = new HashMap();
+
+    // Map (String name -> LockStrategy)
+    private Map lockStrategies = new HashMap();
+
+    // Map (String namespaceName -> Namespace)
+    private Map namespaces = new HashMap();
+
+    // Map (String matcherName -> Matcher)
+    private Map matchers = new HashMap();
+
+    // Map (String outputterName -> ReportOutputter)
+    private Map reportOutputters = new HashMap();
+
+    // Map (String matcherName -> VersionMatcher)
+    private Map versionMatchers = new HashMap();
+
+    // Map (String name -> CircularDependencyStrategy)
+    private Map circularDependencyStrategies = new HashMap();
+
+    // Map (String name -> RepositoryCacheManager)
+    private Map repositoryCacheManagers = new HashMap();
+
+    // Map (String name -> SignatureGenerator)
+    private Map signatureGenerators = new HashMap();
+
+    // List (Trigger)
+    private List triggers = new ArrayList();
+
+    private IvyVariableContainer variableContainer = new IvyVariableContainerImpl();
+
+    private boolean validate = true;
+
+    private LatestStrategy defaultLatestStrategy = null;
+
+    private LockStrategy defaultLockStrategy = null;
+
+    private ConflictManager defaultConflictManager = null;
+
+    private CircularDependencyStrategy circularDependencyStrategy = null;
+
+    private RepositoryCacheManager defaultRepositoryCacheManager = null;
+
+    private ResolutionCacheManager resolutionCacheManager = null;
+
+    private List listingIgnore = new ArrayList();
+
+    private boolean repositoriesConfigured;
+
+    private boolean useRemoteConfig = false;
+
+    private File defaultUserDir;
+
+    private File baseDir = new File(".").getAbsoluteFile();
+
+    private List classpathURLs = new ArrayList();
+
+    private ClassLoader classloader;
+
+    private Boolean debugConflictResolution;
+
+    private boolean logNotConvertedExclusionRule;
+
+    private VersionMatcher versionMatcher;
+
+    private StatusManager statusManager;
+
+    private Boolean debugLocking;
+
+    private Boolean dumpMemoryUsage;
+
+    private String defaultCacheIvyPattern;
+
+    private String defaultCacheArtifactPattern;
+
+    private boolean defaultUseOrigin;
+
+    private String defaultResolveMode = ResolveOptions.RESOLVEMODE_DEFAULT;
+
+    private PackingRegistry packingRegistry = new PackingRegistry();
+
+    public IvySettings() {
+        this(new IvyVariableContainerImpl());
+    }
+
+    public IvySettings(IvyVariableContainer variableContainer) {
+        setVariableContainer(variableContainer);
+        setVariable("ivy.default.settings.dir", getDefaultSettingsDir(), true);
+        setVariable("ivy.basedir", getBaseDir().getAbsolutePath());
+        setDeprecatedVariable("ivy.default.conf.dir", "ivy.default.settings.dir");
+
+        String ivyTypeDefs = System.getProperty("ivy.typedef.files");
+        if (ivyTypeDefs != null) {
+            String[] files = ivyTypeDefs.split("\\,");
+            for (int i = 0; i < files.length; i++) {
+                try {
+                    typeDefs(
+                        new FileInputStream(Checks.checkAbsolute(files[i].trim(),
+                            "ivy.typedef.files")), true);
+                } catch (FileNotFoundException e) {
+                    Message.warn("typedefs file not found: " + files[i].trim());
+                } catch (IOException e) {
+                    Message.warn("problem with typedef file: " + files[i].trim(), e);
+                }
+            }
+        } else {
+            try {
+                typeDefs(getSettingsURL("typedef.properties").openStream(), true);
+            } catch (IOException e) {
+                Message.warn("impossible to load default type defs", e);
+            }
+        }
+        LatestLexicographicStrategy latestLexicographicStrategy = new LatestLexicographicStrategy();
+        LatestRevisionStrategy latestRevisionStrategy = new LatestRevisionStrategy();
+        LatestTimeStrategy latestTimeStrategy = new LatestTimeStrategy();
+        OsgiLatestStrategy osgiLatestStrategy = new OsgiLatestStrategy();
+
+        addLatestStrategy("latest-revision", latestRevisionStrategy);
+        addLatestStrategy("latest-lexico", latestLexicographicStrategy);
+        addLatestStrategy("latest-time", latestTimeStrategy);
+        addLatestStrategy("latest-osgi", osgiLatestStrategy);
+
+        addLockStrategy("no-lock", new NoLockStrategy());
+        addLockStrategy("artifact-lock", new CreateFileLockStrategy(debugLocking()));
+        addLockStrategy("artifact-lock-nio", new NIOFileLockStrategy(debugLocking()));
+
+        addConflictManager("latest-revision", new LatestConflictManager("latest-revision",
+                latestRevisionStrategy));
+        addConflictManager("latest-compatible", new LatestCompatibleConflictManager(
+                "latest-compatible", latestRevisionStrategy));
+        addConflictManager("latest-time", new LatestConflictManager("latest-time",
+                latestTimeStrategy));
+        addConflictManager("all", new NoConflictManager());
+        addConflictManager("strict", new StrictConflictManager());
+
+        addMatcher(ExactPatternMatcher.INSTANCE);
+        addMatcher(RegexpPatternMatcher.INSTANCE);
+        addMatcher(ExactOrRegexpPatternMatcher.INSTANCE);
+
+        try {
+            // GlobPatternMatcher is optional. Only add it when available.
+            Class globClazz = IvySettings.class.getClassLoader().loadClass(
+                "org.apache.ivy.plugins.matcher.GlobPatternMatcher");
+            Field instanceField = globClazz.getField("INSTANCE");
+            addMatcher((PatternMatcher) instanceField.get(null));
+        } catch (Exception e) {
+            // ignore: the matcher isn't on the classpath
+            Message.info("impossible to define glob matcher: "
+                    + "org.apache.ivy.plugins.matcher.GlobPatternMatcher was not found", e);
+        }
+
+        addReportOutputter(new LogReportOutputter());
+        addReportOutputter(new XmlReportOutputter());
+
+        configureDefaultCircularDependencyStrategies();
+
+        listingIgnore.add(".cvsignore");
+        listingIgnore.add("CVS");
+        listingIgnore.add(".svn");
+        listingIgnore.add("maven-metadata.xml");
+        listingIgnore.add("maven-metadata.xml.md5");
+        listingIgnore.add("maven-metadata.xml.sha1");
+
+        addSystemProperties();
+    }
+
+    private synchronized void addSystemProperties() {
+        try {
+            addAllVariables((Map) System.getProperties().clone());
+        } catch (AccessControlException ex) {
+            Message.verbose("access denied to getting all system properties: they won't be available as Ivy variables."
+                    + "\nset " + ex.getPermission() + " permission if you want to access them");
+        }
+    }
+
+    /**
+     * Call this method to ask ivy to configure some variables using either a remote or a local
+     * properties file
+     */
+    public synchronized void configureRepositories(boolean remote) {
+        if (!repositoriesConfigured) {
+            Properties props = new Properties();
+            boolean configured = false;
+            if (useRemoteConfig && remote) {
+                try {
+                    URL url = new URL("http://ant.apache.org/ivy/repository.properties");
+                    Message.verbose("configuring repositories with " + url);
+                    props.load(URLHandlerRegistry.getDefault().openStream(url));
+                    configured = true;
+                } catch (Exception ex) {
+                    Message.verbose("unable to use remote repository configuration", ex);
+                    props = new Properties();
+                }
+            }
+            if (!configured) {
+                InputStream repositoryPropsStream = null;
+                try {
+                    repositoryPropsStream = getSettingsURL("repository.properties").openStream();
+                    props.load(repositoryPropsStream);
+                } catch (IOException e) {
+                    Message.error("unable to use internal repository configuration", e);
+                    if (repositoryPropsStream != null) {
+                        try {
+                            repositoryPropsStream.close();
+                        } catch (Exception ex) {
+                            // nothing to do
+                        }
+                    }
+                }
+            }
+            addAllVariables(props, false);
+            repositoriesConfigured = true;
+        }
+    }
+
+    public synchronized void typeDefs(InputStream stream) throws IOException {
+        typeDefs(stream, false);
+    }
+
+    public synchronized void typeDefs(InputStream stream, boolean silentFail) throws IOException {
+        try {
+            Properties p = new Properties();
+            p.load(stream);
+            typeDefs(p, silentFail);
+        } finally {
+            stream.close();
+        }
+    }
+
+    public synchronized void typeDefs(Properties p) {
+        typeDefs(p, false);
+    }
+
+    public synchronized void typeDefs(Properties p, boolean silentFail) {
+        for (Iterator iter = p.keySet().iterator(); iter.hasNext();) {
+            String name = (String) iter.next();
+            typeDef(name, p.getProperty(name), silentFail);
+        }
+    }
+
+    public synchronized void load(File settingsFile) throws ParseException, IOException {
+        Message.info(":: loading settings :: file = " + settingsFile);
+        long start = System.currentTimeMillis();
+        setSettingsVariables(settingsFile);
+        if (getVariable("ivy.default.ivy.user.dir") != null) {
+            setDefaultIvyUserDir(Checks.checkAbsolute(getVariable("ivy.default.ivy.user.dir"),
+                "ivy.default.ivy.user.dir"));
+        } else {
+            getDefaultIvyUserDir();
+        }
+
+        loadDefaultProperties();
+        try {
+            new XmlSettingsParser(this).parse(settingsFile.toURI().toURL());
+        } catch (MalformedURLException e) {
+            IllegalArgumentException iae = new IllegalArgumentException(
+                    "given file cannot be transformed to url: " + settingsFile);
+            iae.initCause(e);
+            throw iae;
+        }
+        setVariable("ivy.default.ivy.user.dir", getDefaultIvyUserDir().getAbsolutePath(), false);
+        Message.verbose("settings loaded (" + (System.currentTimeMillis() - start) + "ms)");
+        dumpSettings();
+    }
+
+    public synchronized void load(URL settingsURL) throws ParseException, IOException {
+        Message.info(":: loading settings :: url = " + settingsURL);
+        long start = System.currentTimeMillis();
+        setSettingsVariables(settingsURL);
+        if (getVariable("ivy.default.ivy.user.dir") != null) {
+            setDefaultIvyUserDir(Checks.checkAbsolute(getVariable("ivy.default.ivy.user.dir"),
+                "ivy.default.ivy.user.dir"));
+        } else {
+            getDefaultIvyUserDir();
+        }
+
+        loadDefaultProperties();
+        new XmlSettingsParser(this).parse(settingsURL);
+        setVariable("ivy.default.ivy.user.dir", getDefaultIvyUserDir().getAbsolutePath(), false);
+        Message.verbose("settings loaded (" + (System.currentTimeMillis() - start) + "ms)");
+        dumpSettings();
+    }
+
+    /**
+     * Default initialization of settings, useful when you don't want to load your settings from a
+     * settings file or URL, but prefer to set them manually. By calling this method you will still
+     * have the basic initialization done when loading settings.
+     * 
+     * @throws IOException
+     */
+    public synchronized void defaultInit() throws IOException {
+        if (getVariable("ivy.default.ivy.user.dir") != null) {
+            setDefaultIvyUserDir(Checks.checkAbsolute(getVariable("ivy.default.ivy.user.dir"),
+                "ivy.default.ivy.user.dir"));
+        } else {
+            getDefaultIvyUserDir();
+        }
+        getDefaultCache();
+
+        loadDefaultProperties();
+        setVariable("ivy.default.ivy.user.dir", getDefaultIvyUserDir().getAbsolutePath(), false);
+        dumpSettings();
+    }
+
+    public synchronized void loadDefault() throws ParseException, IOException {
+        load(getDefaultSettingsURL());
+    }
+
+    public synchronized void loadDefault14() throws ParseException, IOException {
+        load(getDefault14SettingsURL());
+    }
+
+    private void loadDefaultProperties() throws IOException {
+        loadProperties(getDefaultPropertiesURL(), false);
+    }
+
+    public static URL getDefaultPropertiesURL() {
+        return getSettingsURL("ivy.properties");
+    }
+
+    public static URL getDefaultSettingsURL() {
+        return getSettingsURL("ivysettings.xml");
+    }
+
+    public static URL getDefault14SettingsURL() {
+        return getSettingsURL("ivysettings-1.4.xml");
+    }
+
+    private String getDefaultSettingsDir() {
+        String ivysettingsLocation = getDefaultSettingsURL().toExternalForm();
+        return ivysettingsLocation.substring(0,
+            ivysettingsLocation.length() - "ivysettings.xml".length() - 1);
+    }
+
+    private static URL getSettingsURL(String file) {
+        return XmlSettingsParser.class.getResource(file);
+    }
+
+    public synchronized void setSettingsVariables(File settingsFile) {
+        try {
+            setVariable("ivy.settings.dir", new File(settingsFile.getAbsolutePath()).getParent());
+            setDeprecatedVariable("ivy.conf.dir", "ivy.settings.dir");
+            setVariable("ivy.settings.file", settingsFile.getAbsolutePath());
+            setDeprecatedVariable("ivy.conf.file", "ivy.settings.file");
+            setVariable("ivy.settings.url", settingsFile.toURI().toURL().toExternalForm());
+            setDeprecatedVariable("ivy.conf.url", "ivy.settings.url");
+            setVariable("ivy.settings.dir.url", new File(settingsFile.getAbsolutePath())
+                    .getParentFile().toURI().toURL().toExternalForm());
+        } catch (MalformedURLException e) {
+            IllegalArgumentException iae = new IllegalArgumentException(
+                    "given file cannot be transformed to url: " + settingsFile);
+            iae.initCause(e);
+            throw iae;
+        }
+    }
+
+    /**
+     * Sets a deprecated variable with the value of the new variable
+     * 
+     * @param deprecatedKey
+     *            the deprecated variable name
+     * @param newKey
+     *            the new variable name
+     */
+    private void setDeprecatedVariable(String deprecatedKey, String newKey) {
+        setVariable(deprecatedKey, getVariable(newKey));
+    }
+
+    public synchronized void setSettingsVariables(URL settingsURL) {
+        String settingsURLStr = settingsURL.toExternalForm();
+        setVariable("ivy.settings.url", settingsURLStr);
+        setDeprecatedVariable("ivy.conf.url", "ivy.settings.url");
+        int slashIndex = settingsURLStr.lastIndexOf('/');
+        if (slashIndex != -1) {
+            String dirUrl = settingsURLStr.substring(0, slashIndex);
+            setVariable("ivy.settings.dir", dirUrl);
+            setVariable("ivy.settings.dir.url", dirUrl);
+            setDeprecatedVariable("ivy.conf.dir", "ivy.settings.dir");
+        } else {
+            Message.warn("settings url does not contain any slash (/): "
+                    + "ivy.settings.dir variable not set");
+        }
+    }
+
+    private void dumpSettings() {
+        Message.verbose("\tdefault cache: " + getDefaultCache());
+        Message.verbose("\tdefault resolver: " + getDefaultResolver());
+        Message.debug("\tdefault latest strategy: " + getDefaultLatestStrategy());
+        Message.debug("\tdefault conflict manager: " + getDefaultConflictManager());
+        Message.debug("\tcircular dependency strategy: " + getCircularDependencyStrategy());
+        Message.debug("\tvalidate: " + doValidate());
+        Message.debug("\tcheck up2date: " + isCheckUpToDate());
+
+        if (!classpathURLs.isEmpty()) {
+            Message.verbose("\t-- " + classpathURLs.size() + " custom classpath urls:");
+            for (Iterator iter = classpathURLs.iterator(); iter.hasNext();) {
+                Message.debug("\t\t" + iter.next());
+            }
+        }
+        Message.verbose("\t-- " + resolversMap.size() + " resolvers:");
+        for (Iterator iter = resolversMap.values().iterator(); iter.hasNext();) {
+            DependencyResolver resolver = (DependencyResolver) iter.next();
+            resolver.dumpSettings();
+        }
+        Message.debug("\tmodule settings:");
+        moduleSettings.dump("\t\t");
+    }
+
+    public synchronized void loadProperties(URL url) throws IOException {
+        loadProperties(url, true);
+    }
+
+    public synchronized void loadProperties(URL url, boolean overwrite) throws IOException {
+        loadProperties(url.openStream(), overwrite);
+    }
+
+    public synchronized void loadProperties(File file) throws IOException {
+        loadProperties(file, true);
+    }
+
+    public synchronized void loadProperties(File file, boolean overwrite) throws IOException {
+        loadProperties(new FileInputStream(file), overwrite);
+    }
+
+    private void loadProperties(InputStream stream, boolean overwrite) throws IOException {
+        try {
+            Properties properties = new Properties();
+            properties.load(stream);
+            addAllVariables(properties, overwrite);
+        } finally {
+            if (stream != null) {
+                try {
+                    stream.close();
+                } catch (IOException e) {
+                    // nothing
+                }
+            }
+        }
+    }
+
+    public synchronized void setVariable(String varName, String value) {
+        setVariable(varName, value, true);
+    }
+
+    public synchronized void setVariable(String varName, String value, boolean overwrite) {
+        setVariable(varName, value, overwrite, null, null);
+    }
+
+    public synchronized void setVariable(String varName, String value, boolean overwrite,
+            String ifSetVar, String unlessSetVar) {
+        if (ifSetVar != null && variableContainer.getVariable(ifSetVar) == null) {
+            Message.verbose("Not setting '" + varName + "' to '" + value + "' since '" + ifSetVar
+                    + "' is not set.");
+            return;
+        }
+        if (unlessSetVar != null && variableContainer.getVariable(unlessSetVar) != null) {
+            Message.verbose("Not setting '" + varName + "' to '" + value + "' since '"
+                    + unlessSetVar + "' is set.");
+            return;
+        }
+        variableContainer.setVariable(varName, value, overwrite);
+    }
+
+    public synchronized void addAllVariables(Map variables) {
+        addAllVariables(variables, true);
+    }
+
+    public synchronized void addAllVariables(Map<?, ?> variables, boolean overwrite) {
+        for (Map.Entry<?, ?> entry : variables.entrySet()) {
+            String key = entry.getKey().toString();
+            Object val = entry.getValue();
+            if (val == null || val instanceof String) {
+                setVariable(key, (String) val, overwrite);
+            }
+        }
+    }
+
+    /**
+     * Substitute variables in the given string by their value found in the current set of variables
+     * 
+     * @param str
+     *            the string in which substitution should be made
+     * @return the string where all current ivy variables have been substituted by their value If
+     *         the input str doesn't use any variable, the same object is returned
+     */
+    public synchronized String substitute(String str) {
+        return IvyPatternHelper.substituteVariables(str, variableContainer);
+    }
+
+    /**
+     * Substitute variables in the given map values by their value found in the current set of
+     * variables
+     * 
+     * @param strings
+     *            the map of strings in which substitution should be made
+     * @return a new map of strings in which all current ivy variables in values have been
+     *         substituted by their value
+     */
+    public synchronized Map/* <String, String> */substitute(Map/* <String, String> */strings) {
+        Map substituted = new LinkedHashMap();
+        for (Iterator it = strings.entrySet().iterator(); it.hasNext();) {
+            Map.Entry entry = (Map.Entry) it.next();
+            substituted.put(entry.getKey(), substitute((String) entry.getValue()));
+        }
+        return substituted;
+    }
+
+    /**
+     * Returns the variables loaded in configuration file. Those variables may better be seen as ant
+     * properties
+     * 
+     * @return
+     */
+    public synchronized IvyVariableContainer getVariables() {
+        return variableContainer;
+    }
+
+    public synchronized Class typeDef(String name, String className) {
+        return typeDef(name, className, false);
+    }
+
+    public synchronized Class typeDef(String name, String className, boolean silentFail) {
+        Class clazz = classForName(className, silentFail);
+        if (clazz != null) {
+            typeDefs.put(name, clazz);
+        }
+        return clazz;
+    }
+
+    private Class classForName(String className, boolean silentFail) {
+        try {
+            return getClassLoader().loadClass(className);
+        } catch (ClassNotFoundException e) {
+            if (silentFail) {
+                Message.info("impossible to define new type: class not found: " + className
+                        + " in " + classpathURLs + " nor Ivy classloader");
+                return null;
+            } else {
+                throw new RuntimeException("impossible to define new type: class not found: "
+                        + className + " in " + classpathURLs + " nor Ivy classloader");
+            }
+        }
+    }
+
+    private ClassLoader getClassLoader() {
+        if (classloader == null) {
+            if (classpathURLs.isEmpty()) {
+                classloader = Ivy.class.getClassLoader();
+            } else {
+                classloader = new URLClassLoader(
+                        (URL[]) classpathURLs.toArray(new URL[classpathURLs.size()]),
+                        Ivy.class.getClassLoader());
+            }
+        }
+        return classloader;
+    }
+
+    public synchronized void addClasspathURL(URL url) {
+        classpathURLs.add(url);
+        classloader = null;
+    }
+
+    public synchronized Map getTypeDefs() {
+        return typeDefs;
+    }
+
+    public synchronized Class getTypeDef(String name) {
+        return (Class) typeDefs.get(name);
+    }
+
+    // methods which match ivy conf method signature specs
+    public synchronized void addConfigured(DependencyResolver resolver) {
+        addResolver(resolver);
+    }
+
+    public synchronized void addConfigured(ModuleDescriptorParser parser) {
+        ModuleDescriptorParserRegistry.getInstance().addParser(parser);
+    }
+
+    public synchronized void addConfigured(SignatureGenerator generator) {
+        addSignatureGenerator(generator);
+    }
+
+    public synchronized void addSignatureGenerator(SignatureGenerator generator) {
+        init(generator);
+        signatureGenerators.put(generator.getName(), generator);
+    }
+
+    public synchronized SignatureGenerator getSignatureGenerator(String name) {
+        return (SignatureGenerator) signatureGenerators.get(name);
+    }
+
+    public synchronized void addResolver(DependencyResolver resolver) {
+        if (resolver == null) {
+            throw new NullPointerException("null resolver");
+        }
+        init(resolver);
+        resolversMap.put(resolver.getName(), resolver);
+        if (resolver instanceof ChainResolver) {
+            List subresolvers = ((ChainResolver) resolver).getResolvers();
+            for (Iterator iter = subresolvers.iterator(); iter.hasNext();) {
+                DependencyResolver dr = (DependencyResolver) iter.next();
+                addResolver(dr);
+            }
+        } else if (resolver instanceof DualResolver) {
+            DependencyResolver ivyResolver = ((DualResolver) resolver).getIvyResolver();
+            if (ivyResolver != null) {
+                addResolver(ivyResolver);
+            }
+            DependencyResolver artifactResolver = ((DualResolver) resolver).getArtifactResolver();
+            if (artifactResolver != null) {
+                addResolver(artifactResolver);
+            }
+        }
+    }
+
+    public synchronized void setDefaultCache(File cacheDirectory) {
+        setVariable("ivy.cache.dir", cacheDirectory.getAbsolutePath(), false);
+        defaultCache = cacheDirectory;
+        if (defaultRepositoryCacheManager != null) {
+            if ("default-cache".equals(defaultRepositoryCacheManager.getName())
+                    && defaultRepositoryCacheManager instanceof DefaultRepositoryCacheManager) {
+                ((DefaultRepositoryCacheManager) defaultRepositoryCacheManager)
+                        .setBasedir(defaultCache);
+            }
+        }
+    }
+
+    public synchronized void setDefaultResolver(String resolverName) {
+        checkResolverName(resolverName);
+        if (resolverName != null && !resolverName.equals(defaultResolverName)) {
+            defaultResolver = null;
+        }
+        defaultResolverName = resolverName;
+    }
+
+    private void checkResolverName(String resolverName) {
+        if (resolverName != null && !resolversMap.containsKey(resolverName)) {
+            throw new IllegalArgumentException("no resolver found called " + resolverName
+                    + ": check your settings");
+        }
+    }
+
+    /**
+     * regular expressions as explained in Pattern class may be used in attributes
+     */
+    public synchronized void addModuleConfiguration(Map attributes, PatternMatcher matcher,
+            String resolverName, String branch, String conflictManager, String resolveMode) {
+        checkResolverName(resolverName);
+        moduleSettings.defineRule(new MapMatcher(attributes, matcher), new ModuleSettings(
+                resolverName, branch, conflictManager, resolveMode));
+    }
+
+    /**
+     * Return the canonical form of a filename.
+     * <p>
+     * If the specified file name is relative it is resolved with respect to the settings's base
+     * directory.
+     * 
+     * @param fileName
+     *            The name of the file to resolve. Must not be <code>null</code>.
+     * 
+     * @return the resolved File.
+     * 
+     */
+    public synchronized File resolveFile(String fileName) {
+        return FileUtil.resolveFile(baseDir, fileName);
+    }
+
+    public synchronized void setBaseDir(File baseDir) {
+        this.baseDir = baseDir.getAbsoluteFile();
+        setVariable("ivy.basedir", this.baseDir.getAbsolutePath());
+        setVariable("basedir", this.baseDir.getAbsolutePath(), false);
+    }
+
+    public synchronized File getBaseDir() {
+        return baseDir;
+    }
+
+    public synchronized File getDefaultIvyUserDir() {
+        if (defaultUserDir == null) {
+            if (getVariable("ivy.home") != null) {
+                setDefaultIvyUserDir(Checks.checkAbsolute(getVariable("ivy.home"), "ivy.home"));
+                Message.verbose("using ivy.default.ivy.user.dir variable for default ivy user dir: "
+                        + defaultUserDir);
+            } else {
+                setDefaultIvyUserDir(new File(System.getProperty("user.home"), ".ivy2"));
+                Message.verbose("no default ivy user dir defined: set to " + defaultUserDir);
+            }
+        }
+        return defaultUserDir;
+    }
+
+    public synchronized void setDefaultIvyUserDir(File defaultUserDir) {
+        this.defaultUserDir = defaultUserDir;
+        setVariable("ivy.default.ivy.user.dir", this.defaultUserDir.getAbsolutePath());
+        setVariable("ivy.home", this.defaultUserDir.getAbsolutePath());
+    }
+
+    public synchronized File getDefaultCache() {
+        if (defaultCache == null) {
+            String cache = getVariable("ivy.cache.dir");
+            if (cache != null) {
+                defaultCache = Checks.checkAbsolute(cache, "ivy.cache.dir");
+            } else {
+                setDefaultCache(new File(getDefaultIvyUserDir(), "cache"));
+                Message.verbose("no default cache defined: set to " + defaultCache);
+            }
+        }
+        return defaultCache;
+    }
+
+    public synchronized void setDefaultRepositoryCacheBasedir(String repositoryCacheRoot) {
+        setVariable("ivy.cache.repository", repositoryCacheRoot, true);
+        if (defaultRepositoryCacheManager != null
+                && "default-cache".equals(defaultRepositoryCacheManager.getName())
+                && defaultRepositoryCacheManager instanceof DefaultRepositoryCacheManager) {
+            ((DefaultRepositoryCacheManager) defaultRepositoryCacheManager)
+                    .setBasedir(getDefaultRepositoryCacheBasedir());
+        }
+    }
+
+    public synchronized void setDefaultResolutionCacheBasedir(String resolutionCacheRoot) {
+        setVariable("ivy.cache.resolution", resolutionCacheRoot, true);
+        if (resolutionCacheManager != null
+                && resolutionCacheManager instanceof DefaultResolutionCacheManager) {
+            ((DefaultResolutionCacheManager) resolutionCacheManager)
+                    .setBasedir(getDefaultResolutionCacheBasedir());
+        }
+    }
+
+    public synchronized File getDefaultRepositoryCacheBasedir() {
+        String repositoryCacheRoot = getVariable("ivy.cache.repository");
+        if (repositoryCacheRoot != null) {
+            return Checks.checkAbsolute(repositoryCacheRoot, "ivy.cache.repository");
+        } else {
+            return getDefaultCache();
+        }
+    }
+
+    public synchronized File getDefaultResolutionCacheBasedir() {
+        String resolutionCacheRoot = getVariable("ivy.cache.resolution");
+        if (resolutionCacheRoot != null) {
+            return Checks.checkAbsolute(resolutionCacheRoot, "ivy.cache.resolution");
+        } else {
+            return getDefaultCache();
+        }
+    }
+
+    public synchronized void setDictatorResolver(DependencyResolver resolver) {
+        dictatorResolver = resolver;
+    }
+
+    public synchronized DependencyResolver getResolver(ModuleRevisionId mrid) {
+        if (dictatorResolver != null) {
+            return dictatorResolver;
+        }
+        String resolverName = getResolverName(mrid);
+        return getResolver(resolverName);
+    }
+
+    public synchronized boolean hasResolver(String resolverName) {
+        return resolversMap.containsKey(resolverName);
+    }
+
+    public synchronized DependencyResolver getResolver(String resolverName) {
+        if (dictatorResolver != null) {
+            return dictatorResolver;
+        }
+        DependencyResolver resolver = (DependencyResolver) resolversMap.get(resolverName);
+        if (resolver == null) {
+            Message.error("unknown resolver " + resolverName);
+        }
+        return resolver;
+    }
+
+    public synchronized DependencyResolver getDefaultResolver() {
+        if (dictatorResolver != null) {
+            return dictatorResolver;
+        }
+        if (defaultResolver == null) {
+            defaultResolver = (DependencyResolver) resolversMap.get(defaultResolverName);
+        }
+        return defaultResolver;
+    }
+
+    public synchronized String getResolverName(ModuleRevisionId mrid) {
+        ModuleSettings ms = (ModuleSettings) moduleSettings.getRule(mrid, new Filter() {
+            public boolean accept(Object o) {
+                return ((ModuleSettings) o).getResolverName() != null;
+            }
+        });
+        return ms == null ? defaultResolverName : ms.getResolverName();
+    }
+
+    public synchronized String getDefaultBranch(ModuleId moduleId) {
+        ModuleSettings ms = (ModuleSettings) moduleSettings.getRule(moduleId, new Filter() {
+            public boolean accept(Object o) {
+                return ((ModuleSettings) o).getBranch() != null;
+            }
+        });
+        return ms == null ? getDefaultBranch() : ms.getBranch();
+    }
+
+    public synchronized String getDefaultBranch() {
+        return defaultBranch;
+    }
+
+    public synchronized void setDefaultBranch(String defaultBranch) {
+        this.defaultBranch = defaultBranch;
+    }
+
+    public synchronized ConflictManager getConflictManager(ModuleId moduleId) {
+        ModuleSettings ms = (ModuleSettings) moduleSettings.getRule(moduleId, new Filter() {
+            public boolean accept(Object o) {
+                return ((ModuleSettings) o).getConflictManager() != null;
+            }
+        });
+        if (ms == null) {
+            return getDefaultConflictManager();
+        } else {
+            ConflictManager cm = getConflictManager(ms.getConflictManager());
+            if (cm == null) {
+                throw new IllegalStateException("ivy badly configured: unknown conflict manager "
+                        + ms.getConflictManager());
+            }
+            return cm;
+        }
+    }
+
+    public synchronized String getResolveMode(ModuleId moduleId) {
+        ModuleSettings ms = (ModuleSettings) moduleSettings.getRule(moduleId, new Filter() {
+            public boolean accept(Object o) {
+                return ((ModuleSettings) o).getResolveMode() != null;
+            }
+        });
+        return ms == null ? getDefaultResolveMode() : ms.getResolveMode();
+    }
+
+    public synchronized String getDefaultResolveMode() {
+        return defaultResolveMode;
+    }
+
+    public synchronized void setDefaultResolveMode(String defaultResolveMode) {
+        this.defaultResolveMode = defaultResolveMode;
+    }
+
+    public synchronized void addConfigured(ConflictManager cm) {
+        addConflictManager(cm.getName(), cm);
+    }
+
+    public synchronized ConflictManager getConflictManager(String name) {
+        if ("default".equals(name)) {
+            return getDefaultConflictManager();
+        }
+        return (ConflictManager) conflictsManager.get(name);
+    }
+
+    public synchronized void addConflictManager(String name, ConflictManager cm) {
+        init(cm);
+        conflictsManager.put(name, cm);
+    }
+
+    public synchronized void addConfigured(LatestStrategy latest) {
+        addLatestStrategy(latest.getName(), latest);
+    }
+
+    public synchronized LatestStrategy getLatestStrategy(String name) {
+        if ("default".equals(name)) {
+            return getDefaultLatestStrategy();
+        }
+        return (LatestStrategy) latestStrategies.get(name);
+    }
+
+    public synchronized void addLatestStrategy(String name, LatestStrategy latest) {
+        init(latest);
+        latestStrategies.put(name, latest);
+    }
+
+    public synchronized void addConfigured(LockStrategy lockStrategy) {
+        addLockStrategy(lockStrategy.getName(), lockStrategy);
+    }
+
+    public synchronized LockStrategy getLockStrategy(String name) {
+        if ("default".equals(name)) {
+            return getDefaultLockStrategy();
+        }
+        return (LockStrategy) lockStrategies.get(name);
+    }
+
+    public synchronized void addLockStrategy(String name, LockStrategy lockStrategy) {
+        init(lockStrategy);
+        lockStrategies.put(name, lockStrategy);
+    }
+
+    public synchronized void addConfigured(Namespace ns) {
+        addNamespace(ns);
+    }
+
+    public synchronized Namespace getNamespace(String name) {
+        if ("system".equals(name)) {
+            return getSystemNamespace();
+        }
+        return (Namespace) namespaces.get(name);
+    }
+
+    public final Namespace getSystemNamespace() {
+        return Namespace.SYSTEM_NAMESPACE;
+    }
+
+    public synchronized void addNamespace(Namespace ns) {
+        init(ns);
+        namespaces.put(ns.getName(), ns);
+    }
+
+    public synchronized void addConfigured(PatternMatcher m) {
+        addMatcher(m);
+    }
+
+    public synchronized PatternMatcher getMatcher(String name) {
+        return (PatternMatcher) matchers.get(name);
+    }
+
+    public synchronized void addMatcher(PatternMatcher m) {
+        init(m);
+        matchers.put(m.getName(), m);
+    }
+
+    public synchronized void addConfigured(RepositoryCacheManager c) {
+        addRepositoryCacheManager(c);
+    }
+
+    public synchronized RepositoryCacheManager getRepositoryCacheManager(String name) {
+        return (RepositoryCacheManager) repositoryCacheManagers.get(name);
+    }
+
+    public synchronized void addRepositoryCacheManager(RepositoryCacheManager c) {
+        init(c);
+        repositoryCacheManagers.put(c.getName(), c);
+    }
+
+    public synchronized RepositoryCacheManager[] getRepositoryCacheManagers() {
+        return (RepositoryCacheManager[]) repositoryCacheManagers.values().toArray(
+            new RepositoryCacheManager[repositoryCacheManagers.size()]);
+    }
+
+    public synchronized void addConfigured(ReportOutputter outputter) {
+        addReportOutputter(outputter);
+    }
+
+    public synchronized ReportOutputter getReportOutputter(String name) {
+        return (ReportOutputter) reportOutputters.get(name);
+    }
+
+    public synchronized void addReportOutputter(ReportOutputter outputter) {
+        init(outputter);
+        reportOutputters.put(outputter.getName(), outputter);
+    }
+
+    public synchronized ReportOutputter[] getReportOutputters() {
+        return (ReportOutputter[]) reportOutputters.values().toArray(
+            new ReportOutputter[reportOutputters.size()]);
+    }
+
+    public synchronized void addConfigured(VersionMatcher vmatcher) {
+        addVersionMatcher(vmatcher);
+    }
+
+    public synchronized VersionMatcher getVersionMatcher(String name) {
+        return (VersionMatcher) versionMatchers.get(name);
+    }
+
+    public synchronized void addVersionMatcher(VersionMatcher vmatcher) {
+        init(vmatcher);
+        versionMatchers.put(vmatcher.getName(), vmatcher);
+
+        if (versionMatcher == null) {
+            versionMatcher = new ChainVersionMatcher();
+            addVersionMatcher(new ExactVersionMatcher());
+        }
+        if (versionMatcher instanceof ChainVersionMatcher) {
+            ChainVersionMatcher chain = (ChainVersionMatcher) versionMatcher;
+            chain.add(vmatcher);
+        }
+    }
+
+    public synchronized VersionMatcher[] getVersionMatchers() {
+        return (VersionMatcher[]) versionMatchers.values().toArray(
+            new VersionMatcher[versionMatchers.size()]);
+    }
+
+    public synchronized VersionMatcher getVersionMatcher() {
+        if (versionMatcher == null) {
+            configureDefaultVersionMatcher();
+        }
+        return versionMatcher;
+    }
+
+    public synchronized void configureDefaultVersionMatcher() {
+        addVersionMatcher(new LatestVersionMatcher());
+        addVersionMatcher(new SubVersionMatcher());
+        addVersionMatcher(new VersionRangeMatcher());
+    }
+
+    public synchronized CircularDependencyStrategy getCircularDependencyStrategy() {
+        if (circularDependencyStrategy == null) {
+            circularDependencyStrategy = getCircularDependencyStrategy("default");
+        }
+        return circularDependencyStrategy;
+    }
+
+    public synchronized CircularDependencyStrategy getCircularDependencyStrategy(String name) {
+        if ("default".equals(name)) {
+            name = "warn";
+        }
+        return (CircularDependencyStrategy) circularDependencyStrategies.get(name);
+    }
+
+    public synchronized void setCircularDependencyStrategy(CircularDependencyStrategy strategy) {
+        circularDependencyStrategy = strategy;
+    }
+
+    public synchronized void addConfigured(CircularDependencyStrategy strategy) {
+        addCircularDependencyStrategy(strategy);
+    }
+
+    private void addCircularDependencyStrategy(CircularDependencyStrategy strategy) {
+        circularDependencyStrategies.put(strategy.getName(), strategy);
+    }
+
+    private void configureDefaultCircularDependencyStrategies() {
+        addCircularDependencyStrategy(WarnCircularDependencyStrategy.getInstance());
+        addCircularDependencyStrategy(ErrorCircularDependencyStrategy.getInstance());
+        addCircularDependencyStrategy(IgnoreCircularDependencyStrategy.getInstance());
+    }
+
+    public synchronized StatusManager getStatusManager() {
+        if (statusManager == null) {
+            statusManager = StatusManager.newDefaultInstance();
+        }
+        return statusManager;
+    }
+
+    public void setStatusManager(StatusManager statusManager) {
+        this.statusManager = statusManager;
+    }
+
+    /**
+     * Returns the file names of the files that should be ignored when creating a file listing.
+     */
+    public synchronized String[] getIgnorableFilenames() {
+        return (String[]) listingIgnore.toArray(new String[listingIgnore.size()]);
+    }
+
+    /**
+     * Filters the names list by removing all names that should be ignored as defined by the listing
+     * ignore list
+     * 
+     * @param names
+     */
+    public synchronized void filterIgnore(Collection names) {
+        names.removeAll(listingIgnore);
+    }
+
+    public synchronized boolean isCheckUpToDate() {
+        return checkUpToDate;
+    }
+
+    public synchronized void setCheckUpToDate(boolean checkUpToDate) {
+        this.checkUpToDate = checkUpToDate;
+    }
+
+    public synchronized boolean doValidate() {
+        return validate;
+    }
+
+    public synchronized void setValidate(boolean validate) {
+        this.validate = validate;
+    }
+
+    public synchronized String getVariable(String name) {
+        return variableContainer.getVariable(name);
+    }
+
+    public synchronized ConflictManager getDefaultConflictManager() {
+        if (defaultConflictManager == null) {
+            defaultConflictManager = new LatestConflictManager(getDefaultLatestStrategy());
+            ((LatestConflictManager) defaultConflictManager).setSettings(this);
+        }
+        return defaultConflictManager;
+    }
+
+    public synchronized void setDefaultConflictManager(ConflictManager defaultConflictManager) {
+        this.defaultConflictManager = defaultConflictManager;
+    }
+
+    public synchronized LatestStrategy getDefaultLatestStrategy() {
+        if (defaultLatestStrategy == null) {
+            defaultLatestStrategy = new LatestRevisionStrategy();
+        }
+        return defaultLatestStrategy;
+    }
+
+    public synchronized void setDefaultLatestStrategy(LatestStrategy defaultLatestStrategy) {
+        this.defaultLatestStrategy = defaultLatestStrategy;
+    }
+
+    public synchronized LockStrategy getDefaultLockStrategy() {
+        if (defaultLockStrategy == null) {
+            defaultLockStrategy = new NoLockStrategy();
+        }
+        return defaultLockStrategy;
+    }
+
+    public synchronized void setDefaultLockStrategy(LockStrategy defaultLockStrategy) {
+        this.defaultLockStrategy = defaultLockStrategy;
+    }
+
+    public synchronized RepositoryCacheManager getDefaultRepositoryCacheManager() {
+        if (defaultRepositoryCacheManager == null) {
+            defaultRepositoryCacheManager = new DefaultRepositoryCacheManager("default-cache",
+                    this, getDefaultRepositoryCacheBasedir());
+            addRepositoryCacheManager(defaultRepositoryCacheManager);
+        }
+        return defaultRepositoryCacheManager;
+    }
+
+    public synchronized void setDefaultRepositoryCacheManager(RepositoryCacheManager cache) {
+        this.defaultRepositoryCacheManager = cache;
+    }
+
+    public synchronized ResolutionCacheManager getResolutionCacheManager() {
+        if (resolutionCacheManager == null) {
+            resolutionCacheManager = new DefaultResolutionCacheManager(
+                    getDefaultResolutionCacheBasedir());
+            init(resolutionCacheManager);
+        }
+        return resolutionCacheManager;
+    }
+
+    public synchronized void setResolutionCacheManager(ResolutionCacheManager resolutionCacheManager) {
+        this.resolutionCacheManager = resolutionCacheManager;
+    }
+
+    public synchronized void addTrigger(Trigger trigger) {
+        init(trigger);
+        triggers.add(trigger);
+    }
+
+    public synchronized List getTriggers() {
+        return triggers;
+    }
+
+    public synchronized void addConfigured(Trigger trigger) {
+        addTrigger(trigger);
+    }
+
+    public synchronized boolean isUseRemoteConfig() {
+        return useRemoteConfig;
+    }
+
+    public synchronized void setUseRemoteConfig(boolean useRemoteConfig) {
+        this.useRemoteConfig = useRemoteConfig;
+    }
+
+    public synchronized boolean logModulesInUse() {
+        String var = getVariable("ivy.log.modules.in.use");
+        return var == null || Boolean.valueOf(var).booleanValue();
+    }
+
+    public synchronized boolean logModuleWhenFound() {
+        String var = getVariable("ivy.log.module.when.found");
+        return var == null || Boolean.valueOf(var).booleanValue();
+    }
+
+    public synchronized boolean logResolvedRevision() {
+        String var = getVariable("ivy.log.resolved.revision");
+        return var == null || Boolean.valueOf(var).booleanValue();
+    }
+
+    public synchronized boolean debugConflictResolution() {
+        if (debugConflictResolution == null) {
+            String var = getVariable("ivy.log.conflict.resolution");
+            debugConflictResolution = Boolean.valueOf(var != null
+                    && Boolean.valueOf(var).booleanValue());
+        }
+        return debugConflictResolution.booleanValue();
+    }
+
+    public synchronized boolean debugLocking() {
+        if (debugLocking == null) {
+            String var = getVariable("ivy.log.locking");
+            debugLocking = Boolean.valueOf(var != null && Boolean.valueOf(var).booleanValue());
+        }
+        return debugLocking.booleanValue();
+    }
+
+    public synchronized boolean dumpMemoryUsage() {
+        if (dumpMemoryUsage == null) {
+            String var = getVariable("ivy.log.memory");
+            dumpMemoryUsage = Boolean.valueOf(var != null && Boolean.valueOf(var).booleanValue());
+        }
+        return dumpMemoryUsage.booleanValue();
+    }
+
+    public synchronized boolean logNotConvertedExclusionRule() {
+        return logNotConvertedExclusionRule;
+    }
+
+    public synchronized void setLogNotConvertedExclusionRule(boolean logNotConvertedExclusionRule) {
+        this.logNotConvertedExclusionRule = logNotConvertedExclusionRule;
+    }
+
+    private void init(Object obj) {
+        if (obj instanceof IvySettingsAware) {
+            ((IvySettingsAware) obj).setSettings(this);
+        } else if (obj instanceof DependencyResolver) {
+            ((DependencyResolver) obj).setSettings(this);
+        }
+    }
+
+    private static class ModuleSettings {
+        private String resolverName;
+
+        private String branch;
+
+        private String conflictManager;
+
+        private String resolveMode;
+
+        public ModuleSettings(String resolver, String branchName, String conflictMgr,
+                String resolveMode) {
+            this.resolverName = resolver;
+            this.branch = branchName;
+            this.conflictManager = conflictMgr;
+            this.resolveMode = resolveMode;
+        }
+
+        public String toString() {
+            return (resolverName != null ? "resolver: " + resolverName : "")
+                    + (branch != null ? "branch: " + branch : "")
+                    + (conflictManager != null ? "conflictManager: " + conflictManager : "")
+                    + (resolveMode != null ? "resolveMode: " + resolveMode : "");
+        }
+
+        public String getBranch() {
+            return branch;
+        }
+
+        public String getResolverName() {
+            return resolverName;
+        }
+
+        public String getConflictManager() {
+            return conflictManager;
+        }
+
+        public String getResolveMode() {
+            return resolveMode;
+        }
+    }
+
+    public final long getInterruptTimeout() {
+        return INTERUPT_TIMEOUT;
+    }
+
+    public synchronized Collection getResolvers() {
+        return resolversMap.values();
+    }
+
+    public synchronized Collection getResolverNames() {
+        return resolversMap.keySet();
+    }
+
+    public synchronized Collection getMatcherNames() {
+        return matchers.keySet();
+    }
+
+    public synchronized IvyVariableContainer getVariableContainer() {
+        return variableContainer;
+    }
+
+    /**
+     * Use a different variable container.
+     * 
+     * @param variables
+     */
+    public synchronized void setVariableContainer(IvyVariableContainer variables) {
+        variableContainer = variables;
+    }
+
+    public synchronized RelativeUrlResolver getRelativeUrlResolver() {
+        return new NormalRelativeUrlResolver();
+    }
+
+    public synchronized void setDefaultCacheIvyPattern(String defaultCacheIvyPattern) {
+        CacheUtil.checkCachePattern(defaultCacheIvyPattern);
+        this.defaultCacheIvyPattern = defaultCacheIvyPattern;
+    }
+
+    public synchronized String getDefaultCacheIvyPattern() {
+        return defaultCacheIvyPattern;
+    }
+
+    public synchronized void setDefaultCacheArtifactPattern(String defaultCacheArtifactPattern) {
+        CacheUtil.checkCachePattern(defaultCacheArtifactPattern);
+        this.defaultCacheArtifactPattern = defaultCacheArtifactPattern;
+    }
+
+    public synchronized String getDefaultCacheArtifactPattern() {
+        return defaultCacheArtifactPattern;
+    }
+
+    public synchronized void setDefaultUseOrigin(boolean useOrigin) {
+        defaultUseOrigin = useOrigin;
+    }
+
+    public synchronized boolean isDefaultUseOrigin() {
+        return defaultUseOrigin;
+    }
+
+    public synchronized void useDeprecatedUseOrigin() {
+        Message.deprecated("useOrigin option is deprecated when calling resolve, use useOrigin"
+                + " setting on the cache implementation instead");
+        setDefaultUseOrigin(true);
+
+    }
+
+    /**
+     * Validates the settings, throwing an {@link IllegalStateException} if the current state is not
+     * valid.
+     * 
+     * @throws IllegalStateException
+     *             if the settings is not valid.
+     */
+    public synchronized void validate() {
+        validateAll(resolversMap.values());
+        validateAll(conflictsManager.values());
+        validateAll(latestStrategies.values());
+        validateAll(lockStrategies.values());
+        validateAll(repositoryCacheManagers.values());
+        validateAll(reportOutputters.values());
+        validateAll(circularDependencyStrategies.values());
+        validateAll(versionMatchers.values());
+        validateAll(namespaces.values());
+    }
+
+    /**
+     * Validates all {@link Validatable} objects in the collection.
+     * 
+     * @param objects
+     *            the collection of objects to validate.
+     * @throws IllegalStateException
+     *             if any of the objects is not valid.
+     */
+    private void validateAll(Collection values) {
+        for (Iterator iterator = values.iterator(); iterator.hasNext();) {
+            Object object = iterator.next();
+            if (object instanceof Validatable) {
+                ((Validatable) object).validate();
+            }
+        }
+    }
+
+    public Namespace getContextNamespace() {
+        return Namespace.SYSTEM_NAMESPACE;
+    }
+
+    public synchronized void addConfigured(ArchivePacking packing) {
+        init(packing);
+        packingRegistry.register(packing);
+    }
+
+    public PackingRegistry getPackingRegistry() {
+        return packingRegistry;
+    }
+}
diff --git a/src/java/org/apache/ivy/core/settings/IvyVariableContainer.java b/src/java/org/apache/ivy/core/settings/IvyVariableContainer.java
new file mode 100644
index 0000000..e3d15a4
--- /dev/null
+++ b/src/java/org/apache/ivy/core/settings/IvyVariableContainer.java
@@ -0,0 +1,39 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.settings;
+
+/**
+ * Store and provide access to the ivy variables.
+ */
+public interface IvyVariableContainer extends Cloneable {
+
+    public void setVariable(String varName, String value, boolean overwrite);
+
+    public String getVariable(String name);
+
+    /**
+     * Specifies the prefix used to indicate a variable is an environment variable. If the prefix
+     * doesn't end with a '.', it will be added automatically.
+     * 
+     * @param prefix
+     *            the prefix to use for the environment variables
+     */
+    public void setEnvironmentPrefix(String prefix);
+
+    public Object clone();
+}
diff --git a/src/java/org/apache/ivy/core/settings/IvyVariableContainerImpl.java b/src/java/org/apache/ivy/core/settings/IvyVariableContainerImpl.java
new file mode 100644
index 0000000..8d89091
--- /dev/null
+++ b/src/java/org/apache/ivy/core/settings/IvyVariableContainerImpl.java
@@ -0,0 +1,101 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.settings;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.util.Message;
+
+public class IvyVariableContainerImpl implements IvyVariableContainer {
+
+    private Map variables;
+
+    private String envPrefix;
+
+    public IvyVariableContainerImpl() {
+        this.variables = new HashMap();
+    }
+
+    public IvyVariableContainerImpl(Map variables) {
+        this.variables = variables;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.ivy.core.settings.IvyVariableContainer#setVariable(java.lang.String,
+     * java.lang.String, boolean)
+     */
+    public void setVariable(String varName, String value, boolean overwrite) {
+        if (overwrite || !variables.containsKey(varName)) {
+            Message.debug("setting '" + varName + "' to '" + value + "'");
+            variables.put(varName, substitute(value));
+        } else {
+            Message.debug("'" + varName + "' already set: discarding '" + value + "'");
+        }
+    }
+
+    public void setEnvironmentPrefix(String prefix) {
+        if ((prefix != null) && !prefix.endsWith(".")) {
+            this.envPrefix = prefix + ".";
+        } else {
+            this.envPrefix = prefix;
+        }
+    }
+
+    protected String substitute(String value) {
+        return IvyPatternHelper.substituteVariables(value, this);
+    }
+
+    protected Map getVariables() {
+        return variables;
+    }
+
+    protected String getEnvironmentPrefix() {
+        return envPrefix;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.ivy.core.settings.IvyVariableContainer#getVariable(java.lang.String)
+     */
+    public String getVariable(String name) {
+        String val = null;
+        if ((envPrefix != null) && name.startsWith(envPrefix)) {
+            val = System.getenv(name.substring(envPrefix.length()));
+        } else {
+            val = (String) variables.get(name);
+        }
+
+        return val;
+    }
+
+    public Object clone() {
+        IvyVariableContainerImpl clone;
+        try {
+            clone = (IvyVariableContainerImpl) super.clone();
+        } catch (CloneNotSupportedException e) {
+            throw new RuntimeException("unable to clone a " + this.getClass());
+        }
+        clone.variables = new HashMap(this.variables);
+        return clone;
+    }
+}
diff --git a/src/java/org/apache/ivy/core/settings/Validatable.java b/src/java/org/apache/ivy/core/settings/Validatable.java
new file mode 100644
index 0000000..f79370d
--- /dev/null
+++ b/src/java/org/apache/ivy/core/settings/Validatable.java
@@ -0,0 +1,32 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.settings;
+
+/**
+ * Implemented by settings element which need to perform validation when settings are loaded.
+ */
+public interface Validatable {
+    /**
+     * Validates the Validatable, throwing an {@link IllegalStateException} if the current state is
+     * not valid.
+     * 
+     * @throws IllegalStateException
+     *             if the state of the {@link Validatable} is not valid.
+     */
+    public void validate();
+}
diff --git a/src/java/org/apache/ivy/core/settings/XmlSettingsParser.java b/src/java/org/apache/ivy/core/settings/XmlSettingsParser.java
new file mode 100644
index 0000000..a40cc1c
--- /dev/null
+++ b/src/java/org/apache/ivy/core/settings/XmlSettingsParser.java
@@ -0,0 +1,648 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.settings;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.text.ParseException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.parsers.SAXParserFactory;
+
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.core.cache.RepositoryCacheManager;
+import org.apache.ivy.core.module.status.StatusManager;
+import org.apache.ivy.plugins.circular.CircularDependencyStrategy;
+import org.apache.ivy.plugins.conflict.ConflictManager;
+import org.apache.ivy.plugins.latest.LatestStrategy;
+import org.apache.ivy.plugins.lock.LockStrategy;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+import org.apache.ivy.util.Checks;
+import org.apache.ivy.util.Configurator;
+import org.apache.ivy.util.FileResolver;
+import org.apache.ivy.util.Message;
+import org.apache.ivy.util.url.CredentialsStore;
+import org.apache.ivy.util.url.URLHandler;
+import org.apache.ivy.util.url.URLHandlerRegistry;
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ */
+public class XmlSettingsParser extends DefaultHandler {
+    /**
+     * Wraps an {@link IvyVariableContainer} delegating most method calls to the wrapped instance,
+     * except for a set of variables which are only stored locally in the wrapper, and not
+     * propagated to the wrapped instance.
+     */
+    private static final class IvyVariableContainerWrapper implements IvyVariableContainer {
+        private static final Collection SETTINGS_VARIABLES = Arrays.asList(new String[] {
+                "ivy.settings.dir", "ivy.settings.url", "ivy.settings.file", "ivy.conf.dir",
+                "ivy.conf.url", "ivy.conf.file"});
+
+        private final IvyVariableContainer variables;
+
+        private Map localVariables = new HashMap();
+
+        private IvyVariableContainerWrapper(IvyVariableContainer variables) {
+            this.variables = variables;
+        }
+
+        public void setVariable(String varName, String value, boolean overwrite) {
+            if (SETTINGS_VARIABLES.contains(varName)) {
+                if (!localVariables.containsKey(varName) || overwrite) {
+                    localVariables.put(varName, value);
+                }
+            } else {
+                variables.setVariable(varName, value, overwrite);
+            }
+        }
+
+        public void setEnvironmentPrefix(String prefix) {
+            variables.setEnvironmentPrefix(prefix);
+        }
+
+        public String getVariable(String name) {
+            if (localVariables.containsKey(name)) {
+                return (String) localVariables.get(name);
+            }
+            return variables.getVariable(name);
+        }
+
+        public Object clone() {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    private Configurator configurator;
+
+    private List configuratorTags = Arrays.asList(new String[] {"resolvers", "namespaces",
+            "parsers", "latest-strategies", "conflict-managers", "outputters", "version-matchers",
+            "statuses", "circular-dependency-strategies", "triggers", "lock-strategies", "caches",
+            "signers"});
+
+    private IvySettings ivy;
+
+    private String defaultResolver;
+
+    private String defaultCM;
+
+    private String defaultLatest;
+
+    private String defaultCacheManager;
+
+    private String defaultCircular;
+
+    private String defaultLock;
+
+    private String currentConfiguratorTag;
+
+    private URL settings;
+
+    private boolean deprecatedMessagePrinted = false;
+
+    public XmlSettingsParser(IvySettings ivy) {
+        this.ivy = ivy;
+    }
+
+    public void parse(URL settings) throws ParseException, IOException {
+        configurator = new Configurator();
+        configurator.setFileResolver(new FileResolver() {
+            public File resolveFile(String path, String filename) {
+                return Checks.checkAbsolute(path, filename);
+            }
+        });
+        // put every type definition from ivy to configurator
+        Map typeDefs = ivy.getTypeDefs();
+        for (Iterator iter = typeDefs.keySet().iterator(); iter.hasNext();) {
+            String name = (String) iter.next();
+            configurator.typeDef(name, (Class) typeDefs.get(name));
+        }
+
+        doParse(settings);
+    }
+
+    private void doParse(URL settingsUrl) throws IOException, ParseException {
+        this.settings = settingsUrl;
+        InputStream stream = null;
+        try {
+            stream = URLHandlerRegistry.getDefault().openStream(settingsUrl);
+            InputSource inSrc = new InputSource(stream);
+            inSrc.setSystemId(settingsUrl.toExternalForm());
+            SAXParserFactory.newInstance().newSAXParser().parse(settingsUrl.toExternalForm(), this);
+            ivy.validate();
+        } catch (IOException e) {
+            throw e;
+        } catch (Exception e) {
+            ParseException pe = new ParseException("failed to load settings from " + settingsUrl
+                    + ": " + e.getMessage(), 0);
+            pe.initCause(e);
+            throw pe;
+        } finally {
+            if (stream != null) {
+                try {
+                    stream.close();
+                } catch (IOException e) {
+                    // ignored
+                }
+            }
+        }
+    }
+
+    private void parse(Configurator configurator, URL configuration) throws IOException,
+            ParseException {
+        this.configurator = configurator;
+        doParse(configuration);
+    }
+
+    public void startElement(String uri, String localName, String qName, Attributes att)
+            throws SAXException {
+        // we first copy attributes in a Map to be able to modify them
+        Map attributes = new HashMap();
+        for (int i = 0; i < att.getLength(); i++) {
+            attributes.put(att.getQName(i), ivy.substitute(att.getValue(i)));
+        }
+
+        try {
+            if ("ivyconf".equals(qName)) {
+                deprecatedMessagePrinted = true;
+                Message.deprecated("'ivyconf' element is deprecated, use 'ivysettings' instead ("
+                        + settings + ")");
+            }
+            if (configurator.getCurrent() != null) {
+                inConfiguratorStarted(qName, attributes);
+            } else if ("classpath".equals(qName)) {
+                classpathStarted(attributes);
+            } else if ("typedef".equals(qName)) {
+                typedefStarted(attributes);
+            } else if ("property".equals(qName)) {
+                propertyStarted(attributes);
+            } else if ("properties".equals(qName)) {
+                propertiesStarted(attributes);
+            } else if ("include".equals(qName)) {
+                includeStarted(attributes);
+            } else if ("settings".equals(qName) || "conf".equals(qName)) {
+                settingsStarted(qName, attributes);
+            } else if ("caches".equals(qName)) {
+                cachesStarted(qName, attributes);
+            } else if ("version-matchers".equals(qName)) {
+                versionMatchersStarted(qName, attributes);
+            } else if ("statuses".equals(qName)) {
+                statusesStarted(qName, attributes);
+            } else if (configuratorTags.contains(qName)) {
+                anyConfiguratorStarted(qName);
+            } else if ("macrodef".equals(qName)) {
+                macrodefStarted(qName, attributes);
+            } else if ("module".equals(qName)) {
+                moduleStarted(attributes);
+            } else if ("credentials".equals(qName)) {
+                credentialsStarted(attributes);
+            }
+        } catch (ParseException ex) {
+            SAXException sax = new SAXException("problem in config file: " + ex.getMessage(), ex);
+            sax.initCause(ex);
+            throw sax;
+        } catch (IOException ex) {
+            SAXException sax = new SAXException("io problem while parsing config file: "
+                    + ex.getMessage(), ex);
+            sax.initCause(ex);
+            throw sax;
+        }
+    }
+
+    private void credentialsStarted(Map attributes) {
+        String realm = (String) attributes.remove("realm");
+        String host = (String) attributes.remove("host");
+        String userName = (String) attributes.remove("username");
+        String passwd = (String) attributes.remove("passwd");
+        CredentialsStore.INSTANCE.addCredentials(realm, host, userName, passwd);
+    }
+
+    private void moduleStarted(Map attributes) {
+        attributes.put(IvyPatternHelper.MODULE_KEY, attributes.remove("name"));
+        String resolver = (String) attributes.remove("resolver");
+        String branch = (String) attributes.remove("branch");
+        String cm = (String) attributes.remove("conflict-manager");
+        String resolveMode = (String) attributes.remove("resolveMode");
+        String matcher = (String) attributes.remove("matcher");
+        matcher = matcher == null ? PatternMatcher.EXACT_OR_REGEXP : matcher;
+        ivy.addModuleConfiguration(attributes, ivy.getMatcher(matcher), resolver, branch, cm,
+            resolveMode);
+    }
+
+    private void macrodefStarted(String qName, Map attributes) {
+        currentConfiguratorTag = qName;
+        Configurator.MacroDef macrodef = configurator
+                .startMacroDef((String) attributes.get("name"));
+        macrodef.addAttribute("name", null);
+    }
+
+    private void anyConfiguratorStarted(String qName) {
+        currentConfiguratorTag = qName;
+        configurator.setRoot(ivy);
+    }
+
+    private void statusesStarted(String qName, Map attributes) {
+        currentConfiguratorTag = qName;
+        StatusManager m = new StatusManager();
+        String defaultStatus = (String) attributes.get("default");
+        if (defaultStatus != null) {
+            m.setDefaultStatus(defaultStatus);
+        }
+        ivy.setStatusManager(m);
+        configurator.setRoot(m);
+    }
+
+    private void versionMatchersStarted(String qName, Map attributes) {
+        anyConfiguratorStarted(qName);
+        if ("true".equals(attributes.get("usedefaults"))) {
+            ivy.configureDefaultVersionMatcher();
+        }
+    }
+
+    private void cachesStarted(String qName, Map attributes) {
+        anyConfiguratorStarted(qName);
+        defaultLock = (String) attributes.get("lockStrategy");
+        defaultCacheManager = (String) attributes.get("default");
+
+        String cache = (String) attributes.get("defaultCacheDir");
+        if (cache != null) {
+            ivy.setDefaultCache(Checks.checkAbsolute(cache, "defaultCacheDir"));
+        }
+        String up2d = (String) attributes.get("checkUpToDate");
+        if (up2d != null) {
+            Message.deprecated("'checkUpToDate' is deprecated, "
+                    + "use the 'overwriteMode' on the 'ivy:retrieve' task instead (" + settings
+                    + ")");
+            ivy.setCheckUpToDate(Boolean.valueOf(up2d).booleanValue());
+        }
+        String resolutionDir = (String) attributes.get("resolutionCacheDir");
+        if (resolutionDir != null) {
+            ivy.setDefaultResolutionCacheBasedir(resolutionDir);
+        }
+        String useOrigin = (String) attributes.get("useOrigin");
+        if (useOrigin != null) {
+            ivy.setDefaultUseOrigin(Boolean.valueOf(useOrigin).booleanValue());
+        }
+        String cacheIvyPattern = (String) attributes.get("ivyPattern");
+        if (cacheIvyPattern != null) {
+            ivy.setDefaultCacheIvyPattern(cacheIvyPattern);
+        }
+        String cacheArtPattern = (String) attributes.get("artifactPattern");
+        if (cacheArtPattern != null) {
+            ivy.setDefaultCacheArtifactPattern(cacheArtPattern);
+        }
+        String repositoryDir = (String) attributes.get("repositoryCacheDir");
+        if (repositoryDir != null) {
+            ivy.setDefaultRepositoryCacheBasedir(repositoryDir);
+        }
+    }
+
+    private void settingsStarted(String qName, Map attributes) {
+        if ("conf".equals(qName) && !deprecatedMessagePrinted) {
+            Message.deprecated("'conf' is deprecated, use 'settings' instead (" + settings + ")");
+        }
+        String cache = (String) attributes.get("defaultCache");
+        if (cache != null) {
+            Message.deprecated("'defaultCache' is deprecated, "
+                    + "use 'caches[@defaultCacheDir]' instead (" + settings + ")");
+            ivy.setDefaultCache(Checks.checkAbsolute(cache, "defaultCache"));
+        }
+        String defaultBranch = (String) attributes.get("defaultBranch");
+        if (defaultBranch != null) {
+            ivy.setDefaultBranch(defaultBranch);
+        }
+        String defaultResolveMode = (String) attributes.get("defaultResolveMode");
+        if (defaultResolveMode != null) {
+            ivy.setDefaultResolveMode(defaultResolveMode);
+        }
+        String validate = (String) attributes.get("validate");
+        if (validate != null) {
+            ivy.setValidate(Boolean.valueOf(validate).booleanValue());
+        }
+        String up2d = (String) attributes.get("checkUpToDate");
+        if (up2d != null) {
+            Message.deprecated("'checkUpToDate' is deprecated, "
+                    + "use the 'overwriteMode' on the 'ivy:retrieve' task instead (" + settings
+                    + ")");
+            ivy.setCheckUpToDate(Boolean.valueOf(up2d).booleanValue());
+        }
+        String useRemoteConfig = (String) attributes.get("useRemoteConfig");
+        if (useRemoteConfig != null) {
+            ivy.setUseRemoteConfig(Boolean.valueOf(useRemoteConfig).booleanValue());
+        }
+        String cacheIvyPattern = (String) attributes.get("cacheIvyPattern");
+        if (cacheIvyPattern != null) {
+            Message.deprecated("'cacheIvyPattern' is deprecated, use 'caches[@ivyPattern]' instead"
+                    + " (" + settings + ")");
+            ivy.setDefaultCacheIvyPattern(cacheIvyPattern);
+        }
+        String cacheArtPattern = (String) attributes.get("cacheArtifactPattern");
+        if (cacheArtPattern != null) {
+            Message.deprecated("'cacheArtifactPattern' is deprecated, "
+                    + "use 'caches[@artifactPattern]' instead (" + settings + ")");
+            ivy.setDefaultCacheArtifactPattern(cacheArtPattern);
+        }
+
+        // we do not set following defaults here since no instances has been registered yet
+        defaultResolver = (String) attributes.get("defaultResolver");
+        defaultCM = (String) attributes.get("defaultConflictManager");
+        defaultLatest = (String) attributes.get("defaultLatestStrategy");
+        defaultCircular = (String) attributes.get("circularDependencyStrategy");
+
+        String requestMethod = (String) attributes.get("httpRequestMethod");
+        if ("head".equalsIgnoreCase(requestMethod)) {
+            URLHandlerRegistry.getHttp().setRequestMethod(URLHandler.REQUEST_METHOD_HEAD);
+        } else if ("get".equalsIgnoreCase(requestMethod)) {
+            URLHandlerRegistry.getHttp().setRequestMethod(URLHandler.REQUEST_METHOD_GET);
+        } else if ((requestMethod != null) && (requestMethod.trim().length() > 0)) {
+            throw new IllegalArgumentException("Invalid httpRequestMethod specified, must be "
+                    + "one of {'HEAD', 'GET'}");
+        }
+    }
+
+    private void includeStarted(Map attributes) throws IOException, ParseException {
+        final IvyVariableContainer variables = ivy.getVariableContainer();
+        ivy.setVariableContainer(new IvyVariableContainerWrapper(variables));
+        try {
+            String propFilePath = (String) attributes.get("file");
+            URL settingsURL = null;
+            if (propFilePath == null) {
+                propFilePath = (String) attributes.get("url");
+                if (propFilePath == null) {
+                    throw new IllegalArgumentException(
+                            "bad include tag: specify file or url to include");
+                } else {
+                    try {
+                        // First asume that it is an absolute URL
+                        settingsURL = new URL(propFilePath);
+                    } catch (MalformedURLException e) {
+                        // If that fail, it may be because it is a relative one.
+                        settingsURL = new URL(this.settings, propFilePath);
+                    }
+                    Message.verbose("including url: " + settingsURL.toString());
+                    ivy.setSettingsVariables(settingsURL);
+                }
+            } else {
+                settingsURL = urlFromFileAttribute(propFilePath);
+                Message.verbose("including file: " + settingsURL);
+                if ("file".equals(settingsURL.getProtocol())) {
+                    try {
+                        File settingsFile = new File(new URI(settingsURL.toExternalForm()));
+                        String optional = (String) attributes.get("optional");
+                        if ("true".equals(optional) && !settingsFile.exists()) {
+                            return;
+                        }
+
+                        ivy.setSettingsVariables(Checks.checkAbsolute(settingsFile,
+                            "settings include path"));
+                    } catch (URISyntaxException e) {
+                        // try to make the best of it...
+                        ivy.setSettingsVariables(Checks.checkAbsolute(settingsURL.getPath(),
+                            "settings include path"));
+                    }
+                } else {
+                    ivy.setSettingsVariables(settingsURL);
+                }
+            }
+            new XmlSettingsParser(ivy).parse(configurator, settingsURL);
+        } finally {
+            ivy.setVariableContainer(variables);
+        }
+    }
+
+    /**
+     * Provide an URL referencing the given filepath. If filePath is an absolute path, then the
+     * resulting URL point to a local file, otherwise, the filepath is evaluated relatively to the
+     * URL of the current settings file (can be local file or remote URL).
+     */
+    private URL urlFromFileAttribute(String filePath) throws IOException {
+        try {
+            return new URL(filePath);
+        } catch (MalformedURLException e) {
+            // ignore, we'll try to create a correct URL below
+        }
+
+        File incFile = new File(filePath);
+        if (incFile.isAbsolute()) {
+            if (!incFile.exists()) {
+                throw new FileNotFoundException(incFile.getAbsolutePath());
+            }
+            return incFile.toURI().toURL();
+        } else if ("file".equals(this.settings.getProtocol())) {
+            try {
+                File settingsFile = new File(new URI(this.settings.toExternalForm()));
+                if (!settingsFile.exists()) {
+                    throw new FileNotFoundException(settingsFile.getAbsolutePath());
+                }
+                return new File(settingsFile.getParentFile(), filePath).toURI().toURL();
+            } catch (URISyntaxException e) {
+                return new URL(this.settings, filePath);
+            }
+        } else {
+            return new URL(this.settings, filePath);
+        }
+    }
+
+    private void propertiesStarted(Map attributes) throws IOException {
+        String propFilePath = (String) attributes.get("file");
+        String environmentPrefix = (String) attributes.get("environment");
+        if (propFilePath != null) {
+            String overrideStr = (String) attributes.get("override");
+            boolean override = overrideStr == null ? true : Boolean.valueOf(overrideStr)
+                    .booleanValue();
+            Message.verbose("loading properties: " + propFilePath);
+            try {
+                URL fileUrl = urlFromFileAttribute(propFilePath);
+                ivy.loadProperties(fileUrl, override);
+            } catch (FileNotFoundException e) {
+                Message.verbose("Unable to find property file: " + propFilePath);
+            }
+        } else if (environmentPrefix != null) {
+            ivy.getVariableContainer().setEnvironmentPrefix(environmentPrefix);
+        } else {
+            throw new IllegalArgumentException("Didn't find a 'file' or 'environment' attribute "
+                    + "on the 'properties' element");
+        }
+    }
+
+    private void propertyStarted(Map attributes) {
+        String name = (String) attributes.get("name");
+        String value = (String) attributes.get("value");
+        String override = (String) attributes.get("override");
+        String isSetVar = (String) attributes.get("ifset");
+        String unlessSetVar = (String) attributes.get("unlessset");
+        if (name == null) {
+            throw new IllegalArgumentException("missing attribute name on property tag");
+        }
+        if (value == null) {
+            throw new IllegalArgumentException("missing attribute value on property tag");
+        }
+        ivy.setVariable(name, value, override == null ? true : Boolean.valueOf(override)
+                .booleanValue(), isSetVar, unlessSetVar);
+    }
+
+    private void typedefStarted(Map attributes) {
+        String name = (String) attributes.get("name");
+        String className = (String) attributes.get("classname");
+        Class clazz = ivy.typeDef(name, className);
+        configurator.typeDef(name, clazz);
+    }
+
+    private void classpathStarted(Map attributes) throws IOException {
+        String urlStr = (String) attributes.get("url");
+        URL url = null;
+        if (urlStr == null) {
+            String file = (String) attributes.get("file");
+            if (file == null) {
+                throw new IllegalArgumentException(
+                        "either url or file should be given for classpath element");
+            } else {
+                url = urlFromFileAttribute(file);
+            }
+        } else {
+            url = new URL(urlStr);
+        }
+        ivy.addClasspathURL(url);
+    }
+
+    private void inConfiguratorStarted(String qName, Map attributes) {
+        if ("macrodef".equals(currentConfiguratorTag) && configurator.getTypeDef(qName) != null) {
+            String name = (String) attributes.get("name");
+            if (name == null) {
+                attributes.put("name", "@{name}");
+            } else if (name.indexOf("@{name}") != -1) {
+                attributes.put("name", name);
+            } else {
+                attributes.put("name", "@{name}-" + name);
+            }
+        }
+        if (attributes.get("ref") != null) {
+            if (attributes.size() != 1) {
+                throw new IllegalArgumentException("ref attribute should be the only one ! found "
+                        + attributes.size() + " in " + qName);
+            }
+            String name = (String) attributes.get("ref");
+            Object child = null;
+            if ("resolvers".equals(currentConfiguratorTag) || "resolver".equals(qName)) {
+                child = ivy.getResolver(name);
+                if (child == null) {
+                    throw new IllegalArgumentException("unknown resolver " + name
+                            + ": resolver should be defined before being referenced");
+                }
+            } else if ("latest-strategies".equals(currentConfiguratorTag)) {
+                child = ivy.getLatestStrategy(name);
+                if (child == null) {
+                    throw new IllegalArgumentException("unknown latest strategy " + name
+                            + ": latest strategy should be defined before being referenced");
+                }
+            } else if ("conflict-managers".equals(currentConfiguratorTag)) {
+                child = ivy.getConflictManager(name);
+                if (child == null) {
+                    throw new IllegalArgumentException("unknown conflict manager " + name
+                            + ": conflict manager should be defined before being referenced");
+                }
+            }
+            if (child == null) {
+                throw new IllegalArgumentException("bad reference " + name);
+            }
+            configurator.addChild(qName, child);
+        } else {
+            configurator.startCreateChild(qName);
+            for (Iterator iter = attributes.keySet().iterator(); iter.hasNext();) {
+                String attName = (String) iter.next();
+                configurator.setAttribute(attName, (String) attributes.get(attName));
+            }
+        }
+    }
+
+    public void endElement(String uri, String localName, String qName) throws SAXException {
+        if (configurator.getCurrent() != null) {
+            if (configuratorTags.contains(qName) && configurator.getDepth() == 1) {
+                configurator.clear();
+                currentConfiguratorTag = null;
+            } else if ("macrodef".equals(qName) && configurator.getDepth() == 1) {
+                configurator.endMacroDef();
+                currentConfiguratorTag = null;
+            } else {
+                configurator.endCreateChild();
+            }
+        }
+    }
+
+    public void endDocument() throws SAXException {
+        if (defaultResolver != null) {
+            ivy.setDefaultResolver(ivy.substitute(defaultResolver));
+        }
+        if (defaultCM != null) {
+            ConflictManager conflictManager = ivy.getConflictManager(ivy.substitute(defaultCM));
+            if (conflictManager == null) {
+                throw new IllegalArgumentException("unknown conflict manager "
+                        + ivy.substitute(defaultCM));
+            }
+            ivy.setDefaultConflictManager(conflictManager);
+        }
+        if (defaultLatest != null) {
+            LatestStrategy latestStrategy = ivy.getLatestStrategy(ivy.substitute(defaultLatest));
+            if (latestStrategy == null) {
+                throw new IllegalArgumentException("unknown latest strategy "
+                        + ivy.substitute(defaultLatest));
+            }
+            ivy.setDefaultLatestStrategy(latestStrategy);
+        }
+        if (defaultCacheManager != null) {
+            RepositoryCacheManager cache = ivy.getRepositoryCacheManager(ivy
+                    .substitute(defaultCacheManager));
+            if (cache == null) {
+                throw new IllegalArgumentException("unknown cache manager "
+                        + ivy.substitute(defaultCacheManager));
+            }
+            ivy.setDefaultRepositoryCacheManager(cache);
+        }
+        if (defaultCircular != null) {
+            CircularDependencyStrategy strategy = ivy.getCircularDependencyStrategy(ivy
+                    .substitute(defaultCircular));
+            if (strategy == null) {
+                throw new IllegalArgumentException("unknown circular dependency strategy "
+                        + ivy.substitute(defaultCircular));
+            }
+            ivy.setCircularDependencyStrategy(strategy);
+        }
+        if (defaultLock != null) {
+            LockStrategy strategy = ivy.getLockStrategy(ivy.substitute(defaultLock));
+            if (strategy == null) {
+                throw new IllegalArgumentException("unknown lock strategy "
+                        + ivy.substitute(defaultLock));
+            }
+            ivy.setDefaultLockStrategy(strategy);
+        }
+    }
+}
diff --git a/src/java/org/apache/ivy/core/settings/ivy.properties b/src/java/org/apache/ivy/core/settings/ivy.properties
new file mode 100644
index 0000000..6dcb219
--- /dev/null
+++ b/src/java/org/apache/ivy/core/settings/ivy.properties
@@ -0,0 +1,43 @@
+#	 ***************************************************************
+#	 * Licensed to the Apache Software Foundation (ASF) under one
+#	 * or more contributor license agreements.  See the NOTICE file
+#	 * distributed with this work for additional information
+#	 * regarding copyright ownership.  The ASF licenses this file
+#	 * to you under the Apache License, Version 2.0 (the
+#	 * "License"); you may not use this file except in compliance
+#	 * with the License.  You may obtain a copy of the License at
+#	 * 
+#	 *   http://www.apache.org/licenses/LICENSE-2.0
+#	 * 
+#	 * Unless required by applicable law or agreed to in writing,
+#	 * software distributed under the License is distributed on an
+#	 * "AS IS" BASIS, WITHOUT 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.project.dir = ${basedir}
+ivy.lib.dir = ${ivy.project.dir}/lib
+ivy.build.artifacts.dir = ${ivy.project.dir}/build/artifacts
+ivy.distrib.dir = ${ivy.project.dir}/distrib
+	
+ivy.resolver.default.check.modified = false
+ivy.default.always.check.exact.revision = false
+
+ivy.configurations = *
+ivy.resolve.default.type.filter = *
+ivy.status = integration
+ivy.dep.file = ivy.xml
+ivy.settings.file = ivysettings.xml
+ivy.retrieve.pattern = ${ivy.lib.dir}/[artifact]-[revision](-[classifier]).[ext]
+ivy.deliver.ivy.pattern = ${ivy.distrib.dir}/[type]s/[artifact]-[revision](-[classifier]).[ext]
+ivy.publish.src.artifacts.pattern = ${ivy.distrib.dir}/[type]s/[artifact]-[revision](-[classifier]).[ext]
+ivy.cache.ttl.default = 10s
+
+ivy.report.output.pattern = [organisation]-[module]-[conf].[ext]
+
+ivy.buildlist.ivyfilepath = ivy.xml
+
+ivy.checksums=sha1,md5
+
+ivy.log.modules.in.use=false
diff --git a/src/java/org/apache/ivy/core/settings/ivysettings-1.4.xml b/src/java/org/apache/ivy/core/settings/ivysettings-1.4.xml
new file mode 100644
index 0000000..1998556
--- /dev/null
+++ b/src/java/org/apache/ivy/core/settings/ivysettings-1.4.xml
@@ -0,0 +1,28 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<ivysettings>
+	<settings defaultResolver="default"/>
+	<resolvers>
+		<ivyrep name="public" ivyroot="http://ivyrep.jayasoft.org/" />
+	</resolvers>
+	<include url="${ivy.default.settings.dir}/ivysettings-shared.xml"/>
+	<include url="${ivy.default.settings.dir}/ivysettings-local.xml"/>
+	<include url="${ivy.default.settings.dir}/ivysettings-main-chain.xml"/>
+	<include url="${ivy.default.settings.dir}/ivysettings-default-chain.xml"/>
+</ivysettings>
diff --git a/src/java/org/apache/ivy/core/settings/ivysettings-default-chain.xml b/src/java/org/apache/ivy/core/settings/ivysettings-default-chain.xml
new file mode 100644
index 0000000..2eeefd8
--- /dev/null
+++ b/src/java/org/apache/ivy/core/settings/ivysettings-default-chain.xml
@@ -0,0 +1,26 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<ivysettings>
+	<resolvers>
+		<chain name="default" returnFirst="true" checkmodified="true">
+			<resolver ref="local"/>
+			<resolver ref="main"/>
+		</chain>
+	</resolvers>
+</ivysettings>
diff --git a/src/java/org/apache/ivy/core/settings/ivysettings-local.xml b/src/java/org/apache/ivy/core/settings/ivysettings-local.xml
new file mode 100644
index 0000000..acf224d
--- /dev/null
+++ b/src/java/org/apache/ivy/core/settings/ivysettings-local.xml
@@ -0,0 +1,29 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<ivysettings>
+	<property name="ivy.local.default.root"             value="${ivy.default.ivy.user.dir}/local" override="false"/>
+	<property name="ivy.local.default.ivy.pattern"      value="[organisation]/[module]/[revision]/[type]s/[artifact].[ext]" override="false"/>
+	<property name="ivy.local.default.artifact.pattern" value="[organisation]/[module]/[revision]/[type]s/[artifact].[ext]" override="false"/>
+	<resolvers>
+		<filesystem name="local">
+			<ivy pattern="${ivy.local.default.root}/${ivy.local.default.ivy.pattern}" />
+			<artifact pattern="${ivy.local.default.root}/${ivy.local.default.artifact.pattern}" />
+		</filesystem>
+	</resolvers>
+</ivysettings>
diff --git a/src/java/org/apache/ivy/core/settings/ivysettings-main-chain.xml b/src/java/org/apache/ivy/core/settings/ivysettings-main-chain.xml
new file mode 100644
index 0000000..8cb5cf5
--- /dev/null
+++ b/src/java/org/apache/ivy/core/settings/ivysettings-main-chain.xml
@@ -0,0 +1,26 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<ivysettings>
+	<resolvers>
+		<chain name="main" dual="true">
+			<resolver ref="shared"/>
+			<resolver ref="public"/>
+		</chain>
+	</resolvers>
+</ivysettings>
diff --git a/src/java/org/apache/ivy/core/settings/ivysettings-public.xml b/src/java/org/apache/ivy/core/settings/ivysettings-public.xml
new file mode 100644
index 0000000..09d1e58
--- /dev/null
+++ b/src/java/org/apache/ivy/core/settings/ivysettings-public.xml
@@ -0,0 +1,23 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<ivysettings>
+	<resolvers>
+		<ibiblio name="public" m2compatible="true"/>
+	</resolvers>
+</ivysettings>
diff --git a/src/java/org/apache/ivy/core/settings/ivysettings-shared.xml b/src/java/org/apache/ivy/core/settings/ivysettings-shared.xml
new file mode 100644
index 0000000..b5b530c
--- /dev/null
+++ b/src/java/org/apache/ivy/core/settings/ivysettings-shared.xml
@@ -0,0 +1,29 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<ivysettings>
+	<property name="ivy.shared.default.root"             value="${ivy.default.ivy.user.dir}/shared" override="false"/>
+	<property name="ivy.shared.default.ivy.pattern"      value="[organisation]/[module]/[revision]/[type]s/[artifact].[ext]" override="false"/>
+	<property name="ivy.shared.default.artifact.pattern" value="[organisation]/[module]/[revision]/[type]s/[artifact].[ext]" override="false"/>
+	<resolvers>
+		<filesystem name="shared">
+			<ivy pattern="${ivy.shared.default.root}/${ivy.shared.default.ivy.pattern}" />
+			<artifact pattern="${ivy.shared.default.root}/${ivy.shared.default.artifact.pattern}" />
+		</filesystem>
+	</resolvers>
+</ivysettings>
diff --git a/src/java/org/apache/ivy/core/settings/ivysettings.xml b/src/java/org/apache/ivy/core/settings/ivysettings.xml
new file mode 100644
index 0000000..6f0beb3
--- /dev/null
+++ b/src/java/org/apache/ivy/core/settings/ivysettings.xml
@@ -0,0 +1,26 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<ivysettings>
+	<settings defaultResolver="default"/>
+	<include url="${ivy.default.settings.dir}/ivysettings-public.xml"/>
+	<include url="${ivy.default.settings.dir}/ivysettings-shared.xml"/>
+	<include url="${ivy.default.settings.dir}/ivysettings-local.xml"/>
+	<include url="${ivy.default.settings.dir}/ivysettings-main-chain.xml"/>
+	<include url="${ivy.default.settings.dir}/ivysettings-default-chain.xml"/>
+</ivysettings>
diff --git a/src/java/org/apache/ivy/core/settings/repository.properties b/src/java/org/apache/ivy/core/settings/repository.properties
new file mode 100644
index 0000000..d42e3d8
--- /dev/null
+++ b/src/java/org/apache/ivy/core/settings/repository.properties
@@ -0,0 +1,24 @@
+#	 ***************************************************************
+#	 * Licensed to the Apache Software Foundation (ASF) under one
+#	 * or more contributor license agreements.  See the NOTICE file
+#	 * distributed with this work for additional information
+#	 * regarding copyright ownership.  The ASF licenses this file
+#	 * to you under the Apache License, Version 2.0 (the
+#	 * "License"); you may not use this file except in compliance
+#	 * with the License.  You may obtain a copy of the License at
+#	 * 
+#	 *   http://www.apache.org/licenses/LICENSE-2.0
+#	 * 
+#	 * Unless required by applicable law or agreed to in writing,
+#	 * software distributed under the License is distributed on an
+#	 * "AS IS" BASIS, WITHOUT 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.ibiblio.default.artifact.root = http://www.ibiblio.org/maven/
+ivy.ibiblio.default.artifact.pattern = [module]/[type]s/[artifact]-[revision].[ext]
+
+ivy.ivyrep.default.ivy.pattern = [organisation]/[module]/ivy-[revision].xml
+ivy.ivyrep.default.artifact.root = ${ivy.ibiblio.default.artifact.root}
+ivy.ivyrep.default.artifact.pattern = ${ivy.ibiblio.default.artifact.pattern}
diff --git a/src/java/org/apache/ivy/core/settings/typedef.properties b/src/java/org/apache/ivy/core/settings/typedef.properties
new file mode 100644
index 0000000..ba140d2
--- /dev/null
+++ b/src/java/org/apache/ivy/core/settings/typedef.properties
@@ -0,0 +1,67 @@
+#	 ***************************************************************
+#	 * Licensed to the Apache Software Foundation (ASF) under one
+#	 * or more contributor license agreements.  See the NOTICE file
+#	 * distributed with this work for additional information
+#	 * regarding copyright ownership.  The ASF licenses this file
+#	 * to you under the Apache License, Version 2.0 (the
+#	 * "License"); you may not use this file except in compliance
+#	 * with the License.  You may obtain a copy of the License at
+#	 * 
+#	 *   http://www.apache.org/licenses/LICENSE-2.0
+#	 * 
+#	 * Unless required by applicable law or agreed to in writing,
+#	 * software distributed under the License is distributed on an
+#	 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#	 * KIND, either express or implied.  See the License for the
+#	 * specific language governing permissions and limitations
+#	 * under the License.
+#	 ***************************************************************
+filesystem 	= org.apache.ivy.plugins.resolver.FileSystemResolver
+chain 		= org.apache.ivy.plugins.resolver.ChainResolver
+ibiblio		= org.apache.ivy.plugins.resolver.IBiblioResolver
+bintray     = org.apache.ivy.plugins.resolver.BintrayResolver
+url			= org.apache.ivy.plugins.resolver.URLResolver
+dual		= org.apache.ivy.plugins.resolver.DualResolver
+ivyrep		= org.apache.ivy.plugins.resolver.IvyRepResolver
+ssh         = org.apache.ivy.plugins.resolver.SshResolver
+sftp        = org.apache.ivy.plugins.resolver.SFTPResolver
+vsftp       = org.apache.ivy.plugins.resolver.VsftpResolver
+vfs         = org.apache.ivy.plugins.resolver.VfsResolver
+jar         = org.apache.ivy.plugins.resolver.JarResolver
+cache		= org.apache.ivy.plugins.resolver.CacheResolver
+packager	= org.apache.ivy.plugins.resolver.packager.PackagerResolver
+obr         = org.apache.ivy.osgi.obr.OBRResolver
+mirroredurl = org.apache.ivy.plugins.resolver.MirroredURLResolver
+updatesite  = org.apache.ivy.osgi.updatesite.UpdateSiteResolver
+osgi-agg    = org.apache.ivy.osgi.repo.AggregatedOSGiResolver
+
+latest-revision = org.apache.ivy.plugins.latest.LatestRevisionStrategy
+latest-lexico   = org.apache.ivy.plugins.latest.LatestLexicographicStrategy
+latest-time     = org.apache.ivy.plugins.latest.LatestTimeStrategy
+
+fixed-cm 		= org.apache.ivy.plugins.conflict.FixedConflictManager
+latest-cm   	= org.apache.ivy.plugins.conflict.LatestConflictManager
+compatible-cm   = org.apache.ivy.plugins.conflict.LatestCompatibleConflictManager
+no-cm     		= org.apache.ivy.plugins.conflict.NoConflictManager
+strict-cm   	= org.apache.ivy.plugins.conflict.StrictConflictManager
+regexp-cm   	= org.apache.ivy.plugins.conflict.RegexpConflictManager
+
+
+namespace   = org.apache.ivy.plugins.namespace.Namespace
+
+exact-vm		= org.apache.ivy.plugins.version.ExactVersionMatcher
+chain-vm		= org.apache.ivy.plugins.version.ChainVersionMatcher
+latest-vm  		= org.apache.ivy.plugins.version.LatestVersionMatcher
+sub-vm  		= org.apache.ivy.plugins.version.SubVersionMatcher
+range-vm		= org.apache.ivy.plugins.version.VersionRangeMatcher
+pattern-vm		= org.apache.ivy.plugins.version.PatternVersionMatcher
+
+ant-build		= org.apache.ivy.ant.AntBuildTrigger
+ant-call		= org.apache.ivy.ant.AntCallTrigger
+log		        = org.apache.ivy.plugins.trigger.LogTrigger
+
+cache			= org.apache.ivy.core.cache.DefaultRepositoryCacheManager
+
+pgp             = org.apache.ivy.plugins.signer.bouncycastle.OpenPGPSignatureGenerator
+
+osgi-manifest-parser = org.apache.ivy.osgi.core.OSGiManifestParser
diff --git a/src/java/org/apache/ivy/core/sort/CollectionOfModulesToSort.java b/src/java/org/apache/ivy/core/sort/CollectionOfModulesToSort.java
new file mode 100644
index 0000000..c185e37
--- /dev/null
+++ b/src/java/org/apache/ivy/core/sort/CollectionOfModulesToSort.java
@@ -0,0 +1,111 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.sort;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.plugins.version.VersionMatcher;
+
+/**
+ * Wrap a collection of descriptores wrapped themself in ModuleInSort elements. It contains some
+ * dedicated function to retrieve module descriptors based on dependencies descriptors.<br>
+ * <i>This class is designed to be used internally by the ModuleDescriptorSorter.</i>
+ */
+class CollectionOfModulesToSort implements Iterable<ModuleInSort> {
+
+    private final List<ModuleInSort> moduleDescriptors;
+
+    private final VersionMatcher versionMatcher;
+
+    private final Map<ModuleId, Collection<ModuleInSort>> modulesByModuleId;
+
+    private final NonMatchingVersionReporter nonMatchingVersionReporter;
+
+    /**
+     * @param modulesToSort
+     *            The collection of ModuleDescriptor to sort
+     * @param matcher
+     *            The matcher to used to check if dependencyDescriptor match a module in this
+     *            collection
+     * @param nonMatchingVersionReporter
+     */
+    public CollectionOfModulesToSort(Collection<ModuleDescriptor> modulesToSort,
+            VersionMatcher matcher, NonMatchingVersionReporter nonMatchingVersionReporter) {
+        this.versionMatcher = matcher;
+        this.nonMatchingVersionReporter = nonMatchingVersionReporter;
+        this.modulesByModuleId = new HashMap<ModuleId, Collection<ModuleInSort>>();
+        moduleDescriptors = new ArrayList<ModuleInSort>(modulesToSort.size());
+        for (ModuleDescriptor md : modulesToSort) {
+            ModuleInSort mdInSort = new ModuleInSort(md);
+            moduleDescriptors.add(mdInSort);
+            addToModulesByModuleId(md, mdInSort);
+        }
+    }
+
+    private void addToModulesByModuleId(ModuleDescriptor md, ModuleInSort mdInSort) {
+        ModuleId mdId = md.getModuleRevisionId().getModuleId();
+        List<ModuleInSort> mdInSortAsList = new LinkedList<ModuleInSort>();
+        mdInSortAsList.add(mdInSort);
+        Collection<ModuleInSort> previousList = modulesByModuleId.put(mdId, mdInSortAsList);
+        if (previousList != null) {
+            mdInSortAsList.addAll(previousList);
+        }
+    }
+
+    public Iterator<ModuleInSort> iterator() {
+        return moduleDescriptors.iterator();
+    }
+
+    public int size() {
+        return moduleDescriptors.size();
+    }
+
+    /**
+     * Find a matching module descriptor in the list of module to sort.
+     * 
+     * @param descriptor
+     * @return a ModuleDescriptor from the collection of module descriptors to sort. If none exists
+     *         returns null.
+     */
+    public ModuleInSort getModuleDescriptorDependency(DependencyDescriptor descriptor) {
+        Collection<ModuleInSort> modulesOfSameId = modulesByModuleId.get(descriptor
+                .getDependencyId());
+        if (modulesOfSameId == null) {
+            return null;
+        }
+        for (ModuleInSort mdInSort : modulesOfSameId) {
+            if (mdInSort.match(descriptor, versionMatcher)) {
+                return mdInSort;
+            } else {
+                nonMatchingVersionReporter.reportNonMatchingVersion(descriptor,
+                    mdInSort.getSortedModuleDescriptor());
+            }
+        }
+        return null;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/sort/MessageBasedNonMatchingVersionReporter.java b/src/java/org/apache/ivy/core/sort/MessageBasedNonMatchingVersionReporter.java
new file mode 100644
index 0000000..7034ebd
--- /dev/null
+++ b/src/java/org/apache/ivy/core/sort/MessageBasedNonMatchingVersionReporter.java
@@ -0,0 +1,46 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.sort;
+
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+
+abstract class MessageBasedNonMatchingVersionReporter implements NonMatchingVersionReporter {
+
+    public void reportNonMatchingVersion(DependencyDescriptor descriptor, ModuleDescriptor md) {
+        ModuleRevisionId dependencyRevisionId = descriptor.getDependencyRevisionId();
+        ModuleRevisionId parentRevisionId = descriptor.getParentRevisionId();
+        if (parentRevisionId == null) {
+            // There are some rare case where DependencyDescriptor have no parent.
+            // This is should not be used in the SortEngine, but if it is, we
+            // show a decent trace.
+            reportMessage("Non matching revision detected when sorting.  Dependency "
+                    + dependencyRevisionId + " doesn't match " + md.getModuleRevisionId());
+        } else {
+            ModuleId parentModuleId = parentRevisionId.getModuleId();
+            reportMessage("Non matching revision detected when sorting.  " + parentModuleId
+                    + " depends on " + dependencyRevisionId + ", doesn't match "
+                    + md.getModuleRevisionId());
+        }
+    }
+
+    protected abstract void reportMessage(String msg);
+
+}
diff --git a/src/java/org/apache/ivy/core/sort/ModuleDescriptorSorter.java b/src/java/org/apache/ivy/core/sort/ModuleDescriptorSorter.java
new file mode 100644
index 0000000..da0fb3b
--- /dev/null
+++ b/src/java/org/apache/ivy/core/sort/ModuleDescriptorSorter.java
@@ -0,0 +1,105 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.sort;
+
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.plugins.circular.CircularDependencyException;
+import org.apache.ivy.plugins.circular.CircularDependencyStrategy;
+import org.apache.ivy.plugins.version.VersionMatcher;
+import org.apache.ivy.util.Message;
+
+/**
+ * Inner helper class for sorting ModuleDescriptors.<br>
+ * ModuleDescriptorSorter use CollectionOfModulesToSort to find the dependencies of the modules, and
+ * use ModuleInSort to store some temporary values attached to the modules to sort.
+ * 
+ * @see ModuleInSort
+ * @see CollectionOfModulesToSort
+ */
+public class ModuleDescriptorSorter {
+
+    private final CollectionOfModulesToSort moduleDescriptors;
+
+    private final List<ModuleDescriptor> sorted = new LinkedList<ModuleDescriptor>();
+
+    private final CircularDependencyStrategy circularDepStrategy;
+
+    public ModuleDescriptorSorter(Collection<ModuleDescriptor> modulesDescriptorsToSort,
+            VersionMatcher matcher, NonMatchingVersionReporter nonMatchingVersionReporter,
+            CircularDependencyStrategy circularDepStrategy) {
+        this.circularDepStrategy = circularDepStrategy;
+        moduleDescriptors = new CollectionOfModulesToSort(modulesDescriptorsToSort, matcher,
+                nonMatchingVersionReporter);
+    }
+
+    /**
+     * Iterates over all modules calling sortModuleDescriptorsHelp.
+     * 
+     * @return sorted module
+     * @throws CircularDependencyException
+     */
+    public List<ModuleDescriptor> sortModuleDescriptors() throws CircularDependencyException {
+        Message.debug("Nbr of module to sort : " + moduleDescriptors.size());
+        for (ModuleInSort m : moduleDescriptors) {
+            sortModuleDescriptorsHelp(m, m);
+        }
+        return sorted;
+    }
+
+    /**
+     * If current module has already been added to list, returns, Otherwise invokes
+     * sortModuleDescriptorsHelp for all dependencies contained within set of moduleDescriptors.
+     * Then finally adds self to list of sorted.<br/>
+     * When a loop is detected by a recursive call, the moduleDescriptors are not added immediately
+     * added to the sorted list. They are added as loop dependencies of the root, and will be added
+     * to the sorted list only when the root itself will be added.
+     * 
+     * @param current
+     *            Current module to add to sorted list.
+     * @throws CircularDependencyException
+     */
+    private void sortModuleDescriptorsHelp(ModuleInSort current, ModuleInSort caller)
+            throws CircularDependencyException {
+        // if already sorted return
+        if (current.isProcessed()) {
+            return;
+        }
+        if (current.checkLoop(caller, circularDepStrategy)) {
+            return;
+        }
+        DependencyDescriptor[] descriptors = current.getDependencies();
+        Message.debug("Sort dependencies of : " + current.toString()
+                + " / Number of dependencies = " + descriptors.length);
+        current.setCaller(caller);
+        for (int i = 0; i < descriptors.length; i++) {
+            ModuleInSort child = moduleDescriptors.getModuleDescriptorDependency(descriptors[i]);
+            if (child != null) {
+                sortModuleDescriptorsHelp(child, current);
+            }
+        }
+        current.endOfCall();
+        Message.debug("Sort done for : " + current.toString());
+        current.addToSortedListIfRequired(sorted);
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/sort/ModuleInSort.java b/src/java/org/apache/ivy/core/sort/ModuleInSort.java
new file mode 100644
index 0000000..f0f5556
--- /dev/null
+++ b/src/java/org/apache/ivy/core/sort/ModuleInSort.java
@@ -0,0 +1,187 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.sort;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.ivy.Ivy;
+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.apache.ivy.plugins.circular.CircularDependencyHelper;
+import org.apache.ivy.plugins.circular.CircularDependencyStrategy;
+import org.apache.ivy.plugins.version.VersionMatcher;
+import org.apache.ivy.util.Message;
+
+/**
+ * Decorates a ModuleDescriptor with some attributes used during the sort. Thus every instance of a
+ * ModuleInSort can be used in only one ModuleDescriptorSorter at a time.<br>
+ * The added fields are : <br>
+ * <ul>
+ * <li><code>isSorted</code> : is true iff this module has already been added to the sorted list.</li>
+ * <li><code>loopElements</code> : When the module is the root of a loop (=the first element of a
+ * loop met during the sort), <code>loopElements</code> contains all ModuleInSort of the loop
+ * (excluding the root itself.</li>
+ * <li><code>isLoopIntermediateElement</code> : When a loop is detected, all modules included in the
+ * loop (except the root) have <code>isLoopIntermediateElement</code> set to true.</li>
+ * <li><code>caller</code> : During the sort, we traverse recursively the graph. When doing that,
+ * caller point to the parent element.
+ */
+class ModuleInSort {
+
+    private final ModuleDescriptor module;
+
+    private boolean isSorted = false;
+
+    private List<ModuleInSort> loopElements = new LinkedList<ModuleInSort>();
+
+    private boolean isLoopIntermediateElement = false;
+
+    private ModuleInSort caller;
+
+    public ModuleInSort(ModuleDescriptor moduleToSort) {
+        module = moduleToSort;
+    }
+
+    public boolean isInLoop() {
+        return isLoopIntermediateElement;
+    }
+
+    /** This ModuleInSort has been placed on the sorted list */
+    public boolean isSorted() {
+        if (isSorted) {
+            Message.debug("Module descriptor already sorted : "
+                    + module.getModuleRevisionId().toString());
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * This ModuleInSort has already been analyzed. It is either already added to the sorted list,
+     * either it is included in a loop and will be added when the root of the loop will be added to
+     * the list.
+     */
+    public boolean isProcessed() {
+        if (isSorted || isLoopIntermediateElement) {
+            Message.debug("Module descriptor is processed : "
+                    + module.getModuleRevisionId().toString());
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    public void setCaller(ModuleInSort caller) {
+        this.caller = caller;
+    }
+
+    public void endOfCall() {
+        caller = null;
+    }
+
+    /**
+     * Check if a adding this element as a dependency of caller will introduce a circular
+     * dependency. If it is, all the elements of the loop are flaged as 'loopIntermediateElement',
+     * and the loopElements of this module (which is the root of the loop) is updated. The
+     * depStrategy is invoked on order to report a correct circular loop message.
+     * 
+     * @param futurCaller
+     * @param depStrategy
+     * @return true if a loop is detected.
+     */
+    public boolean checkLoop(ModuleInSort futurCaller, CircularDependencyStrategy depStrategy) {
+        if (caller != null) {
+            LinkedList<ModuleRevisionId> elemOfLoop = new LinkedList<ModuleRevisionId>();
+            elemOfLoop.add(this.module.getModuleRevisionId());
+            for (ModuleInSort stackEl = futurCaller; stackEl != this; stackEl = stackEl.caller) {
+                elemOfLoop.add(stackEl.module.getModuleRevisionId());
+                stackEl.isLoopIntermediateElement = true;
+                loopElements.add(stackEl);
+            }
+            elemOfLoop.add(this.module.getModuleRevisionId());
+            ModuleRevisionId[] mrids = elemOfLoop.toArray(new ModuleRevisionId[elemOfLoop.size()]);
+            depStrategy.handleCircularDependency(mrids);
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Add this module to the sorted list except if this module is an intermediary element of a
+     * loop. If this module is the 'root' of a loop, then all elements of that loops are added
+     * before.
+     * 
+     * @param sorted
+     *            The list of sorted elements on which this module will be added
+     */
+    public void addToSortedListIfRequired(List<ModuleDescriptor> sorted) {
+        if (!isLoopIntermediateElement) {
+            addToSortList(sorted);
+        }
+    }
+
+    /**
+     * Add this module to the sorted list. If current is the 'root' of a loop, then all elements of
+     * that loops are added before.
+     */
+    private void addToSortList(List<ModuleDescriptor> sortedList) {
+        for (ModuleInSort moduleInLoop : loopElements) {
+            moduleInLoop.addToSortList(sortedList);
+        }
+        if (!this.isSorted()) {
+            sortedList.add(module);
+            this.isSorted = true;
+        }
+    }
+
+    public String toString() {
+        return module.getModuleRevisionId().toString();
+    }
+
+    public DependencyDescriptor[] getDependencies() {
+        return module.getDependencies();
+    }
+
+    /** Log a warning saying that a loop is detected */
+    public static void logLoopWarning(List loopElement) {
+        Message.warn("circular dependency detected during sort: "
+                + CircularDependencyHelper.formatMessageFromDescriptors(loopElement));
+    }
+
+    /**
+     * Return true if this module match the DependencyDescriptor with the given versionMatcher. If
+     * this module has no version defined, then true is always returned.
+     */
+    public boolean match(DependencyDescriptor descriptor, VersionMatcher versionMatcher) {
+        ModuleDescriptor md = module;
+        return md.getResolvedModuleRevisionId().getRevision() == null
+                || md.getResolvedModuleRevisionId().getRevision().equals(Ivy.getWorkingRevision())
+                || versionMatcher.accept(descriptor.getDependencyRevisionId(), md);
+        // Checking md.getResolvedModuleRevisionId().getRevision().equals(Ivy.getWorkingRevision()
+        // allow to consider any local non resolved ivy.xml
+        // as a valid module.
+    }
+
+    public ModuleDescriptor getSortedModuleDescriptor() {
+        return module;
+    }
+}
diff --git a/src/java/org/apache/ivy/core/sort/NonMatchingVersionReporter.java b/src/java/org/apache/ivy/core/sort/NonMatchingVersionReporter.java
new file mode 100644
index 0000000..5870474
--- /dev/null
+++ b/src/java/org/apache/ivy/core/sort/NonMatchingVersionReporter.java
@@ -0,0 +1,36 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.sort;
+
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+
+public interface NonMatchingVersionReporter {
+
+    /**
+     * Report to the user that ivy has detected that a module to sort has a dependency on an other
+     * module to sort, but the revisions doesn't match.
+     * 
+     * @param descriptor
+     *            The non matching dependency descriptor.
+     * @param md
+     *            The module to sort having the corect moduleID but a non matching revision
+     */
+    public void reportNonMatchingVersion(DependencyDescriptor descriptor, ModuleDescriptor md);
+
+}
diff --git a/src/java/org/apache/ivy/core/sort/SilentNonMatchingVersionReporter.java b/src/java/org/apache/ivy/core/sort/SilentNonMatchingVersionReporter.java
new file mode 100644
index 0000000..5212d24
--- /dev/null
+++ b/src/java/org/apache/ivy/core/sort/SilentNonMatchingVersionReporter.java
@@ -0,0 +1,32 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.sort;
+
+import org.apache.ivy.util.Message;
+
+/**
+ * A NonMatchingVersionReporter that only print debug message.
+ */
+public class SilentNonMatchingVersionReporter extends MessageBasedNonMatchingVersionReporter
+        implements NonMatchingVersionReporter {
+
+    protected void reportMessage(String msg) {
+        Message.debug(msg);
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/sort/SimpleSortEngineSettings.java b/src/java/org/apache/ivy/core/sort/SimpleSortEngineSettings.java
new file mode 100644
index 0000000..cb645b7
--- /dev/null
+++ b/src/java/org/apache/ivy/core/sort/SimpleSortEngineSettings.java
@@ -0,0 +1,45 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.sort;
+
+import org.apache.ivy.plugins.circular.CircularDependencyStrategy;
+import org.apache.ivy.plugins.version.VersionMatcher;
+
+public class SimpleSortEngineSettings implements SortEngineSettings {
+
+    private CircularDependencyStrategy circularStrategy;
+
+    private VersionMatcher versionMatcher;
+
+    public CircularDependencyStrategy getCircularDependencyStrategy() {
+        return circularStrategy;
+    }
+
+    public VersionMatcher getVersionMatcher() {
+        return versionMatcher;
+    }
+
+    public void setCircularDependencyStrategy(CircularDependencyStrategy strategy) {
+        circularStrategy = strategy;
+    }
+
+    public void setVersionMatcher(VersionMatcher matcher) {
+        versionMatcher = matcher;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/sort/SortEngine.java b/src/java/org/apache/ivy/core/sort/SortEngine.java
new file mode 100644
index 0000000..e6b6774
--- /dev/null
+++ b/src/java/org/apache/ivy/core/sort/SortEngine.java
@@ -0,0 +1,127 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.sort;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.resolve.IvyNode;
+import org.apache.ivy.plugins.circular.CircularDependencyException;
+import org.apache.ivy.plugins.circular.CircularDependencyStrategy;
+import org.apache.ivy.plugins.circular.IgnoreCircularDependencyStrategy;
+import org.apache.ivy.plugins.version.VersionMatcher;
+import org.apache.ivy.util.Checks;
+
+public class SortEngine {
+
+    private SortEngineSettings settings;
+
+    public SortEngine(SortEngineSettings settings) {
+        if (settings == null) {
+            throw new NullPointerException("SortEngine.settings can not be null");
+        }
+        this.settings = settings;
+    }
+
+    /**
+     * Same as {@link #sortModuleDescriptors(Collection, SortOptions)} but for <code>IvyNode</code>
+     * s.
+     * 
+     * @param nodes
+     *            a Collection of nodes to sort
+     * @param options
+     *            Options to use to sort the nodes.
+     * @return a List of sorted IvyNode
+     * @throws CircularDependencyException
+     *             if a circular dependency exists and circular dependency strategy decide to throw
+     *             an exception
+     */
+    public List<IvyNode> sortNodes(Collection<IvyNode> nodes, SortOptions options) {
+        /*
+         * here we want to use the sort algorithm which work on module descriptors : so we first put
+         * dependencies on a map from descriptors to dependency, then we sort the keySet (i.e. a
+         * collection of descriptors), then we replace in the sorted list each descriptor by the
+         * corresponding dependency
+         */
+
+        Map<ModuleDescriptor, List<IvyNode>> dependenciesMap = new LinkedHashMap<ModuleDescriptor, List<IvyNode>>();
+        List<IvyNode> nulls = new ArrayList<IvyNode>();
+        for (IvyNode node : nodes) {
+            if (node.getDescriptor() == null) {
+                nulls.add(node);
+            } else {
+                List<IvyNode> n = dependenciesMap.get(node.getDescriptor());
+                if (n == null) {
+                    n = new ArrayList<IvyNode>();
+                    dependenciesMap.put(node.getDescriptor(), n);
+                }
+                n.add(node);
+            }
+        }
+        List<ModuleDescriptor> list = sortModuleDescriptors(dependenciesMap.keySet(), options);
+        final double adjustFactor = 1.3;
+        List<IvyNode> ret = new ArrayList<IvyNode>(
+                (int) (list.size() * adjustFactor + nulls.size()));
+        // attempt to adjust the size to avoid too much list resizing
+        for (int i = 0; i < list.size(); i++) {
+            ModuleDescriptor md = list.get(i);
+            List<IvyNode> n = dependenciesMap.get(md);
+            ret.addAll(n);
+        }
+        ret.addAll(0, nulls);
+        return ret;
+    }
+
+    /**
+     * Sorts the given ModuleDescriptors from the less dependent to the more dependent. This sort
+     * ensures that a ModuleDescriptor is always found in the list before all ModuleDescriptors
+     * depending directly on it.
+     * 
+     * @param moduleDescriptors
+     *            a Collection of ModuleDescriptor to sort
+     * @param options
+     *            Options to use to sort the descriptors.
+     * @return a List of sorted ModuleDescriptors
+     * @throws CircularDependencyException
+     *             if a circular dependency exists and circular dependency strategy decide to throw
+     *             an exception
+     */
+    public List<ModuleDescriptor> sortModuleDescriptors(
+            Collection<ModuleDescriptor> moduleDescriptors, SortOptions options)
+            throws CircularDependencyException {
+        Checks.checkNotNull(options, "options");
+        ModuleDescriptorSorter sorter = new ModuleDescriptorSorter(moduleDescriptors,
+                getVersionMatcher(), options.getNonMatchingVersionReporter(),
+                options.isUseCircularDependencyStrategy() ? getCircularStrategy()
+                        : IgnoreCircularDependencyStrategy.getInstance());
+        return sorter.sortModuleDescriptors();
+    }
+
+    protected CircularDependencyStrategy getCircularStrategy() {
+        return settings.getCircularDependencyStrategy();
+    }
+
+    protected VersionMatcher getVersionMatcher() {
+        return settings.getVersionMatcher();
+    }
+
+}
diff --git a/src/java/org/apache/ivy/core/sort/SortEngineSettings.java b/src/java/org/apache/ivy/core/sort/SortEngineSettings.java
new file mode 100644
index 0000000..a1fe8ea
--- /dev/null
+++ b/src/java/org/apache/ivy/core/sort/SortEngineSettings.java
@@ -0,0 +1,31 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.sort;
+
+import org.apache.ivy.plugins.circular.CircularDependencyStrategy;
+import org.apache.ivy.plugins.version.VersionMatcher;
+
+/**
+ * The settings/collaborators used by the SortEngine.
+ */
+public interface SortEngineSettings {
+
+    public CircularDependencyStrategy getCircularDependencyStrategy();
+
+    public VersionMatcher getVersionMatcher();
+}
diff --git a/src/java/org/apache/ivy/core/sort/SortOptions.java b/src/java/org/apache/ivy/core/sort/SortOptions.java
new file mode 100644
index 0000000..adb7bb1
--- /dev/null
+++ b/src/java/org/apache/ivy/core/sort/SortOptions.java
@@ -0,0 +1,56 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.sort;
+
+public class SortOptions {
+    public static final SortOptions DEFAULT = new SortOptions();
+
+    public static final SortOptions SILENT = new SortOptions().setNonMatchingVersionReporter(
+        new SilentNonMatchingVersionReporter()).setUseCircularDependencyStrategy(false);
+
+    /**
+     * Used to report some non matching version (when a modules depends on a specific revision of an
+     * other modules present in the of modules to sort with a different revision.
+     */
+    private NonMatchingVersionReporter nonMatchingVersionReporter = new WarningNonMatchingVersionReporter();
+
+    /**
+     * Should the default circular dependency strategy be used when a circular dependency is found,
+     * or should circular dependencies be ignored?
+     */
+    private boolean useCircularDependencyStrategy = true;
+
+    public NonMatchingVersionReporter getNonMatchingVersionReporter() {
+        return nonMatchingVersionReporter;
+    }
+
+    public SortOptions setNonMatchingVersionReporter(
+            NonMatchingVersionReporter nonMatchingVersionReporter) {
+        this.nonMatchingVersionReporter = nonMatchingVersionReporter;
+        return this;
+    }
+
+    public boolean isUseCircularDependencyStrategy() {
+        return useCircularDependencyStrategy;
+    }
+
+    public SortOptions setUseCircularDependencyStrategy(boolean useCircularDependencyStrategy) {
+        this.useCircularDependencyStrategy = useCircularDependencyStrategy;
+        return this;
+    }
+}
diff --git a/src/java/org/apache/ivy/core/sort/WarningNonMatchingVersionReporter.java b/src/java/org/apache/ivy/core/sort/WarningNonMatchingVersionReporter.java
new file mode 100644
index 0000000..56b39f3
--- /dev/null
+++ b/src/java/org/apache/ivy/core/sort/WarningNonMatchingVersionReporter.java
@@ -0,0 +1,32 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.core.sort;
+
+import org.apache.ivy.util.Message;
+
+/**
+ * A NonMatchingVersionReporter that raise warnings.
+ */
+public class WarningNonMatchingVersionReporter extends MessageBasedNonMatchingVersionReporter
+        implements NonMatchingVersionReporter {
+
+    protected void reportMessage(String msg) {
+        Message.warn(msg);
+    }
+
+}
diff --git a/src/java/org/apache/ivy/logo.png b/src/java/org/apache/ivy/logo.png
new file mode 100644
index 0000000..4baf170
Binary files /dev/null and b/src/java/org/apache/ivy/logo.png differ
diff --git a/src/java/org/apache/ivy/osgi/core/BundleArtifact.java b/src/java/org/apache/ivy/osgi/core/BundleArtifact.java
new file mode 100644
index 0000000..180fa01
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/core/BundleArtifact.java
@@ -0,0 +1,48 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.core;
+
+import java.net.URI;
+
+public class BundleArtifact {
+
+    private boolean source = false;
+
+    private URI uri;
+
+    private String format;
+
+    public BundleArtifact(boolean source, URI uri, String format) {
+        this.source = source;
+        this.uri = uri;
+        this.format = format;
+    }
+
+    public boolean isSource() {
+        return source;
+    }
+
+    public URI getUri() {
+        return uri;
+    }
+
+    public String getFormat() {
+        return format;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/osgi/core/BundleCapability.java b/src/java/org/apache/ivy/osgi/core/BundleCapability.java
new file mode 100644
index 0000000..96f493d
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/core/BundleCapability.java
@@ -0,0 +1,92 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.core;
+
+import org.apache.ivy.osgi.util.Version;
+
+public class BundleCapability {
+
+    private final String name;
+
+    private final Version version;
+
+    private final String type;
+
+    public BundleCapability(String type, String name, Version version) {
+        this.type = type;
+        this.name = name;
+        this.version = version;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public Version getVersion() {
+        return version;
+    }
+
+    public Version getRawVersion() {
+        return version;
+    }
+
+    public String toString() {
+        return name + (version == null ? "" : ";" + version);
+    }
+
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((name == null) ? 0 : name.hashCode());
+        result = prime * result + ((version == null) ? 0 : version.hashCode());
+        return result;
+    }
+
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (!(obj instanceof BundleCapability)) {
+            return false;
+        }
+        BundleCapability other = (BundleCapability) obj;
+        if (name == null) {
+            if (other.name != null) {
+                return false;
+            }
+        } else if (!name.equals(other.name)) {
+            return false;
+        }
+        if (version == null) {
+            if (other.version != null) {
+                return false;
+            }
+        } else if (!version.equals(other.version)) {
+            return false;
+        }
+        return true;
+    }
+
+}
\ No newline at end of file
diff --git a/src/java/org/apache/ivy/osgi/core/BundleInfo.java b/src/java/org/apache/ivy/osgi/core/BundleInfo.java
new file mode 100644
index 0000000..8840351
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/core/BundleInfo.java
@@ -0,0 +1,381 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.core;
+
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.ivy.osgi.util.Version;
+
+/**
+ * Bundle info extracted from the bundle manifest.
+ * 
+ */
+public class BundleInfo {
+
+    public static final Version DEFAULT_VERSION = new Version(1, 0, 0, null);
+
+    public static final String PACKAGE_TYPE = "package";
+
+    public static final String BUNDLE_TYPE = "bundle";
+
+    public static final String EXECUTION_ENVIRONMENT_TYPE = "ee";
+
+    public static final String SERVICE_TYPE = "service";
+
+    private String symbolicName;
+
+    private String presentationName;
+
+    private String id;
+
+    private Version version;
+
+    private Set<BundleRequirement> requirements = new LinkedHashSet<BundleRequirement>();
+
+    private Set<BundleCapability> capabilities = new LinkedHashSet<BundleCapability>();
+
+    private List<String> executionEnvironments = new ArrayList<String>();
+
+    private String description;
+
+    private String documentation;
+
+    private String license;
+
+    private Integer size;
+
+    private boolean isSource = false;
+
+    /** the symbolic name of the bundle it is source of */
+    private String symbolicNameTarget;
+
+    /** the version of the bundle it is source of */
+    private Version versionTarget;
+
+    private boolean hasInnerClasspath;
+
+    private List<String> classpath;
+
+    private List<BundleArtifact> artifacts = new ArrayList<BundleArtifact>();
+
+    public BundleInfo(String name, Version version) {
+        this.symbolicName = name;
+        this.version = version;
+    }
+
+    public String toString() {
+        StringBuffer builder = new StringBuffer();
+        builder.append("BundleInfo [executionEnvironments=");
+        builder.append(executionEnvironments);
+        builder.append(", capabilities=");
+        builder.append(capabilities);
+        builder.append(", requirements=");
+        builder.append(requirements);
+        builder.append(", symbolicName=");
+        builder.append(symbolicName);
+        builder.append(", version=");
+        builder.append(version);
+        builder.append("]");
+        if (symbolicNameTarget != null) {
+            builder.append(" source of ");
+            builder.append(symbolicNameTarget);
+            builder.append("@");
+            builder.append(versionTarget);
+        }
+        return builder.toString();
+    }
+
+    public String getSymbolicName() {
+        return symbolicName;
+    }
+
+    public Version getVersion() {
+        return version == null ? DEFAULT_VERSION : version;
+    }
+
+    public Version getRawVersion() {
+        return version;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setPresentationName(String presentationName) {
+        this.presentationName = presentationName;
+    }
+
+    public String getPresentationName() {
+        return presentationName;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDocumentation(String documentation) {
+        this.documentation = documentation;
+    }
+
+    public String getDocumentation() {
+        return documentation;
+    }
+
+    public void setLicense(String license) {
+        this.license = license;
+    }
+
+    public String getLicense() {
+        return license;
+    }
+
+    public void setSize(Integer size) {
+        this.size = size;
+    }
+
+    public Integer getSize() {
+        return size;
+    }
+
+    public void addRequirement(BundleRequirement requirement) {
+        requirements.add(requirement);
+    }
+
+    public Set<BundleRequirement> getRequirements() {
+        return requirements;
+    }
+
+    public void addCapability(BundleCapability capability) {
+        capabilities.add(capability);
+    }
+
+    public Set<BundleCapability> getCapabilities() {
+        return capabilities;
+    }
+
+    public List<String> getExecutionEnvironments() {
+        return executionEnvironments;
+    }
+
+    public void setExecutionEnvironments(List<String> executionEnvironments) {
+        this.executionEnvironments = executionEnvironments;
+        for (String executionEnvironment : executionEnvironments) {
+            addRequirement(new BundleRequirement(EXECUTION_ENVIRONMENT_TYPE, executionEnvironment,
+                    null, null));
+        }
+    }
+
+    public void addExecutionEnvironment(String name) {
+        executionEnvironments.add(name);
+    }
+
+    public void setSource(boolean isSource) {
+        this.isSource = isSource;
+    }
+
+    public boolean isSource() {
+        return isSource;
+    }
+
+    public void setSymbolicNameTarget(String symbolicNameTarget) {
+        this.symbolicNameTarget = symbolicNameTarget;
+    }
+
+    public String getSymbolicNameTarget() {
+        return symbolicNameTarget;
+    }
+
+    public void setVersionTarget(Version versionTarget) {
+        this.versionTarget = versionTarget;
+    }
+
+    public Version getVersionTarget() {
+        return versionTarget;
+    }
+
+    public void setHasInnerClasspath(boolean hasInnerClasspath) {
+        this.hasInnerClasspath = hasInnerClasspath;
+    }
+
+    public boolean hasInnerClasspath() {
+        return hasInnerClasspath;
+    }
+
+    public void setClasspath(List<String> classpath) {
+        this.classpath = classpath;
+    }
+
+    public List<String> getClasspath() {
+        return classpath;
+    }
+
+    public void addArtifact(BundleArtifact artifact) {
+        artifacts.add(artifact);
+    }
+
+    public void removeArtifact(BundleArtifact same) {
+        artifacts.remove(same);
+    }
+
+    public List<BundleArtifact> getArtifacts() {
+        return artifacts;
+    }
+
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((capabilities == null) ? 0 : capabilities.hashCode());
+        result = prime * result + ((requirements == null) ? 0 : requirements.hashCode());
+        result = prime * result + ((symbolicName == null) ? 0 : symbolicName.hashCode());
+        result = prime * result + ((version == null) ? 0 : version.hashCode());
+        result = prime * result
+                + ((executionEnvironments == null) ? 0 : executionEnvironments.hashCode());
+        result = prime * result
+                + ((symbolicNameTarget == null) ? 0 : symbolicNameTarget.hashCode());
+        result = prime * result + ((versionTarget == null) ? 0 : versionTarget.hashCode());
+        return result;
+    }
+
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (!(obj instanceof BundleInfo)) {
+            return false;
+        }
+        BundleInfo other = (BundleInfo) obj;
+        if (capabilities == null) {
+            if (other.capabilities != null) {
+                return false;
+            }
+        } else if (!capabilities.equals(other.capabilities)) {
+            return false;
+        }
+        if (requirements == null) {
+            if (other.requirements != null) {
+                return false;
+            }
+        } else if (!requirements.equals(other.requirements)) {
+            return false;
+        }
+        if (symbolicName == null) {
+            if (other.symbolicName != null) {
+                return false;
+            }
+        } else if (!symbolicName.equals(other.symbolicName)) {
+            return false;
+        }
+        if (version == null) {
+            if (other.version != null) {
+                return false;
+            }
+        } else if (!version.equals(other.version)) {
+            return false;
+        }
+        if (executionEnvironments == null) {
+            if (other.executionEnvironments != null) {
+                return false;
+            }
+        } else if (!executionEnvironments.equals(other.executionEnvironments)) {
+            return false;
+        }
+        if (isSource != other.isSource) {
+            return false;
+        }
+        if (symbolicNameTarget == null) {
+            if (other.symbolicNameTarget != null) {
+                return false;
+            }
+        } else if (!symbolicNameTarget.equals(other.symbolicNameTarget)) {
+            return false;
+        }
+        if (versionTarget == null) {
+            if (other.versionTarget != null) {
+                return false;
+            }
+        } else if (!versionTarget.equals(other.versionTarget)) {
+            return false;
+        }
+        if (hasInnerClasspath != other.hasInnerClasspath) {
+            return false;
+        }
+        if (classpath == null) {
+            if (other.classpath != null) {
+                return false;
+            }
+        } else if (!classpath.equals(other.classpath)) {
+            return false;
+        }
+        return true;
+    }
+
+    public Set<BundleRequirement> getRequires() {
+        Set<BundleRequirement> set = new LinkedHashSet<BundleRequirement>();
+        for (BundleRequirement requirement : requirements) {
+            if (requirement.getType().equals(BUNDLE_TYPE)) {
+                set.add(requirement);
+            }
+        }
+        return set;
+    }
+
+    public Set<BundleRequirement> getImports() {
+        Set<BundleRequirement> set = new LinkedHashSet<BundleRequirement>();
+        for (BundleRequirement requirement : requirements) {
+            if (requirement.getType().equals(PACKAGE_TYPE)) {
+                set.add(requirement);
+            }
+        }
+        return set;
+    }
+
+    public Set<ExportPackage> getExports() {
+        Set<ExportPackage> set = new LinkedHashSet<ExportPackage>();
+        for (BundleCapability capability : capabilities) {
+            if (PACKAGE_TYPE.equals(capability.getType())) {
+                set.add((ExportPackage) capability);
+            }
+        }
+        return set;
+    }
+
+    public Set<BundleCapability> getServices() {
+        Set<BundleCapability> set = new LinkedHashSet<BundleCapability>();
+        for (BundleCapability capability : capabilities) {
+            if (SERVICE_TYPE.equals(capability.getType())) {
+                set.add(capability);
+            }
+        }
+        return set;
+    }
+
+}
\ No newline at end of file
diff --git a/src/java/org/apache/ivy/osgi/core/BundleInfoAdapter.java b/src/java/org/apache/ivy/osgi/core/BundleInfoAdapter.java
new file mode 100644
index 0000000..136a32c
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/core/BundleInfoAdapter.java
@@ -0,0 +1,370 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.core;
+
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.jar.Manifest;
+
+import org.apache.ivy.Ivy;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.Configuration;
+import org.apache.ivy.core.module.descriptor.Configuration.Visibility;
+import org.apache.ivy.core.module.descriptor.DefaultArtifact;
+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.descriptor.ExtraInfoHolder;
+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.osgi.util.Version;
+import org.apache.ivy.osgi.util.VersionRange;
+import org.apache.ivy.plugins.matcher.ExactOrRegexpPatternMatcher;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+import org.apache.ivy.plugins.parser.ModuleDescriptorParser;
+
+public class BundleInfoAdapter {
+
+    public static final String CONF_NAME_DEFAULT = "default";
+
+    public static final Configuration CONF_DEFAULT = new Configuration(CONF_NAME_DEFAULT);
+
+    public static final String CONF_NAME_OPTIONAL = "optional";
+
+    public static final Configuration CONF_OPTIONAL = new Configuration(CONF_NAME_OPTIONAL,
+            Visibility.PUBLIC, "Optional dependencies", new String[] {CONF_NAME_DEFAULT}, true,
+            null);
+
+    public static final String CONF_NAME_TRANSITIVE_OPTIONAL = "transitive-optional";
+
+    public static final Configuration CONF_TRANSITIVE_OPTIONAL = new Configuration(
+            CONF_NAME_TRANSITIVE_OPTIONAL, Visibility.PUBLIC, "Optional dependencies",
+            new String[] {CONF_NAME_OPTIONAL}, true, null);
+
+    public static final String CONF_USE_PREFIX = "use_";
+
+    public static final String EXTRA_INFO_EXPORT_PREFIX = "_osgi_export_";
+
+    public static DefaultModuleDescriptor toModuleDescriptor(ModuleDescriptorParser parser,
+            URI baseUri, BundleInfo bundle, ExecutionEnvironmentProfileProvider profileProvider) {
+        return toModuleDescriptor(parser, baseUri, bundle, null, profileProvider);
+    }
+
+    /**
+     * 
+     * @param baseUri
+     *            uri to help build the absolute url if the bundle info has a relative uri.
+     * @return
+     * @throws ProfileNotFoundException
+     */
+    public static DefaultModuleDescriptor toModuleDescriptor(ModuleDescriptorParser parser,
+            URI baseUri, BundleInfo bundle, Manifest manifest,
+            ExecutionEnvironmentProfileProvider profileProvider) throws ProfileNotFoundException {
+        DefaultModuleDescriptor md = new DefaultModuleDescriptor(parser, null);
+        md.addExtraAttributeNamespace("o", Ivy.getIvyHomeURL() + "osgi");
+        ModuleRevisionId mrid = asMrid(BundleInfo.BUNDLE_TYPE, bundle.getSymbolicName(),
+            bundle.getVersion());
+        md.setResolvedPublicationDate(new Date());
+        md.setModuleRevisionId(mrid);
+
+        md.addConfiguration(CONF_DEFAULT);
+        md.addConfiguration(CONF_OPTIONAL);
+        md.addConfiguration(CONF_TRANSITIVE_OPTIONAL);
+
+        Set<String> exportedPkgNames = new HashSet<String>(bundle.getExports().size());
+        for (ExportPackage exportPackage : bundle.getExports()) {
+            md.getExtraInfos().add(
+                new ExtraInfoHolder(EXTRA_INFO_EXPORT_PREFIX + exportPackage.getName(),
+                        exportPackage.getVersion().toString()));
+            exportedPkgNames.add(exportPackage.getName());
+            String[] confDependencies = new String[exportPackage.getUses().size() + 1];
+            int i = 0;
+            for (String use : exportPackage.getUses()) {
+                confDependencies[i++] = CONF_USE_PREFIX + use;
+            }
+            confDependencies[i] = CONF_NAME_DEFAULT;
+            md.addConfiguration(new Configuration(CONF_USE_PREFIX + exportPackage.getName(),
+                    Visibility.PUBLIC, "Exported package " + exportPackage.getName(),
+                    confDependencies, true, null));
+        }
+
+        requirementAsDependency(md, bundle, exportedPkgNames);
+
+        if (baseUri != null) {
+            for (BundleArtifact bundleArtifact : bundle.getArtifacts()) {
+                String type = "jar";
+                String ext = "jar";
+                String packaging = null;
+                if (bundle.hasInnerClasspath() && !bundleArtifact.isSource()) {
+                    packaging = "bundle";
+                }
+                if ("packed".equals(bundleArtifact.getFormat())) {
+                    ext = "jar.pack.gz";
+                    if (packaging != null) {
+                        packaging += ",pack200";
+                    } else {
+                        packaging = "pack200";
+                    }
+                }
+                if (bundleArtifact.isSource()) {
+                    type = "source";
+                }
+                URI uri = bundleArtifact.getUri();
+                if (uri != null) {
+                    DefaultArtifact artifact = buildArtifact(mrid, baseUri, uri, type, ext,
+                        packaging);
+                    md.addArtifact(CONF_NAME_DEFAULT, artifact);
+                }
+            }
+        }
+
+        if (profileProvider != null) {
+            for (String env : bundle.getExecutionEnvironments()) {
+                ExecutionEnvironmentProfile profile = profileProvider.getProfile(env);
+                if (profile == null) {
+                    throw new ProfileNotFoundException("Execution environment profile " + env
+                            + " not found");
+                }
+                for (String pkg : profile.getPkgNames()) {
+                    ArtifactId id = new ArtifactId(ModuleId.newInstance(BundleInfo.PACKAGE_TYPE,
+                        pkg), PatternMatcher.ANY_EXPRESSION, PatternMatcher.ANY_EXPRESSION,
+                            PatternMatcher.ANY_EXPRESSION);
+                    DefaultExcludeRule rule = new DefaultExcludeRule(id,
+                            ExactOrRegexpPatternMatcher.INSTANCE, null);
+                    String[] confs = md.getConfigurationsNames();
+                    for (int i = 0; i < confs.length; i++) {
+                        rule.addConfiguration(confs[i]);
+                    }
+                    md.addExcludeRule(rule);
+                }
+            }
+        }
+
+        if (manifest != null) {
+            for (Entry<Object, Object> entries : manifest.getMainAttributes().entrySet()) {
+                md.addExtraInfo(new ExtraInfoHolder(entries.getKey().toString(), entries.getValue()
+                        .toString()));
+            }
+        }
+
+        return md;
+    }
+
+    public static DefaultArtifact buildArtifact(ModuleRevisionId mrid, URI baseUri, URI uri,
+            String type, String ext, String packaging) {
+        DefaultArtifact artifact;
+        if ("ivy".equals(uri.getScheme())) {
+            artifact = decodeIvyURI(uri);
+        } else {
+            if (!uri.isAbsolute()) {
+                uri = baseUri.resolve(uri);
+            }
+            Map<String, String> extraAtt = new HashMap<String, String>();
+            if (packaging != null) {
+                extraAtt.put("packaging", packaging);
+            }
+            try {
+                artifact = new DefaultArtifact(mrid, null, mrid.getName(), type, ext, new URL(
+                        uri.toString()), extraAtt);
+            } catch (MalformedURLException e) {
+                throw new RuntimeException("Unable to make the uri into the url", e);
+            }
+        }
+        return artifact;
+    }
+
+    public static List<String> getConfigurations(BundleInfo bundle) {
+        List<String> confs = new ArrayList<String>();
+        confs.add(CONF_NAME_DEFAULT);
+        confs.add(CONF_NAME_OPTIONAL);
+        confs.add(CONF_NAME_TRANSITIVE_OPTIONAL);
+
+        for (ExportPackage exportPackage : bundle.getExports()) {
+            confs.add(CONF_USE_PREFIX + exportPackage.getName());
+        }
+
+        return confs;
+    }
+
+    public static URI buildIvyURI(Artifact artifact) {
+        ModuleRevisionId mrid = artifact.getModuleRevisionId();
+        return asIvyURI(mrid.getOrganisation(), mrid.getName(), mrid.getBranch(),
+            mrid.getRevision(), artifact.getType(), artifact.getName(), artifact.getExt());
+    }
+
+    private static URI asIvyURI(String org, String name, String branch, String rev, String type,
+            String art, String ext) {
+        StringBuffer builder = new StringBuffer();
+        builder.append("ivy:///");
+        builder.append(org);
+        builder.append('/');
+        builder.append(name);
+        builder.append('?');
+        if (branch != null) {
+            builder.append("branch=");
+            builder.append(branch);
+        }
+        if (rev != null) {
+            builder.append("&rev=");
+            builder.append(rev);
+        }
+        if (type != null) {
+            builder.append("&type=");
+            builder.append(type);
+        }
+        if (art != null) {
+            builder.append("&art=");
+            builder.append(art);
+        }
+        if (ext != null) {
+            builder.append("&ext=");
+            builder.append(ext);
+        }
+        try {
+            return new URI(builder.toString());
+        } catch (URISyntaxException e) {
+            throw new RuntimeException("illformed ivy url", e);
+        }
+    }
+
+    private static DefaultArtifact decodeIvyURI(final URI uri) {
+        String org = null;
+        String name = null;
+        String branch = null;
+        String rev = null;
+        String art = null;
+        String type = null;
+        String ext = null;
+
+        String path = uri.getPath();
+        if (!path.startsWith("/")) {
+            throw new IllegalArgumentException(
+                    "An ivy url should be of the form ivy:///org/module but was : " + uri);
+        }
+        int i = path.indexOf('/', 1);
+        if (i < 0) {
+            throw new IllegalArgumentException("Expecting an organisation in the ivy url: " + uri);
+        }
+        org = path.substring(1, i);
+        name = path.substring(i + 1);
+
+        String query = uri.getQuery();
+        String[] parameters = query.split("&");
+        for (int j = 0; j < parameters.length; j++) {
+            String parameter = parameters[j];
+            if (parameter.length() == 0) {
+                continue;
+            }
+            String[] nameAndValue = parameter.split("=");
+            if (nameAndValue.length != 2) {
+                throw new IllegalArgumentException("Malformed query string in the ivy url: " + uri);
+            } else if (nameAndValue[0].equals("branch")) {
+                branch = nameAndValue[1];
+            } else if (nameAndValue[0].equals("rev")) {
+                rev = nameAndValue[1];
+            } else if (nameAndValue[0].equals("art")) {
+                art = nameAndValue[1];
+            } else if (nameAndValue[0].equals("type")) {
+                type = nameAndValue[1];
+            } else if (nameAndValue[0].equals("ext")) {
+                ext = nameAndValue[1];
+            } else {
+                throw new IllegalArgumentException("Unrecognized parameter '" + nameAndValue[0]
+                        + " in the query string of the ivy url: " + uri);
+            }
+        }
+
+        ModuleRevisionId amrid = ModuleRevisionId.newInstance(org, name, branch, rev);
+        DefaultArtifact artifact = new DefaultArtifact(amrid, null, art, type, ext);
+        return artifact;
+    }
+
+    private static void requirementAsDependency(DefaultModuleDescriptor md, BundleInfo bundleInfo,
+            Set<String> exportedPkgNames) {
+        for (BundleRequirement requirement : bundleInfo.getRequirements()) {
+            String type = requirement.getType();
+            String name = requirement.getName();
+
+            if (BundleInfo.PACKAGE_TYPE.equals(type) && exportedPkgNames.contains(name)) {
+                // don't declare package exported by the current bundle
+                continue;
+            }
+
+            if (BundleInfo.EXECUTION_ENVIRONMENT_TYPE.equals(type)) {
+                // execution environment are handled elsewhere
+                continue;
+            }
+
+            ModuleRevisionId ddmrid = asMrid(type, name, requirement.getVersion());
+            DefaultDependencyDescriptor dd = new DefaultDependencyDescriptor(ddmrid, false);
+
+            String conf = CONF_NAME_DEFAULT;
+            if (BundleInfo.PACKAGE_TYPE.equals(type)) {
+                // declare the configuration for the package
+                conf = CONF_USE_PREFIX + name;
+                md.addConfiguration(new Configuration(CONF_USE_PREFIX + name, Visibility.PUBLIC,
+                        "Exported package " + name, new String[] {CONF_NAME_DEFAULT}, true, null));
+                dd.addDependencyConfiguration(conf, conf);
+            }
+
+            if ("optional".equals(requirement.getResolution())) {
+                dd.addDependencyConfiguration(CONF_NAME_OPTIONAL, conf);
+                dd.addDependencyConfiguration(CONF_NAME_TRANSITIVE_OPTIONAL,
+                    CONF_NAME_TRANSITIVE_OPTIONAL);
+            } else {
+                dd.addDependencyConfiguration(CONF_NAME_DEFAULT, conf);
+            }
+
+            md.addDependency(dd);
+        }
+
+    }
+
+    public static ModuleRevisionId asMrid(String type, String name, Version v) {
+        return ModuleRevisionId.newInstance(type, name, v == null ? null : v.toString());
+    }
+
+    public static ModuleRevisionId asMrid(String type, String name, VersionRange v) {
+        String revision;
+        if (v == null) {
+            revision = "[0,)";
+        } else {
+            revision = v.toIvyRevision();
+        }
+        return ModuleRevisionId.newInstance(type, name, revision);
+    }
+
+    public static class ProfileNotFoundException extends RuntimeException {
+
+        public ProfileNotFoundException(String msg) {
+            super(msg);
+        }
+
+    }
+}
diff --git a/src/java/org/apache/ivy/osgi/core/BundleRequirement.java b/src/java/org/apache/ivy/osgi/core/BundleRequirement.java
new file mode 100644
index 0000000..92b5dd3
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/core/BundleRequirement.java
@@ -0,0 +1,111 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.core;
+
+import org.apache.ivy.osgi.util.VersionRange;
+
+public class BundleRequirement {
+
+    private final String name;
+
+    private final String resolution;
+
+    private final VersionRange version;
+
+    private final String type;
+
+    public BundleRequirement(String type, String name, VersionRange version, String resolution) {
+        this.type = type;
+        this.name = name;
+        this.version = version;
+        this.resolution = resolution;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public VersionRange getVersion() {
+        return version;
+    }
+
+    public String getResolution() {
+        return resolution;
+    }
+
+    public String toString() {
+        return name + (version == null ? "" : ";" + version)
+                + (resolution == null ? "" : " (" + resolution + ")");
+    }
+
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((type == null) ? 0 : type.hashCode());
+        result = prime * result + ((name == null) ? 0 : name.hashCode());
+        result = prime * result + ((resolution == null) ? 0 : resolution.hashCode());
+        result = prime * result + ((version == null) ? 0 : version.hashCode());
+        return result;
+    }
+
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (!(obj instanceof BundleRequirement)) {
+            return false;
+        }
+        BundleRequirement other = (BundleRequirement) obj;
+        if (type == null) {
+            if (other.type != null) {
+                return false;
+            }
+        } else if (!type.equals(other.type)) {
+            return false;
+        }
+        if (name == null) {
+            if (other.name != null) {
+                return false;
+            }
+        } else if (!name.equals(other.name)) {
+            return false;
+        }
+        if (resolution == null) {
+            if (other.resolution != null) {
+                return false;
+            }
+        } else if (!resolution.equals(other.resolution)) {
+            return false;
+        }
+        if (version == null) {
+            if (other.version != null) {
+                return false;
+            }
+        } else if (!version.equals(other.version)) {
+            return false;
+        }
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/src/java/org/apache/ivy/osgi/core/ExecutionEnvironmentProfile.java b/src/java/org/apache/ivy/osgi/core/ExecutionEnvironmentProfile.java
new file mode 100644
index 0000000..8c079bb
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/core/ExecutionEnvironmentProfile.java
@@ -0,0 +1,80 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.core;
+
+import java.util.Set;
+import java.util.TreeSet;
+
+public class ExecutionEnvironmentProfile {
+
+    Set<String> pkgNames = new TreeSet<String>();
+
+    private final String name;
+
+    public ExecutionEnvironmentProfile(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public Set<String> getPkgNames() {
+        return pkgNames;
+    }
+
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((name == null) ? 0 : name.hashCode());
+        result = prime * result + ((pkgNames == null) ? 0 : pkgNames.hashCode());
+        return result;
+    }
+
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (!(obj instanceof ExecutionEnvironmentProfile)) {
+            return false;
+        }
+        ExecutionEnvironmentProfile other = (ExecutionEnvironmentProfile) obj;
+        if (name == null) {
+            if (other.name != null) {
+                return false;
+            }
+        } else if (!name.equals(other.name)) {
+            return false;
+        }
+        if (pkgNames == null) {
+            if (other.pkgNames != null) {
+                return false;
+            }
+        } else if (!pkgNames.equals(other.pkgNames)) {
+            return false;
+        }
+        return true;
+    }
+
+    public String toString() {
+        return name + ":" + pkgNames;
+    }
+}
diff --git a/src/java/org/apache/ivy/osgi/core/ExecutionEnvironmentProfileProvider.java b/src/java/org/apache/ivy/osgi/core/ExecutionEnvironmentProfileProvider.java
new file mode 100644
index 0000000..45f3bc1
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/core/ExecutionEnvironmentProfileProvider.java
@@ -0,0 +1,135 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.core;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Properties;
+
+import org.apache.ivy.util.Message;
+
+public class ExecutionEnvironmentProfileProvider {
+
+    private static final String DEFAULT_PROFILES_FILE = "jvm-packages.properties";
+
+    private static final String PACKAGE_PREFIX = "org/apache/ivy/osgi/core/";
+
+    private Map<String, ExecutionEnvironmentProfile> profileList;
+
+    private static final ExecutionEnvironmentProfileProvider INSTANCE;
+
+    static {
+        try {
+            INSTANCE = new ExecutionEnvironmentProfileProvider();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static ExecutionEnvironmentProfileProvider getInstance() {
+        return INSTANCE;
+    }
+
+    public ExecutionEnvironmentProfileProvider() throws IOException {
+        profileList = loadDefaultProfileList();
+    }
+
+    public ExecutionEnvironmentProfile getProfile(String profile) {
+        return profileList.get(profile);
+    }
+
+    public static Map<String, ExecutionEnvironmentProfile> loadDefaultProfileList()
+            throws IOException {
+        ClassLoader loader = ExecutionEnvironmentProfileProvider.class.getClassLoader();
+        InputStream defaultProfilesFile = loader.getResourceAsStream(PACKAGE_PREFIX
+                + DEFAULT_PROFILES_FILE);
+        if (defaultProfilesFile == null) {
+            throw new FileNotFoundException(PACKAGE_PREFIX + DEFAULT_PROFILES_FILE
+                    + " not found in the classpath");
+        }
+        Properties props = new Properties();
+        try {
+            props.load(defaultProfilesFile);
+        } finally {
+            defaultProfilesFile.close();
+        }
+        Map<String, ExecutionEnvironmentProfile> profiles = new HashMap<String, ExecutionEnvironmentProfile>();
+        for (Entry<Object, Object> prop : props.entrySet()) {
+            String propName = (String) prop.getKey();
+            if (propName.endsWith(".pkglist")) {
+                String profileName = propName.substring(0, propName.length() - 8);
+                if (!profiles.containsKey(profileName)) {
+                    loadProfile(props, profiles, profileName);
+                }
+            }
+        }
+        return profiles;
+    }
+
+    private static ExecutionEnvironmentProfile loadProfile(Properties props,
+            Map<String, ExecutionEnvironmentProfile> profiles, String name) {
+
+        ExecutionEnvironmentProfile profile = new ExecutionEnvironmentProfile(name);
+
+        // load the package for the extended profile
+        String extendedProfileName = props.getProperty(name + ".extends");
+        if (extendedProfileName != null) {
+            ExecutionEnvironmentProfile extendedProfile = profiles.get(extendedProfileName);
+            if (extendedProfile == null) {
+                // not loaded yet, so load it now
+                extendedProfile = loadProfile(props, profiles, extendedProfileName);
+            }
+            profile.pkgNames.addAll(extendedProfile.pkgNames);
+        }
+
+        // load the actual list
+        String pkgList = props.getProperty(name + ".pkglist");
+        String[] packages = pkgList.split(",");
+        for (int i = 0; i < packages.length; i++) {
+            String pkg = packages[i].trim();
+            if (pkg.length() != 0) {
+                profile.pkgNames.add(pkg);
+            }
+        }
+
+        profiles.put(name, profile);
+
+        String aliasList = props.getProperty(name + ".aliases");
+        if (aliasList != null) {
+            String[] aliases = aliasList.split(",");
+            for (int i = 0; i < aliases.length; i++) {
+                String alias = aliases[i].trim();
+                if (alias.length() != 0) {
+                    ExecutionEnvironmentProfile profileAlias = new ExecutionEnvironmentProfile(
+                            alias);
+                    profileAlias.pkgNames = profile.pkgNames;
+                    profiles.put(alias, profileAlias);
+                }
+            }
+        }
+
+        Message.verbose("Execution environment profile " + profile.getName() + " loaded");
+
+        return profile;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/osgi/core/ExportPackage.java b/src/java/org/apache/ivy/osgi/core/ExportPackage.java
new file mode 100644
index 0000000..25e0334
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/core/ExportPackage.java
@@ -0,0 +1,70 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.core;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.ivy.osgi.util.Version;
+
+public class ExportPackage extends BundleCapability {
+
+    private final Set<String> uses = new HashSet<String>();
+
+    public ExportPackage(String name, Version version) {
+        super(BundleInfo.PACKAGE_TYPE, name, version);
+    }
+
+    public void addUse(String pkg) {
+        uses.add(pkg);
+    }
+
+    public Version getVersion() {
+        return super.getVersion() == null ? BundleInfo.DEFAULT_VERSION : super.getVersion();
+    }
+
+    public Set<String> getUses() {
+        return uses;
+    }
+
+    public int hashCode() {
+        final int prime = 31;
+        int result = super.hashCode();
+        result = prime * result + ((uses == null) ? 0 : uses.hashCode());
+        return result;
+    }
+
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!super.equals(obj)) {
+            return false;
+        }
+        ExportPackage other = (ExportPackage) obj;
+        if (uses == null) {
+            if (other.uses != null) {
+                return false;
+            }
+        } else if (!uses.equals(other.uses)) {
+            return false;
+        }
+        return true;
+    }
+
+}
\ No newline at end of file
diff --git a/src/java/org/apache/ivy/osgi/core/ManifestHeaderElement.java b/src/java/org/apache/ivy/osgi/core/ManifestHeaderElement.java
new file mode 100644
index 0000000..7aa2176
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/core/ManifestHeaderElement.java
@@ -0,0 +1,113 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.core;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+public class ManifestHeaderElement {
+    private List<String> values = new ArrayList<String>();
+
+    private Map<String, String> attributes = new HashMap<String, String>();
+
+    private Map<String, String> directives = new HashMap<String, String>();
+
+    public List<String> getValues() {
+        return values;
+    }
+
+    public void addValue(String value) {
+        values.add(value);
+    }
+
+    public Map<String, String> getAttributes() {
+        return attributes;
+    }
+
+    public void addAttribute(String name, String value) {
+        attributes.put(name, value);
+    }
+
+    public Map<String, String> getDirectives() {
+        return directives;
+    }
+
+    public void addDirective(String name, String value) {
+        directives.put(name, value);
+    }
+
+    public boolean equals(Object obj) {
+        if (!(obj instanceof ManifestHeaderElement)) {
+            return false;
+        }
+        ManifestHeaderElement other = (ManifestHeaderElement) obj;
+        if (other.values.size() != values.size()) {
+            return false;
+        }
+        for (String value : values) {
+            if (!other.values.contains(value)) {
+                return false;
+            }
+        }
+        if (other.directives.size() != directives.size()) {
+            return false;
+        }
+        for (Entry<String, String> directive : directives.entrySet()) {
+            if (!directive.getValue().equals(other.directives.get(directive.getKey()))) {
+                return false;
+            }
+        }
+        if (other.attributes.size() != attributes.size()) {
+            return false;
+        }
+        for (Entry<String, String> attribute : attributes.entrySet()) {
+            if (!attribute.getValue().equals(other.attributes.get(attribute.getKey()))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public String toString() {
+        String string = "";
+        Iterator<String> itValues = values.iterator();
+        while (itValues.hasNext()) {
+            string = string.concat(itValues.next());
+            if (itValues.hasNext()) {
+                string = string.concat(";");
+            }
+        }
+        for (Entry<String, String> directive : directives.entrySet()) {
+            string = string.concat(";");
+            string = string.concat(directive.getKey());
+            string = string.concat(":=");
+            string = string.concat(directive.getValue());
+        }
+        for (Entry<String, String> attribute : attributes.entrySet()) {
+            string = string.concat(";");
+            string = string.concat(attribute.getKey());
+            string = string.concat("=");
+            string = string.concat(attribute.getValue());
+        }
+        return string;
+    }
+}
diff --git a/src/java/org/apache/ivy/osgi/core/ManifestHeaderValue.java b/src/java/org/apache/ivy/osgi/core/ManifestHeaderValue.java
new file mode 100644
index 0000000..d793975
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/core/ManifestHeaderValue.java
@@ -0,0 +1,416 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.core;
+
+import java.io.PrintStream;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Parse a header of a manifest. The manifest header is composed with the following rules:
+ * 
+ * <pre>
+ * header ::= header-element (',' header-element)*
+ * header-element ::= values (';' (attribute | directive) )*
+ * values ::= value (';' value)*
+ * value ::= <any string value that does not have ';' or ','>
+ * attribute ::= key '=' value
+ * directive ::= key '=' value
+ * key ::= token
+ * value ::= token | quoted-string | double-quoted-string
+ * </pre>
+ */
+public class ManifestHeaderValue {
+
+    private List<ManifestHeaderElement> elements = new ArrayList<ManifestHeaderElement>();
+
+    ManifestHeaderValue() {
+        // just for unit testing
+    }
+
+    public ManifestHeaderValue(String header) throws ParseException {
+        if (header != null) {
+            new ManifestHeaderParser(header).parse();
+        }
+    }
+
+    public List<ManifestHeaderElement> getElements() {
+        return elements;
+    }
+
+    public String getSingleValue() {
+        if (elements.isEmpty()) {
+            return null;
+        }
+        List<String> values = getElements().iterator().next().getValues();
+        if (values.isEmpty()) {
+            return null;
+        }
+        return values.iterator().next();
+    }
+
+    public List<String> getValues() {
+        if (elements.isEmpty()) {
+            return Collections.emptyList();
+        }
+        List<String> list = new ArrayList<String>();
+        for (ManifestHeaderElement element : getElements()) {
+            list.addAll(element.getValues());
+        }
+        return list;
+    }
+
+    void addElement(ManifestHeaderElement element) {
+        this.elements.add(element);
+    }
+
+    class ManifestHeaderParser {
+
+        /**
+         * header to parse
+         */
+        private final String header;
+
+        /**
+         * the length of the source
+         */
+        private int length;
+
+        /**
+         * buffer
+         */
+        private StringBuffer buffer = new StringBuffer();
+
+        /**
+         * position in the source
+         */
+        private int pos = 0;
+
+        /**
+         * last read character
+         */
+        private char c;
+
+        /**
+         * the header element being build
+         */
+        private ManifestHeaderElement elem = new ManifestHeaderElement();
+
+        /**
+         * Once at true (at the first attribute parsed), only parameters are allowed
+         */
+        private boolean valuesParsed;
+
+        /**
+         * the last parsed parameter name
+         */
+        private String paramName;
+
+        /**
+         * true if the last parsed parameter is a directive (assigned via :=)
+         */
+        private boolean isDirective;
+
+        /**
+         * Default constructor
+         * 
+         * @param header
+         *            the header to parse
+         */
+        ManifestHeaderParser(String header) {
+            this.header = header;
+            this.length = header.length();
+        }
+
+        /**
+         * Do the parsing
+         * 
+         * @throws ParseException
+         */
+        void parse() throws ParseException {
+            do {
+                elem = new ManifestHeaderElement();
+                int posElement = pos;
+                parseElement();
+                if (elem.getValues().isEmpty()) {
+                    error("No defined value", posElement);
+                    // try to recover: ignore that element
+                    continue;
+                }
+                addElement(elem);
+            } while (pos < length);
+        }
+
+        private char readNext() {
+            if (pos == length) {
+                c = '\0';
+            } else {
+                c = header.charAt(pos++);
+            }
+            return c;
+        }
+
+        private void error(String message) throws ParseException {
+            error(message, pos - 1);
+        }
+
+        private void error(String message, int p) throws ParseException {
+            throw new ParseException(message, p);
+        }
+
+        private void parseElement() throws ParseException {
+            valuesParsed = false;
+            do {
+                parseValueOrParameter();
+            } while (c == ';' && pos < length);
+        }
+
+        private void parseValueOrParameter() throws ParseException {
+            // true if the value/parameter parsing has started, white spaces skipped
+            boolean start = false;
+            do {
+                switch (readNext()) {
+                    case '\0':
+                        break;
+                    case ';':
+                    case ',':
+                        endValue();
+                        return;
+                    case ':':
+                    case '=':
+                        endParameterName();
+                        parseSeparator();
+                        parseParameterValue();
+                        return;
+                    case ' ':
+                    case '\t':
+                    case '\n':
+                    case '\r':
+                        if (start) {
+                            buffer.append(c);
+                        }
+                        break;
+                    default:
+                        start = true;
+                        buffer.append(c);
+                }
+            } while (pos < length);
+            endValue();
+        }
+
+        private void endValue() throws ParseException {
+            if (valuesParsed) {
+                error("Early end of a parameter");
+                // try to recover: ignore it
+                buffer.setLength(0);
+                return;
+            }
+            if (buffer.length() == 0) {
+                error("Empty value");
+                // try to recover: just ignore the error
+            }
+            elem.addValue(buffer.toString());
+            buffer.setLength(0);
+        }
+
+        private void endParameterName() throws ParseException {
+            if (buffer.length() == 0) {
+                error("Empty parameter name");
+                // try to recover: won't store the value
+                paramName = null;
+            }
+            paramName = buffer.toString();
+            buffer.setLength(0);
+        }
+
+        private void parseSeparator() throws ParseException {
+            if (c == '=') {
+                isDirective = false;
+                return;
+            }
+            if (readNext() != '=') {
+                error("Expecting '='");
+                // try to recover: will ignore this parameter
+                pos--;
+                paramName = null;
+            }
+            isDirective = true;
+        }
+
+        private void parseParameterValue() throws ParseException {
+            // true if the value parsing has started, white spaces skipped
+            boolean start = false;
+            // true if the value parsing is ended, then only white spaces are allowed
+            boolean end = false;
+            boolean doubleQuoted = false;
+            do {
+                switch (readNext()) {
+                    case '\0':
+                        break;
+
+                    case ',':
+                    case ';':
+                        endParameterValue();
+                        return;
+                    case '=':
+                    case ':':
+                        error("Illegal character '" + c + "' in parameter value of " + paramName);
+                        // try to recover: ignore that parameter
+                        paramName = null;
+                        break;
+                    case '\"':
+                        doubleQuoted = true;
+                    case '\'':
+                        if (end && paramName != null) {
+                            error("Expecting the end of a parameter value");
+                            // try to recover: ignore that parameter
+                            paramName = null;
+                        }
+                        if (start) {
+                            // quote in the middle of the value, just add it as a quote
+                            buffer.append(c);
+                        } else {
+                            start = true;
+                            appendQuoted(doubleQuoted);
+                            end = true;
+                        }
+                        break;
+                    case '\\':
+                        if (end && paramName != null) {
+                            error("Expecting the end of a parameter value");
+                            // try to recover: ignore that parameter
+                            paramName = null;
+                        }
+                        start = true;
+                        appendEscaped();
+                        break;
+                    case ' ':
+                    case '\t':
+                    case '\n':
+                    case '\r':
+                        if (start) {
+                            end = true;
+                        }
+                        break;
+                    default:
+                        if (end && paramName != null) {
+                            error("Expecting the end of a parameter value");
+                            // try to recover: ignore that parameter
+                            paramName = null;
+                        }
+                        start = true;
+                        buffer.append(c);
+                }
+            } while (pos < length);
+            endParameterValue();
+        }
+
+        private void endParameterValue() throws ParseException {
+            if (paramName == null) {
+                // recovering from an incorrect parameter: skip the value
+                return;
+            }
+            if (buffer.length() == 0) {
+                error("Empty parameter value");
+                // try to recover: do not store the parameter
+                return;
+            }
+            String value = buffer.toString();
+            if (isDirective) {
+                elem.addDirective(paramName, value);
+            } else {
+                elem.addAttribute(paramName, value);
+            }
+            valuesParsed = true;
+            buffer.setLength(0);
+        }
+
+        private void appendQuoted(boolean doubleQuoted) {
+            do {
+                switch (readNext()) {
+                    case '\0':
+                        break;
+                    case '\"':
+                        if (doubleQuoted) {
+                            return;
+                        }
+                        buffer.append(c);
+                        break;
+                    case '\'':
+                        if (!doubleQuoted) {
+                            return;
+                        }
+                        buffer.append(c);
+                        break;
+                    case '\\':
+                        break;
+                    default:
+                        buffer.append(c);
+                }
+            } while (pos < length);
+        }
+
+        private void appendEscaped() {
+            if (pos < length) {
+                buffer.append(readNext());
+            } else {
+                buffer.append(c);
+            }
+        }
+    }
+
+    public boolean equals(Object obj) {
+        if (!(obj instanceof ManifestHeaderValue)) {
+            return false;
+        }
+        ManifestHeaderValue other = (ManifestHeaderValue) obj;
+        if (other.elements.size() != elements.size()) {
+            return false;
+        }
+        for (ManifestHeaderElement element : elements) {
+            if (!other.elements.contains(element)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public String toString() {
+        String string = "";
+        Iterator<ManifestHeaderElement> it = elements.iterator();
+        while (it.hasNext()) {
+            string = string.concat(it.next().toString());
+            if (it.hasNext()) {
+                string = string.concat(",");
+            }
+        }
+        return string;
+    }
+
+    public static void writeParseException(PrintStream out, String source, ParseException e) {
+        out.println(e.getMessage());
+        out.print("   " + source + "\n   ");
+        for (int i = 0; i < e.getErrorOffset(); i++) {
+            out.print(' ');
+        }
+        out.println('^');
+    }
+}
diff --git a/src/java/org/apache/ivy/osgi/core/ManifestParser.java b/src/java/org/apache/ivy/osgi/core/ManifestParser.java
new file mode 100644
index 0000000..2aec72f
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/core/ManifestParser.java
@@ -0,0 +1,302 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.core;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.text.ParseException;
+import java.util.List;
+import java.util.jar.Attributes;
+import java.util.jar.JarInputStream;
+import java.util.jar.Manifest;
+
+import org.apache.ivy.osgi.util.Version;
+import org.apache.ivy.osgi.util.VersionRange;
+
+/**
+ * Provides an OSGi manifest parser.
+ * 
+ */
+public class ManifestParser {
+
+    private static final String EXPORT_PACKAGE = "Export-Package";
+
+    private static final String IMPORT_PACKAGE = "Import-Package";
+
+    private static final String EXPORT_SERVICE = "Export-Service";
+
+    private static final String IMPORT_SERVICE = "Import-Service";
+
+    private static final String REQUIRE_BUNDLE = "Require-Bundle";
+
+    private static final String BUNDLE_VERSION = "Bundle-Version";
+
+    private static final String BUNDLE_NAME = "Bundle-Name";
+
+    private static final String BUNDLE_DESCRIPTION = "Bundle-Description";
+
+    private static final String BUNDLE_SYMBOLIC_NAME = "Bundle-SymbolicName";
+
+    private static final String BUNDLE_MANIFEST_VERSION = "Bundle-ManifestVersion";
+
+    private static final String BUNDLE_REQUIRED_EXECUTION_ENVIRONMENT = "Bundle-RequiredExecutionEnvironment";
+
+    private static final String BUNDLE_CLASSPATH = "Bundle-ClassPath";
+
+    private static final String ECLIPSE_SOURCE_BUNDLE = "Eclipse-SourceBundle";
+
+    private static final String ATTR_RESOLUTION = "resolution";
+
+    private static final String ATTR_VERSION = "version";
+
+    private static final String ATTR_BUNDLE_VERSION = "bundle-version";
+
+    private static final String ATTR_USE = "use";
+
+    public static BundleInfo parseJarManifest(InputStream jarStream) throws IOException,
+            ParseException {
+        JarInputStream jis = new JarInputStream(jarStream);
+        Manifest manifest = jis.getManifest();
+        if (manifest == null) {
+            return null;
+        }
+        BundleInfo bundleInfo = parseManifest(manifest);
+        return bundleInfo;
+    }
+
+    public static BundleInfo parseManifest(File manifestFile) throws IOException, ParseException {
+        FileInputStream fis = new FileInputStream(manifestFile);
+        try {
+            BundleInfo parseManifest = parseManifest(fis);
+            return parseManifest;
+        } finally {
+            try {
+                fis.close();
+            } catch (IOException e) {
+                // ignore
+            }
+        }
+    }
+
+    public static BundleInfo parseManifest(String manifest) throws IOException, ParseException {
+        ByteArrayInputStream bais = new ByteArrayInputStream(manifest.getBytes("UTF-8"));
+        BundleInfo parseManifest = parseManifest(bais);
+        bais.close();
+        return parseManifest;
+    }
+
+    public static BundleInfo parseManifest(InputStream manifestStream) throws IOException,
+            ParseException {
+        BundleInfo parseManifest = parseManifest(new Manifest(manifestStream));
+        return parseManifest;
+    }
+
+    public static BundleInfo parseManifest(Manifest manifest) throws ParseException {
+        Attributes mainAttributes = manifest.getMainAttributes();
+
+        // Eclipse source bundle doesn't have it. Disable it until proven actually useful
+        // String manifestVersion = mainAttributes.getValue(BUNDLE_MANIFEST_VERSION);
+        // if (manifestVersion == null) {
+        // // non OSGi manifest
+        // throw new ParseException("No " + BUNDLE_MANIFEST_VERSION + " in the manifest", 0);
+        // }
+
+        String symbolicName = new ManifestHeaderValue(mainAttributes.getValue(BUNDLE_SYMBOLIC_NAME))
+                .getSingleValue();
+        if (symbolicName == null) {
+            throw new ParseException("No " + BUNDLE_SYMBOLIC_NAME + " in the manifest", 0);
+        }
+
+        String description = new ManifestHeaderValue(mainAttributes.getValue(BUNDLE_DESCRIPTION))
+                .getSingleValue();
+        if (description == null) {
+            description = new ManifestHeaderValue(mainAttributes.getValue(BUNDLE_DESCRIPTION))
+                    .getSingleValue();
+        }
+
+        String vBundle = new ManifestHeaderValue(mainAttributes.getValue(BUNDLE_VERSION))
+                .getSingleValue();
+        Version version;
+        try {
+            version = versionOf(vBundle);
+        } catch (NumberFormatException e) {
+            throw new ParseException("The " + BUNDLE_VERSION + " has an incorrect version: "
+                    + vBundle + " (" + e.getMessage() + ")", 0);
+        }
+
+        BundleInfo bundleInfo = new BundleInfo(symbolicName, version);
+
+        bundleInfo.setDescription(description);
+
+        List<String> environments = new ManifestHeaderValue(
+                mainAttributes.getValue(BUNDLE_REQUIRED_EXECUTION_ENVIRONMENT)).getValues();
+        bundleInfo.setExecutionEnvironments(environments);
+
+        parseRequirement(bundleInfo, mainAttributes, REQUIRE_BUNDLE, BundleInfo.BUNDLE_TYPE,
+            ATTR_BUNDLE_VERSION);
+        parseRequirement(bundleInfo, mainAttributes, IMPORT_PACKAGE, BundleInfo.PACKAGE_TYPE,
+            ATTR_VERSION);
+        parseRequirement(bundleInfo, mainAttributes, IMPORT_SERVICE, BundleInfo.SERVICE_TYPE,
+            ATTR_VERSION);
+
+        ManifestHeaderValue exportElements = new ManifestHeaderValue(
+                mainAttributes.getValue(EXPORT_PACKAGE));
+        for (ManifestHeaderElement exportElement : exportElements.getElements()) {
+            String vExport = exportElement.getAttributes().get(ATTR_VERSION);
+            Version v = null;
+            try {
+                v = versionOf(vExport);
+            } catch (NumberFormatException e) {
+                throw new ParseException("The " + EXPORT_PACKAGE + " has an incorrect version: "
+                        + vExport + " (" + e.getMessage() + ")", 0);
+            }
+
+            for (String name : exportElement.getValues()) {
+                ExportPackage export = new ExportPackage(name, v);
+                String uses = exportElement.getDirectives().get(ATTR_USE);
+                if (uses != null) {
+                    String[] split = uses.trim().split(",");
+                    for (int i = 0; i < split.length; i++) {
+                        export.addUse(split[i].trim());
+                    }
+                }
+                bundleInfo.addCapability(export);
+            }
+        }
+
+        parseCapability(bundleInfo, mainAttributes, EXPORT_SERVICE, BundleInfo.SERVICE_TYPE);
+
+        // handle Eclipse specific source attachement
+        String eclipseSourceBundle = mainAttributes.getValue(ECLIPSE_SOURCE_BUNDLE);
+        if (eclipseSourceBundle != null) {
+            bundleInfo.setSource(true);
+            ManifestHeaderValue eclipseSourceBundleValue = new ManifestHeaderValue(
+                    eclipseSourceBundle);
+            ManifestHeaderElement element = eclipseSourceBundleValue.getElements().iterator()
+                    .next();
+            String symbolicNameTarget = element.getValues().iterator().next();
+            bundleInfo.setSymbolicNameTarget(symbolicNameTarget);
+            String v = element.getAttributes().get(ATTR_VERSION);
+            if (v != null) {
+                bundleInfo.setVersionTarget(new Version(v));
+            }
+        }
+
+        String bundleClasspath = mainAttributes.getValue(BUNDLE_CLASSPATH);
+        if (bundleClasspath != null) {
+            ManifestHeaderValue bundleClasspathValue = new ManifestHeaderValue(bundleClasspath);
+            bundleInfo.setClasspath(bundleClasspathValue.getValues());
+            bundleInfo.setHasInnerClasspath(true);
+        }
+
+        return bundleInfo;
+    }
+
+    private static void parseRequirement(BundleInfo bundleInfo, Attributes mainAttributes,
+            String headerName, String type, String versionAttr) throws ParseException {
+        ManifestHeaderValue elements = new ManifestHeaderValue(mainAttributes.getValue(headerName));
+        for (ManifestHeaderElement element : elements.getElements()) {
+            String resolution = element.getDirectives().get(ATTR_RESOLUTION);
+            String attVersion = element.getAttributes().get(versionAttr);
+            VersionRange version = null;
+            try {
+                version = versionRangeOf(attVersion);
+            } catch (ParseException e) {
+                throw new ParseException("The " + headerName + " has an incorrect version: "
+                        + attVersion + " (" + e.getMessage() + ")", 0);
+            }
+
+            for (String name : element.getValues()) {
+                bundleInfo.addRequirement(new BundleRequirement(type, name, version, resolution));
+            }
+        }
+    }
+
+    private static void parseCapability(BundleInfo bundleInfo, Attributes mainAttributes,
+            String headerName, String type) throws ParseException {
+        ManifestHeaderValue elements = new ManifestHeaderValue(mainAttributes.getValue(headerName));
+        for (ManifestHeaderElement element : elements.getElements()) {
+            String attVersion = element.getAttributes().get(ATTR_VERSION);
+            Version version = null;
+            try {
+                version = versionOf(attVersion);
+            } catch (NumberFormatException e) {
+                throw new ParseException("The " + headerName + " has an incorrect version: "
+                        + attVersion + " (" + e.getMessage() + ")", 0);
+            }
+
+            for (String name : element.getValues()) {
+                BundleCapability export = new BundleCapability(type, name, version);
+                bundleInfo.addCapability(export);
+            }
+        }
+
+    }
+
+    private static VersionRange versionRangeOf(String v) throws ParseException {
+        if (v == null) {
+            return null;
+        }
+        return new VersionRange(v);
+    }
+
+    private static Version versionOf(String v) throws ParseException {
+        if (v == null) {
+            return null;
+        }
+        return new Version(v);
+    }
+
+    /**
+     * Ensure that the lines are not longer than 72 characters, so it can be parsed by the
+     * {@link Manifest} class
+     * 
+     * @param manifest
+     * @return
+     */
+    public static String formatLines(String manifest) {
+        StringBuffer buffer = new StringBuffer(manifest.length());
+        String[] lines = manifest.split("\n");
+        for (int i = 0; i < lines.length; i++) {
+            if (lines[i].length() <= 72) {
+                buffer.append(lines[i]);
+                buffer.append('\n');
+            } else {
+                buffer.append(lines[i].substring(0, 72));
+                buffer.append("\n ");
+                int n = 72;
+                while (n <= lines[i].length() - 1) {
+                    int end = n + 71;
+                    if (end > lines[i].length()) {
+                        end = lines[i].length();
+                    }
+                    buffer.append(lines[i].substring(n, end));
+                    buffer.append('\n');
+                    if (end != lines[i].length()) {
+                        buffer.append(' ');
+                    }
+                    n = end;
+                }
+            }
+        }
+        return buffer.toString();
+    }
+}
diff --git a/src/java/org/apache/ivy/osgi/core/OSGiManifestParser.java b/src/java/org/apache/ivy/osgi/core/OSGiManifestParser.java
new file mode 100644
index 0000000..66cced1
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/core/OSGiManifestParser.java
@@ -0,0 +1,103 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.core;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.text.ParseException;
+import java.util.Date;
+import java.util.Locale;
+import java.util.jar.Manifest;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DefaultArtifact;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.plugins.parser.ModuleDescriptorParser;
+import org.apache.ivy.plugins.parser.ParserSettings;
+import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorWriter;
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.plugins.repository.url.URLResource;
+
+public class OSGiManifestParser implements ModuleDescriptorParser {
+
+    private static final OSGiManifestParser INSTANCE = new OSGiManifestParser();
+
+    public static OSGiManifestParser getInstance() {
+        return INSTANCE;
+    }
+
+    private ExecutionEnvironmentProfileProvider profileProvider = ExecutionEnvironmentProfileProvider
+            .getInstance();
+
+    public void add(ExecutionEnvironmentProfileProvider pp) {
+        this.profileProvider = pp;
+    }
+
+    public boolean accept(Resource res) {
+        if (res == null || res.getName() == null || res.getName().trim().equals("")) {
+            return false;
+        }
+        return res.getName().toUpperCase(Locale.US).endsWith("MANIFEST.MF");
+    }
+
+    public ModuleDescriptor parseDescriptor(ParserSettings ivySettings, URL descriptorURL,
+            Resource res, boolean validate) throws ParseException, IOException {
+        Manifest m = new Manifest(res.openStream());
+        BundleInfo bundleInfo = ManifestParser.parseManifest(m);
+        try {
+            bundleInfo.addArtifact(new BundleArtifact(false, new URI(res.getName()), null));
+        } catch (URISyntaxException e) {
+            throw new RuntimeException("Unsupported repository, resources names are not uris", e);
+        }
+        return BundleInfoAdapter.toModuleDescriptor(this, null, bundleInfo, m, profileProvider);
+    }
+
+    public void toIvyFile(InputStream is, Resource res, File destFile, ModuleDescriptor md)
+            throws ParseException, IOException {
+        try {
+            XmlModuleDescriptorWriter.write(md, destFile);
+        } finally {
+            if (is != null) {
+                is.close();
+            }
+        }
+    }
+
+    public ModuleDescriptor parseDescriptor(ParserSettings ivySettings, URL descriptorURL,
+            boolean validate) throws ParseException, IOException {
+        URLResource resource = new URLResource(descriptorURL);
+        return parseDescriptor(ivySettings, descriptorURL, resource, validate);
+    }
+
+    public String getType() {
+        return "manifest";
+    }
+
+    public Artifact getMetadataArtifact(ModuleRevisionId mrid, Resource res) {
+        return DefaultArtifact.newIvyArtifact(mrid, new Date(res.getLastModified()));
+    }
+
+    public String toString() {
+        return "manifest parser";
+    }
+}
diff --git a/src/java/org/apache/ivy/osgi/core/OsgiLatestStrategy.java b/src/java/org/apache/ivy/osgi/core/OsgiLatestStrategy.java
new file mode 100644
index 0000000..e160fce
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/core/OsgiLatestStrategy.java
@@ -0,0 +1,96 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.core;
+
+import java.text.ParseException;
+import java.util.Comparator;
+
+import org.apache.ivy.core.IvyContext;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.osgi.util.Version;
+import org.apache.ivy.plugins.latest.ArtifactInfo;
+import org.apache.ivy.plugins.latest.ComparatorLatestStrategy;
+import org.apache.ivy.plugins.version.VersionMatcher;
+
+public class OsgiLatestStrategy extends ComparatorLatestStrategy {
+
+    final class MridComparator implements Comparator<ModuleRevisionId> {
+
+        public int compare(ModuleRevisionId o1, ModuleRevisionId o2) {
+            Version v1;
+            Version v2;
+            try {
+                v1 = new Version(o1.getRevision());
+                v2 = new Version(o2.getRevision());
+            } catch (ParseException e) {
+                throw new RuntimeException("Uncomparable versions:" + o1.getRevision() + " and "
+                        + o2.getRevision() + " (" + e.getMessage() + ")");
+            }
+            try {
+                return v1.compareTo(v2);
+            } catch (RuntimeException e) {
+                if (e.getCause() instanceof ParseException) {
+                    throw new RuntimeException("Uncomparable versions:" + o1.getRevision()
+                            + " and " + o2.getRevision() + " (" + e.getMessage() + ")");
+                }
+                throw e;
+            }
+        }
+
+    }
+
+    final class ArtifactInfoComparator implements Comparator<ArtifactInfo> {
+
+        public int compare(ArtifactInfo o1, ArtifactInfo o2) {
+            String rev1 = o1.getRevision();
+            String rev2 = o2.getRevision();
+
+            /*
+             * The revisions can still be not resolved, so we use the current version matcher to
+             * know if one revision is dynamic, and in this case if it should be considered greater
+             * or lower than the other one. Note that if the version matcher compare method returns
+             * 0, it's because it's not possible to know which revision is greater. In this case we
+             * consider the dynamic one to be greater, because most of the time it will then be
+             * actually resolved and a real comparison will occur.
+             */
+            VersionMatcher vmatcher = IvyContext.getContext().getSettings().getVersionMatcher();
+            ModuleRevisionId mrid1 = ModuleRevisionId.newInstance("", "", rev1);
+            ModuleRevisionId mrid2 = ModuleRevisionId.newInstance("", "", rev2);
+
+            if (vmatcher.isDynamic(mrid1)) {
+                int c = vmatcher.compare(mrid1, mrid2, mridComparator);
+                return c >= 0 ? 1 : -1;
+            } else if (vmatcher.isDynamic(mrid2)) {
+                int c = vmatcher.compare(mrid2, mrid1, mridComparator);
+                return c >= 0 ? -1 : 1;
+            }
+
+            return mridComparator.compare(mrid1, mrid2);
+        }
+    }
+
+    private final Comparator<ModuleRevisionId> mridComparator = new MridComparator();
+
+    private final Comparator<ArtifactInfo> artifactInfoComparator = new ArtifactInfoComparator();
+
+    public OsgiLatestStrategy() {
+        setComparator(artifactInfoComparator);
+        setName("latest-osgi");
+    }
+
+}
diff --git a/src/java/org/apache/ivy/osgi/core/jvm-packages.properties b/src/java/org/apache/ivy/osgi/core/jvm-packages.properties
new file mode 100644
index 0000000..4aa4ec1
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/core/jvm-packages.properties
@@ -0,0 +1,220 @@
+#	 ***************************************************************
+#	 * Licensed to the Apache Software Foundation (ASF) under one
+#	 * or more contributor license agreements.  See the NOTICE file
+#	 * distributed with this work for additional information
+#	 * regarding copyright ownership.  The ASF licenses this file
+#	 * to you under the Apache License, Version 2.0 (the
+#	 * "License"); you may not use this file except in compliance
+#	 * with the License.  You may obtain a copy of the License at
+#	 * 
+#	 *   http://www.apache.org/licenses/LICENSE-2.0
+#	 * 
+#	 * Unless required by applicable law or agreed to in writing,
+#	 * software distributed under the License is distributed on an
+#	 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#	 * KIND, either express or implied.  See the License for the
+#	 * specific language governing permissions and limitations
+#	 * under the License.
+#	 ***************************************************************
+
+OSGi/Minimum-1.0.aliases = OSGI_MINIMUM-1.0
+OSGi/Minimum-1.0.pkglist = 
+
+OSGi/Minimum-1.1.aliases = OSGI_MINIMUM-1.1
+OSGi/Minimum-1.1.pkglist = 
+
+OSGi/Minimum-1.2.aliases = OSGI_MINIMUM-1.2
+OSGi/Minimum-1.2.pkglist = 
+
+CDC-1.0/Foundation-1.0.aliases = CDC-1.0_Foundation-1.0 
+CDC-1.0/Foundation-1.0.pkglist = \
+ javax.microedition.io
+
+CDC-1.1/Foundation-1.1.aliases = CDC-1.1_Foundation-1.1
+CDC-1.1/Foundation-1.1.extends = CDC-1.0/Foundation-1.0
+CDC-1.1/Foundation-1.1.pkglist = \
+ javax.microedition.pki,\
+ javax.security.auth.x500
+
+JavaSE-1.2.aliases = J2SE-1.2
+JavaSE-1.2.pkglist = \
+ javax.accessibility,\
+ javax.swing,\
+ javax.swing.border,\
+ javax.swing.colorchooser,\
+ javax.swing.event,\
+ javax.swing.filechooser,\
+ javax.swing.plaf,\
+ javax.swing.plaf.basic,\
+ javax.swing.plaf.metal,\
+ javax.swing.plaf.multi,\
+ javax.swing.table,\
+ javax.swing.text,\
+ javax.swing.text.html,\
+ javax.swing.text.html.parser,\
+ javax.swing.text.rtf,\
+ javax.swing.tree,\
+ javax.swing.undo,\
+ org.omg.CORBA,\
+ org.omg.CORBA.DynAnyPackage,\
+ org.omg.CORBA.ORBPackage,\
+ org.omg.CORBA.portable,\
+ org.omg.CORBA.TypeCodePackage,\
+ org.omg.CosNaming,\
+ org.omg.CosNaming.NamingContextPackage
+
+JavaSE-1.3.aliases = J2SE-1.3
+JavaSE-1.3.extends = JavaSE-1.2
+JavaSE-1.3.pkglist = \
+ javax.naming,\
+ javax.naming.directory,\
+ javax.naming.event,\
+ javax.naming.ldap,\
+ javax.naming.spi,\
+ javax.rmi,\
+ javax.rmi.CORBA,\
+ javax.sound.midi,\
+ javax.sound.midi.spi,\
+ javax.sound.sampled,\
+ javax.sound.sampled.spi,\
+ javax.transaction,\
+ org.omg.CORBA_2_3,\
+ org.omg.CORBA_2_3.portable,\
+ org.omg.SendingContext,\
+ org.omg.stub.java.rmi
+
+JavaSE-1.4.aliases = J2SE-1.4
+JavaSE-1.4.extends = JavaSE-1.3
+JavaSE-1.4.pkglist = \
+ javax.crypto,\
+ javax.crypto.interfaces,\
+ javax.crypto.spec,\
+ javax.imageio,\
+ javax.imageio.event,\
+ javax.imageio.metadata,\
+ javax.imageio.plugins.jpeg,\
+ javax.imageio.spi,\
+ javax.imageio.stream,\
+ javax.net,\
+ javax.net.ssl,\
+ javax.print,\
+ javax.print.attribute,\
+ javax.print.attribute.standard,\
+ javax.print.event,\
+ javax.security.auth,\
+ javax.security.auth.callback,\
+ javax.security.auth.kerberos,\
+ javax.security.auth.login,\
+ javax.security.auth.spi,\
+ javax.security.auth.x500,\
+ javax.security.cert,\
+ javax.sql,\
+ javax.transaction.xa,\
+ javax.xml.parsers,\
+ javax.xml.transform,\
+ javax.xml.transform.dom,\
+ javax.xml.transform.sax,\
+ javax.xml.transform.stream,\
+ org.ietf.jgss,\
+ org.omg.CosNaming.NamingContextExtPackage,\
+ org.omg.Dynamic,\
+ org.omg.DynamicAny,\
+ org.omg.DynamicAny.DynAnyFactoryPackage,\
+ org.omg.DynamicAny.DynAnyPackage,\
+ org.omg.IOP,\
+ org.omg.IOP.CodecFactoryPackage,\
+ org.omg.IOP.CodecPackage,\
+ org.omg.Messaging,\
+ org.omg.PortableInterceptor,\
+ org.omg.PortableInterceptor.ORBInitInfoPackage,\
+ org.omg.PortableServer,\
+ org.omg.PortableServer.CurrentPackage,\
+ org.omg.PortableServer.POAManagerPackage,\
+ org.omg.PortableServer.POAPackage,\
+ org.omg.PortableServer.portable,\
+ org.omg.PortableServer.ServantLocatorPackage,\
+ org.w3c.dom,\
+ org.w3c.dom.css,\
+ org.w3c.dom.events,\
+ org.w3c.dom.html,\
+ org.w3c.dom.stylesheets,\
+ org.w3c.dom.views,\
+ org.xml.sax,\
+ org.xml.sax.ext,\
+ org.xml.sax.helpers
+
+JavaSE-1.5.aliases = J2SE-1.5
+JavaSE-1.5.extends = JavaSE-1.4
+JavaSE-1.5.pkglist = \
+ javax.activity,\
+ javax.imageio.plugins.bmp,\
+ javax.management,\
+ javax.management.loading,\
+ javax.management.modelmbean,\
+ javax.management.monitor,\
+ javax.management.openmbean,\
+ javax.management.relation,\
+ javax.management.remote,\
+ javax.management.remote.rmi,\
+ javax.management.timer,\
+ javax.rmi.ssl,\
+ javax.security.sasl,\
+ javax.sql.rowset,\
+ javax.sql.rowset.serial,\
+ javax.sql.rowset.spi,\
+ javax.swing.plaf.synth,\
+ javax.xml,\
+ javax.xml.datatype,\
+ javax.xml.namespace,\
+ javax.xml.validation,\
+ javax.xml.xpath,\
+ org.w3c.dom.bootstrap,\
+ org.w3c.dom.ls,\
+ org.w3c.dom.ranges,\
+ org.w3c.dom.traversal
+
+JavaSE-1.6.extends = JavaSE-1.5
+JavaSE-1.6.pkglist = \
+ javax.activation,\
+ javax.annotation,\
+ javax.annotation.processing,\
+ javax.jws,\
+ javax.jws.soap,\
+ javax.lang.model,\
+ javax.lang.model.element,\
+ javax.lang.model.type,\
+ javax.lang.model.util,\
+ javax.script,\
+ javax.tools,\
+ javax.xml.bind,\
+ javax.xml.bind.annotation,\
+ javax.xml.bind.annotation.adapters,\
+ javax.xml.bind.attachment,\
+ javax.xml.bind.helpers,\
+ javax.xml.bind.util,\
+ javax.xml.crypto,\
+ javax.xml.crypto.dom,\
+ javax.xml.crypto.dsig,\
+ javax.xml.crypto.dsig.dom,\
+ javax.xml.crypto.dsig.keyinfo,\
+ javax.xml.crypto.dsig.spec,\
+ javax.xml.soap,\
+ javax.xml.stream,\
+ javax.xml.stream.events,\
+ javax.xml.stream.util,\
+ javax.xml.transform.stax,\
+ javax.xml.ws,\
+ javax.xml.ws.handler,\
+ javax.xml.ws.handler.soap,\
+ javax.xml.ws.http,\
+ javax.xml.ws.soap,\
+ javax.xml.ws.spi,\
+ javax.xml.ws.spi.http,\
+ javax.xml.ws.wsaddressing
+
+JavaSE-1.7.extends = JavaSE-1.6
+JavaSE-1.7.pkglist = \
+ javax.swing.plaf.nimbus
+ 
+JavaSE-1.8.extends = JavaSE-1.7
+JavaSE-1.8.pkglist =
diff --git a/src/java/org/apache/ivy/osgi/filter/AndFilter.java b/src/java/org/apache/ivy/osgi/filter/AndFilter.java
new file mode 100644
index 0000000..7fe700e
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/filter/AndFilter.java
@@ -0,0 +1,45 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.filter;
+
+import java.util.Map;
+
+public class AndFilter extends MultiOperatorFilter {
+
+    public AndFilter() {
+        super();
+    }
+
+    public AndFilter(OSGiFilter[] filters) {
+        super(filters);
+    }
+
+    protected char operator() {
+        return '&';
+    }
+
+    @Override
+    public boolean eval(Map<String, String> properties) {
+        for (OSGiFilter filter : getSubFilters()) {
+            if (!filter.eval(properties)) {
+                return false;
+            }
+        }
+        return true;
+    }
+}
diff --git a/src/java/org/apache/ivy/osgi/filter/CompareFilter.java b/src/java/org/apache/ivy/osgi/filter/CompareFilter.java
new file mode 100644
index 0000000..cc8f564
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/filter/CompareFilter.java
@@ -0,0 +1,138 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.filter;
+
+import java.util.Map;
+
+public class CompareFilter extends OSGiFilter {
+
+    public static enum Operator {
+
+        EQUALS("="), LOWER_THAN("<"), LOWER_OR_EQUAL("<="), GREATER_THAN(">"), GREATER_OR_EQUAL(
+                ">=");
+
+        private String op;
+
+        private Operator(String op) {
+            this.op = op;
+        }
+
+        public String toString() {
+            return op;
+        }
+    }
+
+    private Operator operator;
+
+    private final String rightValue;
+
+    private final String leftValue;
+
+    public CompareFilter(String leftValue, Operator operator, String rightValue) {
+        this.leftValue = leftValue;
+        this.rightValue = rightValue;
+        this.operator = operator;
+    }
+
+    public String getLeftValue() {
+        return leftValue;
+    }
+
+    public Operator getOperator() {
+        return operator;
+    }
+
+    public String getRightValue() {
+        return rightValue;
+    }
+
+    public void append(StringBuffer builder) {
+        builder.append("(");
+        builder.append(leftValue);
+        builder.append(operator.toString());
+        builder.append(rightValue);
+        builder.append(")");
+    }
+
+    @Override
+    public boolean eval(Map<String, String> properties) {
+        String actualValue = properties.get(leftValue);
+        if (actualValue == null) {
+            return false;
+        }
+        int diff = rightValue.compareTo(actualValue);
+        switch (operator) {
+            case EQUALS:
+                return diff == 0;
+            case GREATER_THAN:
+                return diff > 0;
+            case GREATER_OR_EQUAL:
+                return diff >= 0;
+            case LOWER_OR_EQUAL:
+                return diff <= 0;
+            case LOWER_THAN:
+                return diff < 0;
+            default:
+                throw new IllegalStateException();
+        }
+    }
+
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((leftValue == null) ? 0 : leftValue.hashCode());
+        result = prime * result + ((operator == null) ? 0 : operator.hashCode());
+        result = prime * result + ((rightValue == null) ? 0 : rightValue.hashCode());
+        return result;
+    }
+
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (!(obj instanceof CompareFilter)) {
+            return false;
+        }
+        CompareFilter other = (CompareFilter) obj;
+        if (leftValue == null) {
+            if (other.leftValue != null) {
+                return false;
+            }
+        } else if (!leftValue.equals(other.leftValue)) {
+            return false;
+        }
+        if (operator == null) {
+            if (other.operator != null) {
+                return false;
+            }
+        } else if (!operator.equals(other.operator)) {
+            return false;
+        }
+        if (rightValue == null) {
+            if (other.rightValue != null) {
+                return false;
+            }
+        } else if (!rightValue.equals(other.rightValue)) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/src/java/org/apache/ivy/osgi/filter/MultiOperatorFilter.java b/src/java/org/apache/ivy/osgi/filter/MultiOperatorFilter.java
new file mode 100644
index 0000000..f407c05
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/filter/MultiOperatorFilter.java
@@ -0,0 +1,90 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.filter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public abstract class MultiOperatorFilter extends OSGiFilter {
+
+    private List<OSGiFilter> subFilters = new ArrayList<OSGiFilter>();
+
+    public MultiOperatorFilter() {
+        // default constructor
+    }
+
+    public MultiOperatorFilter(OSGiFilter[] filters) {
+        for (int i = 0; i < filters.length; i++) {
+            OSGiFilter filter = filters[i];
+            add(filter);
+        }
+    }
+
+    abstract protected char operator();
+
+    public void append(StringBuffer builder) {
+        builder.append('(');
+        builder.append(operator());
+        for (OSGiFilter filter : subFilters) {
+            filter.append(builder);
+        }
+        builder.append(')');
+    }
+
+    public void add(OSGiFilter subFilter2) {
+        subFilters.add(subFilter2);
+    }
+
+    public List<OSGiFilter> getSubFilters() {
+        return subFilters;
+    }
+
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        for (OSGiFilter subFilter : subFilters) {
+            result = prime * result + ((subFilter == null) ? 0 : subFilter.hashCode());
+        }
+        return result;
+    }
+
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (!(obj instanceof MultiOperatorFilter)) {
+            return false;
+        }
+        MultiOperatorFilter other = (MultiOperatorFilter) obj;
+        if (subFilters == null) {
+            if (other.subFilters != null) {
+                return false;
+            }
+        } else if (other.subFilters == null) {
+            return false;
+        } else if (subFilters.size() != other.subFilters.size()) {
+            return false;
+        } else if (!subFilters.containsAll(other.subFilters)) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/src/java/org/apache/ivy/osgi/filter/NotFilter.java b/src/java/org/apache/ivy/osgi/filter/NotFilter.java
new file mode 100644
index 0000000..fce5cd8
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/filter/NotFilter.java
@@ -0,0 +1,36 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.filter;
+
+import java.util.Map;
+
+public class NotFilter extends UniOperatorFilter {
+
+    public NotFilter(OSGiFilter subFilter) {
+        super(subFilter);
+    }
+
+    protected char operator() {
+        return '!';
+    }
+
+    @Override
+    public boolean eval(Map<String, String> properties) {
+        return !getSubFilter().eval(properties);
+    }
+}
diff --git a/src/java/org/apache/ivy/osgi/filter/OSGiFilter.java b/src/java/org/apache/ivy/osgi/filter/OSGiFilter.java
new file mode 100644
index 0000000..e406755
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/filter/OSGiFilter.java
@@ -0,0 +1,34 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.filter;
+
+import java.util.Map;
+
+public abstract class OSGiFilter {
+
+    public String toString() {
+        StringBuffer builder = new StringBuffer();
+        append(builder);
+        return builder.toString();
+    }
+
+    public abstract void append(StringBuffer builder);
+
+    public abstract boolean eval(Map<String, String> properties);
+
+}
diff --git a/src/java/org/apache/ivy/osgi/filter/OSGiFilterParser.java b/src/java/org/apache/ivy/osgi/filter/OSGiFilterParser.java
new file mode 100644
index 0000000..cd3d6e2
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/filter/OSGiFilterParser.java
@@ -0,0 +1,217 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.filter;
+
+import java.text.ParseException;
+
+import org.apache.ivy.osgi.filter.CompareFilter.Operator;
+
+public class OSGiFilterParser {
+
+    public static OSGiFilter parse(String text) throws ParseException {
+        return new Parser(text).parse();
+    }
+
+    static class Parser {
+
+        /**
+         * text to parse
+         */
+        private final String text;
+
+        /**
+         * the length of the source
+         */
+        private int length;
+
+        /**
+         * position in the source
+         */
+        private int pos = 0;
+
+        /**
+         * last read character
+         */
+        private char c;
+
+        /**
+         * Default constructor
+         * 
+         * @param header
+         *            the header to parse
+         */
+        Parser(String text) {
+            this.text = text;
+            this.length = text.length();
+        }
+
+        /**
+         * Do the parsing
+         * 
+         * @return
+         * 
+         * @throws ParseException
+         */
+        OSGiFilter parse() throws ParseException {
+            return parseFilter();
+        }
+
+        private char readNext() {
+            if (pos == length) {
+                c = '\0';
+            } else {
+                c = text.charAt(pos++);
+            }
+            return c;
+        }
+
+        private void unread() {
+            if (pos > 0) {
+                pos--;
+            }
+        }
+
+        private OSGiFilter parseFilter() throws ParseException {
+            skipWhiteSpace();
+            readNext();
+            if (c != '(') {
+                throw new ParseException("Expecting '(' as the start of the filter", pos);
+            }
+            OSGiFilter filter;
+            switch (readNext()) {
+                case '&':
+                    filter = parseAnd();
+                    break;
+                case '|':
+                    filter = parseOr();
+                    break;
+                case '!':
+                    filter = parseNot();
+                    break;
+                default:
+                    unread();
+                    filter = parseCompare();
+                    break;
+            }
+            readNext();
+            if (c != ')') {
+                throw new ParseException("Expecting ')' as the end of the filter", pos);
+            }
+            return filter;
+        }
+
+        private OSGiFilter parseCompare() throws ParseException {
+            String leftValue = parseCompareValue();
+            Operator operator = parseCompareOperator();
+            String rightValue = parseCompareValue();
+            return new CompareFilter(leftValue, operator, rightValue);
+        }
+
+        private String parseCompareValue() {
+            StringBuffer builder = new StringBuffer();
+            do {
+                readNext();
+                if (!isOperator(c) && c != ')' && c != '(') {
+                    builder.append(c);
+                } else {
+                    unread();
+                    break;
+                }
+            } while (pos < length);
+            return builder.toString();
+        }
+
+        private boolean isOperator(char ch) {
+            return ch == '=' || ch == '<' || ch == '>';
+        }
+
+        private Operator parseCompareOperator() throws ParseException {
+            switch (readNext()) {
+                case '=':
+                    return Operator.EQUALS;
+                case '>':
+                    if (readNext() == '=') {
+                        return Operator.GREATER_OR_EQUAL;
+                    }
+                    unread();
+                    return Operator.GREATER_THAN;
+                case '<':
+                    if (readNext() == '=') {
+                        return Operator.LOWER_OR_EQUAL;
+                    }
+                    unread();
+                    return Operator.LOWER_THAN;
+                default:
+                    break;
+            }
+            throw new ParseException("Expecting an operator: =, <, <=, > or >=", pos);
+        }
+
+        private OSGiFilter parseAnd() throws ParseException {
+            AndFilter filter = new AndFilter();
+            parseMultiOperator(filter);
+            return filter;
+        }
+
+        private OSGiFilter parseOr() throws ParseException {
+            OrFilter filter = new OrFilter();
+            parseMultiOperator(filter);
+            return filter;
+        }
+
+        private void parseMultiOperator(MultiOperatorFilter filter) throws ParseException {
+            do {
+                skipWhiteSpace();
+                readNext();
+                if (c == '(') {
+                    unread();
+                    filter.add(parseFilter());
+                } else {
+                    unread();
+                    break;
+                }
+            } while (pos < length);
+            if (filter.getSubFilters().size() == 0) {
+                throw new ParseException("Expecting at least one sub filter", pos);
+            }
+        }
+
+        private OSGiFilter parseNot() throws ParseException {
+            readNext();
+            if (c != '(') {
+                throw new ParseException("The ! operator is expecting a filter", pos);
+            }
+            unread();
+            return new NotFilter(parseFilter());
+        }
+
+        private void skipWhiteSpace() {
+            do {
+                switch (readNext()) {
+                    case ' ':
+                        continue;
+                    default:
+                        unread();
+                        return;
+                }
+            } while (pos < length);
+        }
+
+    }
+
+}
diff --git a/src/java/org/apache/ivy/osgi/filter/OrFilter.java b/src/java/org/apache/ivy/osgi/filter/OrFilter.java
new file mode 100644
index 0000000..ef8b126
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/filter/OrFilter.java
@@ -0,0 +1,45 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.filter;
+
+import java.util.Map;
+
+public class OrFilter extends MultiOperatorFilter {
+
+    public OrFilter() {
+        super();
+    }
+
+    public OrFilter(OSGiFilter[] filters) {
+        super(filters);
+    }
+
+    protected char operator() {
+        return '|';
+    }
+
+    @Override
+    public boolean eval(Map<String, String> properties) {
+        for (OSGiFilter filter : getSubFilters()) {
+            if (filter.eval(properties)) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/src/java/org/apache/ivy/osgi/filter/UniOperatorFilter.java b/src/java/org/apache/ivy/osgi/filter/UniOperatorFilter.java
new file mode 100644
index 0000000..02e7ef5
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/filter/UniOperatorFilter.java
@@ -0,0 +1,68 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.filter;
+
+public abstract class UniOperatorFilter extends OSGiFilter {
+
+    private final OSGiFilter subFilter;
+
+    public UniOperatorFilter(OSGiFilter subFilter) {
+        this.subFilter = subFilter;
+    }
+
+    abstract protected char operator();
+
+    public void append(StringBuffer builder) {
+        builder.append("(");
+        builder.append(operator());
+        builder.append(subFilter.toString());
+        builder.append(")");
+    }
+
+    public OSGiFilter getSubFilter() {
+        return subFilter;
+    }
+
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((subFilter == null) ? 0 : subFilter.hashCode());
+        return result;
+    }
+
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (!(obj instanceof UniOperatorFilter)) {
+            return false;
+        }
+        UniOperatorFilter other = (UniOperatorFilter) obj;
+        if (subFilter == null) {
+            if (other.subFilter != null) {
+                return false;
+            }
+        } else if (!subFilter.equals(other.subFilter)) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/src/java/org/apache/ivy/osgi/obr/OBRResolver.java b/src/java/org/apache/ivy/osgi/obr/OBRResolver.java
new file mode 100644
index 0000000..581be3c
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/obr/OBRResolver.java
@@ -0,0 +1,147 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.obr;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.text.ParseException;
+
+import org.apache.ivy.core.cache.CacheResourceOptions;
+import org.apache.ivy.core.event.EventManager;
+import org.apache.ivy.core.report.ArtifactDownloadReport;
+import org.apache.ivy.osgi.obr.xml.OBRXMLParser;
+import org.apache.ivy.osgi.repo.AbstractOSGiResolver;
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.plugins.repository.url.URLResource;
+import org.xml.sax.SAXException;
+
+public class OBRResolver extends AbstractOSGiResolver {
+
+    private String repoXmlURL;
+
+    private String repoXmlFile;
+
+    private Long metadataTtl;
+
+    private Boolean forceMetadataUpdate;
+
+    public void setRepoXmlFile(String repositoryXmlFile) {
+        this.repoXmlFile = repositoryXmlFile;
+    }
+
+    public void setRepoXmlURL(String repositoryXmlURL) {
+        this.repoXmlURL = repositoryXmlURL;
+    }
+
+    public void setMetadataTtl(Long metadataTtl) {
+        this.metadataTtl = metadataTtl;
+    }
+
+    public void setForceMetadataUpdate(Boolean forceMetadataUpdate) {
+        this.forceMetadataUpdate = forceMetadataUpdate;
+    }
+
+    protected void init() {
+        if (repoXmlFile != null && repoXmlURL != null) {
+            throw new RuntimeException("The OBR repository resolver " + getName()
+                    + " couldn't be configured: repoXmlFile and repoXmlUrl cannot be set both");
+        }
+        if (repoXmlFile != null) {
+            File f = new File(repoXmlFile);
+            loadRepoFromFile(f.getParentFile().toURI(), f, repoXmlFile);
+        } else if (repoXmlURL != null) {
+            final URL url;
+            try {
+                url = new URL(repoXmlURL);
+            } catch (MalformedURLException e) {
+                throw new RuntimeException("The OBR repository resolver " + getName()
+                        + " couldn't be configured: repoXmlURL '" + repoXmlURL + "' is not an URL");
+            }
+
+            ArtifactDownloadReport report;
+            EventManager eventManager = getEventManager();
+            try {
+                if (eventManager != null) {
+                    getRepository().addTransferListener(eventManager);
+                }
+                Resource obrResource = new URLResource(url);
+                CacheResourceOptions options = new CacheResourceOptions();
+                if (metadataTtl != null) {
+                    options.setTtl(metadataTtl.longValue());
+                }
+                if (forceMetadataUpdate != null) {
+                    options.setForce(forceMetadataUpdate.booleanValue());
+                }
+                report = getRepositoryCacheManager().downloadRepositoryResource(obrResource, "obr",
+                    "obr", "xml", options, getRepository());
+            } finally {
+                if (eventManager != null) {
+                    getRepository().removeTransferListener(eventManager);
+                }
+            }
+
+            URI baseURI;
+            try {
+                baseURI = new URI(repoXmlURL);
+            } catch (URISyntaxException e) {
+                throw new RuntimeException("illegal uri");
+            }
+            loadRepoFromFile(baseURI, report.getLocalFile(), repoXmlURL);
+
+        } else {
+            throw new RuntimeException("The OBR repository resolver " + getName()
+                    + " couldn't be configured: repoXmlFile or repoXmlUrl is missing");
+        }
+    }
+
+    private void loadRepoFromFile(URI baseUri, File repoFile, String sourceLocation) {
+        FileInputStream in;
+        try {
+            in = new FileInputStream(repoFile);
+        } catch (FileNotFoundException e) {
+            throw new RuntimeException("The OBR repository resolver " + getName()
+                    + " couldn't be configured: the file " + sourceLocation + " was not found");
+        }
+        try {
+            setRepoDescriptor(OBRXMLParser.parse(baseUri, in));
+        } catch (ParseException e) {
+            throw new RuntimeException("The OBR repository resolver " + getName()
+                    + " couldn't be configured: the file " + sourceLocation
+                    + " is incorrectly formed (" + e.getMessage() + ")", e);
+        } catch (IOException e) {
+            throw new RuntimeException("The OBR repository resolver " + getName()
+                    + " couldn't be configured: the file " + sourceLocation
+                    + " could not be read (" + e.getMessage() + ")", e);
+        } catch (SAXException e) {
+            throw new RuntimeException("The OBR repository resolver " + getName()
+                    + " couldn't be configured: the file " + sourceLocation
+                    + " has incorrect XML (" + e.getMessage() + ")", e);
+        }
+        try {
+            in.close();
+        } catch (IOException e) {
+            // don't care
+        }
+    }
+}
diff --git a/src/java/org/apache/ivy/osgi/obr/xml/Capability.java b/src/java/org/apache/ivy/osgi/obr/xml/Capability.java
new file mode 100644
index 0000000..4712b94
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/obr/xml/Capability.java
@@ -0,0 +1,55 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.obr.xml;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Capability {
+
+    private List<CapabilityProperty> properties = new ArrayList<CapabilityProperty>();
+
+    private String name;
+
+    public Capability(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void addProperty(String n, String value, String type) {
+        properties.add(new CapabilityProperty(n, value, type));
+    }
+
+    public List<CapabilityProperty> getProperties() {
+        return properties;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder buffer = new StringBuilder();
+        buffer.append(name);
+        for (CapabilityProperty p : properties) {
+            buffer.append(" ");
+            buffer.append(p);
+        }
+        return buffer.toString();
+    }
+}
diff --git a/src/java/org/apache/ivy/osgi/obr/xml/CapabilityAdapter.java b/src/java/org/apache/ivy/osgi/obr/xml/CapabilityAdapter.java
new file mode 100644
index 0000000..2df1f11
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/obr/xml/CapabilityAdapter.java
@@ -0,0 +1,103 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.obr.xml;
+
+import java.text.ParseException;
+
+import org.apache.ivy.osgi.core.BundleCapability;
+import org.apache.ivy.osgi.core.BundleInfo;
+import org.apache.ivy.osgi.core.ExportPackage;
+import org.apache.ivy.osgi.util.Version;
+import org.apache.ivy.util.Message;
+
+public class CapabilityAdapter {
+
+    public static void adapt(BundleInfo bundleInfo, Capability capability) throws ParseException {
+        String name = capability.getName();
+        if (BundleInfo.PACKAGE_TYPE.equals(name)) {
+            ExportPackage exportPackage = getExportPackage(bundleInfo, capability);
+            bundleInfo.addCapability(exportPackage);
+        } else if (BundleInfo.BUNDLE_TYPE.equals(name)) {
+            // nothing to do, already handled at the resource tag level
+        } else if (BundleInfo.SERVICE_TYPE.equals(name)) {
+            BundleCapability service = getOSGiService(bundleInfo, capability);
+            bundleInfo.addCapability(service);
+        } else {
+            Message.warn("Unsupported capability '" + name + "' on the bundle '"
+                    + bundleInfo.getSymbolicName() + "'");
+        }
+    }
+
+    private static ExportPackage getExportPackage(BundleInfo bundleInfo, Capability capability)
+            throws ParseException {
+        String pkgName = null;
+        Version version = null;
+        String uses = null;
+        for (CapabilityProperty property : capability.getProperties()) {
+            String propName = property.getName();
+            if ("package".equals(propName)) {
+                pkgName = property.getValue();
+            } else if ("version".equals(propName)) {
+                version = new Version(property.getValue());
+            } else if ("uses".equals(propName)) {
+                uses = property.getValue();
+            } else {
+                Message.warn("Unsupported property '" + propName
+                        + "' on the 'package' capability of the bundle '"
+                        + bundleInfo.getSymbolicName() + "'");
+            }
+        }
+        if (pkgName == null) {
+            throw new ParseException("No package name for the capability", 0);
+        }
+        ExportPackage exportPackage = new ExportPackage(pkgName, version);
+        if (uses != null) {
+            String[] split = uses.trim().split(",");
+            for (int i = 0; i < split.length; i++) {
+                String u = split[i];
+                exportPackage.addUse(u.trim());
+            }
+        }
+        return exportPackage;
+    }
+
+    private static BundleCapability getOSGiService(BundleInfo bundleInfo, Capability capability)
+            throws ParseException {
+        String name = null;
+        Version version = null;
+
+        for (CapabilityProperty property : capability.getProperties()) {
+            String propName = property.getName();
+            if ("service".equals(propName)) {
+                name = property.getValue();
+            } else if ("version".equals(propName)) {
+                version = new Version(property.getValue());
+            } else {
+                Message.warn("Unsupported property '" + propName
+                        + "' on the 'package' capability of the bundle '"
+                        + bundleInfo.getSymbolicName() + "'");
+            }
+        }
+
+        if (name == null) {
+            throw new ParseException("No service name for the capability", 0);
+        }
+        BundleCapability service = new BundleCapability(BundleInfo.SERVICE_TYPE, name, version);
+        return service;
+    }
+}
diff --git a/src/java/org/apache/ivy/osgi/obr/xml/CapabilityProperty.java b/src/java/org/apache/ivy/osgi/obr/xml/CapabilityProperty.java
new file mode 100644
index 0000000..a363ae0
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/obr/xml/CapabilityProperty.java
@@ -0,0 +1,51 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.obr.xml;
+
+public class CapabilityProperty {
+
+    private String name;
+
+    private String value;
+
+    private String type;
+
+    public CapabilityProperty(String name, String value, String type) {
+        this.name = name;
+        this.value = value;
+        this.type = type;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    @Override
+    public String toString() {
+        return (type == null ? "" : "[" + type + "]") + name + "=" + value;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/osgi/obr/xml/OBRXMLParser.java b/src/java/org/apache/ivy/osgi/obr/xml/OBRXMLParser.java
new file mode 100644
index 0000000..9353880
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/obr/xml/OBRXMLParser.java
@@ -0,0 +1,407 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.obr.xml;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.text.ParseException;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.apache.ivy.osgi.core.BundleArtifact;
+import org.apache.ivy.osgi.core.BundleInfo;
+import org.apache.ivy.osgi.core.ExecutionEnvironmentProfileProvider;
+import org.apache.ivy.osgi.filter.OSGiFilter;
+import org.apache.ivy.osgi.filter.OSGiFilterParser;
+import org.apache.ivy.osgi.repo.BundleRepoDescriptor;
+import org.apache.ivy.osgi.util.DelegatingHandler;
+import org.apache.ivy.osgi.util.Version;
+import org.apache.ivy.util.Message;
+import org.apache.ivy.util.XMLHelper;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+public class OBRXMLParser {
+
+    public static BundleRepoDescriptor parse(URI baseUri, InputStream in) throws ParseException,
+            IOException, SAXException {
+        RepositoryHandler handler = new RepositoryHandler(baseUri);
+        try {
+            XMLHelper.parse(in, null, handler, null);
+        } catch (ParserConfigurationException e) {
+            throw new SAXException(e);
+        }
+        return handler.repo;
+    }
+
+    static class RepositoryHandler extends DelegatingHandler {
+
+        static final String REPOSITORY = "repository";
+
+        static final String LASTMODIFIED = "lastmodified";
+
+        static final String NAME = "name";
+
+        BundleRepoDescriptor repo;
+
+        private final URI baseUri;
+
+        public RepositoryHandler(URI baseUri) {
+            super(REPOSITORY);
+            this.baseUri = baseUri;
+            addChild(new ResourceHandler(), new ChildElementHandler<ResourceHandler>() {
+                public void childHanlded(ResourceHandler child) {
+                    repo.addBundle(child.bundleInfo);
+                }
+            });
+        }
+
+        protected void handleAttributes(Attributes atts) {
+            repo = new BundleRepoDescriptor(baseUri,
+                    ExecutionEnvironmentProfileProvider.getInstance());
+
+            repo.setName(atts.getValue(NAME));
+
+            repo.setLastModified(atts.getValue(LASTMODIFIED));
+        }
+    }
+
+    static class ResourceHandler extends DelegatingHandler {
+
+        private static final String DEFAULT_VERSION = "1.0.0";
+
+        static final String RESOURCE = "resource";
+
+        static final String ID = "id";
+
+        static final String PRESENTATION_NAME = "presentationname";
+
+        static final String SYMBOLIC_NAME = "symbolicname";
+
+        static final String URI = "uri";
+
+        static final String VERSION = "version";
+
+        BundleInfo bundleInfo;
+
+        public ResourceHandler() {
+            super(RESOURCE);
+
+            setSkipOnError(true); // if anything bad happen in any children, just ignore the
+                                  // resource
+
+            addChild(new ResourceSourceHandler(), new ChildElementHandler<ResourceSourceHandler>() {
+                public void childHanlded(ResourceSourceHandler child) {
+                    String uri = child.getBufferedChars().trim();
+                    if (!uri.endsWith(".jar")) {
+                        // the maven plugin is putting some useless source url sometimes...
+                        log(Message.MSG_WARN,
+                            "A source uri is suspect, it is not ending with .jar, it is probably"
+                                    + " a pointer to a download page. Ignoring it.");
+                        return;
+                    }
+                    try {
+                        bundleInfo.addArtifact(new BundleArtifact(true, new URI(uri), null));
+                    } catch (URISyntaxException e) {
+                        log(Message.MSG_WARN, "Incorrect uri " + uri + ". The source of "
+                                + bundleInfo.getSymbolicName() + " is then ignored.");
+                        return;
+                    }
+                }
+            });
+            addChild(new ResourceDescriptionHandler(),
+                new ChildElementHandler<ResourceDescriptionHandler>() {
+                    public void childHanlded(ResourceDescriptionHandler child) {
+                        bundleInfo.setDescription(child.getBufferedChars().trim());
+                    }
+                });
+            addChild(new ResourceDocumentationHandler(),
+                new ChildElementHandler<ResourceDocumentationHandler>() {
+                    public void childHanlded(ResourceDocumentationHandler child) {
+                        bundleInfo.setDocumentation(child.getBufferedChars().trim());
+                    }
+                });
+            addChild(new ResourceLicenseHandler(),
+                new ChildElementHandler<ResourceLicenseHandler>() {
+                    public void childHanlded(ResourceLicenseHandler child) {
+                        bundleInfo.setLicense(child.getBufferedChars().trim());
+                    }
+                });
+            addChild(new ResourceSizeHandler(), new ChildElementHandler<ResourceSizeHandler>() {
+                public void childHanlded(ResourceSizeHandler child) {
+                    String size = child.getBufferedChars().trim();
+                    try {
+                        bundleInfo.setSize(Integer.valueOf(size));
+                    } catch (NumberFormatException e) {
+                        log(Message.MSG_WARN,
+                            "Invalid size for the bundle " + bundleInfo.getSymbolicName() + ": "
+                                    + size + ". This size is then ignored.");
+                    }
+                }
+            });
+            addChild(new CapabilityHandler(), new ChildElementHandler<CapabilityHandler>() {
+                public void childHanlded(CapabilityHandler child) throws SAXParseException {
+
+                    try {
+                        CapabilityAdapter.adapt(bundleInfo, child.capability);
+                    } catch (ParseException e) {
+                        throw new SAXParseException("Invalid capability: " + e.getMessage(), child
+                                .getLocator());
+                    }
+                }
+            });
+            addChild(new RequireHandler(), new ChildElementHandler<RequireHandler>() {
+                public void childHanlded(RequireHandler child) throws SAXParseException {
+                    try {
+                        RequirementAdapter.adapt(bundleInfo, child.requirement);
+                    } catch (UnsupportedFilterException e) {
+                        throw new SAXParseException("Unsupported requirement filter: "
+                                + child.filter + " (" + e.getMessage() + ")", getLocator());
+                    } catch (ParseException e) {
+                        throw new SAXParseException(
+                                "Error in the requirement filter on the bundle: " + e.getMessage(),
+                                getLocator());
+                    }
+                }
+            });
+            addChild(new ExtendHandler(), new ChildElementHandler<ExtendHandler>() {
+                public void childHanlded(ExtendHandler child) throws SAXParseException {
+                    // TODO handle fragment host
+                }
+            });
+        }
+
+        protected void handleAttributes(Attributes atts) throws SAXException {
+            String symbolicname = atts.getValue(SYMBOLIC_NAME);
+            if (symbolicname == null) {
+                log(Message.MSG_ERR, "Resource with no symobilc name, skipping it.");
+                skip();
+                return;
+            }
+
+            String v = getOptionalAttribute(atts, VERSION, DEFAULT_VERSION);
+            Version version;
+            try {
+                version = new Version(v);
+            } catch (ParseException e) {
+                log(Message.MSG_ERR, "Incorrect resource version: " + v + ". The resource "
+                        + symbolicname + " is then ignored.");
+                skip();
+                return;
+            }
+
+            bundleInfo = new BundleInfo(symbolicname, version);
+            bundleInfo.setPresentationName(atts.getValue(PRESENTATION_NAME));
+            String uri = atts.getValue(URI);
+            if (uri != null) {
+                try {
+                    bundleInfo.addArtifact(new BundleArtifact(false, new URI(uri), null));
+                } catch (URISyntaxException e) {
+                    log(Message.MSG_ERR, "Incorrect uri " + uri + ". The resource " + symbolicname
+                            + " is then ignored.");
+                    skip();
+                    return;
+                }
+            }
+            bundleInfo.setId(atts.getValue(ID));
+        }
+
+        protected String getCurrentElementIdentifier() {
+            return bundleInfo.getSymbolicName() + "/" + bundleInfo.getVersion();
+        }
+
+    }
+
+    static class ResourceSourceHandler extends DelegatingHandler {
+
+        static final String SOURCE = "source";
+
+        public ResourceSourceHandler() {
+            super(SOURCE);
+            setBufferingChar(true);
+        }
+
+    }
+
+    static class ResourceDescriptionHandler extends DelegatingHandler {
+
+        static final String DESCRIPTION = "description";
+
+        public ResourceDescriptionHandler() {
+            super(DESCRIPTION);
+            setBufferingChar(true);
+        }
+
+    }
+
+    static class ResourceDocumentationHandler extends DelegatingHandler {
+
+        static final String DOCUMENTATION = "documentation";
+
+        public ResourceDocumentationHandler() {
+            super(DOCUMENTATION);
+            setBufferingChar(true);
+        }
+    }
+
+    static class ResourceLicenseHandler extends DelegatingHandler {
+
+        static final String LICENSE = "license";
+
+        public ResourceLicenseHandler() {
+            super(LICENSE);
+            setBufferingChar(true);
+        }
+
+    }
+
+    static class ResourceSizeHandler extends DelegatingHandler {
+
+        static final String SIZE = "size";
+
+        public ResourceSizeHandler() {
+            super(SIZE);
+            setBufferingChar(true);
+        }
+    }
+
+    static class CapabilityHandler extends DelegatingHandler {
+
+        static final String CAPABILITY = "capability";
+
+        static final String NAME = "name";
+
+        Capability capability;
+
+        public CapabilityHandler() {
+            super(CAPABILITY);
+            addChild(new CapabilityPropertyHandler(),
+                new ChildElementHandler<CapabilityPropertyHandler>() {
+                    public void childHanlded(CapabilityPropertyHandler child) {
+                        String name = child.name;
+                        String value = child.value;
+                        String type = child.type;
+
+                        capability.addProperty(name, value, type);
+                    }
+                });
+        }
+
+        protected void handleAttributes(Attributes atts) throws SAXException {
+            String name = getRequiredAttribute(atts, NAME);
+            capability = new Capability(name);
+        }
+
+    }
+
+    static class CapabilityPropertyHandler extends DelegatingHandler {
+
+        static final String CAPABILITY_PROPERTY = "p";
+
+        static final String NAME = "n";
+
+        static final String VALUE = "v";
+
+        static final String TYPE = "t";
+
+        String name;
+
+        String value;
+
+        String type;
+
+        public CapabilityPropertyHandler() {
+            super(CAPABILITY_PROPERTY);
+        }
+
+        protected void handleAttributes(Attributes atts) throws SAXException {
+            name = getRequiredAttribute(atts, NAME);
+            value = getRequiredAttribute(atts, VALUE);
+            type = atts.getValue(TYPE);
+        }
+    }
+
+    static class AbstractRequirementHandler extends DelegatingHandler {
+
+        static final String NAME = "name";
+
+        static final String OPTIONAL = "optional";
+
+        static final String MULTIPLE = "multiple";
+
+        static final String FILTER = "filter";
+
+        Requirement requirement;
+
+        OSGiFilter filter;
+
+        public AbstractRequirementHandler(String name) {
+            super(name);
+        }
+
+        protected void handleAttributes(Attributes atts) throws SAXException {
+            String name = getRequiredAttribute(atts, NAME);
+
+            String filterText = atts.getValue(FILTER);
+            filter = null;
+            if (filterText != null) {
+                try {
+                    filter = OSGiFilterParser.parse(filterText);
+                } catch (ParseException e) {
+                    throw new SAXParseException("Requirement with illformed filter: " + filterText,
+                            getLocator());
+                }
+            }
+
+            Boolean optional = getOptionalBooleanAttribute(atts, OPTIONAL, null);
+            Boolean multiple = getOptionalBooleanAttribute(atts, MULTIPLE, null);
+
+            requirement = new Requirement(name, filter);
+            if (optional != null) {
+                requirement.setOptional(optional.booleanValue());
+            }
+            if (multiple != null) {
+                requirement.setMultiple(multiple.booleanValue());
+            }
+        }
+
+    }
+
+    static class RequireHandler extends AbstractRequirementHandler {
+
+        static final String REQUIRE = "require";
+
+        public RequireHandler() {
+            super(REQUIRE);
+        }
+
+    }
+
+    static class ExtendHandler extends AbstractRequirementHandler {
+
+        static final String EXTEND = "extend";
+
+        public ExtendHandler() {
+            super(EXTEND);
+        }
+
+    }
+}
diff --git a/src/java/org/apache/ivy/osgi/obr/xml/OBRXMLWriter.java b/src/java/org/apache/ivy/osgi/obr/xml/OBRXMLWriter.java
new file mode 100644
index 0000000..190b957
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/obr/xml/OBRXMLWriter.java
@@ -0,0 +1,288 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.obr.xml;
+
+import java.io.OutputStream;
+import java.text.ParseException;
+import java.util.Set;
+
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.sax.SAXTransformerFactory;
+import javax.xml.transform.sax.TransformerHandler;
+import javax.xml.transform.stream.StreamResult;
+
+import org.apache.ivy.core.IvyContext;
+import org.apache.ivy.osgi.core.BundleArtifact;
+import org.apache.ivy.osgi.core.BundleCapability;
+import org.apache.ivy.osgi.core.BundleInfo;
+import org.apache.ivy.osgi.core.BundleRequirement;
+import org.apache.ivy.osgi.core.ExportPackage;
+import org.apache.ivy.osgi.core.ManifestParser;
+import org.apache.ivy.osgi.obr.xml.OBRXMLParser.CapabilityHandler;
+import org.apache.ivy.osgi.obr.xml.OBRXMLParser.CapabilityPropertyHandler;
+import org.apache.ivy.osgi.obr.xml.OBRXMLParser.RepositoryHandler;
+import org.apache.ivy.osgi.obr.xml.OBRXMLParser.RequireHandler;
+import org.apache.ivy.osgi.obr.xml.OBRXMLParser.ResourceHandler;
+import org.apache.ivy.osgi.obr.xml.OBRXMLParser.ResourceSourceHandler;
+import org.apache.ivy.osgi.repo.ManifestAndLocation;
+import org.apache.ivy.osgi.util.Version;
+import org.apache.ivy.osgi.util.VersionRange;
+import org.apache.ivy.util.Message;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+public class OBRXMLWriter {
+
+    public static ContentHandler newHandler(OutputStream out, String encoding, boolean indent)
+            throws TransformerConfigurationException {
+        SAXTransformerFactory tf = (SAXTransformerFactory) SAXTransformerFactory.newInstance();
+        TransformerHandler hd = tf.newTransformerHandler();
+        Transformer serializer = tf.newTransformer();
+        StreamResult stream = new StreamResult(out);
+        serializer.setOutputProperty(OutputKeys.ENCODING, encoding);
+        serializer.setOutputProperty(OutputKeys.INDENT, indent ? "yes" : "no");
+        hd.setResult(stream);
+        return hd;
+    }
+
+    public static void writeManifests(Iterable<ManifestAndLocation> manifestAndLocations,
+            ContentHandler handler, boolean quiet) throws SAXException {
+        int level = quiet ? Message.MSG_DEBUG : Message.MSG_WARN;
+        handler.startDocument();
+        AttributesImpl atts = new AttributesImpl();
+        handler.startElement("", RepositoryHandler.REPOSITORY, RepositoryHandler.REPOSITORY, atts);
+        int nbOk = 0;
+        int nbRejected = 0;
+        for (ManifestAndLocation manifestAndLocation : manifestAndLocations) {
+            BundleInfo bundleInfo;
+            try {
+                bundleInfo = ManifestParser.parseManifest(manifestAndLocation.getManifest());
+                bundleInfo
+                        .addArtifact(new BundleArtifact(false, manifestAndLocation.getUri(), null));
+                if (manifestAndLocation.getSourceURI() != null) {
+                    bundleInfo.addArtifact(new BundleArtifact(true, manifestAndLocation
+                            .getSourceURI(), null));
+                }
+                nbOk++;
+            } catch (ParseException e) {
+                nbRejected++;
+                IvyContext
+                        .getContext()
+                        .getMessageLogger()
+                        .log("Rejected " + manifestAndLocation.getUri() + ": " + e.getMessage(),
+                            level);
+                continue;
+            }
+            saxBundleInfo(bundleInfo, handler);
+        }
+        handler.endElement("", RepositoryHandler.REPOSITORY, RepositoryHandler.REPOSITORY);
+        handler.endDocument();
+        Message.info(nbOk + " bundle" + (nbOk > 1 ? "s" : "") + " added, " + nbRejected + " bundle"
+                + (nbRejected > 1 ? "s" : "") + " rejected.");
+    }
+
+    public static void writeBundles(Iterable<BundleInfo> bundleInfos, ContentHandler handler)
+            throws SAXException {
+        handler.startDocument();
+        AttributesImpl atts = new AttributesImpl();
+        handler.startElement("", RepositoryHandler.REPOSITORY, RepositoryHandler.REPOSITORY, atts);
+        for (BundleInfo bundleInfo : bundleInfos) {
+            saxBundleInfo(bundleInfo, handler);
+        }
+        handler.endElement("", RepositoryHandler.REPOSITORY, RepositoryHandler.REPOSITORY);
+        handler.endDocument();
+    }
+
+    private static void saxBundleInfo(BundleInfo bundleInfo, ContentHandler handler)
+            throws SAXException {
+        AttributesImpl atts = new AttributesImpl();
+        addAttr(atts, ResourceHandler.SYMBOLIC_NAME, bundleInfo.getSymbolicName());
+        addAttr(atts, ResourceHandler.VERSION, bundleInfo.getRawVersion());
+        for (BundleArtifact artifact : bundleInfo.getArtifacts()) {
+            if (!artifact.isSource()) {
+                addAttr(atts, ResourceHandler.URI, bundleInfo.getArtifacts().get(0).getUri()
+                        .toString());
+                break;
+            }
+        }
+        handler.startElement("", ResourceHandler.RESOURCE, ResourceHandler.RESOURCE, atts);
+        for (BundleArtifact artifact : bundleInfo.getArtifacts()) {
+            if (artifact.isSource()) {
+                startElement(handler, ResourceSourceHandler.SOURCE);
+                characters(handler, artifact.getUri().toString());
+                endElement(handler, ResourceSourceHandler.SOURCE);
+                break;
+            }
+        }
+        for (BundleCapability capability : bundleInfo.getCapabilities()) {
+            saxCapability(capability, handler);
+        }
+        for (BundleRequirement requirement : bundleInfo.getRequirements()) {
+            saxRequirement(requirement, handler);
+        }
+        handler.endElement("", ResourceHandler.RESOURCE, ResourceHandler.RESOURCE);
+        handler.characters("\n".toCharArray(), 0, 1);
+    }
+
+    private static void saxCapability(BundleCapability capability, ContentHandler handler)
+            throws SAXException {
+        AttributesImpl atts = new AttributesImpl();
+        String type = capability.getType();
+        addAttr(atts, CapabilityHandler.NAME, type);
+        handler.startElement("", CapabilityHandler.CAPABILITY, CapabilityHandler.CAPABILITY, atts);
+        if (type.equals(BundleInfo.BUNDLE_TYPE)) {
+            // nothing to do, already handled with the resource tag
+        } else if (type.equals(BundleInfo.PACKAGE_TYPE)) {
+            saxCapabilityProperty("package", capability.getName(), handler);
+            Version v = capability.getRawVersion();
+            if (v != null) {
+                saxCapabilityProperty("version", v.toString(), handler);
+            }
+            Set<String> uses = ((ExportPackage) capability).getUses();
+            if (uses != null && !uses.isEmpty()) {
+                StringBuffer builder = new StringBuffer();
+                for (String use : uses) {
+                    if (builder.length() != 0) {
+                        builder.append(',');
+                    }
+                    builder.append(use);
+                }
+                saxCapabilityProperty("uses", builder.toString(), handler);
+            }
+        } else if (type.equals(BundleInfo.SERVICE_TYPE)) {
+            saxCapabilityProperty("service", capability.getName(), handler);
+            Version v = capability.getRawVersion();
+            if (v != null) {
+                saxCapabilityProperty("version", v.toString(), handler);
+            }
+        } else {
+            // oups
+        }
+        handler.endElement("", CapabilityHandler.CAPABILITY, CapabilityHandler.CAPABILITY);
+        handler.characters("\n".toCharArray(), 0, 1);
+    }
+
+    private static void saxCapabilityProperty(String n, String v, ContentHandler handler)
+            throws SAXException {
+        saxCapabilityProperty(n, null, v, handler);
+    }
+
+    private static void saxCapabilityProperty(String n, String t, String v, ContentHandler handler)
+            throws SAXException {
+        AttributesImpl atts = new AttributesImpl();
+        addAttr(atts, CapabilityPropertyHandler.NAME, n);
+        if (t != null) {
+            addAttr(atts, CapabilityPropertyHandler.TYPE, t);
+        }
+        addAttr(atts, CapabilityPropertyHandler.VALUE, v);
+        handler.startElement("", CapabilityPropertyHandler.CAPABILITY_PROPERTY,
+            CapabilityPropertyHandler.CAPABILITY_PROPERTY, atts);
+        handler.endElement("", CapabilityPropertyHandler.CAPABILITY_PROPERTY,
+            CapabilityPropertyHandler.CAPABILITY_PROPERTY);
+        handler.characters("\n".toCharArray(), 0, 1);
+    }
+
+    private static void saxRequirement(BundleRequirement requirement, ContentHandler handler)
+            throws SAXException {
+        AttributesImpl atts = new AttributesImpl();
+        addAttr(atts, RequireHandler.NAME, requirement.getType());
+        if ("optional".equals(requirement.getResolution())) {
+            addAttr(atts, RequireHandler.OPTIONAL, Boolean.TRUE.toString());
+        }
+        addAttr(atts, RequireHandler.FILTER, buildFilter(requirement));
+        handler.startElement("", RequireHandler.REQUIRE, RequireHandler.REQUIRE, atts);
+        handler.endElement("", RequireHandler.REQUIRE, RequireHandler.REQUIRE);
+        handler.characters("\n".toCharArray(), 0, 1);
+    }
+
+    private static String buildFilter(BundleRequirement requirement) {
+        StringBuffer filter = new StringBuffer();
+        VersionRange v = requirement.getVersion();
+        if (v != null) {
+            appendVersion(filter, v);
+        }
+        filter.append('(');
+        filter.append(requirement.getType());
+        filter.append("=");
+        filter.append(requirement.getName());
+        filter.append(')');
+        if (v != null) {
+            filter.append(')');
+        }
+        return filter.toString();
+    }
+
+    private static void appendVersion(StringBuffer filter, VersionRange v) {
+        filter.append("(&");
+        Version start = v.getStartVersion();
+        if (start != null) {
+            if (!v.isStartExclusive()) {
+                filter.append("(version>=");
+                filter.append(start.toString());
+                filter.append(')');
+            } else {
+                filter.append("(!");
+                filter.append("(version<=");
+                filter.append(start.toString());
+                filter.append("))");
+            }
+        }
+        Version end = v.getEndVersion();
+        if (end != null) {
+            if (!v.isEndExclusive()) {
+                filter.append("(version<=");
+                filter.append(end.toString());
+                filter.append(')');
+            } else {
+                filter.append("(!");
+                filter.append("(version>=");
+                filter.append(end.toString());
+                filter.append("))");
+            }
+        }
+    }
+
+    private static void addAttr(AttributesImpl atts, String name, String value) {
+        if (value != null) {
+            atts.addAttribute("", name, name, "CDATA", value);
+        }
+    }
+
+    private static void addAttr(AttributesImpl atts, String name, Object value) {
+        if (value != null) {
+            atts.addAttribute("", name, name, "CDATA", value.toString());
+        }
+    }
+
+    private static void startElement(ContentHandler handler, String name) throws SAXException {
+        handler.startElement("", name, name, new AttributesImpl());
+    }
+
+    private static void endElement(ContentHandler handler, String name) throws SAXException {
+        handler.endElement("", name, name);
+    }
+
+    private static void characters(ContentHandler handler, String value) throws SAXException {
+        char[] chars = value.toCharArray();
+        handler.characters(chars, 0, chars.length);
+    }
+
+}
diff --git a/src/java/org/apache/ivy/osgi/obr/xml/Requirement.java b/src/java/org/apache/ivy/osgi/obr/xml/Requirement.java
new file mode 100644
index 0000000..14f12c6
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/obr/xml/Requirement.java
@@ -0,0 +1,61 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.obr.xml;
+
+import org.apache.ivy.osgi.filter.OSGiFilter;
+
+public class Requirement {
+
+    private final String name;
+
+    private boolean optional;
+
+    private final OSGiFilter filter;
+
+    private boolean multiple = false;
+
+    public Requirement(String name, OSGiFilter filter) {
+        this.name = name;
+        this.filter = filter;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public OSGiFilter getFilter() {
+        return filter;
+    }
+
+    public void setOptional(boolean optional) {
+        this.optional = optional;
+    }
+
+    public boolean isOptional() {
+        return optional;
+    }
+
+    public void setMultiple(boolean multiple) {
+        this.multiple = multiple;
+    }
+
+    public boolean isMultiple() {
+        return multiple;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/osgi/obr/xml/RequirementAdapter.java b/src/java/org/apache/ivy/osgi/obr/xml/RequirementAdapter.java
new file mode 100644
index 0000000..0f9a68d
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/obr/xml/RequirementAdapter.java
@@ -0,0 +1,184 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.obr.xml;
+
+import java.text.ParseException;
+
+import org.apache.ivy.osgi.core.BundleInfo;
+import org.apache.ivy.osgi.core.BundleRequirement;
+import org.apache.ivy.osgi.filter.AndFilter;
+import org.apache.ivy.osgi.filter.CompareFilter;
+import org.apache.ivy.osgi.filter.CompareFilter.Operator;
+import org.apache.ivy.osgi.filter.NotFilter;
+import org.apache.ivy.osgi.filter.OSGiFilter;
+import org.apache.ivy.osgi.util.Version;
+import org.apache.ivy.osgi.util.VersionRange;
+
+public class RequirementAdapter {
+
+    private Version startVersion = null;
+
+    private boolean startExclusive = false;
+
+    private Version endVersion = null;
+
+    private boolean endExclusive = false;
+
+    private String type = null;
+
+    private String name = null;
+
+    public static void adapt(BundleInfo info, Requirement requirement)
+            throws UnsupportedFilterException, ParseException {
+        RequirementAdapter adapter = new RequirementAdapter();
+        adapter.extractFilter(requirement.getFilter());
+        adapter.adapt(info, requirement.isOptional());
+    }
+
+    private void extractFilter(OSGiFilter filter) throws UnsupportedFilterException, ParseException {
+        if (filter instanceof AndFilter) {
+            AndFilter andFilter = (AndFilter) filter;
+            for (OSGiFilter subFilter : andFilter.getSubFilters()) {
+                extractFilter(subFilter);
+            }
+        } else if (filter instanceof CompareFilter) {
+            CompareFilter compareFilter = ((CompareFilter) filter);
+            parseCompareFilter(compareFilter, false);
+        } else if (filter instanceof NotFilter) {
+            NotFilter notFilter = ((NotFilter) filter);
+            if (notFilter.getSubFilter() instanceof CompareFilter) {
+                CompareFilter compareFilter = ((CompareFilter) notFilter.getSubFilter());
+                parseCompareFilter(compareFilter, true);
+            }
+        } else {
+            throw new UnsupportedFilterException("Unsupported filter: "
+                    + filter.getClass().getName());
+        }
+    }
+
+    private void adapt(BundleInfo info, boolean optional) throws ParseException {
+        VersionRange range = getVersionRange();
+        String resolution = optional ? "optional" : null;
+        if (type == null) {
+            throw new ParseException("No requirement actually specified", 0);
+        }
+        BundleRequirement requirement = new BundleRequirement(type, name, range, resolution);
+        info.addRequirement(requirement);
+        if (BundleInfo.EXECUTION_ENVIRONMENT_TYPE.equals(type)) {
+            info.addExecutionEnvironment(name);
+        }
+    }
+
+    private VersionRange getVersionRange() {
+        VersionRange range = null;
+        if (startVersion != null || endVersion != null) {
+            range = new VersionRange(startExclusive, startVersion, endExclusive, endVersion);
+        }
+        return range;
+    }
+
+    private void parseCompareFilter(CompareFilter compareFilter, boolean not)
+            throws UnsupportedFilterException, ParseException {
+        String att = compareFilter.getLeftValue();
+        if (BundleInfo.PACKAGE_TYPE.equals(att) || BundleInfo.BUNDLE_TYPE.equals(att)
+                || BundleInfo.EXECUTION_ENVIRONMENT_TYPE.equals(att) || "symbolicname".equals(att)
+                || BundleInfo.SERVICE_TYPE.equals(att)) {
+            if (not) {
+                throw new UnsupportedFilterException(
+                        "Not filter on requirement comparaison is not supported");
+            }
+            if (type != null) {
+                throw new UnsupportedFilterException("Multiple requirement type are not supported");
+            }
+            if ("symbolicname".equals(att)) {
+                type = BundleInfo.BUNDLE_TYPE;
+            } else {
+                type = att;
+            }
+            if (compareFilter.getOperator() != Operator.EQUALS) {
+                throw new UnsupportedFilterException(
+                        "Filtering is only supported with the operator '='");
+            }
+            name = compareFilter.getRightValue();
+        } else if ("version".equals(att)) {
+            String v = compareFilter.getRightValue();
+            Version version;
+            try {
+                version = new Version(v);
+            } catch (ParseException e) {
+                throw new ParseException("Ill formed version: " + v, 0);
+            }
+            Operator operator = compareFilter.getOperator();
+            if (not) {
+                if (operator == Operator.EQUALS) {
+                    throw new UnsupportedFilterException(
+                            "Not filter on equals comparaison is not supported");
+                } else if (operator == Operator.GREATER_OR_EQUAL) {
+                    operator = Operator.LOWER_THAN;
+                } else if (operator == Operator.GREATER_THAN) {
+                    operator = Operator.LOWER_OR_EQUAL;
+                } else if (operator == Operator.LOWER_OR_EQUAL) {
+                    operator = Operator.GREATER_THAN;
+                } else if (operator == Operator.LOWER_THAN) {
+                    operator = Operator.GREATER_OR_EQUAL;
+                }
+            }
+            if (operator == Operator.EQUALS) {
+                if (startVersion != null || endVersion != null) {
+                    throw new UnsupportedFilterException(
+                            "Multiple version matching is not supported");
+                }
+                startVersion = version;
+                startExclusive = false;
+                endVersion = version;
+                endExclusive = false;
+            } else if (operator == Operator.GREATER_OR_EQUAL) {
+                if (startVersion != null) {
+                    throw new UnsupportedFilterException(
+                            "Multiple version matching is not supported");
+                }
+                startVersion = version;
+                startExclusive = false;
+            } else if (operator == Operator.GREATER_THAN) {
+                if (startVersion != null) {
+                    throw new UnsupportedFilterException(
+                            "Multiple version matching is not supported");
+                }
+                startVersion = version;
+                startExclusive = true;
+            } else if (operator == Operator.LOWER_OR_EQUAL) {
+                if (endVersion != null) {
+                    throw new UnsupportedFilterException(
+                            "Multiple version matching is not supported");
+                }
+                endVersion = version;
+                endExclusive = false;
+            } else if (operator == Operator.LOWER_THAN) {
+                if (endVersion != null) {
+                    throw new UnsupportedFilterException(
+                            "Multiple version matching is not supported");
+                }
+                endVersion = version;
+                endExclusive = true;
+            }
+        } else {
+            throw new UnsupportedFilterException("Unsupported attribute: " + att);
+        }
+
+    }
+}
diff --git a/src/java/org/apache/ivy/osgi/obr/xml/UnsupportedFilterException.java b/src/java/org/apache/ivy/osgi/obr/xml/UnsupportedFilterException.java
new file mode 100644
index 0000000..3218bca
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/obr/xml/UnsupportedFilterException.java
@@ -0,0 +1,25 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.obr.xml;
+
+public class UnsupportedFilterException extends Exception {
+
+    public UnsupportedFilterException(String message) {
+        super(message);
+    }
+}
diff --git a/src/java/org/apache/ivy/osgi/p2/P2Artifact.java b/src/java/org/apache/ivy/osgi/p2/P2Artifact.java
new file mode 100644
index 0000000..60d402d
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/p2/P2Artifact.java
@@ -0,0 +1,53 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.p2;
+
+import org.apache.ivy.osgi.util.Version;
+
+public class P2Artifact {
+
+    private String id;
+
+    private Version version;
+
+    private String classifier;
+
+    public P2Artifact(String id, Version version, String classifier) {
+        this.id = id;
+        this.version = version;
+        this.classifier = classifier;
+    }
+
+    public String getClassifier() {
+        return classifier;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public Version getVersion() {
+        return version;
+    }
+
+    @Override
+    public String toString() {
+        return id + "@" + version + " (" + classifier + ")";
+    }
+
+}
diff --git a/src/java/org/apache/ivy/osgi/p2/P2ArtifactParser.java b/src/java/org/apache/ivy/osgi/p2/P2ArtifactParser.java
new file mode 100644
index 0000000..71d4d75
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/p2/P2ArtifactParser.java
@@ -0,0 +1,248 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.p2;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.text.ParseException;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.apache.ivy.osgi.filter.OSGiFilter;
+import org.apache.ivy.osgi.filter.OSGiFilterParser;
+import org.apache.ivy.osgi.p2.PropertiesParser.PropertiesHandler;
+import org.apache.ivy.osgi.util.DelegatingHandler;
+import org.apache.ivy.osgi.util.Version;
+import org.apache.ivy.util.XMLHelper;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+public class P2ArtifactParser implements XMLInputParser {
+
+    private final P2Descriptor p2Descriptor;
+
+    private final String repoUrl;
+
+    public P2ArtifactParser(P2Descriptor p2Descriptor, String repoUrl) {
+        this.p2Descriptor = p2Descriptor;
+        this.repoUrl = repoUrl;
+    }
+
+    public void parse(InputStream in) throws ParseException, IOException, SAXException {
+        RepositoryHandler handler = new RepositoryHandler(p2Descriptor, repoUrl);
+        try {
+            XMLHelper.parse(in, null, handler, null);
+        } catch (ParserConfigurationException e) {
+            throw new SAXException(e);
+        }
+    }
+
+    private static class RepositoryHandler extends DelegatingHandler {
+
+        private static final String REPOSITORY = "repository";
+
+        // private static final String NAME = "name";
+        //
+        // private static final String TYPE = "type";
+        //
+        // private static final String VERSION = "version";
+
+        private Map<OSGiFilter, String> artifactPatterns = new LinkedHashMap<OSGiFilter, String>();
+
+        public RepositoryHandler(final P2Descriptor p2Descriptor, String repoUrl) {
+            super(REPOSITORY);
+            // addChild(new PropertiesHandler(), new ChildElementHandler<PropertiesHandler>() {
+            // public void childHanlded(PropertiesHandler child) {
+            // }
+            // });
+            addChild(new MappingsHandler(), new ChildElementHandler<MappingsHandler>() {
+                public void childHanlded(MappingsHandler child) {
+                    for (Entry<String, String> entry : child.outputByFilter.entrySet()) {
+                        OSGiFilter filter;
+                        try {
+                            filter = OSGiFilterParser.parse(entry.getKey());
+                        } catch (ParseException e) {
+                            throw new IllegalStateException();
+                        }
+                        artifactPatterns.put(filter, entry.getValue());
+
+                    }
+                }
+            });
+            addChild(new ArtifactsHandler(p2Descriptor, artifactPatterns, repoUrl),
+                new ChildElementHandler<ArtifactsHandler>() {
+                    public void childHanlded(ArtifactsHandler child) {
+                        // nothing to do
+                    }
+                });
+        }
+        // protected void handleAttributes(Attributes atts) {
+        // String name = atts.getValue(NAME);
+        // String type = atts.getValue(TYPE);
+        // String version = atts.getValue(VERSION);
+        // }
+    }
+
+    private static class MappingsHandler extends DelegatingHandler {
+
+        private static final String MAPPINGS = "mappings";
+
+        private static final String SIZE = "size";
+
+        Map<String, String> outputByFilter;
+
+        public MappingsHandler() {
+            super(MAPPINGS);
+            addChild(new RuleHandler(), new ChildElementHandler<RuleHandler>() {
+                public void childHanlded(RuleHandler child) {
+                    outputByFilter.put(child.filter, child.output);
+                }
+            });
+        }
+
+        protected void handleAttributes(Attributes atts) {
+            int size = Integer.parseInt(atts.getValue(SIZE));
+            outputByFilter = new LinkedHashMap<String, String>(size);
+        }
+
+    }
+
+    private static class RuleHandler extends DelegatingHandler {
+
+        private static final String RULE = "rule";
+
+        private static final String FILTER = "filter";
+
+        private static final String OUTPUT = "output";
+
+        private String filter;
+
+        private String output;
+
+        public RuleHandler() {
+            super(RULE);
+        }
+
+        protected void handleAttributes(Attributes atts) {
+            filter = atts.getValue(FILTER);
+            output = atts.getValue(OUTPUT);
+        }
+
+    }
+
+    private static class ArtifactsHandler extends DelegatingHandler {
+
+        private static final String ARTIFACTS = "artifacts";
+
+        // private static final String SIZE = "size";
+
+        public ArtifactsHandler(final P2Descriptor p2Descriptor,
+                final Map<OSGiFilter, String> artifactPatterns, final String repoUrl) {
+            super(ARTIFACTS);
+            addChild(new ArtifactHandler(), new ChildElementHandler<ArtifactHandler>() {
+                public void childHanlded(ArtifactHandler child) throws SAXParseException {
+                    String url = getPattern(child.p2Artifact, child.properties);
+                    if (url != null) {
+                        url = url.replaceAll("\\$\\{repoUrl\\}", repoUrl);
+                        url = url.replaceAll("\\$\\{id\\}", child.p2Artifact.getId());
+                        url = url.replaceAll("\\$\\{version\\}", child.p2Artifact.getVersion()
+                                .toString());
+                        URI uri;
+                        try {
+                            uri = new URL(url).toURI();
+                        } catch (MalformedURLException e) {
+                            throw new SAXParseException("Incorrect artifact url '" + url + "' ("
+                                    + e.getMessage() + ")", getLocator(), e);
+                        } catch (URISyntaxException e) {
+                            throw new SAXParseException("Incorrect artifact url '" + url + "' ("
+                                    + e.getMessage() + ")", getLocator(), e);
+                        }
+                        p2Descriptor.addArtifactUrl(child.p2Artifact.getClassifier(),
+                            child.p2Artifact.getId(), child.p2Artifact.getVersion(), uri,
+                            child.properties.get("format"));
+                    }
+                }
+
+                private String getPattern(P2Artifact p2Artifact, Map<String, String> properties) {
+                    Map<String, String> props = new HashMap<String, String>(properties);
+                    props.put("classifier", p2Artifact.getClassifier());
+                    for (Entry<OSGiFilter, String> pattern : artifactPatterns.entrySet()) {
+                        if (pattern.getKey().eval(props)) {
+                            return pattern.getValue();
+                        }
+                    }
+                    return null;
+                }
+            });
+        }
+
+        // protected void handleAttributes(Attributes atts) {
+        // int size = Integer.parseInt(atts.getValue(SIZE));
+        // artifacts = new ArrayList(size);
+        // }
+
+    }
+
+    private static class ArtifactHandler extends DelegatingHandler {
+
+        private static final String ARTIFACT = "artifact";
+
+        private static final String CLASSIFIER = "classifier";
+
+        private static final String ID = "id";
+
+        private static final String VERSION = "version";
+
+        private P2Artifact p2Artifact;
+
+        private Map<String, String> properties;
+
+        public ArtifactHandler() {
+            super(ARTIFACT);
+            addChild(new PropertiesHandler(), new ChildElementHandler<PropertiesHandler>() {
+                public void childHanlded(PropertiesHandler child) {
+                    properties = child.properties;
+                }
+            });
+        }
+
+        protected void handleAttributes(Attributes atts) throws SAXException {
+            String id = atts.getValue(ID);
+            Version version;
+            try {
+                version = new Version(atts.getValue(VERSION));
+            } catch (ParseException e) {
+                throw new SAXException("Incorrect version attribute on artifact '" + id + "': "
+                        + atts.getValue(VERSION) + " (" + e.getMessage() + ")");
+            }
+            String classifier = atts.getValue(CLASSIFIER);
+
+            p2Artifact = new P2Artifact(id, version, classifier);
+        }
+    }
+
+}
diff --git a/src/java/org/apache/ivy/osgi/p2/P2CompositeParser.java b/src/java/org/apache/ivy/osgi/p2/P2CompositeParser.java
new file mode 100644
index 0000000..8b1b041
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/p2/P2CompositeParser.java
@@ -0,0 +1,123 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.p2;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.apache.ivy.osgi.util.DelegatingHandler;
+import org.apache.ivy.util.XMLHelper;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+
+public class P2CompositeParser implements XMLInputParser {
+
+    private Set<String> childLocations = new LinkedHashSet<String>();
+
+    public Set<String> getChildLocations() {
+        return childLocations;
+    }
+
+    public void parse(InputStream in) throws ParseException, IOException, SAXException {
+        RepositoryHandler handler = new RepositoryHandler();
+        try {
+            XMLHelper.parse(in, null, handler, null);
+        } catch (ParserConfigurationException e) {
+            throw new SAXException(e);
+        }
+        childLocations.addAll(handler.childLocations);
+    }
+
+    private static class RepositoryHandler extends DelegatingHandler {
+
+        private static final String REPOSITORY = "repository";
+
+        // private static final String NAME = "name";
+        //
+        // private static final String TYPE = "type";
+        //
+        // private static final String VERSION = "version";
+
+        List<String> childLocations = Collections.emptyList();
+
+        public RepositoryHandler() {
+            super(REPOSITORY);
+            addChild(new ChildrenHandler(), new ChildElementHandler<ChildrenHandler>() {
+                public void childHanlded(ChildrenHandler child) {
+                    childLocations = child.childLocations;
+                }
+            });
+        }
+        // protected void handleAttributes(Attributes atts) {
+        // String name = atts.getValue(NAME);
+        // String type = atts.getValue(TYPE);
+        // String version = atts.getValue(VERSION);
+        // }
+    }
+
+    private static class ChildrenHandler extends DelegatingHandler {
+
+        private static final String CHILDREN = "children";
+
+        private static final String SIZE = "size";
+
+        List<String> childLocations;
+
+        public ChildrenHandler() {
+            super(CHILDREN);
+            addChild(new ChildHandler(), new ChildElementHandler<ChildHandler>() {
+                public void childHanlded(ChildHandler child) {
+                    childLocations.add(child.location);
+                }
+            });
+        }
+
+        protected void handleAttributes(Attributes atts) {
+            int size = Integer.parseInt(atts.getValue(SIZE));
+            childLocations = new ArrayList<String>(size);
+        }
+
+    }
+
+    private static class ChildHandler extends DelegatingHandler {
+
+        private static final String CHILD = "child";
+
+        private static final String LOCATION = "location";
+
+        String location;
+
+        public ChildHandler() {
+            super(CHILD);
+        }
+
+        protected void handleAttributes(Attributes atts) {
+            location = atts.getValue(LOCATION);
+        }
+
+    }
+
+}
diff --git a/src/java/org/apache/ivy/osgi/p2/P2Descriptor.java b/src/java/org/apache/ivy/osgi/p2/P2Descriptor.java
new file mode 100644
index 0000000..7dbfe32
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/p2/P2Descriptor.java
@@ -0,0 +1,152 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.p2;
+
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.ivy.osgi.core.BundleArtifact;
+import org.apache.ivy.osgi.core.BundleInfo;
+import org.apache.ivy.osgi.core.ExecutionEnvironmentProfileProvider;
+import org.apache.ivy.osgi.repo.EditableRepoDescriptor;
+import org.apache.ivy.osgi.repo.ModuleDescriptorWrapper;
+import org.apache.ivy.osgi.util.Version;
+import org.apache.ivy.util.Message;
+
+public class P2Descriptor extends EditableRepoDescriptor {
+
+    private Map<String, Map<Version, BundleInfo>> sourceTargetBundles = new HashMap<String, Map<Version, BundleInfo>>();
+
+    private Map<String, Map<Version, BundleInfo>> sourceBundles = new HashMap<String, Map<Version, BundleInfo>>();
+
+    public P2Descriptor(URI repoUri, ExecutionEnvironmentProfileProvider profileProvider) {
+        super(repoUri, profileProvider);
+    }
+
+    public void addBundle(BundleInfo bundleInfo) {
+        if (bundleInfo.isSource()) {
+            if (bundleInfo.getSymbolicNameTarget() == null || bundleInfo.getVersionTarget() == null) {
+                if (getLogLevel() <= Message.MSG_VERBOSE) {
+                    Message.verbose("The source bundle " + bundleInfo.getSymbolicName()
+                            + " did not declare its target. Ignoring it");
+                }
+                return;
+            }
+            Map<Version, BundleInfo> byVersion = sourceBundles.get(bundleInfo.getSymbolicName());
+            if (byVersion == null) {
+                byVersion = new HashMap<Version, BundleInfo>();
+                sourceBundles.put(bundleInfo.getSymbolicName(), byVersion);
+            }
+            byVersion.put(bundleInfo.getVersion(), bundleInfo);
+
+            Map<Version, BundleInfo> byTargetVersion = sourceTargetBundles.get(bundleInfo
+                    .getSymbolicNameTarget());
+            if (byTargetVersion == null) {
+                byTargetVersion = new HashMap<Version, BundleInfo>();
+                sourceTargetBundles.put(bundleInfo.getSymbolicNameTarget(), byTargetVersion);
+            }
+            BundleInfo old = byTargetVersion.put(bundleInfo.getVersionTarget(), bundleInfo);
+            if (old != null && !old.equals(bundleInfo)) {
+                if (getLogLevel() <= Message.MSG_VERBOSE) {
+                    Message.verbose("Duplicate source for the bundle "
+                            + bundleInfo.getSymbolicNameTarget() + "@"
+                            + bundleInfo.getVersionTarget() + " : " + bundleInfo + " is replacing "
+                            + old);
+                }
+            }
+            return;
+        }
+
+        super.addBundle(bundleInfo);
+    }
+
+    public void finish() {
+        sourceBundles = null;
+        Set<String> bundleIds = getCapabilityValues(BundleInfo.BUNDLE_TYPE);
+        if (bundleIds == null) {
+            return;
+        }
+        for (String bundleId : bundleIds) {
+            Set<ModuleDescriptorWrapper> modules = findModules(BundleInfo.BUNDLE_TYPE, bundleId);
+            for (ModuleDescriptorWrapper mdw : modules) {
+                String symbolicName = mdw.getBundleInfo().getSymbolicName();
+                Map<Version, BundleInfo> byVersion = sourceTargetBundles.get(symbolicName);
+                if (byVersion == null) {
+                    continue;
+                }
+                BundleInfo source = byVersion.get(mdw.getBundleInfo().getVersion());
+                if (source == null) {
+                    continue;
+                }
+                for (BundleArtifact artifact : source.getArtifacts()) {
+                    mdw.getBundleInfo().addArtifact(artifact);
+                }
+            }
+        }
+        sourceTargetBundles = null;
+    }
+
+    public void addArtifactUrl(String classifier, String id, Version version, URI uri, String format) {
+        if (!classifier.equals("osgi.bundle")) {
+            // we only support OSGi bundle, no Eclipse feature or anything else
+            return;
+        }
+        ModuleDescriptorWrapper module = findModule(id, version);
+        if (module != null) {
+            addArtifact(module.getBundleInfo(), new BundleArtifact(false, uri, format));
+            return;
+        }
+
+        // not found in the regular bundle. Let's look up in the source ones
+        Map<Version, BundleInfo> byVersion = sourceBundles.get(id);
+        if (byVersion == null) {
+            return;
+        }
+        BundleInfo source = byVersion.get(version);
+        if (source == null) {
+            return;
+        }
+        addArtifact(source, new BundleArtifact(true, uri, format));
+    }
+
+    private void addArtifact(BundleInfo bundle, BundleArtifact artifact) {
+        // find an existing artifact that might be a duplicate
+        BundleArtifact same = null;
+        for (BundleArtifact a : bundle.getArtifacts()) {
+            if (a.isSource() == artifact.isSource()) {
+                same = a;
+                break;
+            }
+        }
+
+        BundleArtifact best = artifact;
+
+        if (same != null) {
+            // we have two artifacts for the same bundle, let's choose a "packed" one
+            if (artifact.getFormat() == null || same.getFormat() != null) {
+                // the new one cannot be better
+                return;
+            }
+            bundle.removeArtifact(same);
+        }
+
+        bundle.addArtifact(best);
+    }
+}
diff --git a/src/java/org/apache/ivy/osgi/p2/P2MetadataParser.java b/src/java/org/apache/ivy/osgi/p2/P2MetadataParser.java
new file mode 100644
index 0000000..42b4b84
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/p2/P2MetadataParser.java
@@ -0,0 +1,922 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.p2;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.apache.ivy.osgi.core.BundleCapability;
+import org.apache.ivy.osgi.core.BundleInfo;
+import org.apache.ivy.osgi.core.BundleRequirement;
+import org.apache.ivy.osgi.core.ExportPackage;
+import org.apache.ivy.osgi.core.ManifestParser;
+import org.apache.ivy.osgi.p2.PropertiesParser.PropertiesHandler;
+import org.apache.ivy.osgi.util.DelegatingHandler;
+import org.apache.ivy.osgi.util.Version;
+import org.apache.ivy.osgi.util.VersionRange;
+import org.apache.ivy.util.Message;
+import org.apache.ivy.util.XMLHelper;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+public class P2MetadataParser implements XMLInputParser {
+
+    private final P2Descriptor p2Descriptor;
+
+    private int logLevel = Message.MSG_INFO;
+
+    public P2MetadataParser(P2Descriptor p2Descriptor) {
+        this.p2Descriptor = p2Descriptor;
+    }
+
+    public void setLogLevel(int logLevel) {
+        this.logLevel = logLevel;
+    }
+
+    public void parse(InputStream in) throws ParseException, IOException, SAXException {
+        RepositoryHandler handler = new RepositoryHandler(p2Descriptor);
+        try {
+            XMLHelper.parse(in, null, handler, null);
+        } catch (ParserConfigurationException e) {
+            throw new SAXException(e);
+        }
+    }
+
+    private class RepositoryHandler extends DelegatingHandler {
+
+        // private static final String P2_TIMESTAMP = "p2.timestamp";
+
+        private static final String REPOSITORY = "repository";
+
+        // private static final String NAME = "name";
+        //
+        // private static final String TYPE = "type";
+        //
+        // private static final String VERSION = "version";
+        //
+        // private static final String DESCRIPTION = "description";
+        //
+        // private static final String PROVIDER = "provider";
+
+        public RepositoryHandler(final P2Descriptor p2Descriptor) {
+            super(REPOSITORY);
+            // addChild(new PropertiesHandler(P2_TIMESTAMP),
+            // new ChildElementHandler<PropertiesHandler>() {
+            // public void childHanlded(PropertiesHandler child) {
+            // String timestamp = child.properties.get(P2_TIMESTAMP);
+            // if (timestamp != null) {
+            // p2Descriptor.setTimestamp(Long.parseLong(timestamp));
+            // }
+            // }
+            // });
+            addChild(new UnitsHandler(), new ChildElementHandler<UnitsHandler>() {
+                public void childHanlded(UnitsHandler child) {
+                    for (BundleInfo bundle : child.bundles) {
+                        p2Descriptor.addBundle(bundle);
+                    }
+                }
+            });
+            addChild(new ReferencesHandler(), new ChildElementHandler<ReferencesHandler>() {
+                public void childHanlded(ReferencesHandler child) {
+                }
+            });
+        }
+
+        // protected void handleAttributes(Attributes atts) {
+        // String name = atts.getValue(NAME);
+        // String type = atts.getValue(TYPE);
+        // String version = atts.getValue(VERSION);
+        // String description = atts.getValue(DESCRIPTION);
+        // String provider = atts.getValue(PROVIDER);
+        // }
+    }
+
+    private class ReferencesHandler extends DelegatingHandler {
+
+        private static final String REFERENCES = "references";
+
+        private static final String SIZE = "size";
+
+        List<URI> repositoryUris;
+
+        public ReferencesHandler() {
+            super(REFERENCES);
+            addChild(new RepositoryReferenceHandler(),
+                new ChildElementHandler<RepositoryReferenceHandler>() {
+                    public void childHanlded(RepositoryReferenceHandler child) {
+                        repositoryUris.add(child.uri);
+                    }
+                });
+        }
+
+        protected void handleAttributes(Attributes atts) throws SAXException {
+            int size = Integer.parseInt(atts.getValue(SIZE));
+            repositoryUris = new ArrayList<URI>(size);
+        }
+    }
+
+    private class RepositoryReferenceHandler extends DelegatingHandler {
+
+        private static final String REPOSITORY = "repository";
+
+        // private static final String TYPE = "type";
+        //
+        // private static final String OPTIONS = "options";
+        //
+        // private static final String NAME = "name";
+
+        private static final String URI = "uri";
+
+        private static final String URL = "url";
+
+        public RepositoryReferenceHandler() {
+            super(REPOSITORY);
+        }
+
+        // int type;
+        //
+        // int options;
+        //
+        // String name;
+
+        URI uri;
+
+        protected void handleAttributes(Attributes atts) throws SAXException {
+            // type = Integer.parseInt(atts.getValue(TYPE));
+            // options = Integer.parseInt(atts.getValue(OPTIONS));
+            // name = atts.getValue(NAME);
+
+            String uriAtt = atts.getValue(URI);
+            String urlAtt = atts.getValue(URL);
+
+            if (uriAtt != null) {
+                try {
+                    uri = new URI(uriAtt);
+                } catch (URISyntaxException e) {
+                    throw new SAXParseException("Invalid uri attribute " + uriAtt + "("
+                            + e.getMessage() + ")", getLocator());
+                }
+            }
+            if (uri != null && urlAtt != null) {
+                try {
+                    uri = new URI(urlAtt);
+                } catch (URISyntaxException e) {
+                    throw new SAXParseException("Invalid url attribute " + urlAtt + "("
+                            + e.getMessage() + ")", getLocator());
+                }
+            }
+        }
+    }
+
+    private class UnitsHandler extends DelegatingHandler {
+
+        private static final String UNITS = "units";
+
+        private static final String SIZE = "size";
+
+        List<BundleInfo> bundles;
+
+        public UnitsHandler() {
+            super(UNITS);
+            addChild(new UnitHandler(), new ChildElementHandler<UnitHandler>() {
+                public void childHanlded(UnitHandler child) {
+                    if (child.bundleInfo != null && !child.bundleInfo.getCapabilities().isEmpty()) {
+                        bundles.add(child.bundleInfo);
+                    }
+                }
+            });
+        }
+
+        protected void handleAttributes(Attributes atts) {
+            int size = Integer.parseInt(atts.getValue(SIZE));
+            bundles = new ArrayList<BundleInfo>(size);
+        }
+
+    }
+
+    class UnitHandler extends DelegatingHandler {
+
+        private static final String CATEGORY_PROPERTY = "org.eclipse.equinox.p2.type.category";
+
+        private static final String UNIT = "unit";
+
+        private static final String ID = "id";
+
+        private static final String VERSION = "version";
+
+        // private static final String SINGLETON = "singleton";
+
+        BundleInfo bundleInfo;
+
+        public UnitHandler() {
+            super(UNIT);
+            // addChild(new UpdateHandler(), new ChildElementHandler() {
+            // public void childHanlded(DelegetingHandler child) {
+            // }
+            // });
+            addChild(new PropertiesHandler(CATEGORY_PROPERTY),
+                new ChildElementHandler<PropertiesHandler>() {
+                    public void childHanlded(PropertiesHandler child) {
+                        String category = child.properties.get(CATEGORY_PROPERTY);
+                        if (category != null && Boolean.valueOf(category).booleanValue()) {
+                            // this is a category definition, this is useless, skip this unit
+                            child.getParent().skip();
+                            bundleInfo = null;
+                        }
+                    }
+                });
+            addChild(new ProvidesHandler(), new ChildElementHandler<ProvidesHandler>() {
+                public void childHanlded(ProvidesHandler child) {
+                    if ("source".equals(child.eclipseType)) {
+                        // this is some source of some bundle
+                        bundleInfo.setSource(true);
+                        // we need to parse the manifest in the toupointData to figure out the
+                        // targeted bundle
+                        // in case we won't have the proper data in the manifest, prepare the source
+                        // data from the convention
+                        String symbolicName = bundleInfo.getSymbolicName();
+                        if (symbolicName.endsWith(".source")) {
+                            bundleInfo.setSymbolicNameTarget(symbolicName.substring(0,
+                                symbolicName.length() - 7));
+                            bundleInfo.setVersionTarget(bundleInfo.getVersion());
+                        }
+                    }
+                    for (BundleCapability capability : child.capabilities) {
+                        bundleInfo.addCapability(capability);
+                    }
+                }
+            });
+            addChild(new FilterHandler(), new ChildElementHandler<FilterHandler>() {
+                public void childHanlded(FilterHandler child) {
+                }
+            });
+            addChild(new RequiresHandler(), new ChildElementHandler<RequiresHandler>() {
+                public void childHanlded(RequiresHandler child) {
+                    for (BundleRequirement requirement : child.requirements) {
+                        bundleInfo.addRequirement(requirement);
+                    }
+                }
+            });
+            addChild(new HostRequirementsHandler(),
+                new ChildElementHandler<HostRequirementsHandler>() {
+                    public void childHanlded(HostRequirementsHandler child) {
+                    }
+                });
+            addChild(new MetaRequirementsHandler(),
+                new ChildElementHandler<MetaRequirementsHandler>() {
+                    public void childHanlded(MetaRequirementsHandler child) {
+                    }
+                });
+            addChild(new ArtifactsHandler(), new ChildElementHandler<ArtifactsHandler>() {
+                public void childHanlded(ArtifactsHandler child) {
+                }
+            });
+            // addChild(new TouchpointHandler(), new ChildElementHandler() {
+            // public void childHanlded(DelegetingHandler child) {
+            // }
+            // });
+            addChild(new TouchpointDataHandler(), new ChildElementHandler<TouchpointDataHandler>() {
+                public void childHanlded(TouchpointDataHandler child) throws SAXParseException {
+                    if (child.zipped != null) {
+                        bundleInfo.setHasInnerClasspath(child.zipped.booleanValue());
+                    }
+                    if (!bundleInfo.isSource()) {
+                        // we only care about parsing the manifest if it is a source
+                        return;
+                    }
+                    if (child.manifest != null) {
+                        // Eclipse may have serialized a little bit weirdly
+                        String manifest = ManifestParser.formatLines(child.manifest.trim());
+                        BundleInfo embeddedInfo;
+                        try {
+                            embeddedInfo = ManifestParser.parseManifest(manifest);
+                        } catch (IOException e) {
+                            if (logLevel >= Message.MSG_VERBOSE) {
+                                Message.verbose(
+                                    "The Manifest of the source bundle "
+                                            + bundleInfo.getSymbolicName() + " could not be parsed",
+                                    e);
+                            }
+                            return;
+                        } catch (ParseException e) {
+                            if (logLevel >= Message.MSG_VERBOSE) {
+                                Message.verbose(
+                                    "The Manifest of the source bundle "
+                                            + bundleInfo.getSymbolicName() + " is ill formed", e);
+                            }
+                            return;
+                        }
+                        if (!embeddedInfo.isSource()) {
+                            if (logLevel >= Message.MSG_VERBOSE) {
+                                Message.verbose("The Manifest of the source bundle "
+                                        + bundleInfo.getSymbolicName()
+                                        + " is not declaring being a source.");
+                            }
+                            return;
+                        }
+                        String symbolicNameTarget = embeddedInfo.getSymbolicNameTarget();
+                        if (symbolicNameTarget == null) {
+                            if (logLevel >= Message.MSG_VERBOSE) {
+                                Message.verbose("The Manifest of the source bundle "
+                                        + bundleInfo.getSymbolicName()
+                                        + " is not declaring a target symbolic name.");
+                            }
+                            return;
+                        }
+                        Version versionTarget = embeddedInfo.getVersionTarget();
+                        if (versionTarget == null) {
+                            if (logLevel >= Message.MSG_VERBOSE) {
+                                Message.verbose("The Manifest of the source bundle "
+                                        + bundleInfo.getSymbolicName()
+                                        + " is not declaring a target version.");
+                            }
+                            return;
+                        }
+                        bundleInfo.setSymbolicNameTarget(symbolicNameTarget);
+                        bundleInfo.setVersionTarget(versionTarget);
+                    }
+                }
+            });
+            // addChild(new LicensesHandler(), new ChildElementHandler() {
+            // public void childHanlded(DelegetingHandler child) {
+            // }
+            // });
+            // addChild(new CopyrightHandler(), new ChildElementHandler() {
+            // public void childHanlded(DelegetingHandler child) {
+            // }
+            // });
+            // addChild(new ChangesHandler(), new ChildElementHandler() {
+            // public void childHanlded(DelegetingHandler child) {
+            // }
+            // });
+
+        }
+
+        protected void handleAttributes(Attributes atts) throws SAXException {
+            String id = atts.getValue(ID);
+            String version = atts.getValue(VERSION);
+            // Boolean singleton = Boolean.valueOf(atts.getValue(SINGLETON));
+            try {
+                bundleInfo = new BundleInfo(id, new Version(version));
+            } catch (ParseException e) {
+                throw new SAXException("Incorrect version on bundle '" + id + "': " + version
+                        + " (" + e.getMessage() + ")");
+            }
+        }
+
+    }
+
+    // static class UpdateHandler extends DelegetingHandler {
+    //
+    // private static final String UPDATE = "update";
+    //
+    // private static final String ID = "id";
+    //
+    // private static final String RANGE = "range";
+    //
+    // private static final String SEVERITY = "severity";
+    //
+    // public UpdateHandler() {
+    // super(UPDATE);
+    // }
+    //
+    // protected void handleAttributes(Attributes atts) {
+    // String id = atts.getValue(ID);
+    // String range = atts.getValue(RANGE);
+    // String severity = atts.getValue(SEVERITY);
+    // }
+    //
+    // }
+
+    private static class FilterHandler extends DelegatingHandler {
+
+        private static final String FILTER = "filter";
+
+        public FilterHandler() {
+            super(FILTER);
+            setBufferingChar(true);
+        }
+
+    }
+
+    private static String namespace2Type(String namespace) {
+        if (namespace.equals("java.package")) {
+            return BundleInfo.PACKAGE_TYPE;
+        }
+        if (namespace.equals("osgi.bundle")) {
+            return BundleInfo.BUNDLE_TYPE;
+        }
+        return null;
+    }
+
+    private class ProvidesHandler extends DelegatingHandler {
+
+        private static final String PROVIDES = "provides";
+
+        private static final String SIZE = "size";
+
+        List<BundleCapability> capabilities;
+
+        String eclipseType;
+
+        public ProvidesHandler() {
+            super(PROVIDES);
+            addChild(new ProvidedHandler(), new ChildElementHandler<ProvidedHandler>() {
+                public void childHanlded(ProvidedHandler child) {
+                    if (child.namespace.equals("org.eclipse.equinox.p2.eclipse.type")) {
+                        eclipseType = child.name;
+                    } else {
+                        String type = namespace2Type(child.namespace);
+                        if (type == null) {
+                            if (logLevel >= Message.MSG_DEBUG) {
+                                Message.debug("Unsupported provided capability " + child.namespace
+                                        + " " + child.name + " " + child.version);
+                            }
+                            return;
+                        }
+                        BundleCapability capability;
+                        if (type == BundleInfo.PACKAGE_TYPE) {
+                            capability = new ExportPackage(child.name, child.version);
+                        } else {
+                            capability = new BundleCapability(type, child.name, child.version);
+                        }
+                        capabilities.add(capability);
+                    }
+                }
+            });
+        }
+
+        protected void handleAttributes(Attributes atts) {
+            int size = Integer.parseInt(atts.getValue(SIZE));
+            capabilities = new ArrayList<BundleCapability>(size);
+        }
+
+    }
+
+    private static class ProvidedHandler extends DelegatingHandler {
+
+        private static final String PROVIDED = "provided";
+
+        private static final String NAMESPACE = "namespace";
+
+        private static final String NAME = "name";
+
+        private static final String VERSION = "version";
+
+        String namespace;
+
+        String name;
+
+        Version version;
+
+        public ProvidedHandler() {
+            super(PROVIDED);
+        }
+
+        protected void handleAttributes(Attributes atts) throws SAXException {
+            namespace = atts.getValue(NAMESPACE);
+            name = atts.getValue(NAME);
+            try {
+                version = new Version(atts.getValue(VERSION));
+            } catch (ParseException e) {
+                throw new SAXException("Incorrect version on provided capability: " + version
+                        + " (" + e.getMessage() + ")");
+            }
+        }
+
+    }
+
+    abstract class AbstractRequirementHandler extends DelegatingHandler {
+
+        private static final String SIZE = "size";
+
+        List<BundleRequirement> requirements;
+
+        public AbstractRequirementHandler(String name) {
+            super(name);
+            addChild(new RequiredHandler(), new ChildElementHandler<RequiredHandler>() {
+                public void childHanlded(RequiredHandler child) {
+                    String name = child.name;
+                    VersionRange range = child.range;
+                    String type = namespace2Type(child.namespace);
+                    if (type == null) {
+                        if (logLevel >= Message.MSG_DEBUG) {
+                            Message.debug("Unsupported required capability " + child.namespace
+                                    + " " + name + " " + range);
+                        }
+                    } else {
+                        String resolution = child.optional ? "optional" : null;
+                        requirements.add(new BundleRequirement(type, name, range, resolution));
+                    }
+                }
+            });
+        }
+
+        protected void handleAttributes(Attributes atts) {
+            int size = Integer.parseInt(atts.getValue(SIZE));
+            requirements = new ArrayList<BundleRequirement>(size);
+        }
+
+    }
+
+    private class RequiresHandler extends AbstractRequirementHandler {
+
+        private static final String REQUIRES = "requires";
+
+        public RequiresHandler() {
+            super(REQUIRES);
+        }
+
+    }
+
+    private class RequiredHandler extends DelegatingHandler {
+
+        private static final String REQUIRED = "required";
+
+        private static final String NAMESPACE = "namespace";
+
+        private static final String NAME = "name";
+
+        private static final String RANGE = "range";
+
+        private static final String OPTIONAL = "optional";
+
+        // private static final String GREEDY = "greedy";
+
+        String namespace;
+
+        String name;
+
+        VersionRange range;
+
+        // String filter;
+        //
+        // boolean greedy;
+
+        boolean optional;
+
+        public RequiredHandler() {
+            super(REQUIRED);
+            // addChild(new FilterHandler(), new ChildElementHandler<FilterHandler>() {
+            // public void childHanlded(FilterHandler child) {
+            // filter = child.getBufferedChars().trim();
+            // }
+            // });
+        }
+
+        protected void handleAttributes(Attributes atts) throws SAXParseException {
+            namespace = atts.getValue(NAMESPACE);
+            name = atts.getValue(NAME);
+            try {
+                range = new VersionRange(atts.getValue(RANGE));
+            } catch (ParseException e) {
+                throw new RuntimeException(e);
+            }
+            // greedy = getOptionalBooleanAttribute(atts, GREEDY, Boolean.TRUE).booleanValue();
+            optional = getOptionalBooleanAttribute(atts, OPTIONAL, Boolean.FALSE).booleanValue();
+        }
+
+    }
+
+    private class HostRequirementsHandler extends AbstractRequirementHandler {
+
+        private static final String HOST_REQUIREMENTS = "hostRequirements";
+
+        public HostRequirementsHandler() {
+            super(HOST_REQUIREMENTS);
+        }
+
+    }
+
+    private class MetaRequirementsHandler extends AbstractRequirementHandler {
+
+        private static final String META_REQUIREMENTS = "metaRequirements";
+
+        public MetaRequirementsHandler() {
+            super(META_REQUIREMENTS);
+        }
+
+    }
+
+    private class ArtifactsHandler extends DelegatingHandler {
+
+        private static final String ARTIFACTS = "artifacts";
+
+        private static final String SIZE = "size";
+
+        List<P2Artifact> artifacts;
+
+        public ArtifactsHandler() {
+            super(ARTIFACTS);
+            addChild(new ArtifactHandler(), new ChildElementHandler<ArtifactHandler>() {
+                public void childHanlded(ArtifactHandler child) {
+                    artifacts.add(child.artifact);
+                }
+            });
+        }
+
+        protected void handleAttributes(Attributes atts) {
+            int size = Integer.parseInt(atts.getValue(SIZE));
+            artifacts = new ArrayList<P2Artifact>(size);
+        }
+
+    }
+
+    private class ArtifactHandler extends DelegatingHandler {
+
+        private static final String ARTIFACT = "artifact";
+
+        private static final String ID = "id";
+
+        private static final String VERSION = "version";
+
+        private static final String CLASSIFIER = "classifier";
+
+        P2Artifact artifact;
+
+        public ArtifactHandler() {
+            super(ARTIFACT);
+        }
+
+        protected void handleAttributes(Attributes atts) throws SAXException {
+            String id = atts.getValue(ID);
+            String version = atts.getValue(VERSION);
+            String classifier = atts.getValue(CLASSIFIER);
+            try {
+                artifact = new P2Artifact(id, new Version(version), classifier);
+            } catch (ParseException e) {
+                throw new SAXException("Incorrect version on artifact '" + id + "': " + version
+                        + " (" + e.getMessage() + ")");
+            }
+        }
+
+    }
+
+    // private static class TouchpointHandler extends DelegetingHandler {
+    //
+    // private static final String TOUCHPOINT = "touchpoint";
+    //
+    // private static final String ID = "id";
+    //
+    // private static final String VERSION = "version";
+    //
+    // public TouchpointHandler() {
+    // super(TOUCHPOINT);
+    // }
+    //
+    // protected void handleAttributes(Attributes atts) {
+    // String id = atts.getValue(ID);
+    // String version = atts.getValue(VERSION);
+    // }
+    //
+    // }
+
+    private class TouchpointDataHandler extends DelegatingHandler {
+
+        private static final String TOUCHPOINTDATA = "touchpointData";
+
+        // private static final String SIZE = "size";
+
+        String manifest;
+
+        Boolean zipped;
+
+        public TouchpointDataHandler() {
+            super(TOUCHPOINTDATA);
+            addChild(new InstructionsHandler(), new ChildElementHandler<InstructionsHandler>() {
+                public void childHanlded(InstructionsHandler child) {
+                    manifest = child.manifest;
+                    zipped = child.zipped;
+                }
+            });
+        }
+
+        protected void handleAttributes(Attributes atts) {
+            // String size = atts.getValue(SIZE);
+        }
+
+    }
+
+    private class InstructionsHandler extends DelegatingHandler {
+
+        private static final String INSTRUCTIONS = "instructions";
+
+        // private static final String SIZE = "size";
+
+        String manifest;
+
+        Boolean zipped;
+
+        public InstructionsHandler() {
+            super(INSTRUCTIONS);
+            addChild(new InstructionHandler(), new ChildElementHandler<InstructionHandler>() {
+                public void childHanlded(InstructionHandler child) {
+                    manifest = null;
+                    zipped = null;
+                    String buffer = child.getBufferedChars().trim();
+                    if ("manifest".equals(child.key)) {
+                        manifest = buffer;
+                    } else if ("zipped".equals(child.key) && buffer.length() != 0) {
+                        zipped = Boolean.valueOf(buffer);
+                    }
+                }
+            });
+        }
+
+        protected void handleAttributes(Attributes atts) {
+            // String size = atts.getValue(SIZE);
+        }
+
+    }
+
+    private class InstructionHandler extends DelegatingHandler {
+
+        private static final String INSTRUCTION = "instruction";
+
+        private static final String KEY = "key";
+
+        String key;
+
+        public InstructionHandler() {
+            super(INSTRUCTION);
+            setBufferingChar(true);
+        }
+
+        protected void handleAttributes(Attributes atts) {
+            key = atts.getValue(KEY);
+        }
+
+    }
+
+    // private static class LicensesHandler extends DelegetingHandler {
+    //
+    // private static final String LICENSES = "licenses";
+    //
+    // private static final String SIZE = "size";
+    //
+    // public LicensesHandler() {
+    // super(LICENSES);
+    // addChild(new LicenseHandler(), new ChildElementHandler() {
+    // public void childHanlded(DelegetingHandler child) {
+    // }
+    // });
+    // }
+    //
+    // protected void handleAttributes(Attributes atts) {
+    // String size = atts.getValue(SIZE);
+    // }
+    //
+    // }
+
+    // private static class LicenseHandler extends DelegetingHandler {
+    //
+    // private static final String LICENSE = "license";
+    //
+    // private static final String URI = "uri";
+    //
+    // private static final String URL = "url";
+    //
+    // public LicenseHandler() {
+    // super(LICENSE);
+    // setBufferingChar(true);
+    // }
+    //
+    // protected void handleAttributes(Attributes atts) {
+    // String uri = atts.getValue(URI);
+    // String url = atts.getValue(URL);
+    // }
+    //
+    // }
+
+    // private static class CopyrightHandler extends DelegetingHandler {
+    //
+    // private static final String COPYRIGHT = "copyright";
+    //
+    // private static final String URI = "uri";
+    //
+    // private static final String URL = "url";
+    //
+    // public CopyrightHandler() {
+    // super(COPYRIGHT);
+    // }
+    //
+    // protected void handleAttributes(Attributes atts) {
+    // String uri = atts.getValue(URI);
+    // String url = atts.getValue(URL);
+    // }
+    //
+    // }
+
+    // private class ChangesHandler extends DelegetingHandler {
+    //
+    // private static final String CHANGES = "changes";
+    //
+    // private static final String SIZE = "size";
+    //
+    // public ChangesHandler() {
+    // super(CHANGES);
+    // addChild(new ChangeHandler(), new ChildElementHandler<ChangeHandler>() {
+    // public void childHanlded(ChangeHandler child) {
+    // }
+    // });
+    // }
+    //
+    // protected void handleAttributes(Attributes atts) {
+    // int size = Integer.parseInt(atts.getValue(SIZE));
+    // }
+    // }
+
+    // private class ChangeHandler extends DelegetingHandler {
+    //
+    // private static final String CHANGE = "change";
+    //
+    // public ChangeHandler() {
+    // super(CHANGE);
+    // }
+    // }
+
+    // private class FromHandler extends AbstractRequirementHandler {
+    //
+    // private static final String FROM = "from";
+    //
+    // public FromHandler() {
+    // super(FROM);
+    // }
+    //
+    // }
+
+    // private class ToHandler extends AbstractRequirementHandler {
+    //
+    // private static final String TO = "to";
+    //
+    // public ToHandler() {
+    // super(TO);
+    // }
+    //
+    // }
+
+    // private class PatchScopeHandler extends DelegetingHandler {
+    //
+    // private static final String PATCH_SCOPE = "patchScope";
+    //
+    // private static final String SIZE = "size";
+    //
+    // public PatchScopeHandler() {
+    // super(PATCH_SCOPE);
+    // addChild(new PatchScopeHandler(), new ChildElementHandler<PatchScopeHandler>() {
+    // public void childHanlded(PatchScopeHandler child) {
+    // }
+    // });
+    // }
+    //
+    // protected void handleAttributes(Attributes atts) {
+    // int size = Integer.parseInt(atts.getValue(SIZE));
+    // }
+    // }
+
+    // private class ScopeHandler extends DelegetingHandler {
+    //
+    // private static final String SCOPE = "scope";
+    //
+    // public ScopeHandler() {
+    // super(SCOPE);
+    // addChild(new RequiresHandler(), new ChildElementHandler<RequiresHandler>() {
+    // public void childHanlded(RequiresHandler child) {
+    // }
+    // });
+    // }
+    // }
+
+    // private class LifeCycleHandler extends AbstractRequirementHandler {
+    //
+    // private static final String LIFE_CYCLE = "lifeCycle";
+    //
+    // public LifeCycleHandler() {
+    // super(LIFE_CYCLE);
+    // }
+    // }
+
+}
diff --git a/src/java/org/apache/ivy/osgi/p2/PropertiesParser.java b/src/java/org/apache/ivy/osgi/p2/PropertiesParser.java
new file mode 100644
index 0000000..60cb96b
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/p2/PropertiesParser.java
@@ -0,0 +1,80 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.p2;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.ivy.osgi.util.DelegatingHandler;
+import org.xml.sax.Attributes;
+
+public class PropertiesParser {
+
+    static class PropertiesHandler extends DelegatingHandler {
+
+        private static final String PROPERTIES = "properties";
+
+        private static final String SIZE = "size";
+
+        Map<String, String> properties;
+
+        public PropertiesHandler(String... props) {
+            super(PROPERTIES);
+            final List<String> propList = Arrays.asList(props);
+            addChild(new PropertyHandler(), new ChildElementHandler<PropertyHandler>() {
+                public void childHanlded(PropertyHandler child) {
+                    if (propList.isEmpty() || propList.contains(child.name)) {
+                        properties.put(child.name, child.value);
+                    }
+                }
+            });
+        }
+
+        protected void handleAttributes(Attributes atts) {
+            int size = Integer.parseInt(atts.getValue(SIZE));
+            properties = new HashMap<String, String>(size);
+        }
+
+    }
+
+    static class PropertyHandler extends DelegatingHandler {
+
+        private static final String PROPERTY = "property";
+
+        private static final String NAME = "name";
+
+        private static final String VALUE = "value";
+
+        String name;
+
+        String value;
+
+        public PropertyHandler() {
+            super(PROPERTY);
+        }
+
+        protected void handleAttributes(Attributes atts) {
+            name = atts.getValue(NAME);
+            value = atts.getValue(VALUE);
+        }
+
+    }
+
+}
diff --git a/src/java/org/apache/ivy/osgi/p2/XMLInputParser.java b/src/java/org/apache/ivy/osgi/p2/XMLInputParser.java
new file mode 100644
index 0000000..4d79f41
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/p2/XMLInputParser.java
@@ -0,0 +1,30 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.p2;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.text.ParseException;
+
+import org.xml.sax.SAXException;
+
+public interface XMLInputParser {
+
+    public void parse(InputStream in) throws ParseException, IOException, SAXException;
+
+}
diff --git a/src/java/org/apache/ivy/osgi/repo/AbstractFSManifestIterable.java b/src/java/org/apache/ivy/osgi/repo/AbstractFSManifestIterable.java
new file mode 100644
index 0000000..0318f67
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/repo/AbstractFSManifestIterable.java
@@ -0,0 +1,155 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.repo;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Stack;
+import java.util.jar.JarInputStream;
+import java.util.jar.Manifest;
+
+import org.apache.ivy.util.Message;
+
+// T is the type of the resource "path"
+public abstract class AbstractFSManifestIterable<T> implements Iterable<ManifestAndLocation> {
+
+    private final T root;
+
+    public AbstractFSManifestIterable(T root) {
+        this.root = root;
+    }
+
+    public Iterator<ManifestAndLocation> iterator() {
+        return new FSManifestIterator();
+    }
+
+    abstract protected List<T> listBundleFiles(T dir) throws IOException;
+
+    abstract protected List<T> listDirs(T dir) throws IOException;
+
+    abstract protected InputStream getInputStream(T f) throws IOException;
+
+    abstract protected URI buildBundleURI(T location) throws IOException;
+
+    class FSManifestIterator implements Iterator<ManifestAndLocation> {
+
+        private ManifestAndLocation next = null;
+
+        /**
+         * Stack of list of directories. An iterator in the stack represents the current directory
+         * being lookup. The first element in the stack is the root directory. The next element in
+         * the stack is an iterator on the children on the root. The last iterator in the stack
+         * points to {@link #currentDir}.
+         */
+        private Stack<Iterator<T>> dirs = new Stack<Iterator<T>>();
+
+        /**
+         * The bundles files being lookup.
+         */
+        private Iterator<T> bundleCandidates = null;
+
+        private T currentDir = null;
+
+        FSManifestIterator() {
+            dirs.add(Collections.<T> singleton(root).iterator());
+        }
+
+        /**
+         * Deep first tree lookup for the directories and the bundles are searched on each found
+         * directory.
+         */
+        public boolean hasNext() {
+            while (next == null) {
+                // no current directory
+                if (currentDir == null) {
+                    // so get the next one
+                    if (dirs.peek().hasNext()) {
+                        currentDir = dirs.peek().next();
+                        try {
+                            bundleCandidates = listBundleFiles(currentDir).iterator();
+                        } catch (IOException e) {
+                            Message.warn("Unlistable dir: " + currentDir, e);
+                            currentDir = null;
+                        }
+                    } else if (dirs.size() <= 1) {
+                        // no next directory, but we are at the root: finished
+                        return false;
+                    } else {
+                        // remove the top of the stack and continue with a sibling.
+                        dirs.pop();
+                    }
+                } else if (bundleCandidates.hasNext()) {
+                    T bundleCandidate = bundleCandidates.next();
+                    JarInputStream in = null;
+                    try {
+                        in = new JarInputStream(getInputStream(bundleCandidate));
+                        Manifest manifest = in.getManifest();
+                        if (manifest != null) {
+                            next = new ManifestAndLocation(manifest,
+                                    buildBundleURI(bundleCandidate), null);
+                        } else {
+                            Message.debug("No manifest in jar: " + bundleCandidate);
+                        }
+                    } catch (FileNotFoundException e) {
+                        Message.debug("Jar file just removed: " + bundleCandidate, e);
+                    } catch (IOException e) {
+                        Message.warn("Unreadable jar: " + bundleCandidate, e);
+                    } finally {
+                        if (in != null) {
+                            try {
+                                in.close();
+                            } catch (IOException e) {
+                                // Don't care
+                            }
+                        }
+                    }
+                } else {
+                    // no more candidate on the current directory
+                    // so lookup in the children directories
+                    try {
+                        dirs.add(listDirs(currentDir).iterator());
+                    } catch (IOException e) {
+                        Message.warn("Unlistable dir: " + currentDir + " (" + e + ")");
+                        dirs.add(Collections.<T> emptyList().iterator());
+                    }
+                    currentDir = null;
+                }
+            }
+            return true;
+        }
+
+        public ManifestAndLocation next() {
+            if (!hasNext()) {
+                throw new NoSuchElementException();
+            }
+            ManifestAndLocation manifest = next;
+            next = null;
+            return manifest;
+        }
+
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+    }
+}
diff --git a/src/java/org/apache/ivy/osgi/repo/AbstractOSGiResolver.java b/src/java/org/apache/ivy/osgi/repo/AbstractOSGiResolver.java
new file mode 100644
index 0000000..45e92ed
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/repo/AbstractOSGiResolver.java
@@ -0,0 +1,540 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.repo;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.Configuration;
+import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
+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.apache.ivy.core.report.DownloadStatus;
+import org.apache.ivy.core.report.MetadataArtifactDownloadReport;
+import org.apache.ivy.core.resolve.IvyNode;
+import org.apache.ivy.core.resolve.ResolveData;
+import org.apache.ivy.core.resolve.ResolvedModuleRevision;
+import org.apache.ivy.osgi.core.BundleInfo;
+import org.apache.ivy.osgi.core.BundleInfoAdapter;
+import org.apache.ivy.osgi.util.Version;
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.plugins.repository.url.URLRepository;
+import org.apache.ivy.plugins.repository.url.URLResource;
+import org.apache.ivy.plugins.resolver.BasicResolver;
+import org.apache.ivy.plugins.resolver.util.MDResolvedResource;
+import org.apache.ivy.plugins.resolver.util.ResolvedResource;
+import org.apache.ivy.plugins.resolver.util.ResourceMDParser;
+import org.apache.ivy.util.Message;
+
+public abstract class AbstractOSGiResolver extends BasicResolver {
+
+    private static final String CAPABILITY_EXTRA_ATTR = "osgi_bundle";
+
+    protected static final RepoDescriptor FAILING_REPO_DESCRIPTOR = new EditableRepoDescriptor(
+            null, null);
+
+    private RepoDescriptor repoDescriptor = null;
+
+    private URLRepository repository = new URLRepository();
+
+    public static class RequirementStrategy {
+        // take the first matching
+        public static RequirementStrategy first = new RequirementStrategy();
+
+        // if there are any ambiguity, fail to resolve
+        public static RequirementStrategy noambiguity = new RequirementStrategy();
+
+        public static RequirementStrategy valueOf(String strategy) {
+            if (strategy.equals("first")) {
+                return first;
+            }
+            if (strategy.equals("noambiguity")) {
+                return noambiguity;
+            }
+            throw new IllegalStateException();
+        }
+    }
+
+    private RequirementStrategy requirementStrategy = RequirementStrategy.noambiguity;
+
+    public void setRequirementStrategy(RequirementStrategy importPackageStrategy) {
+        this.requirementStrategy = importPackageStrategy;
+    }
+
+    public void setRequirementStrategy(String strategy) {
+        setRequirementStrategy(RequirementStrategy.valueOf(strategy));
+    }
+
+    protected void setRepoDescriptor(RepoDescriptor repoDescriptor) {
+        this.repoDescriptor = repoDescriptor;
+    }
+
+    public URLRepository getRepository() {
+        return repository;
+    }
+
+    protected void ensureInit() {
+        if (repoDescriptor == null) {
+            try {
+                init();
+            } catch (Exception e) {
+                repoDescriptor = FAILING_REPO_DESCRIPTOR;
+                throw new RuntimeException("Error while loading the OSGi repo descriptor"
+                        + e.getMessage() + " (" + e.getClass().getName() + ")", e);
+            }
+        } else if (repoDescriptor == FAILING_REPO_DESCRIPTOR) {
+            throw new RuntimeException("The repository " + getName() + " already failed to load");
+        }
+    }
+
+    abstract protected void init();
+
+    public RepoDescriptor getRepoDescriptor() {
+        ensureInit();
+        return repoDescriptor;
+    }
+
+    @Override
+    public boolean isAllownomd() {
+        // this a repo based resolver, we always have md
+        return false;
+    }
+
+    public ResolvedResource findIvyFileRef(DependencyDescriptor dd, ResolveData data) {
+        ModuleRevisionId mrid = dd.getDependencyRevisionId();
+
+        String osgiType = mrid.getOrganisation();
+        if (osgiType == null) {
+            throw new RuntimeException("Unsupported OSGi module Id: " + mrid.getModuleId());
+        }
+        String id = mrid.getName();
+        Collection<ModuleDescriptor> mds = ModuleDescriptorWrapper.unwrap(getRepoDescriptor()
+                .findModules(osgiType, id));
+        if (mds == null || mds.isEmpty()) {
+            Message.verbose("\t " + id + " not found.");
+            return null;
+        }
+
+        ResolvedResource[] ret;
+        if (BundleInfo.BUNDLE_TYPE.equals(osgiType)) {
+            ret = findBundle(dd, data, mds);
+        } else {
+            ret = findCapability(dd, data, mds);
+        }
+
+        ResolvedResource found = findResource(ret, getDefaultRMDParser(dd.getDependencyId()), mrid,
+            data.getDate());
+        if (found == null) {
+            Message.debug("\t" + getName() + ": no resource found for " + mrid);
+        }
+        return found;
+    }
+
+    public ResolvedResource[] findBundle(DependencyDescriptor dd, ResolveData data,
+            Collection<ModuleDescriptor> mds) {
+        ResolvedResource[] ret = new ResolvedResource[mds.size()];
+        int i = 0;
+        for (ModuleDescriptor md : mds) {
+            MetadataArtifactDownloadReport report = new MetadataArtifactDownloadReport(null);
+            report.setDownloadStatus(DownloadStatus.NO);
+            report.setSearched(true);
+            ResolvedModuleRevision rmr = new ResolvedModuleRevision(this, this, md, report);
+            MDResolvedResource mdrr = new MDResolvedResource(null, md.getRevision(), rmr);
+            ret[i++] = mdrr;
+        }
+        return ret;
+    }
+
+    public ResolvedResource[] findCapability(DependencyDescriptor dd, ResolveData data,
+            Collection<ModuleDescriptor> mds) {
+        ResolvedResource[] ret = new ResolvedResource[mds.size()];
+        int i = 0;
+        for (ModuleDescriptor md : mds) {
+            IvyNode node = data.getNode(md.getModuleRevisionId());
+            if (node != null && node.getDescriptor() != null) {
+                // already resolved import, no need to go further
+                return new ResolvedResource[] {buildResolvedCapabilityMd(dd, node.getDescriptor())};
+            }
+            ret[i++] = buildResolvedCapabilityMd(dd, md);
+        }
+        return ret;
+    }
+
+    private MDResolvedResource buildResolvedCapabilityMd(DependencyDescriptor dd,
+            ModuleDescriptor md) {
+        String org = dd.getDependencyRevisionId().getOrganisation();
+        String name = dd.getDependencyRevisionId().getName();
+        String rev = md.getExtraInfoContentByTagName(BundleInfoAdapter.EXTRA_INFO_EXPORT_PREFIX
+                + name);
+        ModuleRevisionId capabilityRev = ModuleRevisionId.newInstance(org, name, rev,
+            Collections.singletonMap(CAPABILITY_EXTRA_ATTR, md.getModuleRevisionId().toString()));
+
+        DefaultModuleDescriptor capabilityMd = new DefaultModuleDescriptor(capabilityRev,
+                getSettings().getStatusManager().getDefaultStatus(), new Date());
+
+        String useConf = BundleInfoAdapter.CONF_USE_PREFIX + dd.getDependencyRevisionId().getName();
+
+        capabilityMd.addConfiguration(BundleInfoAdapter.CONF_DEFAULT);
+        capabilityMd.addConfiguration(BundleInfoAdapter.CONF_OPTIONAL);
+        capabilityMd.addConfiguration(BundleInfoAdapter.CONF_TRANSITIVE_OPTIONAL);
+        capabilityMd.addConfiguration(new Configuration(useConf));
+
+        DefaultDependencyDescriptor capabilityDD = new DefaultDependencyDescriptor(
+                md.getModuleRevisionId(), false);
+        capabilityDD.addDependencyConfiguration(BundleInfoAdapter.CONF_NAME_DEFAULT,
+            BundleInfoAdapter.CONF_NAME_DEFAULT);
+        capabilityDD.addDependencyConfiguration(BundleInfoAdapter.CONF_NAME_OPTIONAL,
+            BundleInfoAdapter.CONF_NAME_OPTIONAL);
+        capabilityDD.addDependencyConfiguration(BundleInfoAdapter.CONF_NAME_TRANSITIVE_OPTIONAL,
+            BundleInfoAdapter.CONF_NAME_TRANSITIVE_OPTIONAL);
+        capabilityDD.addDependencyConfiguration(useConf, useConf);
+        capabilityMd.addDependency(capabilityDD);
+
+        MetadataArtifactDownloadReport report = new MetadataArtifactDownloadReport(null);
+        report.setDownloadStatus(DownloadStatus.NO);
+        report.setSearched(true);
+        ResolvedModuleRevision rmr = new ResolvedModuleRevision(this, this, capabilityMd, report);
+        return new MDResolvedResource(null, capabilityMd.getRevision(), rmr);
+    }
+
+    @Override
+    public ResolvedResource findResource(ResolvedResource[] rress, ResourceMDParser rmdparser,
+            ModuleRevisionId mrid, Date date) {
+        ResolvedResource found = super.findResource(rress, rmdparser, mrid, date);
+        if (found == null) {
+            return null;
+        }
+
+        String osgiType = mrid.getOrganisation();
+        // for non bundle requirement : log the selected bundle
+        if (!BundleInfo.BUNDLE_TYPE.equals(osgiType)) {
+            // several candidates with different symbolic name : make an warning about the ambiguity
+            if (rress.length != 1) {
+                // several candidates with different symbolic name ?
+                Map<String, List<MDResolvedResource>> matching = new HashMap<String, List<MDResolvedResource>>();
+                for (int i = 0; i < rress.length; i++) {
+                    String name = ((MDResolvedResource) rress[i]).getResolvedModuleRevision()
+                            .getDescriptor().getExtraAttribute(CAPABILITY_EXTRA_ATTR);
+                    List<MDResolvedResource> list = matching.get(name);
+                    if (list == null) {
+                        list = new ArrayList<MDResolvedResource>();
+                        matching.put(name, list);
+                    }
+                    list.add((MDResolvedResource) rress[i]);
+                }
+                if (matching.keySet().size() != 1) {
+                    if (requirementStrategy == RequirementStrategy.first) {
+                        Message.warn("Ambiguity for the '" + osgiType + "' requirement "
+                                + mrid.getName() + ";version=" + mrid.getRevision());
+                        for (Entry<String, List<MDResolvedResource>> entry : matching.entrySet()) {
+                            Message.warn("\t" + entry.getKey());
+                            for (MDResolvedResource c : entry.getValue()) {
+                                Message.warn("\t\t" + c.getRevision()
+                                        + (found == c ? " (selected)" : ""));
+                            }
+                        }
+                    } else if (requirementStrategy == RequirementStrategy.noambiguity) {
+                        Message.error("Ambiguity for the '" + osgiType + "' requirement "
+                                + mrid.getName() + ";version=" + mrid.getRevision());
+                        for (Entry<String, List<MDResolvedResource>> entry : matching.entrySet()) {
+                            Message.error("\t" + entry.getKey());
+                            for (MDResolvedResource c : entry.getValue()) {
+                                Message.error("\t\t" + c.getRevision()
+                                        + (found == c ? " (best match)" : ""));
+                            }
+                        }
+                        return null;
+                    }
+                }
+            }
+            Message.info("'" + osgiType + "' requirement " + mrid.getName() + ";version="
+                    + mrid.getRevision() + " satisfied by "
+                    + ((MDResolvedResource) found).getResolvedModuleRevision().getId().getName()
+                    + ";" + found.getRevision());
+        }
+
+        return found;
+    }
+
+    public ResolvedResource findArtifactRef(Artifact artifact, Date date) {
+        URL url = artifact.getUrl();
+        if (url == null) {
+            // not an artifact resolved by this resolver
+            return null;
+        }
+        Message.verbose("\tusing url for " + artifact + ": " + url);
+        logArtifactAttempt(artifact, url.toExternalForm());
+        Resource resource = new URLResource(url);
+        return new ResolvedResource(resource, artifact.getModuleRevisionId().getRevision());
+    }
+
+    @Override
+    protected void checkModuleDescriptorRevision(ModuleDescriptor systemMd,
+            ModuleRevisionId systemMrid) {
+        String osgiType = systemMrid.getOrganisation();
+        // only check revision if we're searching for a bundle (package and bundle have different
+        // version
+        if (osgiType == null || osgiType.equals(BundleInfo.BUNDLE_TYPE)) {
+            super.checkModuleDescriptorRevision(systemMd, systemMrid);
+        }
+    }
+
+    @Override
+    protected Collection/* <String> */filterNames(Collection/* <String> */names) {
+        getSettings().filterIgnore(names);
+        return names;
+    }
+
+    @Override
+    protected Collection findNames(Map tokenValues, String token) {
+        if (IvyPatternHelper.ORGANISATION_KEY.equals(token)) {
+            return getRepoDescriptor().getCapabilities();
+        }
+
+        String osgiType = (String) tokenValues.get(IvyPatternHelper.ORGANISATION_KEY);
+        if (osgiType == null || osgiType.length() == 0) {
+            return Collections.EMPTY_LIST;
+        }
+
+        if (IvyPatternHelper.MODULE_KEY.equals(token)) {
+            return getRepoDescriptor().getCapabilityValues(osgiType);
+        }
+
+        if (IvyPatternHelper.REVISION_KEY.equals(token)) {
+            String name = (String) tokenValues.get(IvyPatternHelper.MODULE_KEY);
+            List<String> versions = new ArrayList<String>();
+            Set<ModuleDescriptorWrapper> mds = getRepoDescriptor().findModules(osgiType, name);
+            if (mds != null) {
+                for (ModuleDescriptorWrapper md : mds) {
+                    versions.add(md.getBundleInfo().getVersion().toString());
+                }
+            }
+            return versions;
+        }
+
+        if (IvyPatternHelper.CONF_KEY.equals(token)) {
+            String name = (String) tokenValues.get(IvyPatternHelper.MODULE_KEY);
+            if (name == null) {
+                return Collections.EMPTY_LIST;
+            }
+            if (osgiType.equals(BundleInfo.PACKAGE_TYPE)) {
+                return Collections.singletonList(BundleInfoAdapter.CONF_USE_PREFIX + name);
+            }
+            Collection<ModuleDescriptor> mds = ModuleDescriptorWrapper.unwrap(getRepoDescriptor()
+                    .findModules(osgiType, name));
+            if (mds == null) {
+                return Collections.EMPTY_LIST;
+            }
+            String version = (String) tokenValues.get(IvyPatternHelper.REVISION_KEY);
+            if (version == null) {
+                return Collections.EMPTY_LIST;
+            }
+            ModuleDescriptor found = null;
+            for (ModuleDescriptor md : mds) {
+                if (md.getRevision().equals(version)) {
+                    found = md;
+                }
+            }
+            if (found == null) {
+                return Collections.EMPTY_LIST;
+            }
+            List<String> confs = Arrays.asList(found.getConfigurationsNames());
+            return confs;
+        }
+        return Collections.EMPTY_LIST;
+    }
+
+    /**
+     * Populate capabilityValues with capability values for which at least one module match the
+     * expected revision
+     */
+    private void filterCapabilityValues(Set<String> capabilityValues,
+            Map<String, Set<ModuleDescriptor>> moduleByCapbilityValue,
+            Map<String, String> tokenValues, String rev) {
+        if (rev == null) {
+            // no revision, all match then
+            capabilityValues.addAll(moduleByCapbilityValue.keySet());
+        } else {
+            for (Entry<String, Set<ModuleDescriptor>> entry : moduleByCapbilityValue.entrySet()) {
+                Iterator<ModuleDescriptor> itModules = entry.getValue().iterator();
+                boolean moduleMatchRev = false;
+                while (!moduleMatchRev && itModules.hasNext()) {
+                    ModuleDescriptor md = itModules.next();
+                    moduleMatchRev = rev.equals(md.getRevision());
+                }
+                if (moduleMatchRev) {
+                    // at least one module matched, the capability value is ok to add
+                    capabilityValues.add(entry.getKey());
+                }
+            }
+        }
+    }
+
+    @Override
+    public Map[] listTokenValues(String[] tokens, Map criteria) {
+        Set<String> tokenSet = new HashSet<String>(Arrays.asList(tokens));
+        Set<Map<String, String>> listTokenValues = listTokenValues(tokenSet, criteria);
+        return listTokenValues.toArray(new Map[listTokenValues.size()]);
+    }
+
+    private Set<Map<String, String>> listTokenValues(Set<String> tokens,
+            Map<String, String> criteria) {
+        if (tokens.isEmpty()) {
+            return Collections.singleton(criteria);
+        }
+
+        Set<String> remainingTokens = new HashSet<String>(tokens);
+
+        Map<String, String> values = new HashMap<String, String>();
+
+        remainingTokens.remove(IvyPatternHelper.ORGANISATION_KEY);
+        String osgiType = criteria.get(IvyPatternHelper.ORGANISATION_KEY);
+        if (osgiType == null || osgiType.length() == 0) {
+            return Collections.emptySet();
+        }
+        values.put(IvyPatternHelper.ORGANISATION_KEY, osgiType);
+
+        if (osgiType == null) {
+            Set<Map<String, String>> tokenValues = new HashSet<Map<String, String>>();
+            Map<String, String> newCriteria = new HashMap<String, String>(criteria);
+            newCriteria.put(IvyPatternHelper.ORGANISATION_KEY, BundleInfo.BUNDLE_TYPE);
+            tokenValues.addAll(listTokenValues(remainingTokens, newCriteria));
+            newCriteria = new HashMap<String, String>(criteria);
+            newCriteria.put(IvyPatternHelper.ORGANISATION_KEY, BundleInfo.PACKAGE_TYPE);
+            tokenValues.addAll(listTokenValues(remainingTokens, newCriteria));
+            newCriteria = new HashMap<String, String>(criteria);
+            newCriteria.put(IvyPatternHelper.ORGANISATION_KEY, BundleInfo.SERVICE_TYPE);
+            tokenValues.addAll(listTokenValues(remainingTokens, newCriteria));
+            return tokenValues;
+        }
+
+        Set<String> capabilities = getRepoDescriptor().getCapabilityValues(osgiType);
+        if (capabilities == null || capabilities.isEmpty()) {
+            return Collections.emptySet();
+        }
+
+        remainingTokens.remove(IvyPatternHelper.MODULE_KEY);
+        String module = criteria.get(IvyPatternHelper.MODULE_KEY);
+        if (module == null) {
+            Set<Map<String, String>> tokenValues = new HashSet<Map<String, String>>();
+            for (String name : capabilities) {
+                Map<String, String> newCriteria = new HashMap<String, String>(criteria);
+                newCriteria.put(IvyPatternHelper.MODULE_KEY, name);
+                tokenValues.addAll(listTokenValues(remainingTokens, newCriteria));
+            }
+            return tokenValues;
+        }
+        values.put(IvyPatternHelper.MODULE_KEY, module);
+
+        remainingTokens.remove(IvyPatternHelper.REVISION_KEY);
+        String rev = criteria.get(IvyPatternHelper.REVISION_KEY);
+        if (rev == null) {
+            Set<ModuleDescriptorWrapper> mdws = getRepoDescriptor().findModules(osgiType, module);
+            if (mdws == null || mdws.isEmpty()) {
+                return Collections.emptySet();
+            }
+            Set<Map<String, String>> tokenValues = new HashSet<Map<String, String>>();
+            for (ModuleDescriptorWrapper mdw : mdws) {
+                Map<String, String> newCriteria = new HashMap<String, String>(criteria);
+                newCriteria.put(IvyPatternHelper.REVISION_KEY, mdw.getBundleInfo().getVersion()
+                        .toString());
+                tokenValues.addAll(listTokenValues(remainingTokens, newCriteria));
+            }
+            return tokenValues;
+        }
+        values.put(IvyPatternHelper.REVISION_KEY, rev);
+
+        remainingTokens.remove(IvyPatternHelper.CONF_KEY);
+        String conf = criteria.get(IvyPatternHelper.CONF_KEY);
+        if (conf == null) {
+            if (osgiType.equals(BundleInfo.PACKAGE_TYPE)) {
+                values.put(IvyPatternHelper.CONF_KEY, BundleInfoAdapter.CONF_USE_PREFIX + module);
+                return Collections.singleton(values);
+            }
+            Set<ModuleDescriptorWrapper> bundles = getRepoDescriptor()
+                    .findModules(osgiType, module);
+            if (bundles == null) {
+                return Collections.emptySet();
+            }
+            Version v;
+            try {
+                v = new Version(rev);
+            } catch (ParseException e) {
+                return Collections.emptySet();
+            }
+            ModuleDescriptorWrapper found = null;
+            for (ModuleDescriptorWrapper bundle : bundles) {
+                if (bundle.getBundleInfo().getVersion().equals(v)) {
+                    found = bundle;
+                }
+            }
+            if (found == null) {
+                return Collections.emptySet();
+            }
+            Set<Map<String, String>> tokenValues = new HashSet<Map<String, String>>();
+            List<String> configurations = BundleInfoAdapter
+                    .getConfigurations(found.getBundleInfo());
+            for (int i = 0; i < configurations.size(); i++) {
+                Map<String, String> newCriteria = new HashMap<String, String>(criteria);
+                newCriteria.put(IvyPatternHelper.CONF_KEY, configurations.get(i));
+                tokenValues.add(newCriteria);
+            }
+            return tokenValues;
+        }
+        values.put(IvyPatternHelper.CONF_KEY, conf);
+
+        return Collections.singleton(values);
+    }
+
+    protected long get(Resource resource, File dest) throws IOException {
+        Message.verbose("\t" + getName() + ": downloading " + resource.getName());
+        Message.debug("\t\tto " + dest);
+        if (dest.getParentFile() != null) {
+            dest.getParentFile().mkdirs();
+        }
+        getRepository().get(resource.getName(), dest);
+        return dest.length();
+    }
+
+    protected Resource getResource(String source) throws IOException {
+        return getRepository().getResource(source);
+    }
+
+    public void publish(Artifact artifact, File src, boolean overwrite) throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+}
diff --git a/src/java/org/apache/ivy/osgi/repo/AggregatedOSGiResolver.java b/src/java/org/apache/ivy/osgi/repo/AggregatedOSGiResolver.java
new file mode 100644
index 0000000..ad00cf4
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/repo/AggregatedOSGiResolver.java
@@ -0,0 +1,40 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.repo;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class AggregatedOSGiResolver extends AbstractOSGiResolver {
+
+    private List<AbstractOSGiResolver> resolvers = new ArrayList<AbstractOSGiResolver>();
+
+    public void add(AbstractOSGiResolver resolver) {
+        resolvers.add(resolver);
+    }
+
+    @Override
+    protected void init() {
+        List<RepoDescriptor> repos = new ArrayList<RepoDescriptor>();
+        for (AbstractOSGiResolver resolver : resolvers) {
+            repos.add(resolver.getRepoDescriptor());
+        }
+        setRepoDescriptor(new AggregatedRepoDescriptor(repos));
+    }
+
+}
diff --git a/src/java/org/apache/ivy/osgi/repo/AggregatedRepoDescriptor.java b/src/java/org/apache/ivy/osgi/repo/AggregatedRepoDescriptor.java
new file mode 100644
index 0000000..0963bd7
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/repo/AggregatedRepoDescriptor.java
@@ -0,0 +1,112 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.repo;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+public class AggregatedRepoDescriptor extends RepoDescriptor {
+
+    private List<RepoDescriptor> repos;
+
+    public AggregatedRepoDescriptor(List<RepoDescriptor> repos) {
+        this.repos = repos;
+    }
+
+    @Override
+    public Iterator<ModuleDescriptorWrapper> getModules() {
+        final Iterator<RepoDescriptor> itRepos = repos.iterator();
+        return new Iterator<ModuleDescriptorWrapper>() {
+
+            private Iterator<ModuleDescriptorWrapper> current = null;
+
+            private ModuleDescriptorWrapper next = null;
+
+            public boolean hasNext() {
+                while (next == null) {
+                    if (current == null) {
+                        if (!itRepos.hasNext()) {
+                            return false;
+                        }
+                        RepoDescriptor repo = itRepos.next();
+                        current = repo.getModules();
+                    }
+                    if (current.hasNext()) {
+                        next = current.next();
+                    } else {
+                        current = null;
+                    }
+                }
+                return true;
+            }
+
+            public ModuleDescriptorWrapper next() {
+                if (!hasNext()) {
+                    throw new NoSuchElementException();
+                }
+                ModuleDescriptorWrapper ret = next;
+                next = null;
+                return ret;
+            }
+
+            public void remove() {
+                throw new UnsupportedOperationException();
+            }
+        };
+    }
+
+    @Override
+    public Set<String> getCapabilities() {
+        Set<String> ret = new HashSet<String>();
+        for (RepoDescriptor repo : repos) {
+            Set<String> capabilities = repo.getCapabilities();
+            if (capabilities != null) {
+                ret.addAll(capabilities);
+            }
+        }
+        return ret;
+    }
+
+    @Override
+    public Set<ModuleDescriptorWrapper> findModules(String requirement, String value) {
+        Set<ModuleDescriptorWrapper> ret = new HashSet<ModuleDescriptorWrapper>();
+        for (RepoDescriptor repo : repos) {
+            Set<ModuleDescriptorWrapper> modules = repo.findModules(requirement, value);
+            if (modules != null) {
+                ret.addAll(modules);
+            }
+        }
+        return ret;
+    }
+
+    @Override
+    public Set<String> getCapabilityValues(String capabilityName) {
+        Set<String> ret = new HashSet<String>();
+        for (RepoDescriptor repo : repos) {
+            Set<String> capabilityValues = repo.getCapabilityValues(capabilityName);
+            if (capabilityValues != null) {
+                ret.addAll(capabilityValues);
+            }
+        }
+        return ret;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/osgi/repo/ArtifactReportManifestIterable.java b/src/java/org/apache/ivy/osgi/repo/ArtifactReportManifestIterable.java
new file mode 100644
index 0000000..5b3ae2d
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/repo/ArtifactReportManifestIterable.java
@@ -0,0 +1,172 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.repo;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.jar.JarInputStream;
+import java.util.jar.Manifest;
+
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.report.ArtifactDownloadReport;
+import org.apache.ivy.util.Message;
+
+public class ArtifactReportManifestIterable implements Iterable<ManifestAndLocation> {
+
+    private final Map<ModuleRevisionId, List<ArtifactDownloadReport>> artifactReports = new HashMap<ModuleRevisionId, List<ArtifactDownloadReport>>();
+
+    private List<String> sourceTypes;
+
+    public ArtifactReportManifestIterable(List<ArtifactDownloadReport> reports,
+            List<String> sourceTypes) {
+        this.sourceTypes = sourceTypes;
+        for (ArtifactDownloadReport report : reports) {
+            ModuleRevisionId mrid = report.getArtifact().getModuleRevisionId();
+            List<ArtifactDownloadReport> moduleReports = artifactReports.get(mrid);
+            if (moduleReports == null) {
+                moduleReports = new ArrayList<ArtifactDownloadReport>();
+                artifactReports.put(mrid, moduleReports);
+            }
+            moduleReports.add(report);
+        }
+    }
+
+    public Iterator<ManifestAndLocation> iterator() {
+        return new ArtifactReportManifestIterator();
+    }
+
+    class ArtifactReportManifestIterator implements Iterator<ManifestAndLocation> {
+
+        private ManifestAndLocation next = null;
+
+        private Iterator<ModuleRevisionId> it;
+
+        public ArtifactReportManifestIterator() {
+            it = artifactReports.keySet().iterator();
+        }
+
+        public boolean hasNext() {
+            while (next == null && it.hasNext()) {
+                ModuleRevisionId mrid = it.next();
+                List<ArtifactDownloadReport> reports = artifactReports.get(mrid);
+                ArtifactDownloadReport jar = null;
+                ArtifactDownloadReport source = null;
+                for (ArtifactDownloadReport report : reports) {
+                    if (sourceTypes != null && sourceTypes.contains(report.getArtifact().getType())) {
+                        source = report;
+                    } else {
+                        jar = report;
+                    }
+                }
+                if (jar == null) {
+                    // didn't found any suitable jar
+                    continue;
+                }
+                URI sourceURI = null;
+                if (source != null) {
+                    if (source.getUnpackedLocalFile() != null) {
+                        sourceURI = source.getUnpackedLocalFile().toURI();
+                    } else {
+                        sourceURI = source.getLocalFile().toURI();
+                    }
+                }
+                if (jar.getUnpackedLocalFile() != null && jar.getUnpackedLocalFile().isDirectory()) {
+                    FileInputStream in = null;
+                    try {
+                        in = new FileInputStream(new File(jar.getUnpackedLocalFile(),
+                                "META-INF/MANIFEST.MF"));
+                        next = new ManifestAndLocation(new Manifest(in), jar.getUnpackedLocalFile()
+                                .toURI(), sourceURI);
+                        return true;
+                    } catch (FileNotFoundException e) {
+                        Message.debug(
+                            "Bundle directory file just removed: " + jar.getUnpackedLocalFile(), e);
+                    } catch (IOException e) {
+                        Message.debug("The Manifest in the bundle directory could not be read: "
+                                + jar.getUnpackedLocalFile(), e);
+                    } finally {
+                        if (in != null) {
+                            try {
+                                in.close();
+                            } catch (IOException e) {
+                                // ignore
+                            }
+                        }
+                    }
+                } else {
+                    File artifact;
+                    if (jar.getUnpackedLocalFile() != null) {
+                        artifact = jar.getUnpackedLocalFile();
+                    } else {
+                        artifact = jar.getLocalFile();
+                    }
+                    JarInputStream in = null;
+                    try {
+                        in = new JarInputStream(new FileInputStream(artifact));
+                        Manifest manifest = in.getManifest();
+                        if (manifest != null) {
+                            next = new ManifestAndLocation(manifest, artifact.toURI(), sourceURI);
+                            return true;
+                        }
+                        Message.debug("No manifest in jar: " + artifact);
+                    } catch (FileNotFoundException e) {
+                        Message.debug("Jar file just removed: " + artifact, e);
+                    } catch (IOException e) {
+                        Message.warn("Unreadable jar: " + artifact, e);
+                    } finally {
+                        if (in != null) {
+                            try {
+                                in.close();
+                            } catch (IOException e) {
+                                // Don't care
+                            }
+                        }
+                    }
+                }
+            }
+            if (next == null) {
+                return false;
+            }
+            return true;
+        }
+
+        public ManifestAndLocation next() {
+            if (!hasNext()) {
+                throw new NoSuchElementException();
+            }
+            ManifestAndLocation manifest = next;
+            next = null;
+            return manifest;
+        }
+
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+
+    }
+
+}
\ No newline at end of file
diff --git a/src/java/org/apache/ivy/osgi/repo/BundleCapabilityAndLocation.java b/src/java/org/apache/ivy/osgi/repo/BundleCapabilityAndLocation.java
new file mode 100644
index 0000000..823245e
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/repo/BundleCapabilityAndLocation.java
@@ -0,0 +1,57 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.repo;
+
+import org.apache.ivy.osgi.core.BundleInfo;
+import org.apache.ivy.osgi.util.Version;
+
+public class BundleCapabilityAndLocation {
+
+    private final String name;
+
+    private final Version version;
+
+    private final BundleInfo bundleInfo;
+
+    private final String type;
+
+    public BundleCapabilityAndLocation(String type, String name, Version version,
+            BundleInfo bundleInfo) {
+        this.type = type;
+        this.name = name;
+        this.version = version;
+        this.bundleInfo = bundleInfo;
+    }
+
+    public BundleInfo getBundleInfo() {
+        return bundleInfo;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public Version getVersion() {
+        return version;
+    }
+
+}
\ No newline at end of file
diff --git a/src/java/org/apache/ivy/osgi/repo/BundleRepoDescriptor.java b/src/java/org/apache/ivy/osgi/repo/BundleRepoDescriptor.java
new file mode 100644
index 0000000..90bb6ae
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/repo/BundleRepoDescriptor.java
@@ -0,0 +1,71 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.repo;
+
+import java.net.URI;
+import java.text.ParseException;
+import java.util.Iterator;
+
+import org.apache.ivy.osgi.core.BundleArtifact;
+import org.apache.ivy.osgi.core.BundleInfo;
+import org.apache.ivy.osgi.core.ExecutionEnvironmentProfileProvider;
+import org.apache.ivy.osgi.core.ManifestParser;
+import org.apache.ivy.util.Message;
+
+public class BundleRepoDescriptor extends EditableRepoDescriptor {
+
+    private String name;
+
+    private String lastModified;
+
+    public BundleRepoDescriptor(URI baseUri, ExecutionEnvironmentProfileProvider profileProvider) {
+        super(baseUri, profileProvider);
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setLastModified(String lastModified) {
+        this.lastModified = lastModified;
+    }
+
+    public String getLastModified() {
+        return lastModified;
+    }
+
+    public void populate(Iterator<ManifestAndLocation> it) {
+        while (it.hasNext()) {
+            ManifestAndLocation manifestAndLocation = it.next();
+            try {
+                BundleInfo bundleInfo = ManifestParser.parseManifest(manifestAndLocation
+                        .getManifest());
+                bundleInfo
+                        .addArtifact(new BundleArtifact(false, manifestAndLocation.getUri(), null));
+                addBundle(bundleInfo);
+            } catch (ParseException e) {
+                Message.error("Rejected " + manifestAndLocation.getUri() + ": " + e.getMessage());
+            }
+        }
+    }
+
+}
diff --git a/src/java/org/apache/ivy/osgi/repo/EditableRepoDescriptor.java b/src/java/org/apache/ivy/osgi/repo/EditableRepoDescriptor.java
new file mode 100644
index 0000000..ae77af2
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/repo/EditableRepoDescriptor.java
@@ -0,0 +1,173 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.repo;
+
+import java.net.URI;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.ivy.osgi.core.BundleCapability;
+import org.apache.ivy.osgi.core.BundleInfo;
+import org.apache.ivy.osgi.core.ExecutionEnvironmentProfileProvider;
+import org.apache.ivy.osgi.util.Version;
+import org.apache.ivy.util.Message;
+
+public class EditableRepoDescriptor extends RepoDescriptor {
+
+    private final Map<String, Map<String, Set<ModuleDescriptorWrapper>>> moduleByCapabilities = new HashMap<String, Map<String, Set<ModuleDescriptorWrapper>>>();
+
+    private final Set<ModuleDescriptorWrapper> modules = new HashSet<ModuleDescriptorWrapper>();
+
+    private final ExecutionEnvironmentProfileProvider profileProvider;
+
+    private final URI baseUri;
+
+    private int logLevel = Message.MSG_INFO;
+
+    public EditableRepoDescriptor(URI baseUri, ExecutionEnvironmentProfileProvider profileProvider) {
+        this.baseUri = baseUri;
+        this.profileProvider = profileProvider;
+    }
+
+    public void setLogLevel(int logLevel) {
+        this.logLevel = logLevel;
+    }
+
+    public int getLogLevel() {
+        return logLevel;
+    }
+
+    public URI getBaseUri() {
+        return baseUri;
+    }
+
+    public Iterator<ModuleDescriptorWrapper> getModules() {
+        return modules.iterator();
+    }
+
+    public Set<String> getCapabilities() {
+        return moduleByCapabilities.keySet();
+    }
+
+    public Set<ModuleDescriptorWrapper> findModules(String requirement, String value) {
+        Map<String, Set<ModuleDescriptorWrapper>> modules = moduleByCapabilities.get(requirement);
+        if (modules == null) {
+            return null;
+        }
+        return modules.get(value);
+    }
+
+    public ModuleDescriptorWrapper findModule(String symbolicName, Version version) {
+        Set<ModuleDescriptorWrapper> modules = findModules(BundleInfo.BUNDLE_TYPE, symbolicName);
+        if (modules == null) {
+            return null;
+        }
+        for (ModuleDescriptorWrapper module : modules) {
+            if (module.getBundleInfo().getVersion().equals(version)) {
+                return module;
+            }
+        }
+        return null;
+    }
+
+    public Set<String> getCapabilityValues(String capabilityName) {
+        Map<String, Set<ModuleDescriptorWrapper>> modules = moduleByCapabilities
+                .get(capabilityName);
+        if (modules == null) {
+            return Collections.emptySet();
+        }
+        return modules.keySet();
+    }
+
+    private void add(String type, String value, ModuleDescriptorWrapper md) {
+        modules.add(md);
+        Map<String, Set<ModuleDescriptorWrapper>> map = moduleByCapabilities.get(type);
+        if (map == null) {
+            map = new HashMap<String, Set<ModuleDescriptorWrapper>>();
+            moduleByCapabilities.put(type, map);
+        }
+        Set<ModuleDescriptorWrapper> bundleReferences = map.get(value);
+        if (bundleReferences == null) {
+            bundleReferences = new HashSet<ModuleDescriptorWrapper>();
+            map.put(value, bundleReferences);
+        }
+        if (!bundleReferences.add(md)) {
+            if (logLevel <= Message.MSG_DEBUG) {
+                Message.debug("Duplicate module in the repo " + baseUri + " for " + type + " "
+                        + value + ": " + md.getBundleInfo().getSymbolicName() + "#"
+                        + md.getBundleInfo().getVersion());
+            }
+        }
+    }
+
+    public void addBundle(BundleInfo bundleInfo) {
+        ModuleDescriptorWrapper module = findModule(bundleInfo.getSymbolicName(),
+            bundleInfo.getVersion());
+        if (module != null) {
+            Message.debug("Duplicate module " + bundleInfo.getSymbolicName() + "@"
+                    + bundleInfo.getVersion());
+            return;
+        }
+        ModuleDescriptorWrapper md = new ModuleDescriptorWrapper(bundleInfo, baseUri,
+                profileProvider);
+        add(BundleInfo.BUNDLE_TYPE, bundleInfo.getSymbolicName(), md);
+        for (BundleCapability capability : bundleInfo.getCapabilities()) {
+            add(capability.getType(), capability.getName(), md);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return modules.toString();
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((modules == null) ? 0 : modules.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        EditableRepoDescriptor other = (EditableRepoDescriptor) obj;
+        if (modules == null) {
+            if (other.modules != null) {
+                return false;
+            }
+        } else if (!modules.equals(other.modules)) {
+            return false;
+        }
+        return true;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/osgi/repo/FSManifestIterable.java b/src/java/org/apache/ivy/osgi/repo/FSManifestIterable.java
new file mode 100644
index 0000000..fe997e2
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/repo/FSManifestIterable.java
@@ -0,0 +1,126 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.repo;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FilenameFilter;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class FSManifestIterable extends AbstractFSManifestIterable<File> {
+
+    /**
+     * List of directory name that usually contains jars but are not bundles
+     */
+    public static final Set<String> NON_BUNDLE_DIRS = new HashSet<String>(Arrays.asList("source",
+        "sources", "javadoc", "javadocs", "doc", "docs"));
+
+    /**
+     * Default directory filter that doesn't select .svn directories, neither the directories that
+     * match {@link #NON_BUNDLE_DIRS}.
+     */
+    public static final FilenameFilter DEFAULT_DIR_FILTER = new FilenameFilter() {
+        public boolean accept(File dir, String name) {
+            return !name.equals(".svn") && !NON_BUNDLE_DIRS.contains(name);
+        }
+    };
+
+    /**
+     * Default bundle filter that select only .jar files
+     */
+    public static final FilenameFilter DEFAULT_BUNLDE_FILTER = new FilenameFilter() {
+        public boolean accept(File dir, String name) {
+            return name.endsWith(".jar");
+        }
+    };
+
+    private FilenameFilter dirFilter = DEFAULT_DIR_FILTER;
+
+    private FilenameFilter bundleFilter = DEFAULT_BUNLDE_FILTER;
+
+    /**
+     * Default constructor
+     * 
+     * @param root
+     *            the root directory of the file system to lookup
+     */
+    public FSManifestIterable(File root) {
+        super(root);
+    }
+
+    public FilenameFilter getDirFilter() {
+        return dirFilter;
+    }
+
+    public void setDirFilter(FilenameFilter dirFilter) {
+        this.dirFilter = dirFilter;
+    }
+
+    public FilenameFilter getBundleFilter() {
+        return bundleFilter;
+    }
+
+    public void setBundleFilter(FilenameFilter bundleFilter) {
+        this.bundleFilter = bundleFilter;
+    }
+
+    protected URI buildBundleURI(File location) {
+        try {
+            return new URI(location.toURI().toURL().toExternalForm());
+        } catch (MalformedURLException e) {
+            throw new RuntimeException("Unexpected file to url conversion error", e);
+        } catch (URISyntaxException e) {
+            throw new RuntimeException("Unexpected url to uri conversion error", e);
+        }
+    }
+
+    protected InputStream getInputStream(File f) throws FileNotFoundException {
+        return new FileInputStream(f);
+    }
+
+    protected List<File> listBundleFiles(File dir) {
+        return Arrays.asList(dir.listFiles(new FileFilter() {
+            public boolean accept(File f) {
+                if (!f.isFile()) {
+                    return false;
+                }
+                return bundleFilter.accept(f.getParentFile(), f.getName());
+            }
+        }));
+    }
+
+    protected List<File> listDirs(File dir) {
+        return Arrays.asList(dir.listFiles(new FileFilter() {
+            public boolean accept(File f) {
+                if (!f.isDirectory()) {
+                    return false;
+                }
+                return dirFilter == null || dirFilter.accept(f.getParentFile(), f.getName());
+            }
+        }));
+    }
+}
\ No newline at end of file
diff --git a/src/java/org/apache/ivy/osgi/repo/ManifestAndLocation.java b/src/java/org/apache/ivy/osgi/repo/ManifestAndLocation.java
new file mode 100644
index 0000000..8416266
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/repo/ManifestAndLocation.java
@@ -0,0 +1,54 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.repo;
+
+import java.net.URI;
+import java.util.jar.Manifest;
+
+public class ManifestAndLocation {
+
+    private final Manifest manifest;
+
+    /**
+     * location of the jar
+     */
+    private final URI uri;
+
+    /**
+     * location of the source jar
+     */
+    private final URI sourceURI;
+
+    public ManifestAndLocation(Manifest manifest, URI uri, URI sourceURI) {
+        this.manifest = manifest;
+        this.uri = uri;
+        this.sourceURI = sourceURI;
+    }
+
+    public URI getUri() {
+        return uri;
+    }
+
+    public Manifest getManifest() {
+        return manifest;
+    }
+
+    public URI getSourceURI() {
+        return sourceURI;
+    }
+}
diff --git a/src/java/org/apache/ivy/osgi/repo/ModuleDescriptorWrapper.java b/src/java/org/apache/ivy/osgi/repo/ModuleDescriptorWrapper.java
new file mode 100644
index 0000000..3707647
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/repo/ModuleDescriptorWrapper.java
@@ -0,0 +1,99 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.repo;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.osgi.core.BundleInfo;
+import org.apache.ivy.osgi.core.BundleInfoAdapter;
+import org.apache.ivy.osgi.core.ExecutionEnvironmentProfileProvider;
+import org.apache.ivy.osgi.core.OSGiManifestParser;
+
+public class ModuleDescriptorWrapper {
+
+    private BundleInfo bundleInfo;
+
+    private DefaultModuleDescriptor md;
+
+    private URI baseUri;
+
+    private ExecutionEnvironmentProfileProvider profileProvider;
+
+    public ModuleDescriptorWrapper(BundleInfo bundleInfo, URI baseUri,
+            ExecutionEnvironmentProfileProvider profileProvider) {
+        this.bundleInfo = bundleInfo;
+        this.baseUri = baseUri;
+        this.profileProvider = profileProvider;
+    }
+
+    public BundleInfo getBundleInfo() {
+        return bundleInfo;
+    }
+
+    public DefaultModuleDescriptor getModuleDescriptor() {
+        if (md == null) {
+            synchronized (this) {
+                if (md != null) {
+                    return md;
+                }
+                md = BundleInfoAdapter.toModuleDescriptor(OSGiManifestParser.getInstance(),
+                    baseUri, bundleInfo, profileProvider);
+            }
+        }
+        return md;
+    }
+
+    public static Collection<ModuleDescriptor> unwrap(Collection<ModuleDescriptorWrapper> collection) {
+        if (collection == null) {
+            return null;
+        }
+        if (collection.isEmpty()) {
+            return Collections.emptyList();
+        }
+        List<ModuleDescriptor> unwrapped = new ArrayList<ModuleDescriptor>();
+        for (ModuleDescriptorWrapper wrapped : collection) {
+            unwrapped.add(wrapped.getModuleDescriptor());
+        }
+        return unwrapped;
+    }
+
+    @Override
+    public int hashCode() {
+        return bundleInfo.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null || !(obj instanceof ModuleDescriptorWrapper)) {
+            return false;
+        }
+        return bundleInfo.equals(((ModuleDescriptorWrapper) obj).bundleInfo);
+    }
+
+    @Override
+    public String toString() {
+        return getModuleDescriptor().toString();
+    }
+
+}
diff --git a/src/java/org/apache/ivy/osgi/repo/RelativeURLRepository.java b/src/java/org/apache/ivy/osgi/repo/RelativeURLRepository.java
new file mode 100644
index 0000000..29ab44a
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/repo/RelativeURLRepository.java
@@ -0,0 +1,73 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.repo;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.plugins.repository.url.URLRepository;
+import org.apache.ivy.plugins.repository.url.URLResource;
+
+public class RelativeURLRepository extends URLRepository {
+
+    private final URL baseUrl;
+
+    public RelativeURLRepository() {
+        super();
+        baseUrl = null;
+    }
+
+    public RelativeURLRepository(URL baseUrl) {
+        super();
+        this.baseUrl = baseUrl;
+    }
+
+    private Map<String, Resource> resourcesCache = new HashMap<String, Resource>();
+
+    public Resource getResource(String source) throws IOException {
+        source = encode(source);
+        Resource res = resourcesCache.get(source);
+        if (res == null) {
+            URI uri;
+            try {
+                uri = new URI(source);
+            } catch (URISyntaxException e) {
+                // very wierd URL, let's assume it is absolute
+                uri = null;
+            }
+            if (uri == null || uri.isAbsolute()) {
+                res = new URLResource(new URL(source));
+            } else {
+                res = new URLResource(new URL(baseUrl + source));
+            }
+            resourcesCache.put(source, res);
+        }
+        return res;
+    }
+
+    private static String encode(String source) {
+        // TODO: add some more URL encodings here
+        return source.trim().replaceAll(" ", "%20");
+    }
+
+}
diff --git a/src/java/org/apache/ivy/osgi/repo/RepoDescriptor.java b/src/java/org/apache/ivy/osgi/repo/RepoDescriptor.java
new file mode 100644
index 0000000..22a57d1
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/repo/RepoDescriptor.java
@@ -0,0 +1,33 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.repo;
+
+import java.util.Iterator;
+import java.util.Set;
+
+public abstract class RepoDescriptor {
+
+    abstract public Iterator<ModuleDescriptorWrapper> getModules();
+
+    abstract public Set<String> getCapabilities();
+
+    abstract public Set<ModuleDescriptorWrapper> findModules(String requirement, String value);
+
+    abstract public Set<String> getCapabilityValues(String capabilityName);
+
+}
diff --git a/src/java/org/apache/ivy/osgi/repo/RepositoryManifestIterable.java b/src/java/org/apache/ivy/osgi/repo/RepositoryManifestIterable.java
new file mode 100644
index 0000000..b832c09
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/repo/RepositoryManifestIterable.java
@@ -0,0 +1,73 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.repo;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.ivy.plugins.repository.Repository;
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.plugins.resolver.util.ResolverHelper;
+
+public class RepositoryManifestIterable extends AbstractFSManifestIterable<String> {
+
+    private final Repository repo;
+
+    /**
+     * Default constructor
+     * 
+     * @param root
+     *            the root directory of the file system to lookup
+     */
+    public RepositoryManifestIterable(Repository repo) {
+        super("");
+        this.repo = repo;
+    }
+
+    protected URI buildBundleURI(String location) throws IOException {
+        Resource resource = repo.getResource(location);
+        // We have a resource to transform into an URI, let's use some heuristics
+        try {
+            return new URI(resource.getName());
+        } catch (URISyntaxException e) {
+            return new File(resource.getName()).toURI();
+        }
+    }
+
+    protected InputStream getInputStream(String f) throws IOException {
+        return repo.getResource(f).openStream();
+    }
+
+    protected List<String> listBundleFiles(String dir) throws IOException {
+        return asList(ResolverHelper.listAll(repo, dir));
+    }
+
+    protected List<String> listDirs(String dir) throws IOException {
+        return asList(ResolverHelper.listAll(repo, dir));
+    }
+
+    private List<String> asList(String[] array) {
+        return array == null ? Collections.<String> emptyList() : Arrays.<String> asList(array);
+    }
+}
diff --git a/src/java/org/apache/ivy/osgi/repo/ResolverManifestIterable.java b/src/java/org/apache/ivy/osgi/repo/ResolverManifestIterable.java
new file mode 100644
index 0000000..fa1ddf8
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/repo/ResolverManifestIterable.java
@@ -0,0 +1,205 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.repo;
+
+import java.io.IOException;
+import java.net.URI;
+import java.text.ParseException;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.jar.JarInputStream;
+import java.util.jar.Manifest;
+
+import org.apache.ivy.core.event.EventManager;
+import org.apache.ivy.core.module.descriptor.Artifact;
+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.apache.ivy.core.resolve.ResolveData;
+import org.apache.ivy.core.resolve.ResolveEngine;
+import org.apache.ivy.core.resolve.ResolveOptions;
+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.core.settings.IvySettings;
+import org.apache.ivy.core.sort.SortEngine;
+import org.apache.ivy.osgi.core.BundleInfoAdapter;
+import org.apache.ivy.plugins.resolver.BasicResolver;
+import org.apache.ivy.plugins.resolver.util.ResolvedResource;
+import org.apache.ivy.util.Message;
+
+public class ResolverManifestIterable implements Iterable<ManifestAndLocation> {
+
+    // We should support the interface DependencyResolver, but the API is not convenient to get
+    // references to artifact
+    private final BasicResolver resolver;
+
+    public ResolverManifestIterable(BasicResolver resolver) {
+        this.resolver = resolver;
+    }
+
+    public Iterator<ManifestAndLocation> iterator() {
+        return new ResolverManifestIterator();
+    }
+
+    class ResolverManifestIterator implements Iterator<ManifestAndLocation> {
+
+        private OrganisationEntry[] organisations;
+
+        private int indexOrganisation = 0;
+
+        private OrganisationEntry organisation;
+
+        private ModuleEntry[] modules;
+
+        private int indexModule = -1;
+
+        private ModuleEntry module;
+
+        private ManifestAndLocation next = null;
+
+        private RevisionEntry[] revisions;
+
+        private int indexRevision;
+
+        private RevisionEntry revision;
+
+        private Artifact[] artifacts;
+
+        private int indexArtifact;
+
+        private Artifact artifact;
+
+        private ModuleRevisionId mrid;
+
+        private ResolveData data;
+
+        public ResolverManifestIterator() {
+            organisations = resolver.listOrganisations();
+            IvySettings settings = new IvySettings();
+            ResolveEngine engine = new ResolveEngine(settings, new EventManager(), new SortEngine(
+                    settings));
+            data = new ResolveData(engine, new ResolveOptions());
+        }
+
+        public boolean hasNext() {
+            while (next == null) {
+                if (organisation == null) {
+                    if (indexOrganisation >= organisations.length) {
+                        return false;
+                    }
+                    organisation = organisations[indexOrganisation++];
+                    modules = resolver.listModules(organisation);
+                    indexModule = 0;
+                    module = null;
+                }
+                if (module == null) {
+                    if (indexModule >= modules.length) {
+                        organisation = null;
+                        continue;
+                    }
+                    module = modules[indexModule++];
+                    revisions = resolver.listRevisions(module);
+                    indexRevision = 0;
+                    revision = null;
+                }
+                if (revision == null) {
+                    if (indexRevision >= revisions.length) {
+                        module = null;
+                        continue;
+                    }
+                    revision = revisions[indexRevision++];
+                    mrid = ModuleRevisionId.newInstance(organisation.getOrganisation(),
+                        module.getModule(), revision.getRevision());
+                    DefaultDependencyDescriptor dd = new DefaultDependencyDescriptor(mrid, false);
+                    ResolvedModuleRevision dependency;
+                    try {
+                        dependency = resolver.getDependency(dd, data);
+                    } catch (ParseException e) {
+                        Message.error("Error while resolving " + mrid + " : " + e.getMessage());
+                        revision = null;
+                        continue;
+                    }
+                    if (dependency == null) {
+                        revision = null;
+                        continue;
+                    }
+                    ModuleDescriptor md = dependency.getDescriptor();
+                    mrid = md.getModuleRevisionId();
+                    artifacts = md.getAllArtifacts();
+                    indexArtifact = 0;
+                    artifact = null;
+                }
+                if (artifact == null) {
+                    if (indexArtifact >= artifacts.length) {
+                        revision = null;
+                        continue;
+                    }
+                    artifact = artifacts[indexArtifact++];
+                }
+                ResolvedResource resource = resolver.doFindArtifactRef(artifact, null);
+                if (resource == null) {
+                    artifact = null;
+                    continue;
+                }
+                JarInputStream in;
+                try {
+                    in = new JarInputStream(resource.getResource().openStream());
+                } catch (IOException e) {
+                    Message.warn("Unreadable jar " + resource.getResource().getName() + " ("
+                            + e.getMessage() + ")");
+                    artifact = null;
+                    continue;
+                }
+                Manifest manifest;
+                try {
+                    manifest = in.getManifest();
+                } finally {
+                    try {
+                        in.close();
+                    } catch (IOException e) {
+                        // don't care
+                    }
+                }
+                if (manifest == null) {
+                    Message.debug("No manifest on " + artifact);
+                } else {
+                    URI uri = BundleInfoAdapter.buildIvyURI(artifact);
+                    next = new ManifestAndLocation(manifest, uri, null);
+                }
+                artifact = null;
+            }
+            return true;
+        }
+
+        public ManifestAndLocation next() {
+            if (!hasNext()) {
+                throw new NoSuchElementException();
+            }
+            ManifestAndLocation manifest = next;
+            next = null;
+            return manifest;
+        }
+
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/src/java/org/apache/ivy/osgi/updatesite/PluginAdapter.java b/src/java/org/apache/ivy/osgi/updatesite/PluginAdapter.java
new file mode 100644
index 0000000..973c6f2
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/updatesite/PluginAdapter.java
@@ -0,0 +1,81 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.updatesite;
+
+import java.net.URI;
+
+import org.apache.ivy.osgi.core.BundleArtifact;
+import org.apache.ivy.osgi.core.BundleInfo;
+import org.apache.ivy.osgi.core.BundleRequirement;
+import org.apache.ivy.osgi.updatesite.xml.EclipseFeature;
+import org.apache.ivy.osgi.updatesite.xml.EclipsePlugin;
+import org.apache.ivy.osgi.updatesite.xml.Require;
+import org.apache.ivy.osgi.util.VersionRange;
+
+public class PluginAdapter {
+
+    public static BundleInfo featureAsBundle(URI baseUri, EclipseFeature feature) {
+        BundleInfo b = new BundleInfo(feature.getId(), feature.getVersion());
+
+        URI uri;
+        if (feature.getUrl() == null) {
+            uri = baseUri.resolve("features/" + feature.getId() + '_' + feature.getVersion()
+                    + ".jar");
+        } else {
+            uri = baseUri.resolve(feature.getUrl());
+        }
+        b.addArtifact(new BundleArtifact(false, uri, null));
+
+        b.setDescription(feature.getDescription());
+        b.setLicense(feature.getLicense());
+
+        for (EclipsePlugin plugin : feature.getPlugins()) {
+            BundleRequirement r = new BundleRequirement(BundleInfo.BUNDLE_TYPE, plugin.getId(),
+                    new VersionRange(plugin.getVersion()), null);
+            b.addRequirement(r);
+        }
+
+        for (Require require : feature.getRequires()) {
+            String id;
+            if (require.getPlugin() != null) {
+                id = require.getPlugin();
+            } else {
+                id = require.getFeature();
+            }
+            VersionRange range;
+            if (require.getMatch().equals("greaterOrEqual")) {
+                range = new VersionRange(require.getVersion());
+            } else {
+                throw new IllegalStateException("unsupported match " + require.getMatch());
+            }
+            BundleRequirement r = new BundleRequirement(BundleInfo.BUNDLE_TYPE, id, range, null);
+            b.addRequirement(r);
+        }
+
+        return b;
+    }
+
+    public static BundleInfo pluginAsBundle(URI baseUri, EclipsePlugin plugin) {
+        BundleInfo b = new BundleInfo(plugin.getId(), plugin.getVersion());
+
+        URI uri = baseUri.resolve("plugins/" + plugin.getId() + '_' + plugin.getVersion() + ".jar");
+        b.addArtifact(new BundleArtifact(false, uri, null));
+
+        return b;
+    }
+}
diff --git a/src/java/org/apache/ivy/osgi/updatesite/UpdateSiteDescriptor.java b/src/java/org/apache/ivy/osgi/updatesite/UpdateSiteDescriptor.java
new file mode 100644
index 0000000..15b2291
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/updatesite/UpdateSiteDescriptor.java
@@ -0,0 +1,41 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.updatesite;
+
+import java.net.URI;
+
+import org.apache.ivy.osgi.core.ExecutionEnvironmentProfileProvider;
+import org.apache.ivy.osgi.repo.EditableRepoDescriptor;
+import org.apache.ivy.osgi.updatesite.xml.EclipseFeature;
+import org.apache.ivy.osgi.updatesite.xml.EclipsePlugin;
+
+public class UpdateSiteDescriptor extends EditableRepoDescriptor {
+
+    public UpdateSiteDescriptor(URI baseUri, ExecutionEnvironmentProfileProvider profileProvider) {
+        super(baseUri, profileProvider);
+    }
+
+    public void addFeature(EclipseFeature feature) {
+        addBundle(PluginAdapter.featureAsBundle(getBaseUri(), feature));
+
+        for (EclipsePlugin plugin : feature.getPlugins()) {
+            addBundle(PluginAdapter.pluginAsBundle(getBaseUri(), plugin));
+        }
+    }
+
+}
diff --git a/src/java/org/apache/ivy/osgi/updatesite/UpdateSiteLoader.java b/src/java/org/apache/ivy/osgi/updatesite/UpdateSiteLoader.java
new file mode 100644
index 0000000..6e3f9c9
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/updatesite/UpdateSiteLoader.java
@@ -0,0 +1,338 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.updatesite;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.text.ParseException;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+import org.apache.ivy.core.cache.CacheResourceOptions;
+import org.apache.ivy.core.cache.RepositoryCacheManager;
+import org.apache.ivy.core.event.EventManager;
+import org.apache.ivy.core.report.ArtifactDownloadReport;
+import org.apache.ivy.core.report.DownloadStatus;
+import org.apache.ivy.osgi.core.ExecutionEnvironmentProfileProvider;
+import org.apache.ivy.osgi.p2.P2ArtifactParser;
+import org.apache.ivy.osgi.p2.P2CompositeParser;
+import org.apache.ivy.osgi.p2.P2Descriptor;
+import org.apache.ivy.osgi.p2.P2MetadataParser;
+import org.apache.ivy.osgi.p2.XMLInputParser;
+import org.apache.ivy.osgi.repo.RepoDescriptor;
+import org.apache.ivy.osgi.updatesite.xml.EclipseFeature;
+import org.apache.ivy.osgi.updatesite.xml.EclipseUpdateSiteParser;
+import org.apache.ivy.osgi.updatesite.xml.FeatureParser;
+import org.apache.ivy.osgi.updatesite.xml.UpdateSite;
+import org.apache.ivy.osgi.updatesite.xml.UpdateSiteDigestParser;
+import org.apache.ivy.plugins.repository.url.URLRepository;
+import org.apache.ivy.plugins.repository.url.URLResource;
+import org.apache.ivy.util.Message;
+import org.xml.sax.SAXException;
+
+public class UpdateSiteLoader {
+
+    private final RepositoryCacheManager repositoryCacheManager;
+
+    private final URLRepository urlRepository = new URLRepository();
+
+    private final CacheResourceOptions options;
+
+    private int logLevel = Message.MSG_INFO;
+
+    public UpdateSiteLoader(RepositoryCacheManager repositoryCacheManager,
+            EventManager eventManager, CacheResourceOptions options) {
+        this.repositoryCacheManager = repositoryCacheManager;
+        this.options = options;
+        if (eventManager != null) {
+            urlRepository.addTransferListener(eventManager);
+        }
+    }
+
+    public void setLogLevel(int logLevel) {
+        this.logLevel = logLevel;
+    }
+
+    public RepoDescriptor load(URI repoUri) throws IOException, ParseException, SAXException {
+        if (!repoUri.toString().endsWith("/")) {
+            try {
+                repoUri = new URI(repoUri.toString() + "/");
+            } catch (URISyntaxException e) {
+                throw new RuntimeException("Cannot make an uri for the repo");
+            }
+        }
+        Message.info("Loading the update site " + repoUri);
+        // first look for a p2 repository
+        RepoDescriptor repo = loadP2(repoUri);
+        if (repo != null) {
+            return repo;
+        }
+        Message.verbose("\tNo P2 artifacts, falling back on the old fashioned updatesite");
+        // then try the old update site
+        UpdateSite site = loadSite(repoUri);
+        if (site == null) {
+            return null;
+        }
+        repo = loadFromDigest(site);
+        if (repo != null) {
+            return repo;
+        }
+        return loadFromSite(site);
+    }
+
+    private P2Descriptor loadP2(URI repoUri) throws IOException, ParseException, SAXException {
+        P2Descriptor p2Descriptor = new P2Descriptor(repoUri,
+                ExecutionEnvironmentProfileProvider.getInstance());
+        p2Descriptor.setLogLevel(logLevel);
+        if (!populateP2Descriptor(repoUri, p2Descriptor)) {
+            return null;
+        }
+        p2Descriptor.finish();
+        return p2Descriptor;
+    }
+
+    private boolean populateP2Descriptor(URI repoUri, P2Descriptor p2Descriptor)
+            throws IOException, ParseException, SAXException {
+        Message.verbose("Loading P2 repository " + repoUri);
+        boolean contentExists = readContent(repoUri, p2Descriptor);
+        boolean artifactExists = readArtifacts(repoUri, p2Descriptor);
+        return artifactExists || contentExists;
+    }
+
+    private boolean readContent(URI repoUri, P2Descriptor p2Descriptor) throws IOException,
+            ParseException, SAXException {
+        boolean contentExists = readCompositeContent(repoUri, "compositeContent", p2Descriptor);
+        if (!contentExists) {
+            P2MetadataParser metadataParser = new P2MetadataParser(p2Descriptor);
+            metadataParser.setLogLevel(logLevel);
+            contentExists = readJarOrXml(repoUri, "content", metadataParser);
+        }
+        return contentExists;
+    }
+
+    private boolean readArtifacts(URI repoUri, P2Descriptor p2Descriptor) throws IOException,
+            ParseException, SAXException {
+        boolean artifactExists = readCompositeArtifact(repoUri, "compositeArtifacts", p2Descriptor);
+        if (!artifactExists) {
+            artifactExists = readJarOrXml(repoUri, "artifacts", new P2ArtifactParser(p2Descriptor,
+                    repoUri.toURL().toExternalForm()));
+        }
+
+        return artifactExists;
+    }
+
+    private boolean readCompositeContent(URI repoUri, String name, P2Descriptor p2Descriptor)
+            throws IOException, ParseException, SAXException {
+        P2CompositeParser p2CompositeParser = new P2CompositeParser();
+        boolean exist = readJarOrXml(repoUri, name, p2CompositeParser);
+        if (exist) {
+            for (String childLocation : p2CompositeParser.getChildLocations()) {
+                if (!childLocation.endsWith("/")) {
+                    childLocation += "/";
+                }
+                URI childUri = repoUri.resolve(childLocation);
+                readContent(childUri, p2Descriptor);
+            }
+        }
+        return exist;
+    }
+
+    private boolean readCompositeArtifact(URI repoUri, String name, P2Descriptor p2Descriptor)
+            throws IOException, ParseException, SAXException {
+        P2CompositeParser p2CompositeParser = new P2CompositeParser();
+        boolean exist = readJarOrXml(repoUri, name, p2CompositeParser);
+        if (exist) {
+            for (String childLocation : p2CompositeParser.getChildLocations()) {
+                if (!childLocation.endsWith("/")) {
+                    childLocation += "/";
+                }
+                URI childUri = repoUri.resolve(childLocation);
+                readArtifacts(childUri, p2Descriptor);
+            }
+        }
+        return exist;
+    }
+
+    private boolean readJarOrXml(URI repoUri, String baseName, XMLInputParser reader)
+            throws IOException, ParseException, SAXException {
+        InputStream readIn = null; // the input stream from which the xml should be read
+
+        URL contentUrl = repoUri.resolve(baseName + ".jar").toURL();
+        URLResource res = new URLResource(contentUrl);
+
+        ArtifactDownloadReport report = repositoryCacheManager.downloadRepositoryResource(res,
+            baseName, baseName, "jar", options, urlRepository);
+
+        if (report.getDownloadStatus() == DownloadStatus.FAILED) {
+            // no jar file, try the xml one
+            contentUrl = repoUri.resolve(baseName + ".xml").toURL();
+            res = new URLResource(contentUrl);
+
+            report = repositoryCacheManager.downloadRepositoryResource(res, baseName, baseName,
+                "xml", options, urlRepository);
+
+            if (report.getDownloadStatus() == DownloadStatus.FAILED) {
+                // no xml either
+                return false;
+            }
+
+            readIn = new FileInputStream(report.getLocalFile());
+        } else {
+            InputStream in = new FileInputStream(report.getLocalFile());
+
+            try {
+                // compressed, let's get the pointer on the actual xml
+                readIn = findEntry(in, baseName + ".xml");
+                if (readIn == null) {
+                    in.close();
+                    return false;
+                }
+            } catch (IOException e) {
+                in.close();
+                throw e;
+            }
+
+        }
+
+        try {
+            reader.parse(readIn);
+        } finally {
+            readIn.close();
+        }
+
+        return true;
+    }
+
+    private UpdateSite loadSite(URI repoUri) throws IOException, ParseException, SAXException {
+        URI siteUri = normalizeSiteUri(repoUri, null);
+        URL u = siteUri.resolve("site.xml").toURL();
+
+        URLResource res = new URLResource(u);
+        ArtifactDownloadReport report = repositoryCacheManager.downloadRepositoryResource(res,
+            "site", "updatesite", "xml", options, urlRepository);
+        if (report.getDownloadStatus() == DownloadStatus.FAILED) {
+            return null;
+        }
+        InputStream in = new FileInputStream(report.getLocalFile());
+        try {
+            UpdateSite site = EclipseUpdateSiteParser.parse(in);
+            site.setUri(normalizeSiteUri(site.getUri(), siteUri));
+            return site;
+        } finally {
+            in.close();
+        }
+    }
+
+    private URI normalizeSiteUri(URI uri, URI defaultValue) {
+        if (uri == null) {
+            return defaultValue;
+        }
+        String uriString = uri.toString();
+        if (uriString.endsWith("site.xml")) {
+            try {
+                return new URI(uriString.substring(0, uriString.length() - 8));
+            } catch (URISyntaxException e) {
+                throw new RuntimeException("Illegal uri", e);
+            }
+        }
+        if (!uriString.endsWith("/")) {
+            try {
+                return new URI(uriString + "/");
+            } catch (URISyntaxException e) {
+                throw new RuntimeException("Illegal uri", e);
+            }
+        }
+        return uri;
+    }
+
+    private UpdateSiteDescriptor loadFromDigest(UpdateSite site) throws IOException,
+            ParseException, SAXException {
+        URI digestBaseUri = site.getDigestUri();
+        if (digestBaseUri == null) {
+            digestBaseUri = site.getUri();
+        } else if (!digestBaseUri.isAbsolute()) {
+            digestBaseUri = site.getUri().resolve(digestBaseUri);
+        }
+        URL digest = digestBaseUri.resolve("digest.zip").toURL();
+        Message.verbose("\tReading " + digest);
+
+        URLResource res = new URLResource(digest);
+        ArtifactDownloadReport report = repositoryCacheManager.downloadRepositoryResource(res,
+            "digest", "digest", "zip", options, urlRepository);
+        if (report.getDownloadStatus() == DownloadStatus.FAILED) {
+            return null;
+        }
+        InputStream in = new FileInputStream(report.getLocalFile());
+        try {
+            ZipInputStream zipped = findEntry(in, "digest.xml");
+            if (zipped == null) {
+                return null;
+            }
+            return UpdateSiteDigestParser.parse(zipped, site);
+        } finally {
+            in.close();
+        }
+    }
+
+    private UpdateSiteDescriptor loadFromSite(UpdateSite site) throws IOException, ParseException,
+            SAXException {
+        UpdateSiteDescriptor repoDescriptor = new UpdateSiteDescriptor(site.getUri(),
+                ExecutionEnvironmentProfileProvider.getInstance());
+
+        for (EclipseFeature feature : site.getFeatures()) {
+            URL url = site.getUri().resolve(feature.getUrl()).toURL();
+
+            URLResource res = new URLResource(url);
+            ArtifactDownloadReport report = repositoryCacheManager.downloadRepositoryResource(res,
+                feature.getId(), "feature", "jar", options, urlRepository);
+            if (report.getDownloadStatus() == DownloadStatus.FAILED) {
+                return null;
+            }
+            InputStream in = new FileInputStream(report.getLocalFile());
+            try {
+                ZipInputStream zipped = findEntry(in, "feature.xml");
+                if (zipped == null) {
+                    return null;
+                }
+                EclipseFeature f = FeatureParser.parse(zipped);
+                f.setURL(feature.getUrl());
+                repoDescriptor.addFeature(f);
+            } finally {
+                in.close();
+            }
+        }
+
+        return repoDescriptor;
+    }
+
+    private ZipInputStream findEntry(InputStream in, String entryName) throws IOException {
+        ZipInputStream zipped = new ZipInputStream(in);
+        ZipEntry zipEntry = zipped.getNextEntry();
+        while (zipEntry != null && !zipEntry.getName().equals(entryName)) {
+            zipEntry = zipped.getNextEntry();
+        }
+        if (zipEntry == null) {
+            return null;
+        }
+        return zipped;
+    }
+}
diff --git a/src/java/org/apache/ivy/osgi/updatesite/UpdateSiteResolver.java b/src/java/org/apache/ivy/osgi/updatesite/UpdateSiteResolver.java
new file mode 100644
index 0000000..e8bf6d1
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/updatesite/UpdateSiteResolver.java
@@ -0,0 +1,140 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.updatesite;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.text.ParseException;
+
+import org.apache.ivy.core.cache.ArtifactOrigin;
+import org.apache.ivy.core.cache.CacheResourceOptions;
+import org.apache.ivy.core.cache.DownloadListener;
+import org.apache.ivy.core.cache.RepositoryCacheManager;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.report.ArtifactDownloadReport;
+import org.apache.ivy.osgi.repo.AbstractOSGiResolver;
+import org.apache.ivy.osgi.repo.RepoDescriptor;
+import org.apache.ivy.plugins.resolver.util.ResolvedResource;
+import org.apache.ivy.util.Message;
+import org.xml.sax.SAXException;
+
+public class UpdateSiteResolver extends AbstractOSGiResolver {
+
+    private String url;
+
+    private Long metadataTtl;
+
+    private Boolean forceMetadataUpdate;
+
+    private String logLevel;
+
+    public void setUrl(String url) {
+        this.url = url;
+    }
+
+    public void setMetadataTtl(Long metadataTtl) {
+        this.metadataTtl = metadataTtl;
+    }
+
+    public void setForceMetadataUpdate(Boolean forceMetadataUpdate) {
+        this.forceMetadataUpdate = forceMetadataUpdate;
+    }
+
+    public void setLogLevel(String logLevel) {
+        this.logLevel = logLevel;
+    }
+
+    protected void init() {
+        if (url == null) {
+            throw new RuntimeException("Missing url");
+        }
+        CacheResourceOptions options = new CacheResourceOptions();
+        if (metadataTtl != null) {
+            options.setTtl(metadataTtl.longValue());
+        }
+        if (forceMetadataUpdate != null) {
+            options.setForce(forceMetadataUpdate.booleanValue());
+        }
+        final int log;
+        if (logLevel != null) {
+            if ("debug".equalsIgnoreCase(logLevel)) {
+                log = Message.MSG_DEBUG;
+            } else if ("verbose".equalsIgnoreCase(logLevel)) {
+                log = Message.MSG_VERBOSE;
+            } else if ("info".equalsIgnoreCase(logLevel)) {
+                log = Message.MSG_INFO;
+            } else if ("warn".equalsIgnoreCase(logLevel)) {
+                log = Message.MSG_WARN;
+            } else if ("error".equalsIgnoreCase(logLevel)) {
+                log = Message.MSG_ERR;
+            } else {
+                throw new RuntimeException("Unknown log level: " + logLevel);
+            }
+        } else {
+            log = Message.MSG_INFO;
+        }
+        options.setListener(new DownloadListener() {
+            public void startArtifactDownload(RepositoryCacheManager cache, ResolvedResource rres,
+                    Artifact artifact, ArtifactOrigin origin) {
+                if (log <= Message.MSG_INFO) {
+                    Message.info("\tdownloading " + rres.getResource().getName());
+                }
+            }
+
+            public void needArtifact(RepositoryCacheManager cache, Artifact artifact) {
+                if (log <= Message.MSG_VERBOSE) {
+                    Message.verbose("\ttrying to download " + artifact);
+                }
+            }
+
+            public void endArtifactDownload(RepositoryCacheManager cache, Artifact artifact,
+                    ArtifactDownloadReport adr, File archiveFile) {
+                if (log <= Message.MSG_VERBOSE) {
+                    if (adr.isDownloaded()) {
+                        Message.verbose("\tdownloaded to " + archiveFile.getAbsolutePath());
+                    } else {
+                        Message.verbose("\tnothing to download");
+                    }
+                }
+            }
+        });
+        UpdateSiteLoader loader = new UpdateSiteLoader(getRepositoryCacheManager(),
+                getEventManager(), options);
+        loader.setLogLevel(log);
+        RepoDescriptor repoDescriptor;
+        try {
+            repoDescriptor = loader.load(new URI(url));
+        } catch (IOException e) {
+            throw new RuntimeException("IO issue while trying to read the update site ("
+                    + e.getMessage() + ")");
+        } catch (ParseException e) {
+            throw new RuntimeException("Failed to parse the updatesite (" + e.getMessage() + ")", e);
+        } catch (SAXException e) {
+            throw new RuntimeException("Illformed updatesite (" + e.getMessage() + ")", e);
+        } catch (URISyntaxException e) {
+            throw new RuntimeException("Illformed url (" + e.getMessage() + ")", e);
+        }
+        if (repoDescriptor == null) {
+            setRepoDescriptor(FAILING_REPO_DESCRIPTOR);
+            throw new RuntimeException("No update site was found at the location: " + url);
+        }
+        setRepoDescriptor(repoDescriptor);
+    }
+}
diff --git a/src/java/org/apache/ivy/osgi/updatesite/xml/Archive.java b/src/java/org/apache/ivy/osgi/updatesite/xml/Archive.java
new file mode 100644
index 0000000..6215382
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/updatesite/xml/Archive.java
@@ -0,0 +1,32 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.updatesite.xml;
+
+public class Archive {
+
+    public void setPath(String path) {
+        // TODO Auto-generated method stub
+
+    }
+
+    public void setURL(String url) {
+        // TODO Auto-generated method stub
+
+    }
+
+}
diff --git a/src/java/org/apache/ivy/osgi/updatesite/xml/CategoryDef.java b/src/java/org/apache/ivy/osgi/updatesite/xml/CategoryDef.java
new file mode 100644
index 0000000..c26f27f
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/updatesite/xml/CategoryDef.java
@@ -0,0 +1,37 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.updatesite.xml;
+
+public class CategoryDef {
+
+    public void setLabel(String label) {
+        // TODO Auto-generated method stub
+
+    }
+
+    public void setName(String name) {
+        // TODO Auto-generated method stub
+
+    }
+
+    public void setDescription(String trim) {
+        // TODO Auto-generated method stub
+
+    }
+
+}
diff --git a/src/java/org/apache/ivy/osgi/updatesite/xml/EclipseFeature.java b/src/java/org/apache/ivy/osgi/updatesite/xml/EclipseFeature.java
new file mode 100644
index 0000000..2e21044
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/updatesite/xml/EclipseFeature.java
@@ -0,0 +1,176 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.updatesite.xml;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.ivy.osgi.util.Version;
+
+public class EclipseFeature {
+
+    private String id;
+
+    private Version version;
+
+    private List<EclipsePlugin> plugins = new ArrayList<EclipsePlugin>();
+
+    private List<Require> requires = new ArrayList<Require>();
+
+    private String url;
+
+    private String description;
+
+    private String license;
+
+    public EclipseFeature(String id, Version version) {
+        this.id = id;
+        this.version = version;
+        this.url = "features/" + id + '_' + version + ".jar";
+    }
+
+    public void setURL(String url) {
+        this.url = url;
+    }
+
+    public String getUrl() {
+        return url;
+    }
+
+    public void setType(String type) {
+        // TODO Auto-generated method stub
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public Version getVersion() {
+        return version;
+    }
+
+    public void setLabel(String label) {
+        // TODO Auto-generated method stub
+
+    }
+
+    public void setOS(String os) {
+        // TODO Auto-generated method stub
+
+    }
+
+    public void setWS(String ws) {
+        // TODO Auto-generated method stub
+
+    }
+
+    public void setNL(String nl) {
+        // TODO Auto-generated method stub
+
+    }
+
+    public void setArch(String arch) {
+        // TODO Auto-generated method stub
+
+    }
+
+    public void setPatch(String patch) {
+        // TODO Auto-generated method stub
+
+    }
+
+    public void addCategory(String name) {
+        // TODO Auto-generated method stub
+
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setCopyright(String trim) {
+        // not useful
+    }
+
+    public void setLicense(String license) {
+        this.license = license;
+    }
+
+    public String getLicense() {
+        return license;
+    }
+
+    public void addPlugin(EclipsePlugin plugin) {
+        plugins.add(plugin);
+    }
+
+    public List<EclipsePlugin> getPlugins() {
+        return plugins;
+    }
+
+    public void addRequire(Require require) {
+        requires.add(require);
+    }
+
+    public List<Require> getRequires() {
+        return requires;
+    }
+
+    public void setApplication(String value) {
+        // TODO Auto-generated method stub
+
+    }
+
+    public void setPlugin(String value) {
+        // TODO Auto-generated method stub
+
+    }
+
+    public void setExclusive(boolean booleanValue) {
+        // TODO Auto-generated method stub
+
+    }
+
+    public void setPrimary(boolean booleanValue) {
+        // TODO Auto-generated method stub
+
+    }
+
+    public void setColocationAffinity(String value) {
+        // TODO Auto-generated method stub
+
+    }
+
+    public void setProviderName(String value) {
+        // TODO Auto-generated method stub
+
+    }
+
+    public void setImage(String value) {
+        // TODO Auto-generated method stub
+
+    }
+
+    public String toString() {
+        return id + "#" + version;
+    }
+}
diff --git a/src/java/org/apache/ivy/osgi/updatesite/xml/EclipsePlugin.java b/src/java/org/apache/ivy/osgi/updatesite/xml/EclipsePlugin.java
new file mode 100644
index 0000000..2226344
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/updatesite/xml/EclipsePlugin.java
@@ -0,0 +1,59 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.updatesite.xml;
+
+import org.apache.ivy.osgi.util.Version;
+
+public class EclipsePlugin {
+
+    private String id;
+
+    private Version version;
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setVersion(Version version) {
+        this.version = version;
+    }
+
+    public Version getVersion() {
+        return version;
+    }
+
+    public void setUnpack(boolean valueOf) {
+        // TODO Auto-generated method stub
+
+    }
+
+    public void setFragment(String value) {
+        // TODO Auto-generated method stub
+
+    }
+
+    public void setFilter(String value) {
+        // TODO Auto-generated method stub
+
+    }
+
+}
diff --git a/src/java/org/apache/ivy/osgi/updatesite/xml/EclipseUpdateSiteParser.java b/src/java/org/apache/ivy/osgi/updatesite/xml/EclipseUpdateSiteParser.java
new file mode 100644
index 0000000..08f2fb9
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/updatesite/xml/EclipseUpdateSiteParser.java
@@ -0,0 +1,277 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.updatesite.xml;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.text.ParseException;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.apache.ivy.osgi.util.DelegatingHandler;
+import org.apache.ivy.osgi.util.Version;
+import org.apache.ivy.util.XMLHelper;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+
+public class EclipseUpdateSiteParser {
+
+    public static UpdateSite parse(InputStream in) throws ParseException, IOException, SAXException {
+        SiteHandler handler = new SiteHandler();
+        try {
+            XMLHelper.parse(in, null, handler, null);
+        } catch (ParserConfigurationException e) {
+            throw new SAXException(e);
+        }
+        return handler.updatesite;
+    }
+
+    private static class SiteHandler extends DelegatingHandler {
+
+        private static final String SITE = "site";
+
+        private static final String URL = "url";
+
+        private static final String PACK200 = "pack200";
+
+        private static final String MIRRORS_URL = "mirrorsURL";
+
+        private static final String ASSOCIATE_SITES_URL = "associateSitesURL";
+
+        private static final String DIGEST_URL = "digestURL";
+
+        UpdateSite updatesite;
+
+        public SiteHandler() {
+            super(SITE);
+            // addChild(new DescriptionHandler(), new ChildElementHandler() {
+            // public void childHanlded(DelegetingHandler child) {
+            // updateSite.setDescription(child.getBufferedChars().trim());
+            // }
+            // });
+            addChild(new FeatureHandler(), new ChildElementHandler<FeatureHandler>() {
+                public void childHanlded(FeatureHandler child) {
+                    updatesite.addFeature(child.feature);
+                }
+            });
+            // addChild(new ArchiveHandler(), new ChildElementHandler() {
+            // public void childHanlded(DelegetingHandler child) {
+            // updateSite.addArchive(((ArchiveHandler) child).archive);
+            // }
+            // });
+            // addChild(new CategoryDefHandler(), new ChildElementHandler() {
+            // public void childHanlded(DelegetingHandler child) {
+            // updateSite.addCategoryDef(((CategoryDefHandler) child).categoryDef);
+            // }
+            // });
+        }
+
+        protected void handleAttributes(Attributes atts) {
+            updatesite = new UpdateSite();
+
+            String url = atts.getValue(URL);
+            if (url != null && !("".equals(url.trim()))) {
+                if (!url.endsWith("/") && !url.endsWith(File.separator)) {
+                    url += "/";
+                }
+                try {
+                    updatesite.setUri(new URI(url));
+                } catch (URISyntaxException e) {
+                    throw new RuntimeException("illegal url", e);
+                }
+            }
+
+            String mirrorsURL = atts.getValue(MIRRORS_URL);
+            if (mirrorsURL != null && mirrorsURL.trim().length() > 0) {
+                updatesite.setMirrorsURL(mirrorsURL);
+            }
+
+            String pack200 = atts.getValue(PACK200);
+            if (pack200 != null && new Boolean(pack200).booleanValue()) {
+                updatesite.setPack200(true);
+            }
+
+            String digestURL = atts.getValue(DIGEST_URL);
+            if (digestURL != null) {
+                try {
+                    updatesite.setDigestUri(new URI(digestURL));
+                } catch (URISyntaxException e) {
+                    throw new RuntimeException("illegal url", e);
+                }
+            }
+
+            String associateSitesURL = atts.getValue(ASSOCIATE_SITES_URL);
+            if (associateSitesURL != null) {
+                updatesite.setAssociateSitesURL(associateSitesURL);
+            }
+        }
+    }
+
+    // private static class DescriptionHandler extends DelegetingHandler {
+    //
+    // private static final String DESCRIPTION = "description";
+    //
+    // private static final String URL = "url";
+    //
+    // public DescriptionHandler() {
+    // super(DESCRIPTION);
+    // setBufferingChar(true);
+    // }
+    //
+    // protected void handleAttributes(Attributes atts) {
+    // String url = atts.getValue(URL);
+    // }
+    // }
+
+    private static class FeatureHandler extends DelegatingHandler {
+
+        private static final String FEATURE = "feature";
+
+        private static final String VERSION = "version";
+
+        private static final String ID = "id";
+
+        private static final String URL = "url";
+
+        private static final String PATCH = "patch";
+
+        private static final String ARCH = "arch";
+
+        private static final String NL = "nl";
+
+        private static final String WS = "ws";
+
+        private static final String OS = "os";
+
+        private static final String LABEL = "label";
+
+        private static final String TYPE = "type";
+
+        private EclipseFeature feature;
+
+        public FeatureHandler() {
+            super(FEATURE);
+            addChild(new CategoryHandler(), new ChildElementHandler<CategoryHandler>() {
+                public void childHanlded(CategoryHandler child) {
+                    feature.addCategory(child.name);
+                }
+            });
+        }
+
+        protected void handleAttributes(Attributes atts) throws SAXException {
+            String id = atts.getValue(ID);
+            String version = atts.getValue(VERSION);
+            try {
+                feature = new EclipseFeature(id, new Version(version));
+            } catch (ParseException e) {
+                throw new SAXException("Incorrect version on the feature '" + id + "': " + version
+                        + " (" + e.getMessage() + ")");
+            }
+
+            String url = atts.getValue(URL);
+            if (url != null) {
+                feature.setURL(url);
+            }
+            feature.setType(atts.getValue(TYPE));
+            feature.setLabel(atts.getValue(LABEL));
+            feature.setOS(atts.getValue(OS));
+            feature.setWS(atts.getValue(WS));
+            feature.setNL(atts.getValue(NL));
+            feature.setArch(atts.getValue(ARCH));
+            feature.setPatch(atts.getValue(PATCH));
+        }
+
+    }
+
+    private static class CategoryHandler extends DelegatingHandler {
+
+        private static final String CATEGORY = "category";
+
+        private static final String NAME = "name";
+
+        String name;
+
+        public CategoryHandler() {
+            super(CATEGORY);
+        }
+
+        protected void handleAttributes(Attributes atts) throws SAXException {
+            name = atts.getValue(NAME);
+        }
+    }
+
+    // private static class ArchiveHandler extends DelegetingHandler {
+    //
+    // private static final String ARCHIVE = "archive";
+    //
+    // private static final String URL = "url";
+    //
+    // private static final String PATH = "path";
+    //
+    // private Archive archive;
+    //
+    // public ArchiveHandler() {
+    // super(ARCHIVE);
+    // }
+    //
+    // protected void handleAttributes(Attributes atts) throws SAXException {
+    // archive = new Archive();
+    //
+    // String path = atts.getValue(PATH);
+    // archive.setPath(path);
+    //
+    // String url = atts.getValue(URL);
+    // archive.setURL(url);
+    //
+    // }
+    // }
+
+    // private static class CategoryDefHandler extends DelegetingHandler {
+    //
+    // private static final String CATEGORY_DEF = "category-def";
+    //
+    // private static final String NAME = "name";
+    //
+    // private static final String LABEL = "label";
+    //
+    // private CategoryDef categoryDef;
+    //
+    // public CategoryDefHandler() {
+    // super(CATEGORY_DEF);
+    // addChild(new DescriptionHandler(), new ChildElementHandler<DescriptionHandler>() {
+    // public void childHanlded(DescriptionHandler child) {
+    // categoryDef.setDescription(child.getBufferedChars().trim());
+    // }
+    // });
+    // }
+    //
+    // protected void handleAttributes(Attributes atts) throws SAXException {
+    // categoryDef = new CategoryDef();
+    //
+    // String name = atts.getValue(NAME);
+    // categoryDef.setName(name);
+    //
+    // String label = atts.getValue(LABEL);
+    // categoryDef.setLabel(label);
+    // }
+    // }
+
+}
diff --git a/src/java/org/apache/ivy/osgi/updatesite/xml/FeatureParser.java b/src/java/org/apache/ivy/osgi/updatesite/xml/FeatureParser.java
new file mode 100644
index 0000000..739b828
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/updatesite/xml/FeatureParser.java
@@ -0,0 +1,390 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.updatesite.xml;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.apache.ivy.osgi.util.DelegatingHandler;
+import org.apache.ivy.osgi.util.Version;
+import org.apache.ivy.util.XMLHelper;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+
+public class FeatureParser {
+
+    public static EclipseFeature parse(InputStream in) throws ParseException, IOException,
+            SAXException {
+        FeatureHandler handler = new FeatureHandler();
+        try {
+            XMLHelper.parse(in, null, handler, null);
+        } catch (ParserConfigurationException e) {
+            throw new SAXException(e);
+        }
+        return handler.feature;
+    }
+
+    static class FeatureHandler extends DelegatingHandler {
+
+        private static final String FEATURE = "feature";
+
+        private static final String COLOCATION_AFFINITY = "colocation-affinity";
+
+        private static final String PRIMARY = "primary";
+
+        private static final String EXCLUSIVE = "exclusive";
+
+        private static final String PLUGIN = "plugin";
+
+        private static final String APPLICATION = "application";
+
+        private static final String ARCH = "arch";
+
+        private static final String NL = "nl";
+
+        private static final String WS = "ws";
+
+        private static final String OS = "os";
+
+        private static final String VERSION = "version";
+
+        private static final String ID = "id";
+
+        private static final String PROVIDER_NAME = "provider-name";
+
+        private static final String LABEL = "label";
+
+        private static final String IMAGE = "image";
+
+        EclipseFeature feature;
+
+        public FeatureHandler() {
+            super(FEATURE);
+            addChild(new DescriptionHandler(), new ChildElementHandler<DescriptionHandler>() {
+                public void childHanlded(DescriptionHandler child) {
+                    feature.setDescription(child.getBufferedChars().trim());
+                }
+            });
+            addChild(new LicenseHandler(), new ChildElementHandler<LicenseHandler>() {
+                public void childHanlded(LicenseHandler child) {
+                    feature.setLicense(child.getBufferedChars().trim());
+                }
+            });
+            addChild(new CopyrightHandler(), new ChildElementHandler<CopyrightHandler>() {
+                public void childHanlded(CopyrightHandler child) {
+                    feature.setCopyright(child.getBufferedChars().trim());
+                }
+            });
+            addChild(new PluginHandler(), new ChildElementHandler<PluginHandler>() {
+                public void childHanlded(PluginHandler child) {
+                    feature.addPlugin(child.plugin);
+                }
+            });
+            addChild(new RequiresHandler(), new ChildElementHandler<RequiresHandler>() {
+                public void childHanlded(RequiresHandler child) {
+                    for (Require require : child.requires) {
+                        feature.addRequire(require);
+                    }
+                }
+            });
+            // addChild(new UrlHandler(), new ChildElementHandler<UrlHandler>() {
+            // public void childHanlded(UrlHandler child) {
+            // }
+            // });
+        }
+
+        protected void handleAttributes(Attributes atts) throws SAXException {
+            String id = atts.getValue(ID);
+            String version = atts.getValue(VERSION);
+            try {
+                feature = new EclipseFeature(id, new Version(version));
+            } catch (ParseException e) {
+                throw new SAXException("Incorrect version on feature '" + id + "': " + version
+                        + " (" + e.getMessage() + ")");
+            }
+
+            feature.setOS(atts.getValue(OS));
+            feature.setWS(atts.getValue(WS));
+            feature.setNL(atts.getValue(NL));
+            feature.setArch(atts.getValue(ARCH));
+            feature.setApplication(atts.getValue(APPLICATION));
+            feature.setPlugin(atts.getValue(PLUGIN));
+            feature.setExclusive(Boolean.valueOf(atts.getValue(EXCLUSIVE)).booleanValue());
+            feature.setPrimary(Boolean.valueOf(atts.getValue(PRIMARY)).booleanValue());
+            feature.setColocationAffinity(atts.getValue(COLOCATION_AFFINITY));
+            feature.setProviderName(atts.getValue(PROVIDER_NAME));
+            feature.setLabel(atts.getValue(LABEL));
+            feature.setImage(atts.getValue(IMAGE));
+        }
+
+    }
+
+    private static class PluginHandler extends DelegatingHandler {
+
+        private static final String PLUGIN = "plugin";
+
+        private static final String FILTER = "filter";
+
+        private static final String FRAGMENT = "fragment";
+
+        private static final String UNPACK = "unpack";
+
+        private static final String VERSION = "version";
+
+        private static final String ID = "id";
+
+        private EclipsePlugin plugin;
+
+        public PluginHandler() {
+            super(PLUGIN);
+        }
+
+        protected void handleAttributes(Attributes atts) throws SAXException {
+            plugin = new EclipsePlugin();
+
+            String id = atts.getValue(ID);
+            String version = atts.getValue(VERSION);
+
+            plugin.setId(id);
+            try {
+                plugin.setVersion(new Version(version));
+            } catch (ParseException e) {
+                throw new SAXException("Incorrect version on feature's plugin '" + id + "': "
+                        + version + " (" + e.getMessage() + ")");
+            }
+            plugin.setUnpack(Boolean.valueOf(atts.getValue(UNPACK)).booleanValue());
+            plugin.setFragment(atts.getValue(FRAGMENT));
+            plugin.setFilter(atts.getValue(FILTER));
+        }
+    }
+
+    private static class DescriptionHandler extends DelegatingHandler {
+
+        private static final String DESCRIPTION = "description";
+
+        // private static final String URL = "url";
+
+        public DescriptionHandler() {
+            super(DESCRIPTION);
+            setBufferingChar(true);
+        }
+
+        protected void handleAttributes(Attributes atts) throws SAXException {
+            // String url = atts.getValue(URL);
+        }
+    }
+
+    private static class LicenseHandler extends DelegatingHandler {
+
+        private static final String LICENSE = "license";
+
+        // private static final String URL = "url";
+
+        public LicenseHandler() {
+            super(LICENSE);
+            setBufferingChar(true);
+        }
+
+        protected void handleAttributes(Attributes atts) throws SAXException {
+            // String url = atts.getValue(URL);
+        }
+
+    }
+
+    private static class CopyrightHandler extends DelegatingHandler {
+
+        private static final String COPYRIGHT = "copyright";
+
+        // private static final String URL = "url";
+
+        public CopyrightHandler() {
+            super(COPYRIGHT);
+            setBufferingChar(true);
+        }
+
+        protected void handleAttributes(Attributes atts) throws SAXException {
+            // String url = atts.getValue(URL);
+        }
+    }
+
+    static class RequiresHandler extends DelegatingHandler {
+
+        private static final String REQUIRES = "requires";
+
+        List<Require> requires = new ArrayList<Require>();
+
+        public RequiresHandler() {
+            super(REQUIRES);
+            addChild(new ImportHandler(), new ChildElementHandler<ImportHandler>() {
+                public void childHanlded(ImportHandler child) {
+                    requires.add(child.require);
+                }
+            });
+        }
+    }
+
+    private static class ImportHandler extends DelegatingHandler {
+
+        Require require;
+
+        private static final String IMPORT = "import";
+
+        private static final String FILTER = "filter";
+
+        private static final String MATCH = "match";
+
+        private static final String VERSION = "version";
+
+        private static final String PLUGIN = "plugin";
+
+        private static final String FEATURE = "feature";
+
+        public ImportHandler() {
+            super(IMPORT);
+        }
+
+        protected void handleAttributes(Attributes atts) throws SAXException {
+            require = new Require();
+
+            String version = atts.getValue(VERSION);
+
+            require.setFeature(atts.getValue(FEATURE));
+            require.setPlugin(atts.getValue(PLUGIN));
+            try {
+                require.setVersion(new Version(version));
+            } catch (ParseException e) {
+                throw new SAXException("Incorrect version on feature's import: " + version + " ("
+                        + e.getMessage() + ")");
+            }
+            require.setMatch(atts.getValue(MATCH));
+            require.setFilter(atts.getValue(FILTER));
+        }
+    }
+
+    // private static class IncludesHandler extends DelegetingHandler {
+    //
+    // private static final String INCLUDES = "includes";
+    //
+    // private static final String FILTER = "filter";
+    //
+    // private static final String OPTIONAL = "optional";
+    //
+    // private static final String VERSION = "version";
+    //
+    // private static final String ID = "id";
+    //
+    // public IncludesHandler() {
+    // super(INCLUDES);
+    // }
+    //
+    // protected void handleAttributes(Attributes atts) throws SAXException {
+    // String id = atts.getValue(ID);
+    // String version = atts.getValue(VERSION);
+    // String optional = atts.getValue(OPTIONAL);
+    // String filter = atts.getValue(FILTER);
+    // }
+    //
+    // }
+
+    // private static class InstallHandlerHandler extends DelegetingHandler {
+    //
+    // private static final String INSTALL_HANDLER = "install-handler";
+    //
+    // private static final String URL = "url";
+    //
+    // private static final String LIBRARY = "library";
+    //
+    // private static final String HANDLER = "handler";
+    //
+    // public InstallHandlerHandler() {
+    // super(INSTALL_HANDLER);
+    // }
+    //
+    // protected void handleAttributes(Attributes atts) throws SAXException {
+    // String handler = atts.getValue(HANDLER);
+    // String library = atts.getValue(LIBRARY);
+    // String url = atts.getValue(URL);
+    // }
+    //
+    // }
+
+    // private static class UrlHandler extends DelegetingHandler {
+    //
+    // private static final String URL = "url";
+    //
+    // public UrlHandler() {
+    // super(URL);
+    // addChild(new UpdateHandler(), new ChildElementHandler<UpdateHandler>() {
+    // public void childHanlded(UpdateHandler child) {
+    // }
+    // });
+    // addChild(new DiscoveryHandler(), new ChildElementHandler<DiscoveryHandler>() {
+    // public void childHanlded(DiscoveryHandler child) {
+    // }
+    // });
+    // }
+    //
+    // }
+
+    // private static class UpdateHandler extends DelegetingHandler {
+    //
+    // private static final String UPDATE = "update";
+    //
+    // private static final String LABEL = "label";
+    //
+    // private static final String URL = "url";
+    //
+    // public UpdateHandler() {
+    // super(UPDATE);
+    // }
+    //
+    // protected void handleAttributes(Attributes atts) throws SAXException {
+    // String label = atts.getValue(LABEL);
+    // String url = atts.getValue(URL);
+    // }
+    //
+    // }
+
+    // private static class DiscoveryHandler extends DelegetingHandler {
+    //
+    // private static final String DISCOVERY = "discovery";
+    //
+    // private static final String URL = "url";
+    //
+    // private static final String LABEL = "label";
+    //
+    // private static final String TYPE = "type";
+    //
+    // public DiscoveryHandler() {
+    // super(DISCOVERY);
+    // }
+    //
+    // protected void handleAttributes(Attributes atts) throws SAXException {
+    // String type = atts.getValue(TYPE);
+    // String label = atts.getValue(LABEL);
+    // String url = atts.getValue(URL);
+    // }
+    //
+    // }
+
+}
diff --git a/src/java/org/apache/ivy/osgi/updatesite/xml/Require.java b/src/java/org/apache/ivy/osgi/updatesite/xml/Require.java
new file mode 100644
index 0000000..701bdc8
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/updatesite/xml/Require.java
@@ -0,0 +1,73 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.updatesite.xml;
+
+import org.apache.ivy.osgi.util.Version;
+
+public class Require {
+
+    private String plugin;
+
+    private String feature;
+
+    private Version version;
+
+    private String match;
+
+    private String filter;
+
+    public void setFeature(String feature) {
+        this.feature = feature;
+    }
+
+    public String getFeature() {
+        return feature;
+    }
+
+    public void setPlugin(String plugin) {
+        this.plugin = plugin;
+    }
+
+    public String getPlugin() {
+        return plugin;
+    }
+
+    public void setVersion(Version version) {
+        this.version = version;
+    }
+
+    public Version getVersion() {
+        return version;
+    }
+
+    public void setMatch(String match) {
+        this.match = match;
+    }
+
+    public String getMatch() {
+        return match;
+    }
+
+    public void setFilter(String filter) {
+        this.filter = filter;
+    }
+
+    public String getFilter() {
+        return filter;
+    }
+}
diff --git a/src/java/org/apache/ivy/osgi/updatesite/xml/UpdateSite.java b/src/java/org/apache/ivy/osgi/updatesite/xml/UpdateSite.java
new file mode 100644
index 0000000..de1f84d
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/updatesite/xml/UpdateSite.java
@@ -0,0 +1,72 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.updatesite.xml;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+
+public class UpdateSite {
+
+    private URI uri;
+
+    private String mirrorsURL;
+
+    private boolean pack200;
+
+    private URI digestUri;
+
+    private List<EclipseFeature> features = new ArrayList<EclipseFeature>();
+
+    public void setUri(URI uri) {
+        this.uri = uri;
+    }
+
+    public URI getUri() {
+        return uri;
+    }
+
+    public void setMirrorsURL(String mirrorsURL) {
+        this.mirrorsURL = mirrorsURL;
+    }
+
+    public void setPack200(boolean pack200) {
+        this.pack200 = pack200;
+    }
+
+    public void setDigestUri(URI digestUri) {
+        this.digestUri = digestUri;
+    }
+
+    public URI getDigestUri() {
+        return digestUri;
+    }
+
+    public void addFeature(EclipseFeature feature) {
+        features.add(feature);
+    }
+
+    public List<EclipseFeature> getFeatures() {
+        return features;
+    }
+
+    public void setAssociateSitesURL(String associateSitesURL) {
+        // TODO what's that ?
+    }
+
+}
diff --git a/src/java/org/apache/ivy/osgi/updatesite/xml/UpdateSiteDigestParser.java b/src/java/org/apache/ivy/osgi/updatesite/xml/UpdateSiteDigestParser.java
new file mode 100644
index 0000000..681823b
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/updatesite/xml/UpdateSiteDigestParser.java
@@ -0,0 +1,65 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.updatesite.xml;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.text.ParseException;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.apache.ivy.osgi.core.ExecutionEnvironmentProfileProvider;
+import org.apache.ivy.osgi.updatesite.UpdateSiteDescriptor;
+import org.apache.ivy.osgi.updatesite.xml.FeatureParser.FeatureHandler;
+import org.apache.ivy.osgi.util.DelegatingHandler;
+import org.apache.ivy.util.XMLHelper;
+import org.xml.sax.SAXException;
+
+public class UpdateSiteDigestParser {
+
+    public static UpdateSiteDescriptor parse(InputStream in, UpdateSite site)
+            throws ParseException, IOException, SAXException {
+        DigestHandler handler = new DigestHandler(site);
+        try {
+            XMLHelper.parse(in, null, handler, null);
+        } catch (ParserConfigurationException e) {
+            throw new SAXException(e);
+        }
+        return handler.repoDescriptor;
+    }
+
+    static class DigestHandler extends DelegatingHandler {
+
+        private static final String DIGEST = "digest";
+
+        UpdateSiteDescriptor repoDescriptor;
+
+        public DigestHandler(final UpdateSite site) {
+            super(DIGEST);
+            repoDescriptor = new UpdateSiteDescriptor(site.getUri(),
+                    ExecutionEnvironmentProfileProvider.getInstance());
+            addChild(new FeatureHandler(), new ChildElementHandler<FeatureHandler>() {
+                public void childHanlded(FeatureHandler child) {
+                    repoDescriptor.addFeature(child.feature);
+                }
+            });
+        }
+
+    }
+
+}
diff --git a/src/java/org/apache/ivy/osgi/util/DelegatingHandler.java b/src/java/org/apache/ivy/osgi/util/DelegatingHandler.java
new file mode 100644
index 0000000..05fb53a
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/util/DelegatingHandler.java
@@ -0,0 +1,636 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.util;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.ivy.util.Message;
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.DTDHandler;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.helpers.DefaultHandler;
+
+public class DelegatingHandler extends DefaultHandler implements DTDHandler, ContentHandler,
+        ErrorHandler {
+
+    private DelegatingHandler delegate = null;
+
+    DelegatingHandler parent;
+
+    private final Map<String, DelegatingHandler> saxHandlerMapping = new HashMap<String, DelegatingHandler>();
+
+    private final Map<String, ChildElementHandler<?>> childHandlerMapping = new HashMap<String, DelegatingHandler.ChildElementHandler<?>>();
+
+    private final String tagName;
+
+    private boolean started = false;
+
+    private boolean skip = false;
+
+    private boolean skipOnError = false;
+
+    private StringBuffer charBuffer = new StringBuffer();
+
+    private boolean bufferingChar = false;
+
+    private Locator locator;
+
+    public DelegatingHandler(String name) {
+        this.tagName = name;
+        charBuffer.setLength(0);
+    }
+
+    protected <DH extends DelegatingHandler> void addChild(DH saxHandler,
+            ChildElementHandler<DH> elementHandler) {
+        saxHandlerMapping.put(saxHandler.getName(), saxHandler);
+        childHandlerMapping.put(saxHandler.getName(), elementHandler);
+        saxHandler.parent = this;
+    }
+
+    public String getName() {
+        return tagName;
+    }
+
+    public DelegatingHandler getParent() {
+        return parent;
+    }
+
+    public void setBufferingChar(boolean bufferingChar) {
+        this.bufferingChar = bufferingChar;
+    }
+
+    public void setSkipOnError(boolean skipOnError) {
+        this.skipOnError = skipOnError;
+    }
+
+    public boolean isBufferingChar() {
+        return bufferingChar;
+    }
+
+    public String getBufferedChars() {
+        return charBuffer.toString();
+    }
+
+    public void setDocumentLocator(Locator locator) {
+        this.locator = locator;
+        for (DelegatingHandler subHandler : saxHandlerMapping.values()) {
+            subHandler.setDocumentLocator(locator);
+        }
+    }
+
+    public Locator getLocator() {
+        return locator;
+    }
+
+    /**
+     * Return an sort of identifier of the current element being parsed. It will only be used for
+     * logging purpose.
+     * 
+     * @return an empty string by default
+     */
+    protected String getCurrentElementIdentifier() {
+        return "";
+    }
+
+    public void skip() {
+        skip = true;
+        for (DelegatingHandler subHandler : saxHandlerMapping.values()) {
+            subHandler.stopDelegating();
+        }
+    }
+
+    protected void stopDelegating() {
+        parent.delegate = null;
+        skip = false;
+        started = false;
+        for (DelegatingHandler/* <?> */subHandler : saxHandlerMapping.values()) {
+            subHandler.stopDelegating();
+        }
+    }
+
+    private interface SkipOnErrorCallback {
+        public void call() throws SAXException;
+    }
+
+    private void skipOnError(SkipOnErrorCallback callback) throws SAXException {
+        try {
+            callback.call();
+        } catch (SAXException e) {
+            if (skipOnError) {
+                skip();
+                log(Message.MSG_ERR, e.getMessage(), e);
+            } else {
+                throw e;
+            }
+        }
+    }
+
+    public final void startDocument() throws SAXException {
+        if (skip) {
+            return;
+        }
+        if (delegate != null) {
+            delegate.startDocument();
+        } else {
+            doStartDocument();
+        }
+    }
+
+    /**
+     * @throws SAXException
+     */
+    protected void doStartDocument() throws SAXException {
+        // by default do nothing
+    }
+
+    public final void endDocument() throws SAXException {
+        if (skip) {
+            return;
+        }
+        if (delegate != null) {
+            delegate.endDocument();
+        } else {
+            doEndDocument();
+        }
+    }
+
+    /**
+     * @throws SAXException
+     */
+    protected void doEndDocument() throws SAXException {
+        // by default do nothing
+    }
+
+    public final void startElement(final String uri, final String localName, final String n,
+            final Attributes atts) throws SAXException {
+        // reset the char buffer
+        charBuffer.setLength(0);
+        if (delegate != null) {
+            // we are already delegating, let's continue
+            skipOnError(new SkipOnErrorCallback() {
+                public void call() throws SAXException {
+                    delegate.startElement(uri, localName, n, atts);
+                }
+            });
+        } else {
+            if (!started) { // first time called ?
+                // just for the root, check the expected element name
+                // not need to check the delegated as the mapping is already taking care of it
+                if (parent == null && !localName.equals(tagName)) {
+                    // we are at the root and the saxed element doesn't match
+                    throw new SAXException("The root element of the parsed document '" + localName
+                            + "' didn't matched the expected one: '" + tagName + "'");
+                }
+                skipOnError(new SkipOnErrorCallback() {
+                    public void call() throws SAXException {
+                        handleAttributes(atts);
+                    }
+                });
+                started = true;
+            } else {
+                if (skip) {
+                    // we con't care anymore about that part of the xml tree
+                    return;
+                }
+                // time now to delegate for a new element
+                delegate = saxHandlerMapping.get(localName);
+                if (delegate != null) {
+                    skipOnError(new SkipOnErrorCallback() {
+                        public void call() throws SAXException {
+                            delegate.startElement(uri, localName, n, atts);
+                        }
+                    });
+                }
+            }
+        }
+    }
+
+    /**
+     * Called when the expected node is achieved
+     * 
+     * @param atts
+     *            the xml attributes attached to the expected node
+     * @exception SAXException
+     *                in case the parsing should be completely stopped
+     */
+    protected void handleAttributes(Attributes atts) throws SAXException {
+        // nothing to do by default
+    }
+
+    /**
+     * @throws SAXException
+     */
+    protected void doStartElement(String uri, String localName, String name, Attributes atts)
+            throws SAXException {
+        // by default do nothing
+    }
+
+    public final void endElement(final String uri, final String localName, final String n)
+            throws SAXException {
+        if (delegate != null) {
+            final DelegatingHandler savedDelegate = delegate;
+            // we are already delegating, let's continue
+            skipOnError(new SkipOnErrorCallback() {
+                public void call() throws SAXException {
+                    delegate.endElement(uri, localName, n);
+                }
+            });
+            if (delegate == null) {
+                // we just stopped delegating, it means that the child has ended
+                final ChildElementHandler<?> childHandler = childHandlerMapping.get(localName);
+                if (childHandler != null) {
+                    skipOnError(new SkipOnErrorCallback() {
+                        public void call() throws SAXException {
+                            childHandler._childHanlded(savedDelegate);
+                        }
+                    });
+                }
+            }
+        } else {
+            if (!skip) {
+                doEndElement(uri, localName, n);
+            }
+            if (parent != null && tagName.equals(localName)) {
+                // the current element is closed, let's tell the parent to stop delegating
+                stopDelegating();
+            }
+        }
+    }
+
+    /**
+     * @throws SAXException
+     */
+    protected void doEndElement(String uri, String localName, String name) throws SAXException {
+        // by default do nothing
+    }
+
+    public static abstract class ChildElementHandler<DH extends DelegatingHandler> {
+
+        public abstract void childHanlded(DH child) throws SAXParseException;
+
+        // because we know what we're doing
+        @SuppressWarnings("unchecked")
+        private void _childHanlded(DelegatingHandler delegate) throws SAXParseException {
+            childHanlded((DH) delegate);
+        }
+
+    }
+
+    public final void characters(char[] ch, int start, int length) throws SAXException {
+        if (skip) {
+            return;
+        }
+        if (delegate != null) {
+            delegate.characters(ch, start, length);
+        } else {
+            doCharacters(ch, start, length);
+        }
+    }
+
+    /**
+     * @throws SAXException
+     */
+    protected void doCharacters(char[] ch, int start, int length) throws SAXException {
+        if (bufferingChar) {
+            charBuffer.append(ch, start, length);
+        }
+    }
+
+    public final void startPrefixMapping(String prefix, String uri) throws SAXException {
+        if (skip) {
+            return;
+        }
+        if (delegate != null) {
+            delegate.startPrefixMapping(prefix, uri);
+        } else {
+            doStartPrefixMapping(prefix, uri);
+        }
+    }
+
+    /**
+     * @throws SAXException
+     */
+    protected void doStartPrefixMapping(String prefix, String uri) throws SAXException {
+        // by default do nothing
+    }
+
+    public final void endPrefixMapping(String prefix) throws SAXException {
+        if (skip) {
+            return;
+        }
+        if (delegate != null) {
+            delegate.endPrefixMapping(prefix);
+        } else {
+            doEndPrefixMapping(prefix);
+        }
+    }
+
+    /**
+     * @throws SAXException
+     */
+    protected void doEndPrefixMapping(String prefix) throws SAXException {
+        // by default do nothing
+    }
+
+    public final void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
+        if (skip) {
+            return;
+        }
+        if (delegate != null) {
+            delegate.ignorableWhitespace(ch, start, length);
+        } else {
+            doIgnorableWhitespace(ch, start, length);
+        }
+    }
+
+    /**
+     * @throws SAXException
+     */
+    protected void doIgnorableWhitespace(char[] ch, int start, int length) throws SAXException {
+        // by default do nothing
+    }
+
+    public final void notationDecl(String name, String publicId, String systemId)
+            throws SAXException {
+        if (skip) {
+            return;
+        }
+        if (delegate != null) {
+            delegate.notationDecl(name, publicId, systemId);
+        } else {
+            doNotationDecl(name, publicId, systemId);
+        }
+    }
+
+    /**
+     * @throws SAXException
+     */
+    protected void doNotationDecl(String name, String publicId, String systemId)
+            throws SAXException {
+        // by default do nothing
+    }
+
+    public final void processingInstruction(String target, String data) throws SAXException {
+        if (skip) {
+            return;
+        }
+        if (delegate != null) {
+            delegate.processingInstruction(target, data);
+        } else {
+            doProcessingInstruction(target, data);
+        }
+    }
+
+    /**
+     * @throws SAXException
+     */
+    protected void doProcessingInstruction(String target, String data) throws SAXException {
+        // by default do nothing
+    }
+
+    public final void skippedEntity(String name) throws SAXException {
+        if (skip) {
+            return;
+        }
+        if (delegate != null) {
+            delegate.skippedEntity(name);
+        } else {
+            doSkippedEntity(name);
+        }
+    }
+
+    /**
+     * @throws SAXException
+     */
+    protected void doSkippedEntity(String name) throws SAXException {
+        // by default do nothing
+    }
+
+    /**
+     * @throws SAXException
+     */
+    public final void unparsedEntityDecl(String name, String publicId, String systemId,
+            String notationName) throws SAXException {
+        if (skip) {
+            return;
+        }
+        if (delegate != null) {
+            delegate.unparsedEntityDecl(name, publicId, systemId, notationName);
+        } else {
+            doUnparsedEntityDecl(name, publicId, systemId, notationName);
+        }
+    }
+
+    /**
+     * @throws SAXException
+     */
+    protected void doUnparsedEntityDecl(String name, String publicId, String systemId,
+            String notationName) throws SAXException {
+        // by default do nothing
+    }
+
+    // ERROR HANDLING
+
+    public final void warning(SAXParseException exception) throws SAXException {
+        if (skip) {
+            return;
+        }
+        if (delegate != null) {
+            delegate.warning(exception);
+        } else {
+            doWarning(exception);
+        }
+    }
+
+    /**
+     * @throws SAXException
+     */
+    protected void doWarning(SAXParseException exception) throws SAXException {
+        // by default do nothing
+    }
+
+    public final void error(SAXParseException exception) throws SAXException {
+        if (skip) {
+            return;
+        }
+        if (delegate != null) {
+            delegate.error(exception);
+        } else {
+            doError(exception);
+        }
+    }
+
+    /**
+     * @throws SAXException
+     */
+    protected void doError(SAXParseException exception) throws SAXException {
+        // by default do nothing
+    }
+
+    public final void fatalError(SAXParseException exception) throws SAXException {
+        if (skip) {
+            return;
+        }
+        if (delegate != null) {
+            delegate.fatalError(exception);
+        } else {
+            doFatalError(exception);
+        }
+    }
+
+    /**
+     * @throws SAXException
+     */
+    protected void doFatalError(SAXParseException exception) throws SAXException {
+        // by default do nothing
+    }
+
+    // //////////////////////
+    // Functions related to error handling
+    // //////////////////////
+
+    protected void log(int logLevel, String message, Throwable t) {
+        Message.debug(t);
+        log(logLevel, message);
+    }
+
+    protected void log(int logLevel, String message) {
+        Message.log(logLevel, getLocation(getLocator()) + message);
+    }
+
+    protected static String getLocation(Locator locator) {
+        if (locator == null) {
+            return "";
+        }
+        return "[line " + locator.getLineNumber() + " col. " + locator.getColumnNumber() + "] ";
+    }
+
+    private void skipOnError(DelegatingHandler currentHandler,
+            Class<? extends DelegatingHandler> handlerClassToSkip, String message) {
+        DelegatingHandler handlerToSkip = currentHandler;
+        while (!(handlerClassToSkip.isAssignableFrom(handlerToSkip.getClass()))) {
+            handlerToSkip = handlerToSkip.getParent();
+        }
+        log(Message.MSG_ERR, message + ". The '" + handlerToSkip.getName() + "' element "
+                + getCurrentElementIdentifier() + " is then ignored.");
+        handlerToSkip.skip();
+    }
+
+    // //////////////////////
+    // Helpers to parse the attributes
+    // //////////////////////
+
+    protected String getRequiredAttribute(Attributes atts, String name) throws SAXParseException {
+        String value = atts.getValue(name);
+        if (value == null) {
+            throw new SAXParseException("Required attribute '" + name + "' not found", getLocator());
+        }
+        return value;
+    }
+
+    protected String getOptionalAttribute(Attributes atts, String name, String defaultValue) {
+        String value = atts.getValue(name);
+        if (value == null) {
+            return defaultValue;
+        }
+        return value;
+    }
+
+    protected int getRequiredIntAttribute(Attributes atts, String name, Integer logLevel)
+            throws SAXParseException {
+        return parseInt(name, getRequiredAttribute(atts, name));
+    }
+
+    protected Integer getOptionalIntAttribute(Attributes atts, String name, Integer defaultValue)
+            throws SAXParseException {
+        String value = atts.getValue(name);
+        if (value == null) {
+            return defaultValue;
+        }
+        return new Integer(parseInt(name, value));
+    }
+
+    private int parseInt(String name, String value) throws SAXParseException {
+        try {
+            return Integer.parseInt(value);
+        } catch (NumberFormatException e) {
+            throw new SAXParseException("Attribute '" + name
+                    + "' is expected to be an integer but was '" + value + "' (" + e.getMessage()
+                    + ")", getLocator());
+        }
+    }
+
+    protected long getRequiredLongAttribute(Attributes atts, String name) throws SAXParseException {
+        return parseLong(name, getRequiredAttribute(atts, name));
+    }
+
+    protected Long getOptionalLongAttribute(Attributes atts, String name, Long defaultValue)
+            throws SAXParseException {
+        String value = atts.getValue(name);
+        if (value == null) {
+            return defaultValue;
+        }
+        return new Long(parseLong(name, value));
+    }
+
+    private long parseLong(String name, String value) throws SAXParseException {
+        try {
+            return Long.parseLong(value);
+        } catch (NumberFormatException e) {
+            throw new SAXParseException("Attribute '" + name
+                    + "' is expected to be an long but was '" + value + "' (" + e.getMessage()
+                    + ")", getLocator());
+        }
+    }
+
+    protected boolean getRequiredBooleanAttribute(Attributes atts, String name)
+            throws SAXParseException {
+        return parseBoolean(name, getRequiredAttribute(atts, name));
+    }
+
+    protected Boolean getOptionalBooleanAttribute(Attributes atts, String name, Boolean defaultValue)
+            throws SAXParseException {
+        String value = atts.getValue(name);
+        if (value == null) {
+            return defaultValue;
+        }
+        return Boolean.valueOf(parseBoolean(name, value));
+    }
+
+    static final String TRUE = Boolean.TRUE.toString().toLowerCase(Locale.US);
+
+    static final String FALSE = Boolean.FALSE.toString().toLowerCase(Locale.US);
+
+    private boolean parseBoolean(String name, String value) throws SAXParseException {
+        String lowerValue = value.toLowerCase(Locale.US);
+        if (lowerValue.equals(TRUE)) {
+            return true;
+        }
+        if (lowerValue.equals(FALSE)) {
+            return false;
+        }
+        throw new SAXParseException("Attribute '" + name
+                + "' is expected to be a boolean but was '" + value + "'", getLocator());
+    }
+}
diff --git a/src/java/org/apache/ivy/osgi/util/ParseUtil.java b/src/java/org/apache/ivy/osgi/util/ParseUtil.java
new file mode 100644
index 0000000..bb31d0c
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/util/ParseUtil.java
@@ -0,0 +1,82 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.util;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ParseUtil {
+
+    /**
+     * Parses delimited string and returns an array containing the tokens. This parser obeys quotes,
+     * so the delimiter character will be ignored if it is inside of a quote. This method assumes
+     * that the quote character is not included in the set of delimiter characters.
+     * 
+     * @param value
+     *            the delimited string to parse.
+     * @param delim
+     *            the characters delimiting the tokens.
+     * @return an array of string tokens or null if there were no tokens.
+     */
+    // method largely inspired by Apache Felix 1.0.4 ManifestParser method
+    public static String[] parseDelimitedString(String value, String delim) {
+        if (value == null) {
+            value = "";
+        }
+
+        final List<String> list = new ArrayList<String>();
+
+        final int CHAR = 1;
+        final int DELIMITER = 2;
+        final int STARTQUOTE = 4;
+        final int ENDQUOTE = 8;
+
+        final StringBuffer sb = new StringBuffer();
+
+        int expecting = (CHAR | DELIMITER | STARTQUOTE);
+
+        for (int i = 0; i < value.length(); i++) {
+            final char c = value.charAt(i);
+
+            final boolean isDelimiter = (delim.indexOf(c) >= 0);
+            final boolean isQuote = (c == '"');
+
+            if (isDelimiter && ((expecting & DELIMITER) > 0)) {
+                list.add(sb.toString().trim());
+                sb.delete(0, sb.length());
+                expecting = (CHAR | DELIMITER | STARTQUOTE);
+            } else if (isQuote && ((expecting & STARTQUOTE) > 0)) {
+                sb.append(c);
+                expecting = CHAR | ENDQUOTE;
+            } else if (isQuote && ((expecting & ENDQUOTE) > 0)) {
+                sb.append(c);
+                expecting = (CHAR | STARTQUOTE | DELIMITER);
+            } else if ((expecting & CHAR) > 0) {
+                sb.append(c);
+            } else {
+                throw new IllegalArgumentException("Invalid delimited string: " + value);
+            }
+        }
+
+        if (sb.length() > 0) {
+            list.add(sb.toString().trim());
+        }
+
+        return list.toArray(new String[list.size()]);
+    }
+}
diff --git a/src/java/org/apache/ivy/osgi/util/Version.java b/src/java/org/apache/ivy/osgi/util/Version.java
new file mode 100644
index 0000000..4db670e
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/util/Version.java
@@ -0,0 +1,223 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.util;
+
+import java.text.ParseException;
+
+/**
+ * Provides OSGi version support.
+ */
+public class Version implements Comparable<Version> {
+
+    private int major;
+
+    private int minor;
+
+    private int patch;
+
+    private String qualifier;
+
+    private String version;
+
+    private String input;
+
+    private boolean splitted = false;
+
+    private boolean toString = false;
+
+    public Version(String versionStr, String qualifier) throws ParseException {
+        this(qualifier == null ? versionStr : (versionStr + "." + qualifier));
+    }
+
+    public Version(String versionStr) throws ParseException {
+        this.input = versionStr;
+        splitted = false;
+        toString = false;
+    }
+
+    public Version(int major, int minor, int patch, String qualifier) {
+        this.major = major;
+        this.minor = minor;
+        this.patch = patch;
+        this.qualifier = qualifier;
+        splitted = true;
+        toString = false;
+    }
+
+    /**
+     * Build a version from another one while appending an extra qualifier
+     * 
+     * @param baseVersion
+     * @param qualifier
+     */
+    public Version(Version baseVersion, String extraQualifier) {
+        this.major = baseVersion.major;
+        this.minor = baseVersion.minor;
+        this.patch = baseVersion.patch;
+        this.qualifier = baseVersion.qualifier == null ? extraQualifier
+                : (baseVersion.qualifier + extraQualifier);
+        splitted = true;
+        toString = false;
+    }
+
+    private void ensureSplitted() {
+        if (!splitted) {
+            synchronized (this) {
+                if (splitted) {
+                    return;
+                }
+                String[] splits = input.split("\\.");
+                if (splits == null || splits.length == 0 || splits.length > 4) {
+                    throw new RuntimeException(new ParseException("Ill formed OSGi version", 0));
+                }
+                try {
+                    major = Integer.parseInt(splits[0]);
+                } catch (NumberFormatException e) {
+                    throw new RuntimeException(new ParseException(
+                            "Major part of an OSGi version should be an integer", 0));
+                }
+                try {
+                    minor = splits.length >= 2 ? Integer.parseInt(splits[1]) : 0;
+                } catch (NumberFormatException e) {
+                    throw new RuntimeException(new ParseException(
+                            "Minor part of an OSGi version should be an integer", 0));
+                }
+                try {
+                    patch = splits.length >= 3 ? Integer.parseInt(splits[2]) : 0;
+                } catch (NumberFormatException e) {
+                    throw new RuntimeException(new ParseException(
+                            "Patch part of an OSGi version should be an integer", 0));
+                }
+                qualifier = splits.length == 4 ? splits[3] : null;
+                splitted = true;
+            }
+        }
+    }
+
+    private void ensureToString() {
+        if (!toString) {
+            synchronized (this) {
+                if (toString) {
+                    return;
+                }
+                ensureSplitted();
+                version = major + "." + minor + "." + patch
+                        + (qualifier == null ? "" : "." + qualifier);
+                toString = true;
+            }
+        }
+    }
+
+    public String toString() {
+        ensureToString();
+        return version;
+    }
+
+    public int hashCode() {
+        ensureSplitted();
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + major;
+        result = prime * result + minor;
+        result = prime * result + patch;
+        result = prime * result + ((qualifier == null) ? 0 : qualifier.hashCode());
+        return result;
+    }
+
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (!(obj instanceof Version)) {
+            return false;
+        }
+        Version other = (Version) obj;
+        ensureSplitted();
+        other.ensureSplitted();
+        if (major != other.major) {
+            return false;
+        }
+        if (minor != other.minor) {
+            return false;
+        }
+        if (patch != other.patch) {
+            return false;
+        }
+        if (qualifier == null) {
+            if (other.qualifier != null) {
+                return false;
+            }
+        } else if (!qualifier.equals(other.qualifier)) {
+            return false;
+        }
+        return true;
+    }
+
+    public Version withNudgedPatch() {
+        ensureSplitted();
+        return new Version(major, minor, patch + 1, null);
+    }
+
+    public Version withoutQualifier() {
+        ensureSplitted();
+        return new Version(major, minor, patch, null);
+    }
+
+    public String qualifier() {
+        ensureSplitted();
+        return qualifier == null ? "" : qualifier;
+    }
+
+    public int compareUnqualified(Version other) {
+        ensureSplitted();
+        other.ensureSplitted();
+        int diff = major - other.major;
+        if (diff != 0) {
+            return diff;
+        }
+        diff = minor - other.minor;
+        if (diff != 0) {
+            return diff;
+        }
+        diff = patch - other.patch;
+        if (diff != 0) {
+            return diff;
+        }
+        return 0;
+    }
+
+    public int compareTo(Version other) {
+        ensureSplitted();
+        other.ensureSplitted();
+        int diff = compareUnqualified(other);
+        if (diff != 0) {
+            return diff;
+        }
+        if (qualifier == null) {
+            return other.qualifier != null ? -1 : 0;
+        }
+        if (other.qualifier == null) {
+            return 1;
+        }
+        return qualifier.compareTo(other.qualifier);
+    }
+
+}
diff --git a/src/java/org/apache/ivy/osgi/util/VersionComparator.java b/src/java/org/apache/ivy/osgi/util/VersionComparator.java
new file mode 100644
index 0000000..8872431
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/util/VersionComparator.java
@@ -0,0 +1,39 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.util;
+
+import java.util.Comparator;
+
+public class VersionComparator implements Comparator<Version> {
+
+    public static final Comparator<Version> ASCENDING = new VersionComparator(false);
+
+    public static final Comparator<Version> DESCENDING = new VersionComparator(true);
+
+    public final boolean reverse;
+
+    private VersionComparator(boolean reverse) {
+        this.reverse = reverse;
+    }
+
+    public int compare(Version objA, Version objB) {
+        final int val = objA.compareTo(objB);
+        return (reverse ? -val : val);
+    }
+
+}
diff --git a/src/java/org/apache/ivy/osgi/util/VersionRange.java b/src/java/org/apache/ivy/osgi/util/VersionRange.java
new file mode 100644
index 0000000..575ff0a
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/util/VersionRange.java
@@ -0,0 +1,372 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.util;
+
+import java.text.ParseException;
+
+/**
+ * Provides version range support.
+ */
+public class VersionRange {
+
+    private boolean startExclusive;
+
+    private Version startVersion;
+
+    private boolean endExclusive;
+
+    private Version endVersion;
+
+    public VersionRange(String versionStr) throws ParseException {
+        if (versionStr == null || versionStr.length() == 0) {
+            startExclusive = false;
+            startVersion = new Version(0, 0, 0, null);
+            endExclusive = true;
+            endVersion = null;
+        } else {
+            new VersionRangeParser(versionStr).parse();
+        }
+    }
+
+    class VersionRangeParser {
+
+        /**
+         * value to parse
+         */
+        private final String version;
+
+        /**
+         * the length of the source
+         */
+        private int length;
+
+        /**
+         * position in the source
+         */
+        private int pos = 0;
+
+        /**
+         * last read character
+         */
+        private char c;
+
+        /**
+         * Default constructor
+         * 
+         * @param header
+         *            the header to parse
+         */
+        VersionRangeParser(String version) {
+            this.version = version;
+            this.length = version.length();
+        }
+
+        /**
+         * Do the parsing
+         * 
+         * @throws ParseException
+         */
+        void parse() throws ParseException {
+            boolean range = parseStart();
+            startVersion = parseVersion();
+            if (startVersion == null) {
+                throw new ParseException("Expecting a number", pos);
+            }
+            if (parseVersionSeparator()) {
+                endVersion = parseVersion();
+                parseEnd();
+            } else if (range) {
+                throw new ParseException("Expecting ,", pos);
+            } else {
+                // simple number
+                endVersion = null;
+                startExclusive = false;
+                endExclusive = false;
+            }
+        }
+
+        private char readNext() {
+            if (pos == length) {
+                c = '\0';
+            } else {
+                c = version.charAt(pos++);
+            }
+            return c;
+        }
+
+        private void unread() {
+            if (pos > 0) {
+                pos--;
+            }
+        }
+
+        private boolean parseStart() {
+            skipWhiteSpace();
+            switch (readNext()) {
+                case '[':
+                    startExclusive = false;
+                    return true;
+                case '(':
+                    startExclusive = true;
+                    return true;
+                default:
+                    unread();
+                    return false;
+            }
+        }
+
+        private void skipWhiteSpace() {
+            do {
+                switch (readNext()) {
+                    case ' ':
+                        continue;
+                    default:
+                        unread();
+                        return;
+                }
+            } while (pos < length);
+        }
+
+        private Version parseVersion() {
+            Integer major = parseNumber();
+            if (major == null) {
+                return null;
+            }
+            Integer minor = new Integer(0);
+            Integer patch = new Integer(0);
+            String qualififer = null;
+            if (parseNumberSeparator()) {
+                minor = parseNumber();
+                if (minor == null) {
+                    minor = new Integer(0);
+                } else if (parseNumberSeparator()) {
+                    patch = parseNumber();
+                    if (patch == null) {
+                        patch = new Integer(0);
+                    } else if (parseNumberSeparator()) {
+                        qualififer = parseQualifier();
+                    }
+                }
+            }
+            return new Version(major.intValue(), minor.intValue(), patch.intValue(), qualififer);
+        }
+
+        private Integer parseNumber() {
+            skipWhiteSpace();
+            Integer n = null;
+            do {
+                switch (readNext()) {
+                    case '\0':
+                        return n;
+                    case '0':
+                    case '1':
+                    case '2':
+                    case '3':
+                    case '4':
+                    case '5':
+                    case '6':
+                    case '7':
+                    case '8':
+                    case '9':
+                        n = new Integer((n == null ? 0 : n.intValue() * 10) + c - '0');
+                        break;
+                    default:
+                        unread();
+                        return n;
+                }
+            } while (pos < length);
+            return n;
+        }
+
+        private boolean parseNumberSeparator() {
+            switch (readNext()) {
+                case '.':
+                    return true;
+                default:
+                    unread();
+                    return false;
+            }
+        }
+
+        private boolean parseVersionSeparator() {
+            skipWhiteSpace();
+            switch (readNext()) {
+                case ',':
+                    return true;
+                default:
+                    unread();
+                    return false;
+            }
+        }
+
+        private String parseQualifier() {
+            StringBuffer q = new StringBuffer();
+            do {
+                readNext();
+                if (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9'
+                        || c == '-' || c == '_') {
+                    q.append(c);
+                } else {
+                    unread();
+                    break;
+                }
+            } while (pos < length);
+            if (q.length() == 0) {
+                return null;
+            }
+            return q.toString();
+        }
+
+        private void parseEnd() throws ParseException {
+            skipWhiteSpace();
+            switch (readNext()) {
+                case ']':
+                    endExclusive = false;
+                    break;
+                case ')':
+                    endExclusive = true;
+                    break;
+                default:
+                    unread();
+                    throw new ParseException("Expexting ] or )", pos);
+            }
+        }
+    }
+
+    public VersionRange(boolean startExclusive, Version startVersion, boolean endExclusive,
+            Version endVersion) {
+        this.startExclusive = startExclusive;
+        this.startVersion = startVersion;
+        this.endExclusive = endExclusive;
+        this.endVersion = endVersion;
+    }
+
+    public VersionRange(Version startVersion) {
+        this.startExclusive = false;
+        this.startVersion = startVersion;
+        this.endExclusive = true;
+        this.endVersion = null;
+    }
+
+    public String toString() {
+        return (startExclusive ? "(" : "[") + startVersion + ","
+                + (endVersion == null ? "" : endVersion.toString()) + (endExclusive ? ")" : "]");
+    }
+
+    public String toIvyRevision() {
+        StringBuffer buffer = new StringBuffer();
+        buffer.append(startExclusive ? "(" : "[");
+        buffer.append(startVersion);
+        if (endVersion == null) {
+            buffer.append(",)");
+        } else if (!endExclusive || startVersion.equals(endVersion)) {
+            buffer.append(",");
+            buffer.append(endVersion.withNudgedPatch());
+            buffer.append(")");
+        } else {
+            buffer.append(",");
+            buffer.append(endVersion);
+            buffer.append(")");
+        }
+        return buffer.toString();
+    }
+
+    public boolean isEndExclusive() {
+        return this.endExclusive;
+    }
+
+    public Version getEndVersion() {
+        return this.endVersion;
+    }
+
+    public boolean isStartExclusive() {
+        return this.startExclusive;
+    }
+
+    public Version getStartVersion() {
+        return this.startVersion;
+    }
+
+    public boolean isClosedRange() {
+        return startVersion.equals(endVersion);
+    }
+
+    public boolean contains(String versionStr) throws ParseException {
+        return contains(new Version(versionStr));
+    }
+
+    public boolean contains(Version version) {
+        if (startExclusive ? version.compareUnqualified(startVersion) <= 0 : version
+                .compareUnqualified(startVersion) < 0) {
+            return false;
+        }
+        if (endVersion == null) {
+            return true;
+        }
+        if (endExclusive ? version.compareUnqualified(endVersion) >= 0 : version
+                .compareUnqualified(endVersion) > 0) {
+            return false;
+        }
+        return true;
+    }
+
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + (endExclusive ? 1231 : 1237);
+        result = prime * result + ((endVersion == null) ? 0 : endVersion.hashCode());
+        result = prime * result + (startExclusive ? 1231 : 1237);
+        result = prime * result + ((startVersion == null) ? 0 : startVersion.hashCode());
+        return result;
+    }
+
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (!(obj instanceof VersionRange)) {
+            return false;
+        }
+        VersionRange other = (VersionRange) obj;
+        if (endExclusive != other.endExclusive) {
+            return false;
+        }
+        if (endVersion == null) {
+            if (other.endVersion != null) {
+                return false;
+            }
+        } else if (!endVersion.equals(other.endVersion)) {
+            return false;
+        }
+        if (startExclusive != other.startExclusive) {
+            return false;
+        }
+        if (startVersion == null) {
+            if (other.startVersion != null) {
+                return false;
+            }
+        } else if (!startVersion.equals(other.startVersion)) {
+            return false;
+        }
+        return true;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/osgi/util/ZipUtil.java b/src/java/org/apache/ivy/osgi/util/ZipUtil.java
new file mode 100644
index 0000000..92737c3
--- /dev/null
+++ b/src/java/org/apache/ivy/osgi/util/ZipUtil.java
@@ -0,0 +1,66 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.osgi.util;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * Class derived from code posted here: http://forums.sun.com/thread.jspa?messageID=2160923
+ */
+public class ZipUtil {
+    public static void zip(File sourceDir, OutputStream targetStream) throws IOException {
+        if (!sourceDir.isFile() && !sourceDir.isDirectory()) {
+            return;
+        }
+
+        final ZipOutputStream cpZipOutputStream = new ZipOutputStream(targetStream);
+        cpZipOutputStream.setLevel(9);
+        zipFiles(sourceDir, sourceDir, cpZipOutputStream);
+        cpZipOutputStream.finish();
+        cpZipOutputStream.close();
+    }
+
+    private static void zipFiles(File rootDir, File currDir, ZipOutputStream zos)
+            throws IOException {
+        if (currDir.isDirectory()) {
+            final File[] files = currDir.listFiles();
+            for (int i = 0; i < files.length; i++) {
+                zipFiles(rootDir, files[i], zos);
+            }
+        } else {
+            final String strAbsPath = currDir.getPath();
+            final String strZipEntryName = strAbsPath.substring(rootDir.getPath().length() + 1,
+                strAbsPath.length());
+
+            final byte[] b = new byte[(int) (currDir.length())];
+            final FileInputStream fis = new FileInputStream(currDir);
+            fis.read(b);
+            fis.close();
+
+            final ZipEntry entry = new ZipEntry(strZipEntryName);
+            zos.putNextEntry(entry);
+            zos.write(b, 0, (int) currDir.length());
+            zos.closeEntry();
+        }
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/IvySettingsAware.java b/src/java/org/apache/ivy/plugins/IvySettingsAware.java
new file mode 100644
index 0000000..3690614
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/IvySettingsAware.java
@@ -0,0 +1,24 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins;
+
+import org.apache.ivy.core.settings.IvySettings;
+
+public interface IvySettingsAware {
+    void setSettings(IvySettings settings);
+}
diff --git a/src/java/org/apache/ivy/plugins/circular/AbstractCircularDependencyStrategy.java b/src/java/org/apache/ivy/plugins/circular/AbstractCircularDependencyStrategy.java
new file mode 100644
index 0000000..7b3ed10
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/circular/AbstractCircularDependencyStrategy.java
@@ -0,0 +1,34 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.circular;
+
+public abstract class AbstractCircularDependencyStrategy implements CircularDependencyStrategy {
+    private String name;
+
+    protected AbstractCircularDependencyStrategy(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public String toString() {
+        return getName();
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/circular/AbstractLogCircularDependencyStrategy.java b/src/java/org/apache/ivy/plugins/circular/AbstractLogCircularDependencyStrategy.java
new file mode 100644
index 0000000..4d42173
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/circular/AbstractLogCircularDependencyStrategy.java
@@ -0,0 +1,56 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.circular;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+
+import org.apache.ivy.core.IvyContext;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.resolve.ResolveData;
+
+public abstract class AbstractLogCircularDependencyStrategy extends
+        AbstractCircularDependencyStrategy {
+
+    protected AbstractLogCircularDependencyStrategy(String name) {
+        super(name);
+    }
+
+    private Collection/* <String> */circularDependencies = new HashSet();
+
+    public void handleCircularDependency(ModuleRevisionId[] mrids) {
+        String circularDependencyId = getCircularDependencyId(mrids);
+        if (!circularDependencies.contains(circularDependencyId)) {
+            circularDependencies.add(circularDependencyId);
+            logCircularDependency(mrids);
+        }
+    }
+
+    protected abstract void logCircularDependency(ModuleRevisionId[] mrids);
+
+    protected String getCircularDependencyId(ModuleRevisionId[] mrids) {
+        String contextPrefix = "";
+        ResolveData data = IvyContext.getContext().getResolveData();
+        if (data != null) {
+            contextPrefix = data.getOptions().getResolveId() + " ";
+        }
+        return contextPrefix + Arrays.asList(mrids);
+    }
+
+}
diff --git a/src/java/org/apache/ivy/plugins/circular/CircularDependencyException.java b/src/java/org/apache/ivy/plugins/circular/CircularDependencyException.java
new file mode 100644
index 0000000..0b37dfd
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/circular/CircularDependencyException.java
@@ -0,0 +1,43 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.circular;
+
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+
+/**
+ * Unchecked exception thrown when a circular dependency exists between projects.
+ */
+
+public class CircularDependencyException extends RuntimeException {
+
+    private ModuleRevisionId[] mrids;
+
+    /**
+     * @param descriptors
+     *            module descriptors in order of circular dependency
+     */
+    public CircularDependencyException(final ModuleRevisionId[] mrids) {
+        super(CircularDependencyHelper.formatMessage(mrids));
+        this.mrids = mrids;
+    }
+
+    public ModuleRevisionId[] getPath() {
+        return this.mrids;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/plugins/circular/CircularDependencyHelper.java b/src/java/org/apache/ivy/plugins/circular/CircularDependencyHelper.java
new file mode 100644
index 0000000..59d5595
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/circular/CircularDependencyHelper.java
@@ -0,0 +1,86 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.circular;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+
+public final class CircularDependencyHelper {
+
+    /** CircularDependencyHelper is not designed to be an instance */
+    private CircularDependencyHelper() {
+
+    }
+
+    /**
+     * Returns a string representation of this circular dependency graph
+     * 
+     * @param descriptors
+     *            in order of circular dependency
+     * @return a string representation of this circular dependency graph
+     */
+    public static String formatMessage(final ModuleRevisionId[] mrids) {
+        Set alreadyAdded = new HashSet();
+        StringBuffer buff = new StringBuffer();
+        buff.append(mrids[0]);
+        alreadyAdded.add(mrids[0]);
+        for (int i = 1; i < mrids.length; i++) {
+            buff.append("->");
+            if (alreadyAdded.add(mrids[i])) {
+                buff.append(mrids[i]);
+            } else {
+                buff.append("...");
+                break;
+            }
+        }
+        return buff.toString();
+    }
+
+    public static String formatMessage(final ModuleDescriptor[] descriptors) {
+        return formatMessage(toMrids(descriptors));
+    }
+
+    /**
+     * @param loopElements
+     *            a List<ModuleDescriptor>
+     */
+    public static String formatMessageFromDescriptors(List loopElements) {
+        ModuleRevisionId[] mrids = new ModuleRevisionId[loopElements.size()];
+        int pos = 0;
+        for (Iterator it = loopElements.iterator(); it.hasNext();) {
+            ModuleDescriptor descriptor = (ModuleDescriptor) it.next();
+            mrids[pos] = descriptor.getModuleRevisionId();
+            pos++;
+        }
+        return formatMessage(mrids);
+    }
+
+    public static ModuleRevisionId[] toMrids(ModuleDescriptor[] descriptors) {
+        ModuleRevisionId[] mrids = new ModuleRevisionId[descriptors.length];
+        for (int i = 0; i < descriptors.length; i++) {
+            mrids[i] = descriptors[i].getModuleRevisionId();
+        }
+        return mrids;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/plugins/circular/CircularDependencyStrategy.java b/src/java/org/apache/ivy/plugins/circular/CircularDependencyStrategy.java
new file mode 100644
index 0000000..f1ae506
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/circular/CircularDependencyStrategy.java
@@ -0,0 +1,31 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.circular;
+
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+
+/**
+ * A CircularDependencyStrategy indicates what ivy does when a circular dependency is detected. Ivy
+ * can ignore it, warn the user, or interupt the processing.
+ */
+public interface CircularDependencyStrategy {
+    String getName();
+
+    void handleCircularDependency(ModuleRevisionId[] mrids) throws CircularDependencyException;
+
+}
diff --git a/src/java/org/apache/ivy/plugins/circular/ErrorCircularDependencyStrategy.java b/src/java/org/apache/ivy/plugins/circular/ErrorCircularDependencyStrategy.java
new file mode 100644
index 0000000..93aa387
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/circular/ErrorCircularDependencyStrategy.java
@@ -0,0 +1,38 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.circular;
+
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+
+public final class ErrorCircularDependencyStrategy extends AbstractCircularDependencyStrategy {
+
+    private static final CircularDependencyStrategy INSTANCE = new ErrorCircularDependencyStrategy();
+
+    public static CircularDependencyStrategy getInstance() {
+        return INSTANCE;
+    }
+
+    private ErrorCircularDependencyStrategy() {
+        super("error");
+    }
+
+    public void handleCircularDependency(ModuleRevisionId[] mrids)
+            throws CircularDependencyException {
+        throw new CircularDependencyException(mrids);
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/circular/IgnoreCircularDependencyStrategy.java b/src/java/org/apache/ivy/plugins/circular/IgnoreCircularDependencyStrategy.java
new file mode 100644
index 0000000..23c5950
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/circular/IgnoreCircularDependencyStrategy.java
@@ -0,0 +1,40 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.circular;
+
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.util.Message;
+
+public final class IgnoreCircularDependencyStrategy extends AbstractLogCircularDependencyStrategy {
+
+    private static final CircularDependencyStrategy INSTANCE = new IgnoreCircularDependencyStrategy();
+
+    public static CircularDependencyStrategy getInstance() {
+        return INSTANCE;
+    }
+
+    private IgnoreCircularDependencyStrategy() {
+        super("ignore");
+    }
+
+    protected void logCircularDependency(ModuleRevisionId[] mrids) {
+        Message.verbose("circular dependency found: "
+                + CircularDependencyHelper.formatMessage(mrids));
+    }
+
+}
diff --git a/src/java/org/apache/ivy/plugins/circular/WarnCircularDependencyStrategy.java b/src/java/org/apache/ivy/plugins/circular/WarnCircularDependencyStrategy.java
new file mode 100644
index 0000000..c247947
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/circular/WarnCircularDependencyStrategy.java
@@ -0,0 +1,39 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.circular;
+
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.util.Message;
+
+public final class WarnCircularDependencyStrategy extends AbstractLogCircularDependencyStrategy {
+
+    private static final CircularDependencyStrategy INSTANCE = new WarnCircularDependencyStrategy();
+
+    public static CircularDependencyStrategy getInstance() {
+        return INSTANCE;
+    }
+
+    private WarnCircularDependencyStrategy() {
+        super("warn");
+    }
+
+    protected void logCircularDependency(ModuleRevisionId[] mrids) {
+        Message.warn("circular dependency found: " + CircularDependencyHelper.formatMessage(mrids));
+    }
+
+}
diff --git a/src/java/org/apache/ivy/plugins/conflict/AbstractConflictManager.java b/src/java/org/apache/ivy/plugins/conflict/AbstractConflictManager.java
new file mode 100644
index 0000000..ee68337
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/conflict/AbstractConflictManager.java
@@ -0,0 +1,53 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.conflict;
+
+import java.util.Collection;
+
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.ivy.plugins.IvySettingsAware;
+
+public abstract class AbstractConflictManager implements ConflictManager, IvySettingsAware {
+    private String name;
+
+    private IvySettings settings;
+
+    public IvySettings getSettings() {
+        return settings;
+    }
+
+    public void setSettings(IvySettings settings) {
+        this.settings = settings;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String toString() {
+        return name;
+    }
+
+    public void handleAllBlacklistedRevisions(DependencyDescriptor dd, Collection foundBlacklisted) {
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/conflict/ConflictManager.java b/src/java/org/apache/ivy/plugins/conflict/ConflictManager.java
new file mode 100644
index 0000000..46c2060
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/conflict/ConflictManager.java
@@ -0,0 +1,60 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.conflict;
+
+import java.util.Collection;
+
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.resolve.IvyNode;
+
+public interface ConflictManager {
+    /**
+     * Resolves the eventual conflicts found in the given collection of IvyNode. This method return
+     * a Collection of IvyNode which have not been evicted. The given conflicts Collection contains
+     * at least one IvyNode. This method can be called with IvyNodes which are not yet loaded. If
+     * this conflict manager is not able to resolve conflicts with the current data found in the
+     * IvyNodes and need them to be fully loaded, it will return null to indicate that no conflict
+     * resolution has been done.
+     * 
+     * @param parent
+     *            the ivy node parent for which the conflict is to be resolved
+     * @param conflicts
+     *            the collection of IvyNode to check for conflicts
+     * @return a Collection of IvyNode which have not been evicted, or null if conflict management
+     *         resolution is not possible yet
+     */
+    Collection resolveConflicts(IvyNode parent, Collection conflicts);
+
+    String getName();
+
+    /**
+     * Method called when all revisions available for a version constraint have been blacklisted,
+     * and thus the dependency can't be resolved.
+     * <p>
+     * This will never happen if the conflict manager doesn't blacklist any module, so providing an
+     * empty implementation in this case is fine.
+     * </p>
+     * 
+     * @param dd
+     *            the dependency descriptor for which all revisions are blacklisted.
+     * @param foundBlacklisted
+     *            the list of all ModuleRevisionId found which are blacklisted
+     */
+    void handleAllBlacklistedRevisions(DependencyDescriptor dd,
+            Collection/* <ModuleRevisionId> */foundBlacklisted);
+}
diff --git a/src/java/org/apache/ivy/plugins/conflict/FixedConflictManager.java b/src/java/org/apache/ivy/plugins/conflict/FixedConflictManager.java
new file mode 100644
index 0000000..34815bf
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/conflict/FixedConflictManager.java
@@ -0,0 +1,51 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.conflict;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.apache.ivy.core.resolve.IvyNode;
+
+public class FixedConflictManager extends AbstractConflictManager {
+    private Collection revisions;
+
+    public FixedConflictManager(String[] revs) {
+        revisions = Arrays.asList(revs);
+        setName("fixed" + revisions);
+    }
+
+    public Collection resolveConflicts(IvyNode parent, Collection conflicts) {
+        Collection resolved = new ArrayList(conflicts.size());
+        for (Iterator iter = conflicts.iterator(); iter.hasNext();) {
+            IvyNode node = (IvyNode) iter.next();
+            String revision = node.getResolvedId().getRevision();
+            if (revisions.contains(revision)) {
+                resolved.add(node);
+            }
+        }
+        return resolved;
+    }
+
+    public Collection getRevs() {
+        return revisions;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/plugins/conflict/LatestCompatibleConflictManager.java b/src/java/org/apache/ivy/plugins/conflict/LatestCompatibleConflictManager.java
new file mode 100644
index 0000000..f4e775c
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/conflict/LatestCompatibleConflictManager.java
@@ -0,0 +1,312 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.conflict;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Stack;
+
+import org.apache.ivy.core.IvyContext;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.resolve.IvyNode;
+import org.apache.ivy.core.resolve.IvyNodeBlacklist;
+import org.apache.ivy.core.resolve.IvyNodeCallers.Caller;
+import org.apache.ivy.core.resolve.IvyNodeEviction.EvictionData;
+import org.apache.ivy.core.resolve.ResolveData;
+import org.apache.ivy.core.resolve.RestartResolveProcess;
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.ivy.plugins.latest.LatestStrategy;
+import org.apache.ivy.plugins.version.VersionMatcher;
+import org.apache.ivy.util.Message;
+
+/**
+ * This conflict manager can be used to allow only compatible dependencies to be used together (like
+ * the strict conflict manager), but it has the advantage of using a best effort algorithm to find a
+ * set of compatible dependencies, even if it requires stepping back to older revisions (as long as
+ * they are in the set of compatibility).
+ * <p>
+ * Here is an example of what this conflict manager is able to do:<br/>
+ * <b>Available Modules</b>:
+ * 
+ * <pre>
+ * #A;2->{ #B;[1.0,1.5] #C;[2.0,2.5] }
+ * #B;1.4->#D;1.5
+ * #B;1.5->#D;2.0
+ * #C;2.5->#D;[1.0,1.6]
+ * </pre>
+ * 
+ * <b>Result</b>: #B;1.4, #C;2.5, #D;1.5<br/>
+ * <b>Details</b>The conflict manager finds that the latest matching version of #B (1.5) depends on
+ * a version of #D incompatible with what is expected by the latest matching version of #C. Hence
+ * the conflict manager blacklists #B;1.5, and the version range [1.0,1.5] is resolved again to end
+ * up with #B;1.4 which depends on #D;1.5, which is fine to work with #C;2.5.
+ * </p>
+ */
+public class LatestCompatibleConflictManager extends LatestConflictManager {
+    public LatestCompatibleConflictManager() {
+    }
+
+    public LatestCompatibleConflictManager(String name, LatestStrategy strategy) {
+        super(name, strategy);
+    }
+
+    public Collection resolveConflicts(IvyNode parent, Collection conflicts) {
+        if (conflicts.size() < 2) {
+            return conflicts;
+        }
+        VersionMatcher versionMatcher = getSettings().getVersionMatcher();
+
+        Iterator iter = conflicts.iterator();
+        IvyNode node = (IvyNode) iter.next();
+        ModuleRevisionId mrid = node.getResolvedId();
+
+        if (versionMatcher.isDynamic(mrid)) {
+            while (iter.hasNext()) {
+                IvyNode other = (IvyNode) iter.next();
+                if (versionMatcher.isDynamic(other.getResolvedId())) {
+                    // two dynamic versions in conflict, not enough information yet
+                    return null;
+                } else if (!versionMatcher.accept(mrid, other.getResolvedId())) {
+                    // incompatibility found
+                    if (!handleIncompatibleConflict(parent, conflicts, node, other)) {
+                        return null;
+                    }
+                }
+            }
+            // no incompatibility nor dynamic version found, let's return the latest static version
+            if (conflicts.size() == 2) {
+                // very common special case of only two modules in conflict,
+                // let's return the second one (static)
+                Iterator it = conflicts.iterator();
+                it.next();
+                return Collections.singleton(it.next());
+            }
+            Collection newConflicts = new LinkedHashSet(conflicts);
+            newConflicts.remove(node);
+            return super.resolveConflicts(parent, newConflicts);
+        } else {
+            // the first node is a static revision, let's see if all other versions match
+            while (iter.hasNext()) {
+                IvyNode other = (IvyNode) iter.next();
+                if (!versionMatcher.accept(other.getResolvedId(), mrid)) {
+                    // incompatibility found
+                    if (!handleIncompatibleConflict(parent, conflicts, node, other)) {
+                        return null;
+                    }
+                }
+            }
+            // no incompatibility found, let's return this static version
+            return Collections.singleton(node);
+        }
+    }
+
+    /**
+     * Handles an incompatible conflict
+     * <p>
+     * An incompatible conflicts is handled with this pseudo algorithm:
+     * 
+     * <pre>
+     * take latest among two nodes in conflict
+     *   for all callers
+     *      if dependency is a version constraint (dynamic)
+     *         blacklist the mapped version
+     *      else
+     *         recurse for all callers
+     *   if a version constraint has been found
+     *     restart resolve
+     *   else
+     *     throw strict conflict exception
+     * </pre>
+     * 
+     * </p>
+     * 
+     * @param parent
+     *            the parent node of nodes in conflict
+     * @param conflicts
+     *            all the nodes in conflict
+     * @param node
+     *            one of the two incompatible nodes
+     * @param other
+     *            the other incompatible node
+     * @return true if the incompatible conflict has been handled, false otherwise (in which case
+     *         resolveConflicts should return null)
+     */
+    private boolean handleIncompatibleConflict(IvyNode parent, Collection conflicts, IvyNode node,
+            IvyNode other) {
+        // we never actually return anything else than false or throw an exception,
+        // but returning a boolean make the calling code cleaner
+        try {
+            IvyNodeArtifactInfo latest = (IvyNodeArtifactInfo) getStrategy().findLatest(
+                toArtifactInfo(Arrays.asList(new IvyNode[] {node, other})), null);
+            if (latest != null) {
+                IvyNode latestNode = latest.getNode();
+                IvyNode oldestNode = latestNode == node ? other : node;
+                blackListIncompatibleCallerAndRestartResolveIfPossible(getSettings(), parent,
+                    oldestNode, latestNode);
+                // if we arrive here, we haven't managed to blacklist all paths to the latest
+                // node, we try with the oldest
+                blackListIncompatibleCallerAndRestartResolveIfPossible(getSettings(), parent,
+                    latestNode, oldestNode);
+                // still not possible, we aren't able to find a solution to the incompatibility
+                handleUnsolvableConflict(parent, conflicts, node, other);
+
+                return true; // never actually reached
+            } else {
+                return false;
+            }
+        } catch (NoConflictResolvedYetException ex) {
+            // we have not enough informations in the nodes to resolve conflict
+            // according to the resolveConflicts contract, resolveConflicts must return null
+            return false;
+        }
+    }
+
+    private void blackListIncompatibleCallerAndRestartResolveIfPossible(IvySettings settings,
+            IvyNode parent, IvyNode selected, IvyNode evicted) {
+        Stack callerStack = new Stack();
+        callerStack.push(evicted);
+        final Collection toBlacklist = blackListIncompatibleCaller(settings.getVersionMatcher(),
+            parent, selected, evicted, callerStack);
+        if (toBlacklist != null) {
+            final StringBuffer blacklisted = new StringBuffer();
+            for (Iterator iterator = toBlacklist.iterator(); iterator.hasNext();) {
+                IvyNodeBlacklist blacklist = (IvyNodeBlacklist) iterator.next();
+                blacklist.getBlacklistedNode().blacklist(blacklist);
+                blacklisted.append(blacklist.getBlacklistedNode());
+                if (iterator.hasNext()) {
+                    blacklisted.append(" ");
+                }
+            }
+
+            String rootModuleConf = parent.getData().getReport().getConfiguration();
+            evicted.markEvicted(new EvictionData(rootModuleConf, parent, this, Collections
+                    .singleton(selected), "with blacklisting of " + blacklisted));
+
+            if (settings.debugConflictResolution()) {
+                Message.debug("evicting " + evicted + " by "
+                        + evicted.getEvictedData(rootModuleConf));
+            }
+            throw new RestartResolveProcess("trying to handle incompatibilities between "
+                    + selected + " and " + evicted);
+        }
+    }
+
+    private boolean handleIncompatibleCaller(Stack callerStack, IvyNode node, IvyNode callerNode,
+            IvyNode conflictParent, IvyNode selectedNode, IvyNode evictedNode,
+            Collection blacklisted, VersionMatcher versionMatcher) {
+        if (callerStack.subList(0, callerStack.size() - 1).contains(node)) {
+            // circular dependency found and handled: the current top of the stack (node)
+            // was already contained in the rest of the stack, the circle is closed, nothing
+            // else to do
+            return true;
+        } else {
+            callerStack.push(callerNode);
+            Collection sub = blackListIncompatibleCaller(versionMatcher, conflictParent,
+                selectedNode, evictedNode, callerStack);
+            callerStack.pop();
+            if (sub == null) {
+                // propagate the fact that a path with unblacklistable caller has been found
+                return false;
+            } else {
+                blacklisted.addAll(sub);
+                return true;
+            }
+        }
+    }
+
+    /**
+     * Tries to blacklist exactly one version for all callers paths.
+     * 
+     * @param versionMatcher
+     *            the version matcher to use to interpret versions
+     * @param conflictParent
+     *            the node in which the conflict is occurring
+     * @param selectedNode
+     *            the node in favor of which the conflict is resolved
+     * @param evictedNode
+     *            the node which will be evicted if we are able to blacklist all paths
+     * @param node
+     *            the node for which callers should be considered
+     * @return the collection of blacklisting to do, null if a blacklist is not possible in at least
+     *         one caller path
+     */
+    private Collection/* <IvyNodeBlacklist> */blackListIncompatibleCaller(
+            VersionMatcher versionMatcher, IvyNode conflictParent, IvyNode selectedNode,
+            IvyNode evictedNode, Stack/* <IvyNode> */callerStack) {
+        Collection/* <IvyNodeBlacklist> */blacklisted = new ArrayList/* <IvyNodeBlacklist> */();
+        IvyNode node = (IvyNode) callerStack.peek();
+        String rootModuleConf = conflictParent.getData().getReport().getConfiguration();
+        Caller[] callers = node.getCallers(rootModuleConf);
+        for (int i = 0; i < callers.length; i++) {
+            IvyNode callerNode = node.findNode(callers[i].getModuleRevisionId());
+            if (callerNode.isBlacklisted(rootModuleConf)) {
+                continue;
+            }
+            if (versionMatcher.isDynamic(callers[i].getAskedDependencyId(node.getData()))) {
+                blacklisted.add(new IvyNodeBlacklist(conflictParent, selectedNode, evictedNode,
+                        node, rootModuleConf));
+                if (node.isEvicted(rootModuleConf)
+                        && !handleIncompatibleCaller(callerStack, node, callerNode, conflictParent,
+                            selectedNode, evictedNode, blacklisted, versionMatcher)) {
+                    return null;
+                }
+            } else if (!handleIncompatibleCaller(callerStack, node, callerNode, conflictParent,
+                selectedNode, evictedNode, blacklisted, versionMatcher)) {
+                return null;
+            }
+        }
+        if (blacklisted.isEmpty() && !callerStack.subList(0, callerStack.size() - 1).contains(node)) {
+            return null;
+        }
+        return blacklisted;
+    }
+
+    protected void handleUnsolvableConflict(IvyNode parent, Collection conflicts, IvyNode node1,
+            IvyNode node2) {
+        throw new StrictConflictException(node1, node2);
+    }
+
+    public void handleAllBlacklistedRevisions(DependencyDescriptor dd,
+            Collection/* <ModuleRevisionId> */foundBlacklisted) {
+        ResolveData resolveData = IvyContext.getContext().getResolveData();
+        Collection/* <IvyNode> */blacklisted = new HashSet();
+        for (Iterator iterator = foundBlacklisted.iterator(); iterator.hasNext();) {
+            ModuleRevisionId mrid = (ModuleRevisionId) iterator.next();
+            blacklisted.add(resolveData.getNode(mrid));
+        }
+
+        for (Iterator iterator = blacklisted.iterator(); iterator.hasNext();) {
+            IvyNode node = (IvyNode) iterator.next();
+            IvyNodeBlacklist bdata = node.getBlacklistData(resolveData.getReport()
+                    .getConfiguration());
+            handleUnsolvableConflict(bdata.getConflictParent(),
+                Arrays.asList(new Object[] {bdata.getEvictedNode(), bdata.getSelectedNode()}),
+                bdata.getEvictedNode(), bdata.getSelectedNode());
+        }
+    }
+
+    public String toString() {
+        return getName();
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/conflict/LatestConflictManager.java b/src/java/org/apache/ivy/plugins/conflict/LatestConflictManager.java
new file mode 100644
index 0000000..aafe069
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/conflict/LatestConflictManager.java
@@ -0,0 +1,171 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.conflict;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.resolve.IvyNode;
+import org.apache.ivy.plugins.latest.ArtifactInfo;
+import org.apache.ivy.plugins.latest.LatestStrategy;
+import org.apache.ivy.util.Message;
+
+public class LatestConflictManager extends AbstractConflictManager {
+    public static class NoConflictResolvedYetException extends RuntimeException {
+    }
+
+    protected static final class IvyNodeArtifactInfo implements ArtifactInfo {
+        private final IvyNode node;
+
+        private IvyNodeArtifactInfo(IvyNode dep) {
+            node = dep;
+        }
+
+        public long getLastModified() {
+            long lastModified = node.getLastModified();
+            if (lastModified == 0) {
+                // if the last modified timestamp is unknown, we can't resolve
+                // the conflicts now, and trigger an exception which will be catched
+                // in the main resolveConflicts method
+                throw new NoConflictResolvedYetException();
+            } else {
+                return lastModified;
+            }
+        }
+
+        public String getRevision() {
+            return node.getResolvedId().getRevision();
+        }
+
+        public IvyNode getNode() {
+            return node;
+        }
+    }
+
+    private LatestStrategy strategy;
+
+    private String strategyName;
+
+    public LatestConflictManager() {
+    }
+
+    public LatestConflictManager(LatestStrategy strategy) {
+        this.strategy = strategy;
+    }
+
+    public LatestConflictManager(String name, LatestStrategy strategy) {
+        setName(name);
+        this.strategy = strategy;
+    }
+
+    public Collection resolveConflicts(IvyNode parent, Collection conflicts) {
+        if (conflicts.size() < 2) {
+            return conflicts;
+        }
+        for (Iterator iter = conflicts.iterator(); iter.hasNext();) {
+            IvyNode node = (IvyNode) iter.next();
+            DependencyDescriptor dd = node.getDependencyDescriptor(parent);
+            if (dd != null && dd.isForce()
+                    && parent.getResolvedId().equals(dd.getParentRevisionId())) {
+                return Collections.singleton(node);
+            }
+        }
+
+        /*
+         * If the list of conflicts contains dynamic revisions, delay the conflict calculation until
+         * they are resolved. TODO: we probably could already evict some of the dynamic revisions!
+         */
+        for (Iterator iter = conflicts.iterator(); iter.hasNext();) {
+            IvyNode node = (IvyNode) iter.next();
+            ModuleRevisionId modRev = node.getResolvedId();
+            if (getSettings().getVersionMatcher().isDynamic(modRev)) {
+                return null;
+            }
+        }
+
+        ArrayList unevicted = new ArrayList();
+        for (Iterator iter = conflicts.iterator(); iter.hasNext();) {
+            IvyNode node = (IvyNode) iter.next();
+            if (!node.isCompletelyEvicted())
+                unevicted.add(node);
+        }
+        if (unevicted.size() > 0) {
+            conflicts = unevicted;
+        }
+
+        try {
+            IvyNodeArtifactInfo latest = (IvyNodeArtifactInfo) getStrategy().findLatest(
+                toArtifactInfo(conflicts), null);
+            if (latest != null) {
+                return Collections.singleton(latest.getNode());
+            } else {
+                return conflicts;
+            }
+        } catch (NoConflictResolvedYetException ex) {
+            // we have not enough informations in the nodes to resolve conflict
+            // according to the resolveConflicts contract, we must return null
+            return null;
+        }
+    }
+
+    protected ArtifactInfo[] toArtifactInfo(Collection conflicts) {
+        List artifacts = new ArrayList(conflicts.size());
+        for (Iterator iter = conflicts.iterator(); iter.hasNext();) {
+            IvyNode node = (IvyNode) iter.next();
+            artifacts.add(new IvyNodeArtifactInfo(node));
+        }
+        return (ArtifactInfo[]) artifacts.toArray(new ArtifactInfo[artifacts.size()]);
+    }
+
+    public LatestStrategy getStrategy() {
+        if (strategy == null) {
+            if (strategyName != null) {
+                strategy = getSettings().getLatestStrategy(strategyName);
+                if (strategy == null) {
+                    Message.error("unknown latest strategy: " + strategyName);
+                    strategy = getSettings().getDefaultLatestStrategy();
+                }
+            } else {
+                strategy = getSettings().getDefaultLatestStrategy();
+            }
+        }
+        return strategy;
+    }
+
+    /**
+     * To conform to configurator API
+     * 
+     * @param latestStrategy
+     */
+    public void setLatest(String strategyName) {
+        this.strategyName = strategyName;
+    }
+
+    public void setStrategy(LatestStrategy strategy) {
+        this.strategy = strategy;
+    }
+
+    public String toString() {
+        return strategy != null ? String.valueOf(strategy) : strategyName;
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/conflict/NoConflictManager.java b/src/java/org/apache/ivy/plugins/conflict/NoConflictManager.java
new file mode 100644
index 0000000..cd6f5ce
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/conflict/NoConflictManager.java
@@ -0,0 +1,32 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.conflict;
+
+import java.util.Collection;
+
+import org.apache.ivy.core.resolve.IvyNode;
+
+public class NoConflictManager extends AbstractConflictManager {
+    public NoConflictManager() {
+        setName("all");
+    }
+
+    public Collection resolveConflicts(IvyNode parent, Collection conflicts) {
+        return conflicts;
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/conflict/RegexpConflictManager.java b/src/java/org/apache/ivy/plugins/conflict/RegexpConflictManager.java
new file mode 100644
index 0000000..b003cbf
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/conflict/RegexpConflictManager.java
@@ -0,0 +1,120 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.conflict;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.ivy.core.resolve.IvyNode;
+import org.apache.ivy.util.Message;
+
+/**
+ * A ConflictManager that can be used to resolve conflicts based on regular expressions of the
+ * revision of the module. The conflict manager is added like this:
+ * 
+ * <pre>
+ *    <!-- Match all revisions, but ignore the last dot(.) and the character after it.
+ *        Used to match api changes in out milestones. -->
+ *    <conflict-managers>
+ *        <regexp-cm name="regexp" 
+ *                   regexp="(.*)\..$" ignoreNonMatching="true"/>
+ *    </conflict-managers>
+ * </pre>
+ * 
+ * The regular expression must contain a capturing group. The group will be used to resolve the
+ * conflicts by an String.equals() test. If ignoreNonMatching is false non matching modules will
+ * result in an exception. If it is true they will be compaired by their full revision.
+ */
+public class RegexpConflictManager extends AbstractConflictManager {
+    private Pattern pattern = Pattern.compile("(.*)");
+
+    private boolean mIgnoreNonMatching;
+
+    public RegexpConflictManager() {
+    }
+
+    public void setRegexp(String regexp) {
+        pattern = Pattern.compile(regexp);
+        Matcher matcher = pattern.matcher("abcdef");
+        if (matcher.groupCount() != 1) {
+            String message = "Pattern does not contain ONE (capturing group): '" + pattern + "'";
+            Message.error(message);
+            throw new IllegalArgumentException(message);
+        }
+    }
+
+    public void setIgnoreNonMatching(boolean ignoreNonMatching) {
+        mIgnoreNonMatching = ignoreNonMatching;
+    }
+
+    public Collection resolveConflicts(IvyNode parent, Collection conflicts) {
+        IvyNode lastNode = null;
+        for (Iterator iter = conflicts.iterator(); iter.hasNext();) {
+            IvyNode node = (IvyNode) iter.next();
+
+            if (lastNode != null && !matchEquals(node, lastNode)) {
+                String msg = lastNode + ":" + getMatch(lastNode) + " (needed by "
+                        + Arrays.asList(lastNode.getAllRealCallers()) + ") conflicts with " + node
+                        + ":" + getMatch(node) + " (needed by "
+                        + Arrays.asList(node.getAllRealCallers()) + ")";
+                throw new StrictConflictException(msg);
+            }
+            if (lastNode == null || nodeIsGreater(node, lastNode)) {
+                lastNode = node;
+            }
+        }
+
+        return Collections.singleton(lastNode);
+    }
+
+    private boolean nodeIsGreater(IvyNode node, IvyNode lastNode) {
+        return getMatch(node).compareTo(getMatch(lastNode)) > 0;
+    }
+
+    private boolean matchEquals(IvyNode lastNode, IvyNode node) {
+        return getMatch(lastNode).equals(getMatch(node));
+    }
+
+    private String getMatch(IvyNode node) {
+        String revision = node.getId().getRevision();
+        Matcher matcher = pattern.matcher(revision);
+        if (matcher.matches()) {
+            String match = matcher.group(1);
+            if (match != null) {
+                return match;
+            }
+            warnOrThrow("First group of pattern: '" + pattern + "' does not match: " + revision
+                    + " " + node);
+        } else {
+            warnOrThrow("Pattern: '" + pattern + "' does not match: " + revision + " " + node);
+        }
+        return revision;
+    }
+
+    private void warnOrThrow(String message) {
+        if (mIgnoreNonMatching) {
+            Message.warn(message);
+        } else {
+            throw new StrictConflictException(message);
+        }
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/conflict/StrictConflictException.java b/src/java/org/apache/ivy/plugins/conflict/StrictConflictException.java
new file mode 100644
index 0000000..b2e0441
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/conflict/StrictConflictException.java
@@ -0,0 +1,49 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.conflict;
+
+import java.util.Arrays;
+
+import org.apache.ivy.core.resolve.IvyNode;
+import org.apache.ivy.core.resolve.ResolveProcessException;
+
+public class StrictConflictException extends ResolveProcessException {
+
+    public StrictConflictException() {
+        super();
+    }
+
+    public StrictConflictException(IvyNode node1, IvyNode node2) {
+        super(node1 + " (needed by " + Arrays.asList(node1.getAllRealCallers())
+                + ") conflicts with " + node2 + " (needed by "
+                + Arrays.asList(node2.getAllRealCallers()) + ")");
+    }
+
+    public StrictConflictException(String msg) {
+        super(msg);
+    }
+
+    public StrictConflictException(Throwable t) {
+        super(t);
+    }
+
+    public StrictConflictException(String msg, Throwable t) {
+        super(msg, t);
+    }
+
+}
diff --git a/src/java/org/apache/ivy/plugins/conflict/StrictConflictManager.java b/src/java/org/apache/ivy/plugins/conflict/StrictConflictManager.java
new file mode 100644
index 0000000..7d3fde4
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/conflict/StrictConflictManager.java
@@ -0,0 +1,53 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.conflict;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+
+import org.apache.ivy.core.resolve.IvyNode;
+import org.apache.ivy.plugins.version.VersionMatcher;
+
+public class StrictConflictManager extends AbstractConflictManager {
+
+    public StrictConflictManager() {
+    }
+
+    public Collection resolveConflicts(IvyNode parent, Collection conflicts) {
+        VersionMatcher versionMatcher = getSettings().getVersionMatcher();
+
+        IvyNode lastNode = null;
+        for (Iterator iter = conflicts.iterator(); iter.hasNext();) {
+            IvyNode node = (IvyNode) iter.next();
+
+            if (versionMatcher.isDynamic(node.getResolvedId())) {
+                // dynamic revision, not enough information to resolve conflict
+                return null;
+            }
+
+            if (lastNode != null && !lastNode.equals(node)) {
+                throw new StrictConflictException(lastNode, node);
+            }
+            lastNode = node;
+        }
+
+        return Collections.singleton(lastNode);
+    }
+
+}
diff --git a/src/java/org/apache/ivy/plugins/latest/AbstractLatestStrategy.java b/src/java/org/apache/ivy/plugins/latest/AbstractLatestStrategy.java
new file mode 100644
index 0000000..9c35d51
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/latest/AbstractLatestStrategy.java
@@ -0,0 +1,52 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.latest;
+
+import java.util.Date;
+import java.util.List;
+import java.util.ListIterator;
+
+public abstract class AbstractLatestStrategy implements LatestStrategy {
+    private String name;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String toString() {
+        return name;
+    }
+
+    public ArtifactInfo findLatest(ArtifactInfo[] infos, Date date) {
+        List l = sort(infos);
+
+        // the latest revision comes last, use a ListIterator to iterate the
+        // sorted list in the reverse direction.
+        for (ListIterator iter = l.listIterator(l.size()); iter.hasPrevious();) {
+            ArtifactInfo info = (ArtifactInfo) iter.previous();
+            if (date == null || info.getLastModified() < date.getTime()) {
+                return info;
+            }
+        }
+        return null;
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/latest/ArtifactInfo.java b/src/java/org/apache/ivy/plugins/latest/ArtifactInfo.java
new file mode 100644
index 0000000..f76bc13
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/latest/ArtifactInfo.java
@@ -0,0 +1,24 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.latest;
+
+public interface ArtifactInfo {
+    String getRevision();
+
+    long getLastModified();
+}
diff --git a/src/java/org/apache/ivy/plugins/latest/ComparatorLatestStrategy.java b/src/java/org/apache/ivy/plugins/latest/ComparatorLatestStrategy.java
new file mode 100644
index 0000000..8ad6677
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/latest/ComparatorLatestStrategy.java
@@ -0,0 +1,51 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.latest;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+public class ComparatorLatestStrategy extends AbstractLatestStrategy {
+
+    private Comparator comparator;
+
+    public ComparatorLatestStrategy() {
+    }
+
+    public ComparatorLatestStrategy(Comparator comparator) {
+        this.comparator = comparator;
+    }
+
+    public List sort(ArtifactInfo[] infos) {
+        List ret = new ArrayList(Arrays.asList(infos));
+        Collections.sort(ret, comparator);
+        return ret;
+    }
+
+    public Comparator getComparator() {
+        return comparator;
+    }
+
+    public void setComparator(Comparator comparator) {
+        this.comparator = comparator;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/plugins/latest/LatestLexicographicStrategy.java b/src/java/org/apache/ivy/plugins/latest/LatestLexicographicStrategy.java
new file mode 100644
index 0000000..b33bce8
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/latest/LatestLexicographicStrategy.java
@@ -0,0 +1,55 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.latest;
+
+import java.util.Comparator;
+
+public class LatestLexicographicStrategy extends ComparatorLatestStrategy {
+    /**
+     * Compares two revisions. Revisions are compared lexicographically unless a 'latest' revision
+     * is found. If the latest revision found is an absolute latest (latest. like), then it is
+     * assumed to be the greater. If a partial latest is found, then it is assumed to be greater
+     * than any matching fixed revision.
+     */
+    private static final Comparator COMPARATOR = new Comparator() {
+        public int compare(Object o1, Object o2) {
+            String rev1 = ((ArtifactInfo) o1).getRevision();
+            String rev2 = ((ArtifactInfo) o2).getRevision();
+            if (rev1.startsWith("latest")) {
+                return 1;
+            }
+            if (rev1.endsWith("+") && rev2.startsWith(rev1.substring(0, rev1.length() - 1))) {
+                return 1;
+            }
+            if (rev2.startsWith("latest")) {
+                return -1;
+            }
+            if (rev2.endsWith("+") && rev1.startsWith(rev2.substring(0, rev2.length() - 1))) {
+                return -1;
+            }
+            return rev1.compareTo(rev2);
+        }
+
+    };
+
+    public LatestLexicographicStrategy() {
+        super(COMPARATOR);
+        setName("latest-lexico");
+    }
+
+}
diff --git a/src/java/org/apache/ivy/plugins/latest/LatestRevisionStrategy.java b/src/java/org/apache/ivy/plugins/latest/LatestRevisionStrategy.java
new file mode 100644
index 0000000..51d72d1
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/latest/LatestRevisionStrategy.java
@@ -0,0 +1,198 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.latest;
+
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.ivy.core.IvyContext;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.plugins.version.VersionMatcher;
+
+public class LatestRevisionStrategy extends ComparatorLatestStrategy {
+    /**
+     * Compares two ModuleRevisionId by their revision. Revisions are compared using an algorithm
+     * inspired by PHP version_compare one.
+     */
+    final class MridComparator implements Comparator {
+        public int compare(Object o1, Object o2) {
+            String rev1 = ((ModuleRevisionId) o1).getRevision();
+            String rev2 = ((ModuleRevisionId) o2).getRevision();
+
+            rev1 = rev1.replaceAll("([a-zA-Z])(\\d)", "$1.$2");
+            rev1 = rev1.replaceAll("(\\d)([a-zA-Z])", "$1.$2");
+            rev2 = rev2.replaceAll("([a-zA-Z])(\\d)", "$1.$2");
+            rev2 = rev2.replaceAll("(\\d)([a-zA-Z])", "$1.$2");
+
+            String[] parts1 = rev1.split("[\\._\\-\\+]");
+            String[] parts2 = rev2.split("[\\._\\-\\+]");
+
+            int i = 0;
+            for (; i < parts1.length && i < parts2.length; i++) {
+                if (parts1[i].equals(parts2[i])) {
+                    continue;
+                }
+                boolean is1Number = isNumber(parts1[i]);
+                boolean is2Number = isNumber(parts2[i]);
+                if (is1Number && !is2Number) {
+                    return 1;
+                }
+                if (is2Number && !is1Number) {
+                    return -1;
+                }
+                if (is1Number && is2Number) {
+                    return Long.valueOf(parts1[i]).compareTo(Long.valueOf(parts2[i]));
+                }
+                // both are strings, we compare them taking into account special meaning
+                Map specialMeanings = getSpecialMeanings();
+                Integer sm1 = (Integer) specialMeanings.get(parts1[i].toLowerCase(Locale.US));
+                Integer sm2 = (Integer) specialMeanings.get(parts2[i].toLowerCase(Locale.US));
+                if (sm1 != null) {
+                    sm2 = sm2 == null ? new Integer(0) : sm2;
+                    return sm1.compareTo(sm2);
+                }
+                if (sm2 != null) {
+                    return new Integer(0).compareTo(sm2);
+                }
+                return parts1[i].compareTo(parts2[i]);
+            }
+            if (i < parts1.length) {
+                return isNumber(parts1[i]) ? 1 : -1;
+            }
+            if (i < parts2.length) {
+                return isNumber(parts2[i]) ? -1 : 1;
+            }
+            return 0;
+        }
+
+        private boolean isNumber(String str) {
+            return str.matches("\\d+");
+        }
+    }
+
+    /**
+     * Compares two ArtifactInfo by their revision. Revisions are compared using an algorithm
+     * inspired by PHP version_compare one, unless a dynamic revision is given, in which case the
+     * version matcher is used to perform the comparison.
+     */
+    final class ArtifactInfoComparator implements Comparator {
+        public int compare(Object o1, Object o2) {
+            String rev1 = ((ArtifactInfo) o1).getRevision();
+            String rev2 = ((ArtifactInfo) o2).getRevision();
+
+            /*
+             * The revisions can still be not resolved, so we use the current version matcher to
+             * know if one revision is dynamic, and in this case if it should be considered greater
+             * or lower than the other one. Note that if the version matcher compare method returns
+             * 0, it's because it's not possible to know which revision is greater. In this case we
+             * consider the dynamic one to be greater, because most of the time it will then be
+             * actually resolved and a real comparison will occur.
+             */
+            VersionMatcher vmatcher = IvyContext.getContext().getSettings().getVersionMatcher();
+            ModuleRevisionId mrid1 = ModuleRevisionId.newInstance("", "", rev1);
+            ModuleRevisionId mrid2 = ModuleRevisionId.newInstance("", "", rev2);
+
+            if (vmatcher.isDynamic(mrid1)) {
+                int c = vmatcher.compare(mrid1, mrid2, mridComparator);
+                return c >= 0 ? 1 : -1;
+            } else if (vmatcher.isDynamic(mrid2)) {
+                int c = vmatcher.compare(mrid2, mrid1, mridComparator);
+                return c >= 0 ? -1 : 1;
+            }
+
+            return mridComparator.compare(mrid1, mrid2);
+        }
+    }
+
+    public static class SpecialMeaning {
+        private String name;
+
+        private Integer value;
+
+        public String getName() {
+            return name;
+        }
+
+        public void setName(String name) {
+            this.name = name;
+        }
+
+        public Integer getValue() {
+            return value;
+        }
+
+        public void setValue(Integer value) {
+            this.value = value;
+        }
+
+        public void validate() {
+            if (name == null) {
+                throw new IllegalStateException("a special meaning should have a name");
+            }
+            if (value == null) {
+                throw new IllegalStateException("a special meaning should have a value");
+            }
+        }
+    }
+
+    private static final Map DEFAULT_SPECIAL_MEANINGS;
+    static {
+        DEFAULT_SPECIAL_MEANINGS = new HashMap();
+        DEFAULT_SPECIAL_MEANINGS.put("dev", new Integer(-1));
+        DEFAULT_SPECIAL_MEANINGS.put("rc", new Integer(1));
+        DEFAULT_SPECIAL_MEANINGS.put("final", new Integer(2));
+    }
+
+    private final Comparator mridComparator = new MridComparator();
+
+    private final Comparator artifactInfoComparator = new ArtifactInfoComparator();
+
+    private Map specialMeanings = null;
+
+    private boolean usedefaultspecialmeanings = true;
+
+    public LatestRevisionStrategy() {
+        setComparator(artifactInfoComparator);
+        setName("latest-revision");
+    }
+
+    public void addConfiguredSpecialMeaning(SpecialMeaning meaning) {
+        meaning.validate();
+        getSpecialMeanings().put(meaning.getName().toLowerCase(Locale.US), meaning.getValue());
+    }
+
+    public synchronized Map getSpecialMeanings() {
+        if (specialMeanings == null) {
+            specialMeanings = new HashMap();
+            if (isUsedefaultspecialmeanings()) {
+                specialMeanings.putAll(DEFAULT_SPECIAL_MEANINGS);
+            }
+        }
+        return specialMeanings;
+    }
+
+    public boolean isUsedefaultspecialmeanings() {
+        return usedefaultspecialmeanings;
+    }
+
+    public void setUsedefaultspecialmeanings(boolean usedefaultspecialmeanings) {
+        this.usedefaultspecialmeanings = usedefaultspecialmeanings;
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/latest/LatestStrategy.java b/src/java/org/apache/ivy/plugins/latest/LatestStrategy.java
new file mode 100644
index 0000000..3666d6a
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/latest/LatestStrategy.java
@@ -0,0 +1,45 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.latest;
+
+import java.util.Date;
+import java.util.List;
+
+public interface LatestStrategy {
+    /**
+     * Finds the latest artifact among the given artifacts info. The definition of 'latest' depends
+     * on the strategy itself. Given artifacts info are all good candidate. If the given date is not
+     * null, then found artifact should not be later than this date.
+     * 
+     * @param infos
+     * @param date
+     * @return the latest artifact among the given ones.
+     */
+    ArtifactInfo findLatest(ArtifactInfo[] infos, Date date);
+
+    /**
+     * Sorts the given artifacts info from the oldest one to the latest one. The definition of
+     * 'latest' depends on the strategy itself. Given artifacts info are all good candidate.
+     * 
+     * @param infos
+     * @return
+     */
+    List sort(ArtifactInfo[] infos);
+
+    String getName();
+}
diff --git a/src/java/org/apache/ivy/plugins/latest/LatestTimeStrategy.java b/src/java/org/apache/ivy/plugins/latest/LatestTimeStrategy.java
new file mode 100644
index 0000000..e5f8ccf
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/latest/LatestTimeStrategy.java
@@ -0,0 +1,36 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.latest;
+
+import java.util.Comparator;
+
+public class LatestTimeStrategy extends ComparatorLatestStrategy {
+    private static final Comparator COMPARATOR = new Comparator() {
+        public int compare(Object o1, Object o2) {
+            long d1 = ((ArtifactInfo) o1).getLastModified();
+            long d2 = ((ArtifactInfo) o2).getLastModified();
+            return new Long(d1).compareTo(new Long(d2));
+        }
+
+    };
+
+    public LatestTimeStrategy() {
+        super(COMPARATOR);
+        setName("latest-time");
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/lock/AbstractLockStrategy.java b/src/java/org/apache/ivy/plugins/lock/AbstractLockStrategy.java
new file mode 100644
index 0000000..6535b04
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/lock/AbstractLockStrategy.java
@@ -0,0 +1,47 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.lock;
+
+public abstract class AbstractLockStrategy implements LockStrategy {
+    private String name;
+
+    private boolean debugLocking = false;
+
+    protected AbstractLockStrategy() {
+    }
+
+    protected AbstractLockStrategy(boolean debugLocking) {
+        this.debugLocking = debugLocking;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String toString() {
+        return name;
+    }
+
+    public boolean isDebugLocking() {
+        return debugLocking;
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/lock/ArtifactLockStrategy.java b/src/java/org/apache/ivy/plugins/lock/ArtifactLockStrategy.java
new file mode 100644
index 0000000..1752486
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/lock/ArtifactLockStrategy.java
@@ -0,0 +1,38 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.lock;
+
+import java.io.File;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+
+public abstract class ArtifactLockStrategy extends FileBasedLockStrategy {
+
+    protected ArtifactLockStrategy(FileLocker locker, boolean debugLocking) {
+        super(locker, debugLocking);
+    }
+
+    public boolean lockArtifact(Artifact artifact, File artifactFileToDownload)
+            throws InterruptedException {
+        return acquireLock(new File(artifactFileToDownload.getAbsolutePath() + ".lck"));
+    }
+
+    public void unlockArtifact(Artifact artifact, File artifactFileToDownload) {
+        releaseLock(new File(artifactFileToDownload.getAbsolutePath() + ".lck"));
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/lock/CreateFileLockStrategy.java b/src/java/org/apache/ivy/plugins/lock/CreateFileLockStrategy.java
new file mode 100644
index 0000000..d8dc93f
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/lock/CreateFileLockStrategy.java
@@ -0,0 +1,27 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.lock;
+
+public class CreateFileLockStrategy extends ArtifactLockStrategy {
+
+    public CreateFileLockStrategy(boolean debugLocking) {
+        super(new CreateFileLocker(debugLocking), debugLocking);
+        setName("artifact-lock");
+    }
+
+}
diff --git a/src/java/org/apache/ivy/plugins/lock/DeleteOnExitHook.java b/src/java/org/apache/ivy/plugins/lock/DeleteOnExitHook.java
new file mode 100644
index 0000000..112f760
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/lock/DeleteOnExitHook.java
@@ -0,0 +1,54 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.lock;
+
+import java.io.File;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+
+class DeleteOnExitHook {
+
+    static {
+        Runtime.getRuntime().addShutdownHook(new Thread() {
+            public void run() {
+                runHook();
+            }
+        });
+    }
+
+    private static LinkedHashSet files = new LinkedHashSet();
+
+    private DeleteOnExitHook() {
+    }
+
+    static synchronized void add(File file) {
+        files.add(file);
+    }
+
+    static synchronized void remove(File file) {
+        files.remove(file);
+    }
+
+    static synchronized void runHook() {
+        Iterator itr = files.iterator();
+        while (itr.hasNext()) {
+            ((File) itr.next()).delete();
+            itr.remove();
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/java/org/apache/ivy/plugins/lock/FileBasedLockStrategy.java b/src/java/org/apache/ivy/plugins/lock/FileBasedLockStrategy.java
new file mode 100644
index 0000000..637988f
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/lock/FileBasedLockStrategy.java
@@ -0,0 +1,356 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.lock;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.channels.FileLock;
+import java.util.Enumeration;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.ivy.util.Message;
+
+public abstract class FileBasedLockStrategy extends AbstractLockStrategy {
+    private static final int SLEEP_TIME = 100;
+
+    private static final long DEFAULT_TIMEOUT = 2 * 60 * 1000;
+
+    /**
+     * The locker to use to make file lock attempts.
+     */
+    private FileLocker locker;
+
+    private long timeout = DEFAULT_TIMEOUT;
+
+    /**
+     * Lock counter list must be static: locks are implicitly shared to the entire process, so the
+     * list too much be.
+     */
+    private static ConcurrentMap/* <File, Map<Thread, Integer>> */currentLockHolders = new ConcurrentHashMap();
+
+    protected FileBasedLockStrategy() {
+        this(new CreateFileLocker(false), false);
+    }
+
+    protected FileBasedLockStrategy(boolean debugLocking) {
+        this(new CreateFileLocker(debugLocking), debugLocking);
+    }
+
+    protected FileBasedLockStrategy(FileLocker locker, boolean debugLocking) {
+        super(debugLocking);
+        this.locker = locker;
+    }
+
+    protected boolean acquireLock(File file) throws InterruptedException {
+        Thread currentThread = Thread.currentThread();
+        if (isDebugLocking()) {
+            debugLocking("acquiring lock on " + file);
+        }
+        long start = System.currentTimeMillis();
+        do {
+            synchronized (currentLockHolders) {
+                if (isDebugLocking()) {
+                    debugLocking("entered synchronized area (locking)");
+                }
+                int lockCount = hasLock(file, currentThread);
+                if (isDebugLocking()) {
+                    debugLocking("current status for " + file + " is " + lockCount
+                            + " held locks: " + getCurrentLockHolderNames(file));
+                }
+                if (lockCount < 0) {
+                    /* Another thread in this process holds the lock; we need to wait */
+                    if (isDebugLocking()) {
+                        debugLocking("waiting for another thread to release the lock: "
+                                + getCurrentLockHolderNames(file));
+                    }
+                } else if (lockCount > 0) {
+                    int holdLocks = incrementLock(file, currentThread);
+                    if (isDebugLocking()) {
+                        debugLocking("reentrant lock acquired on " + file + " in "
+                                + (System.currentTimeMillis() - start) + "ms" + " - hold locks = "
+                                + holdLocks);
+                    }
+                    return true;
+                } else {
+                    /* No prior lock on this file is held at all */
+                    if (locker.tryLock(file)) {
+                        if (isDebugLocking()) {
+                            debugLocking("lock acquired on " + file + " in "
+                                    + (System.currentTimeMillis() - start) + "ms");
+                        }
+                        incrementLock(file, currentThread);
+                        return true;
+                    }
+                }
+            }
+            if (isDebugLocking()) {
+                debugLocking("failed to acquire lock; sleeping for retry...");
+            }
+            Thread.sleep(SLEEP_TIME);
+        } while (System.currentTimeMillis() - start < timeout);
+        return false;
+    }
+
+    protected void releaseLock(File file) {
+        Thread currentThread = Thread.currentThread();
+        if (isDebugLocking()) {
+            debugLocking("releasing lock on " + file);
+        }
+        synchronized (currentLockHolders) {
+            if (isDebugLocking()) {
+                debugLocking("entered synchronized area (unlocking)");
+            }
+            int holdLocks = decrementLock(file, currentThread);
+            if (holdLocks == 0) {
+                locker.unlock(file);
+                if (isDebugLocking()) {
+                    debugLocking("lock released on " + file);
+                }
+            } else {
+                if (isDebugLocking()) {
+                    debugLocking("reentrant lock released on " + file + " - hold locks = "
+                            + holdLocks);
+                }
+            }
+        }
+    }
+
+    private static void debugLocking(String msg) {
+        Message.info(Thread.currentThread() + " " + System.currentTimeMillis() + " " + msg);
+    }
+
+    /**
+     * Determine the state of the lockfile.
+     * 
+     * Must be called from within a synchronized block.
+     * 
+     * Three possibilities exist: - The lock is held by the current thread (>0) - The lock is held
+     * by one or more different threads (-1) - The lock is not held at all (0).
+     * 
+     * @param file
+     *            file to lock
+     * @param forThread
+     *            thread for which lock status is being queried
+     */
+    private int hasLock(File file, Thread forThread) {
+        Map locksPerThread = (Map) currentLockHolders.get(file);
+        if (locksPerThread == null) {
+            return 0;
+        }
+        if (locksPerThread.isEmpty()) {
+            return 0;
+        }
+        Integer counterObj = (Integer) locksPerThread.get(forThread);
+        int counter = counterObj == null ? 0 : counterObj.intValue();
+        if (counter > 0) {
+            return counter;
+        } else {
+            return -1;
+        }
+    }
+
+    /**
+     * Record that this thread holds the lock.
+     * 
+     * Asserts that the lock has been previously grabbed by this thread. Must be called from a
+     * synchronized block in which the lock was grabbed.
+     * 
+     * @param file
+     *            file which has been locked
+     * @param forThread
+     *            thread for which locking occurred
+     * @return number of times this thread has grabbed the lock
+     */
+    private int incrementLock(File file, Thread forThread) {
+        Map locksPerThread = (Map) currentLockHolders.get(file);
+        if (locksPerThread == null) {
+            locksPerThread = new ConcurrentHashMap();
+            currentLockHolders.put(file, locksPerThread);
+        }
+        Integer c = (Integer) locksPerThread.get(forThread);
+        int holdLocks = c == null ? 1 : c.intValue() + 1;
+        locksPerThread.put(forThread, new Integer(holdLocks));
+        return holdLocks;
+    }
+
+    /**
+     * Decrease depth of this thread's lock.
+     * 
+     * Must be called within a synchronized block.
+     * 
+     * If this returns 0, the caller is responsible for releasing the lock within that same block.
+     * 
+     * @param file
+     *            file for which lock depth is being decreased
+     * @param forThread
+     *            thread for which lock depth is being decreased
+     * @return remaining depth of this lock
+     */
+    private int decrementLock(File file, Thread forThread) {
+        ConcurrentHashMap locksPerThread = (ConcurrentHashMap) currentLockHolders.get(file);
+        if (locksPerThread == null) {
+            throw new RuntimeException("Calling decrementLock on a thread which holds no locks");
+        }
+        Integer c = (Integer) locksPerThread.get(forThread);
+        int oldHeldLocks = c == null ? 0 : c.intValue();
+        if (oldHeldLocks <= 0) {
+            throw new RuntimeException("Calling decrementLock on a thread which holds no locks");
+        }
+        int newHeldLocks = oldHeldLocks - 1;
+        if (newHeldLocks > 0) {
+            locksPerThread.put(forThread, new Integer(newHeldLocks));
+        } else {
+            locksPerThread.remove(forThread);
+        }
+        return newHeldLocks;
+    }
+
+    /**
+     * Return a string naming the threads which currently hold this lock.
+     */
+    protected String getCurrentLockHolderNames(File file) {
+        StringBuilder sb = new StringBuilder();
+        ConcurrentHashMap m = (ConcurrentHashMap) currentLockHolders.get(file);
+        if (m == null) {
+            return "(NULL)";
+        }
+        Enumeration threads = m.keys();
+        while (threads.hasMoreElements()) {
+            Thread t = (Thread) threads.nextElement();
+            sb.append(t.toString());
+            if (threads.hasMoreElements()) {
+                sb.append(", ");
+            }
+        }
+        return sb.toString();
+    }
+
+    public static interface FileLocker {
+        boolean tryLock(File f);
+
+        void unlock(File f);
+    }
+
+    /**
+     * "locks" a file by creating it if it doesn't exist, relying on the
+     * {@link File#createNewFile()} atomicity.
+     */
+    public static class CreateFileLocker implements FileLocker {
+        private boolean debugLocking;
+
+        public CreateFileLocker(boolean debugLocking) {
+            this.debugLocking = debugLocking;
+        }
+
+        public boolean tryLock(File file) {
+            try {
+                if (file.getParentFile().exists() || file.getParentFile().mkdirs()) {
+                    if (file.createNewFile()) {
+                        DeleteOnExitHook.add(file);
+                        return true;
+                    } else {
+                        if (debugLocking) {
+                            debugLocking("file creation failed " + file);
+                        }
+                    }
+                }
+            } catch (IOException e) {
+                // ignored
+                Message.verbose("file creation failed due to an exception: " + e.getMessage()
+                        + " (" + file + ")");
+            }
+            return false;
+        }
+
+        public void unlock(File file) {
+            file.delete();
+            DeleteOnExitHook.remove(file);
+        }
+    }
+
+    /**
+     * Locks a file using the {@link FileLock} mechanism.
+     */
+    public static class NIOFileLocker implements FileLocker {
+
+        private Map locks = new ConcurrentHashMap();
+
+        private boolean debugLocking;
+
+        public NIOFileLocker(boolean debugLocking) {
+            this.debugLocking = debugLocking;
+        }
+
+        private static class LockData {
+            private RandomAccessFile raf;
+
+            private FileLock l;
+
+            LockData(RandomAccessFile raf, FileLock l) {
+                this.raf = raf;
+                this.l = l;
+            }
+        }
+
+        public boolean tryLock(File file) {
+            try {
+                if (file.getParentFile().exists() || file.getParentFile().mkdirs()) {
+                    // this must not be closed until unlock
+                    RandomAccessFile raf = new RandomAccessFile(file, "rw");
+                    FileLock l = raf.getChannel().tryLock();
+                    if (l != null) {
+                        synchronized (this) {
+                            locks.put(file, new LockData(raf, l));
+                        }
+                        return true;
+                    } else {
+                        if (debugLocking) {
+                            debugLocking("failed to acquire lock on " + file);
+                        }
+                    }
+                }
+            } catch (IOException e) {
+                // ignored
+                Message.verbose("file lock failed due to an exception: " + e.getMessage() + " ("
+                        + file + ")");
+            }
+            return false;
+        }
+
+        public void unlock(File file) {
+            synchronized (this) {
+                LockData data = (LockData) locks.get(file);
+                if (data == null) {
+                    throw new IllegalArgumentException("file not previously locked: " + file);
+                }
+
+                try {
+                    locks.remove(file);
+                    data.l.release();
+                    data.raf.close();
+                } catch (IOException e) {
+                    Message.error("problem while releasing lock on " + file + ": " + e.getMessage());
+                }
+            }
+        }
+
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/lock/LockStrategy.java b/src/java/org/apache/ivy/plugins/lock/LockStrategy.java
new file mode 100644
index 0000000..a28d6f8
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/lock/LockStrategy.java
@@ -0,0 +1,73 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.lock;
+
+import java.io.File;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+
+/**
+ * A lock strategy determines when and how lock should be performed when downloading data to a
+ * cache.
+ * <p>
+ * Note that some implementations may actually choose to NOT perform locking, when no lock is
+ * necessary (cache not shared). Some other implementations may choose to lock the cache for the
+ * download of a whole module (not possible yet), or at the artifact level.
+ * <p>
+ * </p>
+ * The lock methods should return true when the lock is either actually acquired or not performed by
+ * the strategy. </p>
+ * <p>
+ * Locking used in the locking strategy must support reentrant lock. Reentrant locking should be
+ * performed for the whole strategy.
+ * </p>
+ */
+public interface LockStrategy {
+
+    /**
+     * Returns the name of the strategy
+     * 
+     * @return the name of the strategy
+     */
+    String getName();
+
+    /**
+     * Performs a lock before downloading the given {@link Artifact} to the given file.
+     * 
+     * @param artifact
+     *            the artifact about to be downloaded
+     * @param artifactFileToDownload
+     *            the file where the artifact will be downloaded
+     * @return true if the artifact is locked, false otherwise
+     * @throws InterruptedException
+     *             if the thread is interrupted while waiting to acquire the lock
+     */
+    boolean lockArtifact(Artifact artifact, File artifactFileToDownload)
+            throws InterruptedException;
+
+    /**
+     * Release the lock acquired for an artifact download.
+     * 
+     * @param artifact
+     *            the artifact for which the lock was acquired
+     * @param artifactFileToDownload
+     *            the file where the artifact is supposed to have been downloaded
+     */
+    void unlockArtifact(Artifact artifact, File artifactFileToDownload);
+
+}
diff --git a/src/java/org/apache/ivy/plugins/lock/NIOFileLockStrategy.java b/src/java/org/apache/ivy/plugins/lock/NIOFileLockStrategy.java
new file mode 100644
index 0000000..9f8c474
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/lock/NIOFileLockStrategy.java
@@ -0,0 +1,27 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.lock;
+
+public class NIOFileLockStrategy extends ArtifactLockStrategy {
+
+    public NIOFileLockStrategy(boolean debugLocking) {
+        super(new NIOFileLocker(debugLocking), debugLocking);
+        setName("artifact-lock-nio");
+    }
+
+}
diff --git a/src/java/org/apache/ivy/plugins/lock/NoLockStrategy.java b/src/java/org/apache/ivy/plugins/lock/NoLockStrategy.java
new file mode 100644
index 0000000..6ca735c
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/lock/NoLockStrategy.java
@@ -0,0 +1,35 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.lock;
+
+import java.io.File;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+
+public class NoLockStrategy extends AbstractLockStrategy {
+    public NoLockStrategy() {
+        setName("no-lock");
+    }
+
+    public final boolean lockArtifact(Artifact artifact, File artifactFileToDownload) {
+        return true;
+    }
+
+    public final void unlockArtifact(Artifact artifact, File artifactFileToDownload) {
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/matcher/AbstractPatternMatcher.java b/src/java/org/apache/ivy/plugins/matcher/AbstractPatternMatcher.java
new file mode 100644
index 0000000..cf90573
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/matcher/AbstractPatternMatcher.java
@@ -0,0 +1,63 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.ivy.plugins.matcher;
+
+/**
+ * An abstract implementation of the pattern matcher providing base template methods
+ */
+public abstract class AbstractPatternMatcher implements PatternMatcher {
+    private final String name;
+
+    /**
+     * Create a new instance of a pattern matcher
+     * 
+     * @param name
+     *            the name of the pattern matcher. Never null.
+     */
+    public AbstractPatternMatcher(/* @NotNull */String name) {
+        this.name = name;
+    }
+
+    public/* @NotNull */Matcher getMatcher(/* @NotNull */String expression) {
+        if (expression == null) {
+            throw new NullPointerException();
+        }
+        if (ANY_EXPRESSION.equals(expression)) {
+            return AnyMatcher.INSTANCE;
+        }
+        return newMatcher(expression);
+    }
+
+    public/* @NotNull */String getName() {
+        return name;
+    }
+
+    /**
+     * Returns an instance of the implementation specific matcher.
+     * 
+     * @param expression
+     *            the string to be matched.
+     * @return the instance of the related matcher. Never null.
+     */
+    protected abstract/* @NotNull */Matcher newMatcher(/* @NotNull */String expression);
+
+    public String toString() {
+        return getName();
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/matcher/AnyMatcher.java b/src/java/org/apache/ivy/plugins/matcher/AnyMatcher.java
new file mode 100644
index 0000000..7f6d36f
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/matcher/AnyMatcher.java
@@ -0,0 +1,40 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.matcher;
+
+/**
+ * A matcher that will match everything.
+ */
+public/* @Immutable */class AnyMatcher implements Matcher {
+    public static final Matcher INSTANCE = new AnyMatcher();
+
+    public AnyMatcher() {
+    }
+
+    public boolean matches(String input) {
+        if (input == null) {
+            throw new NullPointerException();
+        }
+        return true;
+    }
+
+    public boolean isExact() {
+        return false;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/plugins/matcher/ExactOrRegexpPatternMatcher.java b/src/java/org/apache/ivy/plugins/matcher/ExactOrRegexpPatternMatcher.java
new file mode 100644
index 0000000..116a47a
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/matcher/ExactOrRegexpPatternMatcher.java
@@ -0,0 +1,64 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.matcher;
+
+/**
+ * A pattern matcher that tries to match exactly the input with the expression, or match it as a
+ * pattern.
+ * <p/>
+ * The evaluation for matching is perform first by checking if expression and input are equals (via
+ * equals method) else it attempts to do it by trying to match the input using the expression as a
+ * regexp.
+ * 
+ * @see ExactPatternMatcher
+ * @see RegexpPatternMatcher
+ */
+public/* @Immutable */final class ExactOrRegexpPatternMatcher extends AbstractPatternMatcher {
+
+    public static final ExactOrRegexpPatternMatcher INSTANCE = new ExactOrRegexpPatternMatcher();
+
+    public ExactOrRegexpPatternMatcher() {
+        super(EXACT_OR_REGEXP);
+    }
+
+    protected Matcher newMatcher(String expression) {
+        return new ExactOrRegexpMatcher(expression);
+    }
+
+    private static final class ExactOrRegexpMatcher implements Matcher {
+        private Matcher exact;
+
+        private Matcher regexp;
+
+        public ExactOrRegexpMatcher(String expression) {
+            exact = ExactPatternMatcher.INSTANCE.getMatcher(expression);
+            regexp = RegexpPatternMatcher.INSTANCE.getMatcher(expression);
+        }
+
+        public boolean matches(String input) {
+            if (input == null) {
+                throw new NullPointerException();
+            }
+            return exact.matches(input) || regexp.matches(input);
+        }
+
+        public boolean isExact() {
+            return regexp.isExact(); // && exact.isExact();
+        }
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/matcher/ExactPatternMatcher.java b/src/java/org/apache/ivy/plugins/matcher/ExactPatternMatcher.java
new file mode 100644
index 0000000..565ee24
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/matcher/ExactPatternMatcher.java
@@ -0,0 +1,56 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.matcher;
+
+/**
+ * Implementation of an exact matcher.
+ * <p/>
+ * The matching will be performed against an expression being a string. It will only matches if both
+ * strings are equal (per equals()) rule or if both strings are null.
+ */
+public/* @Immutable */final class ExactPatternMatcher extends AbstractPatternMatcher {
+
+    public static final ExactPatternMatcher INSTANCE = new ExactPatternMatcher();
+
+    public ExactPatternMatcher() {
+        super(EXACT);
+    }
+
+    protected Matcher newMatcher(String expression) {
+        return new ExactMatcher(expression);
+    }
+
+    private static/* @Immutable */class ExactMatcher implements Matcher {
+        private String expression;
+
+        public ExactMatcher(String expression) {
+            this.expression = expression;
+        }
+
+        public boolean matches(String input) {
+            if (input == null) {
+                throw new NullPointerException();
+            }
+            return input.equals(expression);
+        }
+
+        public boolean isExact() {
+            return true;
+        }
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/matcher/GlobPatternMatcher.java b/src/java/org/apache/ivy/plugins/matcher/GlobPatternMatcher.java
new file mode 100644
index 0000000..544cb23
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/matcher/GlobPatternMatcher.java
@@ -0,0 +1,104 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.matcher;
+
+import java.util.regex.PatternSyntaxException;
+
+import org.apache.oro.text.GlobCompiler;
+import org.apache.oro.text.regex.MalformedPatternException;
+import org.apache.oro.text.regex.Pattern;
+import org.apache.oro.text.regex.Perl5Matcher;
+
+/**
+ * A pattern matcher matching input using unix-like glob matcher expressions. Meta characters are:
+ * <ul>
+ * <li>* - Matches zero or more characters</li>
+ * <li>? - Matches exactly one character.</li>
+ * </ul>
+ * <p/>
+ * <b> Note that this matcher is available only with <a href="http://jakarta.apache.org/oro"Apache
+ * Jakarta Oro 2.0.8</a> in your classpath.</b>
+ * 
+ * @see <a
+ *      href="http://jakarta.apache.org/oro/api/org/apache/oro/text/GlobCompiler.html">GlobCompiler</a>
+ */
+public/* @Immutable */final class GlobPatternMatcher extends AbstractPatternMatcher {
+
+    public static final GlobPatternMatcher INSTANCE = new GlobPatternMatcher();
+
+    /*
+     * NOTE: GlobCompiler does ~100K compilation/s - If necessary look into using ThreadLocal for
+     * GlobCompiler/Perl5Matcher to cut on useless object creation - If expression are reused over
+     * and over a LRU cache could make sense
+     */
+
+    public GlobPatternMatcher() {
+        super(GLOB);
+    }
+
+    protected Matcher newMatcher(String expression) {
+        return new GlobMatcher(expression);
+    }
+
+    private static class GlobMatcher implements Matcher {
+        private Pattern pattern;
+
+        private String expression;
+
+        private Boolean exact;
+
+        public GlobMatcher(String expression) throws PatternSyntaxException {
+            this.expression = expression;
+            try {
+                pattern = new GlobCompiler().compile(expression);
+            } catch (MalformedPatternException e) {
+                throw new PatternSyntaxException(e.getMessage(), expression, 0);
+            }
+        }
+
+        public boolean matches(String input) {
+            if (input == null) {
+                throw new NullPointerException();
+            }
+            return new Perl5Matcher().matches(input, pattern);
+        }
+
+        public boolean isExact() {
+            if (exact == null) {
+                exact = calculateExact();
+            }
+            return exact.booleanValue();
+        }
+
+        private Boolean calculateExact() {
+            Boolean result = Boolean.TRUE;
+
+            char[] expressionChars = expression.toCharArray();
+            for (int i = 0; i < expressionChars.length; i++) {
+                char ch = expressionChars[i];
+                if (ch == '*' || ch == '?' || ch == '[' || ch == ']') {
+                    result = Boolean.FALSE;
+                    break;
+                }
+            }
+
+            return result;
+        }
+    }
+
+}
diff --git a/src/java/org/apache/ivy/plugins/matcher/MapMatcher.java b/src/java/org/apache/ivy/plugins/matcher/MapMatcher.java
new file mode 100644
index 0000000..fb1bc81
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/matcher/MapMatcher.java
@@ -0,0 +1,70 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.matcher;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+
+public class MapMatcher {
+    private Map/* <String, Matcher> */matchers = new HashMap();
+
+    private PatternMatcher pm;
+
+    private Map attributes;
+
+    public MapMatcher(Map attributes, PatternMatcher pm) {
+        this.attributes = attributes;
+        this.pm = pm;
+        for (Iterator iter = attributes.entrySet().iterator(); iter.hasNext();) {
+            Entry entry = (Entry) iter.next();
+            String value = (String) entry.getValue();
+            if (value != null) {
+                matchers.put(entry.getKey(), pm.getMatcher(value));
+            }
+        }
+    }
+
+    public boolean matches(Map/* <String,String> */m) {
+        for (Iterator iter = matchers.entrySet().iterator(); iter.hasNext();) {
+            Entry entry = (Entry) iter.next();
+
+            Matcher matcher = (Matcher) entry.getValue();
+            String value = (String) m.get(entry.getKey());
+            if ((value == null) || !matcher.matches(value)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    public String toString() {
+        return attributes + " (" + pm.getName() + ")";
+    }
+
+    public Map getAttributes() {
+        return Collections.unmodifiableMap(attributes);
+    }
+
+    public PatternMatcher getPatternMatcher() {
+        return pm;
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/matcher/Matcher.java b/src/java/org/apache/ivy/plugins/matcher/Matcher.java
new file mode 100644
index 0000000..0a2cf77
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/matcher/Matcher.java
@@ -0,0 +1,44 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.matcher;
+
+/**
+ * An interface that defines a string matcher.
+ */
+public interface Matcher {
+
+    /**
+     * Check whether a given string is matched by this matcher.
+     * 
+     * @param input
+     *            the string to be matched. Cannot be null.
+     * @return true if the input string is matched, false otherwise.
+     */
+    public boolean matches(/* @NotNull */String input);
+
+    /**
+     * Return if the matcher will match *only* if the expression equals the input. <i> WARN: This is
+     * used only as a performance trick, to avoid scanning for things when you already know exactly
+     * what you want. In the install task where it used it avoid scanning the repository to list all
+     * modules to find that only one matches, and that it has the name requested. </i>
+     * 
+     * @return true if the matcher only matches when the expression is equals to the input, false
+     *         otherwise.
+     */
+    public boolean isExact();
+}
diff --git a/src/java/org/apache/ivy/plugins/matcher/MatcherHelper.java b/src/java/org/apache/ivy/plugins/matcher/MatcherHelper.java
new file mode 100644
index 0000000..69a007a
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/matcher/MatcherHelper.java
@@ -0,0 +1,68 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.matcher;
+
+import org.apache.ivy.core.module.id.ArtifactId;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+
+/**
+ * Set of helper methods to match ModuleId, ModuleRevisionId, ArtifactId
+ */
+public final class MatcherHelper {
+    // TODO this class might be better off as MatcherUtils in util package
+
+    private MatcherHelper() {
+    }
+
+    public static boolean matches(PatternMatcher m, String expression, String input) {
+        return m.getMatcher(expression).matches(input);
+    }
+
+    public static boolean matches(PatternMatcher m, ModuleId exp, ModuleId mid) {
+        return matches(m, exp.getOrganisation(), mid.getOrganisation())
+                && matches(m, exp.getName(), mid.getName());
+    }
+
+    public static boolean matches(PatternMatcher m, ModuleRevisionId exp, ModuleRevisionId mrid) {
+        return matches(m, exp.getOrganisation(), mrid.getOrganisation())
+                && matches(m, exp.getName(), mrid.getName())
+                && matches(m, exp.getRevision(), mrid.getRevision());
+    }
+
+    public static boolean matches(PatternMatcher m, ArtifactId exp, ArtifactId aid) {
+        return matches(m, exp.getModuleId(), aid.getModuleId())
+                && matches(m, exp.getName(), aid.getName())
+                && matches(m, exp.getExt(), aid.getExt())
+                && matches(m, exp.getType(), aid.getType());
+    }
+
+    public static boolean isExact(PatternMatcher m, ModuleRevisionId exp) {
+        return isExact(m, exp.getOrganisation()) && isExact(m, exp.getName())
+                && isExact(m, exp.getRevision());
+    }
+
+    // unused
+    public static boolean isExact(PatternMatcher m, ModuleId exp) {
+        return isExact(m, exp.getOrganisation()) && isExact(m, exp.getName());
+    }
+
+    public static boolean isExact(PatternMatcher m, String exp) {
+        return m.getMatcher(exp).isExact();
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/matcher/NoMatcher.java b/src/java/org/apache/ivy/plugins/matcher/NoMatcher.java
new file mode 100644
index 0000000..87b3c9c
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/matcher/NoMatcher.java
@@ -0,0 +1,41 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.matcher;
+
+/**
+ * A matcher that matches nothing.
+ */
+public final/* @Immutable */class NoMatcher implements Matcher {
+
+    public static final Matcher INSTANCE = new NoMatcher();
+
+    public NoMatcher() {
+    }
+
+    public boolean matches(String input) {
+        if (input == null) {
+            throw new NullPointerException();
+        }
+        return false;
+    }
+
+    public boolean isExact() {
+        return true;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/plugins/matcher/PatternMatcher.java b/src/java/org/apache/ivy/plugins/matcher/PatternMatcher.java
new file mode 100644
index 0000000..27b35eb
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/matcher/PatternMatcher.java
@@ -0,0 +1,72 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.matcher;
+
+/**
+ * Interface for a pattern matcher.
+ * <p/>
+ * The pattern matcher is the main abstraction regarding the matching of an expression.
+ * Implementation may vary depending on the expression syntax handling that is desired.
+ */
+public interface PatternMatcher {
+
+    /**
+     * 'exact' pattern matcher name
+     */
+    public static final String EXACT = "exact";
+
+    /**
+     * pattern matcher name 'regexp'
+     */
+    public static final String REGEXP = "regexp";
+
+    /**
+     * pattern matcher 'glob'
+     */
+    public static final String GLOB = "glob";
+
+    /**
+     * pattern matcher name 'exactOrRegexp'
+     */
+    public static final String EXACT_OR_REGEXP = "exactOrRegexp";
+
+    /**
+     * Any expression string: '*'
+     */
+    public static final String ANY_EXPRESSION = "*";
+
+    /**
+     * Return the matcher for the given expression.
+     * 
+     * @param expression
+     *            the expression to be matched. Cannot be null ?
+     * @return the matcher instance for the given expression. Never null.
+     */
+    public/* @NotNull */Matcher getMatcher(/* @NotNull */String expression);
+
+    /**
+     * return the name of this pattern matcher
+     * 
+     * @return the name of this pattern matcher. Never null.
+     * @see #EXACT
+     * @see #REGEXP
+     * @see #GLOB
+     * @see #EXACT_OR_REGEXP
+     */
+    public/* @NotNull */String getName();
+}
diff --git a/src/java/org/apache/ivy/plugins/matcher/RegexpPatternMatcher.java b/src/java/org/apache/ivy/plugins/matcher/RegexpPatternMatcher.java
new file mode 100644
index 0000000..832ab84
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/matcher/RegexpPatternMatcher.java
@@ -0,0 +1,90 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.matcher;
+
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+/**
+ * A pattern matcher matching input using regular expressions.
+ * 
+ * @see Pattern
+ */
+public final/* @Immutable */class RegexpPatternMatcher extends AbstractPatternMatcher {
+    public static final RegexpPatternMatcher INSTANCE = new RegexpPatternMatcher();
+
+    /*
+     * NOTE: Regexp compiler does ~200K compilation/s - If necessary look into using ThreadLocal
+     * Pattern to cut on useless object creation - If expression are reused over and over a LRU
+     * cache could make sense
+     */
+
+    public RegexpPatternMatcher() {
+        super(REGEXP);
+    }
+
+    protected Matcher newMatcher(String expression) {
+        return new RegexpMatcher(expression);
+    }
+
+    private static/* @Immutable */class RegexpMatcher implements Matcher {
+        private Pattern pattern;
+
+        private String expression;
+
+        private Boolean exact;
+
+        public RegexpMatcher(String expression) throws PatternSyntaxException {
+            if (expression == null) {
+                throw new NullPointerException();
+            }
+            this.expression = expression;
+            pattern = Pattern.compile(expression);
+        }
+
+        public boolean matches(String input) {
+            if (input == null) {
+                throw new NullPointerException();
+            }
+            return pattern.matcher(input).matches();
+        }
+
+        public boolean isExact() {
+            if (exact == null) {
+                exact = calculateExact();
+            }
+            return exact.booleanValue();
+        }
+
+        private Boolean calculateExact() {
+            Boolean result = Boolean.TRUE;
+
+            char[] expressionChars = expression.toCharArray();
+            for (int i = 0; i < expressionChars.length; i++) {
+                char ch = expressionChars[i];
+                if (!Character.isLetterOrDigit(ch) && !Character.isWhitespace(ch) && ('-' != ch)
+                        && ('_' != ch)) {
+                    result = Boolean.FALSE;
+                    break;
+                }
+            }
+
+            return result;
+        }
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/namespace/MRIDRule.java b/src/java/org/apache/ivy/plugins/namespace/MRIDRule.java
new file mode 100644
index 0000000..3e1714b
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/namespace/MRIDRule.java
@@ -0,0 +1,73 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.namespace;
+
+public class MRIDRule {
+    private String org;
+
+    private String module;
+
+    private String branch;
+
+    private String rev;
+
+    public MRIDRule(String org, String mod, String rev) {
+        this.org = org;
+        this.module = mod;
+        this.rev = rev;
+    }
+
+    public MRIDRule() {
+    }
+
+    public String getModule() {
+        return module;
+    }
+
+    public void setModule(String module) {
+        this.module = module;
+    }
+
+    public String getOrg() {
+        return org;
+    }
+
+    public void setOrg(String org) {
+        this.org = org;
+    }
+
+    public String getRev() {
+        return rev;
+    }
+
+    public void setRev(String rev) {
+        this.rev = rev;
+    }
+
+    public String toString() {
+        return "[ " + org + " " + module + (branch != null ? " " + branch : "") + " " + rev + " ]";
+    }
+
+    public String getBranch() {
+        return branch;
+    }
+
+    public void setBranch(String branch) {
+        this.branch = branch;
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/namespace/MRIDTransformationRule.java b/src/java/org/apache/ivy/plugins/namespace/MRIDTransformationRule.java
new file mode 100644
index 0000000..a6880c8
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/namespace/MRIDTransformationRule.java
@@ -0,0 +1,147 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.namespace;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.util.Message;
+
+public class MRIDTransformationRule implements NamespaceTransformer {
+    private static class MridRuleMatcher {
+        private static final String[] TYPES = new String[] {"o", "m", "b", "r"};
+
+        private Matcher[] matchers = new Matcher[TYPES.length];
+
+        public boolean match(MRIDRule src, ModuleRevisionId mrid) {
+            // CheckStyle:MagicNumber| OFF
+            matchers[0] = Pattern.compile(getPattern(src.getOrg())).matcher(mrid.getOrganisation());
+            if (!matchers[0].matches()) {
+                return false;
+            }
+            matchers[1] = Pattern.compile(getPattern(src.getModule())).matcher(mrid.getName());
+            if (!matchers[1].matches()) {
+                return false;
+            }
+            if (mrid.getBranch() == null) {
+                matchers[2] = null;
+            } else {
+                matchers[2] = Pattern.compile(getPattern(src.getBranch()))
+                        .matcher(mrid.getBranch());
+                if (!matchers[2].matches()) {
+                    return false;
+                }
+            }
+            matchers[3] = Pattern.compile(getPattern(src.getRev())).matcher(mrid.getRevision());
+            if (!matchers[3].matches()) {
+                return false;
+            }
+
+            return true;
+            // CheckStyle:MagicNumber| ON
+        }
+
+        public ModuleRevisionId apply(MRIDRule dest, ModuleRevisionId mrid) {
+            String org = applyRules(dest.getOrg(), "o");
+            String mod = applyRules(dest.getModule(), "m");
+            String branch = applyRules(dest.getBranch(), "b");
+            String rev = applyRules(dest.getRev(), "r");
+
+            return ModuleRevisionId.newInstance(org, mod, branch, rev,
+                mrid.getQualifiedExtraAttributes());
+        }
+
+        private String applyRules(String str, String type) {
+            for (int i = 0; i < TYPES.length; i++) {
+                str = applyTypeRule(str, TYPES[i], type, matchers[i]);
+            }
+            return str;
+        }
+
+        private String applyTypeRule(String rule, String type, String ruleType, Matcher m) {
+            if (m == null) {
+                return rule;
+            }
+            String res = rule == null ? "$" + ruleType + "0" : rule;
+            for (int i = 0; i < TYPES.length; i++) {
+                if (TYPES[i].equals(type)) {
+                    res = res.replaceAll("([^\\\\])\\$" + type, "$1\\$");
+                    res = res.replaceAll("^\\$" + type, "\\$");
+                } else {
+                    res = res.replaceAll("([^\\\\])\\$" + TYPES[i], "$1\\\\\\$" + TYPES[i]);
+                    res = res.replaceAll("^\\$" + TYPES[i], "\\\\\\$" + TYPES[i]);
+                }
+            }
+
+            StringBuffer sb = new StringBuffer();
+            m.reset();
+            m.find();
+            m.appendReplacement(sb, res);
+
+            String str = sb.toString();
+            // null rule not replaced, let it be null
+            if (rule == null && ("$" + ruleType + "0").equals(str)) {
+                return null;
+            }
+
+            return str;
+        }
+
+        private String getPattern(String p) {
+            return p == null ? ".*" : p;
+        }
+    }
+
+    private List src = new ArrayList();
+
+    private MRIDRule dest;
+
+    public void addSrc(MRIDRule src) {
+        this.src.add(src);
+    }
+
+    public void addDest(MRIDRule dest) {
+        if (this.dest != null) {
+            throw new IllegalArgumentException("only one dest is allowed per mapping");
+        }
+        this.dest = dest;
+    }
+
+    public ModuleRevisionId transform(ModuleRevisionId mrid) {
+        MridRuleMatcher matcher = new MridRuleMatcher();
+        for (Iterator iter = src.iterator(); iter.hasNext();) {
+            MRIDRule rule = (MRIDRule) iter.next();
+            if (matcher.match(rule, mrid)) {
+                ModuleRevisionId destMrid = matcher.apply(dest, mrid);
+                Message.debug("found matching namespace rule: " + rule + ". Applied " + dest
+                        + " on " + mrid + ". Transformed to " + destMrid);
+                return destMrid;
+            }
+        }
+        return mrid;
+    }
+
+    public boolean isIdentity() {
+        return false;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/plugins/namespace/NameSpaceHelper.java b/src/java/org/apache/ivy/plugins/namespace/NameSpaceHelper.java
new file mode 100644
index 0000000..2199286
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/namespace/NameSpaceHelper.java
@@ -0,0 +1,113 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.namespace;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DefaultArtifact;
+import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
+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.ArtifactId;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.report.MetadataArtifactDownloadReport;
+import org.apache.ivy.core.resolve.ResolvedModuleRevision;
+
+public final class NameSpaceHelper {
+    private NameSpaceHelper() {
+    }
+
+    public static DependencyDescriptor toSystem(DependencyDescriptor dd, Namespace ns) {
+        return DefaultDependencyDescriptor.transformInstance(dd, ns);
+    }
+
+    public static DependencyDescriptor transform(DependencyDescriptor dd, NamespaceTransformer t,
+            boolean fromSystem) {
+        return DefaultDependencyDescriptor.transformInstance(dd, t, fromSystem);
+    }
+
+    public static ModuleDescriptor toSystem(ModuleDescriptor md, Namespace ns) {
+        return DefaultModuleDescriptor.transformInstance(md, ns);
+    }
+
+    public static ResolvedModuleRevision toSystem(ResolvedModuleRevision rmr, Namespace ns) {
+        if (ns.getToSystemTransformer().isIdentity()) {
+            return rmr;
+        }
+        ModuleDescriptor md = toSystem(rmr.getDescriptor(), ns);
+        if (md.equals(rmr.getDescriptor())) {
+            return rmr;
+        }
+        return new ResolvedModuleRevision(rmr.getResolver(), rmr.getArtifactResolver(), md,
+                transform(rmr.getReport(), ns.getToSystemTransformer()), rmr.isForce());
+    }
+
+    public static Artifact transform(Artifact artifact, NamespaceTransformer t) {
+        if (t.isIdentity()) {
+            return artifact;
+        }
+        ModuleRevisionId mrid = t.transform(artifact.getModuleRevisionId());
+        if (artifact.getModuleRevisionId().equals(mrid)) {
+            return artifact;
+        }
+        return new DefaultArtifact(mrid, artifact.getPublicationDate(), artifact.getName(),
+                artifact.getType(), artifact.getExt(), artifact.getUrl(),
+                artifact.getQualifiedExtraAttributes());
+    }
+
+    public static MetadataArtifactDownloadReport transform(MetadataArtifactDownloadReport report,
+            NamespaceTransformer t) {
+        if (t.isIdentity()) {
+            return report;
+        }
+        MetadataArtifactDownloadReport madr = new MetadataArtifactDownloadReport(transform(
+            report.getArtifact(), t));
+        madr.setSearched(report.isSearched());
+        madr.setDownloadStatus(report.getDownloadStatus());
+        madr.setDownloadDetails(report.getDownloadDetails());
+        madr.setArtifactOrigin(report.getArtifactOrigin());
+        madr.setDownloadTimeMillis(report.getDownloadTimeMillis());
+        madr.setOriginalLocalFile(report.getOriginalLocalFile());
+        madr.setLocalFile(report.getLocalFile());
+        madr.setSize(report.getSize());
+        return madr;
+    }
+
+    public static ArtifactId transform(ArtifactId artifactId, NamespaceTransformer t) {
+        if (t.isIdentity()) {
+            return artifactId;
+        }
+        ModuleId mid = transform(artifactId.getModuleId(), t);
+        if (mid.equals(artifactId.getModuleId())) {
+            return artifactId;
+        }
+        return new ArtifactId(mid, artifactId.getName(), artifactId.getType(), artifactId.getExt());
+    }
+
+    public static ModuleId transform(ModuleId mid, NamespaceTransformer t) {
+        if (t.isIdentity()) {
+            return mid;
+        }
+        return t.transform(new ModuleRevisionId(mid, "")).getModuleId();
+    }
+
+    public static String transformOrganisation(String org, NamespaceTransformer t) {
+        return transform(new ModuleId(org, ""), t).getOrganisation();
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/namespace/Namespace.java b/src/java/org/apache/ivy/plugins/namespace/Namespace.java
new file mode 100644
index 0000000..43c61a9
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/namespace/Namespace.java
@@ -0,0 +1,109 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.namespace;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+
+public class Namespace {
+    public static final Namespace SYSTEM_NAMESPACE;
+    static {
+        SYSTEM_NAMESPACE = new Namespace();
+    }
+
+    private List/* <NamespaceRule> */rules = new ArrayList();
+
+    private String name;
+
+    private boolean chainRules = false;
+
+    private NamespaceTransformer fromSystemTransformer = new NamespaceTransformer() {
+        public ModuleRevisionId transform(ModuleRevisionId mrid) {
+            if (mrid == null) {
+                return null;
+            }
+            for (Iterator iter = rules.iterator(); iter.hasNext();) {
+                NamespaceRule rule = (NamespaceRule) iter.next();
+                ModuleRevisionId nmrid = rule.getFromSystem().transform(mrid);
+                if (chainRules) {
+                    mrid = nmrid;
+                } else if (!nmrid.equals(mrid)) {
+                    return nmrid;
+                }
+            }
+            return mrid;
+        }
+
+        public boolean isIdentity() {
+            return rules.isEmpty();
+        }
+    };
+
+    private NamespaceTransformer toSystemTransformer = new NamespaceTransformer() {
+        public ModuleRevisionId transform(ModuleRevisionId mrid) {
+            if (mrid == null) {
+                return null;
+            }
+            for (Iterator iter = rules.iterator(); iter.hasNext();) {
+                NamespaceRule rule = (NamespaceRule) iter.next();
+                ModuleRevisionId nmrid = rule.getToSystem().transform(mrid);
+                if (chainRules) {
+                    mrid = nmrid;
+                } else if (!nmrid.equals(mrid)) {
+                    return nmrid;
+                }
+            }
+            return mrid;
+        }
+
+        public boolean isIdentity() {
+            return rules.isEmpty();
+        }
+    };
+
+    public void addRule(NamespaceRule rule) {
+        rules.add(rule);
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public NamespaceTransformer getFromSystemTransformer() {
+        return fromSystemTransformer;
+    }
+
+    public NamespaceTransformer getToSystemTransformer() {
+        return toSystemTransformer;
+    }
+
+    public boolean isChainrules() {
+        return chainRules;
+    }
+
+    public void setChainrules(boolean chainRules) {
+        this.chainRules = chainRules;
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/namespace/NamespaceRule.java b/src/java/org/apache/ivy/plugins/namespace/NamespaceRule.java
new file mode 100644
index 0000000..d47b897
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/namespace/NamespaceRule.java
@@ -0,0 +1,66 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.namespace;
+
+public class NamespaceRule {
+    private String name;
+
+    private String description;
+
+    private MRIDTransformationRule fromSystem;
+
+    private MRIDTransformationRule toSystem;
+
+    public MRIDTransformationRule getFromSystem() {
+        return fromSystem;
+    }
+
+    public void addFromsystem(MRIDTransformationRule fromSystem) {
+        if (this.fromSystem != null) {
+            throw new IllegalArgumentException("only one fromsystem is allowed per rule");
+        }
+        this.fromSystem = fromSystem;
+    }
+
+    public MRIDTransformationRule getToSystem() {
+        return toSystem;
+    }
+
+    public void addTosystem(MRIDTransformationRule toSystem) {
+        if (this.toSystem != null) {
+            throw new IllegalArgumentException("only one tosystem is allowed per rule");
+        }
+        this.toSystem = toSystem;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/namespace/NamespaceTransformer.java b/src/java/org/apache/ivy/plugins/namespace/NamespaceTransformer.java
new file mode 100644
index 0000000..75148b2
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/namespace/NamespaceTransformer.java
@@ -0,0 +1,26 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.namespace;
+
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+
+public interface NamespaceTransformer {
+    public ModuleRevisionId transform(ModuleRevisionId mrid);
+
+    public boolean isIdentity();
+}
diff --git a/src/java/org/apache/ivy/plugins/parser/AbstractModuleDescriptorParser.java b/src/java/org/apache/ivy/plugins/parser/AbstractModuleDescriptorParser.java
new file mode 100644
index 0000000..25ec67c
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/parser/AbstractModuleDescriptorParser.java
@@ -0,0 +1,366 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.parser;
+
+import java.io.IOException;
+import java.net.URL;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.Configuration;
+import org.apache.ivy.core.module.descriptor.DefaultArtifact;
+import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
+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.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.plugins.repository.ResourceHelper;
+import org.apache.ivy.plugins.repository.url.URLResource;
+import org.apache.ivy.util.Message;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.helpers.DefaultHandler;
+
+public abstract class AbstractModuleDescriptorParser implements ModuleDescriptorParser {
+    public ModuleDescriptor parseDescriptor(ParserSettings ivySettings, URL descriptorURL,
+            boolean validate) throws ParseException, IOException {
+        return parseDescriptor(ivySettings, descriptorURL, new URLResource(descriptorURL), validate);
+    }
+
+    public String getType() {
+        return "ivy";
+    }
+
+    public Artifact getMetadataArtifact(ModuleRevisionId mrid, Resource res) {
+        return DefaultArtifact.newIvyArtifact(mrid, new Date(res.getLastModified()));
+    }
+
+    protected abstract static class AbstractParser extends DefaultHandler {
+        private static final String DEFAULT_CONF_MAPPING = "*->*";
+
+        private String defaultConf; // used only as defaultconf, not used for
+
+        // guesssing right side part of a mapping
+        private String defaultConfMapping; // same as default conf but is used
+
+        // for guesssing right side part of a mapping
+        private DefaultDependencyDescriptor defaultConfMappingDescriptor;
+
+        private Resource res;
+
+        private List errors = new ArrayList();
+
+        private DefaultModuleDescriptor md;
+
+        private ModuleDescriptorParser parser;
+
+        protected AbstractParser(ModuleDescriptorParser parser) {
+            this.parser = parser;
+        }
+
+        public ModuleDescriptorParser getModuleDescriptorParser() {
+            return parser;
+        }
+
+        protected void checkErrors() throws ParseException {
+            if (!errors.isEmpty()) {
+                throw new ParseException(errors.toString(), 0);
+            }
+        }
+
+        public void setResource(Resource res) {
+            this.res = res; // used for log and date only
+            md = new DefaultModuleDescriptor(parser, res);
+            md.setLastModified(ResourceHelper.getLastModifiedOrDefault(res));
+        }
+
+        protected Resource getResource() {
+            return res;
+        }
+
+        protected String getDefaultConfMapping() {
+            return defaultConfMapping;
+        }
+
+        protected void setDefaultConfMapping(String defaultConf) {
+            defaultConfMapping = defaultConf;
+            getMd().setDefaultConfMapping(defaultConf);
+        }
+
+        protected void parseDepsConfs(String confs, DefaultDependencyDescriptor dd) {
+            parseDepsConfs(confs, dd, defaultConfMapping != null);
+        }
+
+        protected void parseDepsConfs(String confs, DefaultDependencyDescriptor dd,
+                boolean useDefaultMappingToGuessRightOperande) {
+            parseDepsConfs(confs, dd, useDefaultMappingToGuessRightOperande, true);
+        }
+
+        protected void parseDepsConfs(String confs, DefaultDependencyDescriptor dd,
+                boolean useDefaultMappingToGuessRightOperande, boolean evaluateConditions) {
+            if (confs == null) {
+                return;
+            }
+
+            String[] conf = confs.split(";");
+            parseDepsConfs(conf, dd, useDefaultMappingToGuessRightOperande, evaluateConditions);
+        }
+
+        protected void parseDepsConfs(String[] conf, DefaultDependencyDescriptor dd,
+                boolean useDefaultMappingToGuessRightOperande) {
+            parseDepsConfs(conf, dd, useDefaultMappingToGuessRightOperande, true);
+        }
+
+        protected void parseDepsConfs(String[] conf, DefaultDependencyDescriptor dd,
+                boolean useDefaultMappingToGuessRightOperande, boolean evaluateConditions) {
+            replaceConfigurationWildcards(md);
+            for (int i = 0; i < conf.length; i++) {
+                String[] ops = conf[i].split("->");
+                if (ops.length == 1) {
+                    String[] modConfs = ops[0].split(",");
+                    if (!useDefaultMappingToGuessRightOperande) {
+                        for (int j = 0; j < modConfs.length; j++) {
+                            dd.addDependencyConfiguration(modConfs[j].trim(), modConfs[j].trim());
+                        }
+                    } else {
+                        for (int j = 0; j < modConfs.length; j++) {
+                            String[] depConfs = getDefaultConfMappingDescriptor()
+                                    .getDependencyConfigurations(modConfs[j]);
+                            if (depConfs.length > 0) {
+                                for (int k = 0; k < depConfs.length; k++) {
+                                    String mappedDependency = evaluateConditions ? evaluateCondition(
+                                        depConfs[k].trim(), dd) : depConfs[k].trim();
+                                    if (mappedDependency != null) {
+                                        dd.addDependencyConfiguration(modConfs[j].trim(),
+                                            mappedDependency);
+                                    }
+                                }
+                            } else {
+                                // no default mapping found for this configuration, map
+                                // configuration to itself
+                                dd.addDependencyConfiguration(modConfs[j].trim(),
+                                    modConfs[j].trim());
+                            }
+                        }
+                    }
+                } else if (ops.length == 2) {
+                    String[] modConfs = ops[0].split(",");
+                    String[] depConfs = ops[1].split(",");
+                    for (int j = 0; j < modConfs.length; j++) {
+                        for (int k = 0; k < depConfs.length; k++) {
+                            String mappedDependency = evaluateConditions ? evaluateCondition(
+                                depConfs[k].trim(), dd) : depConfs[k].trim();
+                            if (mappedDependency != null) {
+                                dd.addDependencyConfiguration(modConfs[j].trim(), mappedDependency);
+                            }
+                        }
+                    }
+                } else {
+                    addError("invalid conf " + conf[i] + " for " + dd);
+                }
+            }
+
+            if (md.isMappingOverride()) {
+                addExtendingConfigurations(conf, dd, useDefaultMappingToGuessRightOperande);
+            }
+        }
+
+        /**
+         * Evaluate the optional condition in the given configuration, like "[org=MYORG]confX". If
+         * the condition evaluates to true, the configuration is returned, if the condition
+         * evaluatate to false, null is returned. If there are no conditions, the configuration
+         * itself is returned.
+         * 
+         * @param conf
+         *            the configuration to evaluate
+         * @param dd
+         *            the dependencydescriptor to which the configuration will be added
+         * @return the evaluated condition
+         */
+        private String evaluateCondition(String conf, DefaultDependencyDescriptor dd) {
+            if (conf.charAt(0) != '[') {
+                return conf;
+            }
+
+            int endConditionIndex = conf.indexOf(']');
+            if (endConditionIndex == -1) {
+                addError("invalid conf " + conf + " for " + dd);
+                return null;
+            }
+
+            String condition = conf.substring(1, endConditionIndex);
+
+            int notEqualIndex = condition.indexOf("!=");
+            if (notEqualIndex == -1) {
+                int equalIndex = condition.indexOf('=');
+                if (equalIndex == -1) {
+                    addError("invalid conf " + conf + " for " + dd.getDependencyRevisionId());
+                    return null;
+                }
+
+                String leftOp = condition.substring(0, equalIndex).trim();
+                String rightOp = condition.substring(equalIndex + 1).trim();
+
+                // allow organisation synonyms, like 'org' or 'organization'
+                if (leftOp.equals("org") || leftOp.equals("organization")) {
+                    leftOp = "organisation";
+                }
+
+                String attrValue = dd.getAttribute(leftOp);
+                if (!rightOp.equals(attrValue)) {
+                    return null;
+                }
+            } else {
+                String leftOp = condition.substring(0, notEqualIndex).trim();
+                String rightOp = condition.substring(notEqualIndex + 2).trim();
+
+                // allow organisation synonyms, like 'org' or 'organization'
+                if (leftOp.equals("org") || leftOp.equals("organization")) {
+                    leftOp = "organisation";
+                }
+
+                String attrValue = dd.getAttribute(leftOp);
+                if (rightOp.equals(attrValue)) {
+                    return null;
+                }
+            }
+
+            return conf.substring(endConditionIndex + 1);
+        }
+
+        private void addExtendingConfigurations(String[] confs, DefaultDependencyDescriptor dd,
+                boolean useDefaultMappingToGuessRightOperande) {
+            for (int i = 0; i < confs.length; i++) {
+                addExtendingConfigurations(confs[i], dd, useDefaultMappingToGuessRightOperande);
+            }
+        }
+
+        private void addExtendingConfigurations(String conf, DefaultDependencyDescriptor dd,
+                boolean useDefaultMappingToGuessRightOperande) {
+            Set configsToAdd = new HashSet();
+            Configuration[] configs = md.getConfigurations();
+            for (int i = 0; i < configs.length; i++) {
+                String[] ext = configs[i].getExtends();
+                for (int j = 0; j < ext.length; j++) {
+                    if (conf.equals(ext[j])) {
+                        String configName = configs[i].getName();
+                        configsToAdd.add(configName);
+                        addExtendingConfigurations(configName, dd,
+                            useDefaultMappingToGuessRightOperande);
+                    }
+                }
+            }
+
+            String[] confs = (String[]) configsToAdd.toArray(new String[configsToAdd.size()]);
+            parseDepsConfs(confs, dd, useDefaultMappingToGuessRightOperande);
+        }
+
+        protected DependencyDescriptor getDefaultConfMappingDescriptor() {
+            if (defaultConfMappingDescriptor == null) {
+                defaultConfMappingDescriptor = new DefaultDependencyDescriptor(
+                        ModuleRevisionId.newInstance("", "", ""), false);
+                parseDepsConfs(defaultConfMapping, defaultConfMappingDescriptor, false, false);
+            }
+            return defaultConfMappingDescriptor;
+        }
+
+        protected void addError(String msg) {
+            if (res != null) {
+                errors.add(msg + " in " + res + "\n");
+            } else {
+                errors.add(msg + "\n");
+            }
+        }
+
+        public void warning(SAXParseException ex) {
+            Message.warn("xml parsing: " + getLocationString(ex) + ": " + ex.getMessage());
+        }
+
+        public void error(SAXParseException ex) {
+            addError("xml parsing: " + getLocationString(ex) + ": " + ex.getMessage());
+        }
+
+        public void fatalError(SAXParseException ex) throws SAXException {
+            addError("[Fatal Error] " + getLocationString(ex) + ": " + ex.getMessage());
+        }
+
+        /** Returns a string of the location. */
+        private String getLocationString(SAXParseException ex) {
+            StringBuffer str = new StringBuffer();
+
+            String systemId = ex.getSystemId();
+            if (systemId != null) {
+                int index = systemId.lastIndexOf('/');
+                if (index != -1) {
+                    systemId = systemId.substring(index + 1);
+                }
+                str.append(systemId);
+            } else if (getResource() != null) {
+                str.append(getResource().toString());
+            }
+            str.append(':');
+            str.append(ex.getLineNumber());
+            str.append(':');
+            str.append(ex.getColumnNumber());
+
+            return str.toString();
+
+        } // getLocationString(SAXParseException):String
+
+        protected String getDefaultConf() {
+            return defaultConf != null ? defaultConf
+                    : (defaultConfMapping != null ? defaultConfMapping : DEFAULT_CONF_MAPPING);
+        }
+
+        protected void setDefaultConf(String defaultConf) {
+            this.defaultConf = defaultConf;
+            getMd().setDefaultConf(defaultConf);
+        }
+
+        public ModuleDescriptor getModuleDescriptor() throws ParseException {
+            checkErrors();
+            return md;
+        }
+
+        protected Date getDefaultPubDate() {
+            return new Date(md.getLastModified());
+        }
+
+        private void replaceConfigurationWildcards(ModuleDescriptor md) {
+            Configuration[] configs = md.getConfigurations();
+            for (int i = 0; i < configs.length; i++) {
+                configs[i].replaceWildcards(md);
+            }
+        }
+
+        protected void setMd(DefaultModuleDescriptor md) {
+            this.md = md;
+        }
+
+        protected DefaultModuleDescriptor getMd() {
+            return md;
+        }
+    }
+
+}
diff --git a/src/java/org/apache/ivy/plugins/parser/ModuleDescriptorParser.java b/src/java/org/apache/ivy/plugins/parser/ModuleDescriptorParser.java
new file mode 100644
index 0000000..0971f33
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/parser/ModuleDescriptorParser.java
@@ -0,0 +1,68 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.parser;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.text.ParseException;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.plugins.repository.Resource;
+
+public interface ModuleDescriptorParser {
+    public ModuleDescriptor parseDescriptor(ParserSettings ivySettings, URL descriptorURL,
+            boolean validate) throws ParseException, IOException;
+
+    public ModuleDescriptor parseDescriptor(ParserSettings ivySettings, URL descriptorURL,
+            Resource res, boolean validate) throws ParseException, IOException;
+
+    /**
+     * Convert a module descriptor to an ivy file. This method MUST close the given input stream
+     * when job is finished
+     * 
+     * @param is
+     *            input stream with opened on original module descriptor resource
+     */
+    public void toIvyFile(InputStream is, Resource res, File destFile, ModuleDescriptor md)
+            throws ParseException, IOException;
+
+    public boolean accept(Resource res);
+
+    /**
+     * Return the 'type' of module artifacts this parser is parsing
+     * 
+     * @return the 'type' of module artifacts this parser is parsing
+     */
+    public String getType();
+
+    /**
+     * Returns the module metadata artifact corresponding to the given module revision id that this
+     * parser parses
+     * 
+     * @param res
+     *            the resource for which the module artifact should be returned
+     * @param mrid
+     *            the module revision id for which the module artifact should be returned
+     * @return the module artifact corresponding to the given mrid and resource
+     */
+    public Artifact getMetadataArtifact(ModuleRevisionId mrid, Resource res);
+}
diff --git a/src/java/org/apache/ivy/plugins/parser/ModuleDescriptorParserRegistry.java b/src/java/org/apache/ivy/plugins/parser/ModuleDescriptorParserRegistry.java
new file mode 100644
index 0000000..f2150d2
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/parser/ModuleDescriptorParserRegistry.java
@@ -0,0 +1,104 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.parser;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.text.ParseException;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.osgi.core.OSGiManifestParser;
+import org.apache.ivy.plugins.parser.m2.PomModuleDescriptorParser;
+import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorParser;
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.util.Message;
+
+public final class ModuleDescriptorParserRegistry extends AbstractModuleDescriptorParser {
+    private static final ModuleDescriptorParserRegistry INSTANCE = new ModuleDescriptorParserRegistry();
+
+    public static ModuleDescriptorParserRegistry getInstance() {
+        return INSTANCE;
+    }
+
+    private List parsers = new LinkedList();
+
+    private ModuleDescriptorParserRegistry() {
+        parsers.add(PomModuleDescriptorParser.getInstance());
+        parsers.add(OSGiManifestParser.getInstance());
+        parsers.add(XmlModuleDescriptorParser.getInstance());
+    }
+
+    /**
+     * Adds a the given parser to this registry.
+     * 
+     * @param parser
+     *            the parser to add
+     */
+    public void addParser(ModuleDescriptorParser parser) {
+        /*
+         * The parser is added in the front of the list of parsers. This is necessary because the
+         * XmlModuleDescriptorParser accepts all resources!
+         */
+        parsers.add(0, parser);
+    }
+
+    public ModuleDescriptorParser[] getParsers() {
+        return (ModuleDescriptorParser[]) parsers
+                .toArray(new ModuleDescriptorParser[parsers.size()]);
+    }
+
+    public ModuleDescriptorParser getParser(Resource res) {
+        for (Iterator iter = parsers.iterator(); iter.hasNext();) {
+            ModuleDescriptorParser parser = (ModuleDescriptorParser) iter.next();
+            if (parser.accept(res)) {
+                return parser;
+            }
+        }
+        return null;
+    }
+
+    public ModuleDescriptor parseDescriptor(ParserSettings settings, URL descriptorURL,
+            Resource res, boolean validate) throws ParseException, IOException {
+        ModuleDescriptorParser parser = getParser(res);
+        if (parser == null) {
+            Message.warn("no module descriptor parser found for " + res);
+            return null;
+        }
+        return parser.parseDescriptor(settings, descriptorURL, res, validate);
+    }
+
+    public boolean accept(Resource res) {
+        return getParser(res) != null;
+    }
+
+    public void toIvyFile(InputStream is, Resource res, File destFile, ModuleDescriptor md)
+            throws ParseException, IOException {
+        ModuleDescriptorParser parser = getParser(res);
+        if (parser == null) {
+            Message.warn("no module descriptor parser found for " + res);
+        } else {
+            parser.toIvyFile(is, res, destFile, md);
+        }
+    }
+
+}
diff --git a/src/java/org/apache/ivy/plugins/parser/ParserSettings.java b/src/java/org/apache/ivy/plugins/parser/ParserSettings.java
new file mode 100644
index 0000000..e306421
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/parser/ParserSettings.java
@@ -0,0 +1,62 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.parser;
+
+import java.io.File;
+import java.util.Map;
+
+import org.apache.ivy.core.RelativeUrlResolver;
+import org.apache.ivy.core.cache.ResolutionCacheManager;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.module.status.StatusManager;
+import org.apache.ivy.plugins.conflict.ConflictManager;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+import org.apache.ivy.plugins.namespace.Namespace;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+
+public interface ParserSettings {
+
+    String substitute(String value);
+
+    Map/* <String, String> */substitute(Map/* <String, String> */strings);
+
+    ResolutionCacheManager getResolutionCacheManager();
+
+    ConflictManager getConflictManager(String name);
+
+    PatternMatcher getMatcher(String matcherName);
+
+    Namespace getNamespace(String namespace);
+
+    StatusManager getStatusManager();
+
+    RelativeUrlResolver getRelativeUrlResolver();
+
+    DependencyResolver getResolver(ModuleRevisionId mRevId);
+
+    File resolveFile(String filename);
+
+    String getDefaultBranch(ModuleId moduleId);
+
+    /**
+     * Returns the namespace context in which the current descriptor is parsed.
+     */
+    Namespace getContextNamespace();
+
+}
diff --git a/src/java/org/apache/ivy/plugins/parser/m2/DefaultPomDependencyMgt.java b/src/java/org/apache/ivy/plugins/parser/m2/DefaultPomDependencyMgt.java
new file mode 100644
index 0000000..5e05f49
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/parser/m2/DefaultPomDependencyMgt.java
@@ -0,0 +1,61 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.parser.m2;
+
+import java.util.List;
+
+public class DefaultPomDependencyMgt implements PomDependencyMgt {
+    private String groupId;
+
+    private String artifactId;
+
+    private String version;
+
+    private String scope;
+
+    private List /* <ModuleId> */excludedModules;
+
+    public DefaultPomDependencyMgt(String groupId, String artifactId, String version, String scope,
+            List /* <ModuleId> */excludedModules) {
+        this.groupId = groupId;
+        this.artifactId = artifactId;
+        this.version = version;
+        this.scope = scope;
+        this.excludedModules = excludedModules;
+    }
+
+    public String getScope() {
+        return scope;
+    }
+
+    public String getGroupId() {
+        return groupId;
+    }
+
+    public String getArtifactId() {
+        return artifactId;
+    }
+
+    public String getVersion() {
+        return version;
+    }
+
+    public List /* <ModuleId> */getExcludedModules() {
+        return excludedModules;
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/parser/m2/PomDependencyMgt.java b/src/java/org/apache/ivy/plugins/parser/m2/PomDependencyMgt.java
new file mode 100644
index 0000000..6b6b0af
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/parser/m2/PomDependencyMgt.java
@@ -0,0 +1,33 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.parser.m2;
+
+import java.util.List;
+
+public interface PomDependencyMgt {
+
+    public abstract String getGroupId();
+
+    public abstract String getArtifactId();
+
+    public abstract String getVersion();
+
+    public abstract String getScope();
+
+    public List /* <ModuleId> */getExcludedModules();
+}
\ No newline at end of file
diff --git a/src/java/org/apache/ivy/plugins/parser/m2/PomModuleDescriptorBuilder.java b/src/java/org/apache/ivy/plugins/parser/m2/PomModuleDescriptorBuilder.java
new file mode 100644
index 0000000..40a007a
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/parser/m2/PomModuleDescriptorBuilder.java
@@ -0,0 +1,736 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.parser.m2;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.ivy.Ivy;
+import org.apache.ivy.core.cache.ArtifactOrigin;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.Configuration;
+import org.apache.ivy.core.module.descriptor.Configuration.Visibility;
+import org.apache.ivy.core.module.descriptor.DefaultArtifact;
+import org.apache.ivy.core.module.descriptor.DefaultDependencyArtifactDescriptor;
+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.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.ExtraInfoHolder;
+import org.apache.ivy.core.module.descriptor.License;
+import org.apache.ivy.core.module.descriptor.MDArtifact;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.descriptor.OverrideDependencyDescriptorMediator;
+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.apache.ivy.plugins.parser.ModuleDescriptorParser;
+import org.apache.ivy.plugins.parser.ParserSettings;
+import org.apache.ivy.plugins.parser.m2.PomReader.PomDependencyData;
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.apache.ivy.util.Message;
+
+/**
+ * Build a module descriptor. This class handle the complexity of the structure of an ivy
+ * ModuleDescriptor and isolate the PomModuleDescriptorParser from it.
+ */
+public class PomModuleDescriptorBuilder {
+
+    private static final int DEPENDENCY_MANAGEMENT_KEY_PARTS_COUNT = 4;
+
+    public static final Configuration[] MAVEN2_CONFIGURATIONS = new Configuration[] {
+            new Configuration("default", Visibility.PUBLIC,
+                    "runtime dependencies and master artifact can be used with this conf",
+                    new String[] {"runtime", "master"}, true, null),
+            new Configuration("master", Visibility.PUBLIC,
+                    "contains only the artifact published by this module itself, "
+                            + "with no transitive dependencies", new String[0], true, null),
+            new Configuration("compile", Visibility.PUBLIC,
+                    "this is the default scope, used if none is specified. "
+                            + "Compile dependencies are available in all classpaths.",
+                    new String[0], true, null),
+            new Configuration(
+                    "provided",
+                    Visibility.PUBLIC,
+                    "this is much like compile, but indicates you expect the JDK or a container "
+                            + "to provide it. "
+                            + "It is only available on the compilation classpath, and is not transitive.",
+                    new String[0], true, null),
+            new Configuration("runtime", Visibility.PUBLIC,
+                    "this scope indicates that the dependency is not required for compilation, "
+                            + "but is for execution. It is in the runtime and test classpaths, "
+                            + "but not the compile classpath.", new String[] {"compile"}, true,
+                    null),
+            new Configuration(
+                    "test",
+                    Visibility.PRIVATE,
+                    "this scope indicates that the dependency is not required for normal use of "
+                            + "the application, and is only available for the test compilation and "
+                            + "execution phases.", new String[] {"runtime"}, true, null),
+            new Configuration(
+                    "system",
+                    Visibility.PUBLIC,
+                    "this scope is similar to provided except that you have to provide the JAR "
+                            + "which contains it explicitly. The artifact is always available and is not "
+                            + "looked up in a repository.", new String[0], true, null),
+            new Configuration("sources", Visibility.PUBLIC,
+                    "this configuration contains the source artifact of this module, if any.",
+                    new String[0], true, null),
+            new Configuration("javadoc", Visibility.PUBLIC,
+                    "this configuration contains the javadoc artifact of this module, if any.",
+                    new String[0], true, null),
+            new Configuration("optional", Visibility.PUBLIC, "contains all optional dependencies",
+                    new String[0], true, null)};
+
+    static final Map MAVEN2_CONF_MAPPING = new HashMap();
+
+    private static final String DEPENDENCY_MANAGEMENT = "m:dependency.management";
+
+    private static final String PROPERTIES = "m:properties";
+
+    private static final String EXTRA_INFO_DELIMITER = "__";
+
+    private static final Collection/* <String> */JAR_PACKAGINGS = Arrays.asList(new String[] {
+            "ejb", "bundle", "maven-plugin", "eclipse-plugin", "jbi-component",
+            "jbi-shared-library", "orbit", "hk2-jar"});
+
+    static interface ConfMapper {
+        public void addMappingConfs(DefaultDependencyDescriptor dd, boolean isOptional);
+    }
+
+    static {
+        MAVEN2_CONF_MAPPING.put("compile", new ConfMapper() {
+            public void addMappingConfs(DefaultDependencyDescriptor dd, boolean isOptional) {
+                if (isOptional) {
+                    dd.addDependencyConfiguration("optional", "compile(*)");
+                    // dd.addDependencyConfiguration("optional", "provided(*)");
+                    dd.addDependencyConfiguration("optional", "master(*)");
+
+                } else {
+                    dd.addDependencyConfiguration("compile", "compile(*)");
+                    // dd.addDependencyConfiguration("compile", "provided(*)");
+                    dd.addDependencyConfiguration("compile", "master(*)");
+                    dd.addDependencyConfiguration("runtime", "runtime(*)");
+                }
+            }
+        });
+        MAVEN2_CONF_MAPPING.put("provided", new ConfMapper() {
+            public void addMappingConfs(DefaultDependencyDescriptor dd, boolean isOptional) {
+                if (isOptional) {
+                    dd.addDependencyConfiguration("optional", "compile(*)");
+                    dd.addDependencyConfiguration("optional", "provided(*)");
+                    dd.addDependencyConfiguration("optional", "runtime(*)");
+                    dd.addDependencyConfiguration("optional", "master(*)");
+                } else {
+                    dd.addDependencyConfiguration("provided", "compile(*)");
+                    dd.addDependencyConfiguration("provided", "provided(*)");
+                    dd.addDependencyConfiguration("provided", "runtime(*)");
+                    dd.addDependencyConfiguration("provided", "master(*)");
+                }
+            }
+        });
+        MAVEN2_CONF_MAPPING.put("runtime", new ConfMapper() {
+            public void addMappingConfs(DefaultDependencyDescriptor dd, boolean isOptional) {
+                if (isOptional) {
+                    dd.addDependencyConfiguration("optional", "compile(*)");
+                    dd.addDependencyConfiguration("optional", "provided(*)");
+                    dd.addDependencyConfiguration("optional", "master(*)");
+
+                } else {
+                    dd.addDependencyConfiguration("runtime", "compile(*)");
+                    dd.addDependencyConfiguration("runtime", "runtime(*)");
+                    dd.addDependencyConfiguration("runtime", "master(*)");
+                }
+            }
+        });
+        MAVEN2_CONF_MAPPING.put("test", new ConfMapper() {
+            public void addMappingConfs(DefaultDependencyDescriptor dd, boolean isOptional) {
+                // optional doesn't make sense in the test scope
+                dd.addDependencyConfiguration("test", "runtime(*)");
+                dd.addDependencyConfiguration("test", "master(*)");
+            }
+        });
+        MAVEN2_CONF_MAPPING.put("system", new ConfMapper() {
+            public void addMappingConfs(DefaultDependencyDescriptor dd, boolean isOptional) {
+                // optional doesn't make sense in the system scope
+                dd.addDependencyConfiguration("system", "master(*)");
+            }
+        });
+    }
+
+    private final PomModuleDescriptor ivyModuleDescriptor;
+
+    private ModuleRevisionId mrid;
+
+    private DefaultArtifact mainArtifact;
+
+    private ParserSettings parserSettings;
+
+    private static final String WRONG_NUMBER_OF_PARTS_MSG = "what seemed to be a dependency "
+            + "management extra info exclusion had the wrong number of parts (should have 2) ";
+
+    public PomModuleDescriptorBuilder(ModuleDescriptorParser parser, Resource res,
+            ParserSettings ivySettings) {
+        ivyModuleDescriptor = new PomModuleDescriptor(parser, res);
+        ivyModuleDescriptor.setResolvedPublicationDate(new Date(res.getLastModified()));
+        for (int i = 0; i < MAVEN2_CONFIGURATIONS.length; i++) {
+            ivyModuleDescriptor.addConfiguration(MAVEN2_CONFIGURATIONS[i]);
+        }
+        ivyModuleDescriptor.setMappingOverride(true);
+        ivyModuleDescriptor.addExtraAttributeNamespace("m", Ivy.getIvyHomeURL() + "maven");
+        parserSettings = ivySettings;
+    }
+
+    public ModuleDescriptor getModuleDescriptor() {
+        return ivyModuleDescriptor;
+    }
+
+    public void setModuleRevId(String groupId, String artifactId, String version) {
+        mrid = ModuleRevisionId.newInstance(groupId, artifactId, version);
+        ivyModuleDescriptor.setModuleRevisionId(mrid);
+
+        if ((version == null) || version.endsWith("SNAPSHOT")) {
+            ivyModuleDescriptor.setStatus("integration");
+        } else {
+            ivyModuleDescriptor.setStatus("release");
+        }
+    }
+
+    public void setHomePage(String homePage) {
+        ivyModuleDescriptor.setHomePage(homePage);
+    }
+
+    public void setDescription(String description) {
+        ivyModuleDescriptor.setDescription(description);
+    }
+
+    public void setLicenses(License[] licenses) {
+        for (int i = 0; i < licenses.length; i++) {
+            ivyModuleDescriptor.addLicense(licenses[i]);
+        }
+    }
+
+    public void addMainArtifact(String artifactId, String packaging) {
+        String ext;
+
+        /*
+         * TODO: we should make packaging to ext mapping configurable, since it's not possible to
+         * cover all cases.
+         */
+        if ("pom".equals(packaging)) {
+            // no artifact defined! Add the default artifact if it exist.
+            DependencyResolver resolver = parserSettings.getResolver(mrid);
+
+            if (resolver != null) {
+                DefaultArtifact artifact = new DefaultArtifact(mrid, new Date(), artifactId, "jar",
+                        "jar");
+                ArtifactOrigin artifactOrigin = resolver.locate(artifact);
+
+                if (!ArtifactOrigin.isUnknown(artifactOrigin)) {
+                    mainArtifact = artifact;
+                    ivyModuleDescriptor.addArtifact("master", mainArtifact);
+                }
+            }
+
+            return;
+        } else if (JAR_PACKAGINGS.contains(packaging)) {
+            ext = "jar";
+        } else if ("pear".equals(packaging)) {
+            ext = "phar";
+        } else {
+            ext = packaging;
+        }
+
+        mainArtifact = new DefaultArtifact(mrid, new Date(), artifactId, packaging, ext);
+        ivyModuleDescriptor.addArtifact("master", mainArtifact);
+    }
+
+    public void addDependency(Resource res, PomDependencyData dep) {
+        String scope = dep.getScope();
+        if ((scope != null) && (scope.length() > 0) && !MAVEN2_CONF_MAPPING.containsKey(scope)) {
+            // unknown scope, defaulting to 'compile'
+            scope = "compile";
+        }
+
+        String version = dep.getVersion();
+        version = (version == null || version.length() == 0) ? getDefaultVersion(dep) : version;
+        ModuleRevisionId moduleRevId = ModuleRevisionId.newInstance(dep.getGroupId(),
+            dep.getArtifactId(), version);
+
+        // Some POMs depend on theirselfves, don't add this dependency: Ivy doesn't allow this!
+        // Example: https://repo1.maven.org/maven2/net/jini/jsk-platform/2.1/jsk-platform-2.1.pom
+        ModuleRevisionId mRevId = ivyModuleDescriptor.getModuleRevisionId();
+        if ((mRevId != null) && mRevId.getModuleId().equals(moduleRevId.getModuleId())) {
+            return;
+        }
+
+        DefaultDependencyDescriptor dd = new PomDependencyDescriptor(dep, ivyModuleDescriptor,
+                moduleRevId);
+        scope = (scope == null || scope.length() == 0) ? getDefaultScope(dep) : scope;
+        ConfMapper mapping = (ConfMapper) MAVEN2_CONF_MAPPING.get(scope);
+        mapping.addMappingConfs(dd, dep.isOptional());
+        Map extraAtt = new HashMap();
+        if ((dep.getClassifier() != null)
+                || ((dep.getType() != null) && !"jar".equals(dep.getType()))) {
+            String type = "jar";
+            if (dep.getType() != null) {
+                type = dep.getType();
+            }
+            String ext = type;
+
+            // if type is 'test-jar', the extension is 'jar' and the classifier is 'tests'
+            // Cfr. http://maven.apache.org/guides/mini/guide-attached-tests.html
+            if ("test-jar".equals(type)) {
+                ext = "jar";
+                extraAtt.put("m:classifier", "tests");
+            } else if (JAR_PACKAGINGS.contains(type)) {
+                ext = "jar";
+            }
+
+            // we deal with classifiers by setting an extra attribute and forcing the
+            // dependency to assume such an artifact is published
+            if (dep.getClassifier() != null) {
+                extraAtt.put("m:classifier", dep.getClassifier());
+            }
+            DefaultDependencyArtifactDescriptor depArtifact = new DefaultDependencyArtifactDescriptor(
+                    dd, dd.getDependencyId().getName(), type, ext, null, extraAtt);
+            // here we have to assume a type and ext for the artifact, so this is a limitation
+            // compared to how m2 behave with classifiers
+            String optionalizedScope = dep.isOptional() ? "optional" : scope;
+            dd.addDependencyArtifact(optionalizedScope, depArtifact);
+        }
+
+        // experimentation shows the following, excluded modules are
+        // inherited from parent POMs if either of the following is true:
+        // the <exclusions> element is missing or the <exclusions> element
+        // is present, but empty.
+        List /* <ModuleId> */excluded = dep.getExcludedModules();
+        if (excluded.isEmpty()) {
+            excluded = getDependencyMgtExclusions(ivyModuleDescriptor, dep.getGroupId(),
+                dep.getArtifactId());
+        }
+        for (Iterator itExcl = excluded.iterator(); itExcl.hasNext();) {
+            ModuleId excludedModule = (ModuleId) itExcl.next();
+            String[] confs = dd.getModuleConfigurations();
+            for (int k = 0; k < confs.length; k++) {
+                dd.addExcludeRule(confs[k], new DefaultExcludeRule(new ArtifactId(excludedModule,
+                        PatternMatcher.ANY_EXPRESSION, PatternMatcher.ANY_EXPRESSION,
+                        PatternMatcher.ANY_EXPRESSION), ExactPatternMatcher.INSTANCE, null));
+            }
+        }
+
+        ivyModuleDescriptor.addDependency(dd);
+    }
+
+    public void addDependency(DependencyDescriptor descriptor) {
+        // Some POMs depend on themselves through their parent pom, don't add this dependency
+        // since Ivy doesn't allow this!
+        // Example:
+        // https://repo1.maven.org/maven2/com/atomikos/atomikos-util/3.6.4/atomikos-util-3.6.4.pom
+        ModuleId dependencyId = descriptor.getDependencyId();
+        ModuleRevisionId mRevId = ivyModuleDescriptor.getModuleRevisionId();
+        if ((mRevId != null) && mRevId.getModuleId().equals(dependencyId)) {
+            return;
+        }
+
+        ivyModuleDescriptor.addDependency(descriptor);
+    }
+
+    public void addDependencyMgt(PomDependencyMgt dep) {
+        ivyModuleDescriptor.addDependencyManagement(dep);
+
+        String key = getDependencyMgtExtraInfoKeyForVersion(dep.getGroupId(), dep.getArtifactId());
+        overwriteExtraInfoIfExists(key, dep.getVersion());
+        if (dep.getScope() != null) {
+            String scopeKey = getDependencyMgtExtraInfoKeyForScope(dep.getGroupId(),
+                dep.getArtifactId());
+            overwriteExtraInfoIfExists(scopeKey, dep.getScope());
+        }
+        if (!dep.getExcludedModules().isEmpty()) {
+            final String exclusionPrefix = getDependencyMgtExtraInfoPrefixForExclusion(
+                dep.getGroupId(), dep.getArtifactId());
+            int index = 0;
+            for (final Iterator iter = dep.getExcludedModules().iterator(); iter.hasNext();) {
+                final ModuleId excludedModule = (ModuleId) iter.next();
+                overwriteExtraInfoIfExists(
+                    exclusionPrefix + index,
+                    excludedModule.getOrganisation() + EXTRA_INFO_DELIMITER
+                            + excludedModule.getName());
+                index += 1;
+            }
+        }
+        // dependency management info is also used for version mediation of transitive dependencies
+        ivyModuleDescriptor.addDependencyDescriptorMediator(
+            ModuleId.newInstance(dep.getGroupId(), dep.getArtifactId()),
+            ExactPatternMatcher.INSTANCE,
+            new OverrideDependencyDescriptorMediator(null, dep.getVersion()));
+    }
+
+    public void addPlugin(PomDependencyMgt plugin) {
+        String pluginValue = plugin.getGroupId() + EXTRA_INFO_DELIMITER + plugin.getArtifactId()
+                + EXTRA_INFO_DELIMITER + plugin.getVersion();
+        ExtraInfoHolder extraInfoByTagName = ivyModuleDescriptor
+                .getExtraInfoByTagName("m:maven.plugins");
+        if (extraInfoByTagName == null) {
+            extraInfoByTagName = new ExtraInfoHolder();
+            extraInfoByTagName.setName("m:maven.plugins");
+            ivyModuleDescriptor.addExtraInfo(extraInfoByTagName);
+        }
+        String pluginExtraInfo = extraInfoByTagName.getContent();
+        if (pluginExtraInfo == null) {
+            pluginExtraInfo = pluginValue;
+        } else {
+            pluginExtraInfo = pluginExtraInfo + "|" + pluginValue;
+        }
+        extraInfoByTagName.setContent(pluginExtraInfo);
+    }
+
+    public static List /* <PomDependencyMgt> */getPlugins(ModuleDescriptor md) {
+        List result = new ArrayList();
+        String plugins = md.getExtraInfoContentByTagName("m:maven.plugins");
+        if (plugins == null) {
+            return new ArrayList();
+        }
+        String[] pluginsArray = plugins.split("\\|");
+        for (int i = 0; i < pluginsArray.length; i++) {
+            String[] parts = pluginsArray[i].split(EXTRA_INFO_DELIMITER);
+            result.add(new PomPluginElement(parts[0], parts[1], parts[2]));
+        }
+
+        return result;
+    }
+
+    private static class PomPluginElement implements PomDependencyMgt {
+        private String groupId;
+
+        private String artifactId;
+
+        private String version;
+
+        public PomPluginElement(String groupId, String artifactId, String version) {
+            this.groupId = groupId;
+            this.artifactId = artifactId;
+            this.version = version;
+        }
+
+        public String getGroupId() {
+            return groupId;
+        }
+
+        public String getArtifactId() {
+            return artifactId;
+        }
+
+        public String getVersion() {
+            return version;
+        }
+
+        public String getScope() {
+            return null;
+        }
+
+        public List /* <ModuleId> */getExcludedModules() {
+            return Collections.EMPTY_LIST; // probably not used?
+        }
+    }
+
+    private String getDefaultVersion(PomDependencyData dep) {
+        ModuleId moduleId = ModuleId.newInstance(dep.getGroupId(), dep.getArtifactId());
+        if (ivyModuleDescriptor.getDependencyManagementMap().containsKey(moduleId)) {
+            return ((PomDependencyMgt) ivyModuleDescriptor.getDependencyManagementMap().get(
+                moduleId)).getVersion();
+        }
+        String key = getDependencyMgtExtraInfoKeyForVersion(dep.getGroupId(), dep.getArtifactId());
+        return ivyModuleDescriptor.getExtraInfoContentByTagName(key);
+    }
+
+    private String getDefaultScope(PomDependencyData dep) {
+        String result;
+        ModuleId moduleId = ModuleId.newInstance(dep.getGroupId(), dep.getArtifactId());
+        if (ivyModuleDescriptor.getDependencyManagementMap().containsKey(moduleId)) {
+            result = ((PomDependencyMgt) ivyModuleDescriptor.getDependencyManagementMap().get(
+                moduleId)).getScope();
+        } else {
+            String key = getDependencyMgtExtraInfoKeyForScope(dep.getGroupId(), dep.getArtifactId());
+            result = ivyModuleDescriptor.getExtraInfoContentByTagName(key);
+        }
+        if ((result == null) || !MAVEN2_CONF_MAPPING.containsKey(result)) {
+            result = "compile";
+        }
+        return result;
+    }
+
+    private static String getDependencyMgtExtraInfoKeyForVersion(String groupId, String artifaceId) {
+        return DEPENDENCY_MANAGEMENT + EXTRA_INFO_DELIMITER + groupId + EXTRA_INFO_DELIMITER
+                + artifaceId + EXTRA_INFO_DELIMITER + "version";
+    }
+
+    private static String getDependencyMgtExtraInfoKeyForScope(String groupId, String artifaceId) {
+        return DEPENDENCY_MANAGEMENT + EXTRA_INFO_DELIMITER + groupId + EXTRA_INFO_DELIMITER
+                + artifaceId + EXTRA_INFO_DELIMITER + "scope";
+    }
+
+    private static String getPropertyExtraInfoKey(String propertyName) {
+        return PROPERTIES + EXTRA_INFO_DELIMITER + propertyName;
+    }
+
+    private static String getDependencyMgtExtraInfoPrefixForExclusion(String groupId,
+            String artifaceId) {
+        return DEPENDENCY_MANAGEMENT + EXTRA_INFO_DELIMITER + groupId + EXTRA_INFO_DELIMITER
+                + artifaceId + EXTRA_INFO_DELIMITER + "exclusion_";
+    }
+
+    private static List /* <ModuleId> */getDependencyMgtExclusions(ModuleDescriptor descriptor,
+            String groupId, String artifactId) {
+        if (descriptor instanceof PomModuleDescriptor) {
+            PomDependencyMgt dependencyMgt = (PomDependencyMgt) ((PomModuleDescriptor) descriptor)
+                    .getDependencyManagementMap().get(ModuleId.newInstance(groupId, artifactId));
+            if (dependencyMgt != null) {
+                return dependencyMgt.getExcludedModules();
+            }
+        }
+        String exclusionPrefix = getDependencyMgtExtraInfoPrefixForExclusion(groupId, artifactId);
+        List /* <ModuleId> */exclusionIds = new LinkedList /* <ModuleId> */();
+        for (ExtraInfoHolder extraInfoHolder : descriptor.getExtraInfos()) {
+            String key = extraInfoHolder.getName();
+            if (key.startsWith(exclusionPrefix)) {
+                String fullExclusion = extraInfoHolder.getContent();
+                String[] exclusionParts = fullExclusion.split(EXTRA_INFO_DELIMITER);
+                if (exclusionParts.length != 2) {
+                    Message.error(WRONG_NUMBER_OF_PARTS_MSG + exclusionParts.length + " : "
+                            + fullExclusion);
+                    continue;
+                }
+                exclusionIds.add(ModuleId.newInstance(exclusionParts[0], exclusionParts[1]));
+            }
+        }
+        return exclusionIds;
+    }
+
+    public static Map/* <ModuleId, String version> */
+    getDependencyManagementMap(ModuleDescriptor md) {
+        Map ret = new LinkedHashMap();
+        if (md instanceof PomModuleDescriptor) {
+            for (final Iterator iterator = ((PomModuleDescriptor) md).getDependencyManagementMap()
+                    .entrySet().iterator(); iterator.hasNext();) {
+                Map.Entry e = (Entry) iterator.next();
+                PomDependencyMgt dependencyMgt = (PomDependencyMgt) e.getValue();
+                ret.put(e.getKey(), dependencyMgt.getVersion());
+            }
+        } else {
+            for (ExtraInfoHolder extraInfoHolder : md.getExtraInfos()) {
+                String key = extraInfoHolder.getName();
+                if ((key).startsWith(DEPENDENCY_MANAGEMENT)) {
+                    String[] parts = key.split(EXTRA_INFO_DELIMITER);
+                    if (parts.length != DEPENDENCY_MANAGEMENT_KEY_PARTS_COUNT) {
+                        Message.warn("what seem to be a dependency management extra info "
+                                + "doesn't match expected pattern: " + key);
+                    } else {
+                        ret.put(ModuleId.newInstance(parts[1], parts[2]),
+                            extraInfoHolder.getContent());
+                    }
+                }
+            }
+        }
+        return ret;
+    }
+
+    public static List getDependencyManagements(ModuleDescriptor md) {
+        List result = new ArrayList();
+
+        if (md instanceof PomModuleDescriptor) {
+            result.addAll(((PomModuleDescriptor) md).getDependencyManagementMap().values());
+        } else {
+            for (ExtraInfoHolder extraInfoHolder : md.getExtraInfos()) {
+                String key = extraInfoHolder.getName();
+                if ((key).startsWith(DEPENDENCY_MANAGEMENT)) {
+                    String[] parts = key.split(EXTRA_INFO_DELIMITER);
+                    if (parts.length != DEPENDENCY_MANAGEMENT_KEY_PARTS_COUNT) {
+                        Message.warn("what seem to be a dependency management extra info "
+                                + "doesn't match expected pattern: " + key);
+                    } else {
+                        String versionKey = DEPENDENCY_MANAGEMENT + EXTRA_INFO_DELIMITER + parts[1]
+                                + EXTRA_INFO_DELIMITER + parts[2] + EXTRA_INFO_DELIMITER
+                                + "version";
+                        String scopeKey = DEPENDENCY_MANAGEMENT + EXTRA_INFO_DELIMITER + parts[1]
+                                + EXTRA_INFO_DELIMITER + parts[2] + EXTRA_INFO_DELIMITER + "scope";
+
+                        String version = md.getExtraInfoContentByTagName(versionKey);
+                        String scope = md.getExtraInfoContentByTagName(scopeKey);
+
+                        List /* <ModuleId> */exclusions = getDependencyMgtExclusions(md, parts[1],
+                            parts[2]);
+                        result.add(new DefaultPomDependencyMgt(parts[1], parts[2], version, scope,
+                                exclusions));
+                    }
+                }
+            }
+        }
+        return result;
+    }
+
+    @Deprecated
+    public void addExtraInfos(Map extraAttributes) {
+        for (Iterator it = extraAttributes.entrySet().iterator(); it.hasNext();) {
+            Map.Entry entry = (Entry) it.next();
+            String key = (String) entry.getKey();
+            String value = (String) entry.getValue();
+            addExtraInfo(key, value);
+        }
+    }
+
+    private void addExtraInfo(String key, String value) {
+        if (ivyModuleDescriptor.getExtraInfoByTagName(key) == null) {
+            ivyModuleDescriptor.getExtraInfos().add(new ExtraInfoHolder(key, value));
+        }
+    }
+
+    private void overwriteExtraInfoIfExists(String key, String value) {
+        boolean found = false;
+        for (ExtraInfoHolder extraInfoHolder : ivyModuleDescriptor.getExtraInfos()) {
+            if (extraInfoHolder.getName().equals(key)) {
+                extraInfoHolder.setContent(value);
+                found = true;
+            }
+        }
+        if (!found) {
+            ivyModuleDescriptor.getExtraInfos().add(new ExtraInfoHolder(key, value));
+        }
+    }
+
+    public void addExtraInfos(List<ExtraInfoHolder> extraInfosHolder) {
+        for (ExtraInfoHolder extraInfoHolder : extraInfosHolder) {
+            addExtraInfo(extraInfoHolder.getName(), extraInfoHolder.getContent());
+        }
+    }
+
+    @Deprecated
+    public static Map extractPomProperties(Map extraInfo) {
+        Map r = new HashMap();
+        for (Iterator it = extraInfo.entrySet().iterator(); it.hasNext();) {
+            Map.Entry extraInfoEntry = (Map.Entry) it.next();
+            if (((String) extraInfoEntry.getKey()).startsWith(PROPERTIES)) {
+                String prop = ((String) extraInfoEntry.getKey()).substring(PROPERTIES.length()
+                        + EXTRA_INFO_DELIMITER.length());
+                r.put(prop, extraInfoEntry.getValue());
+            }
+        }
+        return r;
+    }
+
+    public static Map extractPomProperties(List<ExtraInfoHolder> extraInfos) {
+        Map r = new HashMap();
+        for (ExtraInfoHolder extraInfoHolder : extraInfos) {
+            if ((extraInfoHolder.getName()).startsWith(PROPERTIES)) {
+                String prop = (extraInfoHolder.getName()).substring(PROPERTIES.length()
+                        + EXTRA_INFO_DELIMITER.length());
+                r.put(prop, extraInfoHolder.getContent());
+            }
+        }
+        return r;
+    }
+
+    public void addProperty(String propertyName, String value) {
+        addExtraInfo(getPropertyExtraInfoKey(propertyName), value);
+    }
+
+    public Artifact getMainArtifact() {
+        return mainArtifact;
+    }
+
+    public Artifact getSourceArtifact() {
+        return new MDArtifact(ivyModuleDescriptor, mrid.getName(), "source", "jar", null,
+                Collections.singletonMap("m:classifier", "sources"));
+    }
+
+    public Artifact getSrcArtifact() {
+        return new MDArtifact(ivyModuleDescriptor, mrid.getName(), "source", "jar", null,
+                Collections.singletonMap("m:classifier", "src"));
+    }
+
+    public Artifact getJavadocArtifact() {
+        return new MDArtifact(ivyModuleDescriptor, mrid.getName(), "javadoc", "jar", null,
+                Collections.singletonMap("m:classifier", "javadoc"));
+    }
+
+    public void addSourceArtifact() {
+        ivyModuleDescriptor.addArtifact("sources", getSourceArtifact());
+    }
+
+    public void addSrcArtifact() {
+        ivyModuleDescriptor.addArtifact("sources", getSrcArtifact());
+    }
+
+    public void addJavadocArtifact() {
+        ivyModuleDescriptor.addArtifact("javadoc", getJavadocArtifact());
+    }
+
+    /**
+     * <code>DependencyDescriptor</code> that provides access to the original
+     * <code>PomDependencyData</code>.
+     */
+    public static class PomDependencyDescriptor extends DefaultDependencyDescriptor {
+        private final PomDependencyData pomDependencyData;
+
+        private PomDependencyDescriptor(PomDependencyData pomDependencyData,
+                ModuleDescriptor moduleDescriptor, ModuleRevisionId revisionId) {
+            super(moduleDescriptor, revisionId, true, false, true);
+            this.pomDependencyData = pomDependencyData;
+        }
+
+        /**
+         * Get PomDependencyData.
+         * 
+         * @return PomDependencyData
+         */
+        public PomDependencyData getPomDependencyData() {
+            return pomDependencyData;
+        }
+    }
+
+    public static class PomModuleDescriptor extends DefaultModuleDescriptor {
+        private final Map/* <ModuleId, PomDependencyMgt> */dependencyManagementMap = new HashMap();
+
+        public PomModuleDescriptor(ModuleDescriptorParser parser, Resource res) {
+            super(parser, res);
+        }
+
+        public void addDependencyManagement(PomDependencyMgt dependencyMgt) {
+            dependencyManagementMap.put(
+                ModuleId.newInstance(dependencyMgt.getGroupId(), dependencyMgt.getArtifactId()),
+                dependencyMgt);
+        }
+
+        public Map getDependencyManagementMap() {
+            return dependencyManagementMap;
+        }
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/parser/m2/PomModuleDescriptorParser.java b/src/java/org/apache/ivy/plugins/parser/m2/PomModuleDescriptorParser.java
new file mode 100644
index 0000000..6017e3a
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/parser/m2/PomModuleDescriptorParser.java
@@ -0,0 +1,380 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.parser.m2;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.text.ParseException;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.ivy.core.IvyContext;
+import org.apache.ivy.core.cache.ArtifactOrigin;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.Configuration;
+import org.apache.ivy.core.module.descriptor.Configuration.Visibility;
+import org.apache.ivy.core.module.descriptor.DefaultArtifact;
+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.apache.ivy.core.resolve.ResolveData;
+import org.apache.ivy.core.resolve.ResolveEngine;
+import org.apache.ivy.core.resolve.ResolveOptions;
+import org.apache.ivy.core.resolve.ResolvedModuleRevision;
+import org.apache.ivy.plugins.namespace.NameSpaceHelper;
+import org.apache.ivy.plugins.parser.ModuleDescriptorParser;
+import org.apache.ivy.plugins.parser.ParserSettings;
+import org.apache.ivy.plugins.parser.m2.PomModuleDescriptorBuilder.PomDependencyDescriptor;
+import org.apache.ivy.plugins.parser.m2.PomReader.PomDependencyData;
+import org.apache.ivy.plugins.parser.m2.PomReader.PomDependencyMgtElement;
+import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorWriter;
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.plugins.repository.url.URLResource;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.apache.ivy.util.Message;
+import org.xml.sax.SAXException;
+
+/**
+ * A parser for Maven 2 POM.
+ * <p>
+ * The configurations used in the generated module descriptor mimics the behavior defined by maven 2
+ * scopes, as documented here:<br/>
+ * http://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html The
+ * PomModuleDescriptorParser use a PomDomReader to read the pom, and the PomModuleDescriptorBuilder
+ * to write the ivy module descriptor using the info read by the PomDomReader.
+ */
+public final class PomModuleDescriptorParser implements ModuleDescriptorParser {
+
+    private static final PomModuleDescriptorParser INSTANCE = new PomModuleDescriptorParser();
+
+    public static PomModuleDescriptorParser getInstance() {
+        return INSTANCE;
+    }
+
+    private PomModuleDescriptorParser() {
+    }
+
+    public void toIvyFile(InputStream is, Resource res, File destFile, ModuleDescriptor md)
+            throws ParseException, IOException {
+        try {
+            XmlModuleDescriptorWriter.write(md, destFile);
+        } finally {
+            if (is != null) {
+                is.close();
+            }
+        }
+    }
+
+    public boolean accept(Resource res) {
+        return res.getName().endsWith(".pom") || res.getName().endsWith("pom.xml")
+                || res.getName().endsWith("project.xml");
+    }
+
+    public String toString() {
+        return "pom parser";
+    }
+
+    public Artifact getMetadataArtifact(ModuleRevisionId mrid, Resource res) {
+        return DefaultArtifact.newPomArtifact(mrid, new Date(res.getLastModified()));
+    }
+
+    public String getType() {
+        return "pom";
+    }
+
+    public ModuleDescriptor parseDescriptor(ParserSettings ivySettings, URL descriptorURL,
+            boolean validate) throws ParseException, IOException {
+        URLResource resource = new URLResource(descriptorURL);
+        return parseDescriptor(ivySettings, descriptorURL, resource, validate);
+    }
+
+    public ModuleDescriptor parseDescriptor(ParserSettings ivySettings, URL descriptorURL,
+            Resource res, boolean validate) throws ParseException, IOException {
+
+        PomModuleDescriptorBuilder mdBuilder = new PomModuleDescriptorBuilder(this, res,
+                ivySettings);
+
+        try {
+            PomReader domReader = new PomReader(descriptorURL, res);
+            domReader.setProperty("parent.version", domReader.getParentVersion());
+            domReader.setProperty("parent.groupId", domReader.getParentGroupId());
+            domReader.setProperty("project.parent.version", domReader.getParentVersion());
+            domReader.setProperty("project.parent.groupId", domReader.getParentGroupId());
+
+            Map pomProperties = domReader.getPomProperties();
+            for (Iterator iter = pomProperties.entrySet().iterator(); iter.hasNext();) {
+                Map.Entry prop = (Map.Entry) iter.next();
+                domReader.setProperty((String) prop.getKey(), (String) prop.getValue());
+                mdBuilder.addProperty((String) prop.getKey(), (String) prop.getValue());
+            }
+
+            ModuleDescriptor parentDescr = null;
+            if (domReader.hasParent()) {
+                // Is there any other parent properties?
+
+                ModuleRevisionId parentModRevID = ModuleRevisionId.newInstance(
+                    domReader.getParentGroupId(), domReader.getParentArtifactId(),
+                    domReader.getParentVersion());
+                ResolvedModuleRevision parentModule = parseOtherPom(ivySettings, parentModRevID);
+                if (parentModule != null) {
+                    parentDescr = parentModule.getDescriptor();
+                } else {
+                    throw new IOException("Impossible to load parent for " + res.getName() + "."
+                            + " Parent=" + parentModRevID);
+                }
+                if (parentDescr != null) {
+                    Map parentPomProps = PomModuleDescriptorBuilder
+                            .extractPomProperties(parentDescr.getExtraInfos());
+                    for (Iterator iter = parentPomProps.entrySet().iterator(); iter.hasNext();) {
+                        Map.Entry prop = (Map.Entry) iter.next();
+                        domReader.setProperty((String) prop.getKey(), (String) prop.getValue());
+                    }
+                }
+            }
+
+            String groupId = domReader.getGroupId();
+            String artifactId = domReader.getArtifactId();
+            String version = domReader.getVersion();
+            mdBuilder.setModuleRevId(groupId, artifactId, version);
+
+            mdBuilder.setHomePage(domReader.getHomePage());
+            mdBuilder.setDescription(domReader.getDescription());
+            mdBuilder.setLicenses(domReader.getLicenses());
+
+            ModuleRevisionId relocation = domReader.getRelocation();
+
+            if (relocation != null) {
+                if (groupId != null && artifactId != null
+                        && artifactId.equals(relocation.getName())
+                        && groupId.equals(relocation.getOrganisation())) {
+                    Message.error("Relocation to an other version number not supported in ivy : "
+                            + mdBuilder.getModuleDescriptor().getModuleRevisionId()
+                            + " relocated to " + relocation
+                            + ". Please update your dependency to directly use the right version.");
+                    Message.warn("Resolution will only pick dependencies of the relocated element."
+                            + "  Artefact and other metadata will be ignored.");
+                    ResolvedModuleRevision relocatedModule = parseOtherPom(ivySettings, relocation);
+                    if (relocatedModule == null) {
+                        throw new ParseException("impossible to load module " + relocation
+                                + " to which "
+                                + mdBuilder.getModuleDescriptor().getModuleRevisionId()
+                                + " has been relocated", 0);
+                    }
+                    DependencyDescriptor[] dds = relocatedModule.getDescriptor().getDependencies();
+                    for (int i = 0; i < dds.length; i++) {
+                        mdBuilder.addDependency(dds[i]);
+                    }
+                } else {
+                    Message.info(mdBuilder.getModuleDescriptor().getModuleRevisionId()
+                            + " is relocated to " + relocation
+                            + ". Please update your dependencies.");
+                    Message.verbose("Relocated module will be considered as a dependency");
+                    DefaultDependencyDescriptor dd = new DefaultDependencyDescriptor(
+                            mdBuilder.getModuleDescriptor(), relocation, true, false, true);
+                    /* Map all public dependencies */
+                    Configuration[] m2Confs = PomModuleDescriptorBuilder.MAVEN2_CONFIGURATIONS;
+                    for (int i = 0; i < m2Confs.length; i++) {
+                        if (Visibility.PUBLIC.equals(m2Confs[i].getVisibility())) {
+                            dd.addDependencyConfiguration(m2Confs[i].getName(),
+                                m2Confs[i].getName());
+                        }
+                    }
+                    mdBuilder.addDependency(dd);
+                }
+            } else {
+                domReader.setProperty("project.groupId", groupId);
+                domReader.setProperty("pom.groupId", groupId);
+                domReader.setProperty("groupId", groupId);
+                domReader.setProperty("project.artifactId", artifactId);
+                domReader.setProperty("pom.artifactId", artifactId);
+                domReader.setProperty("artifactId", artifactId);
+                domReader.setProperty("project.version", version);
+                domReader.setProperty("pom.version", version);
+                domReader.setProperty("version", version);
+
+                if (parentDescr != null) {
+                    mdBuilder.addExtraInfos(parentDescr.getExtraInfos());
+
+                    // add dependency management info from parent
+                    List depMgt = PomModuleDescriptorBuilder.getDependencyManagements(parentDescr);
+                    for (Iterator it = depMgt.iterator(); it.hasNext();) {
+                        PomDependencyMgt dep = (PomDependencyMgt) it.next();
+                        if (dep instanceof PomDependencyMgtElement) {
+                            dep = domReader.new PomDependencyMgtElement(
+                                    (PomDependencyMgtElement) dep);
+                        }
+                        mdBuilder.addDependencyMgt(dep);
+                    }
+
+                    // add plugins from parent
+                    List /* <PomDependencyMgt> */plugins = PomModuleDescriptorBuilder
+                            .getPlugins(parentDescr);
+                    for (Iterator it = plugins.iterator(); it.hasNext();) {
+                        mdBuilder.addPlugin((PomDependencyMgt) it.next());
+                    }
+                }
+
+                for (Iterator it = domReader.getDependencyMgt().iterator(); it.hasNext();) {
+                    PomDependencyMgt dep = (PomDependencyMgt) it.next();
+                    if ("import".equals(dep.getScope())) {
+                        ModuleRevisionId importModRevID = ModuleRevisionId.newInstance(
+                            dep.getGroupId(), dep.getArtifactId(), dep.getVersion());
+                        ResolvedModuleRevision importModule = parseOtherPom(ivySettings,
+                            importModRevID);
+                        if (importModule != null) {
+                            ModuleDescriptor importDescr = importModule.getDescriptor();
+
+                            // add dependency management info from imported module
+                            List depMgt = PomModuleDescriptorBuilder
+                                    .getDependencyManagements(importDescr);
+                            for (Iterator it2 = depMgt.iterator(); it2.hasNext();) {
+                                PomDependencyMgt importedDepMgt = (PomDependencyMgt) it2.next();
+                                mdBuilder.addDependencyMgt(new DefaultPomDependencyMgt(
+                                        importedDepMgt.getGroupId(),
+                                        importedDepMgt.getArtifactId(),
+                                        importedDepMgt.getVersion(), importedDepMgt.getScope(),
+                                        importedDepMgt.getExcludedModules()));
+                            }
+                        } else {
+                            throw new IOException("Impossible to import module for "
+                                    + res.getName() + "." + " Import=" + importModRevID);
+                        }
+
+                    } else {
+                        mdBuilder.addDependencyMgt(dep);
+                    }
+                }
+
+                for (Iterator it = domReader.getDependencies().iterator(); it.hasNext();) {
+                    PomReader.PomDependencyData dep = (PomReader.PomDependencyData) it.next();
+                    mdBuilder.addDependency(res, dep);
+                }
+
+                if (parentDescr != null) {
+                    for (int i = 0; i < parentDescr.getDependencies().length; i++) {
+                        DependencyDescriptor descriptor = parentDescr.getDependencies()[i];
+                        if (descriptor instanceof PomDependencyDescriptor) {
+                            PomDependencyData parentDep = ((PomDependencyDescriptor) descriptor)
+                                    .getPomDependencyData();
+                            PomDependencyData dep = domReader.new PomDependencyData(parentDep);
+                            mdBuilder.addDependency(res, dep);
+                        } else {
+                            mdBuilder.addDependency(descriptor);
+                        }
+                    }
+                }
+
+                for (Iterator it = domReader.getPlugins().iterator(); it.hasNext();) {
+                    PomReader.PomPluginElement plugin = (PomReader.PomPluginElement) it.next();
+                    mdBuilder.addPlugin(plugin);
+                }
+
+                mdBuilder.addMainArtifact(artifactId, domReader.getPackaging());
+
+                addSourcesAndJavadocArtifactsIfPresent(mdBuilder, ivySettings);
+            }
+        } catch (SAXException e) {
+            throw newParserException(e);
+        }
+
+        return mdBuilder.getModuleDescriptor();
+    }
+
+    private void addSourcesAndJavadocArtifactsIfPresent(PomModuleDescriptorBuilder mdBuilder,
+            ParserSettings ivySettings) {
+        if (mdBuilder.getMainArtifact() == null) {
+            // no main artifact in pom, we don't need to search for meta artifacts
+            return;
+        }
+        ModuleDescriptor md = mdBuilder.getModuleDescriptor();
+        ModuleRevisionId mrid = md.getModuleRevisionId();
+        DependencyResolver resolver = ivySettings.getResolver(mrid);
+
+        if (resolver == null) {
+            Message.debug("no resolver found for " + mrid
+                    + ": no source or javadoc artifact lookup");
+        } else {
+            ArtifactOrigin mainArtifact = resolver.locate(mdBuilder.getMainArtifact());
+
+            if (!ArtifactOrigin.isUnknown(mainArtifact)) {
+                String mainArtifactLocation = mainArtifact.getLocation();
+
+                ArtifactOrigin sourceArtifact = resolver.locate(mdBuilder.getSourceArtifact());
+                if (!ArtifactOrigin.isUnknown(sourceArtifact)
+                        && !sourceArtifact.getLocation().equals(mainArtifactLocation)) {
+                    Message.debug("source artifact found for " + mrid);
+                    mdBuilder.addSourceArtifact();
+                } else {
+                    // it seems that sometimes the 'src' classifier is used instead of 'sources'
+                    // Cfr. IVY-1138
+                    ArtifactOrigin srcArtifact = resolver.locate(mdBuilder.getSrcArtifact());
+                    if (!ArtifactOrigin.isUnknown(srcArtifact)
+                            && !srcArtifact.getLocation().equals(mainArtifactLocation)) {
+                        Message.debug("source artifact found for " + mrid);
+                        mdBuilder.addSrcArtifact();
+                    } else {
+                        Message.debug("no source artifact found for " + mrid);
+                    }
+                }
+                ArtifactOrigin javadocArtifact = resolver.locate(mdBuilder.getJavadocArtifact());
+                if (!ArtifactOrigin.isUnknown(javadocArtifact)
+                        && !javadocArtifact.getLocation().equals(mainArtifactLocation)) {
+                    Message.debug("javadoc artifact found for " + mrid);
+                    mdBuilder.addJavadocArtifact();
+                } else {
+                    Message.debug("no javadoc artifact found for " + mrid);
+                }
+            }
+        }
+    }
+
+    private ResolvedModuleRevision parseOtherPom(ParserSettings ivySettings,
+            ModuleRevisionId parentModRevID) throws ParseException {
+        DependencyDescriptor dd = new DefaultDependencyDescriptor(parentModRevID, true);
+        ResolveData data = IvyContext.getContext().getResolveData();
+        if (data == null) {
+            ResolveEngine engine = IvyContext.getContext().getIvy().getResolveEngine();
+            ResolveOptions options = new ResolveOptions();
+            options.setDownload(false);
+            data = new ResolveData(engine, options);
+        }
+
+        DependencyResolver resolver = ivySettings.getResolver(parentModRevID);
+        if (resolver == null) {
+            // TODO: Throw exception here?
+            return null;
+        } else {
+            dd = NameSpaceHelper.toSystem(dd, ivySettings.getContextNamespace());
+            ResolvedModuleRevision otherModule = resolver.getDependency(dd, data);
+            return otherModule;
+        }
+    }
+
+    private ParseException newParserException(Exception e) {
+        Message.error(e.getMessage());
+        ParseException pe = new ParseException(e.getMessage(), 0);
+        pe.initCause(e);
+        return pe;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/plugins/parser/m2/PomModuleDescriptorWriter.java b/src/java/org/apache/ivy/plugins/parser/m2/PomModuleDescriptorWriter.java
new file mode 100644
index 0000000..5cd254a
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/parser/m2/PomModuleDescriptorWriter.java
@@ -0,0 +1,392 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.parser.m2;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.ivy.Ivy;
+import org.apache.ivy.core.IvyContext;
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DependencyArtifactDescriptor;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.ExcludeRule;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.ivy.core.settings.IvyVariableContainer;
+import org.apache.ivy.plugins.parser.m2.PomWriterOptions.ConfigurationScopeMapping;
+import org.apache.ivy.plugins.parser.m2.PomWriterOptions.ExtraDependency;
+import org.apache.ivy.util.ConfigurationUtils;
+
+public final class PomModuleDescriptorWriter {
+
+    private static final String SKIP_LINE = "SKIP_LINE";
+
+    private static final ConfigurationScopeMapping DEFAULT_MAPPING = new ConfigurationScopeMapping(
+            new HashMap() {
+                {
+                    put("compile", "compile");
+                    put("runtime", "runtime");
+                    put("provided", "provided");
+                    put("test", "test");
+                    put("system", "system");
+                }
+            });
+
+    private PomModuleDescriptorWriter() {
+    }
+
+    public static void write(ModuleDescriptor md, File output, PomWriterOptions options)
+            throws IOException {
+        LineNumberReader in;
+        if (options.getTemplate() == null) {
+            in = new LineNumberReader(new InputStreamReader(
+                    PomModuleDescriptorWriter.class.getResourceAsStream("pom.template")));
+        } else {
+            in = new LineNumberReader(new InputStreamReader(new FileInputStream(
+                    options.getTemplate())));
+        }
+
+        if (output.getParentFile() != null) {
+            output.getParentFile().mkdirs();
+        }
+        PrintWriter out = new PrintWriter(new OutputStreamWriter(new FileOutputStream(output),
+                "UTF-8"));
+        try {
+            IvySettings settings = IvyContext.getContext().getSettings();
+            IvyVariableContainer variables = new IvyVariableContainerWrapper(
+                    settings.getVariableContainer());
+
+            variables.setVariable("ivy.pom.license", SKIP_LINE, true);
+            variables.setVariable("ivy.pom.header", SKIP_LINE, true);
+            variables.setVariable("ivy.pom.groupId", SKIP_LINE, true);
+            variables.setVariable("ivy.pom.artifactId", SKIP_LINE, true);
+            variables.setVariable("ivy.pom.version", SKIP_LINE, true);
+            variables.setVariable("ivy.pom.packaging", SKIP_LINE, true);
+            variables.setVariable("ivy.pom.name", SKIP_LINE, true);
+            variables.setVariable("ivy.pom.description", SKIP_LINE, true);
+            variables.setVariable("ivy.pom.url", SKIP_LINE, true);
+
+            if (options.getLicenseHeader() != null) {
+                variables.setVariable("ivy.pom.license", options.getLicenseHeader(), true);
+            }
+            if (options.isPrintIvyInfo()) {
+                String header = "<!--\n" + "   Apache Maven 2 POM generated by Apache Ivy\n"
+                        + "   " + Ivy.getIvyHomeURL() + "\n" + "   Apache Ivy version: "
+                        + Ivy.getIvyVersion() + " " + Ivy.getIvyDate() + "\n" + "-->";
+                variables.setVariable("ivy.pom.header", header, true);
+            }
+
+            setModuleVariables(md, variables, options);
+
+            boolean dependenciesPrinted = false;
+
+            int lastIndent = 0;
+            int indent = 0;
+            String line = in.readLine();
+            while (line != null) {
+                line = IvyPatternHelper.substituteVariables(line, variables);
+                if (line.indexOf(SKIP_LINE) != -1) {
+                    // skip this line
+                    line = in.readLine();
+                    continue;
+                }
+
+                if (line.trim().length() == 0) {
+                    // empty line
+                    out.println(line);
+                    line = in.readLine();
+                    continue;
+                }
+
+                lastIndent = indent;
+                indent = line.indexOf('<');
+
+                if (!dependenciesPrinted && line.indexOf("</dependencies>") != -1) {
+                    printDependencies(md, out, options, indent, false);
+                    dependenciesPrinted = true;
+                }
+
+                if (!dependenciesPrinted && line.indexOf("</project>") != -1) {
+                    printDependencies(md, out, options, lastIndent, true);
+                    dependenciesPrinted = true;
+                }
+
+                out.println(line);
+                line = in.readLine();
+            }
+        } finally {
+            in.close();
+            out.close();
+        }
+    }
+
+    private static void setModuleVariables(ModuleDescriptor md, IvyVariableContainer variables,
+            PomWriterOptions options) {
+        ModuleRevisionId mrid = md.getModuleRevisionId();
+        variables.setVariable("ivy.pom.groupId", mrid.getOrganisation(), true);
+
+        String artifactId = options.getArtifactName();
+        if (artifactId == null) {
+            artifactId = mrid.getName();
+        }
+
+        String packaging = options.getArtifactPackaging();
+        if (packaging == null) {
+            // find artifact to determine the packaging
+            Artifact artifact = findArtifact(md, artifactId);
+            if (artifact == null) {
+                // no suitable artifact found, default to 'pom'
+                packaging = "pom";
+            } else {
+                packaging = artifact.getType();
+            }
+        }
+
+        variables.setVariable("ivy.pom.artifactId", artifactId, true);
+        variables.setVariable("ivy.pom.packaging", packaging, true);
+        if (mrid.getRevision() != null) {
+            variables.setVariable("ivy.pom.version", mrid.getRevision(), true);
+        }
+        if (options.getDescription() != null) {
+            variables.setVariable("ivy.pom.description", options.getDescription(), true);
+        }
+        if (md.getHomePage() != null) {
+            variables.setVariable("ivy.pom.url", md.getHomePage(), true);
+        }
+    }
+
+    /**
+     * Returns the first artifact with the correct name and without a classifier.
+     */
+    private static Artifact findArtifact(ModuleDescriptor md, String artifactName) {
+        Artifact[] artifacts = md.getAllArtifacts();
+        for (int i = 0; i < artifacts.length; i++) {
+            if (artifacts[i].getName().equals(artifactName)
+                    && artifacts[i].getAttribute("classifier") == null) {
+                return artifacts[i];
+            }
+        }
+
+        return null;
+    }
+
+    private static void indent(PrintWriter out, int indent) {
+        for (int i = 0; i < indent; i++) {
+            out.print(' ');
+        }
+    }
+
+    private static void printDependencies(ModuleDescriptor md, PrintWriter out,
+            PomWriterOptions options, int indent, boolean printDependencies) {
+        List extraDeps = options.getExtraDependencies();
+        DependencyDescriptor[] dds = getDependencies(md, options);
+
+        if (!extraDeps.isEmpty() || (dds.length > 0)) {
+            if (printDependencies) {
+                indent(out, indent);
+                out.println("<dependencies>");
+            }
+
+            // print the extra dependencies first
+            for (Iterator it = extraDeps.iterator(); it.hasNext();) {
+                PomWriterOptions.ExtraDependency dep = (ExtraDependency) it.next();
+                String groupId = dep.getGroup();
+                if (groupId == null) {
+                    groupId = md.getModuleRevisionId().getOrganisation();
+                }
+                String version = dep.getVersion();
+                if (version == null) {
+                    version = md.getModuleRevisionId().getRevision();
+                }
+                printDependency(out, indent, groupId, dep.getArtifact(), version, dep.getType(),
+                    dep.getClassifier(), dep.getScope(), dep.isOptional(), true, null);
+            }
+
+            // now print the dependencies listed in the ModuleDescriptor
+            ConfigurationScopeMapping mapping = options.getMapping();
+            if (mapping == null) {
+                mapping = DEFAULT_MAPPING;
+            }
+
+            for (int i = 0; i < dds.length; i++) {
+                ModuleRevisionId mrid = dds[i].getDependencyRevisionId();
+                ExcludeRule[] excludes = null;
+                if (dds[i].canExclude()) {
+                    excludes = dds[i].getAllExcludeRules();
+                }
+                DependencyArtifactDescriptor[] dads = dds[i].getAllDependencyArtifacts();
+                if (dads.length > 0) {
+                    for (int j = 0; j < dads.length; j++) {
+                        String type = dads[j].getType();
+                        String classifier = dads[j].getExtraAttribute("classifier");
+                        String scope = mapping.getScope(dds[i].getModuleConfigurations());
+                        boolean optional = mapping.isOptional(dds[i].getModuleConfigurations());
+                        printDependency(out, indent, mrid.getOrganisation(), mrid.getName(),
+                            mrid.getRevision(), type, classifier, scope, optional,
+                            dds[i].isTransitive(), excludes);
+                    }
+                } else {
+                    String scope = mapping.getScope(dds[i].getModuleConfigurations());
+                    boolean optional = mapping.isOptional(dds[i].getModuleConfigurations());
+                    printDependency(out, indent, mrid.getOrganisation(), mrid.getName(),
+                        mrid.getRevision(), null, null, scope, optional, dds[i].isTransitive(),
+                        excludes);
+                }
+            }
+
+            if (printDependencies) {
+                indent(out, indent);
+                out.println("</dependencies>");
+            }
+        }
+    }
+
+    private static void printDependency(PrintWriter out, int indent, String groupId,
+            String artifactId, String version, String type, String classifier, String scope,
+            boolean isOptional, boolean isTransitive, ExcludeRule[] excludes) {
+        indent(out, indent * 2);
+        out.println("<dependency>");
+        indent(out, indent * 3);
+        out.println("<groupId>" + groupId + "</groupId>");
+        indent(out, indent * 3);
+        out.println("<artifactId>" + artifactId + "</artifactId>");
+        indent(out, indent * 3);
+        out.println("<version>" + version + "</version>");
+        if (type != null && !"jar".equals(type)) {
+            indent(out, indent * 3);
+            out.println("<type>" + type + "</type>");
+        }
+        if (classifier != null) {
+            indent(out, indent * 3);
+            out.println("<classifier>" + classifier + "</classifier>");
+        }
+        if (scope != null) {
+            indent(out, indent * 3);
+            out.println("<scope>" + scope + "</scope>");
+        }
+        if (isOptional) {
+            indent(out, indent * 3);
+            out.println("<optional>true</optional>");
+        }
+        if (!isTransitive) {
+            indent(out, indent * 3);
+            out.println("<exclusions>");
+            indent(out, indent * 4);
+            out.println("<exclusion>");
+            indent(out, indent * 5);
+            out.println("<groupId>*</groupId>");
+            indent(out, indent * 5);
+            out.println("<artifactId>*</artifactId>");
+            indent(out, indent * 4);
+            out.println("</exclusion>");
+            indent(out, indent * 3);
+            out.println("</exclusions>");
+        } else if (excludes != null) {
+            printExclusions(excludes, out, indent);
+        }
+        indent(out, indent * 2);
+        out.println("</dependency>");
+    }
+
+    private static void printExclusions(ExcludeRule[] exclusions, PrintWriter out, int indent) {
+        indent(out, indent * 3);
+        out.println("<exclusions>");
+
+        for (int i = 0; i < exclusions.length; i++) {
+            indent(out, indent * 4);
+            out.println("<exclusion>");
+            ExcludeRule rule = exclusions[i];
+            indent(out, indent * 5);
+            out.println("<groupId>" + rule.getId().getModuleId().getOrganisation() + "</groupId>");
+            indent(out, indent * 5);
+            out.println("<artifactId>" + rule.getId().getModuleId().getName() + "</artifactId>");
+            indent(out, indent * 4);
+            out.println("</exclusion>");
+        }
+
+        indent(out, indent * 3);
+        out.println("</exclusions>");
+    }
+
+    private static DependencyDescriptor[] getDependencies(ModuleDescriptor md,
+            PomWriterOptions options) {
+        String[] confs = ConfigurationUtils.replaceWildcards(options.getConfs(), md);
+
+        List result = new ArrayList();
+        DependencyDescriptor[] dds = md.getDependencies();
+        for (int i = 0; i < dds.length; i++) {
+            String[] depConfs = dds[i].getDependencyConfigurations(confs);
+            if ((depConfs != null) && (depConfs.length > 0)) {
+                result.add(dds[i]);
+            }
+        }
+
+        return (DependencyDescriptor[]) result.toArray(new DependencyDescriptor[result.size()]);
+    }
+
+    /**
+     * Wraps an {@link IvyVariableContainer} delegating most method calls to the wrapped instance,
+     * except for a set of variables which are only stored locally in the wrapper, and not
+     * propagated to the wrapped instance.
+     */
+    private static final class IvyVariableContainerWrapper implements IvyVariableContainer {
+
+        private final IvyVariableContainer variables;
+
+        private Map localVariables = new HashMap();
+
+        private IvyVariableContainerWrapper(IvyVariableContainer variables) {
+            this.variables = variables;
+        }
+
+        public void setVariable(String varName, String value, boolean overwrite) {
+            localVariables.put(varName, value);
+        }
+
+        public void setEnvironmentPrefix(String prefix) {
+            variables.setEnvironmentPrefix(prefix);
+        }
+
+        public String getVariable(String name) {
+            String result = variables.getVariable(name);
+            if (result == null) {
+                result = (String) localVariables.get(name);
+            }
+            return result;
+        }
+
+        public Object clone() {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+}
diff --git a/src/java/org/apache/ivy/plugins/parser/m2/PomReader.java b/src/java/org/apache/ivy/plugins/parser/m2/PomReader.java
new file mode 100644
index 0000000..081db57
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/parser/m2/PomReader.java
@@ -0,0 +1,629 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.parser.m2;
+
+import java.io.BufferedInputStream;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.core.module.descriptor.License;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.util.XMLHelper;
+import org.apache.ivy.util.url.URLHandlerRegistry;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+/**
+ * Provides the method to read some data out of the DOM tree of a pom file.
+ */
+public class PomReader {
+
+    private static final String PACKAGING = "packaging";
+
+    private static final String DEPENDENCY = "dependency";
+
+    private static final String DEPENDENCIES = "dependencies";
+
+    private static final String DEPENDENCY_MGT = "dependencyManagement";
+
+    private static final String PROJECT = "project";
+
+    private static final String MODEL = "model";
+
+    private static final String GROUP_ID = "groupId";
+
+    private static final String ARTIFACT_ID = "artifactId";
+
+    private static final String VERSION = "version";
+
+    private static final String DESCRIPTION = "description";
+
+    private static final String HOMEPAGE = "url";
+
+    private static final String LICENSES = "licenses";
+
+    private static final String LICENSE = "license";
+
+    private static final String LICENSE_NAME = "name";
+
+    private static final String LICENSE_URL = "url";
+
+    private static final String PARENT = "parent";
+
+    private static final String SCOPE = "scope";
+
+    private static final String CLASSIFIER = "classifier";
+
+    private static final String OPTIONAL = "optional";
+
+    private static final String EXCLUSIONS = "exclusions";
+
+    private static final String EXCLUSION = "exclusion";
+
+    private static final String DISTRIBUTION_MGT = "distributionManagement";
+
+    private static final String RELOCATION = "relocation";
+
+    private static final String PROPERTIES = "properties";
+
+    private static final String PLUGINS = "plugins";
+
+    private static final String PLUGIN = "plugin";
+
+    private static final String TYPE = "type";
+
+    private HashMap properties = new HashMap();
+
+    private final Element projectElement;
+
+    private final Element parentElement;
+
+    public PomReader(URL descriptorURL, Resource res) throws IOException, SAXException {
+        InputStream stream = new AddDTDFilterInputStream(URLHandlerRegistry.getDefault()
+                .openStream(descriptorURL));
+        InputSource source = new InputSource(stream);
+        source.setSystemId(XMLHelper.toSystemId(descriptorURL));
+        try {
+            Document pomDomDoc = XMLHelper.parseToDom(source, new EntityResolver() {
+                public InputSource resolveEntity(String publicId, String systemId)
+                        throws SAXException, IOException {
+                    if ((systemId != null) && systemId.endsWith("m2-entities.ent")) {
+                        return new InputSource(PomReader.class
+                                .getResourceAsStream("m2-entities.ent"));
+                    }
+                    return null;
+                }
+            });
+            projectElement = pomDomDoc.getDocumentElement();
+            if (!PROJECT.equals(projectElement.getNodeName())
+                    && !MODEL.equals(projectElement.getNodeName())) {
+                throw new SAXParseException("project must be the root tag", res.getName(),
+                        res.getName(), 0, 0);
+            }
+            parentElement = getFirstChildElement(projectElement, PARENT);
+        } finally {
+            try {
+                stream.close();
+            } catch (IOException e) {
+                // ignore
+            }
+        }
+    }
+
+    public boolean hasParent() {
+        return parentElement != null;
+    }
+
+    /**
+     * Add a property if not yet set and value is not null. This garantee that property keep the
+     * first value that is put on it and that the properties are never null.
+     */
+    public void setProperty(String prop, String val) {
+        if (!properties.containsKey(prop) && val != null) {
+            properties.put(prop, val);
+        }
+    }
+
+    public String getGroupId() {
+        String groupId = getFirstChildText(projectElement, GROUP_ID);
+        if (groupId == null) {
+            groupId = getFirstChildText(parentElement, GROUP_ID);
+        }
+        return replaceProps(groupId);
+
+    }
+
+    public String getParentGroupId() {
+        String groupId = getFirstChildText(parentElement, GROUP_ID);
+        if (groupId == null) {
+            groupId = getFirstChildText(projectElement, GROUP_ID);
+        }
+        return replaceProps(groupId);
+    }
+
+    public String getArtifactId() {
+        String val = getFirstChildText(projectElement, ARTIFACT_ID);
+        if (val == null) {
+            val = getFirstChildText(parentElement, ARTIFACT_ID);
+        }
+        return replaceProps(val);
+    }
+
+    public String getParentArtifactId() {
+        String val = getFirstChildText(parentElement, ARTIFACT_ID);
+        if (val == null) {
+            val = getFirstChildText(projectElement, ARTIFACT_ID);
+        }
+        return replaceProps(val);
+    }
+
+    public String getVersion() {
+        String val = getFirstChildText(projectElement, VERSION);
+        if (val == null) {
+            val = getFirstChildText(parentElement, VERSION);
+        }
+        return replaceProps(val);
+    }
+
+    public String getParentVersion() {
+        String val = getFirstChildText(parentElement, VERSION);
+        if (val == null) {
+            val = getFirstChildText(projectElement, VERSION);
+        }
+        return replaceProps(val);
+    }
+
+    public String getPackaging() {
+        String val = getFirstChildText(projectElement, PACKAGING);
+        if (val == null) {
+            val = "jar";
+        }
+        return val;
+    }
+
+    public String getHomePage() {
+        String val = getFirstChildText(projectElement, HOMEPAGE);
+        if (val == null) {
+            val = "";
+        }
+        return val;
+    }
+
+    public String getDescription() {
+        String val = getFirstChildText(projectElement, DESCRIPTION);
+        if (val == null) {
+            val = "";
+        }
+        return val.trim();
+    }
+
+    public License[] getLicenses() {
+        Element licenses = getFirstChildElement(projectElement, LICENSES);
+        if (licenses == null) {
+            return new License[0];
+        }
+        licenses.normalize();
+        List/* <License> */lics = new ArrayList();
+        for (Iterator it = getAllChilds(licenses).iterator(); it.hasNext();) {
+            Element license = (Element) it.next();
+            if (LICENSE.equals(license.getNodeName())) {
+                String name = getFirstChildText(license, LICENSE_NAME);
+                String url = getFirstChildText(license, LICENSE_URL);
+
+                if ((name == null) && (url == null)) {
+                    // move to next license
+                    continue;
+                }
+
+                if (name == null) {
+                    // The license name is required in Ivy but not in a POM!
+                    name = "Unknown License";
+                }
+
+                lics.add(new License(name, url));
+            }
+        }
+        return (License[]) lics.toArray(new License[lics.size()]);
+    }
+
+    public ModuleRevisionId getRelocation() {
+        Element distrMgt = getFirstChildElement(projectElement, DISTRIBUTION_MGT);
+        Element relocation = getFirstChildElement(distrMgt, RELOCATION);
+        if (relocation == null) {
+            return null;
+        } else {
+            String relocGroupId = getFirstChildText(relocation, GROUP_ID);
+            String relocArtId = getFirstChildText(relocation, ARTIFACT_ID);
+            String relocVersion = getFirstChildText(relocation, VERSION);
+            relocGroupId = relocGroupId == null ? getGroupId() : relocGroupId;
+            relocArtId = relocArtId == null ? getArtifactId() : relocArtId;
+            relocVersion = relocVersion == null ? getVersion() : relocVersion;
+            return ModuleRevisionId.newInstance(relocGroupId, relocArtId, relocVersion);
+        }
+    }
+
+    public List /* <PomDependencyData> */getDependencies() {
+        Element dependenciesElement = getFirstChildElement(projectElement, DEPENDENCIES);
+        LinkedList dependencies = new LinkedList();
+        if (dependenciesElement != null) {
+            NodeList childs = dependenciesElement.getChildNodes();
+            for (int i = 0; i < childs.getLength(); i++) {
+                Node node = childs.item(i);
+                if (node instanceof Element && DEPENDENCY.equals(node.getNodeName())) {
+                    dependencies.add(new PomDependencyData((Element) node));
+                }
+            }
+        }
+        return dependencies;
+    }
+
+    public List /* <PomDependencyMgt> */getDependencyMgt() {
+        Element dependenciesElement = getFirstChildElement(projectElement, DEPENDENCY_MGT);
+        dependenciesElement = getFirstChildElement(dependenciesElement, DEPENDENCIES);
+        LinkedList dependencies = new LinkedList();
+        if (dependenciesElement != null) {
+            NodeList childs = dependenciesElement.getChildNodes();
+            for (int i = 0; i < childs.getLength(); i++) {
+                Node node = childs.item(i);
+                if (node instanceof Element && DEPENDENCY.equals(node.getNodeName())) {
+                    dependencies.add(new PomDependencyMgtElement((Element) node));
+                }
+            }
+        }
+        return dependencies;
+    }
+
+    public class PomDependencyMgtElement implements PomDependencyMgt {
+        private final Element depElement;
+
+        public PomDependencyMgtElement(PomDependencyMgtElement copyFrom) {
+            this(copyFrom.depElement);
+        }
+
+        PomDependencyMgtElement(Element depElement) {
+            this.depElement = depElement;
+        }
+
+        /*
+         * (non-Javadoc)
+         * 
+         * @see org.apache.ivy.plugins.parser.m2.PomDependencyMgt#getGroupId()
+         */
+        public String getGroupId() {
+            String val = getFirstChildText(depElement, GROUP_ID);
+            return replaceProps(val);
+        }
+
+        /*
+         * (non-Javadoc)
+         * 
+         * @see org.apache.ivy.plugins.parser.m2.PomDependencyMgt#getArtifaceId()
+         */
+        public String getArtifactId() {
+            String val = getFirstChildText(depElement, ARTIFACT_ID);
+            return replaceProps(val);
+        }
+
+        /*
+         * (non-Javadoc)
+         * 
+         * @see org.apache.ivy.plugins.parser.m2.PomDependencyMgt#getVersion()
+         */
+        public String getVersion() {
+            String val = getFirstChildText(depElement, VERSION);
+            return replaceProps(val);
+        }
+
+        public String getScope() {
+            String val = getFirstChildText(depElement, SCOPE);
+            return replaceProps(val);
+        }
+
+        public List /* <ModuleId> */getExcludedModules() {
+            Element exclusionsElement = getFirstChildElement(depElement, EXCLUSIONS);
+            LinkedList exclusions = new LinkedList();
+            if (exclusionsElement != null) {
+                NodeList childs = exclusionsElement.getChildNodes();
+                for (int i = 0; i < childs.getLength(); i++) {
+                    Node node = childs.item(i);
+                    if (node instanceof Element && EXCLUSION.equals(node.getNodeName())) {
+                        String groupId = getFirstChildText((Element) node, GROUP_ID);
+                        String artifactId = getFirstChildText((Element) node, ARTIFACT_ID);
+                        if ((groupId != null) && (artifactId != null)) {
+                            exclusions.add(ModuleId.newInstance(groupId, artifactId));
+                        }
+                    }
+                }
+            }
+            return exclusions;
+        }
+    }
+
+    public List /* <PomPluginElement> */getPlugins() {
+        LinkedList plugins = new LinkedList();
+
+        Element buildElement = getFirstChildElement(projectElement, "build");
+        if (buildElement == null) {
+            return plugins;
+        }
+
+        Element pluginsElement = getFirstChildElement(buildElement, PLUGINS);
+        if (pluginsElement != null) {
+            NodeList childs = pluginsElement.getChildNodes();
+            for (int i = 0; i < childs.getLength(); i++) {
+                Node node = childs.item(i);
+                if (node instanceof Element && PLUGIN.equals(node.getNodeName())) {
+                    plugins.add(new PomPluginElement((Element) node));
+                }
+            }
+        }
+        return plugins;
+    }
+
+    public class PomPluginElement implements PomDependencyMgt {
+        private Element pluginElement;
+
+        PomPluginElement(Element pluginElement) {
+            this.pluginElement = pluginElement;
+        }
+
+        public String getGroupId() {
+            String val = getFirstChildText(pluginElement, GROUP_ID);
+            return replaceProps(val);
+        }
+
+        public String getArtifactId() {
+            String val = getFirstChildText(pluginElement, ARTIFACT_ID);
+            return replaceProps(val);
+        }
+
+        public String getVersion() {
+            String val = getFirstChildText(pluginElement, VERSION);
+            return replaceProps(val);
+        }
+
+        public String getScope() {
+            return null; // not used
+        }
+
+        public List /* <ModuleId> */getExcludedModules() {
+            return Collections.EMPTY_LIST; // probably not used?
+        }
+    }
+
+    public class PomDependencyData extends PomDependencyMgtElement {
+        private final Element depElement;
+
+        public PomDependencyData(PomDependencyData copyFrom) {
+            this(copyFrom.depElement);
+        }
+
+        PomDependencyData(Element depElement) {
+            super(depElement);
+            this.depElement = depElement;
+        }
+
+        public String getScope() {
+            String val = getFirstChildText(depElement, SCOPE);
+            return replaceProps(val);
+        }
+
+        public String getClassifier() {
+            String val = getFirstChildText(depElement, CLASSIFIER);
+            return replaceProps(val);
+        }
+
+        public String getType() {
+            String val = getFirstChildText(depElement, TYPE);
+            return replaceProps(val);
+        }
+
+        public boolean isOptional() {
+            Element e = getFirstChildElement(depElement, OPTIONAL);
+            return (e != null) && "true".equalsIgnoreCase(getTextContent(e));
+        }
+
+    }
+
+    /**
+     * @return the content of the properties tag into the pom.
+     */
+    public Map/* <String,String> */getPomProperties() {
+        Map pomProperties = new HashMap();
+        Element propsEl = getFirstChildElement(projectElement, PROPERTIES);
+        if (propsEl != null) {
+            propsEl.normalize();
+        }
+        for (Iterator it = getAllChilds(propsEl).iterator(); it.hasNext();) {
+            Element prop = (Element) it.next();
+            pomProperties.put(prop.getNodeName(), getTextContent(prop));
+        }
+        return pomProperties;
+    }
+
+    private String replaceProps(String val) {
+        if (val == null) {
+            return null;
+        } else {
+            return IvyPatternHelper.substituteVariables(val, properties).trim();
+        }
+    }
+
+    private static String getTextContent(Element element) {
+        StringBuffer result = new StringBuffer();
+
+        NodeList childNodes = element.getChildNodes();
+        for (int i = 0; i < childNodes.getLength(); i++) {
+            Node child = childNodes.item(i);
+
+            switch (child.getNodeType()) {
+                case Node.CDATA_SECTION_NODE:
+                case Node.TEXT_NODE:
+                    result.append(child.getNodeValue());
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        return result.toString();
+    }
+
+    private static String getFirstChildText(Element parentElem, String name) {
+        Element node = getFirstChildElement(parentElem, name);
+        if (node != null) {
+            return getTextContent(node);
+        } else {
+            return null;
+        }
+    }
+
+    private static Element getFirstChildElement(Element parentElem, String name) {
+        if (parentElem == null) {
+            return null;
+        }
+        NodeList childs = parentElem.getChildNodes();
+        for (int i = 0; i < childs.getLength(); i++) {
+            Node node = childs.item(i);
+            if (node instanceof Element && name.equals(node.getNodeName())) {
+                return (Element) node;
+            }
+        }
+        return null;
+    }
+
+    private static List/* <Element> */getAllChilds(Element parent) {
+        List r = new LinkedList();
+        if (parent != null) {
+            NodeList childs = parent.getChildNodes();
+            for (int i = 0; i < childs.getLength(); i++) {
+                Node node = childs.item(i);
+                if (node instanceof Element) {
+                    r.add(node);
+                }
+            }
+        }
+        return r;
+    }
+
+    private static final class AddDTDFilterInputStream extends FilterInputStream {
+        private static final int MARK = 10000;
+
+        private static final String DOCTYPE = "<!DOCTYPE project SYSTEM \"m2-entities.ent\">\n";
+
+        private int count;
+
+        private byte[] prefix = DOCTYPE.getBytes();
+
+        private AddDTDFilterInputStream(InputStream in) throws IOException {
+            super(new BufferedInputStream(in));
+
+            this.in.mark(MARK);
+
+            // TODO: we should really find a better solution for this...
+            // maybe we could use a FilterReader instead of a FilterInputStream?
+            int byte1 = this.in.read();
+            int byte2 = this.in.read();
+            int byte3 = this.in.read();
+
+            if (byte1 == 239 && byte2 == 187 && byte3 == 191) {
+                // skip the UTF-8 BOM
+                this.in.mark(MARK);
+            } else {
+                this.in.reset();
+            }
+
+            int bytesToSkip = 0;
+            LineNumberReader reader = new LineNumberReader(new InputStreamReader(this.in, "UTF-8"),
+                    100);
+            String firstLine = reader.readLine();
+            if (firstLine != null) {
+                String trimmed = firstLine.trim();
+                if (trimmed.startsWith("<?xml ")) {
+                    int endIndex = trimmed.indexOf("?>");
+                    String xmlDecl = trimmed.substring(0, endIndex + 2);
+                    prefix = (xmlDecl + "\n" + DOCTYPE).getBytes();
+                    bytesToSkip = xmlDecl.getBytes().length;
+                }
+            } else {
+                prefix = new byte[0];
+            }
+
+            this.in.reset();
+            for (int i = 0; i < bytesToSkip; i++) {
+                this.in.read();
+            }
+        }
+
+        public int read() throws IOException {
+            if (count < prefix.length) {
+                return prefix[count++];
+            }
+
+            int result = super.read();
+            return result;
+        }
+
+        public int read(byte[] b, int off, int len) throws IOException {
+            if (b == null) {
+                throw new NullPointerException();
+            } else if ((off < 0) || (off > b.length) || (len < 0) || ((off + len) > b.length)
+                    || ((off + len) < 0)) {
+                throw new IndexOutOfBoundsException();
+            } else if (len == 0) {
+                return 0;
+            }
+
+            int nbrBytesCopied = 0;
+
+            if (count < prefix.length) {
+                int nbrBytesFromPrefix = Math.min(prefix.length - count, len);
+                System.arraycopy(prefix, count, b, off, nbrBytesFromPrefix);
+                nbrBytesCopied = nbrBytesFromPrefix;
+            }
+
+            if (nbrBytesCopied < len) {
+                nbrBytesCopied += in.read(b, off + nbrBytesCopied, len - nbrBytesCopied);
+            }
+
+            count += nbrBytesCopied;
+            return nbrBytesCopied;
+        }
+    }
+
+}
diff --git a/src/java/org/apache/ivy/plugins/parser/m2/PomWriterOptions.java b/src/java/org/apache/ivy/plugins/parser/m2/PomWriterOptions.java
new file mode 100644
index 0000000..544d932
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/parser/m2/PomWriterOptions.java
@@ -0,0 +1,214 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.parser.m2;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class PomWriterOptions {
+
+    private String[] confs;
+
+    private String licenseHeader;
+
+    private ConfigurationScopeMapping mapping;
+
+    private boolean printIvyInfo = true;
+
+    private String artifactName;
+
+    private String artifactPackaging;
+
+    private List/* <ExtraDependency> */extraDependencies = new ArrayList();
+
+    private String description;
+
+    private File template;
+
+    public File getTemplate() {
+        return template;
+    }
+
+    public PomWriterOptions setTemplate(File template) {
+        this.template = template;
+        return this;
+    }
+
+    public String[] getConfs() {
+        return confs;
+    }
+
+    public PomWriterOptions setConfs(String[] confs) {
+        this.confs = confs;
+        return this;
+    }
+
+    public String getLicenseHeader() {
+        return licenseHeader;
+    }
+
+    public PomWriterOptions setLicenseHeader(String licenseHeader) {
+        this.licenseHeader = licenseHeader;
+        if (this.licenseHeader != null) {
+            this.licenseHeader = this.licenseHeader.trim();
+        }
+        return this;
+    }
+
+    public ConfigurationScopeMapping getMapping() {
+        return mapping;
+    }
+
+    public PomWriterOptions setMapping(ConfigurationScopeMapping mapping) {
+        this.mapping = mapping;
+        return this;
+    }
+
+    public boolean isPrintIvyInfo() {
+        return printIvyInfo;
+    }
+
+    public PomWriterOptions setPrintIvyInfo(boolean printIvyInfo) {
+        this.printIvyInfo = printIvyInfo;
+        return this;
+    }
+
+    public List/* <ExtraDependency> */getExtraDependencies() {
+        return extraDependencies;
+    }
+
+    public PomWriterOptions setExtraDependencies(List/* <ExtraDependency> */extraDependencies) {
+        this.extraDependencies = extraDependencies;
+        return this;
+    }
+
+    public String getArtifactName() {
+        return artifactName;
+    }
+
+    public PomWriterOptions setArtifactName(String artifactName) {
+        this.artifactName = artifactName;
+        return this;
+    }
+
+    public String getArtifactPackaging() {
+        return artifactPackaging;
+    }
+
+    public PomWriterOptions setArtifactPackaging(String artifactPackaging) {
+        this.artifactPackaging = artifactPackaging;
+        return this;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public PomWriterOptions setDescription(String description) {
+        this.description = description;
+        return this;
+    }
+
+    public static class ConfigurationScopeMapping {
+        private Map/* <String,String> */scopes;
+
+        public ConfigurationScopeMapping(Map/* <String,String> */scopesMapping) {
+            this.scopes = new HashMap(scopesMapping);
+        }
+
+        /**
+         * Returns the scope mapped to the given configuration array.
+         * 
+         * @param confs
+         *            the configurations for which the scope should be returned
+         * @return the scope to which the conf is mapped
+         */
+        public String getScope(String[] confs) {
+            for (int i = 0; i < confs.length; i++) {
+                if (scopes.containsKey(confs[i])) {
+                    return (String) scopes.get(confs[i]);
+                }
+            }
+
+            return null;
+        }
+
+        public boolean isOptional(String[] confs) {
+            return getScope(confs) == null;
+        }
+    }
+
+    public static class ExtraDependency {
+        private String group;
+
+        private String artifact;
+
+        private String version;
+
+        private String scope;
+
+        private String type;
+
+        private String classifier;
+
+        private boolean optional;
+
+        public ExtraDependency(String group, String artifact, String version, String scope,
+                String type, String classifier, boolean optional) {
+            this.group = group;
+            this.artifact = artifact;
+            this.version = version;
+            this.scope = scope;
+            this.type = type;
+            this.classifier = classifier;
+            this.optional = optional;
+        }
+
+        public String getGroup() {
+            return group;
+        }
+
+        public String getArtifact() {
+            return artifact;
+        }
+
+        public String getVersion() {
+            return version;
+        }
+
+        public String getScope() {
+            return scope;
+        }
+
+        public String getType() {
+            return type;
+        }
+
+        public String getClassifier() {
+            return classifier;
+        }
+
+        public boolean isOptional() {
+            return optional;
+        }
+    }
+
+}
diff --git a/src/java/org/apache/ivy/plugins/parser/m2/m2-entities.ent b/src/java/org/apache/ivy/plugins/parser/m2/m2-entities.ent
new file mode 100644
index 0000000..ae1ba09
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/parser/m2/m2-entities.ent
@@ -0,0 +1,114 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<!ENTITY nbsp   " ">
+<!ENTITY iexcl  "¡">
+<!ENTITY cent   "¢">
+<!ENTITY pound  "£">
+<!ENTITY curren "¤">
+<!ENTITY yen    "¥">
+<!ENTITY brvbar "¦">
+<!ENTITY sect   "§">
+<!ENTITY uml    "¨">
+<!ENTITY copy   "©">
+<!ENTITY ordf   "ª">
+<!ENTITY laquo  "«">
+<!ENTITY not    "¬">
+<!ENTITY shy    "­">
+<!ENTITY reg    "®">
+<!ENTITY macr   "¯">
+<!ENTITY deg    "°">
+<!ENTITY plusmn "±">
+<!ENTITY sup2   "²">
+<!ENTITY sup3   "³">
+<!ENTITY acute  "´">
+<!ENTITY micro  "µ">
+<!ENTITY para   "¶">
+<!ENTITY middot "·">
+<!ENTITY cedil  "¸">
+<!ENTITY sup1   "¹">
+<!ENTITY ordm   "º">
+<!ENTITY raquo  "»">
+<!ENTITY frac14 "¼">
+<!ENTITY frac12 "½">
+<!ENTITY frac34 "¾">
+<!ENTITY iquest "¿">
+<!ENTITY Agrave "À">
+<!ENTITY Aacute "Á">
+<!ENTITY Acirc  "Â">
+<!ENTITY Atilde "Ã">
+<!ENTITY Auml   "Ä">
+<!ENTITY Aring  "Å">
+<!ENTITY AElig  "Æ">
+<!ENTITY Ccedil "Ç">
+<!ENTITY Egrave "È">
+<!ENTITY Eacute "É">
+<!ENTITY Ecirc  "Ê">
+<!ENTITY Euml   "Ë">
+<!ENTITY Igrave "Ì">
+<!ENTITY Iacute "Í">
+<!ENTITY Icirc  "Î">
+<!ENTITY Iuml   "Ï">
+<!ENTITY ETH    "Ð">
+<!ENTITY Ntilde "Ñ">
+<!ENTITY Ograve "Ò">
+<!ENTITY Oacute "Ó">
+<!ENTITY Ocirc  "Ô">
+<!ENTITY Otilde "Õ">
+<!ENTITY Ouml   "Ö">
+<!ENTITY times  "×">
+<!ENTITY Oslash "Ø">
+<!ENTITY Ugrave "Ù">
+<!ENTITY Uacute "Ú">
+<!ENTITY Ucirc  "Û">
+<!ENTITY Uuml   "Ü">
+<!ENTITY Yacute "Ý">
+<!ENTITY THORN  "Þ">
+<!ENTITY szlig  "ß">
+<!ENTITY agrave "à">
+<!ENTITY aacute "á">
+<!ENTITY acirc  "â">
+<!ENTITY atilde "ã">
+<!ENTITY auml   "ä">
+<!ENTITY aring  "å">
+<!ENTITY aelig  "æ">
+<!ENTITY ccedil "ç">
+<!ENTITY egrave "è">
+<!ENTITY eacute "é">
+<!ENTITY ecirc  "ê">
+<!ENTITY euml   "ë">
+<!ENTITY igrave "ì">
+<!ENTITY iacute "í">
+<!ENTITY icirc  "î">
+<!ENTITY iuml   "ï">
+<!ENTITY eth    "ð">
+<!ENTITY ntilde "ñ">
+<!ENTITY ograve "ò">
+<!ENTITY oacute "ó">
+<!ENTITY ocirc  "ô">
+<!ENTITY otilde "õ">
+<!ENTITY ouml   "ö">
+<!ENTITY divide "÷">
+<!ENTITY oslash "ø">
+<!ENTITY ugrave "ù">
+<!ENTITY uacute "ú">
+<!ENTITY ucirc  "û">
+<!ENTITY uuml   "ü">
+<!ENTITY yacute "ý">
+<!ENTITY thorn  "þ">
+<!ENTITY yuml   "ÿ">
diff --git a/src/java/org/apache/ivy/plugins/parser/m2/pom.template b/src/java/org/apache/ivy/plugins/parser/m2/pom.template
new file mode 100644
index 0000000..d0d4bd9
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/parser/m2/pom.template
@@ -0,0 +1,33 @@
+SKIP_LINE  ***************************************************************
+SKIP_LINE  * Licensed to the Apache Software Foundation (ASF) under one
+SKIP_LINE  * or more contributor license agreements.  See the NOTICE file
+SKIP_LINE  * distributed with this work for additional information
+SKIP_LINE  * regarding copyright ownership.  The ASF licenses this file
+SKIP_LINE  * to you under the Apache License, Version 2.0 (the
+SKIP_LINE  * "License"); you may not use this file except in compliance
+SKIP_LINE  * with the License.  You may obtain a copy of the License at
+SKIP_LINE  * 
+SKIP_LINE  *   http://www.apache.org/licenses/LICENSE-2.0
+SKIP_LINE  * 
+SKIP_LINE  * Unless required by applicable law or agreed to in writing,
+SKIP_LINE  * software distributed under the License is distributed on an
+SKIP_LINE  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+SKIP_LINE  * KIND, either express or implied.  See the License for the
+SKIP_LINE  * specific language governing permissions and limitations
+SKIP_LINE  * under the License.
+SKIP_LINE  ***************************************************************
+<?xml version="1.0" encoding="UTF-8"?>
+${ivy.pom.license}
+${ivy.pom.header}
+<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/xsd/maven-4.0.0.xsd">
+
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>${ivy.pom.groupId}</groupId>
+  <artifactId>${ivy.pom.artifactId}</artifactId>
+  <packaging>${ivy.pom.packaging}</packaging>
+  <version>${ivy.pom.version}</version>
+  <name>${ivy.pom.name}</name>
+  <description>${ivy.pom.description}</description>
+  <url>${ivy.pom.url}</url>
+</project>
diff --git a/src/java/org/apache/ivy/plugins/parser/xml/UpdateOptions.java b/src/java/org/apache/ivy/plugins/parser/xml/UpdateOptions.java
new file mode 100644
index 0000000..994b21e
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/parser/xml/UpdateOptions.java
@@ -0,0 +1,222 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.parser.xml;
+
+import java.util.Collections;
+import java.util.Date;
+import java.util.Map;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.plugins.namespace.Namespace;
+import org.apache.ivy.plugins.parser.ParserSettings;
+
+public class UpdateOptions {
+    /**
+     * Settings to use for update, may be <code>null</code>.
+     */
+    private ParserSettings settings = null;
+
+    /**
+     * Namespace in which the module to update is, may be <code>null</code>.
+     */
+    private Namespace namespace = null;
+
+    /**
+     * Map from ModuleId of dependencies to new revision (as String)
+     */
+    private Map resolvedRevisions = Collections.EMPTY_MAP;
+
+    /**
+     * Map from ModuleId of dependencies to new branch (as String)
+     */
+    private Map resolvedBranches = Collections.EMPTY_MAP;
+
+    /**
+     * the new status, <code>null</code> to keep the old one
+     */
+    private String status = null;
+
+    /**
+     * the new revision, <code>null</code> to keep the old one
+     */
+    private String revision = null;
+
+    /**
+     * the new publication date, <code>null</code> to keep the old one
+     */
+    private Date pubdate = null;
+
+    /**
+     * Should included information be replaced
+     */
+    private boolean replaceInclude = true;
+
+    /**
+     * Should parent descriptor be merged inline
+     */
+    private boolean merge = true;
+
+    private ModuleDescriptor mergedDescriptor = null;
+
+    /**
+     * Configurations to exclude during update, or <code>null</code> to keep all confs.
+     */
+    private String[] confsToExclude = null;
+
+    /**
+     * True to set branch information on dependencies to default branch when omitted, false to keep
+     * it as is.
+     */
+    private boolean updateBranch = true;
+
+    private String branch;
+
+    /**
+     * True to indicate that the revConstraint attribute should be generated if applicable, false to
+     * never generate the revConstraint attribute.
+     */
+    private boolean generateRevConstraint = true;
+
+    public ParserSettings getSettings() {
+        return settings;
+    }
+
+    public UpdateOptions setSettings(ParserSettings settings) {
+        this.settings = settings;
+        return this;
+    }
+
+    public Namespace getNamespace() {
+        return namespace;
+    }
+
+    public UpdateOptions setNamespace(Namespace ns) {
+        this.namespace = ns;
+        return this;
+    }
+
+    public Map getResolvedRevisions() {
+        return resolvedRevisions;
+    }
+
+    public UpdateOptions setResolvedRevisions(Map resolvedRevisions) {
+        this.resolvedRevisions = resolvedRevisions;
+        return this;
+    }
+
+    public String getStatus() {
+        return status;
+    }
+
+    public UpdateOptions setStatus(String status) {
+        this.status = status;
+        return this;
+    }
+
+    public String getRevision() {
+        return revision;
+    }
+
+    public UpdateOptions setRevision(String revision) {
+        this.revision = revision;
+        return this;
+    }
+
+    public Date getPubdate() {
+        return pubdate;
+    }
+
+    public UpdateOptions setPubdate(Date pubdate) {
+        this.pubdate = pubdate;
+        return this;
+    }
+
+    public boolean isReplaceInclude() {
+        return replaceInclude;
+    }
+
+    public UpdateOptions setReplaceInclude(boolean replaceInclude) {
+        this.replaceInclude = replaceInclude;
+        return this;
+    }
+
+    public boolean isMerge() {
+        // only return true if merge is set to true and if there is something to merge!
+        return merge && (mergedDescriptor != null)
+                && (mergedDescriptor.getInheritedDescriptors().length > 0);
+    }
+
+    public UpdateOptions setMerge(boolean merge) {
+        this.merge = merge;
+        return this;
+    }
+
+    public ModuleDescriptor getMergedDescriptor() {
+        return mergedDescriptor;
+    }
+
+    public UpdateOptions setMergedDescriptor(ModuleDescriptor mergedDescriptor) {
+        this.mergedDescriptor = mergedDescriptor;
+        return this;
+    }
+
+    public String[] getConfsToExclude() {
+        return confsToExclude;
+    }
+
+    public UpdateOptions setConfsToExclude(String[] confsToExclude) {
+        this.confsToExclude = confsToExclude;
+        return this;
+    }
+
+    public boolean isUpdateBranch() {
+        return updateBranch;
+    }
+
+    public UpdateOptions setUpdateBranch(boolean updateBranch) {
+        this.updateBranch = updateBranch;
+        return this;
+    }
+
+    public String getBranch() {
+        return branch;
+    }
+
+    public UpdateOptions setBranch(String pubBranch) {
+        this.branch = pubBranch;
+        return this;
+    }
+
+    public boolean isGenerateRevConstraint() {
+        return generateRevConstraint;
+    }
+
+    public UpdateOptions setGenerateRevConstraint(boolean generateRevConstraint) {
+        this.generateRevConstraint = generateRevConstraint;
+        return this;
+    }
+
+    public Map getResolvedBranches() {
+        return resolvedBranches;
+    }
+
+    public UpdateOptions setResolvedBranches(Map resolvedBranches) {
+        this.resolvedBranches = resolvedBranches;
+        return this;
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/parser/xml/XmlModuleDescriptorParser.java b/src/java/org/apache/ivy/plugins/parser/xml/XmlModuleDescriptorParser.java
new file mode 100644
index 0000000..de28d74
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/parser/xml/XmlModuleDescriptorParser.java
@@ -0,0 +1,1387 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.parser.xml;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.text.ParseException;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Stack;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.apache.ivy.Ivy;
+import org.apache.ivy.core.IvyContext;
+import org.apache.ivy.core.module.descriptor.Configuration;
+import org.apache.ivy.core.module.descriptor.ConfigurationAware;
+import org.apache.ivy.core.module.descriptor.DefaultArtifact;
+import org.apache.ivy.core.module.descriptor.DefaultDependencyArtifactDescriptor;
+import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.DefaultExcludeRule;
+import org.apache.ivy.core.module.descriptor.DefaultExtendsDescriptor;
+import org.apache.ivy.core.module.descriptor.DefaultIncludeRule;
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.apache.ivy.core.module.descriptor.DependencyArtifactDescriptor;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.ExcludeRule;
+import org.apache.ivy.core.module.descriptor.ExtraInfoHolder;
+import org.apache.ivy.core.module.descriptor.IncludeRule;
+import org.apache.ivy.core.module.descriptor.License;
+import org.apache.ivy.core.module.descriptor.MDArtifact;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.descriptor.OverrideDependencyDescriptorMediator;
+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.core.resolve.ResolveData;
+import org.apache.ivy.core.resolve.ResolveEngine;
+import org.apache.ivy.core.resolve.ResolveOptions;
+import org.apache.ivy.core.resolve.ResolvedModuleRevision;
+import org.apache.ivy.plugins.conflict.ConflictManager;
+import org.apache.ivy.plugins.conflict.FixedConflictManager;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+import org.apache.ivy.plugins.namespace.NameSpaceHelper;
+import org.apache.ivy.plugins.namespace.Namespace;
+import org.apache.ivy.plugins.parser.AbstractModuleDescriptorParser;
+import org.apache.ivy.plugins.parser.ModuleDescriptorParser;
+import org.apache.ivy.plugins.parser.ModuleDescriptorParserRegistry;
+import org.apache.ivy.plugins.parser.ParserSettings;
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.plugins.repository.file.FileResource;
+import org.apache.ivy.plugins.repository.url.URLResource;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.apache.ivy.util.DateUtil;
+import org.apache.ivy.util.FileUtil;
+import org.apache.ivy.util.Message;
+import org.apache.ivy.util.XMLHelper;
+import org.apache.ivy.util.extendable.ExtendableItemHelper;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+
+/**
+ * Parses an xml ivy file and output a ModuleDescriptor. For dependency and performance reasons, it
+ * uses only the SAX API, which makes the parsing code harder to understand.
+ */
+public class XmlModuleDescriptorParser extends AbstractModuleDescriptorParser {
+    static final String[] DEPENDENCY_REGULAR_ATTRIBUTES = new String[] {"org", "name", "branch",
+            "branchConstraint", "rev", "revConstraint", "force", "transitive", "changing", "conf"};
+
+    private static final XmlModuleDescriptorParser INSTANCE = new XmlModuleDescriptorParser();
+
+    public static XmlModuleDescriptorParser getInstance() {
+        return INSTANCE;
+    }
+
+    protected XmlModuleDescriptorParser() {
+    }
+
+    /**
+     * @param settings
+     * @param xmlURL
+     *            the url pointing to the file to parse
+     * @param res
+     *            the real resource to parse, used for log only
+     * @param validate
+     * @return
+     * @throws ParseException
+     * @throws IOException
+     */
+    public ModuleDescriptor parseDescriptor(ParserSettings ivySettings, URL xmlURL, Resource res,
+            boolean validate) throws ParseException, IOException {
+        Parser parser = newParser(ivySettings);
+        parser.setValidate(validate);
+        parser.setResource(res);
+        parser.setInput(xmlURL);
+        parser.parse();
+        return parser.getModuleDescriptor();
+    }
+
+    /** Used for test purpose */
+    ModuleDescriptor parseDescriptor(ParserSettings ivySettings, InputStream descriptor,
+            Resource res, boolean validate) throws ParseException, IOException {
+        Parser parser = newParser(ivySettings);
+        parser.setValidate(validate);
+        parser.setResource(res);
+        parser.setInput(descriptor);
+        parser.parse();
+        return parser.getModuleDescriptor();
+    }
+
+    /**
+     * Instantiates a Parser instance responsible for actual parsing of Ivy files.
+     * <p>
+     * Override this method if you want to use a custom Parser.
+     * </p>
+     * 
+     * @param ivySettings
+     *            the settings to use during parsing
+     * @return the Parser instance used for parsing Ivy files
+     */
+    protected Parser newParser(ParserSettings ivySettings) {
+        return new Parser(this, ivySettings);
+    }
+
+    public boolean accept(Resource res) {
+        return true; // this the default parser, it thus accepts all resources
+    }
+
+    public void toIvyFile(InputStream is, Resource res, File destFile, ModuleDescriptor md)
+            throws IOException, ParseException {
+        try {
+            Namespace ns = null;
+            if (md instanceof DefaultModuleDescriptor) {
+                DefaultModuleDescriptor dmd = (DefaultModuleDescriptor) md;
+                ns = dmd.getNamespace();
+            }
+            XmlModuleDescriptorUpdater.update(
+                is,
+                res,
+                destFile,
+                new UpdateOptions().setSettings(IvyContext.getContext().getSettings())
+                        .setStatus(md.getStatus())
+                        .setRevision(md.getResolvedModuleRevisionId().getRevision())
+                        .setPubdate(md.getResolvedPublicationDate()).setUpdateBranch(false)
+                        .setNamespace(ns));
+        } catch (SAXException e) {
+            ParseException ex = new ParseException("exception occurred while parsing " + res, 0);
+            ex.initCause(e);
+            throw ex;
+        } finally {
+            if (is != null) {
+                is.close();
+            }
+        }
+    }
+
+    public static class Parser extends AbstractParser {
+        public static final class State {
+            public static final int NONE = 0;
+
+            public static final int INFO = 1;
+
+            public static final int CONF = 2;
+
+            public static final int PUB = 3;
+
+            public static final int DEP = 4;
+
+            public static final int DEP_ARTIFACT = 5;
+
+            public static final int ARTIFACT_INCLUDE = 6;
+
+            public static final int ARTIFACT_EXCLUDE = 7;
+
+            public static final int CONFLICT = 8;
+
+            public static final int EXCLUDE = 9;
+
+            public static final int DEPS = 10;
+
+            public static final int DESCRIPTION = 11;
+
+            public static final int EXTRA_INFO = 12;
+
+            private State() {
+            }
+        }
+
+        protected static final List ALLOWED_VERSIONS = Arrays.asList(new String[] {"1.0", "1.1",
+                "1.2", "1.3", "1.4", "2.0", "2.1", "2.2", "2.3", "2.4"});
+
+        /* how and what do we have to parse */
+        private ParserSettings settings;
+
+        private boolean validate = true;
+
+        private URL descriptorURL;
+
+        private InputStream descriptorInput;
+
+        /* Parsing state */
+        private int state = State.NONE;
+
+        private PatternMatcher defaultMatcher;
+
+        private DefaultDependencyDescriptor dd;
+
+        private ConfigurationAware confAware;
+
+        private MDArtifact artifact;
+
+        private String conf;
+
+        private boolean artifactsDeclared = false;
+
+        private StringBuffer buffer;
+
+        private String descriptorVersion;
+
+        private String[] publicationsDefaultConf;
+
+        private Stack<ExtraInfoHolder> extraInfoStack = new Stack<ExtraInfoHolder>();
+
+        public Parser(ModuleDescriptorParser parser, ParserSettings ivySettings) {
+            super(parser);
+            settings = ivySettings;
+        }
+
+        public void setInput(InputStream descriptorInput) {
+            this.descriptorInput = descriptorInput;
+        }
+
+        public void setInput(URL descriptorURL) {
+            this.descriptorURL = descriptorURL;
+        }
+
+        public void setValidate(boolean validate) {
+            this.validate = validate;
+        }
+
+        public void parse() throws ParseException, IOException {
+            try {
+                URL schemaURL = validate ? getSchemaURL() : null;
+                if (descriptorURL != null) {
+                    XMLHelper.parse(descriptorURL, schemaURL, this);
+                } else {
+                    XMLHelper.parse(descriptorInput, schemaURL, this, null);
+                }
+                checkConfigurations();
+                replaceConfigurationWildcards();
+                getMd().setModuleArtifact(
+                    DefaultArtifact.newIvyArtifact(getMd().getResolvedModuleRevisionId(), getMd()
+                            .getPublicationDate()));
+                if (!artifactsDeclared) {
+                    String[] confs = getMd().getConfigurationsNames();
+                    for (int i = 0; i < confs.length; i++) {
+                        getMd().addArtifact(
+                            confs[i],
+                            new MDArtifact(getMd(), getMd().getModuleRevisionId().getName(), "jar",
+                                    "jar"));
+                    }
+                }
+                getMd().check();
+            } catch (ParserConfigurationException ex) {
+                IllegalStateException ise = new IllegalStateException(ex.getMessage() + " in "
+                        + descriptorURL);
+                ise.initCause(ex);
+                throw ise;
+            } catch (Exception ex) {
+                checkErrors();
+                ParseException pe = new ParseException(ex.getMessage() + " in " + descriptorURL, 0);
+                pe.initCause(ex);
+                throw pe;
+            }
+        }
+
+        public void startElement(String uri, String localName, String qName, Attributes attributes)
+                throws SAXException {
+            try {
+                if (state == State.DESCRIPTION) {
+                    // make sure we don't interpret any tag while in description tag
+                    getBuffer().append("<").append(qName);
+                    for (int i = 0; i < attributes.getLength(); i++) {
+                        getBuffer().append(" ");
+                        getBuffer().append(attributes.getQName(i));
+                        getBuffer().append("=\"");
+                        getBuffer().append(attributes.getValue(i));
+                        getBuffer().append("\"");
+                    }
+                    getBuffer().append(">");
+                    return;
+                } else if ("ivy-module".equals(qName)) {
+                    ivyModuleStarted(attributes);
+                } else if ("info".equals(qName)) {
+                    infoStarted(attributes);
+                } else if (state == State.INFO && "extends".equals(qName)) {
+                    extendsStarted(attributes);
+                } else if (state == State.INFO && "license".equals(qName)) {
+                    getMd().addLicense(
+                        new License(settings.substitute(attributes.getValue("name")), settings
+                                .substitute(attributes.getValue("url"))));
+                } else if (state == State.INFO && "description".equals(qName)) {
+                    getMd().setHomePage(settings.substitute(attributes.getValue("homepage")));
+                    state = State.DESCRIPTION;
+                    buffer = new StringBuffer();
+                } else if (state == State.INFO && "ivyauthor".equals(qName)) {
+                    // nothing to do, we don't store this
+                } else if (state == State.INFO && "repository".equals(qName)) {
+                    // nothing to do, we don't store this
+                } else if (state == State.EXTRA_INFO || state == State.INFO
+                        && isOtherNamespace(qName)) {
+                    buffer = new StringBuffer();
+                    state = State.EXTRA_INFO;
+                    ExtraInfoHolder extraInfo = new ExtraInfoHolder();
+                    extraInfo.setName(qName);
+                    for (int i = 0; i < attributes.getLength(); i++) {
+                        extraInfo.getAttributes().put(attributes.getQName(i),
+                            attributes.getValue(i));
+                    }
+                    extraInfoStack.push(extraInfo);
+                } else if ("configurations".equals(qName)) {
+                    configurationStarted(attributes);
+                } else if ("publications".equals(qName)) {
+                    publicationsStarted(attributes);
+                } else if ("dependencies".equals(qName)) {
+                    dependenciesStarted(attributes);
+                } else if ("conflicts".equals(qName)) {
+                    if (!descriptorVersion.startsWith("1.")) {
+                        Message.deprecated("using conflicts section is deprecated: "
+                                + "please use hints section instead. Ivy file URL: "
+                                + descriptorURL);
+                    }
+                    state = State.CONFLICT;
+                    checkConfigurations();
+                } else if ("artifact".equals(qName)) {
+                    artifactStarted(qName, attributes);
+                } else if ("include".equals(qName) && state == State.DEP) {
+                    addIncludeRule(qName, attributes);
+                } else if ("exclude".equals(qName) && state == State.DEP) {
+                    addExcludeRule(qName, attributes);
+                } else if ("exclude".equals(qName) && state == State.DEPS) {
+                    state = State.EXCLUDE;
+                    parseRule(qName, attributes);
+                    getMd().addExcludeRule((ExcludeRule) confAware);
+                } else if ("dependency".equals(qName)) {
+                    dependencyStarted(attributes);
+                } else if ("conf".equals(qName)) {
+                    confStarted(attributes);
+                } else if ("mapped".equals(qName)) {
+                    dd.addDependencyConfiguration(conf,
+                        settings.substitute(attributes.getValue("name")));
+                } else if (("conflict".equals(qName) && state == State.DEPS)
+                        || "manager".equals(qName) && state == State.CONFLICT) {
+                    managerStarted(attributes, state == State.CONFLICT ? "name" : "manager");
+                } else if ("override".equals(qName) && state == State.DEPS) {
+                    mediationOverrideStarted(attributes);
+                } else if ("include".equals(qName) && state == State.CONF) {
+                    includeConfStarted(attributes);
+                } else if (validate && state != State.EXTRA_INFO && state != State.DESCRIPTION) {
+                    addError("unknown tag " + qName);
+                }
+            } catch (Exception ex) {
+                if (ex instanceof SAXException) {
+                    throw (SAXException) ex;
+                }
+                SAXException sax = new SAXException("Problem occurred while parsing ivy file: "
+                        + ex.getMessage(), ex);
+                sax.initCause(ex);
+                throw sax;
+            }
+        }
+
+        /**
+         * Default parent location to check (for dev ONLY)
+         * 
+         * @return a relative path to a parent module descriptor
+         */
+        protected String getDefaultParentLocation() {
+            return "../ivy.xml";
+        }
+
+        /**
+         * Handle extends elements. It checks :
+         * <ul>
+         * <li>filesystem based on location attribute, if no one is specified it will check the
+         * default parent location</li>
+         * <li>cache to find a resolved parent descriptor</li>
+         * <li>ask repositories to retrieve the parent module descriptor</li>
+         * </ul>
+         * 
+         * @param attributes
+         * @throws ParseException
+         */
+        protected void extendsStarted(Attributes attributes) throws ParseException {
+            String parentOrganisation = settings.substitute(attributes.getValue("organisation"));
+            String parentModule = settings.substitute(attributes.getValue("module"));
+            String parentRevision = attributes.getValue("revision") != null ? settings
+                    .substitute(attributes.getValue("revision")) : Ivy.getWorkingRevision();
+            String location = attributes.getValue("location") != null ? settings
+                    .substitute(attributes.getValue("location")) : getDefaultParentLocation();
+            ModuleDescriptor parent = null;
+
+            String extendType = attributes.getValue("extendType") != null ? settings
+                    .substitute(attributes.getValue("extendType").toLowerCase(Locale.US)) : "all";
+
+            List/* <String> */extendTypes = Arrays.asList(extendType.split(","));
+            ModuleId parentMid = new ModuleId(parentOrganisation, parentModule);
+            ModuleRevisionId parentMrid = new ModuleRevisionId(parentMid, parentRevision);
+
+            // check on filesystem based on location attribute (for dev ONLY)
+            boolean local = false;
+            try {
+                parent = parseParentModuleOnFilesystem(location);
+                if (parent != null) {
+                    ModuleId foundMid = parent.getResolvedModuleRevisionId().getModuleId();
+                    if (!foundMid.equals(parentMid)) {
+                        // the filesystem contains a parent module with different organisation
+                        // or module name; ignore that parent module
+                        Message.info("Found a parent module with unexpected ModuleRevisionId at source location "
+                                + location
+                                + "! Expected: "
+                                + parentMid
+                                + ". Found: "
+                                + foundMid
+                                + ". This parent module will be ignored.");
+                        parent = null;
+                    }
+                }
+
+                local = parent != null;
+            } catch (IOException e) {
+                Message.warn("Unable to parse included ivy file " + location, e);
+            }
+
+            // if not found, tries to resolve using repositories
+            if (parent == null) {
+                try {
+                    parent = parseOtherIvyFile(parentMrid);
+                } catch (ParseException e) {
+                    Message.warn("Unable to parse included ivy file for " + parentMrid.toString(),
+                        e);
+                }
+            }
+
+            // if still not found throw an exception
+            if (parent == null) {
+                throw new ParseException("Unable to parse included ivy file for "
+                        + parentMrid.toString(), 0);
+            }
+
+            DefaultExtendsDescriptor ed = new DefaultExtendsDescriptor(parent, location,
+                    (String[]) extendTypes.toArray(new String[extendTypes.size()]), local);
+            getMd().addInheritedDescriptor(ed);
+
+            mergeWithOtherModuleDescriptor(extendTypes, parent);
+        }
+
+        /**
+         * Merge current module with a given module descriptor and specify what should be inherited
+         * through extendTypes argument
+         * 
+         * @param extendTypes
+         *            specify what should be inherited
+         * @param parent
+         *            a given parent module descriptor
+         */
+        protected void mergeWithOtherModuleDescriptor(List/* <String> */extendTypes,
+                ModuleDescriptor parent) throws ParseException {
+
+            if (extendTypes.contains("all")) {
+                mergeAll(parent);
+            } else {
+                if (extendTypes.contains("info")) {
+                    mergeInfo(parent);
+                }
+
+                if (extendTypes.contains("configurations")) {
+                    mergeConfigurations(parent);
+                }
+
+                if (extendTypes.contains("dependencies")) {
+                    mergeDependencies(parent.getDependencies());
+                }
+
+                if (extendTypes.contains("description")) {
+                    mergeDescription(parent.getDescription());
+                }
+                if (extendTypes.contains("licenses")) {
+                    mergeLicenses(parent.getLicenses());
+                }
+                if (extendTypes.contains("excludes")) {
+                    mergeExcludes(parent.getAllExcludeRules());
+                }
+            }
+
+        }
+
+        /**
+         * Merge everything from a given parent
+         * 
+         * @param parent
+         *            a given parent module desciptor
+         */
+        protected void mergeAll(ModuleDescriptor parent) {
+            mergeInfo(parent);
+            mergeConfigurations(parent);
+            mergeDependencies(parent.getDependencies());
+            mergeDescription(parent.getDescription());
+            mergeLicenses(parent.getLicenses());
+            mergeExcludes(parent.getAllExcludeRules());
+        }
+
+        /**
+         * Explain how to inherit metadatas related to info element
+         * 
+         * @param parent
+         *            a given parent module decriptor
+         */
+        protected void mergeInfo(ModuleDescriptor parent) {
+            ModuleRevisionId parentMrid = parent.getModuleRevisionId();
+
+            DefaultModuleDescriptor descriptor = getMd();
+            ModuleRevisionId currentMrid = descriptor.getModuleRevisionId();
+
+            ModuleRevisionId mergedMrid = ModuleRevisionId.newInstance(
+                mergeValue(parentMrid.getOrganisation(), currentMrid.getOrganisation()),
+                currentMrid.getName(),
+                mergeValue(parentMrid.getBranch(), currentMrid.getBranch()),
+                mergeRevisionValue(parentMrid.getRevision(), currentMrid.getRevision()),
+                mergeValues(parentMrid.getQualifiedExtraAttributes(),
+                    currentMrid.getQualifiedExtraAttributes()));
+
+            descriptor.setModuleRevisionId(mergedMrid);
+            descriptor.setResolvedModuleRevisionId(mergedMrid);
+
+            descriptor.setStatus(mergeValue(parent.getStatus(), descriptor.getStatus()));
+            if (descriptor.getNamespace() == null && parent instanceof DefaultModuleDescriptor) {
+                Namespace parentNamespace = ((DefaultModuleDescriptor) parent).getNamespace();
+                descriptor.setNamespace(parentNamespace);
+            }
+
+            descriptor.getExtraInfos().addAll(parent.getExtraInfos());
+        }
+
+        private static String mergeRevisionValue(String inherited, String override) {
+            if (override == null || override.equals(Ivy.getWorkingRevision())) {
+                return inherited;
+            } else {
+                return override;
+            }
+        }
+
+        private static String mergeValue(String inherited, String override) {
+            return override == null ? inherited : override;
+        }
+
+        private static Map mergeValues(Map inherited, Map overrides) {
+            LinkedHashMap dup = new LinkedHashMap(inherited.size() + overrides.size());
+            dup.putAll(inherited);
+            dup.putAll(overrides);
+            return dup;
+        }
+
+        /**
+         * Describes how to merge configurations elements
+         * 
+         * @param sourceMrid
+         *            the source module revision id
+         * @param configurations
+         *            array of configurations to be inherited
+         */
+        protected void mergeConfigurations(ModuleDescriptor parent) {
+            ModuleRevisionId sourceMrid = parent.getModuleRevisionId();
+            Configuration[] configurations = parent.getConfigurations();
+            for (int i = 0; i < configurations.length; i++) {
+                Configuration configuration = configurations[i];
+                Message.debug("Merging configuration with: " + configuration.getName());
+                // copy configuration from parent descriptor
+                getMd().addConfiguration(new Configuration(configuration, sourceMrid));
+            }
+
+            if (parent instanceof DefaultModuleDescriptor) {
+                setDefaultConfMapping(((DefaultModuleDescriptor) parent).getDefaultConfMapping());
+                setDefaultConf(((DefaultModuleDescriptor) parent).getDefaultConf());
+                getMd().setMappingOverride(((DefaultModuleDescriptor) parent).isMappingOverride());
+            }
+        }
+
+        /**
+         * Describes how dependencies should be inherited
+         * 
+         * @param dependencies
+         *            array of dependencies to inherit
+         */
+        protected void mergeDependencies(DependencyDescriptor[] dependencies) {
+            DefaultModuleDescriptor md = getMd();
+            for (int i = 0; i < dependencies.length; i++) {
+                DependencyDescriptor dependencyDescriptor = dependencies[i];
+                Message.debug("Merging dependency with: "
+                        + dependencyDescriptor.getDependencyRevisionId().toString());
+                md.addDependency(dependencyDescriptor);
+            }
+        }
+
+        /**
+         * Describes how to merge description
+         * 
+         * @param description
+         *            description going to be inherited
+         */
+        protected void mergeDescription(String description) {
+            String current = getMd().getDescription();
+            if (current == null || current.trim().length() == 0) {
+                getMd().setDescription(description);
+            }
+        }
+
+        /**
+         * Describes how to merge licenses
+         * 
+         * @param licenses
+         *            licenses going to be inherited
+         */
+        public void mergeLicenses(License[] licenses) {
+            for (int i = 0; i < licenses.length; i++) {
+                getMd().addLicense(licenses[i]);
+            }
+        }
+
+        /**
+         * Describes how to merge exclude rules
+         * 
+         * @param excludeRules
+         *            exclude rules going to be inherited
+         */
+        public void mergeExcludes(ExcludeRule[] excludeRules) {
+            for (int i = 0; i < excludeRules.length; i++) {
+                getMd().addExcludeRule(excludeRules[i]);
+            }
+        }
+
+        /**
+         * Returns the parent module using the location attribute (for dev purpose).
+         * 
+         * @param location
+         *            a given location
+         * @throws IOException
+         * @throws ParseException
+         */
+        private ModuleDescriptor parseParentModuleOnFilesystem(String location) throws IOException,
+                ParseException {
+            if (!"file".equals(descriptorURL.getProtocol())) {
+                return null;
+            }
+
+            File file = new File(location);
+            if (!file.isAbsolute()) {
+                URL url = settings.getRelativeUrlResolver().getURL(descriptorURL, location);
+                try {
+                    file = new File(new URI(url.toExternalForm()));
+                } catch (URISyntaxException e) {
+                    file = new File(url.getPath());
+                }
+            }
+
+            file = FileUtil.normalize(file.getAbsolutePath());
+            if (!file.exists()) {
+                Message.verbose("Parent module doesn't exist on the filesystem: "
+                        + file.getAbsolutePath());
+                return null;
+            }
+
+            FileResource res = new FileResource(null, file);
+            ModuleDescriptorParser parser = ModuleDescriptorParserRegistry.getInstance().getParser(
+                res);
+            return parser.parseDescriptor(getSettings(), file.toURL(), res, isValidate());
+        }
+
+        /**
+         * Describe how to parse a {@link ModuleDescriptor} by asking repositories
+         * 
+         * @param parentMrid
+         *            a given {@link ModuleRevisionId} to find
+         * @return a {@link ModuleDescriptor} if found. Return null if no {@link ModuleDescriptor}
+         *         was found
+         * @throws ParseException
+         */
+        protected ModuleDescriptor parseOtherIvyFile(ModuleRevisionId parentMrid)
+                throws ParseException {
+            Message.debug("Trying to parse included ivy file by asking repository for module :"
+                    + parentMrid.toString());
+            DependencyDescriptor dd = new DefaultDependencyDescriptor(parentMrid, true);
+            ResolveData data = IvyContext.getContext().getResolveData();
+            if (data == null) {
+                ResolveEngine engine = IvyContext.getContext().getIvy().getResolveEngine();
+                ResolveOptions options = new ResolveOptions();
+                options.setDownload(false);
+                data = new ResolveData(engine, options);
+            }
+            DependencyResolver resolver = getSettings().getResolver(parentMrid);
+            dd = NameSpaceHelper.toSystem(dd, getSettings().getContextNamespace());
+            ResolvedModuleRevision otherModule = resolver.getDependency(dd, data);
+            if (otherModule == null) {
+                throw new ParseException("Unable to find " + parentMrid.toString(), 0);
+            }
+            return otherModule.getDescriptor();
+
+        }
+
+        protected void publicationsStarted(Attributes attributes) {
+            state = State.PUB;
+            artifactsDeclared = true;
+            checkConfigurations();
+            String defaultConf = settings.substitute(attributes.getValue("defaultconf"));
+            if (defaultConf != null) {
+                setPublicationsDefaultConf(defaultConf);
+            }
+        }
+
+        protected void setPublicationsDefaultConf(String defaultConf) {
+            this.publicationsDefaultConf = defaultConf == null ? null : defaultConf.split(",");
+        }
+
+        protected boolean isOtherNamespace(String qName) {
+            return qName.indexOf(':') != -1;
+        }
+
+        protected void managerStarted(Attributes attributes, String managerAtt) {
+            String org = settings.substitute(attributes.getValue("org"));
+            org = org == null ? PatternMatcher.ANY_EXPRESSION : org;
+            String mod = settings.substitute(attributes.getValue("module"));
+            mod = mod == null ? PatternMatcher.ANY_EXPRESSION : mod;
+            ConflictManager cm;
+            String name = settings.substitute(attributes.getValue(managerAtt));
+            String rev = settings.substitute(attributes.getValue("rev"));
+            if (rev != null) {
+                String[] revs = rev.split(",");
+                for (int i = 0; i < revs.length; i++) {
+                    revs[i] = revs[i].trim();
+                }
+                cm = new FixedConflictManager(revs);
+            } else if (name != null) {
+                cm = settings.getConflictManager(name);
+                if (cm == null) {
+                    addError("unknown conflict manager: " + name);
+                    return;
+                }
+            } else {
+                addError("bad conflict manager: no manager nor rev");
+                return;
+            }
+            String matcherName = settings.substitute(attributes.getValue("matcher"));
+            PatternMatcher matcher = matcherName == null ? defaultMatcher : settings
+                    .getMatcher(matcherName);
+            if (matcher == null) {
+                addError("unknown matcher: " + matcherName);
+                return;
+            }
+            getMd().addConflictManager(new ModuleId(org, mod), matcher, cm);
+        }
+
+        protected void mediationOverrideStarted(Attributes attributes) {
+            String org = settings.substitute(attributes.getValue("org"));
+            org = org == null ? PatternMatcher.ANY_EXPRESSION : org;
+            String mod = settings.substitute(attributes.getValue("module"));
+            mod = mod == null ? PatternMatcher.ANY_EXPRESSION : mod;
+            String rev = settings.substitute(attributes.getValue("rev"));
+            String branch = settings.substitute(attributes.getValue("branch"));
+            String matcherName = settings.substitute(attributes.getValue("matcher"));
+            PatternMatcher matcher = matcherName == null ? defaultMatcher : settings
+                    .getMatcher(matcherName);
+            if (matcher == null) {
+                addError("unknown matcher: " + matcherName);
+                return;
+            }
+            getMd().addDependencyDescriptorMediator(new ModuleId(org, mod), matcher,
+                new OverrideDependencyDescriptorMediator(branch, rev));
+        }
+
+        protected void includeConfStarted(Attributes attributes) throws SAXException, IOException,
+                ParserConfigurationException, ParseException {
+            URL url = settings.getRelativeUrlResolver().getURL(descriptorURL,
+                settings.substitute(attributes.getValue("file")),
+                settings.substitute(attributes.getValue("url")));
+
+            if (url == null) {
+                throw new SAXException("include tag must have a file or an url attribute");
+            }
+
+            // create a new temporary parser to read the configurations from
+            // the specified file.
+            Parser parser = new Parser(getModuleDescriptorParser(), settings);
+            parser.setInput(url);
+            parser.setMd(new DefaultModuleDescriptor(getModuleDescriptorParser(), new URLResource(
+                    url)));
+            XMLHelper.parse(url, null, parser);
+
+            // add the configurations from this temporary parser to this module descriptor
+            Configuration[] configs = parser.getModuleDescriptor().getConfigurations();
+            for (int i = 0; i < configs.length; i++) {
+                getMd().addConfiguration(configs[i]);
+            }
+            if (parser.getDefaultConfMapping() != null) {
+                Message.debug("setting default conf mapping from imported configurations file: "
+                        + parser.getDefaultConfMapping());
+                setDefaultConfMapping(parser.getDefaultConfMapping());
+            }
+            if (parser.getDefaultConf() != null) {
+                Message.debug("setting default conf from imported configurations file: "
+                        + parser.getDefaultConf());
+                setDefaultConf(parser.getDefaultConf());
+            }
+            if (parser.getMd().isMappingOverride()) {
+                Message.debug("enabling mapping-override from imported configurations" + " file");
+                getMd().setMappingOverride(true);
+            }
+        }
+
+        protected void confStarted(Attributes attributes) {
+            String conf = settings.substitute(attributes.getValue("name"));
+            switch (state) {
+                case State.CONF:
+                    String visibility = settings.substitute(attributes.getValue("visibility"));
+                    String ext = settings.substitute(attributes.getValue("extends"));
+                    String transitiveValue = attributes.getValue("transitive");
+                    boolean transitive = (transitiveValue == null) ? true : Boolean.valueOf(
+                        attributes.getValue("transitive")).booleanValue();
+                    String deprecated = attributes.getValue("deprecated");
+                    Configuration configuration = new Configuration(conf,
+                            Configuration.Visibility.getVisibility(visibility == null ? "public"
+                                    : visibility), settings.substitute(attributes
+                                    .getValue("description")), ext == null ? null : ext.split(","),
+                            transitive, deprecated);
+                    ExtendableItemHelper.fillExtraAttributes(settings, configuration, attributes,
+                        new String[] {"name", "visibility", "extends", "transitive", "description",
+                                "deprecated"});
+                    getMd().addConfiguration(configuration);
+                    break;
+                case State.PUB:
+                    if ("*".equals(conf)) {
+                        String[] confs = getMd().getConfigurationsNames();
+                        for (int i = 0; i < confs.length; i++) {
+                            artifact.addConfiguration(confs[i]);
+                            getMd().addArtifact(confs[i], artifact);
+                        }
+                    } else {
+                        artifact.addConfiguration(conf);
+                        getMd().addArtifact(conf, artifact);
+                    }
+                    break;
+                case State.DEP:
+                    this.conf = conf;
+                    String mappeds = settings.substitute(attributes.getValue("mapped"));
+                    if (mappeds != null) {
+                        String[] mapped = mappeds.split(",");
+                        for (int i = 0; i < mapped.length; i++) {
+                            dd.addDependencyConfiguration(conf, mapped[i].trim());
+                        }
+                    }
+                    break;
+                case State.DEP_ARTIFACT:
+                case State.ARTIFACT_INCLUDE:
+                case State.ARTIFACT_EXCLUDE:
+                    addConfiguration(conf);
+                    break;
+                default:
+                    if (validate) {
+                        addError("conf tag found in invalid tag: " + state);
+                    }
+                    break;
+            }
+        }
+
+        protected void dependencyStarted(Attributes attributes) {
+            state = State.DEP;
+            String org = settings.substitute(attributes.getValue("org"));
+            if (org == null) {
+                org = getMd().getModuleRevisionId().getOrganisation();
+            }
+            boolean force = Boolean.valueOf(settings.substitute(attributes.getValue("force")))
+                    .booleanValue();
+            boolean changing = Boolean
+                    .valueOf(settings.substitute(attributes.getValue("changing"))).booleanValue();
+
+            String transitiveValue = settings.substitute(attributes.getValue("transitive"));
+            boolean transitive = (transitiveValue == null) ? true : Boolean.valueOf(
+                attributes.getValue("transitive")).booleanValue();
+
+            String name = settings.substitute(attributes.getValue("name"));
+            String branch = settings.substitute(attributes.getValue("branch"));
+            String branchConstraint = settings.substitute(attributes.getValue("branchConstraint"));
+
+            // if (branchConstraint == null) {
+            // // there was no branch constraint before, so we should
+            // // set the branchConstraint to the current default branch
+            // branchConstraint = settings.getDefaultBranch(ModuleId.newInstance(org, name));
+            // }
+
+            String rev = settings.substitute(attributes.getValue("rev"));
+            String revConstraint = settings.substitute(attributes.getValue("revConstraint"));
+
+            Map extraAttributes = ExtendableItemHelper.getExtraAttributes(settings, attributes,
+                DEPENDENCY_REGULAR_ATTRIBUTES);
+
+            ModuleRevisionId revId = ModuleRevisionId.newInstance(org, name, branch, rev,
+                extraAttributes);
+            ModuleRevisionId dynamicId = null;
+            if ((revConstraint == null) && (branchConstraint == null)) {
+                // no dynamic constraints defined, so dynamicId equals revId
+                dynamicId = ModuleRevisionId.newInstance(org, name, branch, rev, extraAttributes,
+                    false);
+            } else {
+                if (branchConstraint == null) {
+                    // this situation occurs when there was no branch defined
+                    // in the original dependency descriptor. So the dynamicId
+                    // shouldn't contain a branch neither
+                    dynamicId = ModuleRevisionId.newInstance(org, name, null, revConstraint,
+                        extraAttributes, false);
+                } else {
+                    dynamicId = ModuleRevisionId.newInstance(org, name, branchConstraint,
+                        revConstraint, extraAttributes);
+                }
+            }
+
+            dd = new DefaultDependencyDescriptor(getMd(), revId, dynamicId, force, changing,
+                    transitive);
+            getMd().addDependency(dd);
+            String confs = settings.substitute(attributes.getValue("conf"));
+            if (confs != null && confs.length() > 0) {
+                parseDepsConfs(confs, dd);
+            }
+        }
+
+        protected void artifactStarted(String qName, Attributes attributes)
+                throws MalformedURLException {
+            if (state == State.PUB) {
+                // this is a published artifact
+                String artName = settings.substitute(attributes.getValue("name"));
+                artName = artName == null ? getMd().getModuleRevisionId().getName() : artName;
+                String type = settings.substitute(attributes.getValue("type"));
+                type = type == null ? "jar" : type;
+                String ext = settings.substitute(attributes.getValue("ext"));
+                ext = ext != null ? ext : type;
+                String url = settings.substitute(attributes.getValue("url"));
+                artifact = new MDArtifact(getMd(), artName, type, ext, url == null ? null
+                        : new URL(url), ExtendableItemHelper.getExtraAttributes(settings,
+                    attributes, new String[] {"ext", "type", "name", "conf"}));
+                String confs = settings.substitute(attributes.getValue("conf"));
+                // only add confs if they are specified. if they aren't, endElement will
+                // handle this
+                // only if there are no conf defined in sub elements
+                if (confs != null && confs.length() > 0) {
+                    String[] conf;
+                    if ("*".equals(confs)) {
+                        conf = getMd().getConfigurationsNames();
+                    } else {
+                        conf = confs.split(",");
+                    }
+                    for (int i = 0; i < conf.length; i++) {
+                        artifact.addConfiguration(conf[i].trim());
+                        getMd().addArtifact(conf[i].trim(), artifact);
+                    }
+                }
+            } else if (state == State.DEP) {
+                // this is an artifact asked for a particular dependency
+                addDependencyArtifacts(qName, attributes);
+            } else if (validate) {
+                addError("artifact tag found in invalid tag: " + state);
+            }
+        }
+
+        protected void dependenciesStarted(Attributes attributes) {
+            state = State.DEPS;
+            String defaultConf = settings.substitute(attributes.getValue("defaultconf"));
+            if (defaultConf != null) {
+                setDefaultConf(defaultConf);
+            }
+            defaultConf = settings.substitute(attributes.getValue("defaultconfmapping"));
+            if (defaultConf != null) {
+                setDefaultConfMapping(defaultConf);
+            }
+            String confMappingOverride = settings.substitute(attributes
+                    .getValue("confmappingoverride"));
+            if (confMappingOverride != null) {
+                getMd().setMappingOverride(Boolean.valueOf(confMappingOverride).booleanValue());
+            }
+            checkConfigurations();
+        }
+
+        protected void configurationStarted(Attributes attributes) {
+            state = State.CONF;
+            setDefaultConfMapping(settings.substitute(attributes.getValue("defaultconfmapping")));
+            setDefaultConf(settings.substitute(attributes.getValue("defaultconf")));
+            getMd().setMappingOverride(
+                Boolean.valueOf(settings.substitute(attributes.getValue("confmappingoverride")))
+                        .booleanValue());
+        }
+
+        protected void infoStarted(Attributes attributes) {
+            state = State.INFO;
+            String org = settings.substitute(attributes.getValue("organisation"));
+            String module = settings.substitute(attributes.getValue("module"));
+            String revision = settings.substitute(attributes.getValue("revision"));
+            String branch = settings.substitute(attributes.getValue("branch"));
+            getMd().setModuleRevisionId(
+                ModuleRevisionId.newInstance(
+                    org,
+                    module,
+                    branch,
+                    revision,
+                    ExtendableItemHelper.getExtraAttributes(settings, attributes, new String[] {
+                            "organisation", "module", "revision", "status", "publication",
+                            "branch", "namespace", "default", "resolver"})));
+
+            String namespace = settings.substitute(attributes.getValue("namespace"));
+            if (namespace != null) {
+                Namespace ns = settings.getNamespace(namespace);
+                if (ns == null) {
+                    Message.warn("namespace not found for " + getMd().getModuleRevisionId() + ": "
+                            + namespace);
+                } else {
+                    getMd().setNamespace(ns);
+                }
+            }
+
+            String status = settings.substitute(attributes.getValue("status"));
+            getMd().setStatus(
+                status == null ? settings.getStatusManager().getDefaultStatus() : status);
+            getMd().setDefault(
+                Boolean.valueOf(settings.substitute(attributes.getValue("default"))).booleanValue());
+            String pubDate = settings.substitute(attributes.getValue("publication"));
+            if (pubDate != null && pubDate.length() > 0) {
+                try {
+                    getMd().setPublicationDate(DateUtil.parse(pubDate));
+                } catch (ParseException e) {
+                    addError("invalid publication date format: " + pubDate);
+                    getMd().setPublicationDate(getDefaultPubDate());
+                }
+            } else {
+                getMd().setPublicationDate(getDefaultPubDate());
+            }
+        }
+
+        protected void ivyModuleStarted(Attributes attributes) throws SAXException {
+            descriptorVersion = attributes.getValue("version");
+            int versionIndex = ALLOWED_VERSIONS.indexOf(descriptorVersion);
+            if (versionIndex == -1) {
+                addError("invalid version " + descriptorVersion);
+                throw new SAXException("invalid version " + descriptorVersion);
+            }
+            if (versionIndex >= ALLOWED_VERSIONS.indexOf("1.3")) {
+                Message.debug("post 1.3 ivy file: using " + PatternMatcher.EXACT
+                        + " as default matcher");
+                defaultMatcher = settings.getMatcher(PatternMatcher.EXACT);
+            } else {
+                Message.debug("pre 1.3 ivy file: using " + PatternMatcher.EXACT_OR_REGEXP
+                        + " as default matcher");
+                defaultMatcher = settings.getMatcher(PatternMatcher.EXACT_OR_REGEXP);
+            }
+
+            for (int i = 0; i < attributes.getLength(); i++) {
+                if (attributes.getQName(i).startsWith("xmlns:")) {
+                    getMd().addExtraAttributeNamespace(
+                        attributes.getQName(i).substring("xmlns:".length()), attributes.getValue(i));
+                }
+            }
+        }
+
+        protected void addDependencyArtifacts(String tag, Attributes attributes)
+                throws MalformedURLException {
+            state = State.DEP_ARTIFACT;
+            parseRule(tag, attributes);
+        }
+
+        protected void addIncludeRule(String tag, Attributes attributes)
+                throws MalformedURLException {
+            state = State.ARTIFACT_INCLUDE;
+            parseRule(tag, attributes);
+        }
+
+        protected void addExcludeRule(String tag, Attributes attributes)
+                throws MalformedURLException {
+            state = State.ARTIFACT_EXCLUDE;
+            parseRule(tag, attributes);
+        }
+
+        protected void parseRule(String tag, Attributes attributes) throws MalformedURLException {
+            String name = settings.substitute(attributes.getValue("name"));
+            if (name == null) {
+                name = settings.substitute(attributes.getValue("artifact"));
+                if (name == null) {
+                    name = "artifact".equals(tag) ? dd.getDependencyId().getName()
+                            : PatternMatcher.ANY_EXPRESSION;
+                }
+            }
+            String type = settings.substitute(attributes.getValue("type"));
+            if (type == null) {
+                type = "artifact".equals(tag) ? "jar" : PatternMatcher.ANY_EXPRESSION;
+            }
+            String ext = settings.substitute(attributes.getValue("ext"));
+            ext = ext != null ? ext : type;
+            if (state == State.DEP_ARTIFACT) {
+                String url = settings.substitute(attributes.getValue("url"));
+                Map extraAtt = ExtendableItemHelper.getExtraAttributes(settings, attributes,
+                    new String[] {"name", "type", "ext", "url", "conf"});
+                confAware = new DefaultDependencyArtifactDescriptor(dd, name, type, ext,
+                        url == null ? null : new URL(url), extraAtt);
+            } else if (state == State.ARTIFACT_INCLUDE) {
+                PatternMatcher matcher = getPatternMatcher(attributes.getValue("matcher"));
+                String org = settings.substitute(attributes.getValue("org"));
+                org = org == null ? PatternMatcher.ANY_EXPRESSION : org;
+                String module = settings.substitute(attributes.getValue("module"));
+                module = module == null ? PatternMatcher.ANY_EXPRESSION : module;
+                ArtifactId aid = new ArtifactId(new ModuleId(org, module), name, type, ext);
+                Map extraAtt = ExtendableItemHelper.getExtraAttributes(settings, attributes,
+                    new String[] {"org", "module", "name", "type", "ext", "matcher", "conf"});
+                confAware = new DefaultIncludeRule(aid, matcher, extraAtt);
+            } else { // _state == ARTIFACT_EXCLUDE || EXCLUDE
+                PatternMatcher matcher = getPatternMatcher(attributes.getValue("matcher"));
+                String org = settings.substitute(attributes.getValue("org"));
+                org = org == null ? PatternMatcher.ANY_EXPRESSION : org;
+                String module = settings.substitute(attributes.getValue("module"));
+                module = module == null ? PatternMatcher.ANY_EXPRESSION : module;
+                ArtifactId aid = new ArtifactId(new ModuleId(org, module), name, type, ext);
+                Map extraAtt = ExtendableItemHelper.getExtraAttributes(settings, attributes,
+                    new String[] {"org", "module", "name", "type", "ext", "matcher", "conf"});
+                confAware = new DefaultExcludeRule(aid, matcher, extraAtt);
+            }
+            String confs = settings.substitute(attributes.getValue("conf"));
+            // only add confs if they are specified. if they aren't, endElement will handle this
+            // only if there are no conf defined in sub elements
+            if (confs != null && confs.length() > 0) {
+                String[] conf;
+                if ("*".equals(confs)) {
+                    conf = getMd().getConfigurationsNames();
+                } else {
+                    conf = confs.split(",");
+                }
+                for (int i = 0; i < conf.length; i++) {
+                    addConfiguration(conf[i].trim());
+                }
+            }
+        }
+
+        protected void addConfiguration(String c) {
+            confAware.addConfiguration(c);
+            if (state == State.EXCLUDE) {
+                // we are adding a configuration to a module wide exclude rule
+                // we have nothing special to do here, the rule has already been added to the module
+                // descriptor
+            } else {
+                // we are currently adding a configuration to either an include, exclude or artifact
+                // element
+                // of a dependency. This means that we have to add this element to the corresponding
+                // conf
+                // of the current dependency descriptor
+                if (confAware instanceof DependencyArtifactDescriptor) {
+                    dd.addDependencyArtifact(c, (DependencyArtifactDescriptor) confAware);
+                } else if (confAware instanceof IncludeRule) {
+                    dd.addIncludeRule(c, (IncludeRule) confAware);
+                } else if (confAware instanceof ExcludeRule) {
+                    dd.addExcludeRule(c, (ExcludeRule) confAware);
+                }
+            }
+        }
+
+        protected PatternMatcher getPatternMatcher(String m) {
+            String matcherName = settings.substitute(m);
+            PatternMatcher matcher = matcherName == null ? defaultMatcher : settings
+                    .getMatcher(matcherName);
+            if (matcher == null) {
+                throw new IllegalArgumentException("unknown matcher " + matcherName);
+            }
+            return matcher;
+        }
+
+        public void characters(char[] ch, int start, int length) throws SAXException {
+            if (buffer != null) {
+                buffer.append(ch, start, length);
+            }
+        }
+
+        public void endElement(String uri, String localName, String qName) throws SAXException {
+            if (state == State.PUB && "artifact".equals(qName)
+                    && artifact.getConfigurations().length == 0) {
+                String[] confs = publicationsDefaultConf == null ? getMd().getConfigurationsNames()
+                        : publicationsDefaultConf;
+                for (int i = 0; i < confs.length; i++) {
+                    artifact.addConfiguration(confs[i].trim());
+                    getMd().addArtifact(confs[i].trim(), artifact);
+                }
+            } else if ("configurations".equals(qName)) {
+                checkConfigurations();
+            } else if ((state == State.DEP_ARTIFACT && "artifact".equals(qName))
+                    || (state == State.ARTIFACT_INCLUDE && "include".equals(qName))
+                    || (state == State.ARTIFACT_EXCLUDE && "exclude".equals(qName))) {
+                state = State.DEP;
+                if (confAware.getConfigurations().length == 0) {
+                    String[] confs = getMd().getConfigurationsNames();
+                    for (int i = 0; i < confs.length; i++) {
+                        addConfiguration(confs[i]);
+                    }
+                }
+                confAware = null;
+            } else if ("exclude".equals(qName) && state == State.EXCLUDE) {
+                if (confAware.getConfigurations().length == 0) {
+                    String[] confs = getMd().getConfigurationsNames();
+                    for (int i = 0; i < confs.length; i++) {
+                        addConfiguration(confs[i]);
+                    }
+                }
+                confAware = null;
+                state = State.DEPS;
+            } else if ("dependency".equals(qName) && state == State.DEP) {
+                if (dd.getModuleConfigurations().length == 0) {
+                    parseDepsConfs(getDefaultConf(), dd);
+                }
+                state = State.DEPS;
+            } else if ("dependencies".equals(qName) && state == State.DEPS) {
+                state = State.NONE;
+            } else if (state == State.INFO && "info".equals(qName)) {
+                state = State.NONE;
+            } else if (state == State.DESCRIPTION && "description".equals(qName)) {
+                getMd().setDescription(buffer == null ? "" : buffer.toString().trim());
+                buffer = null;
+                state = State.INFO;
+            } else if (state == State.EXTRA_INFO) {
+                String content = buffer == null ? "" : buffer.toString();
+                buffer = null;
+                ExtraInfoHolder extraInfo = extraInfoStack.pop();
+                extraInfo.setContent(content);
+                if (extraInfoStack.isEmpty()) {
+                    getMd().addExtraInfo(extraInfo);
+                    state = State.INFO;
+                } else {
+                    ExtraInfoHolder parentHolder = extraInfoStack.peek();
+                    parentHolder.getNestedExtraInfoHolder().add(extraInfo);
+                }
+            } else if (state == State.DESCRIPTION) {
+                if (buffer.toString().endsWith("<" + qName + ">")) {
+                    buffer.deleteCharAt(buffer.length() - 1);
+                    buffer.append("/>");
+                } else {
+                    buffer.append("</" + qName + ">");
+                }
+            }
+        }
+
+        protected void checkConfigurations() {
+            if (getMd().getConfigurations().length == 0) {
+                getMd().addConfiguration(new Configuration("default"));
+            }
+        }
+
+        protected void replaceConfigurationWildcards() {
+            Configuration[] configs = getMd().getConfigurations();
+            for (int i = 0; i < configs.length; i++) {
+                configs[i].replaceWildcards(getMd());
+            }
+        }
+
+        /* getters and setters available for extension only */
+        protected ParserSettings getSettings() {
+            return settings;
+        }
+
+        protected URL getDescriptorURL() {
+            return descriptorURL;
+        }
+
+        protected InputStream getDescriptorInput() {
+            return descriptorInput;
+        }
+
+        protected int getState() {
+            return state;
+        }
+
+        protected void setState(int state) {
+            this.state = state;
+        }
+
+        protected PatternMatcher getDefaultMatcher() {
+            return defaultMatcher;
+        }
+
+        protected DefaultDependencyDescriptor getDd() {
+            return dd;
+        }
+
+        protected void setDd(DefaultDependencyDescriptor dd) {
+            this.dd = dd;
+        }
+
+        protected ConfigurationAware getConfAware() {
+            return confAware;
+        }
+
+        protected void setConfAware(ConfigurationAware confAware) {
+            this.confAware = confAware;
+        }
+
+        protected MDArtifact getArtifact() {
+            return artifact;
+        }
+
+        protected void setArtifact(MDArtifact artifact) {
+            this.artifact = artifact;
+        }
+
+        protected String getConf() {
+            return conf;
+        }
+
+        protected void setConf(String conf) {
+            this.conf = conf;
+        }
+
+        protected boolean isArtifactsDeclared() {
+            return artifactsDeclared;
+        }
+
+        protected void setArtifactsDeclared(boolean artifactsDeclared) {
+            this.artifactsDeclared = artifactsDeclared;
+        }
+
+        protected StringBuffer getBuffer() {
+            return buffer;
+        }
+
+        protected void setBuffer(StringBuffer buffer) {
+            this.buffer = buffer;
+        }
+
+        protected String getDescriptorVersion() {
+            return descriptorVersion;
+        }
+
+        protected void setDescriptorVersion(String descriptorVersion) {
+            this.descriptorVersion = descriptorVersion;
+        }
+
+        protected String[] getPublicationsDefaultConf() {
+            return publicationsDefaultConf;
+        }
+
+        protected void setPublicationsDefaultConf(String[] publicationsDefaultConf) {
+            this.publicationsDefaultConf = publicationsDefaultConf;
+        }
+
+        protected boolean isValidate() {
+            return validate;
+        }
+
+        protected URL getSchemaURL() {
+            return getClass().getResource("ivy.xsd");
+        }
+    }
+
+    public String toString() {
+        return "ivy parser";
+    }
+
+}
diff --git a/src/java/org/apache/ivy/plugins/parser/xml/XmlModuleDescriptorUpdater.java b/src/java/org/apache/ivy/plugins/parser/xml/XmlModuleDescriptorUpdater.java
new file mode 100644
index 0000000..9880f2d
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/parser/xml/XmlModuleDescriptorUpdater.java
@@ -0,0 +1,1340 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.parser.xml;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Stack;
+import java.util.StringTokenizer;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.apache.ivy.core.module.descriptor.Configuration;
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.ExtendsDescriptor;
+import org.apache.ivy.core.module.descriptor.InheritableItem;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.plugins.namespace.NameSpaceHelper;
+import org.apache.ivy.plugins.namespace.Namespace;
+import org.apache.ivy.plugins.parser.ParserSettings;
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.plugins.repository.file.FileResource;
+import org.apache.ivy.plugins.repository.url.URLResource;
+import org.apache.ivy.util.Checks;
+import org.apache.ivy.util.DateUtil;
+import org.apache.ivy.util.Message;
+import org.apache.ivy.util.XMLHelper;
+import org.apache.ivy.util.extendable.ExtendableItemHelper;
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.ext.LexicalHandler;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * Used to update ivy files. Uses ivy file as source and not ModuleDescriptor to preserve as much as
+ * possible the original syntax
+ */
+public final class XmlModuleDescriptorUpdater {
+    // CheckStyle:StaticVariableName| OFF
+    // LINE_SEPARATOR is actually a constant, but we have to modify it for the tests
+    public static String LINE_SEPARATOR = System.getProperty("line.separator");
+
+    // CheckStyle:StaticVariableName| ON
+
+    private XmlModuleDescriptorUpdater() {
+    }
+
+    /**
+     * used to copy a module descriptor xml file (also known as ivy file) and update the revisions
+     * of its dependencies, its status and revision
+     * 
+     * @param srcURL
+     *            the url of the source module descriptor file
+     * @param destFile
+     *            The file to which the updated module descriptor should be output
+     */
+    public static void update(URL srcURL, File destFile, UpdateOptions options) throws IOException,
+            SAXException {
+        if (destFile.getParentFile() != null) {
+            destFile.getParentFile().mkdirs();
+        }
+        OutputStream destStream = new FileOutputStream(destFile);
+        try {
+            update(srcURL, destStream, options);
+        } finally {
+            try {
+                destStream.close();
+            } catch (IOException e) {
+                Message.warn("failed to close a stream : " + e.toString());
+            }
+        }
+    }
+
+    public static void update(URL srcURL, OutputStream destFile, UpdateOptions options)
+            throws IOException, SAXException {
+        InputStream in = srcURL.openStream();
+        try {
+            update(srcURL, in, destFile, options);
+        } finally {
+            try {
+                in.close();
+            } catch (IOException e) {
+                Message.warn("failed to close a stream : " + e.toString());
+            }
+            try {
+                destFile.close();
+            } catch (IOException e) {
+                Message.warn("failed to close a stream : " + e.toString());
+            }
+        }
+
+    }
+
+    public static void update(InputStream in, Resource res, File destFile, UpdateOptions options)
+            throws IOException, SAXException {
+        if (destFile.getParentFile() != null) {
+            destFile.getParentFile().mkdirs();
+        }
+        OutputStream fos = new FileOutputStream(destFile);
+        try {
+            // TODO: use resource as input stream context?
+            URL inputStreamContext = null;
+            if (res instanceof URLResource) {
+                inputStreamContext = ((URLResource) res).getURL();
+            } else if (res instanceof FileResource) {
+                inputStreamContext = ((FileResource) res).getFile().toURI().toURL();
+            }
+            update(inputStreamContext, in, fos, options);
+        } finally {
+            try {
+                in.close();
+            } catch (IOException e) {
+                Message.warn("failed to close a stream : " + e.toString());
+            }
+            try {
+                fos.close();
+            } catch (IOException e) {
+                Message.warn("failed to close a stream : " + e.toString());
+            }
+        }
+    }
+
+    private static class UpdaterHandler extends DefaultHandler implements LexicalHandler {
+
+        /** standard attributes of ivy-module/info */
+        private static final Collection STD_ATTS = Arrays.asList(new String[] {"organisation",
+                "module", "branch", "revision", "status", "publication", "namespace"});
+
+        /** elements that may appear inside ivy-module, in expected order */
+        private static final List MODULE_ELEMENTS = Arrays.asList(new String[] {"info",
+                "configurations", "publications", "dependencies", "conflicts"});
+
+        /** element position of "configurations" inside "ivy-module" */
+        private static final int CONFIGURATIONS_POSITION = MODULE_ELEMENTS
+                .indexOf("configurations");
+
+        /** element position of "dependencies" inside "ivy-module" */
+        private static final int DEPENDENCIES_POSITION = MODULE_ELEMENTS.indexOf("dependencies");
+
+        /** elements that may appear inside of ivy-module/info */
+        private static final Collection INFO_ELEMENTS = Arrays.asList(new String[] {"extends",
+                "ivyauthor", "license", "repository", "description"});
+
+        private final ParserSettings settings;
+
+        private final PrintWriter out;
+
+        private final Map resolvedRevisions;
+
+        private final Map resolvedBranches;
+
+        private final String status;
+
+        private final String revision;
+
+        private final Date pubdate;
+
+        private final Namespace ns;
+
+        private final boolean replaceInclude;
+
+        private final boolean generateRevConstraint;
+
+        private boolean inHeader = true;
+
+        private final List confs;
+
+        private final URL relativePathCtx;
+
+        private final UpdateOptions options;
+
+        public UpdaterHandler(URL relativePathCtx, PrintWriter out, final UpdateOptions options) {
+            this.options = options;
+            this.settings = options.getSettings();
+            this.out = out;
+            this.resolvedRevisions = options.getResolvedRevisions();
+            this.resolvedBranches = options.getResolvedBranches();
+            this.status = options.getStatus();
+            this.revision = options.getRevision();
+            this.pubdate = options.getPubdate();
+            this.ns = options.getNamespace();
+            this.replaceInclude = options.isReplaceInclude();
+            this.generateRevConstraint = options.isGenerateRevConstraint();
+            this.relativePathCtx = relativePathCtx;
+            if (options.getConfsToExclude() != null) {
+                this.confs = Arrays.asList(options.getConfsToExclude());
+            } else {
+                this.confs = Collections.EMPTY_LIST;
+            }
+        }
+
+        // never print *ln* cause \n is found in copied characters stream
+        // nor do we need do handle indentation, original one is maintained except for attributes
+
+        private String organisation = null;
+
+        // defaultConfMapping of imported configurations, if any
+        private String defaultConfMapping = null;
+
+        // confMappingOverride of imported configurations, if any
+        private Boolean confMappingOverride = null;
+
+        // used to know if the last open tag was empty, to adjust termination
+        // with /> instead of ></qName>
+        private String justOpen = null;
+
+        // track the size of the left indent, so that inserted elements are formatted
+        // like nearby elements.
+
+        // true when we're reading indent whitespace
+        private boolean indenting;
+
+        private StringBuffer currentIndent = new StringBuffer();
+
+        private ArrayList indentLevels = new ArrayList(); // ArrayList<String>
+
+        // true if an ivy-module/info/description element has been found in the published descriptor
+        private boolean hasDescription = false;
+
+        // true if merged configurations have been written
+        private boolean mergedConfigurations = false;
+
+        // true if merged deps have been written
+        private boolean mergedDependencies = false;
+
+        // the new value of the defaultconf attribute on the publications tag
+        private String newDefaultConf = null;
+
+        private Stack context = new Stack();
+
+        private Stack buffers = new Stack();
+
+        private Stack confAttributeBuffers = new Stack();
+
+        public void startElement(String uri, String localName, String qName, Attributes attributes)
+                throws SAXException {
+            inHeader = false;
+            endIndent();
+            if (justOpen != null) {
+                write(">");
+            }
+
+            flushMergedElementsBefore(qName);
+
+            // according to ivy.xsd, all <dependency> elements must occur before
+            // the <exclude>, <override> or <conflict> elements
+            if (options.isMerge()
+                    && ("exclude".equals(localName) || "override".equals(localName) || "conflict"
+                            .equals(localName)) && "ivy-module/dependencies".equals(getContext())) {
+                ModuleDescriptor merged = options.getMergedDescriptor();
+                writeInheritedDependencies(merged);
+                out.println();
+                out.print(getIndent());
+            }
+
+            context.push(qName);
+
+            String path = getContext();
+            if ("info".equals(qName)) {
+                infoStarted(attributes);
+            } else if (replaceInclude && "include".equals(qName)
+                    && context.contains("configurations")) {
+                // TODO, in the case of !replaceInclude, we should still replace the relative path
+                // by an absolute path.
+                includeStarted(attributes);
+            } else if ("ivy-module/info/extends".equals(path)) {
+                startExtends(attributes);
+            } else if ("ivy-module/dependencies/dependency".equals(path)) {
+                startElementInDependency(attributes);
+            } else if ("dependencies".equals(qName)) {
+                startDependencies(attributes);
+            } else if ("ivy-module/configurations/conf".equals(path)) {
+                startElementInConfigurationsConf(qName, attributes);
+            } else if ("ivy-module/publications/artifact/conf".equals(path)
+                    || "ivy-module/dependencies/dependency/conf".equals(path)
+                    || "ivy-module/dependencies/dependency/artifact/conf".equals(path)) {
+                buffers.push(new ExtendedBuffer(getContext()));
+                ((ExtendedBuffer) confAttributeBuffers.peek()).setDefaultPrint(false);
+                String confName = substitute(settings, attributes.getValue("name"));
+                if (!confs.contains(confName)) {
+                    ((ExtendedBuffer) confAttributeBuffers.peek()).setPrint(true);
+                    ((ExtendedBuffer) buffers.peek()).setPrint(true);
+                    write("<" + qName);
+                    for (int i = 0; i < attributes.getLength(); i++) {
+                        write(" " + attributes.getQName(i) + "=\""
+                                + substitute(settings, attributes.getValue(i)) + "\"");
+                    }
+                }
+            } else if ("ivy-module/publications/artifact".equals(path)) {
+                ExtendedBuffer buffer = new ExtendedBuffer(getContext());
+                buffers.push(buffer);
+                confAttributeBuffers.push(buffer);
+                write("<" + qName);
+                buffer.setDefaultPrint(attributes.getValue("conf") == null
+                        && ((newDefaultConf == null) || (newDefaultConf.length() > 0)));
+                for (int i = 0; i < attributes.getLength(); i++) {
+                    String attName = attributes.getQName(i);
+                    if ("conf".equals(attName)) {
+                        String confName = substitute(settings, attributes.getValue("conf"));
+                        String newConf = removeConfigurationsFromList(confName, confs);
+                        if (newConf.length() > 0) {
+                            write(" " + attributes.getQName(i) + "=\"" + newConf + "\"");
+                            ((ExtendedBuffer) buffers.peek()).setPrint(true);
+                        }
+                    } else {
+                        write(" " + attributes.getQName(i) + "=\""
+                                + substitute(settings, attributes.getValue(i)) + "\"");
+                    }
+                }
+            } else if ("ivy-module/dependencies/dependency/artifact".equals(path)) {
+                ExtendedBuffer buffer = new ExtendedBuffer(getContext());
+                buffers.push(buffer);
+                confAttributeBuffers.push(buffer);
+                write("<" + qName);
+                buffer.setDefaultPrint(attributes.getValue("conf") == null);
+                for (int i = 0; i < attributes.getLength(); i++) {
+                    String attName = attributes.getQName(i);
+                    if ("conf".equals(attName)) {
+                        String confName = substitute(settings, attributes.getValue("conf"));
+                        String newConf = removeConfigurationsFromList(confName, confs);
+                        if (newConf.length() > 0) {
+                            write(" " + attributes.getQName(i) + "=\"" + newConf + "\"");
+                            ((ExtendedBuffer) buffers.peek()).setPrint(true);
+                        }
+                    } else {
+                        write(" " + attributes.getQName(i) + "=\""
+                                + substitute(settings, attributes.getValue(i)) + "\"");
+                    }
+                }
+            } else if ("ivy-module/publications".equals(path)) {
+                startPublications(attributes);
+            } else {
+                if (options.isMerge() && path.startsWith("ivy-module/info")) {
+                    ModuleDescriptor merged = options.getMergedDescriptor();
+                    if (path.equals("ivy-module/info/description")) {
+                        // if the descriptor already contains a description, don't bother printing
+                        // the merged version.
+                        hasDescription = true;
+                    } else if (!INFO_ELEMENTS.contains(qName)) {
+                        // according to the XSD, we should write description after all of the other
+                        // standard <info> elements but before any extended elements.
+                        writeInheritedDescription(merged);
+                    }
+                }
+
+                // copy
+                write("<" + qName);
+                for (int i = 0; i < attributes.getLength(); i++) {
+                    write(" " + attributes.getQName(i) + "=\""
+                            + substitute(settings, attributes.getValue(i)) + "\"");
+                }
+            }
+            justOpen = qName;
+            // indent.append("\t");
+        }
+
+        private void startExtends(Attributes attributes) {
+            // in merge mode, comment out extends element
+            if (options.isMerge()) {
+                write("<!-- ");
+            }
+            write("<extends");
+
+            String org = substitute(settings, attributes.getValue("organisation"));
+            String module = substitute(settings, attributes.getValue("module"));
+            ModuleId parentId = new ModuleId(org, module);
+
+            for (int i = 0; i < attributes.getLength(); i++) {
+                String name = attributes.getQName(i);
+                String value = null;
+
+                if ("revision".equals(name)) {
+                    // replace inline revision with resolved parent revision
+                    ModuleDescriptor merged = options.getMergedDescriptor();
+                    if (merged != null) {
+                        ExtendsDescriptor[] parents = merged.getInheritedDescriptors();
+                        for (int j = 0; value == null && j < parents.length; ++j) {
+                            ModuleRevisionId resolvedId = parents[j].getResolvedParentRevisionId();
+                            if (parentId.equals(resolvedId.getModuleId())) {
+                                value = resolvedId.getRevision();
+                            }
+                        }
+                    }
+                    if (value == null) {
+                        value = substitute(settings, attributes.getValue(i));
+                    }
+                } else if ("organisation".equals(name)) {
+                    value = org;
+                } else if ("module".equals(name)) {
+                    value = module;
+                } else {
+                    value = substitute(settings, attributes.getValue(i));
+                }
+                write(" " + name + "=\"" + value + "\"");
+            }
+        }
+
+        private void startElementInConfigurationsConf(String qName, Attributes attributes) {
+            buffers.push(new ExtendedBuffer(getContext()));
+            String confName = substitute(settings, attributes.getValue("name"));
+            if (!confs.contains(confName)) {
+                ((ExtendedBuffer) buffers.peek()).setPrint(true);
+                String extend = substitute(settings, attributes.getValue("extends"));
+                if (extend != null) {
+                    for (StringTokenizer tok = new StringTokenizer(extend, ", "); tok
+                            .hasMoreTokens();) {
+                        String current = tok.nextToken();
+                        if (confs.contains(current)) {
+                            throw new IllegalArgumentException(
+                                    "Cannot exclude a configuration which is extended.");
+                        }
+                    }
+                }
+
+                write("<" + qName);
+                for (int i = 0; i < attributes.getLength(); i++) {
+                    write(" " + attributes.getQName(i) + "=\""
+                            + substitute(settings, attributes.getValue(i)) + "\"");
+                }
+            }
+        }
+
+        private void startDependencies(Attributes attributes) {
+            // copy
+            write("<dependencies");
+            for (int i = 0; i < attributes.getLength(); i++) {
+                String attName = attributes.getQName(i);
+                if ("defaultconfmapping".equals(attName)) {
+                    String newMapping = removeConfigurationsFromMapping(
+                        substitute(settings, attributes.getValue("defaultconfmapping")), confs);
+                    if (newMapping.length() > 0) {
+                        write(" " + attributes.getQName(i) + "=\"" + newMapping + "\"");
+                    }
+                } else {
+                    write(" " + attributes.getQName(i) + "=\""
+                            + substitute(settings, attributes.getValue(i)) + "\"");
+                }
+            }
+            // add default conf mapping if needed
+            if (defaultConfMapping != null && attributes.getValue("defaultconfmapping") == null) {
+                String newMapping = removeConfigurationsFromMapping(defaultConfMapping, confs);
+                if (newMapping.length() > 0) {
+                    write(" defaultconfmapping=\"" + newMapping + "\"");
+                }
+            }
+            // add confmappingoverride if needed
+            if (confMappingOverride != null && attributes.getValue("confmappingoverride") == null) {
+                write(" confmappingoverride=\"" + confMappingOverride.toString() + "\"");
+            }
+        }
+
+        private void startPublications(Attributes attributes) {
+            write("<publications");
+            for (int i = 0; i < attributes.getLength(); i++) {
+                String attName = attributes.getQName(i);
+                if ("defaultconf".equals(attName)) {
+                    newDefaultConf = removeConfigurationsFromList(
+                        substitute(settings, attributes.getValue("defaultconf")), confs);
+                    if (newDefaultConf.length() > 0) {
+                        write(" " + attributes.getQName(i) + "=\"" + newDefaultConf + "\"");
+                    }
+                } else {
+                    write(" " + attributes.getQName(i) + "=\""
+                            + substitute(settings, attributes.getValue(i)) + "\"");
+                }
+            }
+        }
+
+        private void startElementInDependency(Attributes attributes) {
+            ExtendedBuffer buffer = new ExtendedBuffer(getContext());
+            buffers.push(buffer);
+            confAttributeBuffers.push(buffer);
+            buffer.setDefaultPrint(attributes.getValue("conf") == null
+                    || attributes.getValue("conf").trim().length() == 0);
+            write("<dependency");
+            String org = substitute(settings, attributes.getValue("org"));
+            org = org == null ? organisation : org;
+            String module = substitute(settings, attributes.getValue("name"));
+            String branch = substitute(settings, attributes.getValue("branch"));
+            String branchConstraint = substitute(settings, attributes.getValue("branchConstraint"));
+            branchConstraint = branchConstraint == null ? branch : branchConstraint;
+
+            // look for the branch used in resolved revisions
+            if (branch == null) {
+                ModuleId mid = ModuleId.newInstance(org, module);
+                if (ns != null) {
+                    mid = NameSpaceHelper.transform(mid, ns.getToSystemTransformer());
+                }
+                for (Iterator iter = resolvedRevisions.keySet().iterator(); iter.hasNext();) {
+                    ModuleRevisionId mrid = (ModuleRevisionId) iter.next();
+                    if (mrid.getModuleId().equals(mid)) {
+                        branch = mrid.getBranch();
+                        break;
+                    }
+                }
+            }
+
+            String revision = substitute(settings, attributes.getValue("rev"));
+            String revisionConstraint = substitute(settings, attributes.getValue("revConstraint"));
+            Map extraAttributes = ExtendableItemHelper.getExtraAttributes(settings, attributes,
+                XmlModuleDescriptorParser.DEPENDENCY_REGULAR_ATTRIBUTES);
+            ModuleRevisionId localMrid = ModuleRevisionId.newInstance(org, module, branch,
+                revision, extraAttributes);
+            ModuleRevisionId systemMrid = ns == null ? localMrid : ns.getToSystemTransformer()
+                    .transform(localMrid);
+
+            String newBranch = (String) resolvedBranches.get(systemMrid);
+
+            for (int i = 0; i < attributes.getLength(); i++) {
+                String attName = attributes.getQName(i);
+                if ("rev".equals(attName)) {
+                    String rev = (String) resolvedRevisions.get(systemMrid);
+                    if (rev != null) {
+                        write(" rev=\"" + rev + "\"");
+                        if (attributes.getIndex("branchConstraint") == -1
+                                && branchConstraint != null) {
+                            write(" branchConstraint=\"" + branchConstraint + "\"");
+                        }
+                        if (generateRevConstraint && attributes.getIndex("revConstraint") == -1
+                                && !rev.equals(systemMrid.getRevision())) {
+                            write(" revConstraint=\"" + systemMrid.getRevision() + "\"");
+                        }
+                    } else {
+                        write(" rev=\"" + systemMrid.getRevision() + "\"");
+                    }
+                } else if ("revConstraint".equals(attName)) {
+                    write(" revConstraint=\"" + revisionConstraint + "\"");
+                } else if ("org".equals(attName)) {
+                    write(" org=\"" + systemMrid.getOrganisation() + "\"");
+                } else if ("name".equals(attName)) {
+                    write(" name=\"" + systemMrid.getName() + "\"");
+                } else if ("branch".equals(attName)) {
+                    if (newBranch != null) {
+                        write(" branch=\"" + newBranch + "\"");
+                    } else if (!resolvedBranches.containsKey(systemMrid)) {
+                        write(" branch=\"" + systemMrid.getBranch() + "\"");
+                    } else {
+                        // if resolvedBranches contains the systemMrid, but the new branch is null,
+                        // the branch attribute will be removed altogether
+                    }
+                } else if ("branchConstraint".equals(attName)) {
+                    write(" branchConstraint=\"" + branchConstraint + "\"");
+                } else if ("conf".equals(attName)) {
+                    String oldMapping = substitute(settings, attributes.getValue("conf"));
+                    if (oldMapping.length() > 0) {
+                        String newMapping = removeConfigurationsFromMapping(oldMapping, confs);
+                        if (newMapping.length() > 0) {
+                            write(" conf=\"" + newMapping + "\"");
+                            ((ExtendedBuffer) buffers.peek()).setPrint(true);
+                        }
+                    }
+                } else {
+                    write(" " + attName + "=\""
+                            + substitute(settings, attributes.getValue(attName)) + "\"");
+                }
+            }
+
+            if (attributes.getIndex("branch") == -1) {
+                if (newBranch != null) {
+                    // erase an existing branch attribute if its new value is blank
+                    if (!newBranch.trim().equals("")) {
+                        write(" branch=\"" + newBranch + "\"");
+                    }
+                } else if (options.isUpdateBranch() && systemMrid.getBranch() != null) {
+                    // this dependency is on a specific branch, we set it explicitly in the updated
+                    // file
+                    write(" branch=\"" + systemMrid.getBranch() + "\"");
+                }
+            }
+        }
+
+        private void includeStarted(Attributes attributes) throws SAXException {
+            final ExtendedBuffer buffer = new ExtendedBuffer(getContext());
+            buffers.push(buffer);
+            try {
+                URL url;
+                if (settings != null) {
+                    url = settings.getRelativeUrlResolver().getURL(relativePathCtx,
+                        settings.substitute(attributes.getValue("file")),
+                        settings.substitute(attributes.getValue("url")));
+                } else {
+                    // TODO : settings can be null, but I don't why.
+                    // Check if the next code is correct in that case
+                    String fileName = attributes.getValue("file");
+                    if (fileName == null) {
+                        String urlStr = attributes.getValue("url");
+                        url = new URL(urlStr);
+                    } else {
+                        url = Checks.checkAbsolute(fileName, "settings.include").toURI().toURL();
+                    }
+                }
+                XMLHelper.parse(url, null, new DefaultHandler() {
+                    private boolean insideConfigurations = false;
+
+                    private boolean doIndent = false;
+
+                    public void startElement(String uri, String localName, String qName,
+                            Attributes attributes) throws SAXException {
+                        if ("configurations".equals(qName)) {
+                            insideConfigurations = true;
+                            String defaultconf = substitute(settings,
+                                attributes.getValue("defaultconfmapping"));
+                            if (defaultconf != null) {
+                                defaultConfMapping = defaultconf;
+                            }
+                            String mappingOverride = substitute(settings,
+                                attributes.getValue("confmappingoverride"));
+                            if (mappingOverride != null) {
+                                confMappingOverride = Boolean.valueOf(mappingOverride);
+                            }
+                        } else if ("conf".equals(qName) && insideConfigurations) {
+                            String confName = substitute(settings, attributes.getValue("name"));
+                            if (!confs.contains(confName)) {
+                                buffer.setPrint(true);
+                                if (doIndent) {
+                                    write("/>\n\t\t");
+                                }
+                                String extend = substitute(settings, attributes.getValue("extends"));
+                                if (extend != null) {
+                                    for (StringTokenizer tok = new StringTokenizer(extend, ", "); tok
+                                            .hasMoreTokens();) {
+                                        String current = tok.nextToken();
+                                        if (confs.contains(current)) {
+                                            throw new IllegalArgumentException("Cannot exclude a "
+                                                    + "configuration which is extended.");
+                                        }
+                                    }
+
+                                }
+
+                                write("<" + qName);
+                                for (int i = 0; i < attributes.getLength(); i++) {
+                                    write(" " + attributes.getQName(i) + "=\""
+                                            + substitute(settings, attributes.getValue(i)) + "\"");
+                                }
+                                doIndent = true;
+                            }
+                        }
+                    }
+
+                    public void endElement(String uri, String localName, String name)
+                            throws SAXException {
+                        if ("configurations".equals(name)) {
+                            insideConfigurations = false;
+                        }
+                    }
+                });
+            } catch (Exception e) {
+                Message.warn("exception occurred while importing configurations: " + e.getMessage());
+                throw new SAXException(e);
+            }
+        }
+
+        private void infoStarted(Attributes attributes) {
+
+            String module = substitute(settings, attributes.getValue("module"));
+            String rev = null;
+            String branch = null;
+            String status = null;
+            String namespace = null;
+            Map/* <String,String> */extraAttributes = null;
+
+            if (options.isMerge()) {
+                // get attributes from merged descriptor, ignoring raw XML
+                ModuleDescriptor merged = options.getMergedDescriptor();
+                ModuleRevisionId mergedMrid = merged.getModuleRevisionId();
+                organisation = mergedMrid.getOrganisation();
+                branch = mergedMrid.getBranch();
+                rev = mergedMrid.getRevision();
+                status = merged.getStatus();
+
+                // TODO: should namespace be added to ModuleDescriptor interface, so we don't
+                // have to do this kind of check?
+                if (merged instanceof DefaultModuleDescriptor) {
+                    Namespace ns = ((DefaultModuleDescriptor) merged).getNamespace();
+                    if (ns != null) {
+                        namespace = ns.getName();
+                    }
+                }
+                if (namespace == null) {
+                    namespace = attributes.getValue("namespace");
+                }
+
+                extraAttributes = merged.getQualifiedExtraAttributes();
+            } else {
+                // get attributes from raw XML, performing property substitution
+                organisation = substitute(settings, attributes.getValue("organisation"));
+                rev = substitute(settings, attributes.getValue("revision"));
+                branch = substitute(settings, attributes.getValue("branch"));
+                status = substitute(settings, attributes.getValue("status"));
+                namespace = substitute(settings, attributes.getValue("namespace"));
+                extraAttributes = new LinkedHashMap(attributes.getLength());
+                for (int i = 0; i < attributes.getLength(); i++) {
+                    String qname = attributes.getQName(i);
+                    if (!STD_ATTS.contains(qname)) {
+                        extraAttributes.put(qname, substitute(settings, attributes.getValue(i)));
+                    }
+                }
+            }
+
+            // apply override values provided in options
+            if (revision != null) {
+                rev = revision;
+            }
+            if (options.getBranch() != null) {
+                branch = options.getBranch();
+            }
+            if (this.status != null) {
+                status = this.status;
+            }
+
+            // if necessary translate mrid using optional namespace argument
+            ModuleRevisionId localMid = ModuleRevisionId.newInstance(organisation, module, branch,
+                rev, ExtendableItemHelper
+                        .getExtraAttributes(settings, attributes, new String[] {"organisation",
+                                "module", "revision", "status", "publication", "namespace"}));
+            ModuleRevisionId systemMid = ns == null ? localMid : ns.getToSystemTransformer()
+                    .transform(localMid);
+
+            write("<info");
+            if (organisation != null) {
+                write(" organisation=\"" + XMLHelper.escape(systemMid.getOrganisation()) + "\"");
+            }
+            write(" module=\"" + XMLHelper.escape(systemMid.getName()) + "\"");
+            if (branch != null) {
+                write(" branch=\"" + XMLHelper.escape(systemMid.getBranch()) + "\"");
+            }
+            if (systemMid.getRevision() != null) {
+                write(" revision=\"" + XMLHelper.escape(systemMid.getRevision()) + "\"");
+            }
+            write(" status=\"" + XMLHelper.escape(status) + "\"");
+            if (pubdate != null) {
+                write(" publication=\"" + DateUtil.format(pubdate) + "\"");
+            } else if (attributes.getValue("publication") != null) {
+                write(" publication=\"" + substitute(settings, attributes.getValue("publication"))
+                        + "\"");
+            }
+            if (namespace != null) {
+                write(" namespace=\"" + namespace + "\"");
+            }
+
+            for (Iterator extras = extraAttributes.entrySet().iterator(); extras.hasNext();) {
+                Map.Entry extra = (Map.Entry) extras.next();
+                write(" " + extra.getKey() + "=\"" + extra.getValue() + "\"");
+            }
+        }
+
+        private void write(String content) {
+            getWriter().print(content);
+        }
+
+        private PrintWriter getWriter() {
+            return buffers.isEmpty() ? out : ((ExtendedBuffer) buffers.peek()).getWriter();
+        }
+
+        private String getContext() {
+            StringBuffer buf = new StringBuffer();
+            for (Iterator iter = context.iterator(); iter.hasNext();) {
+                String ctx = (String) iter.next();
+                buf.append(ctx).append("/");
+            }
+            if (buf.length() > 0) {
+                buf.setLength(buf.length() - 1);
+            }
+            return buf.toString();
+        }
+
+        private String substitute(ParserSettings ivy, String value) {
+            String result = ivy == null ? value : ivy.substitute(value);
+            return XMLHelper.escape(result);
+        }
+
+        private String removeConfigurationsFromMapping(String mapping, List confsToRemove) {
+            StringBuffer newMapping = new StringBuffer();
+            String mappingSep = "";
+            for (StringTokenizer tokenizer = new StringTokenizer(mapping, ";"); tokenizer
+                    .hasMoreTokens();) {
+                String current = tokenizer.nextToken();
+                String[] ops = current.split("->");
+                String[] lhs = ops[0].split(",");
+                List confsToWrite = new ArrayList();
+                for (int j = 0; j < lhs.length; j++) {
+                    if (!confs.contains(lhs[j].trim())) {
+                        confsToWrite.add(lhs[j]);
+                    }
+                }
+                if (!confsToWrite.isEmpty()) {
+                    newMapping.append(mappingSep);
+
+                    String sep = "";
+                    for (Iterator it = confsToWrite.iterator(); it.hasNext();) {
+                        newMapping.append(sep);
+                        newMapping.append(it.next());
+                        sep = ",";
+                    }
+                    if (ops.length == 2) {
+                        newMapping.append("->");
+                        newMapping.append(ops[1]);
+                    }
+                    mappingSep = ";";
+                }
+            }
+
+            return newMapping.toString();
+        }
+
+        private String removeConfigurationsFromList(String list, List confsToRemove) {
+            StringBuffer newList = new StringBuffer();
+            String listSep = "";
+            for (StringTokenizer tokenizer = new StringTokenizer(list, ","); tokenizer
+                    .hasMoreTokens();) {
+                String current = tokenizer.nextToken();
+                if (!confsToRemove.contains(current.trim())) {
+                    newList.append(listSep);
+                    newList.append(current);
+                    listSep = ",";
+                }
+            }
+
+            return newList.toString();
+        }
+
+        public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
+            characters(ch, start, length);
+        }
+
+        public void characters(char[] ch, int start, int length) throws SAXException {
+            if (justOpen != null) {
+                write(">");
+                justOpen = null;
+            }
+            write(XMLHelper.escape(String.valueOf(ch, start, length)));
+
+            // examine characters for current indent level, keeping in mind
+            // that our indent might be split across multiple calls to characters()
+            for (int i = start, end = start + length; i < end; ++i) {
+                char c = ch[i];
+                if (c == '\r' || c == '\n') {
+                    // newline resets the indent level
+                    currentIndent.setLength(0);
+                    indenting = true;
+                } else if (indenting) {
+                    // indent continues until first non-whitespace character
+                    if (Character.isWhitespace(c)) {
+                        currentIndent.append(c);
+                    } else {
+                        endIndent();
+                    }
+                }
+            }
+        }
+
+        /** record the current indent level for future elements that appear at the same depth */
+        private void endIndent() {
+            if (indenting) {
+                // record the indent at this level. if we insert any elements at
+                // this level, we'll use the same indent.
+                setIndent(context.size() - 1, currentIndent.toString());
+                indenting = false;
+            }
+        }
+
+        /**
+         * Set the indent for the given depth. Indents less than the provided depth will be
+         * calculated automatically, if they have not already been defined.
+         */
+        private void setIndent(int level, String indent) {
+            fillIndents(level);
+            indentLevels.set(level, indent);
+        }
+
+        /**
+         * Guarantee that indent levels have been calculated up to and including the given depth
+         * (starting at 0).
+         */
+        private void fillIndents(int level) {
+            if (indentLevels.isEmpty()) {
+                // add a default single-level indent until we see indents in the document
+                indentLevels.add("    ");
+            }
+            String oneLevel = (String) indentLevels.get(0);
+            for (int fill = indentLevels.size(); fill <= level; ++fill) {
+                indentLevels.add(indentLevels.get(fill - 1) + oneLevel);
+            }
+        }
+
+        /** get the whitespace that should precede new elements at the current depth in the document */
+        private String getIndent() {
+            int level = context.size() - 1;
+            fillIndents(level);
+            return (String) indentLevels.get(level);
+        }
+
+        /**
+         * Write XML elements that do not appear in the source descriptor, but have been copied in
+         * from a parent module descriptor via <extends> declaration.
+         * 
+         * @param merged
+         *            child descriptor containing the merged data
+         * @param items
+         *            the list of inherited items to print
+         * @param printer
+         *            a printer that knows how to write the given type of item
+         * @param itemName
+         *            the name of the container element, e.g. "configurations"
+         * @param includeContainer
+         *            if true, include an enclosing element named <code>itemName</code>. Otherwise
+         *            just write the inherited items inline, with a comment indicating where they
+         *            came from.
+         */
+        private void writeInheritedItems(ModuleDescriptor merged, InheritableItem[] items,
+                ItemPrinter printer, String itemName, boolean includeContainer) {
+            // first categorize inherited items by their source module, so that
+            // we can add some useful comments
+            PrintWriter out = getWriter();
+
+            Map inheritedItems = collateInheritedItems(merged, items);
+            boolean hasItems = !inheritedItems.isEmpty();
+
+            if (hasItems && includeContainer) {
+                if (currentIndent.length() == 0) {
+                    out.print(getIndent());
+                }
+                out.print("<" + itemName + ">");
+                context.push(itemName);
+                justOpen = null;
+            }
+
+            for (Iterator parents = inheritedItems.entrySet().iterator(); parents.hasNext();) {
+                Map.Entry entry = (Map.Entry) parents.next();
+                ModuleRevisionId parent = (ModuleRevisionId) entry.getKey();
+                List list = (List) entry.getValue();
+
+                if (justOpen != null) {
+                    out.println(">");
+                    justOpen = null; // helps endElement() decide how to write close tags
+                }
+                writeInheritanceComment(itemName, parent);
+                for (int c = 0; c < list.size(); ++c) {
+                    InheritableItem item = (InheritableItem) list.get(c);
+                    out.print(getIndent());
+                    printer.print(merged, item, out);
+                }
+            }
+
+            if (hasItems) {
+                if (includeContainer) {
+                    context.pop();
+                    out.println(getIndent() + "</" + itemName + ">");
+                    out.println();
+                }
+                // restore the prior indent
+                out.print(currentIndent);
+            }
+        }
+
+        private void writeInheritanceComment(String itemDescription, Object parentInfo) {
+            PrintWriter out = getWriter();
+            out.println();
+            out.println(getIndent() + "<!-- " + itemDescription + " inherited from " + parentInfo
+                    + " -->");
+        }
+
+        /**
+         * Collect the given list of inherited descriptor items into lists keyed by parent Id. Thus
+         * all of the items inherited from parent A can be written together, then all of the items
+         * from parent B, and so on.
+         * 
+         * @param merged
+         *            the merged child descriptor
+         * @param items
+         *            the inherited items to collate
+         * @return maps parent ModuleRevisionId to a List of InheritedItems imported from that
+         *         parent
+         */
+        private Map/* <ModuleRevisionId,List> */collateInheritedItems(ModuleDescriptor merged,
+                InheritableItem[] items) {
+            LinkedHashMap/* <ModuleRevisionId,List> */inheritedItems = new LinkedHashMap();
+            for (int i = 0; i < items.length; ++i) {
+                ModuleRevisionId source = items[i].getSourceModule();
+                // ignore items that are defined directly in the child descriptor
+                if (source != null
+                        && !source.getModuleId().equals(merged.getModuleRevisionId().getModuleId())) {
+                    List accum = (List) inheritedItems.get(source);
+                    if (accum == null) {
+                        accum = new ArrayList();
+                        inheritedItems.put(source, accum);
+                    }
+                    accum.add(items[i]);
+                }
+            }
+            return inheritedItems;
+        }
+
+        /**
+         * If no info/description element has yet been written, write the description inherited from
+         * the parent descriptor, if any. Calling this method more than once has no affect.
+         */
+        private void writeInheritedDescription(ModuleDescriptor merged) {
+            if (!hasDescription) {
+                hasDescription = true;
+                String description = merged.getDescription();
+                if ((description != null) && (description.length() > 0)) {
+                    PrintWriter writer = getWriter();
+                    if (justOpen != null) {
+                        writer.println(">");
+                    }
+                    writeInheritanceComment("description", "parent");
+                    writer.println(getIndent() + "<description>" + XMLHelper.escape(description)
+                            + "</description>");
+                    // restore the indent that existed before we wrote the extra elements
+                    writer.print(currentIndent);
+                    justOpen = null;
+                }
+            }
+        }
+
+        private void writeInheritedConfigurations(ModuleDescriptor merged) {
+            if (!mergedConfigurations) {
+                mergedConfigurations = true;
+                writeInheritedItems(merged, merged.getConfigurations(),
+                    ConfigurationPrinter.INSTANCE, "configurations", false);
+            }
+        }
+
+        private void writeInheritedDependencies(ModuleDescriptor merged) {
+            if (!mergedDependencies) {
+                mergedDependencies = true;
+                writeInheritedItems(merged, merged.getDependencies(), DependencyPrinter.INSTANCE,
+                    "dependencies", false);
+            }
+        }
+
+        /**
+         * <p>
+         * If publishing in merge mode, guarantee that any merged elements appearing before
+         * <code>moduleElement</code> have been written. This method should be called <i>before</i>
+         * we write the start tag of <code>moduleElement</code>. This covers cases where merged
+         * elements like "configurations" and "dependencies" appear in the parent descriptor, but
+         * are completely missing in the child descriptor.
+         * </p>
+         * 
+         * <p>
+         * For example, if "moduleElement" is "dependencies", guarantees that "configurations" has
+         * been written. If <code>moduleElement</code> is <code>null</code>, then all missing merged
+         * elements will be flushed.
+         * </p>
+         * 
+         * @param moduleElement
+         *            a descriptor element name, for example "configurations" or "info"
+         */
+        private void flushMergedElementsBefore(String moduleElement) {
+            if (options.isMerge() && context.size() == 1 && "ivy-module".equals(context.peek())
+                    && !(mergedConfigurations && mergedDependencies)) {
+
+                // calculate the position of the element in ivy-module
+                int position = moduleElement == null ? MODULE_ELEMENTS.size() : MODULE_ELEMENTS
+                        .indexOf(moduleElement);
+
+                ModuleDescriptor merged = options.getMergedDescriptor();
+
+                // see if we should write <configurations>
+                if (!mergedConfigurations && position > CONFIGURATIONS_POSITION
+                        && merged.getConfigurations().length > 0) {
+
+                    mergedConfigurations = true;
+                    writeInheritedItems(merged, merged.getConfigurations(),
+                        ConfigurationPrinter.INSTANCE, "configurations", true);
+
+                }
+                // see if we should write <dependencies>
+                if (!mergedDependencies && position > DEPENDENCIES_POSITION
+                        && merged.getDependencies().length > 0) {
+
+                    mergedDependencies = true;
+                    writeInheritedItems(merged, merged.getDependencies(),
+                        DependencyPrinter.INSTANCE, "dependencies", true);
+
+                }
+            }
+        }
+
+        private void flushAllMergedElements() {
+            flushMergedElementsBefore(null);
+        }
+
+        public void endElement(String uri, String localName, String qName) throws SAXException {
+
+            String path = getContext();
+            if (options.isMerge()) {
+                ModuleDescriptor merged = options.getMergedDescriptor();
+
+                if ("ivy-module/info".equals(path)) {
+                    // guarantee that inherited description has been written before
+                    // info element closes.
+                    writeInheritedDescription(merged);
+                } else if ("ivy-module/configurations".equals(path)) {
+                    // write inherited configurations after all child configurations
+                    writeInheritedConfigurations(merged);
+                } else if ("ivy-module/dependencies".equals(path)) {
+                    // write inherited dependencies after all child dependencies
+                    writeInheritedDependencies(merged);
+                } else if ("ivy-module".equals(path)) {
+                    // write any remaining inherited data before we close the
+                    // descriptor.
+                    flushAllMergedElements();
+                }
+            }
+
+            if (qName.equals(justOpen)) {
+                write("/>");
+            } else {
+                write("</" + qName + ">");
+            }
+
+            if (!buffers.isEmpty()) {
+                ExtendedBuffer buffer = (ExtendedBuffer) buffers.peek();
+                if (buffer.getContext().equals(path)) {
+                    buffers.pop();
+                    if (buffer.isPrint()) {
+                        write(buffer.toString());
+                    }
+                }
+            }
+
+            if (!confAttributeBuffers.isEmpty()) {
+                ExtendedBuffer buffer = (ExtendedBuffer) confAttributeBuffers.peek();
+                if (buffer.getContext().equals(path)) {
+                    confAttributeBuffers.pop();
+                }
+            }
+
+            // <extends> element is commented out when in merge mode.
+            if (options.isMerge() && "ivy-module/info/extends".equals(path)) {
+                write(" -->");
+            }
+
+            justOpen = null;
+            context.pop();
+        }
+
+        public void endDocument() throws SAXException {
+            out.print(LINE_SEPARATOR);
+            out.flush();
+            out.close();
+        }
+
+        public void processingInstruction(String target, String data) throws SAXException {
+            write("<?");
+            write(target);
+            write(" ");
+            write(data);
+            write("?>");
+            write(LINE_SEPARATOR);
+        }
+
+        public void warning(SAXParseException e) throws SAXException {
+            throw e;
+        }
+
+        public void error(SAXParseException e) throws SAXException {
+            throw e;
+        }
+
+        public void fatalError(SAXParseException e) throws SAXException {
+            throw e;
+        }
+
+        public void endCDATA() throws SAXException {
+        }
+
+        public void endDTD() throws SAXException {
+        }
+
+        public void startCDATA() throws SAXException {
+        }
+
+        public void comment(char[] ch, int start, int length) throws SAXException {
+            if (justOpen != null) {
+                write(">");
+                justOpen = null;
+            }
+
+            StringBuffer comment = new StringBuffer();
+            comment.append(ch, start, length);
+            write("<!--");
+            write(comment.toString());
+            write("-->");
+
+            if (inHeader) {
+                write(LINE_SEPARATOR);
+            }
+        }
+
+        public void endEntity(String name) throws SAXException {
+        }
+
+        public void startEntity(String name) throws SAXException {
+        }
+
+        public void startDTD(String name, String publicId, String systemId) throws SAXException {
+        }
+
+    }
+
+    public static void update(URL inStreamCtx, InputStream inStream, OutputStream outStream,
+            final UpdateOptions options) throws IOException, SAXException {
+        final PrintWriter out = new PrintWriter(new OutputStreamWriter(outStream, "UTF-8"));
+        out.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
+        out.write(LINE_SEPARATOR);
+
+        try {
+            UpdaterHandler updaterHandler = new UpdaterHandler(inStreamCtx, out, options);
+            InputSource inSrc = new InputSource(new BufferedInputStream(inStream));
+            if (inStreamCtx != null) {
+                inSrc.setSystemId(inStreamCtx.toExternalForm());
+            }
+            XMLHelper.parse(inSrc, null, updaterHandler, updaterHandler);
+        } catch (ParserConfigurationException e) {
+            IllegalStateException ise = new IllegalStateException(
+                    "impossible to update Ivy files: parser problem");
+            ise.initCause(e);
+            throw ise;
+        }
+    }
+
+    private static class ExtendedBuffer {
+        private String context = null;
+
+        private Boolean print = null;
+
+        private boolean defaultPrint = false;
+
+        private StringWriter buffer = new StringWriter();
+
+        private PrintWriter writer = new PrintWriter(buffer);
+
+        ExtendedBuffer(String context) {
+            this.context = context;
+        }
+
+        boolean isPrint() {
+            if (print == null) {
+                return defaultPrint;
+            }
+            return print.booleanValue();
+        }
+
+        void setPrint(boolean print) {
+            this.print = Boolean.valueOf(print);
+        }
+
+        void setDefaultPrint(boolean print) {
+            this.defaultPrint = print;
+        }
+
+        PrintWriter getWriter() {
+            return writer;
+        }
+
+        String getContext() {
+            return context;
+        }
+
+        public String toString() {
+            writer.flush();
+            return buffer.toString();
+        }
+    }
+
+    /**
+     * Prints a descriptor item's XML representation
+     */
+    protected static interface ItemPrinter {
+        /**
+         * Print an XML representation of <code>item</code> to <code>out</code>.
+         * 
+         * @param parent
+         *            the module descriptor containing <code>item</code>
+         * @param item
+         *            subcomponent of the descriptor, for example a {@link DependencyDescriptor} or
+         *            {@link Configuration}
+         */
+        public void print(ModuleDescriptor parent, Object item, PrintWriter out);
+    }
+
+    protected static class DependencyPrinter implements ItemPrinter {
+
+        public static final DependencyPrinter INSTANCE = new DependencyPrinter();
+
+        public void print(ModuleDescriptor parent, Object item, PrintWriter out) {
+            XmlModuleDescriptorWriter.printDependency(parent, (DependencyDescriptor) item, out);
+        }
+    }
+
+    protected static class ConfigurationPrinter implements ItemPrinter {
+
+        public static final ConfigurationPrinter INSTANCE = new ConfigurationPrinter();
+
+        public void print(ModuleDescriptor parent, Object item, PrintWriter out) {
+            XmlModuleDescriptorWriter.printConfiguration((Configuration) item, out);
+        }
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/parser/xml/XmlModuleDescriptorWriter.java b/src/java/org/apache/ivy/plugins/parser/xml/XmlModuleDescriptorWriter.java
new file mode 100644
index 0000000..2206663
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/parser/xml/XmlModuleDescriptorWriter.java
@@ -0,0 +1,576 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.parser.xml;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.Configuration;
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.apache.ivy.core.module.descriptor.DependencyArtifactDescriptor;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptorMediator;
+import org.apache.ivy.core.module.descriptor.ExcludeRule;
+import org.apache.ivy.core.module.descriptor.ExtendsDescriptor;
+import org.apache.ivy.core.module.descriptor.ExtraInfoHolder;
+import org.apache.ivy.core.module.descriptor.IncludeRule;
+import org.apache.ivy.core.module.descriptor.License;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.descriptor.OverrideDependencyDescriptorMediator;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.plugins.matcher.MapMatcher;
+import org.apache.ivy.util.DateUtil;
+import org.apache.ivy.util.Message;
+import org.apache.ivy.util.StringUtils;
+import org.apache.ivy.util.XMLHelper;
+import org.apache.ivy.util.extendable.ExtendableItem;
+
+/**
+ *
+ */
+public final class XmlModuleDescriptorWriter {
+
+    private XmlModuleDescriptorWriter() {
+        // Utility class
+    }
+
+    public static void write(ModuleDescriptor md, File output) throws IOException {
+        write(md, null, output);
+    }
+
+    public static void write(ModuleDescriptor md, String licenseHeader, File output)
+            throws IOException {
+        if (output.getParentFile() != null) {
+            output.getParentFile().mkdirs();
+        }
+        PrintWriter out = new PrintWriter(new OutputStreamWriter(new FileOutputStream(output),
+                "UTF-8"));
+        try {
+            out.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
+            if (licenseHeader != null) {
+                out.print(licenseHeader);
+            }
+            StringBuffer xmlNamespace = new StringBuffer();
+            Map namespaces = md.getExtraAttributesNamespaces();
+            for (Iterator iter = namespaces.entrySet().iterator(); iter.hasNext();) {
+                Entry ns = (Entry) iter.next();
+                xmlNamespace.append(" xmlns:").append(ns.getKey()).append("=\"")
+                        .append(ns.getValue()).append("\"");
+            }
+
+            String version = "2.0";
+            if (md.getInheritedDescriptors().length > 0) {
+                version = "2.2";
+            }
+
+            out.println("<ivy-module version=\"" + version + "\"" + xmlNamespace + ">");
+            printInfoTag(md, out);
+            printConfigurations(md, out);
+            printPublications(md, out);
+            printDependencies(md, out);
+            out.println("</ivy-module>");
+        } finally {
+            out.close();
+        }
+    }
+
+    private static void printDependencies(ModuleDescriptor md, PrintWriter out) {
+        DependencyDescriptor[] dds = md.getDependencies();
+        if (dds.length > 0) {
+            out.println("\t<dependencies>");
+            for (int i = 0; i < dds.length; i++) {
+                DependencyDescriptor dep = dds[i];
+                out.print("\t\t");
+                printDependency(md, dep, out);
+            }
+            printAllExcludes(md, out);
+            printAllMediators(md, out);
+            out.println("\t</dependencies>");
+        }
+    }
+
+    protected static void printDependency(ModuleDescriptor md, DependencyDescriptor dep,
+            PrintWriter out) {
+        out.print("<dependency");
+        out.print(" org=\"" + XMLHelper.escape(dep.getDependencyRevisionId().getOrganisation())
+                + "\"");
+        out.print(" name=\"" + XMLHelper.escape(dep.getDependencyRevisionId().getName()) + "\"");
+        if (dep.getDependencyRevisionId().getBranch() != null) {
+            out.print(" branch=\"" + XMLHelper.escape(dep.getDependencyRevisionId().getBranch())
+                    + "\"");
+        }
+        out.print(" rev=\"" + XMLHelper.escape(dep.getDependencyRevisionId().getRevision()) + "\"");
+        if (!dep.getDynamicConstraintDependencyRevisionId().equals(dep.getDependencyRevisionId())) {
+            if (dep.getDynamicConstraintDependencyRevisionId().getBranch() != null) {
+                out.print(" branchConstraint=\""
+                        + XMLHelper.escape(dep.getDynamicConstraintDependencyRevisionId()
+                                .getBranch()) + "\"");
+            }
+            out.print(" revConstraint=\""
+                    + XMLHelper
+                            .escape(dep.getDynamicConstraintDependencyRevisionId().getRevision())
+                    + "\"");
+        }
+        if (dep.isForce()) {
+            out.print(" force=\"" + dep.isForce() + "\"");
+        }
+        if (dep.isChanging()) {
+            out.print(" changing=\"" + dep.isChanging() + "\"");
+        }
+        if (!dep.isTransitive()) {
+            out.print(" transitive=\"" + dep.isTransitive() + "\"");
+        }
+        out.print(" conf=\"");
+        String[] modConfs = dep.getModuleConfigurations();
+        for (int j = 0; j < modConfs.length; j++) {
+            String[] depConfs = dep.getDependencyConfigurations(modConfs[j]);
+            out.print(XMLHelper.escape(modConfs[j]) + "->");
+            for (int k = 0; k < depConfs.length; k++) {
+                out.print(XMLHelper.escape(depConfs[k]));
+                if (k + 1 < depConfs.length) {
+                    out.print(",");
+                }
+            }
+            if (j + 1 < modConfs.length) {
+                out.print(";");
+            }
+        }
+        out.print("\"");
+
+        printExtraAttributes(dep, out, " ");
+
+        DependencyArtifactDescriptor[] depArtifacts = dep.getAllDependencyArtifacts();
+        if (depArtifacts.length > 0) {
+            out.println(">");
+        }
+        printDependencyArtefacts(md, out, depArtifacts);
+
+        IncludeRule[] includes = dep.getAllIncludeRules();
+        if (includes.length > 0 && depArtifacts.length == 0) {
+            out.println(">");
+        }
+        printDependencyIncludeRules(md, out, includes);
+
+        ExcludeRule[] excludes = dep.getAllExcludeRules();
+        if (excludes.length > 0 && includes.length == 0 && depArtifacts.length == 0) {
+            out.println(">");
+        }
+        printDependencyExcludeRules(md, out, excludes);
+        if (includes.length + excludes.length + depArtifacts.length == 0) {
+            out.println("/>");
+        } else {
+            out.println("\t\t</dependency>");
+        }
+    }
+
+    private static void printAllMediators(ModuleDescriptor md, PrintWriter out) {
+        Map/* <MapMatcher, DependencyDescriptorMediator> */mediators = md
+                .getAllDependencyDescriptorMediators().getAllRules();
+
+        for (Iterator iterator = mediators.entrySet().iterator(); iterator.hasNext();) {
+            Map.Entry mediatorRule = (Map.Entry) iterator.next();
+            MapMatcher matcher = (MapMatcher) mediatorRule.getKey();
+            DependencyDescriptorMediator mediator = (DependencyDescriptorMediator) mediatorRule
+                    .getValue();
+
+            if (mediator instanceof OverrideDependencyDescriptorMediator) {
+                OverrideDependencyDescriptorMediator oddm = (OverrideDependencyDescriptorMediator) mediator;
+
+                out.print("\t\t<override");
+                out.print(" org=\""
+                        + XMLHelper.escape((String) matcher.getAttributes().get(
+                            IvyPatternHelper.ORGANISATION_KEY)) + "\"");
+                out.print(" module=\""
+                        + XMLHelper.escape((String) matcher.getAttributes().get(
+                            IvyPatternHelper.MODULE_KEY)) + "\"");
+                out.print(" matcher=\"" + XMLHelper.escape(matcher.getPatternMatcher().getName())
+                        + "\"");
+                if (oddm.getBranch() != null) {
+                    out.print(" branch=\"" + XMLHelper.escape(oddm.getBranch()) + "\"");
+                }
+                if (oddm.getVersion() != null) {
+                    out.print(" rev=\"" + XMLHelper.escape(oddm.getVersion()) + "\"");
+                }
+                out.println("/>");
+            } else {
+                Message.verbose("ignoring unhandled DependencyDescriptorMediator: "
+                        + mediator.getClass());
+            }
+        }
+    }
+
+    private static void printAllExcludes(ModuleDescriptor md, PrintWriter out) {
+        ExcludeRule[] excludes = md.getAllExcludeRules();
+        if (excludes.length > 0) {
+            for (int j = 0; j < excludes.length; j++) {
+                out.print("\t\t<exclude");
+                out.print(" org=\""
+                        + XMLHelper.escape(excludes[j].getId().getModuleId().getOrganisation())
+                        + "\"");
+                out.print(" module=\""
+                        + XMLHelper.escape(excludes[j].getId().getModuleId().getName()) + "\"");
+                out.print(" artifact=\"" + XMLHelper.escape(excludes[j].getId().getName()) + "\"");
+                out.print(" type=\"" + XMLHelper.escape(excludes[j].getId().getType()) + "\"");
+                out.print(" ext=\"" + XMLHelper.escape(excludes[j].getId().getExt()) + "\"");
+                String[] ruleConfs = excludes[j].getConfigurations();
+                if (!Arrays.asList(ruleConfs).equals(Arrays.asList(md.getConfigurationsNames()))) {
+                    out.print(" conf=\"");
+                    for (int k = 0; k < ruleConfs.length; k++) {
+                        out.print(XMLHelper.escape(ruleConfs[k]));
+                        if (k + 1 < ruleConfs.length) {
+                            out.print(",");
+                        }
+                    }
+                    out.print("\"");
+                }
+                out.print(" matcher=\"" + XMLHelper.escape(excludes[j].getMatcher().getName())
+                        + "\"");
+                out.println("/>");
+            }
+        }
+    }
+
+    private static void printDependencyExcludeRules(ModuleDescriptor md, PrintWriter out,
+            ExcludeRule[] excludes) {
+        if (excludes.length > 0) {
+            for (int j = 0; j < excludes.length; j++) {
+                out.print("\t\t\t<exclude");
+                out.print(" org=\""
+                        + XMLHelper.escape(excludes[j].getId().getModuleId().getOrganisation())
+                        + "\"");
+                out.print(" module=\""
+                        + XMLHelper.escape(excludes[j].getId().getModuleId().getName()) + "\"");
+                out.print(" name=\"" + XMLHelper.escape(excludes[j].getId().getName()) + "\"");
+                out.print(" type=\"" + XMLHelper.escape(excludes[j].getId().getType()) + "\"");
+                out.print(" ext=\"" + XMLHelper.escape(excludes[j].getId().getExt()) + "\"");
+                String[] ruleConfs = excludes[j].getConfigurations();
+                if (!Arrays.asList(ruleConfs).equals(Arrays.asList(md.getConfigurationsNames()))) {
+                    out.print(" conf=\"");
+                    for (int k = 0; k < ruleConfs.length; k++) {
+                        out.print(XMLHelper.escape(ruleConfs[k]));
+                        if (k + 1 < ruleConfs.length) {
+                            out.print(",");
+                        }
+                    }
+                    out.print("\"");
+                }
+                out.print(" matcher=\"" + XMLHelper.escape(excludes[j].getMatcher().getName())
+                        + "\"");
+                out.println("/>");
+            }
+        }
+    }
+
+    private static void printDependencyIncludeRules(ModuleDescriptor md, PrintWriter out,
+            IncludeRule[] includes) {
+        if (includes.length > 0) {
+            for (int j = 0; j < includes.length; j++) {
+                out.print("\t\t\t<include");
+                out.print(" name=\"" + XMLHelper.escape(includes[j].getId().getName()) + "\"");
+                out.print(" type=\"" + XMLHelper.escape(includes[j].getId().getType()) + "\"");
+                out.print(" ext=\"" + XMLHelper.escape(includes[j].getId().getExt()) + "\"");
+                String[] ruleConfs = includes[j].getConfigurations();
+                if (!Arrays.asList(ruleConfs).equals(Arrays.asList(md.getConfigurationsNames()))) {
+                    out.print(" conf=\"");
+                    for (int k = 0; k < ruleConfs.length; k++) {
+                        out.print(XMLHelper.escape(ruleConfs[k]));
+                        if (k + 1 < ruleConfs.length) {
+                            out.print(",");
+                        }
+                    }
+                    out.print("\"");
+                }
+                out.print(" matcher=\"" + XMLHelper.escape(includes[j].getMatcher().getName())
+                        + "\"");
+                out.println("/>");
+            }
+        }
+    }
+
+    private static void printDependencyArtefacts(ModuleDescriptor md, PrintWriter out,
+            DependencyArtifactDescriptor[] depArtifacts) {
+        if (depArtifacts.length > 0) {
+            for (int j = 0; j < depArtifacts.length; j++) {
+                out.print("\t\t\t<artifact");
+                out.print(" name=\"" + XMLHelper.escape(depArtifacts[j].getName()) + "\"");
+                out.print(" type=\"" + XMLHelper.escape(depArtifacts[j].getType()) + "\"");
+                out.print(" ext=\"" + XMLHelper.escape(depArtifacts[j].getExt()) + "\"");
+                String[] dadconfs = depArtifacts[j].getConfigurations();
+                if (!Arrays.asList(dadconfs).equals(Arrays.asList(md.getConfigurationsNames()))) {
+                    out.print(" conf=\"");
+                    for (int k = 0; k < dadconfs.length; k++) {
+                        out.print(XMLHelper.escape(dadconfs[k]));
+                        if (k + 1 < dadconfs.length) {
+                            out.print(",");
+                        }
+                    }
+                    out.print("\"");
+                }
+                printExtraAttributes(depArtifacts[j], out, " ");
+                out.println("/>");
+            }
+        }
+    }
+
+    /**
+     * Writes the extra attributes of the given {@link ExtendableItem} to the given
+     * <tt>PrintWriter</tt>.
+     * 
+     * @param item
+     *            the {@link ExtendableItem}, cannot be <tt>null</tt>
+     * @param out
+     *            the writer to use
+     * @param prefix
+     *            the string to write before writing the attributes (if any)
+     */
+    private static void printExtraAttributes(ExtendableItem item, PrintWriter out, String prefix) {
+        printExtraAttributes(item.getQualifiedExtraAttributes(), out, prefix);
+    }
+
+    /**
+     * Writes the specified <tt>Map</tt> containing the extra attributes to the given
+     * <tt>PrintWriter</tt>.
+     * 
+     * @param extra
+     *            the extra attributes, can be <tt>null</tt>
+     * @param out
+     *            the writer to use
+     * @param prefix
+     *            the string to write before writing the attributes (if any)
+     */
+    private static void printExtraAttributes(Map extra, PrintWriter out, String prefix) {
+        if (extra == null) {
+            return;
+        }
+
+        String delim = prefix;
+        for (Iterator iter = extra.entrySet().iterator(); iter.hasNext();) {
+            Map.Entry entry = (Map.Entry) iter.next();
+            out.print(delim + entry.getKey() + "=\""
+                    + XMLHelper.escape(entry.getValue().toString()) + "\"");
+            delim = " ";
+        }
+    }
+
+    private static void printPublications(ModuleDescriptor md, PrintWriter out) {
+        out.println("\t<publications>");
+        Artifact[] artifacts = md.getAllArtifacts();
+        for (int i = 0; i < artifacts.length; i++) {
+            out.print("\t\t<artifact");
+            out.print(" name=\"" + XMLHelper.escape(artifacts[i].getName()) + "\"");
+            out.print(" type=\"" + XMLHelper.escape(artifacts[i].getType()) + "\"");
+            out.print(" ext=\"" + XMLHelper.escape(artifacts[i].getExt()) + "\"");
+            out.print(" conf=\"" + XMLHelper.escape(getConfs(md, artifacts[i])) + "\"");
+            printExtraAttributes(artifacts[i], out, " ");
+            out.println("/>");
+        }
+        out.println("\t</publications>");
+    }
+
+    private static void printConfigurations(ModuleDescriptor md, PrintWriter out) {
+        Configuration[] confs = md.getConfigurations();
+        if (confs.length > 0) {
+            out.println("\t<configurations>");
+            for (int i = 0; i < confs.length; i++) {
+                Configuration conf = confs[i];
+                out.print("\t\t");
+                printConfiguration(conf, out);
+            }
+            out.println("\t</configurations>");
+        }
+    }
+
+    protected static void printConfiguration(Configuration conf, PrintWriter out) {
+        out.print("<conf");
+        out.print(" name=\"" + XMLHelper.escape(conf.getName()) + "\"");
+        out.print(" visibility=\"" + XMLHelper.escape(conf.getVisibility().toString()) + "\"");
+        if (conf.getDescription() != null) {
+            out.print(" description=\"" + XMLHelper.escape(conf.getDescription()) + "\"");
+        }
+        String[] exts = conf.getExtends();
+        if (exts.length > 0) {
+            out.print(" extends=\"");
+            for (int j = 0; j < exts.length; j++) {
+                out.print(XMLHelper.escape(exts[j]));
+                if (j + 1 < exts.length) {
+                    out.print(",");
+                }
+            }
+            out.print("\"");
+        }
+        if (!conf.isTransitive()) {
+            out.print(" transitive=\"false\"");
+        }
+        if (conf.getDeprecated() != null) {
+            out.print(" deprecated=\"" + XMLHelper.escape(conf.getDeprecated()) + "\"");
+        }
+        printExtraAttributes(conf, out, " ");
+        out.println("/>");
+    }
+
+    private static void printInfoTag(ModuleDescriptor md, PrintWriter out) {
+        out.println("\t<info organisation=\""
+                + XMLHelper.escape(md.getModuleRevisionId().getOrganisation()) + "\"");
+        out.println("\t\tmodule=\"" + XMLHelper.escape(md.getModuleRevisionId().getName()) + "\"");
+        String branch = md.getResolvedModuleRevisionId().getBranch();
+        if (branch != null) {
+            out.println("\t\tbranch=\"" + XMLHelper.escape(branch) + "\"");
+        }
+        String revision = md.getResolvedModuleRevisionId().getRevision();
+        if (revision != null) {
+            out.println("\t\trevision=\"" + XMLHelper.escape(revision) + "\"");
+        }
+        out.println("\t\tstatus=\"" + XMLHelper.escape(md.getStatus()) + "\"");
+        out.println("\t\tpublication=\"" + DateUtil.format(md.getResolvedPublicationDate()) + "\"");
+        if (md.isDefault()) {
+            out.println("\t\tdefault=\"true\"");
+        }
+        if (md instanceof DefaultModuleDescriptor) {
+            DefaultModuleDescriptor dmd = (DefaultModuleDescriptor) md;
+            if (dmd.getNamespace() != null && !dmd.getNamespace().getName().equals("system")) {
+                out.println("\t\tnamespace=\"" + XMLHelper.escape(dmd.getNamespace().getName())
+                        + "\"");
+            }
+        }
+        if (!md.getExtraAttributes().isEmpty()) {
+            printExtraAttributes(md, out, "\t\t");
+            out.println();
+        }
+        if (requireInnerInfoElement(md)) {
+            out.println("\t>");
+            ExtendsDescriptor[] parents = md.getInheritedDescriptors();
+            for (int i = 0; i < parents.length; i++) {
+                ExtendsDescriptor parent = parents[i];
+                ModuleRevisionId mrid = parent.getParentRevisionId();
+                out.print("\t\t<extends organisation=\"" + XMLHelper.escape(mrid.getOrganisation())
+                        + "\"" + " module=\"" + XMLHelper.escape(mrid.getName()) + "\""
+                        + " revision=\"" + XMLHelper.escape(mrid.getRevision()) + "\"");
+
+                String location = parent.getLocation();
+                if (location != null) {
+                    out.print(" location=\"" + XMLHelper.escape(location) + "\"");
+                }
+                out.print(" extendType=\"" + StringUtils.join(parent.getExtendsTypes(), ",") + "\"");
+                out.println("/>");
+            }
+            License[] licenses = md.getLicenses();
+            for (int i = 0; i < licenses.length; i++) {
+                License license = licenses[i];
+                out.print("\t\t<license ");
+                if (license.getName() != null) {
+                    out.print("name=\"" + XMLHelper.escape(license.getName()) + "\" ");
+                }
+                if (license.getUrl() != null) {
+                    out.print("url=\"" + XMLHelper.escape(license.getUrl()) + "\" ");
+                }
+                out.println("/>");
+            }
+            if (md.getHomePage() != null || md.getDescription() != null) {
+                out.print("\t\t<description");
+                if (md.getHomePage() != null) {
+                    out.print(" homepage=\"" + XMLHelper.escape(md.getHomePage()) + "\"");
+                }
+                if (md.getDescription() != null && md.getDescription().trim().length() > 0) {
+                    out.println(">");
+                    out.println("\t\t" + XMLHelper.escape(md.getDescription()));
+                    out.println("\t\t</description>");
+                } else {
+                    out.println(" />");
+                }
+            }
+            for (ExtraInfoHolder extraInfo : md.getExtraInfos()) {
+                printExtraInfoElement(out, extraInfo, 2);
+            }
+            out.println("\t</info>");
+        } else {
+            out.println("\t/>");
+        }
+
+    }
+
+    private static void printExtraInfoElement(PrintWriter out, ExtraInfoHolder extraInfo, int indent) {
+        for (int i = 1; i <= indent; i++) {
+            out.print("\t");
+        }
+        out.print("<");
+        out.print(extraInfo.getName());
+        for (Entry<String, String> entry : extraInfo.getAttributes().entrySet()) {
+            out.print(" ");
+            out.print(entry.getKey());
+            out.print("=");
+            out.print("\"");
+            out.print(entry.getValue());
+            out.print("\"");
+        }
+        boolean requireClosingTag = false;
+        if (extraInfo.getContent() != null && extraInfo.getContent().trim().length() > 0) {
+            out.print(">");
+            out.print(XMLHelper.escape(extraInfo.getContent()));
+            requireClosingTag = true;
+        }
+        if (!extraInfo.getNestedExtraInfoHolder().isEmpty()) {
+            out.println(">");
+            for (ExtraInfoHolder nestedElement : extraInfo.getNestedExtraInfoHolder()) {
+                printExtraInfoElement(out, nestedElement, indent + 1);
+            }
+            requireClosingTag = true;
+            // prepare indentation for closing tag
+            for (int i = 1; i <= indent; i++) {
+                out.print("\t");
+            }
+        }
+        if (requireClosingTag) {
+            out.print("</");
+            out.print(extraInfo.getName());
+            out.println(">");
+        } else {
+            out.println("/>");
+        }
+    }
+
+    private static boolean requireInnerInfoElement(ModuleDescriptor md) {
+        return md.getExtraInfo().size() > 0 || md.getExtraInfos().size() > 0
+                || md.getHomePage() != null
+                || (md.getDescription() != null && md.getDescription().trim().length() > 0)
+                || md.getLicenses().length > 0 || md.getInheritedDescriptors().length > 0;
+    }
+
+    private static String getConfs(ModuleDescriptor md, Artifact artifact) {
+        StringBuffer ret = new StringBuffer();
+
+        String[] confs = md.getConfigurationsNames();
+        for (int i = 0; i < confs.length; i++) {
+            if (Arrays.asList(md.getArtifacts(confs[i])).contains(artifact)) {
+                ret.append(confs[i]).append(",");
+            }
+        }
+        if (ret.length() > 0) {
+            ret.setLength(ret.length() - 1);
+        }
+        return ret.toString();
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/parser/xml/ivy.xsd b/src/java/org/apache/ivy/plugins/parser/xml/ivy.xsd
new file mode 100644
index 0000000..fabad55
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/parser/xml/ivy.xsd
@@ -0,0 +1,291 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
+
+	<xs:complexType name="configurations-conf">
+        <xs:attribute name="name" type="xs:string" use="required"/>
+        <xs:attribute name="transitive" type="xs:boolean"/>
+        <xs:attribute name="extends" type="xs:string"/>
+        <xs:attribute name="description" type="xs:string"/>
+        <xs:attribute name="deprecated" type="xs:string"/>
+        <xs:attribute name="visibility" default="public">
+			<xs:simpleType>
+				<xs:restriction base="xs:string">
+					<xs:enumeration value="private"/>
+					<xs:enumeration value="public"/>
+				</xs:restriction>
+			</xs:simpleType>
+		</xs:attribute>
+        <xs:anyAttribute namespace="##other" processContents="lax" />
+	</xs:complexType>
+
+	<xs:complexType name="global-exclude">
+        <xs:sequence>
+      		<xs:element name="conf" minOccurs="0" maxOccurs="unbounded">
+            	<xs:complexType>
+		            <xs:attribute name="name" type="xs:string" use="required"/>
+            	</xs:complexType>
+      		</xs:element>
+        </xs:sequence>
+        <xs:attribute name="org" type="xs:string"/>
+        <xs:attribute name="module" type="xs:string"/>
+        <xs:attribute name="artifact" type="xs:string"/>
+        <xs:attribute name="type" type="xs:string"/>
+        <xs:attribute name="ext" type="xs:string"/>
+        <xs:attribute name="conf" type="xs:string"/>
+        <xs:attribute name="matcher" type="xs:string"/>
+        <xs:anyAttribute namespace="##other" processContents="lax" />
+	</xs:complexType>
+
+  <xs:element name="ivy-module">
+      <xs:complexType>
+        <xs:sequence>
+  		    <xs:element name="info">
+            	<xs:complexType>
+			        <xs:sequence>
+			      		<xs:element name="extends" minOccurs="0" maxOccurs="unbounded">
+			      		<xs:complexType>
+			      			<xs:attribute name="organisation" type="xs:string" use="required"/>
+			      			<xs:attribute name="module" type="xs:string" use="required"/>
+			      			<xs:attribute name="revision" type="xs:string" use="required"/>
+			      			<xs:attribute name="location" type="xs:string" />
+			      			<xs:attribute name="extendType" type="xs:string" />
+			              	</xs:complexType>
+			      		</xs:element>
+			      		<xs:element name="license" minOccurs="0" maxOccurs="unbounded">
+			            	<xs:complexType>
+					            <xs:attribute name="name" type="xs:string" use="required"/>
+					            <xs:attribute name="url" type="xs:string"/>
+			            	</xs:complexType>
+			      		</xs:element>
+			      		<xs:element name="ivyauthor" minOccurs="0" maxOccurs="unbounded">
+			            	<xs:complexType>
+					            <xs:attribute name="name" type="xs:string" use="required"/>
+					            <xs:attribute name="url" type="xs:string"/>
+			            	</xs:complexType>
+			      		</xs:element>
+			      		<xs:element name="repository" minOccurs="0" maxOccurs="unbounded">
+			            	<xs:complexType>
+					            <xs:attribute name="name" type="xs:string" use="required"/>
+					            <xs:attribute name="url" type="xs:string"/>
+					            <xs:attribute name="pattern" type="xs:string"/>
+					            <xs:attribute name="ivys" type="xs:boolean"/>
+					            <xs:attribute name="artifacts" type="xs:boolean"/>
+			            	</xs:complexType>
+			      		</xs:element>
+			      		<xs:element name="description" minOccurs="0" maxOccurs="1">
+			            	<xs:complexType mixed="true">
+			            			<xs:sequence>
+			            				<xs:any minOccurs="0" maxOccurs="unbounded" processContents="lax"/>
+					            	</xs:sequence>
+					            <xs:attribute name="homepage" type="xs:string"/>
+			            	</xs:complexType>
+			      		</xs:element>
+			      		<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other" processContents="lax"/>
+			        </xs:sequence>
+		            <xs:attribute name="organisation" type="xs:string"/>
+		            <xs:attribute name="module" type="xs:string" use="required"/>
+		            <xs:attribute name="branch" type="xs:string"/>
+		            <xs:attribute name="revision" type="xs:string"/>
+		            <xs:attribute name="status" type="xs:string"/>
+		            <xs:attribute name="publication" type="xs:string"/>
+		            <xs:attribute name="resolver" type="xs:string"/>
+		            <xs:attribute name="namespace" type="xs:string"/>
+		            <xs:attribute name="default" type="xs:boolean"/>
+		            <xs:anyAttribute namespace="##other" processContents="lax" />
+	            </xs:complexType>
+      		</xs:element>
+      		<xs:element name="configurations" minOccurs="0">
+			      <xs:complexType>
+				        <xs:sequence>
+				        	<xs:choice minOccurs="0" maxOccurs="unbounded">
+					      		<xs:element name="conf" type="configurations-conf"/>
+					      		<xs:element name="include">
+					            	<xs:complexType>
+							            <xs:attribute name="file" type="xs:string"/>
+							            <xs:attribute name="url"  type="xs:string"/>
+					            	</xs:complexType>
+					      		</xs:element>
+					      	</xs:choice>
+				        </xs:sequence>
+				        <xs:attribute name="defaultconf" type="xs:string"/>
+						<xs:attribute name="defaultconfmapping" type="xs:string"/>				  
+						<xs:attribute name="confmappingoverride" type="xs:boolean" />      
+			      </xs:complexType>
+      		</xs:element>
+      		<xs:element name="publications" minOccurs="0">
+			      <xs:complexType>
+				        <xs:sequence>
+				      		<xs:element name="artifact" minOccurs="0" maxOccurs="unbounded">
+				            	<xs:complexType>
+							        <xs:sequence>
+							      		<xs:element name="conf" minOccurs="0" maxOccurs="unbounded">
+							            	<xs:complexType>
+									            <xs:attribute name="name" type="xs:string" use="required"/>
+							            	</xs:complexType>
+							      		</xs:element>
+							        </xs:sequence>
+						            <xs:attribute name="name" type="xs:string"/>
+						            <xs:attribute name="type" type="xs:string"/>
+						            <xs:attribute name="ext" type="xs:string"/>
+						            <xs:attribute name="conf" type="xs:string"/>
+						            <xs:attribute name="url" type="xs:string"/>
+                                    <xs:attribute name="packaging" type="xs:string"/>
+						            <xs:anyAttribute namespace="##other" processContents="lax" />
+				            	</xs:complexType>
+				      		</xs:element>
+				        </xs:sequence>
+						<xs:attribute name="defaultconf" type="xs:string"/>				  
+			      </xs:complexType>
+      		</xs:element>
+      		<xs:element name="dependencies" minOccurs="0">
+			      <xs:complexType>
+				        <xs:sequence>
+				      		<xs:element name="dependency" minOccurs="0" maxOccurs="unbounded">
+				            	<xs:complexType>
+							        <xs:sequence>
+							      		<xs:element name="conf" minOccurs="0" maxOccurs="unbounded">
+							            	<xs:complexType>
+										        <xs:sequence>
+										      		<xs:element name="mapped" minOccurs="0" maxOccurs="unbounded">
+										            	<xs:complexType>
+												            <xs:attribute name="name" type="xs:string" use="required"/>
+										            	</xs:complexType>
+										      		</xs:element>
+										        </xs:sequence>
+									            <xs:attribute name="name" type="xs:string" use="required"/>
+									            <xs:attribute name="mapped" type="xs:string"/>
+							            	</xs:complexType>
+							      		</xs:element>
+							      		<xs:element name="artifact" minOccurs="0" maxOccurs="unbounded">
+							            	<xs:complexType>
+										        <xs:sequence>
+										      		<xs:element name="conf" minOccurs="0" maxOccurs="unbounded">
+										            	<xs:complexType>
+												            <xs:attribute name="name" type="xs:string" use="required"/>
+										            	</xs:complexType>
+										      		</xs:element>
+										        </xs:sequence>
+									            <xs:attribute name="name" type="xs:string" use="required"/>
+									            <xs:attribute name="type" type="xs:string"/>
+									            <xs:attribute name="ext" type="xs:string"/>
+									            <xs:attribute name="conf" type="xs:string"/>
+									            <xs:attribute name="url" type="xs:string"/>
+									            <xs:anyAttribute namespace="##other" processContents="lax" />
+							            	</xs:complexType>
+							      		</xs:element>
+							      		<xs:element name="include" minOccurs="0" maxOccurs="unbounded">
+							            	<xs:complexType>
+										        <xs:sequence>
+										      		<xs:element name="conf" minOccurs="0" maxOccurs="unbounded">
+										            	<xs:complexType>
+												            <xs:attribute name="name" type="xs:string" use="required"/>
+										            	</xs:complexType>
+										      		</xs:element>
+										        </xs:sequence>
+									            <xs:attribute name="name" type="xs:string"/>
+									            <xs:attribute name="type" type="xs:string"/>
+									            <xs:attribute name="ext" type="xs:string"/>
+									            <xs:attribute name="conf" type="xs:string"/>
+									            <xs:attribute name="matcher" type="xs:string"/>
+									            <xs:anyAttribute namespace="##other" processContents="lax" />
+							            	</xs:complexType>
+							      		</xs:element>
+							      		<xs:element name="exclude" minOccurs="0" maxOccurs="unbounded">
+							            	<xs:complexType>
+										        <xs:sequence>
+										      		<xs:element name="conf" minOccurs="0" maxOccurs="unbounded">
+										            	<xs:complexType>
+												            <xs:attribute name="name" type="xs:string" use="required"/>
+										            	</xs:complexType>
+										      		</xs:element>
+										        </xs:sequence>
+									            <xs:attribute name="org" type="xs:string"/>
+									            <xs:attribute name="module" type="xs:string"/>
+									            <xs:attribute name="name" type="xs:string"/>
+									            <xs:attribute name="type" type="xs:string"/>
+									            <xs:attribute name="ext" type="xs:string"/>
+									            <xs:attribute name="conf" type="xs:string"/>
+									            <xs:attribute name="matcher" type="xs:string"/>
+									            <xs:anyAttribute namespace="##other" processContents="lax" />
+							            	</xs:complexType>
+							      		</xs:element>
+							        </xs:sequence>
+						            <xs:attribute name="org" type="xs:string"/>
+						            <xs:attribute name="name" type="xs:string" use="required"/>
+						            <xs:attribute name="branch" type="xs:string"/>
+						            <xs:attribute name="branchConstraint" type="xs:string"/>
+						            <xs:attribute name="rev" type="xs:string" use="required"/>
+						            <xs:attribute name="revConstraint" type="xs:string"/>
+						            <xs:attribute name="force" type="xs:boolean"/>
+						            <xs:attribute name="changing" type="xs:boolean" default="false"/>
+						            <xs:attribute name="transitive" type="xs:boolean" default="true"/>
+						            <xs:attribute name="conf" type="xs:string"/>
+						            <xs:anyAttribute namespace="##other" processContents="lax" />
+				            	</xs:complexType>
+				      		</xs:element>
+				      		<xs:element name="exclude" type="global-exclude" minOccurs="0" maxOccurs="unbounded" />
+				      		<xs:element name="override" minOccurs="0" maxOccurs="unbounded">
+				            	<xs:complexType>
+						            <xs:attribute name="org" type="xs:string"/>
+						            <xs:attribute name="module" type="xs:string"/>
+						            <xs:attribute name="matcher" type="xs:string"/>
+						            <xs:attribute name="rev" type="xs:string"/>
+						            <xs:attribute name="branch" type="xs:string"/>
+				            	</xs:complexType>
+				      		</xs:element>
+				      		<xs:element name="conflict" minOccurs="0" maxOccurs="unbounded">
+				            	<xs:complexType>
+						            <xs:attribute name="org" type="xs:string"/>
+						            <xs:attribute name="module" type="xs:string"/>
+						            <xs:attribute name="manager" type="xs:string"/>
+						            <xs:attribute name="rev" type="xs:string"/>
+						            <xs:attribute name="matcher" type="xs:string"/>
+						            <xs:anyAttribute namespace="##other" processContents="lax" />
+				            	</xs:complexType>
+				      		</xs:element>
+				        </xs:sequence>
+						<xs:attribute name="defaultconf" type="xs:string"/>				        
+						<xs:attribute name="defaultconfmapping" type="xs:string"/>				        
+                        <xs:attribute name="confmappingoverride" type="xs:boolean" />
+			      </xs:complexType>
+      		</xs:element>
+      		<xs:element name="conflicts" minOccurs="0">
+			      <xs:complexType>
+				        <xs:sequence>
+				      		<xs:element name="manager" maxOccurs="unbounded">
+				            	<xs:complexType>
+						            <xs:attribute name="org" type="xs:string"/>
+						            <xs:attribute name="module" type="xs:string"/>
+						            <xs:attribute name="name" type="xs:string"/>
+						            <xs:attribute name="rev" type="xs:string"/>
+						            <xs:attribute name="matcher" type="xs:string"/>
+						            <xs:anyAttribute namespace="##other" processContents="lax" />
+				            	</xs:complexType>
+				      		</xs:element>
+				        </xs:sequence>
+			      </xs:complexType>
+      		</xs:element>
+        </xs:sequence>
+        <xs:attribute name="version" type="xs:string" use="required"/>
+      </xs:complexType>
+  </xs:element>
+
+</xs:schema>
diff --git a/src/java/org/apache/ivy/plugins/report/LogReportOutputter.java b/src/java/org/apache/ivy/plugins/report/LogReportOutputter.java
new file mode 100644
index 0000000..c2b853b
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/report/LogReportOutputter.java
@@ -0,0 +1,199 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.report;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.ivy.core.IvyContext;
+import org.apache.ivy.core.cache.ResolutionCacheManager;
+import org.apache.ivy.core.report.ArtifactDownloadReport;
+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.IvyNodeEviction.EvictionData;
+import org.apache.ivy.core.resolve.ResolveOptions;
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.ivy.util.Message;
+
+/**
+ *
+ */
+public class LogReportOutputter implements ReportOutputter {
+
+    public String getName() {
+        return CONSOLE;
+    }
+
+    public void output(ResolveReport report, ResolutionCacheManager cacheMgr, ResolveOptions options)
+            throws IOException {
+        IvySettings settings = IvyContext.getContext().getSettings();
+
+        if (settings.logModulesInUse() && ResolveOptions.LOG_DEFAULT.equals(options.getLog())) {
+            Message.info("\t:: modules in use:");
+            List dependencies = new ArrayList(report.getDependencies());
+            Collections.sort(dependencies);
+            if (dependencies.size() > 0) {
+                String[] confs = report.getConfigurations();
+                for (int i = 0; i < dependencies.size(); i++) {
+                    IvyNode node = (IvyNode) dependencies.get(i);
+                    if (node.isCompletelyEvicted() || node.hasProblem()) {
+                        continue;
+                    }
+                    List nodeConfs = new ArrayList(confs.length);
+                    for (int j = 0; j < confs.length; j++) {
+                        String conf = confs[j];
+                        if (report.getConfigurationReport(conf).getModuleRevisionIds()
+                                .contains(node.getResolvedId())) {
+                            nodeConfs.add(conf);
+                        }
+                    }
+                    Message.info("\t" + node + " from "
+                            + node.getModuleRevision().getResolver().getName() + " in " + nodeConfs);
+                }
+            }
+        }
+
+        IvyNode[] evicted = report.getEvictedNodes();
+
+        if (evicted.length > 0 && ResolveOptions.LOG_DEFAULT.equals(options.getLog())) {
+            Message.info("\t:: evicted modules:");
+            for (int i = 0; i < evicted.length; i++) {
+                Collection allEvictingNodes = evicted[i].getAllEvictingNodesDetails();
+                if (allEvictingNodes == null) {
+                    Message.info("\t" + evicted[i] + " transitively in "
+                            + Arrays.asList(evicted[i].getEvictedConfs()));
+                } else if (allEvictingNodes.isEmpty()) {
+                    Message.info("\t" + evicted[i] + " by [] ("
+                            + evicted[i].getAllEvictingConflictManagers() + ") in "
+                            + Arrays.asList(evicted[i].getEvictedConfs()));
+                } else {
+                    Message.info("\t" + evicted[i] + " by " + allEvictingNodes + " in "
+                            + Arrays.asList(evicted[i].getEvictedConfs()));
+                }
+                String[] confs = evicted[i].getEvictedConfs();
+                for (int j = 0; j < confs.length; j++) {
+                    EvictionData evictedData = evicted[i].getEvictedData(confs[j]);
+                    if (evictedData.getParent() != null) {
+                        Message.verbose("\t  in " + evictedData.getParent() + " with "
+                                + evictedData.getConflictManager());
+                    }
+                }
+            }
+        }
+
+        if (ResolveOptions.LOG_DEFAULT.equals(options.getLog())) {
+            // CheckStyle:MagicNumber| OFF
+            char[] sep = new char[69];
+            Arrays.fill(sep, '-');
+            Message.rawinfo("\t" + new String(sep));
+            StringBuffer line = new StringBuffer("\t");
+            append(line, "", 18);
+            append(line, "modules", 31);
+            line.append("|");
+            append(line, "artifacts", 15);
+            line.append("|");
+            Message.rawinfo(line.toString());
+
+            line = new StringBuffer("\t");
+            append(line, "conf", 18);
+            append(line, "number", 7);
+            append(line, "search", 7);
+            append(line, "dwnlded", 7);
+            append(line, "evicted", 7);
+            line.append("|");
+            append(line, "number", 7);
+            append(line, "dwnlded", 7);
+            // CheckStyle:MagicNumber| ON
+            line.append("|");
+            Message.rawinfo(line.toString());
+            Message.rawinfo("\t" + new String(sep));
+
+            String[] confs = report.getConfigurations();
+            for (int i = 0; i < confs.length; i++) {
+                output(report.getConfigurationReport(confs[i]));
+            }
+            Message.rawinfo("\t" + new String(sep));
+        }
+
+        IvyNode[] unresolved = report.getUnresolvedDependencies();
+        if (unresolved.length > 0) {
+            Message.warn("\t::::::::::::::::::::::::::::::::::::::::::::::");
+            Message.warn("\t::          UNRESOLVED DEPENDENCIES         ::");
+            Message.warn("\t::::::::::::::::::::::::::::::::::::::::::::::");
+        }
+        for (int i = 0; i < unresolved.length; i++) {
+            Message.warn("\t:: " + unresolved[i] + ": " + unresolved[i].getProblemMessage());
+        }
+        if (unresolved.length > 0) {
+            Message.warn("\t::::::::::::::::::::::::::::::::::::::::::::::\n");
+        }
+
+        ArtifactDownloadReport[] errors = report.getFailedArtifactsReports();
+        if (errors.length > 0) {
+            Message.warn("\t::::::::::::::::::::::::::::::::::::::::::::::");
+            Message.warn("\t::              FAILED DOWNLOADS            ::");
+            Message.warn("\t:: ^ see resolution messages for details  ^ ::");
+            Message.warn("\t::::::::::::::::::::::::::::::::::::::::::::::");
+        }
+        for (int i = 0; i < errors.length; i++) {
+            Message.warn("\t:: " + errors[i].getArtifact());
+        }
+        if (errors.length > 0) {
+            Message.warn("\t::::::::::::::::::::::::::::::::::::::::::::::\n");
+        }
+    }
+
+    public void output(ConfigurationResolveReport report) {
+        StringBuffer line = new StringBuffer("\t");
+        // CheckStyle:MagicNumber| OFF
+        append(line, report.getConfiguration(), 18);
+        append(line, String.valueOf(report.getNodesNumber()), 7);
+        append(line, String.valueOf(report.getSearchedNodes().length), 7);
+        append(line, String.valueOf(report.getDownloadedNodes().length), 7);
+        append(line, String.valueOf(report.getEvictedNodes().length), 7);
+        line.append("|");
+        append(line, String.valueOf(report.getArtifactsNumber()), 7);
+        append(line, String.valueOf(report.getDownloadedArtifactsReports().length), 7);
+        // CheckStyle:MagicNumber| ON
+        line.append("|");
+
+        Message.rawinfo(line.toString());
+    }
+
+    private void append(StringBuffer line, Object o, int limit) {
+        String v = String.valueOf(o);
+        if (v.length() >= limit) {
+            v = v.substring(0, limit);
+        } else {
+            int missing = limit - v.length();
+            int half = missing / 2;
+            char[] c = new char[limit];
+            Arrays.fill(c, ' ');
+            System.arraycopy(v.toCharArray(), 0, c, missing - half, v.length());
+            v = new String(c);
+        }
+        line.append("|");
+        line.append(v);
+    }
+
+}
diff --git a/src/java/org/apache/ivy/plugins/report/ReportOutputter.java b/src/java/org/apache/ivy/plugins/report/ReportOutputter.java
new file mode 100644
index 0000000..f7d4ec3
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/report/ReportOutputter.java
@@ -0,0 +1,38 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.report;
+
+import java.io.IOException;
+
+import org.apache.ivy.core.cache.ResolutionCacheManager;
+import org.apache.ivy.core.report.ResolveReport;
+import org.apache.ivy.core.resolve.ResolveOptions;
+
+/**
+ *
+ */
+public interface ReportOutputter {
+    public static final String CONSOLE = "console";
+
+    public static final String XML = "xml";
+
+    public abstract void output(ResolveReport report, ResolutionCacheManager cacheMgr,
+            ResolveOptions options) throws IOException;
+
+    public abstract String getName();
+}
diff --git a/src/java/org/apache/ivy/plugins/report/XmlReportOutputter.java b/src/java/org/apache/ivy/plugins/report/XmlReportOutputter.java
new file mode 100644
index 0000000..0c5d9f7
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/report/XmlReportOutputter.java
@@ -0,0 +1,75 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.report;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.apache.ivy.core.cache.ResolutionCacheManager;
+import org.apache.ivy.core.report.ConfigurationResolveReport;
+import org.apache.ivy.core.report.ResolveReport;
+import org.apache.ivy.core.resolve.ResolveOptions;
+import org.apache.ivy.util.FileUtil;
+import org.apache.ivy.util.Message;
+
+/**
+ * A Report outputter implementation using {@link XmlReportWriter} to write xml reports to the
+ * resolution cache.
+ */
+public class XmlReportOutputter implements ReportOutputter {
+    private XmlReportWriter writer = new XmlReportWriter();
+
+    public String getName() {
+        return XML;
+    }
+
+    public void output(ResolveReport report, ResolutionCacheManager cacheMgr, ResolveOptions options)
+            throws IOException {
+        String[] confs = report.getConfigurations();
+        for (int i = 0; i < confs.length; i++) {
+            output(report.getConfigurationReport(confs[i]), report.getResolveId(), confs, cacheMgr);
+        }
+    }
+
+    public void output(ConfigurationResolveReport report, String resolveId, String[] confs,
+            ResolutionCacheManager cacheMgr) throws IOException {
+        File reportFile = cacheMgr.getConfigurationResolveReportInCache(resolveId,
+            report.getConfiguration());
+        File reportParentDir = reportFile.getParentFile();
+        reportParentDir.mkdirs();
+        OutputStream stream = new FileOutputStream(reportFile);
+        writer.output(report, confs, stream);
+        stream.close();
+
+        Message.verbose("\treport for " + report.getModuleDescriptor().getModuleRevisionId() + " "
+                + report.getConfiguration() + " produced in " + reportFile);
+
+        File reportXsl = new File(reportParentDir, "ivy-report.xsl");
+        File reportCss = new File(reportParentDir, "ivy-report.css");
+        if (!reportXsl.exists()) {
+            FileUtil.copy(XmlReportOutputter.class.getResourceAsStream("ivy-report.xsl"),
+                reportXsl, null);
+        }
+        if (!reportCss.exists()) {
+            FileUtil.copy(XmlReportOutputter.class.getResourceAsStream("ivy-report.css"),
+                reportCss, null);
+        }
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/report/XmlReportParser.java b/src/java/org/apache/ivy/plugins/report/XmlReportParser.java
new file mode 100644
index 0000000..633eb56
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/report/XmlReportParser.java
@@ -0,0 +1,333 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.report;
+
+import java.io.File;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.apache.ivy.core.cache.ArtifactOrigin;
+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.ivy.core.report.ArtifactDownloadReport;
+import org.apache.ivy.core.report.DownloadStatus;
+import org.apache.ivy.core.report.MetadataArtifactDownloadReport;
+import org.apache.ivy.util.DateUtil;
+import org.apache.ivy.util.extendable.ExtendableItemHelper;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+public class XmlReportParser {
+    private static class SaxXmlReportParser {
+        private final class XmlReportParserHandler extends DefaultHandler {
+            private String organisation;
+
+            private String module;
+
+            private String branch;
+
+            private String revision;
+
+            private int position;
+
+            private Date pubdate;
+
+            private boolean skip;
+
+            private ModuleRevisionId mrid;
+
+            private boolean isDefault;
+
+            // Use a TreeMap to order by
+            private SortedMap<Integer, List<ArtifactDownloadReport>> revisionsMap = new TreeMap<Integer, List<ArtifactDownloadReport>>();
+
+            private List<ArtifactDownloadReport> revisionArtifacts = null;
+
+            public void startElement(String uri, String localName, String qName,
+                    Attributes attributes) throws SAXException {
+                if ("module".equals(qName)) {
+                    organisation = attributes.getValue("organisation");
+                    module = attributes.getValue("name");
+                } else if ("revision".equals(qName)) {
+                    revisionArtifacts = new ArrayList<ArtifactDownloadReport>();
+                    branch = attributes.getValue("branch");
+                    revision = attributes.getValue("name");
+                    isDefault = Boolean.valueOf(attributes.getValue("default")).booleanValue();
+                    // retrieve position from file. If no position is found, it may be an old
+                    // report generated with a previous version,
+                    // in which case, we put it at the last position
+                    String pos = attributes.getValue("position");
+                    position = pos == null ? getMaxPos() + 1 : Integer.valueOf(pos).intValue();
+                    if (attributes.getValue("error") != null) {
+                        hasError = true;
+                        skip = true;
+                    } else if (attributes.getValue("evicted") != null) {
+                        skip = true;
+                    } else {
+                        revisionsMap.put(new Integer(position), revisionArtifacts);
+                        mrid = ModuleRevisionId.newInstance(organisation, module, branch, revision,
+                            ExtendableItemHelper.getExtraAttributes(attributes, "extra-"));
+                        mrids.add(mrid);
+                        if (isDefault) {
+                            defaultMrids.add(mrid);
+                        } else {
+                            Artifact metadataArtifact = DefaultArtifact.newIvyArtifact(mrid,
+                                pubdate);
+                            MetadataArtifactDownloadReport madr = new MetadataArtifactDownloadReport(
+                                    metadataArtifact);
+                            metadataReports.put(mrid, madr);
+                            realMrids.add(mrid);
+                        }
+                        try {
+                            String pubDateAttr = attributes.getValue("pubdate");
+                            if (pubDateAttr != null) {
+                                pubdate = DateUtil.parse(pubDateAttr);
+                            }
+                            skip = false;
+                        } catch (ParseException e) {
+                            throw new IllegalArgumentException("invalid publication date for "
+                                    + organisation + " " + module + " " + revision + ": "
+                                    + attributes.getValue("pubdate"));
+                        }
+                    }
+                } else if ("metadata-artifact".equals(qName)) {
+                    if (skip) {
+                        return;
+                    }
+                    MetadataArtifactDownloadReport madr = metadataReports.get(mrid);
+                    if (madr != null) {
+                        madr.setDownloadStatus(DownloadStatus.fromString(attributes
+                                .getValue("status")));
+                        madr.setDownloadDetails(attributes.getValue("details"));
+                        madr.setSize(Long.parseLong(attributes.getValue("size")));
+                        madr.setDownloadTimeMillis(Long.parseLong(attributes.getValue("time")));
+                        madr.setSearched(parseBoolean(attributes.getValue("searched")));
+                        if (attributes.getValue("location") != null) {
+                            madr.setLocalFile(new File(attributes.getValue("location")));
+                        }
+                        if (attributes.getValue("original-local-location") != null) {
+                            madr.setOriginalLocalFile(new File(attributes
+                                    .getValue("original-local-location")));
+                        }
+                        if (attributes.getValue("origin-location") != null) {
+                            if (ArtifactOrigin.isUnknown(attributes.getValue("origin-location"))) {
+                                madr.setArtifactOrigin(ArtifactOrigin.unkwnown(madr.getArtifact()));
+                            } else {
+                                madr.setArtifactOrigin(new ArtifactOrigin(madr.getArtifact(),
+                                        parseBoolean(attributes.getValue("origin-is-local")),
+                                        attributes.getValue("origin-location")));
+                            }
+                        }
+                    }
+                } else if ("artifact".equals(qName)) {
+                    if (skip) {
+                        return;
+                    }
+                    String status = attributes.getValue("status");
+                    String artifactName = attributes.getValue("name");
+                    String type = attributes.getValue("type");
+                    String ext = attributes.getValue("ext");
+                    Artifact artifact = new DefaultArtifact(mrid, pubdate, artifactName, type, ext,
+                            ExtendableItemHelper.getExtraAttributes(attributes, "extra-"));
+                    ArtifactDownloadReport aReport = new ArtifactDownloadReport(artifact);
+                    aReport.setDownloadStatus(DownloadStatus.fromString(status));
+                    aReport.setDownloadDetails(attributes.getValue("details"));
+                    aReport.setSize(Long.parseLong(attributes.getValue("size")));
+                    aReport.setDownloadTimeMillis(Long.parseLong(attributes.getValue("time")));
+                    if (attributes.getValue("location") != null) {
+                        aReport.setLocalFile(new File(attributes.getValue("location")));
+                    }
+                    if (attributes.getValue("unpackedFile") != null) {
+                        aReport.setUnpackedLocalFile(new File(attributes.getValue("unpackedFile")));
+                    }
+                    revisionArtifacts.add(aReport);
+                } else if ("origin-location".equals(qName)) {
+                    if (skip) {
+                        return;
+                    }
+                    ArtifactDownloadReport aReport = revisionArtifacts
+                            .get(revisionArtifacts.size() - 1);
+
+                    if (ArtifactOrigin.isUnknown(attributes.getValue("location"))) {
+                        aReport.setArtifactOrigin(ArtifactOrigin.unkwnown(aReport.getArtifact()));
+                    } else {
+                        aReport.setArtifactOrigin(new ArtifactOrigin(aReport.getArtifact(),
+                                parseBoolean(attributes.getValue("is-local")), attributes
+                                        .getValue("location")));
+                    }
+                } else if ("info".equals(qName)) {
+                    String organisation = attributes.getValue("organisation");
+                    String name = attributes.getValue("module");
+                    String branch = attributes.getValue("branch");
+                    String revision = attributes.getValue("revision");
+                    Map<String, String> extraAttributes = new HashMap<String, String>();
+                    for (int i = 0; i < attributes.getLength(); i++) {
+                        String attName = attributes.getQName(i);
+                        if (attName.startsWith("extra-")) {
+                            String extraAttrName = attName.substring("extra-".length());
+                            String extraAttrValue = attributes.getValue(i);
+                            extraAttributes.put(extraAttrName, extraAttrValue);
+                        }
+                    }
+                    mRevisionId = ModuleRevisionId.newInstance(organisation, name, branch,
+                        revision, extraAttributes);
+                }
+            }
+
+            public void endElement(String uri, String localName, String qname) throws SAXException {
+                if ("dependencies".equals(qname)) {
+                    // add the artifacts in the correct order
+                    for (List<ArtifactDownloadReport> artifactReports : revisionsMap.values()) {
+                        SaxXmlReportParser.this.artifactReports.addAll(artifactReports);
+                        for (ArtifactDownloadReport artifactReport : artifactReports) {
+                            if (artifactReport.getDownloadStatus() != DownloadStatus.FAILED) {
+                                artifacts.add(artifactReport.getArtifact());
+                            }
+                        }
+
+                    }
+                }
+            }
+
+            private int getMaxPos() {
+                return revisionsMap.isEmpty() ? -1
+                        : ((Integer) revisionsMap.keySet().toArray()[revisionsMap.size() - 1])
+                                .intValue();
+            }
+        }
+
+        private List<ModuleRevisionId> mrids = new ArrayList<ModuleRevisionId>();
+
+        private List<ModuleRevisionId> defaultMrids = new ArrayList<ModuleRevisionId>();
+
+        private List<ModuleRevisionId> realMrids = new ArrayList<ModuleRevisionId>();
+
+        private List<Artifact> artifacts = new ArrayList<Artifact>();
+
+        private List<ArtifactDownloadReport> artifactReports = new ArrayList<ArtifactDownloadReport>();
+
+        private Map<ModuleRevisionId, MetadataArtifactDownloadReport> metadataReports = new HashMap<ModuleRevisionId, MetadataArtifactDownloadReport>();
+
+        private ModuleRevisionId mRevisionId;
+
+        private File report;
+
+        private boolean hasError = false;
+
+        SaxXmlReportParser(File report) {
+            this.report = report;
+        }
+
+        public void parse() throws Exception {
+            SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser();
+            saxParser.parse(report, new XmlReportParserHandler());
+        }
+
+        private static boolean parseBoolean(String str) {
+            return (str != null) && str.equalsIgnoreCase("true");
+        }
+
+        public List<Artifact> getArtifacts() {
+            return artifacts;
+        }
+
+        public List<ArtifactDownloadReport> getArtifactReports() {
+            return artifactReports;
+        }
+
+        public List<ModuleRevisionId> getModuleRevisionIds() {
+            return mrids;
+        }
+
+        public List<ModuleRevisionId> getRealModuleRevisionIds() {
+            return realMrids;
+        }
+
+        public ModuleRevisionId getResolvedModule() {
+            return mRevisionId;
+        }
+
+        public MetadataArtifactDownloadReport getMetadataArtifactReport(ModuleRevisionId id) {
+            return metadataReports.get(id);
+        }
+    }
+
+    private SaxXmlReportParser parser = null;
+
+    public void parse(File report) throws ParseException {
+        if (!report.exists()) {
+            throw new IllegalStateException("Report file '" + report.getAbsolutePath()
+                    + "' does not exist.");
+        }
+
+        parser = new SaxXmlReportParser(report);
+        try {
+            parser.parse();
+        } catch (Exception e) {
+            ParseException pe = new ParseException("failed to parse report: " + report + ": "
+                    + e.getMessage(), 0);
+            pe.initCause(e);
+            throw pe;
+        }
+    }
+
+    public Artifact[] getArtifacts() {
+        return parser.getArtifacts().toArray(new Artifact[parser.getArtifacts().size()]);
+    }
+
+    public ArtifactDownloadReport[] getArtifactReports() {
+        return parser.getArtifactReports().toArray(
+            new ArtifactDownloadReport[parser.getArtifactReports().size()]);
+    }
+
+    public ModuleRevisionId[] getDependencyRevisionIds() {
+        return parser.getModuleRevisionIds().toArray(
+            new ModuleRevisionId[parser.getModuleRevisionIds().size()]);
+    }
+
+    public ModuleRevisionId[] getRealDependencyRevisionIds() {
+        return parser.getRealModuleRevisionIds().toArray(
+            new ModuleRevisionId[parser.getRealModuleRevisionIds().size()]);
+    }
+
+    public MetadataArtifactDownloadReport getMetadataArtifactReport(ModuleRevisionId id) {
+        return parser.getMetadataArtifactReport(id);
+    }
+
+    /**
+     * Returns the <tt>ModuleRevisionId</tt> of the resolved module.
+     */
+    public ModuleRevisionId getResolvedModule() {
+        return parser.getResolvedModule();
+    }
+
+    public boolean hasError() {
+        return parser.hasError;
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/report/XmlReportWriter.java b/src/java/org/apache/ivy/plugins/report/XmlReportWriter.java
new file mode 100644
index 0000000..080c658
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/report/XmlReportWriter.java
@@ -0,0 +1,319 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.report;
+
+import java.io.BufferedWriter;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.ivy.core.cache.ArtifactOrigin;
+import org.apache.ivy.core.module.descriptor.License;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.report.ArtifactDownloadReport;
+import org.apache.ivy.core.report.ConfigurationResolveReport;
+import org.apache.ivy.core.report.MetadataArtifactDownloadReport;
+import org.apache.ivy.core.resolve.IvyNode;
+import org.apache.ivy.core.resolve.IvyNodeCallers.Caller;
+import org.apache.ivy.core.resolve.IvyNodeEviction.EvictionData;
+import org.apache.ivy.util.DateUtil;
+import org.apache.ivy.util.StringUtils;
+import org.apache.ivy.util.XMLHelper;
+
+/**
+ * XmlReportWriter allows to write ResolveReport in an xml format.
+ */
+public class XmlReportWriter {
+
+    static final String REPORT_ENCODING = "UTF-8";
+
+    public void output(ConfigurationResolveReport report, OutputStream stream) {
+        output(report, new String[] {report.getConfiguration()}, stream);
+    }
+
+    public void output(ConfigurationResolveReport report, String[] confs, OutputStream stream) {
+        OutputStreamWriter encodedOutStream;
+        try {
+            encodedOutStream = new OutputStreamWriter(stream, REPORT_ENCODING);
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException(REPORT_ENCODING + " is not known on your jvm", e);
+        }
+        PrintWriter out = new PrintWriter(new BufferedWriter(encodedOutStream));
+        ModuleRevisionId mrid = report.getModuleDescriptor().getModuleRevisionId();
+        // out.println("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>");
+        out.println("<?xml version=\"1.0\" encoding=\"" + REPORT_ENCODING + "\"?>");
+        out.println("<?xml-stylesheet type=\"text/xsl\" href=\"ivy-report.xsl\"?>");
+        out.println("<ivy-report version=\"1.0\">");
+        out.println("\t<info");
+        out.println("\t\torganisation=\"" + XMLHelper.escape(mrid.getOrganisation()) + "\"");
+        out.println("\t\tmodule=\"" + XMLHelper.escape(mrid.getName()) + "\"");
+        out.println("\t\trevision=\"" + XMLHelper.escape(mrid.getRevision()) + "\"");
+        if (mrid.getBranch() != null) {
+            out.println("\t\tbranch=\"" + XMLHelper.escape(mrid.getBranch()) + "\"");
+        }
+        Map extraAttributes = mrid.getExtraAttributes();
+        for (Iterator it = extraAttributes.entrySet().iterator(); it.hasNext();) {
+            Map.Entry entry = (Entry) it.next();
+            out.println("\t\textra-" + entry.getKey() + "=\""
+                    + XMLHelper.escape(entry.getValue().toString()) + "\"");
+        }
+        out.println("\t\tconf=\"" + XMLHelper.escape(report.getConfiguration()) + "\"");
+        out.println("\t\tconfs=\"" + XMLHelper.escape(StringUtils.join(confs, ", ")) + "\"");
+        out.println("\t\tdate=\"" + DateUtil.format(report.getDate()) + "\"/>");
+
+        out.println("\t<dependencies>");
+
+        // create a list of ModuleRevisionIds indicating the position for each dependency
+        List dependencies = new ArrayList(report.getModuleRevisionIds());
+
+        for (Iterator iter = report.getModuleIds().iterator(); iter.hasNext();) {
+            ModuleId mid = (ModuleId) iter.next();
+            out.println("\t\t<module organisation=\"" + XMLHelper.escape(mid.getOrganisation())
+                    + "\"" + " name=\"" + XMLHelper.escape(mid.getName()) + "\">");
+            for (Iterator it2 = report.getNodes(mid).iterator(); it2.hasNext();) {
+                IvyNode dep = (IvyNode) it2.next();
+                ouputRevision(report, out, dependencies, dep);
+            }
+            out.println("\t\t</module>");
+        }
+        out.println("\t</dependencies>");
+        out.println("</ivy-report>");
+        out.flush();
+    }
+
+    private void ouputRevision(ConfigurationResolveReport report, PrintWriter out,
+            List dependencies, IvyNode dep) {
+        Map extraAttributes;
+        ModuleDescriptor md = null;
+        if (dep.getModuleRevision() != null) {
+            md = dep.getModuleRevision().getDescriptor();
+        }
+        StringBuffer details = new StringBuffer();
+        if (dep.isLoaded()) {
+            details.append(" status=\"");
+            details.append(XMLHelper.escape(dep.getDescriptor().getStatus()));
+            details.append("\" pubdate=\"");
+            details.append(DateUtil.format(new Date(dep.getPublication())));
+            details.append("\" resolver=\"");
+            details.append(XMLHelper.escape(dep.getModuleRevision().getResolver().getName()));
+            details.append("\" artresolver=\"");
+            details.append(XMLHelper
+                    .escape(dep.getModuleRevision().getArtifactResolver().getName()));
+            details.append("\"");
+        }
+        if (dep.isEvicted(report.getConfiguration())) {
+            EvictionData ed = dep.getEvictedData(report.getConfiguration());
+            if (ed.getConflictManager() != null) {
+                details.append(" evicted=\"")
+                        .append(XMLHelper.escape(ed.getConflictManager().toString())).append("\"");
+            } else {
+                details.append(" evicted=\"transitive\"");
+            }
+            details.append(" evicted-reason=\"")
+                    .append(XMLHelper.escape(ed.getDetail() == null ? "" : ed.getDetail()))
+                    .append("\"");
+        }
+        if (dep.hasProblem()) {
+            details.append(" error=\"").append(XMLHelper.escape(dep.getProblem().getMessage()))
+                    .append("\"");
+        }
+        if (md != null && md.getHomePage() != null) {
+            details.append(" homepage=\"").append(XMLHelper.escape(md.getHomePage())).append("\"");
+        }
+        extraAttributes = md != null ? md.getExtraAttributes() : dep.getResolvedId()
+                .getExtraAttributes();
+        for (Iterator iterator = extraAttributes.keySet().iterator(); iterator.hasNext();) {
+            String attName = (String) iterator.next();
+            details.append(" extra-").append(attName).append("=\"")
+                    .append(XMLHelper.escape(extraAttributes.get(attName).toString())).append("\"");
+        }
+        String defaultValue = dep.getDescriptor() != null ? " default=\""
+                + dep.getDescriptor().isDefault() + "\"" : "";
+        int position = dependencies.indexOf(dep.getResolvedId());
+        out.println("\t\t\t<revision name=\""
+                + XMLHelper.escape(dep.getResolvedId().getRevision())
+                + "\""
+                + (dep.getResolvedId().getBranch() == null ? "" : " branch=\""
+                        + XMLHelper.escape(dep.getResolvedId().getBranch()) + "\"") + details
+                + " downloaded=\"" + dep.isDownloaded() + "\"" + " searched=\"" + dep.isSearched()
+                + "\"" + defaultValue + " conf=\""
+                + toString(dep.getConfigurations(report.getConfiguration())) + "\""
+                + " position=\"" + position + "\">");
+        if (md != null) {
+            License[] licenses = md.getLicenses();
+            for (int i = 0; i < licenses.length; i++) {
+                String lurl;
+                if (licenses[i].getUrl() != null) {
+                    lurl = " url=\"" + XMLHelper.escape(licenses[i].getUrl()) + "\"";
+                } else {
+                    lurl = "";
+                }
+                out.println("\t\t\t\t<license name=\"" + XMLHelper.escape(licenses[i].getName())
+                        + "\"" + lurl + "/>");
+            }
+        }
+        outputMetadataArtifact(out, dep);
+        outputEvictionInformation(report, out, dep);
+        outputCallers(report, out, dep);
+        outputArtifacts(report, out, dep);
+        out.println("\t\t\t</revision>");
+    }
+
+    private void outputEvictionInformation(ConfigurationResolveReport report, PrintWriter out,
+            IvyNode dep) {
+        if (dep.isEvicted(report.getConfiguration())) {
+            EvictionData ed = dep.getEvictedData(report.getConfiguration());
+            Collection selected = ed.getSelected();
+            if (selected != null) {
+                for (Iterator it3 = selected.iterator(); it3.hasNext();) {
+                    IvyNode sel = (IvyNode) it3.next();
+                    out.println("\t\t\t\t<evicted-by rev=\""
+                            + XMLHelper.escape(sel.getResolvedId().getRevision()) + "\"/>");
+                }
+            }
+        }
+    }
+
+    private void outputMetadataArtifact(PrintWriter out, IvyNode dep) {
+        if (dep.getModuleRevision() != null) {
+            MetadataArtifactDownloadReport madr = dep.getModuleRevision().getReport();
+            out.print("\t\t\t\t<metadata-artifact");
+            out.print(" status=\"" + XMLHelper.escape(madr.getDownloadStatus().toString()) + "\"");
+            out.print(" details=\"" + XMLHelper.escape(madr.getDownloadDetails()) + "\"");
+            out.print(" size=\"" + madr.getSize() + "\"");
+            out.print(" time=\"" + madr.getDownloadTimeMillis() + "\"");
+            if (madr.getLocalFile() != null) {
+                out.print(" location=\"" + XMLHelper.escape(madr.getLocalFile().getAbsolutePath())
+                        + "\"");
+            }
+
+            out.print(" searched=\"" + madr.isSearched() + "\"");
+            if (madr.getOriginalLocalFile() != null) {
+                out.print(" original-local-location=\""
+                        + XMLHelper.escape(madr.getOriginalLocalFile().getAbsolutePath()) + "\"");
+            }
+
+            ArtifactOrigin origin = madr.getArtifactOrigin();
+            if (origin != null) {
+                out.print(" origin-is-local=\"" + String.valueOf(origin.isLocal()) + "\"");
+                out.print(" origin-location=\"" + XMLHelper.escape(origin.getLocation()) + "\"");
+            }
+            out.println("/>");
+
+        }
+    }
+
+    private void outputCallers(ConfigurationResolveReport report, PrintWriter out, IvyNode dep) {
+        Caller[] callers = dep.getCallers(report.getConfiguration());
+        for (int i = 0; i < callers.length; i++) {
+            StringBuffer callerDetails = new StringBuffer();
+            Map callerExtraAttributes = callers[i].getDependencyDescriptor().getExtraAttributes();
+            for (Iterator iterator = callerExtraAttributes.keySet().iterator(); iterator.hasNext();) {
+                String attName = (String) iterator.next();
+                callerDetails.append(" extra-").append(attName).append("=\"")
+                        .append(XMLHelper.escape(callerExtraAttributes.get(attName).toString()))
+                        .append("\"");
+            }
+
+            out.println("\t\t\t\t<caller organisation=\""
+                    + XMLHelper.escape(callers[i].getModuleRevisionId().getOrganisation())
+                    + "\""
+                    + " name=\""
+                    + XMLHelper.escape(callers[i].getModuleRevisionId().getName())
+                    + "\""
+                    + " conf=\""
+                    + XMLHelper.escape(toString(callers[i].getCallerConfigurations()))
+                    + "\""
+                    + " rev=\""
+                    + XMLHelper.escape(callers[i].getAskedDependencyId(dep.getData()).getRevision())
+                    + "\""
+                    + " rev-constraint-default=\""
+                    + XMLHelper.escape(callers[i].getDependencyDescriptor()
+                            .getDependencyRevisionId().getRevision())
+                    + "\""
+                    + " rev-constraint-dynamic=\""
+                    + XMLHelper.escape(callers[i].getDependencyDescriptor()
+                            .getDynamicConstraintDependencyRevisionId().getRevision()) + "\""
+                    + " callerrev=\""
+                    + XMLHelper.escape(callers[i].getModuleRevisionId().getRevision()) + "\""
+                    + callerDetails + "/>");
+        }
+    }
+
+    private void outputArtifacts(ConfigurationResolveReport report, PrintWriter out, IvyNode dep) {
+        Map extraAttributes;
+        ArtifactDownloadReport[] adr = report.getDownloadReports(dep.getResolvedId());
+        out.println("\t\t\t\t<artifacts>");
+        for (int i = 0; i < adr.length; i++) {
+            out.print("\t\t\t\t\t<artifact name=\"" + XMLHelper.escape(adr[i].getName())
+                    + "\" type=\"" + XMLHelper.escape(adr[i].getType()) + "\" ext=\""
+                    + XMLHelper.escape(adr[i].getExt()) + "\"");
+            extraAttributes = adr[i].getArtifact().getExtraAttributes();
+            for (Iterator iterator = extraAttributes.keySet().iterator(); iterator.hasNext();) {
+                String attName = (String) iterator.next();
+                out.print(" extra-" + attName + "=\""
+                        + XMLHelper.escape(extraAttributes.get(attName).toString()) + "\"");
+            }
+            out.print(" status=\"" + XMLHelper.escape(adr[i].getDownloadStatus().toString()) + "\"");
+            out.print(" details=\"" + XMLHelper.escape(adr[i].getDownloadDetails()) + "\"");
+            out.print(" size=\"" + adr[i].getSize() + "\"");
+            out.print(" time=\"" + adr[i].getDownloadTimeMillis() + "\"");
+            if (adr[i].getLocalFile() != null) {
+                out.print(" location=\""
+                        + XMLHelper.escape(adr[i].getLocalFile().getAbsolutePath()) + "\"");
+            }
+            if (adr[i].getUnpackedLocalFile() != null) {
+                out.print(" unpackedFile=\""
+                        + XMLHelper.escape(adr[i].getUnpackedLocalFile().getAbsolutePath()) + "\"");
+            }
+
+            ArtifactOrigin origin = adr[i].getArtifactOrigin();
+            if (origin != null) {
+                out.println(">");
+                out.println("\t\t\t\t\t\t<origin-location is-local=\""
+                        + String.valueOf(origin.isLocal()) + "\"" + " location=\""
+                        + XMLHelper.escape(origin.getLocation()) + "\"/>");
+                out.println("\t\t\t\t\t</artifact>");
+            } else {
+                out.println("/>");
+            }
+        }
+        out.println("\t\t\t\t</artifacts>");
+    }
+
+    private String toString(String[] strs) {
+        StringBuffer buf = new StringBuffer();
+        for (int i = 0; i < strs.length; i++) {
+            buf.append(strs[i]);
+            if (i + 1 < strs.length) {
+                buf.append(", ");
+            }
+        }
+        return XMLHelper.escape(buf.toString());
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/report/ivy-report-dot-all.xsl b/src/java/org/apache/ivy/plugins/report/ivy-report-dot-all.xsl
new file mode 100644
index 0000000..ef79a32
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/report/ivy-report-dot-all.xsl
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT 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 version="1.0"
+  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+<xsl:output method="text"/>
+<xsl:template match="/ivy-report">
+  <xsl:text>/* * directed graph dot input file. * * generated by ivy report */
+            digraph G {</xsl:text>
+  <xsl:for-each select="dependencies/module">
+   	<xsl:text>"</xsl:text><xsl:value-of select="@name"/><xsl:text>"</xsl:text>
+  </xsl:for-each>
+  <xsl:for-each select="dependencies/module/revision[not(@evicted)]/caller[@organisation!='caller']">
+   	<xsl:text>"</xsl:text><xsl:value-of select="@name"/><xsl:text>" -> "</xsl:text><xsl:value-of select="../../@name"/><xsl:text>";</xsl:text>
+  </xsl:for-each>
+  <xsl:text>}</xsl:text>
+</xsl:template>
+</xsl:stylesheet>
diff --git a/src/java/org/apache/ivy/plugins/report/ivy-report-dot.xsl b/src/java/org/apache/ivy/plugins/report/ivy-report-dot.xsl
new file mode 100644
index 0000000..58e7932
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/report/ivy-report-dot.xsl
@@ -0,0 +1,49 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT 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 version="1.0"
+  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+<xsl:output method="text"/>
+<xsl:template match="/ivy-report">
+/* * directed graph dot input file. * * generated by ivy report */
+            digraph G {
+   	 "<xsl:value-of select="info/@organisation"/>-<xsl:value-of select="info/@module"/>" [label="<xsl:value-of select="info/@module"/>"];
+     <xsl:for-each select="dependencies/module">
+   	    "<xsl:value-of select="@organisation"/>-<xsl:value-of select="@name"/>" [label="<xsl:value-of select="@name"/>
+   	    	<xsl:for-each select="revision">
+                  <xsl:text>\n</xsl:text>
+                  <xsl:value-of select="@name"/><xsl:if test="@error"> (error)</xsl:if><xsl:if test="@evicted"> (evicted)</xsl:if>
+            </xsl:for-each>
+   	     <xsl:text>"];
+</xsl:text>
+      </xsl:for-each>
+      <xsl:for-each select="dependencies/module/revision[not(@evicted)]/caller">
+   	      <xsl:text>"</xsl:text>
+   	      		<xsl:value-of select="@organisation"/>-<xsl:value-of select="@name"/>
+   	      		<xsl:text>" -> "</xsl:text>
+   	      		<xsl:value-of select="../../@organisation"/>-<xsl:value-of select="../../@name"/>
+   	      		<xsl:text>" [label="</xsl:text>
+   	      		  <xsl:value-of select="@rev"/>
+   	      		<xsl:text>"]</xsl:text>
+   	      <xsl:text>;
+</xsl:text>
+      </xsl:for-each>
+  <xsl:text>}</xsl:text>
+</xsl:template>
+
+</xsl:stylesheet>
diff --git a/src/java/org/apache/ivy/plugins/report/ivy-report-graph-all.xsl b/src/java/org/apache/ivy/plugins/report/ivy-report-graph-all.xsl
new file mode 100644
index 0000000..6cb6fec
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/report/ivy-report-graph-all.xsl
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT 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 version="1.0"
+  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+
+<xsl:template match="/ivy-report">
+  <graphml xmlns="http://graphml.graphdrawing.org/xmlns/graphml"  
+           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+           xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns/graphml http://www.yworks.com/xml/schema/graphml/1.0/ygraphml.xsd" 
+           xmlns:y="http://www.yworks.com/xml/graphml">
+    <key id="d0" for="node" yfiles.type="nodegraphics"/>
+    <key id="d1" for="edge" yfiles.type="edgegraphics"/>
+    <graph id="G" edgedefault="directed">
+      <xsl:for-each select="dependencies/module">
+         <xsl:element name="node">
+           <xsl:attribute name="id"><xsl:value-of select="@organisation"/>-<xsl:value-of select="@name"/></xsl:attribute>
+            <data key="d0" >
+              <y:ShapeNode>
+                <y:Fill color="#FFFFCC"  transparent="false"/>
+                <y:BorderStyle type="line" width="1.0" color="#000000" />
+                <y:NodeLabel visible="true" alignment="center" fontFamily="Dialog" fontSize="12" fontStyle="plain" textColor="#000000" modelName="internal" modelPosition="c" autoSizePolicy="center">
+                  <xsl:value-of select="@name"/>
+                </y:NodeLabel>
+                <y:Shape type="roundrectangle"/>
+              </y:ShapeNode>
+            </data>
+         </xsl:element>
+      </xsl:for-each>
+      <xsl:for-each select="dependencies/module/revision[not(@evicted)]/caller[@organisation!='caller']">
+         <xsl:element name="edge">
+           <xsl:attribute name="id"><xsl:value-of select="@organisation"/>-<xsl:value-of select="@name"/>-<xsl:value-of select="../../@organisation"/>-<xsl:value-of select="../../@name"/></xsl:attribute>
+           <xsl:attribute name="source"><xsl:value-of select="@organisation"/>-<xsl:value-of select="@name"/></xsl:attribute>
+           <xsl:attribute name="target"><xsl:value-of select="../../@organisation"/>-<xsl:value-of select="../../@name"/></xsl:attribute>
+            <data key="d1">
+              <y:PolyLineEdge>
+                <y:LineStyle type="line" width="1.0" color="#000000" />
+                <y:Arrows source="none" target="standard"/>
+                <y:BendStyle smoothed="false"/>
+              </y:PolyLineEdge>
+            </data>
+         </xsl:element>
+      </xsl:for-each>
+    </graph>
+  </graphml>         
+</xsl:template>
+
+</xsl:stylesheet>
diff --git a/src/java/org/apache/ivy/plugins/report/ivy-report-graph.xsl b/src/java/org/apache/ivy/plugins/report/ivy-report-graph.xsl
new file mode 100644
index 0000000..88d6bac
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/report/ivy-report-graph.xsl
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT 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 version="1.0"
+  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+
+<xsl:template match="/ivy-report">
+  <graphml xmlns="http://graphml.graphdrawing.org/xmlns/graphml"  
+           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+           xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns/graphml http://www.yworks.com/xml/schema/graphml/1.0/ygraphml.xsd" 
+           xmlns:y="http://www.yworks.com/xml/graphml">
+    <key id="d0" for="node" yfiles.type="nodegraphics"/>
+    <key id="d1" for="edge" yfiles.type="edgegraphics"/>
+    <graph id="G" edgedefault="directed">
+       <xsl:element name="node">
+         <xsl:attribute name="id"><xsl:value-of select="info/@organisation"/>-<xsl:value-of select="info/@module"/></xsl:attribute>
+          <data key="d0" >
+            <y:ShapeNode>
+              <y:Fill color="#CCCCFF"  transparent="false"/>
+              <y:BorderStyle type="line" width="1.0" color="#000000" />
+              <y:NodeLabel visible="true" alignment="center" fontFamily="Dialog" fontSize="12" fontStyle="plain" textColor="#000000" modelName="internal" modelPosition="c" autoSizePolicy="center">
+                <xsl:value-of select="info/@module"/>
+              </y:NodeLabel>
+              <y:Shape type="roundrectangle"/>
+            </y:ShapeNode>
+          </data>
+       </xsl:element>
+      <xsl:for-each select="dependencies/module">
+         <xsl:element name="node">
+           <xsl:attribute name="id"><xsl:value-of select="@organisation"/>-<xsl:value-of select="@name"/></xsl:attribute>
+            <data key="d0" >
+              <y:ShapeNode>
+                <y:Fill color="#FFFFCC"  transparent="false"/>
+                <y:BorderStyle type="line" width="1.0" color="#000000" />
+                <y:NodeLabel visible="true" alignment="center" fontFamily="Dialog" fontSize="12" fontStyle="plain" textColor="#000000" modelName="internal" modelPosition="c" autoSizePolicy="center">
+                  <xsl:value-of select="@name"/>
+                  <xsl:for-each select="revision">
+                  <xsl:text>
+</xsl:text>
+                    <xsl:value-of select="@name"/><xsl:if test="@error"> (error)</xsl:if><xsl:if test="@evicted"> (evicted)</xsl:if>
+                  </xsl:for-each>
+                </y:NodeLabel>
+                <y:Shape type="roundrectangle"/>
+              </y:ShapeNode>
+            </data>
+         </xsl:element>
+      </xsl:for-each>
+      <xsl:for-each select="dependencies/module/revision[not(@evicted)]/caller">
+         <xsl:element name="edge">
+           <xsl:attribute name="id"><xsl:value-of select="@organisation"/>-<xsl:value-of select="@name"/>-<xsl:value-of select="../../@organisation"/>-<xsl:value-of select="../../@name"/></xsl:attribute>
+           <xsl:attribute name="source"><xsl:value-of select="@organisation"/>-<xsl:value-of select="@name"/></xsl:attribute>
+           <xsl:attribute name="target"><xsl:value-of select="../../@organisation"/>-<xsl:value-of select="../../@name"/></xsl:attribute>
+            <data key="d1">
+              <y:PolyLineEdge>
+                <y:LineStyle type="line" width="1.0" color="#000000" />
+                <y:Arrows source="none" target="standard"/>
+                <y:EdgeLabel visible="true" alignment="center" fontFamily="Dialog" fontSize="12" fontStyle="plain" textColor="#000000" modelName="free" modelPosition="anywhere" preferredPlacement="target" distance="2.0" ratio="0.5">
+                  <xsl:value-of select="@rev"/>
+                </y:EdgeLabel>
+                <y:BendStyle smoothed="false"/>
+              </y:PolyLineEdge>
+            </data>
+         </xsl:element>
+      </xsl:for-each>
+    </graph>
+  </graphml>         
+</xsl:template>
+
+</xsl:stylesheet>
diff --git a/src/java/org/apache/ivy/plugins/report/ivy-report.css b/src/java/org/apache/ivy/plugins/report/ivy-report.css
new file mode 100644
index 0000000..135bc26
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/report/ivy-report.css
@@ -0,0 +1,279 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT 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 {
+	font-family:"Trebuchet MS",Verdana,Geneva,Arial,Helvetica,sans-serif;
+	font-size:small; 
+}
+
+div#logo {
+    float: right; 
+    padding-left: 10px; 
+    padding-bottom: 10px; 
+    background: white;
+    text-align: center;
+}
+
+#logo img {
+	border: 0;
+}
+
+div#date {
+	font-style:     italic;
+	padding-left:	60px;
+    padding-bottom: 40px; 
+}
+
+
+h1 {
+    margin-bottom:2px;
+    
+	border-color:#7A9437;
+	border-style:solid;
+	border-width:0 0 3px 0;
+}
+
+span#module {
+	color:#7A9437;
+	text-decoration:none;
+}
+
+span#organisation {
+	color:black;
+	text-decoration:none;
+}
+
+#confmenu {
+	color: #000;
+	border-bottom: 2px solid black;
+	margin: 12px 0px 0px 0px;
+	padding: 0px;
+	z-index: 1;
+	padding-left: 10px 
+}
+
+#confmenu li {
+	display: inline;
+	overflow: hidden;
+	list-style-type: none; 
+}
+
+#confmenu a, a.active {
+	color: #DEDECF;
+	background: #898B5E;
+	font: bold 1em "Trebuchet MS", Arial, sans-serif;
+	border: 2px solid black;
+	padding: 2px 5px 0px 5px;
+	text-decoration: none; 
+}
+
+/*
+background: #ABAD85 #CED4BD
+background: #DEE4CD
+ */
+
+#confmenu a.active {
+	color: #7A9437;
+	background: #DEE4CD;
+	border-bottom: 3px solid #DEE4CD;
+}
+
+#confmenu a:hover {
+	color: #fff;
+	background: #ADC09F; 
+}
+
+#confmenu a:visited {
+	color: #DEDECF; 
+}
+
+#confmenu a.active:visited {
+	color: #7A9437; 
+}
+
+#confmenu a.active:hover {
+	background: #DEE4CD;
+	color: #DEDECF; 
+}
+
+#content {
+	background: #DEE4CD;
+	padding: 20px;
+	border: 2px solid black;
+	border-top: none;
+	z-index: 2;	
+}
+
+#content a {
+	text-decoration: none;
+	color: #E8E9BE; 
+}
+
+#content a:hover { 
+  background: #898B5E; 
+}
+
+
+h2 {
+    margin-bottom:2px;
+    font-size:medium;
+    
+    border-color:#7A9437;
+    border-style:solid;
+    border-width:0 0 2px 0;
+}
+
+h3 {
+    margin-top:30px;
+    margin-bottom:2px;
+    padding: 5 5 5 0;
+    font-size: 24px;
+    border-style:solid;
+    border-width:0 0 2px 0;
+}
+
+h4 {
+    margin-bottom:2px;
+    margin-top:2px;
+    font-size:medium;
+    
+    border-color:#7A9437;
+    border-style:dashed;
+    border-width:0 0 1px 0;
+}
+
+h5 {
+    margin-bottom:2px;
+    margin-top:2px;
+    margin-left:20px;
+    font-size:medium;
+}
+
+span.resolved {
+	padding-left: 15px;
+	font-weight: 500;
+	font-size: small;
+}
+
+
+#content table  {
+	border-collapse:collapse;
+	width:90%;
+	margin:auto;
+	margin-top: 5px;
+}
+#content thead {
+	background-color:#CED4BD;
+	border:1px solid #7A9437;
+}
+#content tbody {
+	border-collapse:collapse;
+	background-color:#FFFFFF;
+	border:1px solid #7A9437;
+}
+
+#content th {
+	font-family:monospace;
+	border:1px solid #7A9437;
+	padding:5px;
+}
+
+#content td {
+	border:1px dotted #7A9437;
+	padding:0 3 0 3;
+}
+
+#content table a {
+	color:#7A9437;
+	text-decoration:none;
+}
+
+#content table a:hover {
+	background-color:#CED4BD;
+	color:#7A9437;
+}
+
+
+
+table.deps  {
+	border-collapse:collapse;
+	width:90%;
+	margin:auto;
+	margin-top: 5px;
+}
+
+table.deps thead {
+	background-color:#CED4BD;
+	border:1px solid #7A9437;
+}
+table.deps tbody {
+	border-collapse:collapse;
+	background-color:#FFFFFF;
+	border:1px solid #7A9437;
+}
+
+table.deps th {
+	font-family:monospace;
+	border:1px solid #7A9437;
+	padding:2;
+}
+
+table.deps td {
+	border:1px dotted #7A9437;
+	padding:0 3 0 3;
+}
+
+
+
+
+
+table.header  {
+	border:0;
+	width:90%;
+	margin:auto;
+	margin-top: 5px;
+}
+
+table.header thead {
+	border:0;
+}
+table.header tbody {
+	border:0;
+}
+table.header tr {
+	padding:0px;
+	border:0;
+}
+table.header td {
+	padding:0 3 0 3;
+	border:0;
+}
+
+td.title {
+    width:150px;
+    margin-right:15px;
+
+	font-size:small;
+	font-weight:700;
+}
+
+td.title:first-letter {
+	color:#7A9437;
+	background-color:transparent;
+}
+
diff --git a/src/java/org/apache/ivy/plugins/report/ivy-report.xsl b/src/java/org/apache/ivy/plugins/report/ivy-report.xsl
new file mode 100644
index 0000000..af5f953
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/report/ivy-report.xsl
@@ -0,0 +1,514 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT 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 version="1.0"
+  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+
+<xsl:param name="confs"    select="/ivy-report/info/@confs"/>
+<xsl:param name="extension"    select="'xml'"/>
+
+<xsl:variable name="myorg"    select="/ivy-report/info/@organisation"/>
+<xsl:variable name="mymod"    select="/ivy-report/info/@module"/>
+<xsl:variable name="myconf"   select="/ivy-report/info/@conf"/>
+
+<xsl:variable name="modules"    select="/ivy-report/dependencies/module"/>
+<xsl:variable name="conflicts"    select="$modules[count(revision) > 1]"/>
+
+<xsl:variable name="revisions"  select="$modules/revision"/>
+<xsl:variable name="evicteds"   select="$revisions[@evicted]"/>
+<xsl:variable name="downloadeds"   select="$revisions[@downloaded='true']"/>
+<xsl:variable name="searcheds"   select="$revisions[@searched='true']"/>
+<xsl:variable name="errors"   select="$revisions[@error]"/>
+
+<xsl:variable name="artifacts"   select="$revisions/artifacts/artifact"/>
+<xsl:variable name="cacheartifacts" select="$artifacts[@status='no']"/>
+<xsl:variable name="dlartifacts" select="$artifacts[@status='successful']"/>
+<xsl:variable name="faileds" select="$artifacts[@status='failed']"/>
+<xsl:variable name="artifactsok" select="$artifacts[@status!='failed']"/>
+
+<xsl:template name="calling">
+    <xsl:param name="org" />
+    <xsl:param name="mod" />
+    <xsl:param name="rev" />
+    <xsl:if test="count($modules/revision/caller[(@organisation=$org and @name=$mod) and @callerrev=$rev]) = 0">
+    <table><tr><td>
+    No dependency
+    </td></tr></table>
+    </xsl:if>
+    <xsl:if test="count($modules/revision/caller[(@organisation=$org and @name=$mod) and @callerrev=$rev]) > 0">
+    <table class="deps">
+      <thead>
+      <tr>
+        <th>Module</th>
+        <th>Revision</th>
+        <th>Status</th>
+        <th>Resolver</th>
+        <th>Default</th>
+        <th>Licenses</th>
+        <th>Size</th>
+        <th></th>
+      </tr>
+      </thead>
+      <tbody>
+        <xsl:for-each select="$modules/revision/caller[(@organisation=$org and @name=$mod) and @callerrev=$rev]">
+          <xsl:call-template name="called">
+            <xsl:with-param name="callstack"     select="concat($org, string('/'), $mod)"/>
+            <xsl:with-param name="indent"        select="string('')"/>
+            <xsl:with-param name="revision"      select=".."/>
+          </xsl:call-template>
+        </xsl:for-each>   
+      </tbody>
+    </table>
+    </xsl:if>
+</xsl:template>
+
+<xsl:template name="called">
+    <xsl:param name="callstack"/>
+    <xsl:param name="indent"/>
+    <xsl:param name="revision"/>
+
+    <xsl:param name="organisation" select="$revision/../@organisation"/>
+    <xsl:param name="module" select="$revision/../@name"/>
+    <xsl:param name="rev" select="$revision/@name"/>
+    <xsl:param name="resolver" select="$revision/@resolver"/>
+    <xsl:param name="isdefault" select="$revision/@default"/>
+    <xsl:param name="status" select="$revision/@status"/>
+    <tr>
+    <td>
+       <xsl:element name="a">
+         <xsl:attribute name="href">#<xsl:value-of select="$organisation"/>-<xsl:value-of select="$module"/></xsl:attribute>
+         <xsl:value-of select="concat($indent, ' ')"/>
+         <xsl:value-of select="$module"/>
+         by
+         <xsl:value-of select="$organisation"/>
+       </xsl:element>
+    </td>
+    <td>
+       <xsl:element name="a">
+         <xsl:attribute name="href">#<xsl:value-of select="$organisation"/>-<xsl:value-of select="$module"/>-<xsl:value-of select="$rev"/></xsl:attribute>
+         <xsl:value-of select="$rev"/>
+       </xsl:element>
+    </td>
+    <td align="center">
+         <xsl:value-of select="$status"/>
+    </td>
+    <td align="center">
+         <xsl:value-of select="$resolver"/>
+    </td>
+    <td align="center">
+         <xsl:value-of select="$isdefault"/>
+    </td>
+    <td align="center">
+      <xsl:call-template name="licenses">
+        <xsl:with-param name="revision"      select="$revision"/>
+      </xsl:call-template>
+    </td>
+    <td align="center">
+      <xsl:value-of select="round(sum($revision/artifacts/artifact/@size) div 1024)"/> kB
+    </td>
+    <td align="center">
+          <xsl:call-template name="icons">
+            <xsl:with-param name="revision"      select="$revision"/>
+          </xsl:call-template>
+    </td>
+    </tr>
+    <xsl:if test="not($revision/@evicted)">
+    <xsl:if test="not(contains($callstack, concat($organisation, string('/'), $module)))">
+    <xsl:for-each select="$modules/revision/caller[(@organisation=$organisation and @name=$module) and @callerrev=$rev]">
+          <xsl:call-template name="called">
+            <xsl:with-param name="callstack"     select="concat($callstack, string('#'), $organisation, string('/'), $module)"/>
+            <xsl:with-param name="indent"        select="concat($indent, string('---'))"/>
+            <xsl:with-param name="revision"      select=".."/>
+          </xsl:call-template>
+    </xsl:for-each>   
+    </xsl:if>
+    </xsl:if>
+</xsl:template>
+
+<xsl:template name="licenses">
+      <xsl:param name="revision"/>
+      <xsl:for-each select="$revision/license">
+      	<span style="padding-right:3px;">
+      	<xsl:if test="@url">
+  	        <xsl:element name="a">
+  	            <xsl:attribute name="href"><xsl:value-of select="@url"/></xsl:attribute>
+  		    	<xsl:value-of select="@name"/>
+  	        </xsl:element>
+      	</xsl:if>
+      	<xsl:if test="not(@url)">
+  		    	<xsl:value-of select="@name"/>
+      	</xsl:if>
+      	</span>
+      </xsl:for-each>
+</xsl:template>
+
+<xsl:template name="icons">
+    <xsl:param name="revision"/>
+    <xsl:if test="$revision/@searched = 'true'">
+         <img src="http://ant.apache.org/ivy/images/searched.gif" alt="searched" title="required a search in repository"/>
+    </xsl:if>
+    <xsl:if test="$revision/@downloaded = 'true'">
+         <img src="http://ant.apache.org/ivy/images/downloaded.gif" alt="downloaded" title="downloaded from repository"/>
+    </xsl:if>
+    <xsl:if test="$revision/@evicted">
+        <xsl:element name="img">
+            <xsl:attribute name="src">http://ant.apache.org/ivy/images/evicted.gif</xsl:attribute>
+            <xsl:attribute name="alt">evicted</xsl:attribute>
+            <xsl:attribute name="title">evicted by <xsl:for-each select="$revision/evicted-by"><xsl:value-of select="@rev"/> </xsl:for-each></xsl:attribute>
+        </xsl:element>
+    </xsl:if>
+    <xsl:if test="$revision/@error">
+        <xsl:element name="img">
+            <xsl:attribute name="src">http://ant.apache.org/ivy/images/error.gif</xsl:attribute>
+            <xsl:attribute name="alt">error</xsl:attribute>
+            <xsl:attribute name="title">error: <xsl:value-of select="$revision/@error"/></xsl:attribute>
+        </xsl:element>
+    </xsl:if>
+</xsl:template>
+
+<xsl:template name="error">
+    <xsl:param name="organisation"/>
+    <xsl:param name="module"/>
+    <xsl:param name="revision"/>
+    <xsl:param name="error"/>
+    <tr>
+    <td>
+       <xsl:element name="a">
+         <xsl:attribute name="href">#<xsl:value-of select="$organisation"/>-<xsl:value-of select="$module"/></xsl:attribute>
+         <xsl:value-of select="$module"/>
+         by
+         <xsl:value-of select="$organisation"/>
+       </xsl:element>
+    </td>
+    <td>
+       <xsl:element name="a">
+         <xsl:attribute name="href">#<xsl:value-of select="$organisation"/>-<xsl:value-of select="$module"/>-<xsl:value-of select="$revision"/></xsl:attribute>
+         <xsl:value-of select="$revision"/>
+       </xsl:element>
+    </td>
+    <td>
+         <xsl:value-of select="$error"/>
+    </td>
+    </tr>
+</xsl:template>
+
+<xsl:template name="confs">
+    <xsl:param name="configurations"/>
+    
+    <xsl:if test="contains($configurations, ',')">
+      <xsl:call-template name="conf">
+        <xsl:with-param name="conf" select="normalize-space(substring-before($configurations,','))"/>
+      </xsl:call-template>
+      <xsl:call-template name="confs">
+        <xsl:with-param name="configurations" select="substring-after($configurations,',')"/>
+      </xsl:call-template>
+    </xsl:if>
+    <xsl:if test="not(contains($configurations, ','))">
+      <xsl:call-template name="conf">
+        <xsl:with-param name="conf" select="normalize-space($configurations)"/>
+      </xsl:call-template>
+    </xsl:if>
+</xsl:template>
+
+<xsl:template name="conf">
+    <xsl:param name="conf"/>
+    
+     <li>
+       <xsl:element name="a">
+         <xsl:if test="$conf = $myconf">
+           <xsl:attribute name="class">active</xsl:attribute>
+         </xsl:if>
+         <xsl:attribute name="href"><xsl:value-of select="$myorg"/>-<xsl:value-of select="$mymod"/>-<xsl:value-of select="$conf"/>.<xsl:value-of select="$extension"/></xsl:attribute>
+         <xsl:value-of select="$conf"/>
+       </xsl:element>
+     </li>
+</xsl:template>
+
+<xsl:template name="date">
+    <xsl:param name="date"/>
+    
+    <xsl:value-of select="substring($date,1,4)"/>-<xsl:value-of select="substring($date,5,2)"/>-<xsl:value-of select="substring($date,7,2)"/>
+    <xsl:value-of select="' '"/>
+    <xsl:value-of select="substring($date,9,2)"/>:<xsl:value-of select="substring($date,11,2)"/>:<xsl:value-of select="substring($date,13)"/>
+</xsl:template>
+
+
+<xsl:template match="/ivy-report">
+
+  <html>
+  <head>
+    <title>Ivy report :: <xsl:value-of select="info/@module"/> by <xsl:value-of select="info/@organisation"/> :: <xsl:value-of select="info/@conf"/></title>
+    <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1" />
+    <meta http-equiv="content-language" content="en" />
+    <meta name="robots" content="index,follow" />
+    <link rel="stylesheet" type="text/css" href="ivy-report.css" /> 
+  </head>
+  <body>
+    <div id="logo"><a href="http://ant.apache.org/ivy/"><img src="http://ant.apache.org/ivy/images/logo.png"/></a></div>
+    <h1>
+      <xsl:element name="a">
+        <xsl:attribute name="name"><xsl:value-of select="info/@organisation"/>-<xsl:value-of select="info/@module"/></xsl:attribute>
+      </xsl:element>
+        <span id="module">
+    	        <xsl:value-of select="concat(info/@module, ' ', info/@revision)"/>
+        </span> 
+        by 
+        <span id="organisation">
+    	        <xsl:value-of select="info/@organisation"/>
+        </span>
+    </h1>
+    <div id="date">
+    resolved on 
+      <xsl:call-template name="date">
+        <xsl:with-param name="date" select="info/@date"/>
+      </xsl:call-template>
+    </div>
+    <ul id="confmenu">
+      <xsl:call-template name="confs">
+        <xsl:with-param name="configurations" select="$confs"/>
+      </xsl:call-template>
+    </ul>
+
+    <div id="content">
+    <h2>Dependencies Stats</h2>
+        <table class="header">
+          <tr><td class="title">Modules</td><td class="value"><xsl:value-of select="count($modules)"/></td></tr>
+          <tr><td class="title">Revisions</td><td class="value"><xsl:value-of select="count($revisions)"/>  
+            (<xsl:value-of select="count($searcheds)"/> searched <img src="http://ant.apache.org/ivy/images/searched.gif" alt="searched" title="module revisions which required a search with a dependency resolver to be resolved"/>,
+            <xsl:value-of select="count($downloadeds)"/> downloaded <img src="http://ant.apache.org/ivy/images/downloaded.gif" alt="downloaded" title="module revisions for which ivy file was downloaded by dependency resolver"/>,
+            <xsl:value-of select="count($evicteds)"/> evicted <img src="http://ant.apache.org/ivy/images/evicted.gif" alt="evicted" title="module revisions which were evicted by others"/>,
+            <xsl:value-of select="count($errors)"/> errors <img src="http://ant.apache.org/ivy/images/error.gif" alt="error" title="module revisions on which error occurred"/>)</td></tr>
+          <tr><td class="title">Artifacts</td><td class="value"><xsl:value-of select="count($artifacts)"/> 
+            (<xsl:value-of select="count($dlartifacts)"/> downloaded,
+            <xsl:value-of select="count($faileds)"/> failed)</td></tr>
+          <tr><td class="title">Artifacts size</td><td class="value"><xsl:value-of select="round(sum($artifacts/@size) div 1024)"/> kB
+            (<xsl:value-of select="round(sum($dlartifacts/@size) div 1024)"/> kB downloaded,
+            <xsl:value-of select="round(sum($cacheartifacts/@size) div 1024)"/> kB in cache)</td></tr>
+        </table>
+    
+    <xsl:if test="count($errors) > 0">
+    <h2>Errors</h2>
+    <table class="errors">
+      <thead>
+      <tr>
+        <th>Module</th>
+        <th>Revision</th>
+        <th>Error</th>
+      </tr>
+      </thead>
+      <tbody>
+      <xsl:for-each select="$errors">
+          <xsl:call-template name="error">
+            <xsl:with-param name="organisation"  select="../@organisation"/>
+            <xsl:with-param name="module"        select="../@name"/>
+            <xsl:with-param name="revision"      select="@name"/>
+            <xsl:with-param name="error"        select="@error"/>
+          </xsl:call-template>
+      </xsl:for-each>
+      </tbody>
+      </table>
+    </xsl:if>
+
+    <xsl:if test="count($conflicts) > 0">
+    <h2>Conflicts</h2>
+    <table class="conflicts">
+      <thead>
+      <tr>
+        <th>Module</th>
+        <th>Selected</th>
+        <th>Evicted</th>
+      </tr>
+      </thead>
+      <tbody>
+      <xsl:for-each select="$conflicts">
+        <tr>
+        <td>
+           <xsl:element name="a">
+             <xsl:attribute name="href">#<xsl:value-of select="@organisation"/>-<xsl:value-of select="@name"/></xsl:attribute>
+             <xsl:value-of select="@name"/>
+             by
+             <xsl:value-of select="@organisation"/>
+           </xsl:element>
+        </td>
+        <td>
+          <xsl:for-each select="revision[not(@evicted)]">
+             <xsl:element name="a">
+               <xsl:attribute name="href">#<xsl:value-of select="../@organisation"/>-<xsl:value-of select="../@name"/>-<xsl:value-of select="@name"/></xsl:attribute>
+               <xsl:value-of select="@name"/>
+             </xsl:element>
+             <xsl:text> </xsl:text>
+          </xsl:for-each>
+        </td>
+        <td>
+          <xsl:for-each select="revision[@evicted]">
+             <xsl:element name="a">
+               <xsl:attribute name="href">#<xsl:value-of select="../@organisation"/>-<xsl:value-of select="../@name"/>-<xsl:value-of select="@name"/></xsl:attribute>
+               <xsl:value-of select="@name"/>
+			   <xsl:text> </xsl:text>
+               <xsl:value-of select="@evicted-reason"/>
+             </xsl:element>
+             <xsl:text> </xsl:text>
+          </xsl:for-each>
+        </td>
+        </tr>
+      </xsl:for-each>
+      </tbody>
+      </table>
+    </xsl:if>
+
+    <h2>Dependencies Overview</h2>
+        <xsl:call-template name="calling">
+          <xsl:with-param name="org" select="info/@organisation"/>
+          <xsl:with-param name="mod" select="info/@module"/>
+          <xsl:with-param name="rev" select="info/@revision"/>
+        </xsl:call-template>
+
+    <h2>Details</h2>    
+    <xsl:for-each select="$modules">
+    <h3>
+      <xsl:element name="a">
+         <xsl:attribute name="name"><xsl:value-of select="@organisation"/>-<xsl:value-of select="@name"/></xsl:attribute>
+      </xsl:element>
+      <xsl:value-of select="@name"/> by <xsl:value-of select="@organisation"/>
+    </h3>    
+      <xsl:for-each select="revision">
+        <h4>
+          <xsl:element name="a">
+             <xsl:attribute name="name"><xsl:value-of select="../@organisation"/>-<xsl:value-of select="../@name"/>-<xsl:value-of select="@name"/></xsl:attribute>
+          </xsl:element>
+           Revision: <xsl:value-of select="@name"/>
+          <span style="padding-left:15px;">
+          <xsl:call-template name="icons">
+            <xsl:with-param name="revision"      select="."/>
+          </xsl:call-template>
+          </span>
+        </h4>
+        <table class="header">
+        	<xsl:if test="@homepage">
+            <tr><td class="title">Home Page</td><td class="value">
+              <xsl:element name="a">
+    	            <xsl:attribute name="href"><xsl:value-of select="@homepage"/></xsl:attribute>
+    		    	<xsl:value-of select="@homepage"/>
+    	        </xsl:element></td>
+            </tr>  	        
+        	</xsl:if>
+          <tr><td class="title">Status</td><td class="value"><xsl:value-of select="@status"/></td></tr>
+          <tr><td class="title">Publication</td><td class="value"><xsl:value-of select="@pubdate"/></td></tr>
+          <tr><td class="title">Resolver</td><td class="value"><xsl:value-of select="@resolver"/></td></tr>
+          <tr><td class="title">Configurations</td><td class="value"><xsl:value-of select="@conf"/></td></tr>
+          <tr><td class="title">Artifacts size</td><td class="value"><xsl:value-of select="round(sum(artifacts/artifact/@size) div 1024)"/> kB
+            (<xsl:value-of select="round(sum(artifacts/artifact[@status='successful']/@size) div 1024)"/> kB downloaded,
+            <xsl:value-of select="round(sum(artifacts/artifact[@status='no']/@size) div 1024)"/> kB in cache)</td></tr>
+        	<xsl:if test="count(license) > 0">
+            <tr><td class="title">Licenses</td><td class="value">
+			      <xsl:call-template name="licenses">
+			        <xsl:with-param name="revision"      select="."/>
+			      </xsl:call-template>
+            </td></tr>  	        
+        	</xsl:if>
+        <xsl:if test="@evicted">
+        <tr><td class="title">Evicted by</td><td class="value">  
+            <b>
+			<xsl:for-each select="evicted-by">
+              <xsl:value-of select="@rev"/>
+			  <xsl:text> </xsl:text>
+            </xsl:for-each>
+			</b>
+			<xsl:text> </xsl:text>
+             <b><xsl:value-of select="@evicted-reason"/></b>
+			 in <b><xsl:value-of select="@evicted"/></b> conflict manager
+        </td></tr>
+        </xsl:if>
+        </table>
+        <h5>Required by</h5>
+        <table>
+          <thead>
+          <tr>
+            <th>Organisation</th>
+            <th>Name</th>
+            <th>Revision</th>
+            <th>In Configurations</th>
+            <th>Asked Revision</th>
+          </tr>
+          </thead>
+          <tbody>
+            <xsl:for-each select="caller">
+              <tr>
+              <td><xsl:value-of select="@organisation"/></td>
+              <td>
+      	         <xsl:element name="a">
+	                 <xsl:attribute name="href">#<xsl:value-of select="@organisation"/>-<xsl:value-of select="@name"/></xsl:attribute>
+		    	         <xsl:value-of select="@name"/>
+	               </xsl:element>
+              </td>
+              <td><xsl:value-of select="@callerrev"/></td>
+              <td><xsl:value-of select="@conf"/></td>
+              <td><xsl:value-of select="@rev"/></td>
+              </tr>
+            </xsl:for-each>   
+          </tbody>
+        </table>
+        <xsl:if test="not(@evicted)">
+        
+        <h5>Dependencies</h5>
+        <xsl:call-template name="calling">
+          <xsl:with-param name="org" select="../@organisation"/>
+          <xsl:with-param name="mod" select="../@name"/>
+          <xsl:with-param name="rev" select="@name"/>
+        </xsl:call-template>
+        <h5>Artifacts</h5>
+        <xsl:if test="count(artifacts/artifact) = 0">
+        <table><tr><td>
+        No artifact
+        </td></tr></table>
+        </xsl:if>
+        <xsl:if test="count(artifacts/artifact) > 0">
+        <table>
+          <thead>
+          <tr>
+            <th>Name</th>
+            <th>Type</th>
+            <th>Ext</th>
+            <th>Download</th>
+            <th>Size</th>
+          </tr>
+          </thead>
+          <tbody>
+            <xsl:for-each select="artifacts/artifact">
+              <tr>
+              <td><xsl:value-of select="@name"/></td>
+              <td><xsl:value-of select="@type"/></td>
+              <td><xsl:value-of select="@ext"/></td>
+              <td align="center"><xsl:value-of select="@status"/></td>
+              <td align="center"><xsl:value-of select="round(number(@size) div 1024)"/> kB</td>
+              </tr>
+            </xsl:for-each>    
+          </tbody>
+        </table>
+        </xsl:if>
+        
+        </xsl:if>
+      </xsl:for-each>    
+    </xsl:for-each>
+    </div>
+  </body>
+  </html>
+</xsl:template>
+
+</xsl:stylesheet>
diff --git a/src/java/org/apache/ivy/plugins/repository/AbstractRepository.java b/src/java/org/apache/ivy/plugins/repository/AbstractRepository.java
new file mode 100644
index 0000000..f1f4238
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/repository/AbstractRepository.java
@@ -0,0 +1,136 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.repository;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+
+import javax.swing.event.EventListenerList;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+
+public abstract class AbstractRepository implements Repository {
+    private EventListenerList listeners = new EventListenerList();
+
+    private String name;
+
+    private TransferEvent evt;
+
+    public void addTransferListener(TransferListener listener) {
+        listeners.add(TransferListener.class, listener);
+    }
+
+    public void removeTransferListener(TransferListener listener) {
+        listeners.remove(TransferListener.class, listener);
+    }
+
+    public boolean hasTransferListener(TransferListener listener) {
+        return Arrays.asList(listeners.getListeners(TransferListener.class)).contains(listener);
+    }
+
+    protected void fireTransferInitiated(Resource res, int requestType) {
+        evt = new TransferEvent(this, res, TransferEvent.TRANSFER_INITIATED, requestType);
+        fireTransferEvent(evt);
+    }
+
+    protected void fireTransferStarted() {
+        evt.setEventType(TransferEvent.TRANSFER_STARTED);
+        fireTransferEvent(evt);
+    }
+
+    protected void fireTransferStarted(long totalLength) {
+        evt.setEventType(TransferEvent.TRANSFER_STARTED);
+        evt.setTotalLength(totalLength);
+        evt.setTotalLengthSet(true);
+        fireTransferEvent(evt);
+    }
+
+    protected void fireTransferProgress(long length) {
+        evt.setEventType(TransferEvent.TRANSFER_PROGRESS);
+        evt.setLength(length);
+        if (!evt.isTotalLengthSet()) {
+            evt.setTotalLength(evt.getTotalLength() + length);
+        }
+        fireTransferEvent(evt);
+    }
+
+    protected void fireTransferCompleted() {
+        evt.setEventType(TransferEvent.TRANSFER_COMPLETED);
+        if (evt.getTotalLength() > 0 && !evt.isTotalLengthSet()) {
+            evt.setTotalLengthSet(true);
+        }
+        fireTransferEvent(evt);
+    }
+
+    protected void fireTransferCompleted(long totalLength) {
+        evt.setEventType(TransferEvent.TRANSFER_COMPLETED);
+        evt.setTotalLength(totalLength);
+        evt.setTotalLengthSet(true);
+        fireTransferEvent(evt);
+    }
+
+    protected void fireTransferError() {
+        evt.setEventType(TransferEvent.TRANSFER_ERROR);
+        fireTransferEvent(evt);
+    }
+
+    protected void fireTransferError(Exception ex) {
+        evt.setEventType(TransferEvent.TRANSFER_ERROR);
+        evt.setException(ex);
+        fireTransferEvent(evt);
+    }
+
+    protected void fireTransferEvent(TransferEvent evt) {
+        Object[] listeners = this.listeners.getListenerList();
+        for (int i = listeners.length - 2; i >= 0; i -= 2) {
+            if (listeners[i] == TransferListener.class) {
+                ((TransferListener) listeners[i + 1]).transferProgress(evt);
+            }
+        }
+    }
+
+    public String getFileSeparator() {
+        return "/";
+    }
+
+    public String standardize(String source) {
+        return source.replace('\\', '/');
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String toString() {
+        return getName();
+    }
+
+    public void put(Artifact artifact, File source, String destination, boolean overwrite)
+            throws IOException {
+        put(source, destination, overwrite);
+    }
+
+    protected void put(File source, String destination, boolean overwrite) throws IOException {
+        throw new UnsupportedOperationException("put in not supported by " + getName());
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/repository/ArtifactResourceResolver.java b/src/java/org/apache/ivy/plugins/repository/ArtifactResourceResolver.java
new file mode 100644
index 0000000..6cf719f
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/repository/ArtifactResourceResolver.java
@@ -0,0 +1,29 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.repository;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.plugins.resolver.util.ResolvedResource;
+
+/**
+ * An {@link ArtifactResourceResolver} is responsible for the resolution of an artifact into a
+ * {@link ResolvedResource}.
+ */
+public interface ArtifactResourceResolver {
+    public ResolvedResource resolve(Artifact artifact);
+}
diff --git a/src/java/org/apache/ivy/plugins/repository/BasicResource.java b/src/java/org/apache/ivy/plugins/repository/BasicResource.java
new file mode 100644
index 0000000..9184f29
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/repository/BasicResource.java
@@ -0,0 +1,76 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.repository;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public class BasicResource implements Resource {
+    private boolean local;
+
+    private String name;
+
+    private long lastModified;
+
+    private long contentLength;
+
+    private boolean exists;
+
+    public BasicResource(String name, boolean exists, long contentLength, long lastModified,
+            boolean local) {
+        this.name = name;
+        this.exists = exists;
+        this.contentLength = contentLength;
+        this.lastModified = lastModified;
+        this.local = local;
+    }
+
+    public Resource clone(String cloneName) {
+        throw new UnsupportedOperationException("basic resource do not support the clone method");
+    }
+
+    public boolean exists() {
+        return this.exists;
+    }
+
+    public long getContentLength() {
+        return this.contentLength;
+    }
+
+    public long getLastModified() {
+        return this.lastModified;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public boolean isLocal() {
+        return this.local;
+    }
+
+    public InputStream openStream() throws IOException {
+        throw new UnsupportedOperationException(
+                "basic resource do not support the openStream method");
+    }
+
+    public String toString() {
+        return getName();
+    }
+
+}
diff --git a/src/java/org/apache/ivy/plugins/repository/LazyResource.java b/src/java/org/apache/ivy/plugins/repository/LazyResource.java
new file mode 100644
index 0000000..12dbf84
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/repository/LazyResource.java
@@ -0,0 +1,97 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.repository;
+
+public abstract class LazyResource implements Resource {
+    private boolean init = false;
+
+    private boolean local;
+
+    private String name;
+
+    private long lastModified;
+
+    private long contentLength;
+
+    private boolean exists;
+
+    public LazyResource(String name) {
+        this.name = name;
+    }
+
+    protected abstract void init();
+
+    private void checkInit() {
+        if (!init) {
+            init();
+            init = true;
+        }
+    }
+
+    public boolean exists() {
+        checkInit();
+        return exists;
+    }
+
+    public long getContentLength() {
+        checkInit();
+        return contentLength;
+    }
+
+    public long getLastModified() {
+        checkInit();
+        return lastModified;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public boolean isLocal() {
+        checkInit();
+        return local;
+    }
+
+    public String toString() {
+        return getName();
+    }
+
+    protected void setContentLength(long contentLength) {
+        this.contentLength = contentLength;
+    }
+
+    protected void setExists(boolean exists) {
+        this.exists = exists;
+    }
+
+    protected void setLastModified(long lastModified) {
+        this.lastModified = lastModified;
+    }
+
+    protected void setLocal(boolean local) {
+        this.local = local;
+    }
+
+    protected void init(Resource r) {
+        setContentLength(r.getContentLength());
+        setLocal(r.isLocal());
+        setLastModified(r.getLastModified());
+        setExists(r.exists());
+    }
+
+}
diff --git a/src/java/org/apache/ivy/plugins/repository/LocalizableResource.java b/src/java/org/apache/ivy/plugins/repository/LocalizableResource.java
new file mode 100644
index 0000000..18be7ca
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/repository/LocalizableResource.java
@@ -0,0 +1,38 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.repository;
+
+import java.io.File;
+
+/**
+ * Resource which can be located locally
+ * <p>
+ * If local (checked via {@link #isLocal()}), {@link #getFile()} will return its local location,
+ * without any download or copy involved
+ * </p>
+ */
+public interface LocalizableResource extends Resource {
+
+    /**
+     * @return the local file of this resource.
+     * @throws IllegalStateException
+     *             when {@link #isLocal()} returns <code>false</code>
+     */
+    public File getFile();
+
+}
diff --git a/src/java/org/apache/ivy/plugins/repository/Repository.java b/src/java/org/apache/ivy/plugins/repository/Repository.java
new file mode 100644
index 0000000..9534c99
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/repository/Repository.java
@@ -0,0 +1,153 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.repository;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+
+/**
+ * Represents a collection of resources available to Ivy. Ivy uses one or more repositories as both
+ * a source of resources for Ivy enabled build systems and as a distribution center for resources
+ * generated by Ivy enabled build systems. </p>
+ * <p>
+ * A repository supports the following fundamental operations
+ * <ul>
+ * <li>retrieving a resource from the repository.</li>
+ * <li>transferring a resource to the repository.</li>
+ * <li>retrieving a listing of resources.</li>
+ * </ul>
+ * </p>
+ * <h4>Resource Retrieval</h4> </p>
+ * <p>
+ * {@link #get} retrieves a resource specified by a provided identifier creating a new file .
+ * </p>
+ * </p> <h4>resource Publication</h4> </p>
+ * <p>
+ * {@link #put} transfers a file to the repository.
+ * </p>
+ * </p> <h4>resource Listing</h4> </p>
+ * <p>
+ * {@link #list} returns a listing of file like objects belonging to a specified parent directory.
+ * </p>
+ * </p>
+ */
+public interface Repository {
+
+    /**
+     * Return the resource associated with a specified identifier. If the resource does not exist,
+     * it should return a Resource with exists() returning false. An IOException should only be
+     * thrown when a real IO problem occurs, like the impossibility to connect to a server.
+     * 
+     * @param source
+     *            A string identifying the resource.
+     * @return The resource associated with the resource identifier.
+     * @throws IOException
+     *             On error while trying to get resource.
+     */
+    Resource getResource(String source) throws IOException;
+
+    /**
+     * Fetch a resource from the repository.
+     * 
+     * @param source
+     *            A string identifying the resource to be fetched.
+     * @param destination
+     *            Where to place the fetched resource.
+     * @throws IOException
+     *             On retrieval failure.
+     */
+    void get(String source, File destination) throws IOException;
+
+    /**
+     * Transfer a resource to the repository
+     * 
+     * @param artifact
+     *            The artifact to be transferred.
+     * @param source
+     *            The local file to be transferred.
+     * @param destination
+     *            Where to transfer the resource.
+     * @param overwrite
+     *            Whether the transfer should overwrite an existing resource.
+     * @throws IOException
+     *             On publication failure.
+     */
+    void put(Artifact artifact, File source, String destination, boolean overwrite)
+            throws IOException;
+
+    /**
+     * Return a listing of resources names
+     * 
+     * @param parent
+     *            The parent directory from which to generate the listing.
+     * @return A listing of the parent directory's file content, as a List of String.
+     * @throws IOException
+     *             On listing failure.
+     */
+    List list(String parent) throws IOException;
+
+    /**
+     * Add a listener to the repository.
+     * 
+     * @param listener
+     *            The listener to attach to the repository.
+     */
+    void addTransferListener(TransferListener listener);
+
+    /**
+     * Remove a listener on the repository
+     * 
+     * @param listener
+     *            The listener to remove
+     */
+    void removeTransferListener(TransferListener listener);
+
+    /**
+     * Determine if a given listener is attached to the repository.
+     * 
+     * @param listener
+     *            The listener being queried
+     * @return <code>true</code> if the provided listener is attached to the repository,
+     *         <code>false</code> if not.
+     */
+    boolean hasTransferListener(TransferListener listener);
+
+    /**
+     * Get the repository's file separator string.
+     * 
+     * @return The repository's file separator delimiter
+     */
+    String getFileSeparator();
+
+    /**
+     * Normalize a string.
+     * 
+     * @param source
+     *            The string to normalize.
+     * @return The normalized string.
+     */
+    String standardize(String source);
+
+    /**
+     * Return the name of the repository
+     */
+    String getName();
+}
diff --git a/src/java/org/apache/ivy/plugins/repository/RepositoryCopyProgressListener.java b/src/java/org/apache/ivy/plugins/repository/RepositoryCopyProgressListener.java
new file mode 100644
index 0000000..2f89630
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/repository/RepositoryCopyProgressListener.java
@@ -0,0 +1,56 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.repository;
+
+import org.apache.ivy.util.CopyProgressEvent;
+import org.apache.ivy.util.CopyProgressListener;
+
+public class RepositoryCopyProgressListener implements CopyProgressListener {
+    private final AbstractRepository repository;
+
+    public RepositoryCopyProgressListener(AbstractRepository repository) {
+        this.repository = repository;
+    }
+
+    private Long totalLength = null;
+
+    public void start(CopyProgressEvent evt) {
+        if (totalLength != null) {
+            repository.fireTransferStarted(totalLength.longValue());
+        } else {
+            repository.fireTransferStarted();
+        }
+    }
+
+    public void progress(CopyProgressEvent evt) {
+        repository.fireTransferProgress(evt.getReadBytes());
+    }
+
+    public void end(CopyProgressEvent evt) {
+        repository.fireTransferProgress(evt.getReadBytes());
+        repository.fireTransferCompleted();
+    }
+
+    public Long getTotalLength() {
+        return totalLength;
+    }
+
+    public void setTotalLength(Long totalLength) {
+        this.totalLength = totalLength;
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/repository/Resource.java b/src/java/org/apache/ivy/plugins/repository/Resource.java
new file mode 100644
index 0000000..ea3ef01
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/repository/Resource.java
@@ -0,0 +1,100 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.repository;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Represents a resource in an Ivy {@link Repository}. The resource interface allows one to obtain
+ * the following information about a resource:
+ * <ul>
+ * <li>resource name/identifier in repository syntax</li>
+ * <li>date the resource was last modified.</li>
+ * <li>size of the resource in bytes.</li>
+ * <li>if the resource is available.</li>
+ * </ul>
+ * </p> <h4>Implementation Notes</h4> In implementing the interface you need to ensure the following
+ * behaviors:
+ * <ul>
+ * <li>All of the methods specified in the interface fail by returning an empty value (
+ * <code>false</code>, <code>0</code>, <code>""</code>). In other words, the specified interface
+ * methods should not throw RuntimeExceptions.</li>
+ * <li>Failure conditions should be logged using the {@link org.apache.ivy.util.Message#verbose}
+ * method.</li>
+ * <li>Failure of one of the interface's specified methods results in all other interface specified
+ * methods returning an empty value (<code>false</code>, <code>0</code>, <code>""</code>).</li>
+ * </ul>
+ * </p>
+ */
+
+public interface Resource {
+    /**
+     * Get the name of the resource.
+     * 
+     * @return the repositorie's assigned resource name/identifier.
+     */
+    public String getName();
+
+    /**
+     * Get the date the resource was last modified
+     * 
+     * @return A <code>long</code> value representing the time the file was last modified, measured
+     *         in milliseconds since the epoch (00:00:00 GMT, January 1, 1970), or <code>0L</code>
+     *         if the file does not exist or if an I/O error occurs.
+     */
+    public long getLastModified();
+
+    /**
+     * Get the resource size
+     * 
+     * @return a <code>long</code> value representing the size of the resource in bytes.
+     */
+    public long getContentLength();
+
+    /**
+     * Determine if the resource is available. </p> Note that this method only checks for
+     * availability, not for actual existence.
+     * 
+     * @return <code>boolean</code> value indicating if the resource is available.
+     */
+    public boolean exists();
+
+    /**
+     * Is this resource local to this host, i.e. is it on the file system?
+     * 
+     * @return <code>boolean</code> value indicating if the resource is local.
+     */
+    public boolean isLocal();
+
+    /**
+     * Clones this resource with a new resource with a different name
+     * 
+     * @param cloneName
+     *            the name of the clone
+     * @return the cloned resource
+     */
+    public Resource clone(String cloneName);
+
+    /**
+     * Opens a stream on this resource
+     * 
+     * @return the opened input stream
+     */
+    public InputStream openStream() throws IOException;
+}
diff --git a/src/java/org/apache/ivy/plugins/repository/ResourceDownloader.java b/src/java/org/apache/ivy/plugins/repository/ResourceDownloader.java
new file mode 100644
index 0000000..a2cb829
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/repository/ResourceDownloader.java
@@ -0,0 +1,38 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.repository;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+
+/**
+ * A {@link ResourceDownloader} is able to download a Resource to a File.
+ * <p>
+ * Depending on the implementation, the downloader may also choose to download checksums
+ * automatically and check the consistency of the downloaded resource.
+ * </p>
+ * <p>
+ * The implementation is also responsible for using a .part file during download, to ensure the
+ * destination file will exist only if the download is completed successfully.
+ * </p>
+ */
+public interface ResourceDownloader {
+    public void download(Artifact artifact, Resource resource, File dest) throws IOException;
+}
diff --git a/src/java/org/apache/ivy/plugins/repository/ResourceHelper.java b/src/java/org/apache/ivy/plugins/repository/ResourceHelper.java
new file mode 100644
index 0000000..e0837cc
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/repository/ResourceHelper.java
@@ -0,0 +1,61 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.repository;
+
+import java.io.File;
+import java.net.MalformedURLException;
+
+import org.apache.ivy.plugins.repository.file.FileResource;
+import org.apache.ivy.plugins.repository.url.URLResource;
+import org.apache.ivy.util.Message;
+
+public final class ResourceHelper {
+
+    private ResourceHelper() {
+
+    }
+
+    public static boolean equals(Resource res, File f) {
+        if (res == null && f == null) {
+            return true;
+        }
+        if (res == null || f == null) {
+            return false;
+        }
+        if (res instanceof FileResource) {
+            return new File(res.getName()).equals(f);
+        } else if (res instanceof URLResource) {
+            try {
+                return f.toURI().toURL().toExternalForm().equals(res.getName());
+            } catch (MalformedURLException e) {
+                return false;
+            }
+        }
+        return false;
+    }
+
+    public static long getLastModifiedOrDefault(Resource res) {
+        long last = res.getLastModified();
+        if (last > 0) {
+            return last;
+        } else {
+            Message.debug("impossible to get date for " + res + ": using 'now'");
+            return System.currentTimeMillis();
+        }
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/repository/TransferEvent.java b/src/java/org/apache/ivy/plugins/repository/TransferEvent.java
new file mode 100644
index 0000000..d7697e3
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/repository/TransferEvent.java
@@ -0,0 +1,357 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.repository;
+
+import java.io.File;
+
+import org.apache.ivy.core.event.IvyEvent;
+
+/**
+ * TransferEvent is used to notify TransferListeners about progress in transfer of resources form/to
+ * the respository This class is LARGELY inspired by org.apache.maven.wagon.events.TransferEvent
+ * released under the following copyright license:
+ * 
+ * <pre>
+ * 
+ *  Copyright 2001-2005 The Apache Software Foundation.
+ * 
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ * 
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ * 
+ * </pre>
+ * 
+ * Orginal class written by Michal Maczka.
+ */
+public class TransferEvent extends IvyEvent {
+    /**
+     * A transfer was attempted, but has not yet commenced.
+     */
+    public static final int TRANSFER_INITIATED = 0;
+
+    /**
+     * A transfer was started.
+     */
+    public static final int TRANSFER_STARTED = 1;
+
+    /**
+     * A transfer is completed.
+     */
+    public static final int TRANSFER_COMPLETED = 2;
+
+    /**
+     * A transfer is in progress.
+     */
+    public static final int TRANSFER_PROGRESS = 3;
+
+    /**
+     * An error occurred during transfer
+     */
+    public static final int TRANSFER_ERROR = 4;
+
+    /**
+     * Used to check event type validity: should always be 0 <= type <= LAST_EVENT_TYPE
+     */
+    private static final int LAST_EVENT_TYPE = TRANSFER_ERROR;
+
+    /**
+     * Indicates GET transfer (from the repository)
+     */
+    public static final int REQUEST_GET = 5;
+
+    /**
+     * Indicates PUT transfer (to the repository)
+     */
+    public static final int REQUEST_PUT = 6;
+
+    public static final String TRANSFER_INITIATED_NAME = "transfer-initiated";
+
+    public static final String TRANSFER_STARTED_NAME = "transfer-started";
+
+    public static final String TRANSFER_PROGRESS_NAME = "transfer-progress";
+
+    public static final String TRANSFER_COMPLETED_NAME = "transfer-completed";
+
+    public static final String TRANSFER_ERROR_NAME = "transfer-error";
+
+    private Resource resource;
+
+    private int eventType;
+
+    private int requestType;
+
+    private Exception exception;
+
+    private File localFile;
+
+    private Repository repository;
+
+    private long length;
+
+    private long totalLength;
+
+    private boolean isTotalLengthSet = false;
+
+    /**
+     * This attribute is used to store the time at which the event enters a type.
+     * <p>
+     * The array should better be seen as a Map from event type (int) to the time at which the event
+     * entered that type, 0 if it never entered this type.
+     * </p>
+     */
+    private long[] timeTracking = new long[LAST_EVENT_TYPE + 1];
+
+    public TransferEvent(final Repository repository, final Resource resource, final int eventType,
+            final int requestType) {
+        super(getName(eventType));
+
+        this.repository = repository;
+        setResource(resource);
+
+        setEventType(eventType);
+
+        setRequestType(requestType);
+    }
+
+    public TransferEvent(final Repository repository, final Resource resource,
+            final Exception exception, final int requestType) {
+        this(repository, resource, TRANSFER_ERROR, requestType);
+
+        this.exception = exception;
+    }
+
+    public TransferEvent(final Repository repository, final Resource resource, long length,
+            final int requestType) {
+        this(repository, resource, TRANSFER_PROGRESS, requestType);
+
+        this.length = length;
+        this.totalLength = length;
+    }
+
+    private static String getName(int eventType) {
+        switch (eventType) {
+            case TRANSFER_INITIATED:
+                return TRANSFER_INITIATED_NAME;
+            case TRANSFER_STARTED:
+                return TRANSFER_STARTED_NAME;
+            case TRANSFER_PROGRESS:
+                return TRANSFER_PROGRESS_NAME;
+            case TRANSFER_COMPLETED:
+                return TRANSFER_COMPLETED_NAME;
+            case TRANSFER_ERROR:
+                return TRANSFER_ERROR_NAME;
+            default:
+                return null;
+        }
+    }
+
+    /**
+     * @return Returns the resource.
+     */
+    public Resource getResource() {
+        return resource;
+    }
+
+    /**
+     * @return Returns the exception.
+     */
+    public Exception getException() {
+        return exception;
+    }
+
+    /**
+     * Returns the request type.
+     * 
+     * @return Returns the request type. The Request type is one of
+     *         <code>TransferEvent.REQUEST_GET<code> or <code>TransferEvent.REQUEST_PUT<code>
+     */
+    public int getRequestType() {
+        return requestType;
+    }
+
+    /**
+     * Sets the request type
+     * 
+     * @param requestType
+     *            The requestType to set. The Request type value should be either
+     *            <code>TransferEvent.REQUEST_GET<code> or <code>TransferEvent.REQUEST_PUT<code>.
+     * @throws IllegalArgumentException
+     *             when
+     */
+    protected void setRequestType(final int requestType) {
+        switch (requestType) {
+
+            case REQUEST_PUT:
+                break;
+            case REQUEST_GET:
+                break;
+
+            default:
+                throw new IllegalArgumentException("Illegal request type: " + requestType);
+        }
+
+        this.requestType = requestType;
+        addAttribute("request-type", requestType == REQUEST_GET ? "get" : "put");
+    }
+
+    /**
+     * @return Returns the eventType.
+     */
+    public int getEventType() {
+        return eventType;
+    }
+
+    /**
+     * @param eventType
+     *            The eventType to set.
+     */
+    protected void setEventType(final int eventType) {
+        checkEventType(eventType);
+        if (this.eventType != eventType) {
+            this.eventType = eventType;
+            timeTracking[eventType] = System.currentTimeMillis();
+            if (eventType > TRANSFER_INITIATED) {
+                addAttribute("total-duration",
+                    String.valueOf(getElapsedTime(TRANSFER_INITIATED, eventType)));
+                if (eventType > TRANSFER_STARTED) {
+                    addAttribute("duration",
+                        String.valueOf(getElapsedTime(TRANSFER_STARTED, eventType)));
+                }
+            }
+        }
+    }
+
+    /**
+     * @param resource
+     *            The resource to set.
+     */
+    protected void setResource(final Resource resource) {
+        this.resource = resource;
+        addAttribute("resource", this.resource.getName());
+    }
+
+    /**
+     * @return Returns the local file.
+     */
+    public File getLocalFile() {
+        return localFile;
+    }
+
+    /**
+     * @param localFile
+     *            The local file to set.
+     */
+    protected void setLocalFile(File localFile) {
+        this.localFile = localFile;
+    }
+
+    public long getLength() {
+        return length;
+    }
+
+    protected void setLength(long length) {
+        this.length = length;
+    }
+
+    public long getTotalLength() {
+        return totalLength;
+    }
+
+    protected void setTotalLength(long totalLength) {
+        this.totalLength = totalLength;
+    }
+
+    public void setException(Exception exception) {
+        this.exception = exception;
+    }
+
+    public boolean isTotalLengthSet() {
+        return isTotalLengthSet;
+    }
+
+    public void setTotalLengthSet(boolean isTotalLengthSet) {
+        this.isTotalLengthSet = isTotalLengthSet;
+    }
+
+    public Repository getRepository() {
+        return repository;
+    }
+
+    /**
+     * Returns the elapsed time (in ms) between when the event entered one type until it entered
+     * another event time.
+     * <p>
+     * This is especially useful to get the elapsed transfer time:
+     * 
+     * <pre>
+     * getElapsedTime(TransferEvent.TRANSFER_STARTED, TransferEvent.TRANSFER_COMPLETED);
+     * </pre>
+     * 
+     * </p>
+     * <p>
+     * Special cases:
+     * <ul>
+     * <li>returns -1 if the event never entered the fromEventType or the toEventType.</li>
+     * <li>returns 0 if the event entered toEventType before fromEventType</li>
+     * </ul>
+     * </p>
+     * 
+     * @param fromEventType
+     *            the event type constant from which time should be measured
+     * @param toEventType
+     *            the event type constant to which time should be measured
+     * @return the elapsed time (in ms) between when the event entered fromEventType until it
+     *         entered toEventType.
+     * @throws IllegalArgumentException
+     *             if either type is not a known constant event type.
+     */
+    public long getElapsedTime(int fromEventType, int toEventType) {
+        checkEventType(fromEventType);
+        checkEventType(toEventType);
+        long start = timeTracking[fromEventType];
+        long end = timeTracking[toEventType];
+        if (start == 0 || end == 0) {
+            return -1;
+        } else if (end < start) {
+            return 0;
+        } else {
+            return end - start;
+        }
+    }
+
+    /**
+     * Checks the given event type is a valid event type, throws an {@link IllegalArgumentException}
+     * if it isn't
+     * 
+     * @param eventType
+     *            the event type to check
+     */
+    private void checkEventType(int eventType) {
+        if (eventType < 0 || eventType > LAST_EVENT_TYPE) {
+            throw new IllegalArgumentException("invalid event type " + eventType);
+        }
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/repository/TransferListener.java b/src/java/org/apache/ivy/plugins/repository/TransferListener.java
new file mode 100644
index 0000000..b3ccb9e
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/repository/TransferListener.java
@@ -0,0 +1,27 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.repository;
+
+import java.util.EventListener;
+
+/**
+ * Listen to repository transfer
+ */
+public interface TransferListener extends EventListener {
+    void transferProgress(TransferEvent evt);
+}
diff --git a/src/java/org/apache/ivy/plugins/repository/file/FileRepository.java b/src/java/org/apache/ivy/plugins/repository/file/FileRepository.java
new file mode 100644
index 0000000..0ac4baf
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/repository/file/FileRepository.java
@@ -0,0 +1,150 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.repository.file;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.ivy.plugins.repository.AbstractRepository;
+import org.apache.ivy.plugins.repository.RepositoryCopyProgressListener;
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.plugins.repository.TransferEvent;
+import org.apache.ivy.util.Checks;
+import org.apache.ivy.util.FileUtil;
+
+public class FileRepository extends AbstractRepository {
+    private RepositoryCopyProgressListener progress = new RepositoryCopyProgressListener(this);
+
+    private File baseDir;
+
+    private boolean local = true;
+
+    public FileRepository() {
+        baseDir = null;
+    }
+
+    public FileRepository(File basedir) {
+        setBaseDir(basedir);
+    }
+
+    public Resource getResource(String source) throws IOException {
+        return new FileResource(this, getFile(source));
+    }
+
+    public void get(String source, File destination) throws IOException {
+        fireTransferInitiated(getResource(source), TransferEvent.REQUEST_GET);
+        copy(getFile(source), destination, true);
+    }
+
+    public void put(File source, String destination, boolean overwrite) throws IOException {
+        fireTransferInitiated(getResource(destination), TransferEvent.REQUEST_PUT);
+        copy(source, getFile(destination), overwrite);
+    }
+
+    public void move(File src, File dest) throws IOException {
+        if (!src.renameTo(dest)) {
+            throw new IOException("impossible to move '" + src + "' to '" + dest + "'");
+        }
+    }
+
+    public void delete(File f) throws IOException {
+        if (!FileUtil.forceDelete(f)) {
+            throw new IOException("impossible to delete '" + f + "'");
+        }
+    }
+
+    private void copy(File src, File destination, boolean overwrite) throws IOException {
+        try {
+            getProgressListener().setTotalLength(new Long(src.length()));
+            if (!FileUtil.copy(src, destination, getProgressListener(), overwrite)) {
+                if (!overwrite && destination.exists()) {
+                    throw new IOException("file copy not done from " + src + " to " + destination
+                            + ": destination already exists and overwrite is false");
+                } else {
+                    throw new IOException("file copy not done from " + src + " to " + destination);
+                }
+            }
+        } catch (IOException ex) {
+            fireTransferError(ex);
+            throw ex;
+        } catch (RuntimeException ex) {
+            fireTransferError(ex);
+            throw ex;
+        } finally {
+            getProgressListener().setTotalLength(null);
+        }
+    }
+
+    protected RepositoryCopyProgressListener getProgressListener() {
+        return progress;
+    }
+
+    public List list(String parent) throws IOException {
+        File dir = getFile(parent);
+        if (dir.exists() && dir.isDirectory()) {
+            String[] names = dir.list();
+            if (names != null) {
+                List ret = new ArrayList(names.length);
+                for (int i = 0; i < names.length; i++) {
+                    ret.add(parent + getFileSeparator() + names[i]);
+                }
+                return ret;
+            }
+        }
+        return null;
+    }
+
+    File getFile(String source) {
+        if (baseDir != null) {
+            return FileUtil.resolveFile(baseDir, source);
+        } else {
+            return Checks.checkAbsolute(source, "source");
+        }
+    }
+
+    public boolean isLocal() {
+        return local;
+    }
+
+    public void setLocal(boolean local) {
+        this.local = local;
+    }
+
+    public File getBaseDir() {
+        return baseDir;
+    }
+
+    public final void setBaseDir(File baseDir) {
+        Checks.checkAbsolute(baseDir, "basedir");
+        this.baseDir = baseDir;
+    }
+
+    public String standardize(String source) {
+        if (baseDir != null) {
+            return FileUtil.resolveFile(baseDir, source).getPath();
+        } else {
+            return FileUtil.normalize(source).getPath();
+        }
+    }
+
+    public String getFileSeparator() {
+        return File.separator;
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/repository/file/FileResource.java b/src/java/org/apache/ivy/plugins/repository/file/FileResource.java
new file mode 100644
index 0000000..322299d
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/repository/file/FileResource.java
@@ -0,0 +1,76 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.repository.file;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.ivy.plugins.repository.Resource;
+
+public class FileResource implements Resource {
+    private File file;
+
+    private FileRepository repository;
+
+    public FileResource(FileRepository repository, File f) {
+        this.repository = repository;
+        this.file = f;
+    }
+
+    public String getName() {
+        return file.getPath();
+    }
+
+    public Resource clone(String cloneName) {
+        return new FileResource(repository, repository.getFile(cloneName));
+    }
+
+    public long getLastModified() {
+        return file.lastModified();
+    }
+
+    public long getContentLength() {
+        return file.length();
+    }
+
+    public boolean exists() {
+        return file.exists();
+    }
+
+    public String toString() {
+        return getName();
+    }
+
+    public File getFile() {
+        return file;
+    }
+
+    public FileRepository getRepository() {
+        return repository;
+    }
+
+    public boolean isLocal() {
+        return repository.isLocal();
+    }
+
+    public InputStream openStream() throws IOException {
+        return new FileInputStream(file);
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/repository/jar/JarRepository.java b/src/java/org/apache/ivy/plugins/repository/jar/JarRepository.java
new file mode 100644
index 0000000..7a1cf28
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/repository/jar/JarRepository.java
@@ -0,0 +1,89 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.repository.jar;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.jar.JarFile;
+import java.util.zip.ZipEntry;
+
+import org.apache.ivy.plugins.repository.AbstractRepository;
+import org.apache.ivy.plugins.repository.RepositoryCopyProgressListener;
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.plugins.repository.TransferEvent;
+import org.apache.ivy.util.FileUtil;
+
+public class JarRepository extends AbstractRepository {
+
+    private RepositoryCopyProgressListener progress = new RepositoryCopyProgressListener(this);
+
+    private JarFile jarFile;
+
+    public void setJarFile(JarFile jarFile) {
+        this.jarFile = jarFile;
+    }
+
+    public Resource getResource(String source) throws IOException {
+        return new JarResource(jarFile, source);
+    }
+
+    protected RepositoryCopyProgressListener getProgressListener() {
+        return progress;
+    }
+
+    public void get(String source, File destination) throws IOException {
+        fireTransferInitiated(getResource(source), TransferEvent.REQUEST_GET);
+        try {
+            ZipEntry entry = jarFile.getEntry(source);
+            if (entry == null) {
+                throw new FileNotFoundException();
+            }
+            getProgressListener().setTotalLength(new Long(entry.getSize()));
+            FileUtil.copy(jarFile.getInputStream(entry), destination, getProgressListener());
+        } catch (IOException ex) {
+            fireTransferError(ex);
+            throw ex;
+        } catch (RuntimeException ex) {
+            fireTransferError(ex);
+            throw ex;
+        } finally {
+            getProgressListener().setTotalLength(null);
+        }
+    }
+
+    public List/* <String> */list(String parent) throws IOException {
+        ZipEntry parentEntry = jarFile.getEntry(parent);
+        if (parentEntry == null || !parentEntry.isDirectory()) {
+            return null;
+        }
+        List/* <String> */children = new ArrayList();
+        Enumeration entries = jarFile.entries();
+        while (entries.hasMoreElements()) {
+            ZipEntry entry = (ZipEntry) entries.nextElement();
+            if (entry.getName().startsWith(parent) && entry.getName().equals(parentEntry.getName())) {
+                children.add(entry.getName());
+            }
+        }
+        return children;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/plugins/repository/jar/JarResource.java b/src/java/org/apache/ivy/plugins/repository/jar/JarResource.java
new file mode 100644
index 0000000..3324806
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/repository/jar/JarResource.java
@@ -0,0 +1,73 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.repository.jar;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.jar.JarFile;
+import java.util.zip.ZipEntry;
+
+import org.apache.ivy.plugins.repository.Resource;
+
+public class JarResource implements Resource {
+
+    private final JarFile jarFile;
+
+    private final String path;
+
+    private ZipEntry entry;
+
+    public JarResource(JarFile jarFile, String path) {
+        this.jarFile = jarFile;
+        this.path = path;
+        entry = jarFile.getEntry(path);
+    }
+
+    public String getName() {
+        return path;
+    }
+
+    public long getLastModified() {
+        return entry.getTime();
+    }
+
+    public long getContentLength() {
+        return entry.getSize();
+    }
+
+    public boolean exists() {
+        return entry != null;
+    }
+
+    public boolean isLocal() {
+        // not local as it is not a directly accessible file
+        return false;
+    }
+
+    public Resource clone(String cloneName) {
+        return new JarResource(jarFile, cloneName);
+    }
+
+    public InputStream openStream() throws IOException {
+        return jarFile.getInputStream(entry);
+    }
+
+    public String toString() {
+        return jarFile.getName() + "!" + getName();
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/repository/sftp/SFTPRepository.java b/src/java/org/apache/ivy/plugins/repository/sftp/SFTPRepository.java
new file mode 100644
index 0000000..1f5f93a
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/repository/sftp/SFTPRepository.java
@@ -0,0 +1,289 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.repository.sftp;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.ivy.plugins.repository.BasicResource;
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.plugins.repository.TransferEvent;
+import org.apache.ivy.plugins.repository.ssh.AbstractSshBasedRepository;
+import org.apache.ivy.plugins.repository.ssh.SshCache;
+import org.apache.ivy.util.Message;
+
+import com.jcraft.jsch.ChannelSftp;
+import com.jcraft.jsch.ChannelSftp.LsEntry;
+import com.jcraft.jsch.JSchException;
+import com.jcraft.jsch.Session;
+import com.jcraft.jsch.SftpATTRS;
+import com.jcraft.jsch.SftpException;
+import com.jcraft.jsch.SftpProgressMonitor;
+
+/**
+ * SFTP Repository, allow to use a repository accessed by sftp protocol. It supports all operations:
+ * get, put and list. It relies on jsch for sftp handling, and thus is compatible with sftp version
+ * 0, 1, 2 and 3
+ */
+public class SFTPRepository extends AbstractSshBasedRepository {
+    // this must be a long to ensure the multiplication done below uses longs
+    // instead of ints which are not big enough to hold the result
+    private static final long MILLIS_PER_SECOND = 1000;
+
+    private final class MyProgressMonitor implements SftpProgressMonitor {
+        private long totalLength;
+
+        public void init(int op, String src, String dest, long max) {
+            totalLength = max;
+            fireTransferStarted(max);
+        }
+
+        public void end() {
+            fireTransferCompleted(totalLength);
+        }
+
+        public boolean count(long count) {
+            fireTransferProgress(count);
+            return true;
+        }
+    }
+
+    public SFTPRepository() {
+    }
+
+    public Resource getResource(String source) {
+        return new SFTPResource(this, source);
+    }
+
+    /**
+     * This method is similar to getResource, except that the returned resource is fully initialized
+     * (resolved in the sftp repository), and that the given string is a full remote path
+     * 
+     * @param path
+     *            the full remote path in the repository of the resource
+     * @return a fully initialized resource, able to answer to all its methods without needing any
+     *         further connection
+     */
+    public Resource resolveResource(String path) {
+        try {
+            ChannelSftp c = getSftpChannel(path);
+
+            Collection r = c.ls(getPath(path));
+
+            if (r != null) {
+                for (Iterator iter = r.iterator(); iter.hasNext();) {
+                    Object obj = iter.next();
+                    if (obj instanceof LsEntry) {
+                        LsEntry entry = (LsEntry) obj;
+                        SftpATTRS attrs = entry.getAttrs();
+                        return new BasicResource(path, true, attrs.getSize(), attrs.getMTime()
+                                * MILLIS_PER_SECOND, false);
+                    }
+                }
+            }
+        } catch (Exception e) {
+            Message.debug("Error while resolving resource " + path, e);
+            // silent fail, return unexisting resource
+        }
+
+        return new BasicResource(path, false, 0, 0, false);
+    }
+
+    public InputStream openStream(SFTPResource resource) throws IOException {
+        ChannelSftp c = getSftpChannel(resource.getName());
+        try {
+            String path = getPath(resource.getName());
+            return c.get(path);
+        } catch (SftpException e) {
+            IOException ex = new IOException("impossible to open stream for " + resource + " on "
+                    + getHost() + (e.getMessage() != null ? ": " + e.getMessage() : ""));
+            ex.initCause(e);
+            throw ex;
+        } catch (URISyntaxException e) {
+            IOException ex = new IOException("impossible to open stream for " + resource + " on "
+                    + getHost() + (e.getMessage() != null ? ": " + e.getMessage() : ""));
+            ex.initCause(e);
+            throw ex;
+        }
+    }
+
+    public void get(String source, File destination) throws IOException {
+        fireTransferInitiated(getResource(source), TransferEvent.REQUEST_GET);
+        ChannelSftp c = getSftpChannel(source);
+        try {
+            String path = getPath(source);
+            c.get(path, destination.getAbsolutePath(), new MyProgressMonitor());
+        } catch (SftpException e) {
+            IOException ex = new IOException("impossible to get " + source + " on " + getHost()
+                    + (e.getMessage() != null ? ": " + e.getMessage() : ""));
+            ex.initCause(e);
+            throw ex;
+        } catch (URISyntaxException e) {
+            IOException ex = new IOException("impossible to get " + source + " on " + getHost()
+                    + (e.getMessage() != null ? ": " + e.getMessage() : ""));
+            ex.initCause(e);
+            throw ex;
+        }
+    }
+
+    public void put(File source, String destination, boolean overwrite) throws IOException {
+        fireTransferInitiated(getResource(destination), TransferEvent.REQUEST_PUT);
+        ChannelSftp c = getSftpChannel(destination);
+        try {
+            String path = getPath(destination);
+            if (!overwrite && checkExistence(path, c)) {
+                throw new IOException("destination file exists and overwrite == false");
+            }
+            if (path.indexOf('/') != -1) {
+                mkdirs(path.substring(0, path.lastIndexOf('/')), c);
+            }
+            c.put(source.getAbsolutePath(), path, new MyProgressMonitor());
+        } catch (SftpException e) {
+            IOException ex = new IOException(e.getMessage());
+            ex.initCause(e);
+            throw ex;
+        } catch (URISyntaxException e) {
+            IOException ex = new IOException(e.getMessage());
+            ex.initCause(e);
+            throw ex;
+        }
+    }
+
+    private void mkdirs(String directory, ChannelSftp c) throws IOException, SftpException {
+        try {
+            SftpATTRS att = c.stat(directory);
+            if (att != null) {
+                if (att.isDir()) {
+                    return;
+                }
+            }
+        } catch (SftpException ex) {
+            if (directory.indexOf('/') != -1) {
+                mkdirs(directory.substring(0, directory.lastIndexOf('/')), c);
+            }
+            c.mkdir(directory);
+        }
+    }
+
+    private String getPath(String sftpURI) throws URISyntaxException {
+        String result = null;
+        URI uri = new URI(sftpURI);
+        result = uri.getPath();
+
+        if (result == null) {
+            throw new URISyntaxException(sftpURI, "Missing path in URI.");
+        }
+
+        return result;
+    }
+
+    public List list(String parent) throws IOException {
+        try {
+            ChannelSftp c = getSftpChannel(parent);
+            String path = getPath(parent);
+            Collection r = c.ls(path);
+            if (r != null) {
+                if (!path.endsWith("/")) {
+                    path = parent + "/";
+                }
+                List result = new ArrayList();
+                for (Iterator iter = r.iterator(); iter.hasNext();) {
+                    Object obj = iter.next();
+                    if (obj instanceof LsEntry) {
+                        LsEntry entry = (LsEntry) obj;
+                        if (".".equals(entry.getFilename()) || "..".equals(entry.getFilename())) {
+                            continue;
+                        }
+                        result.add(path + entry.getFilename());
+                    }
+                }
+                return result;
+            }
+        } catch (SftpException e) {
+            IOException ex = new IOException("Failed to return a listing for '" + parent + "'");
+            ex.initCause(e);
+            throw ex;
+        } catch (URISyntaxException usex) {
+            IOException ex = new IOException("Failed to return a listing for '" + parent + "'");
+            ex.initCause(usex);
+            throw ex;
+        }
+        return null;
+    }
+
+    /**
+     * Checks the existence for a remote file
+     * 
+     * @param file
+     *            to check
+     * @param channel
+     *            to use
+     * @returns true if file exists, false otherwise
+     * @throws IOException
+     * @throws SftpException
+     */
+    private boolean checkExistence(String file, ChannelSftp channel) throws IOException,
+            SftpException {
+        try {
+            return channel.stat(file) != null;
+        } catch (SftpException ex) {
+            return false;
+        }
+    }
+
+    /**
+     * Establish the connection to the server if not yet connected, and listen to ivy events for
+     * closing connection when resolve is finished. Not meant to be used in multi threaded
+     * environment.
+     * 
+     * @return the ChannelSftp with which a connection is established
+     * @throws IOException
+     *             if any connection problem occurs
+     */
+    private ChannelSftp getSftpChannel(String pathOrUri) throws IOException {
+        Session session = getSession(pathOrUri);
+        String host = session.getHost();
+        ChannelSftp channel = SshCache.getInstance().getChannelSftp(session);
+        if (channel == null) {
+            try {
+                channel = (ChannelSftp) session.openChannel("sftp");
+                channel.connect();
+                Message.verbose(":: SFTP :: connected to " + host + "!");
+                SshCache.getInstance().attachChannelSftp(session, channel);
+            } catch (JSchException e) {
+                IOException ex = new IOException(e.getMessage());
+                ex.initCause(e);
+                throw ex;
+            }
+        }
+        return channel;
+    }
+
+    protected String getRepositoryScheme() {
+        // use the Resolver type name here?
+        // would be nice if it would be static, so we could use SFTPResolver.getTypeName()
+        return "sftp";
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/repository/sftp/SFTPResource.java b/src/java/org/apache/ivy/plugins/repository/sftp/SFTPResource.java
new file mode 100644
index 0000000..db9447c
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/repository/sftp/SFTPResource.java
@@ -0,0 +1,87 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.repository.sftp;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.ivy.plugins.repository.Resource;
+
+public class SFTPResource implements Resource {
+    private SFTPRepository repository;
+
+    private String path;
+
+    private transient boolean init = false;
+
+    private transient boolean exists;
+
+    private transient long lastModified;
+
+    private transient long contentLength;
+
+    public SFTPResource(SFTPRepository repository, String path) {
+        this.repository = repository;
+        this.path = path;
+    }
+
+    public String getName() {
+        return path;
+    }
+
+    public Resource clone(String cloneName) {
+        return new SFTPResource(repository, cloneName);
+    }
+
+    public long getLastModified() {
+        init();
+        return lastModified;
+    }
+
+    public long getContentLength() {
+        init();
+        return contentLength;
+    }
+
+    public boolean exists() {
+        init();
+        return exists;
+    }
+
+    private void init() {
+        if (!init) {
+            Resource r = repository.resolveResource(path);
+            contentLength = r.getContentLength();
+            lastModified = r.getLastModified();
+            exists = r.exists();
+            init = true;
+        }
+    }
+
+    public String toString() {
+        return getName();
+    }
+
+    public boolean isLocal() {
+        return false;
+    }
+
+    public InputStream openStream() throws IOException {
+        return repository.openStream(this);
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/repository/ssh/AbstractSshBasedRepository.java b/src/java/org/apache/ivy/plugins/repository/ssh/AbstractSshBasedRepository.java
new file mode 100644
index 0000000..94b41df
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/repository/ssh/AbstractSshBasedRepository.java
@@ -0,0 +1,320 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.repository.ssh;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.HashMap;
+import java.util.Locale;
+
+import org.apache.ivy.plugins.repository.AbstractRepository;
+import org.apache.ivy.util.Credentials;
+import org.apache.ivy.util.CredentialsUtil;
+import org.apache.ivy.util.Message;
+
+import com.jcraft.jsch.Session;
+
+public abstract class AbstractSshBasedRepository extends AbstractRepository {
+
+    private File keyFile = null;
+
+    private File passFile = null;
+
+    private String userPassword = null;
+
+    private String keyFilePassword = null;
+
+    private String user = null;
+
+    private String host = null;
+
+    private int port = -1;
+
+    private boolean allowedAgentUse = false;
+
+    public AbstractSshBasedRepository() {
+        super();
+    }
+
+    /**
+     * hashmap of user/hosts with credentials. key is hostname, value is Credentials
+     **/
+    private static HashMap credentialsCache = new HashMap();
+
+    private static final int MAX_CREDENTILAS_CACHE_SIZE = 100;
+
+    /**
+     * get a new session using the default attributes if the given String is a full uri, use the
+     * data from the uri instead
+     * 
+     * @param pathOrUri
+     *            might be just a path or a full ssh or sftp uri
+     * @return matching Session
+     */
+    protected Session getSession(String pathOrUri) throws IOException {
+        URI uri = parseURI(pathOrUri);
+        String host = getHost();
+        int port = getPort();
+        String user = getUser();
+        String userPassword = getUserPassword();
+        if (uri != null && uri.getScheme() != null) {
+            if (uri.getHost() != null) {
+                host = uri.getHost();
+            }
+            if (uri.getPort() != -1) {
+                port = uri.getPort();
+            }
+            if (uri.getUserInfo() != null) {
+                String userInfo = uri.getUserInfo();
+                if (userInfo.indexOf(":") == -1) {
+                    user = userInfo;
+                } else {
+                    user = userInfo.substring(0, userInfo.indexOf(":"));
+                    userPassword = userInfo.substring(userInfo.indexOf(":") + 1);
+                }
+            }
+        }
+        if (host == null) {
+            throw new IllegalArgumentException(
+                    "missing host information. host should be provided either "
+                            + "directly on the repository or in the connection URI");
+        }
+        if (user == null) {
+            Credentials c = requestCredentials(host);
+            if (c != null) {
+                user = c.getUserName();
+                userPassword = c.getPasswd();
+            } else {
+                Message.error("username is not set");
+            }
+        }
+        return SshCache.getInstance().getSession(host, port, user, userPassword, getKeyFile(),
+            getKeyFilePassword(), getPassFile(), isAllowedAgentUse());
+    }
+
+    /**
+     * Just check the uri for sanity
+     * 
+     * @param source
+     *            String of the uri
+     * @return URI object of the String or null
+     */
+    private URI parseURI(String source) {
+        try {
+            URI uri = new URI(source);
+            if (uri.getScheme() != null
+                    && !uri.getScheme().toLowerCase(Locale.US)
+                            .equals(getRepositoryScheme().toLowerCase(Locale.US))) {
+                throw new URISyntaxException(source, "Wrong scheme in URI. Expected "
+                        + getRepositoryScheme() + " as scheme!");
+            }
+            if (uri.getHost() == null && getHost() == null) {
+                throw new URISyntaxException(source, "Missing host in URI or in resolver");
+            }
+            if (uri.getPath() == null) {
+                throw new URISyntaxException(source, "Missing path in URI");
+            }
+            // if (uri.getUserInfo() == null && getUser() == null) {
+            // throw new URISyntaxException(source, "Missing username in URI or in resolver");
+            // }
+            return uri;
+        } catch (URISyntaxException e) {
+            Message.error(e.getMessage());
+            Message.error("The uri '" + source + "' is in the wrong format.");
+            Message.error("Please use " + getRepositoryScheme()
+                    + "://user:pass at hostname/path/to/repository");
+            return null;
+        }
+    }
+
+    /**
+     * Called, when user was not found in URL. Maintain static hashe of credentials and retrieve or
+     * ask credentials for host.
+     * 
+     * @param host
+     *            host for which we want to get credentials.
+     * @return credentials for given host
+     **/
+    private Credentials requestCredentials(String host) {
+        Object o = credentialsCache.get(host);
+        if (o == null) {
+            Credentials c = CredentialsUtil.promptCredentials(new Credentials(null, host, user,
+                    userPassword), getPassFile());
+            if (c != null) {
+                if (credentialsCache.size() > MAX_CREDENTILAS_CACHE_SIZE) {
+                    credentialsCache.clear();
+                }
+                credentialsCache.put(host, c);
+            }
+            return c;
+        } else {
+            return (Credentials) o;
+        }
+    }
+
+    /**
+     * closes the session and remove it from the cache (eg. on case of errors)
+     * 
+     * @param session
+     *            key for the cache
+     * @param pathOrUri
+     *            to release
+     */
+    protected void releaseSession(Session session, String pathOrUri) {
+        session.disconnect();
+        SshCache.getInstance().clearSession(session);
+    }
+
+    /**
+     * set the default user to use for the connection if no user is given or a PEM file is used
+     * 
+     * @param user
+     *            to use
+     */
+    public void setUser(String user) {
+        this.user = user;
+    }
+
+    /**
+     * @return the user to use for the connection if no user is given or a PEM file is used
+     */
+    public String getUser() {
+        return user;
+    }
+
+    /**
+     * Sets the full file path to use for accessing a PEM key file
+     * 
+     * @param filePath
+     *            fully qualified name
+     */
+    public void setKeyFile(File filePath) {
+        this.keyFile = filePath;
+        if (!keyFile.exists()) {
+            Message.warn("Pemfile " + keyFile.getAbsolutePath() + " doesn't exist.");
+            keyFile = null;
+        } else if (!keyFile.canRead()) {
+            Message.warn("Pemfile " + keyFile.getAbsolutePath() + " not readable.");
+            keyFile = null;
+        } else {
+            Message.debug("Using " + keyFile.getAbsolutePath() + " as keyfile.");
+        }
+    }
+
+    /**
+     * @return the keyFile
+     */
+    public File getKeyFile() {
+        return keyFile;
+    }
+
+    /**
+     * @param password
+     *            password to use for user/password authentication
+     */
+    public void setUserPassword(String password) {
+        this.userPassword = password;
+    }
+
+    /**
+     * @return the keyFile password for public key based authentication
+     */
+    public String getKeyFilePassword() {
+        return keyFilePassword;
+    }
+
+    /**
+     * @param keyFilePassword
+     *            sets password for public key based authentication
+     */
+    public void setKeyFilePassword(String keyFilePassword) {
+        this.keyFilePassword = keyFilePassword;
+    }
+
+    /**
+     * @return the user password
+     */
+    public String getUserPassword() {
+        return userPassword;
+    }
+
+    /**
+     * @return the host
+     */
+    public String getHost() {
+        return host;
+    }
+
+    /**
+     * @param host
+     *            the host to set
+     */
+    public void setHost(String host) {
+        this.host = host;
+    }
+
+    /**
+     * @return the port
+     */
+    public int getPort() {
+        return port;
+    }
+
+    /**
+     * @param port
+     *            the port to set
+     */
+    public void setPort(int port) {
+        this.port = port;
+    }
+
+    /**
+     * @param passFile
+     *            the passfile to set
+     */
+    public void setPassFile(File passFile) {
+        this.passFile = passFile;
+    }
+
+    /**
+     * @return the passFile
+     */
+    public File getPassFile() {
+        return passFile;
+    }
+
+    /**
+     * @return allowedAgentUse Whether use of a local SSH agent for authentication is allowed
+     */
+    public boolean isAllowedAgentUse() {
+        return allowedAgentUse;
+    }
+
+    /**
+     * @param allowedAgentUse
+     *            Whether use of a local SSH agent for authentication is allowed
+     */
+    public void setAllowedAgentUse(boolean allowedAgentUse) {
+        this.allowedAgentUse = allowedAgentUse;
+    }
+
+    protected abstract String getRepositoryScheme();
+
+}
diff --git a/src/java/org/apache/ivy/plugins/repository/ssh/RemoteScpException.java b/src/java/org/apache/ivy/plugins/repository/ssh/RemoteScpException.java
new file mode 100644
index 0000000..bf09516
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/repository/ssh/RemoteScpException.java
@@ -0,0 +1,53 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.repository.ssh;
+
+/**
+ * This exception will be used for Remote SCP Exceptions (failures on the target system, no
+ * connetion probs)
+ */
+public class RemoteScpException extends Exception {
+
+    private static final long serialVersionUID = 3107198655563736600L;
+
+    public RemoteScpException() {
+    }
+
+    /**
+     * @param message
+     */
+    public RemoteScpException(String message) {
+        super(message);
+    }
+
+    /**
+     * @param cause
+     */
+    public RemoteScpException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * @param message
+     * @param cause
+     */
+    public RemoteScpException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+}
diff --git a/src/java/org/apache/ivy/plugins/repository/ssh/Scp.java b/src/java/org/apache/ivy/plugins/repository/ssh/Scp.java
new file mode 100644
index 0000000..ab53876
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/repository/ssh/Scp.java
@@ -0,0 +1,611 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.repository.ssh;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import com.jcraft.jsch.Channel;
+import com.jcraft.jsch.ChannelExec;
+import com.jcraft.jsch.JSchException;
+import com.jcraft.jsch.Session;
+
+/**
+ * This class is using the scp client to transfer data and information for the repository.
+ * <p>
+ * It is based on the SCPClient from the ganymed ssh library from Christian Plattner, released under
+ * a BSD style license.
+ * <p>
+ * To minimize the dependency to the ssh library and because we needed some additional
+ * functionality, we decided to copy'n'paste the single class rather than to inherit or delegate it
+ * somehow.
+ * <p>
+ * Nevertheless credit should go to the original author.
+ */
+public class Scp {
+    private static final int MODE_LENGTH = 4;
+
+    private static final int SEND_FILE_BUFFER_LENGTH = 40000;
+
+    private static final int SEND_BYTES_BUFFER_LENGTH = 512;
+
+    private static final int MIN_TLINE_LENGTH = 8;
+
+    private static final int CLINE_SPACE_INDEX2 = 5;
+
+    private static final int CLINE_SPACE_INDEX1 = 4;
+
+    private static final int MIN_C_LINE_LENGTH = 8;
+
+    private static final int DEFAULT_LINE_BUFFER_LENGTH = 30;
+
+    private static final int BUFFER_SIZE = 64 * 1024;
+
+    /*
+     * Maximum length authorized for scp lines. This is a random limit - if your path names are
+     * longer, then adjust it.
+     */
+    private static final int MAX_SCP_LINE_LENGTH = 8192;
+
+    private Session session;
+
+    public class FileInfo {
+        private String filename;
+
+        private long length;
+
+        private long lastModified;
+
+        /**
+         * @param filename
+         *            The filename to set.
+         */
+        public void setFilename(String filename) {
+            this.filename = filename;
+        }
+
+        /**
+         * @return Returns the filename.
+         */
+        public String getFilename() {
+            return filename;
+        }
+
+        /**
+         * @param length
+         *            The length to set.
+         */
+        public void setLength(long length) {
+            this.length = length;
+        }
+
+        /**
+         * @return Returns the length.
+         */
+        public long getLength() {
+            return length;
+        }
+
+        /**
+         * @param lastModified
+         *            The lastModified to set.
+         */
+        public void setLastModified(long lastModified) {
+            this.lastModified = lastModified;
+        }
+
+        /**
+         * @return Returns the lastModified.
+         */
+        public long getLastModified() {
+            return lastModified;
+        }
+    }
+
+    public Scp(Session session) {
+        if (session == null) {
+            throw new IllegalArgumentException("Cannot accept null argument!");
+        }
+        this.session = session;
+    }
+
+    private void readResponse(InputStream is) throws IOException, RemoteScpException {
+        int c = is.read();
+
+        if (c == 0) {
+            return;
+        }
+
+        if (c == -1) {
+            throw new RemoteScpException("Remote scp terminated unexpectedly.");
+        }
+
+        if ((c != 1) && (c != 2)) {
+            throw new RemoteScpException("Remote scp sent illegal error code.");
+        }
+
+        if (c == 2) {
+            throw new RemoteScpException("Remote scp terminated with error.");
+        }
+
+        String err = receiveLine(is);
+        throw new RemoteScpException("Remote scp terminated with error (" + err + ").");
+    }
+
+    private String receiveLine(InputStream is) throws IOException, RemoteScpException {
+        StringBuffer sb = new StringBuffer(DEFAULT_LINE_BUFFER_LENGTH);
+
+        while (true) {
+
+            if (sb.length() > MAX_SCP_LINE_LENGTH) {
+                throw new RemoteScpException("Remote scp sent a too long line");
+            }
+
+            int c = is.read();
+
+            if (c < 0) {
+                throw new RemoteScpException("Remote scp terminated unexpectedly.");
+            }
+
+            if (c == '\n') {
+                break;
+            }
+
+            sb.append((char) c);
+
+        }
+        return sb.toString();
+    }
+
+    private void parseCLine(String line, FileInfo fileInfo) throws RemoteScpException {
+        /* Minimum line: "xxxx y z" ---> 8 chars */
+
+        long len;
+
+        if (line.length() < MIN_C_LINE_LENGTH) {
+            throw new RemoteScpException(
+                    "Malformed C line sent by remote SCP binary, line too short.");
+        }
+
+        if ((line.charAt(CLINE_SPACE_INDEX1) != ' ') || (line.charAt(CLINE_SPACE_INDEX2) == ' ')) {
+            throw new RemoteScpException("Malformed C line sent by remote SCP binary.");
+        }
+
+        int lengthNameSep = line.indexOf(' ', CLINE_SPACE_INDEX2);
+
+        if (lengthNameSep == -1) {
+            throw new RemoteScpException("Malformed C line sent by remote SCP binary.");
+        }
+
+        String lengthSubstring = line.substring(CLINE_SPACE_INDEX2, lengthNameSep);
+        String nameSubstring = line.substring(lengthNameSep + 1);
+
+        if ((lengthSubstring.length() <= 0) || (nameSubstring.length() <= 0)) {
+            throw new RemoteScpException("Malformed C line sent by remote SCP binary.");
+        }
+
+        if ((CLINE_SPACE_INDEX2 + 1 + lengthSubstring.length() + nameSubstring.length()) != line
+                .length()) {
+            throw new RemoteScpException("Malformed C line sent by remote SCP binary.");
+        }
+
+        try {
+            len = Long.parseLong(lengthSubstring);
+        } catch (NumberFormatException e) {
+            throw new RemoteScpException(
+                    "Malformed C line sent by remote SCP binary, cannot parse file length.");
+        }
+
+        if (len < 0) {
+            throw new RemoteScpException(
+                    "Malformed C line sent by remote SCP binary, illegal file length.");
+        }
+
+        fileInfo.setLength(len);
+        fileInfo.setFilename(nameSubstring);
+    }
+
+    private void parseTLine(String line, FileInfo fileInfo) throws RemoteScpException {
+        /* Minimum line: "0 0 0 0" ---> 8 chars */
+
+        long modtime;
+        long firstMsec;
+        long atime;
+        long secondMsec;
+
+        if (line.length() < MIN_TLINE_LENGTH) {
+            throw new RemoteScpException(
+                    "Malformed T line sent by remote SCP binary, line too short.");
+        }
+
+        int firstMsecBegin = line.indexOf(" ") + 1;
+        if (firstMsecBegin == 0 || firstMsecBegin >= line.length()) {
+            throw new RemoteScpException(
+                    "Malformed T line sent by remote SCP binary, line not enough data.");
+        }
+
+        int atimeBegin = line.indexOf(" ", firstMsecBegin + 1) + 1;
+        if (atimeBegin == 0 || atimeBegin >= line.length()) {
+            throw new RemoteScpException(
+                    "Malformed T line sent by remote SCP binary, line not enough data.");
+        }
+
+        int secondMsecBegin = line.indexOf(" ", atimeBegin + 1) + 1;
+        if (secondMsecBegin == 0 || secondMsecBegin >= line.length()) {
+            throw new RemoteScpException(
+                    "Malformed T line sent by remote SCP binary, line not enough data.");
+        }
+
+        try {
+            modtime = Long.parseLong(line.substring(0, firstMsecBegin - 1));
+            firstMsec = Long.parseLong(line.substring(firstMsecBegin, atimeBegin - 1));
+            atime = Long.parseLong(line.substring(atimeBegin, secondMsecBegin - 1));
+            secondMsec = Long.parseLong(line.substring(secondMsecBegin));
+        } catch (NumberFormatException e) {
+            throw new RemoteScpException(
+                    "Malformed C line sent by remote SCP binary, cannot parse file length.");
+        }
+
+        if (modtime < 0 || firstMsec < 0 || atime < 0 || secondMsec < 0) {
+            throw new RemoteScpException(
+                    "Malformed C line sent by remote SCP binary, illegal file length.");
+        }
+
+        fileInfo.setLastModified(modtime);
+    }
+
+    private void sendFile(Channel channel, String localFile, String remoteName, String mode)
+            throws IOException, RemoteScpException {
+        byte[] buffer = new byte[BUFFER_SIZE];
+
+        OutputStream os = new BufferedOutputStream(channel.getOutputStream(),
+                SEND_FILE_BUFFER_LENGTH);
+        InputStream is = new BufferedInputStream(channel.getInputStream(), SEND_BYTES_BUFFER_LENGTH);
+
+        try {
+            if (channel.isConnected()) {
+                channel.start();
+            } else {
+                channel.connect();
+            }
+        } catch (JSchException e1) {
+            throw (IOException) new IOException("Channel connection problems").initCause(e1);
+        }
+
+        readResponse(is);
+
+        File f = new File(localFile);
+        long remain = f.length();
+
+        String cMode = mode;
+        if (cMode == null) {
+            cMode = "0600";
+        }
+        String cline = "C" + cMode + " " + remain + " " + remoteName + "\n";
+
+        os.write(cline.getBytes());
+        os.flush();
+
+        readResponse(is);
+
+        FileInputStream fis = null;
+
+        try {
+            fis = new FileInputStream(f);
+
+            while (remain > 0) {
+                int trans;
+                if (remain > buffer.length) {
+                    trans = buffer.length;
+                } else {
+                    trans = (int) remain;
+                }
+                if (fis.read(buffer, 0, trans) != trans) {
+                    throw new IOException("Cannot read enough from local file " + localFile);
+                }
+
+                os.write(buffer, 0, trans);
+
+                remain -= trans;
+            }
+
+            fis.close();
+        } catch (IOException e) {
+            if (fis != null) {
+                fis.close();
+            }
+            throw (e);
+        }
+
+        os.write(0);
+        os.flush();
+
+        readResponse(is);
+
+        os.write("E\n".getBytes());
+        os.flush();
+    }
+
+    /**
+     * Receive a file via scp and store it in a stream
+     * 
+     * @param channel
+     *            ssh channel to use
+     * @param file
+     *            to receive from remote
+     * @param target
+     *            to store file into (if null, get only file info)
+     * @return file information of the file we received
+     * @throws IOException
+     *             in case of network or protocol trouble
+     * @throws RemoteScpException
+     *             in case of problems on the target system (connection is fine)
+     */
+    private FileInfo receiveStream(Channel channel, String file, OutputStream targetStream)
+            throws IOException, RemoteScpException {
+        byte[] buffer = new byte[BUFFER_SIZE];
+
+        OutputStream os = channel.getOutputStream();
+        InputStream is = channel.getInputStream();
+        try {
+            if (channel.isConnected()) {
+                channel.start();
+            } else {
+                channel.connect();
+            }
+        } catch (JSchException e1) {
+            throw (IOException) new IOException("Channel connection problems").initCause(e1);
+        }
+        os.write(0x0);
+        os.flush();
+
+        FileInfo fileInfo = new FileInfo();
+
+        while (true) {
+            int c = is.read();
+            if (c < 0) {
+                throw new RemoteScpException("Remote scp terminated unexpectedly.");
+            }
+
+            String line = receiveLine(is);
+
+            if (c == 'T') {
+                parseTLine(line, fileInfo);
+                os.write(0x0);
+                os.flush();
+                continue;
+            }
+            if ((c == 1) || (c == 2)) {
+                throw new RemoteScpException("Remote SCP error: " + line);
+            }
+
+            if (c == 'C') {
+                parseCLine(line, fileInfo);
+                break;
+            }
+            throw new RemoteScpException("Remote SCP error: " + ((char) c) + line);
+        }
+        if (targetStream != null) {
+
+            os.write(0x0);
+            os.flush();
+
+            try {
+                long remain = fileInfo.getLength();
+
+                while (remain > 0) {
+                    int trans;
+                    if (remain > buffer.length) {
+                        trans = buffer.length;
+                    } else {
+                        trans = (int) remain;
+                    }
+
+                    int thisTimeReceived = is.read(buffer, 0, trans);
+
+                    if (thisTimeReceived < 0) {
+                        throw new IOException("Remote scp terminated connection unexpectedly");
+                    }
+
+                    targetStream.write(buffer, 0, thisTimeReceived);
+
+                    remain -= thisTimeReceived;
+                }
+
+                targetStream.close();
+            } catch (IOException e) {
+                if (targetStream != null) {
+                    targetStream.close();
+                }
+                throw (e);
+            }
+
+            readResponse(is);
+
+            os.write(0x0);
+            os.flush();
+        }
+        return fileInfo;
+    }
+
+    /**
+     * @return
+     * @throws JSchException
+     */
+    private ChannelExec getExecChannel() throws JSchException {
+        ChannelExec channel;
+        channel = (ChannelExec) session.openChannel("exec");
+        return channel;
+    }
+
+    /**
+     * Copy a local file to a remote site, uses the specified mode when creating the file on the
+     * remote side.
+     * 
+     * @param localFile
+     *            Path and name of local file. Must be absolute.
+     * @param remoteTargetDir
+     *            Remote target directory where the file has to end up (optional)
+     * @param remoteTargetName
+     *            file name to use on the target system
+     * @param mode
+     *            a four digit string (e.g., 0644, see "man chmod", "man open")
+     * @throws IOException
+     *             in case of network problems
+     * @throws RemoteScpException
+     *             in case of problems on the target system (connection ok)
+     */
+    public void put(String localFile, String remoteTargetDir, String remoteTargetName, String mode)
+            throws IOException, RemoteScpException {
+        ChannelExec channel = null;
+
+        if ((localFile == null) || (remoteTargetName == null)) {
+            throw new IllegalArgumentException("Null argument.");
+        }
+
+        if (mode != null) {
+            if (mode.length() != MODE_LENGTH) {
+                throw new IllegalArgumentException("Invalid mode.");
+            }
+
+            for (int i = 0; i < mode.length(); i++) {
+                if (!Character.isDigit(mode.charAt(i))) {
+                    throw new IllegalArgumentException("Invalid mode.");
+                }
+            }
+        }
+
+        String cmd = "scp -t ";
+        if (mode != null) {
+            cmd = cmd + "-p ";
+        }
+        if (remoteTargetDir != null && remoteTargetDir.length() > 0) {
+            cmd = cmd + "-d " + remoteTargetDir;
+        }
+
+        try {
+            channel = getExecChannel();
+            channel.setCommand(cmd);
+            sendFile(channel, localFile, remoteTargetName, mode);
+            channel.disconnect();
+        } catch (JSchException e) {
+            if (channel != null) {
+                channel.disconnect();
+            }
+            throw (IOException) new IOException("Error during SCP transfer." + e.getMessage())
+                    .initCause(e);
+        }
+    }
+
+    /**
+     * Download a file from the remote server to a local file.
+     * 
+     * @param remoteFile
+     *            Path and name of the remote file.
+     * @param localTarget
+     *            Local file where to store the data. Must be absolute.
+     * @throws IOException
+     *             in case of network problems
+     * @throws RemoteScpException
+     *             in case of problems on the target system (connection ok)
+     */
+    public void get(String remoteFile, String localTarget) throws IOException, RemoteScpException {
+        File f = new File(localTarget);
+        FileOutputStream fop = new FileOutputStream(f);
+        get(remoteFile, fop);
+    }
+
+    /**
+     * Download a file from the remote server into an OutputStream
+     * 
+     * @param remoteFile
+     *            Path and name of the remote file.
+     * @param localTarget
+     *            OutputStream to store the data.
+     * @throws IOException
+     *             in case of network problems
+     * @throws RemoteScpException
+     *             in case of problems on the target system (connection ok)
+     */
+    public void get(String remoteFile, OutputStream localTarget) throws IOException,
+            RemoteScpException {
+        ChannelExec channel = null;
+
+        if ((remoteFile == null) || (localTarget == null)) {
+            throw new IllegalArgumentException("Null argument.");
+        }
+
+        String cmd = "scp -p -f " + remoteFile;
+
+        try {
+            channel = getExecChannel();
+            channel.setCommand(cmd);
+            receiveStream(channel, remoteFile, localTarget);
+            channel.disconnect();
+        } catch (JSchException e) {
+            if (channel != null) {
+                channel.disconnect();
+            }
+            throw (IOException) new IOException("Error during SCP transfer." + e.getMessage())
+                    .initCause(e);
+        }
+    }
+
+    /**
+     * Initiates an SCP sequence but stops after getting fileinformation header
+     * 
+     * @param remoteFile
+     *            to get information for
+     * @return the file information got
+     * @throws IOException
+     *             in case of network problems
+     * @throws RemoteScpException
+     *             in case of problems on the target system (connection ok)
+     */
+    public FileInfo getFileinfo(String remoteFile) throws IOException, RemoteScpException {
+        ChannelExec channel = null;
+        FileInfo fileInfo = null;
+
+        if (remoteFile == null) {
+            throw new IllegalArgumentException("Null argument.");
+        }
+
+        String cmd = "scp -p -f \"" + remoteFile + "\"";
+
+        try {
+            channel = getExecChannel();
+            channel.setCommand(cmd);
+            fileInfo = receiveStream(channel, remoteFile, null);
+            channel.disconnect();
+        } catch (JSchException e) {
+            throw (IOException) new IOException("Error during SCP transfer." + e.getMessage())
+                    .initCause(e);
+        } finally {
+            if (channel != null) {
+                channel.disconnect();
+            }
+        }
+        return fileInfo;
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/repository/ssh/SshCache.java b/src/java/org/apache/ivy/plugins/repository/ssh/SshCache.java
new file mode 100644
index 0000000..095ec61
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/repository/ssh/SshCache.java
@@ -0,0 +1,453 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.repository.ssh;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.ivy.core.IvyContext;
+import org.apache.ivy.core.event.IvyEvent;
+import org.apache.ivy.core.event.IvyListener;
+import org.apache.ivy.core.event.resolve.EndResolveEvent;
+import org.apache.ivy.util.Checks;
+import org.apache.ivy.util.Credentials;
+import org.apache.ivy.util.CredentialsUtil;
+import org.apache.ivy.util.Message;
+
+import com.jcraft.jsch.ChannelSftp;
+import com.jcraft.jsch.JSch;
+import com.jcraft.jsch.JSchException;
+import com.jcraft.jsch.Session;
+import com.jcraft.jsch.UIKeyboardInteractive;
+import com.jcraft.jsch.UserInfo;
+import com.jcraft.jsch.agentproxy.Connector;
+import com.jcraft.jsch.agentproxy.ConnectorFactory;
+import com.jcraft.jsch.agentproxy.RemoteIdentityRepository;
+
+/**
+ * a class to cache SSH Connections and Channel for the SSH Repository each session is defined by
+ * connecting user / host / port two maps are used to find cache entries one map is using the above
+ * keys, the other uses the session itself
+ */
+public final class SshCache {
+
+    private static final int SSH_DEFAULT_PORT = 22;
+
+    private SshCache() {
+    };
+
+    private static SshCache instance = new SshCache();
+
+    public static SshCache getInstance() {
+        return instance;
+    }
+
+    private class Entry {
+        private Session session = null;
+
+        private ChannelSftp channelSftp = null;
+
+        private String host = null;
+
+        private String user = null;
+
+        private int port = SSH_DEFAULT_PORT;
+
+        /**
+         * @return the host
+         */
+        public String getHost() {
+            return host;
+        }
+
+        /**
+         * @return the port
+         */
+        public int getPort() {
+            return port;
+        }
+
+        /**
+         * @return the user
+         */
+        public String getUser() {
+            return user;
+        }
+
+        public Entry(Session newSession, String newUser, String newHost, int newPort) {
+            session = newSession;
+            host = newHost;
+            user = newUser;
+            port = newPort;
+            IvyContext.getContext().getEventManager().addIvyListener(new IvyListener() {
+                public void progress(IvyEvent event) {
+                    event.getSource().removeIvyListener(this);
+                    clearSession(session);
+                }
+            }, EndResolveEvent.NAME);
+        }
+
+        /**
+         * attach an sftp channel to this cache entry
+         * 
+         * @param newChannel
+         *            to attach
+         */
+        public void setChannelSftp(ChannelSftp newChannel) {
+            if (channelSftp != null && newChannel != null) {
+                throw new IllegalStateException("Only one sftp channelSftp per session allowed");
+            }
+            this.channelSftp = newChannel;
+        }
+
+        /**
+         * @return the attached sftp channel
+         */
+        public ChannelSftp getChannelSftp() {
+            return channelSftp;
+        }
+
+        /**
+         * @return the session
+         */
+        private Session getSession() {
+            return session;
+        }
+
+        /**
+         * remove channelSftp and disconnect if necessary
+         */
+        public void releaseChannelSftp() {
+            if (channelSftp != null) {
+                if (channelSftp.isConnected()) {
+                    Message.verbose(":: SFTP :: closing sftp connection from " + host + "...");
+                    channelSftp.disconnect();
+                    channelSftp = null;
+                    Message.verbose(":: SFTP :: sftp connection closed from " + host);
+                }
+            }
+        }
+    }
+
+    /**
+     * key is username / host / port
+     * 
+     * @see SshCache.createCacheKey() for details
+     */
+    private Map uriCacheMap = new HashMap();
+
+    /**
+     * key is the session itself
+     */
+    private Map sessionCacheMap = new HashMap();
+
+    /**
+     * retrieves a session entry for a given hostname from the cache
+     * 
+     * @param hostname
+     *            to retrieve session for
+     * @return null or the existing entry
+     */
+    private Entry getCacheEntry(String user, String host, int port) {
+        return (Entry) uriCacheMap.get(createCacheKey(user, host, port));
+    }
+
+    /**
+     * Creates a cobined cache key from the given key parts
+     * 
+     * @param user
+     *            name of the user
+     * @param host
+     *            of the connection
+     * @param port
+     *            of the connection
+     * @return key for the cache
+     */
+    private static String createCacheKey(String user, String host, int port) {
+        String portToUse = "22";
+        if (port != -1 && port != SSH_DEFAULT_PORT) {
+            portToUse = Integer.toString(port);
+        }
+        return user.toLowerCase(Locale.US).trim() + "@" + host.toLowerCase(Locale.US).trim() + ":"
+                + portToUse;
+    }
+
+    /**
+     * retrieves a session entry for a given session from the cache
+     * 
+     * @param session
+     *            to retrieve cache entry for
+     * @return null or the existing entry
+     */
+    private Entry getCacheEntry(Session session) {
+        return (Entry) sessionCacheMap.get(session);
+    }
+
+    /**
+     * Sets a session to a given combined key into the cache If an old session object already
+     * exists, close and remove it
+     * 
+     * @param user
+     *            of the session
+     * @param host
+     *            of the session
+     * @param port
+     *            of the session
+     * @param newSession
+     *            Session to save
+     */
+    private void setSession(String user, String host, int port, Session newSession) {
+        Entry entry = (Entry) uriCacheMap.get(createCacheKey(user, host, port));
+        Session oldSession = null;
+        if (entry != null) {
+            oldSession = entry.getSession();
+        }
+        if (oldSession != null && !oldSession.equals(newSession) && oldSession.isConnected()) {
+            entry.releaseChannelSftp();
+            String oldhost = oldSession.getHost();
+            Message.verbose(":: SSH :: closing ssh connection from " + oldhost + "...");
+            oldSession.disconnect();
+            Message.verbose(":: SSH :: ssh connection closed from " + oldhost);
+        }
+        if ((newSession == null) && (entry != null)) {
+            uriCacheMap.remove(createCacheKey(user, host, port));
+            if (entry.getSession() != null) {
+                sessionCacheMap.remove(entry.getSession());
+            }
+        } else {
+            Entry newEntry = new Entry(newSession, user, host, port);
+            uriCacheMap.put(createCacheKey(user, host, port), newEntry);
+            sessionCacheMap.put(newSession, newEntry);
+        }
+    }
+
+    /**
+     * discardes session entries from the cache
+     * 
+     * @param session
+     *            to clear
+     */
+    public void clearSession(Session session) {
+        Entry entry = (Entry) sessionCacheMap.get(session);
+        if (entry != null) {
+            setSession(entry.getUser(), entry.getHost(), entry.getPort(), null);
+        }
+    }
+
+    /**
+     * retrieves an sftp channel from the cache
+     * 
+     * @param session
+     *            to connect to
+     * @return channelSftp or null if not successful (channel not existent or dead)
+     */
+    public ChannelSftp getChannelSftp(Session session) throws IOException {
+        ChannelSftp channel = null;
+        Entry entry = getCacheEntry(session);
+        if (entry != null) {
+            channel = entry.getChannelSftp();
+            if (channel != null && !channel.isConnected()) {
+                entry.releaseChannelSftp();
+                channel = null;
+            }
+        }
+        return channel;
+    }
+
+    /**
+     * attaches a channelSftp to an existing session cache entry
+     * 
+     * @param session
+     *            to attach the channel to
+     * @param channel
+     *            channel to attach
+     */
+    public void attachChannelSftp(Session session, ChannelSftp channel) {
+        Entry entry = getCacheEntry(session);
+        if (entry == null) {
+            throw new IllegalArgumentException("No entry for " + session + " in the cache");
+        }
+        entry.setChannelSftp(channel);
+    }
+
+    /**
+     * Attempts to connect to a local SSH agent (using either UNIX sockets or PuTTY's Pageant)
+     * 
+     * @param jsch
+     *            Connection to be attached to an available local agent
+     * @return true if connected to agent, false otherwise
+     */
+    private boolean attemptAgentUse(JSch jsch) {
+        try {
+            Connector con = ConnectorFactory.getDefault().createConnector();
+            jsch.setIdentityRepository(new RemoteIdentityRepository(con));
+            return true;
+        } catch (Exception e) {
+            Message.verbose(":: SSH :: Failure connecting to agent :: " + e.toString());
+            return false;
+        }
+    }
+
+    /**
+     * Gets a session from the cache or establishes a new session if necessary
+     * 
+     * @param host
+     *            to connect to
+     * @param port
+     *            to use for session (-1 == use standard port)
+     * @param username
+     *            for the session to use
+     * @param userPassword
+     *            to use for authentication (optional)
+     * @param pemFile
+     *            File to use for public key authentication
+     * @param pemPassword
+     *            to use for accessing the pemFile (optional)
+     * @param passFile
+     *            to store credentials
+     * @param allowedAgentUse
+     *            Whether to communicate with an agent for authentication
+     * @return session or null if not successful
+     */
+    public Session getSession(String host, int port, String username, String userPassword,
+            File pemFile, String pemPassword, File passFile, boolean allowedAgentUse)
+            throws IOException {
+        Checks.checkNotNull(host, "host");
+        Checks.checkNotNull(username, "user");
+        Entry entry = getCacheEntry(username, host, port);
+        Session session = null;
+        if (entry != null) {
+            session = entry.getSession();
+        }
+        if (session == null || !session.isConnected()) {
+            Message.verbose(":: SSH :: connecting to " + host + "...");
+            try {
+                JSch jsch = new JSch();
+                if (port != -1) {
+                    session = jsch.getSession(username, host, port);
+                } else {
+                    session = jsch.getSession(username, host);
+                }
+                if (allowedAgentUse) {
+                    attemptAgentUse(jsch);
+                }
+                if (pemFile != null) {
+                    jsch.addIdentity(pemFile.getAbsolutePath(), pemPassword);
+                }
+                session.setUserInfo(new CfUserInfo(host, username, userPassword, pemFile,
+                        pemPassword, passFile));
+                session.setDaemonThread(true);
+
+                Properties config = new Properties();
+                config.setProperty("PreferredAuthentications",
+                    "publickey,keyboard-interactive,password");
+                session.setConfig(config);
+
+                session.connect();
+                Message.verbose(":: SSH :: connected to " + host + "!");
+                setSession(username, host, port, session);
+            } catch (JSchException e) {
+                if (passFile != null && passFile.exists()) {
+                    passFile.delete();
+                }
+                IOException ex = new IOException(e.getMessage());
+                ex.initCause(e);
+                throw ex;
+            }
+        }
+        return session;
+    }
+
+    /**
+     * feeds in password silently into JSch
+     */
+    private static class CfUserInfo implements UserInfo, UIKeyboardInteractive {
+
+        private String userPassword;
+
+        private String pemPassword;
+
+        private String userName;
+
+        private final File pemFile;
+
+        private final String host;
+
+        private final File passfile;
+
+        public CfUserInfo(String host, String userName, String userPassword, File pemFile,
+                String pemPassword, File passfile) {
+            this.userPassword = userPassword;
+            this.pemPassword = pemPassword;
+            this.host = host;
+            this.passfile = passfile;
+            this.userName = userName;
+            this.pemFile = pemFile;
+        }
+
+        public void showMessage(String message) {
+            Message.info(message);
+        }
+
+        public boolean promptYesNo(String message) {
+            return true;
+        }
+
+        public boolean promptPassword(String message) {
+            return true;
+        }
+
+        public boolean promptPassphrase(String message) {
+            return true;
+        }
+
+        public String getPassword() {
+            if (userPassword == null) {
+                Credentials c = CredentialsUtil.promptCredentials(new Credentials(null, host,
+                        userName, userPassword), passfile);
+                if (c != null) {
+                    userName = c.getUserName();
+                    userPassword = c.getPasswd();
+                }
+            }
+            return userPassword;
+        }
+
+        public String getPassphrase() {
+            if (pemPassword == null && pemFile != null) {
+                Credentials c = CredentialsUtil.promptCredentials(
+                    new Credentials(null, pemFile.getAbsolutePath(), userName, pemPassword),
+                    passfile);
+                if (c != null) {
+                    userName = c.getUserName();
+                    pemPassword = c.getPasswd();
+                }
+            }
+            return pemPassword;
+        }
+
+        public String[] promptKeyboardInteractive(String destination, String name,
+                String instruction, String[] prompt, boolean[] echo) {
+            return new String[] {getPassword()};
+        }
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/repository/ssh/SshRepository.java b/src/java/org/apache/ivy/plugins/repository/ssh/SshRepository.java
new file mode 100644
index 0000000..ec5240c
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/repository/ssh/SshRepository.java
@@ -0,0 +1,473 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.repository.ssh;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringReader;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.util.Message;
+
+import com.jcraft.jsch.ChannelExec;
+import com.jcraft.jsch.JSchException;
+import com.jcraft.jsch.Session;
+
+/**
+ * Ivy Repository based on SSH
+ */
+public class SshRepository extends AbstractSshBasedRepository {
+
+    private static final int BUFFER_SIZE = 64 * 1024;
+
+    private static final String ARGUMENT_PLACEHOLDER = "%arg";
+
+    private static final int POLL_SLEEP_TIME = 500;
+
+    private char fileSeparator = '/';
+
+    private String listCommand = "ls -1";
+
+    private String existCommand = "ls";
+
+    private String createDirCommand = "mkdir";
+
+    private String publishPermissions = null;
+
+    /**
+     * create a new resource with lazy initializing
+     */
+    public Resource getResource(String source) {
+        Message.debug("SShRepository:getResource called: " + source);
+        return new SshResource(this, source);
+    }
+
+    /**
+     * Fetch the needed file information for a given file (size, last modification time) and report
+     * it back in a SshResource
+     * 
+     * @param source
+     *            ssh uri for the file to get info for
+     * @return SshResource filled with the needed informations
+     * @see org.apache.ivy.plugins.repository.Repository#getResource(java.lang.String)
+     */
+    public SshResource resolveResource(String source) {
+        Message.debug("SShRepository:resolveResource called: " + source);
+        SshResource result = null;
+        Session session = null;
+        try {
+            session = getSession(source);
+            Scp myCopy = new Scp(session);
+            Scp.FileInfo fileInfo = myCopy.getFileinfo(new URI(source).getPath());
+            result = new SshResource(this, source, true, fileInfo.getLength(),
+                    fileInfo.getLastModified());
+        } catch (IOException e) {
+            if (session != null) {
+                releaseSession(session, source);
+            }
+            result = new SshResource();
+        } catch (URISyntaxException e) {
+            if (session != null) {
+                releaseSession(session, source);
+            }
+            result = new SshResource();
+        } catch (RemoteScpException e) {
+            result = new SshResource();
+        }
+        Message.debug("SShRepository:resolveResource end.");
+        return result;
+    }
+
+    /**
+     * Reads out the output of a ssh session exec
+     * 
+     * @param channel
+     *            Channel to read from
+     * @param strStdout
+     *            StringBuffer that receives Session Stdout output
+     * @param strStderr
+     *            StringBuffer that receives Session Stderr output
+     * @throws IOException
+     *             in case of trouble with the network
+     */
+    private void readSessionOutput(ChannelExec channel, StringBuffer strStdout,
+            StringBuffer strStderr) throws IOException {
+        InputStream stdout = channel.getInputStream();
+        InputStream stderr = channel.getErrStream();
+
+        try {
+            channel.connect();
+        } catch (JSchException e1) {
+            throw (IOException) new IOException("Channel connection problems").initCause(e1);
+        }
+
+        byte[] buffer = new byte[BUFFER_SIZE];
+        while (true) {
+            int avail = 0;
+            while ((avail = stdout.available()) > 0) {
+                int len = stdout.read(buffer, 0, (avail > BUFFER_SIZE - 1 ? BUFFER_SIZE : avail));
+                strStdout.append(new String(buffer, 0, len));
+            }
+            while ((avail = stderr.available()) > 0) {
+                int len = stderr.read(buffer, 0, (avail > BUFFER_SIZE - 1 ? BUFFER_SIZE : avail));
+                strStderr.append(new String(buffer, 0, len));
+            }
+            if (channel.isClosed()) {
+                break;
+            }
+            try {
+                Thread.sleep(POLL_SLEEP_TIME);
+            } catch (Exception ee) {
+                // ignored
+            }
+        }
+        int avail = 0;
+        while ((avail = stdout.available()) > 0) {
+            int len = stdout.read(buffer, 0, (avail > BUFFER_SIZE - 1 ? BUFFER_SIZE : avail));
+            strStdout.append(new String(buffer, 0, len));
+        }
+        while ((avail = stderr.available()) > 0) {
+            int len = stderr.read(buffer, 0, (avail > BUFFER_SIZE - 1 ? BUFFER_SIZE : avail));
+            strStderr.append(new String(buffer, 0, len));
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.ivy.repository.Repository#list(java.lang.String)
+     */
+    public List list(String parent) throws IOException {
+        Message.debug("SShRepository:list called: " + parent);
+        ArrayList result = new ArrayList();
+        Session session = null;
+        ChannelExec channel = null;
+        session = getSession(parent);
+        channel = getExecChannel(session);
+        URI parentUri = null;
+        try {
+            parentUri = new URI(parent);
+        } catch (URISyntaxException e) {
+            IOException ioe = new IOException("The uri '" + parent + "' is not valid!");
+            ioe.initCause(e);
+            throw ioe;
+        }
+        String fullCmd = replaceArgument(listCommand, parentUri.getPath());
+        channel.setCommand(fullCmd);
+        StringBuffer stdOut = new StringBuffer();
+        StringBuffer stdErr = new StringBuffer();
+        readSessionOutput(channel, stdOut, stdErr);
+        if (channel.getExitStatus() != 0) {
+            Message.error("Ssh ListCommand exited with status != 0");
+            Message.error(stdErr.toString());
+            return null;
+        } else {
+            BufferedReader br = new BufferedReader(new StringReader(stdOut.toString()));
+            String line = null;
+            while ((line = br.readLine()) != null) {
+                result.add(line);
+            }
+        }
+        return result;
+    }
+
+    /**
+     * @param session
+     * @return
+     * @throws JSchException
+     */
+    private ChannelExec getExecChannel(Session session) throws IOException {
+        ChannelExec channel;
+        try {
+            channel = (ChannelExec) session.openChannel("exec");
+        } catch (JSchException e) {
+            throw new IOException();
+        }
+        return channel;
+    }
+
+    /**
+     * Replace the argument placeholder with argument or append the argument if no placeholder is
+     * present
+     * 
+     * @param command
+     *            with argument placeholder or not
+     * @param argument
+     * @return replaced full command
+     */
+    private String replaceArgument(String command, String argument) {
+        String fullCmd;
+        if (command.indexOf(ARGUMENT_PLACEHOLDER) == -1) {
+            fullCmd = command + " " + argument;
+        } else {
+            fullCmd = command.replaceAll(ARGUMENT_PLACEHOLDER, argument);
+        }
+        return fullCmd;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.ivy.repository.Repository#put(java.io.File, java.lang.String, boolean)
+     */
+    public void put(File source, String destination, boolean overwrite) throws IOException {
+        Message.debug("SShRepository:put called: " + destination);
+        Session session = getSession(destination);
+
+        URI destinationUri = null;
+        try {
+            destinationUri = new URI(destination);
+        } catch (URISyntaxException e) {
+            IOException ioe = new IOException("The uri '" + destination + "' is not valid!");
+            ioe.initCause(e);
+            throw ioe;
+        }
+
+        try {
+            String filePath = destinationUri.getPath();
+            int lastSep = filePath.lastIndexOf(fileSeparator);
+            String path;
+            String name;
+            if (lastSep == -1) {
+                name = filePath;
+                path = null;
+            } else {
+                name = filePath.substring(lastSep + 1);
+                path = filePath.substring(0, lastSep);
+            }
+            if (!overwrite) {
+                if (checkExistence(filePath, session)) {
+                    throw new IOException("destination file exists and overwrite == false");
+                }
+            }
+            if (path != null) {
+                makePath(path, session);
+            }
+            Scp myCopy = new Scp(session);
+            myCopy.put(source.getCanonicalPath(), path, name, publishPermissions);
+        } catch (IOException e) {
+            if (session != null) {
+                releaseSession(session, destination);
+            }
+            throw e;
+        } catch (RemoteScpException e) {
+            throw new IOException(e.getMessage());
+        }
+    }
+
+    /**
+     * Tries to create a directory path on the target system
+     * 
+     * @param path
+     *            to create
+     * @param connnection
+     *            to use
+     */
+    private void makePath(String path, Session session) throws IOException {
+        ChannelExec channel = null;
+        String trimmed = path;
+        try {
+            while (trimmed.length() > 0 && trimmed.charAt(trimmed.length() - 1) == fileSeparator) {
+                trimmed = trimmed.substring(0, trimmed.length() - 1);
+            }
+            if (trimmed.length() == 0 || checkExistence(trimmed, session)) {
+                return;
+            }
+            int nextSlash = trimmed.lastIndexOf(fileSeparator);
+            if (nextSlash > 0) {
+                String parent = trimmed.substring(0, nextSlash);
+                makePath(parent, session);
+            }
+            channel = getExecChannel(session);
+            String mkdir = replaceArgument(createDirCommand, trimmed);
+            Message.debug("SShRepository: trying to create path: " + mkdir);
+            channel.setCommand(mkdir);
+            StringBuffer stdOut = new StringBuffer();
+            StringBuffer stdErr = new StringBuffer();
+            readSessionOutput(channel, stdOut, stdErr);
+        } finally {
+            if (channel != null) {
+                channel.disconnect();
+            }
+        }
+    }
+
+    /**
+     * check for existence of file or dir on target system
+     * 
+     * @param filePath
+     *            to the object to check
+     * @param session
+     *            to use
+     * @return true: object exists, false otherwise
+     */
+    private boolean checkExistence(String filePath, Session session) throws IOException {
+        Message.debug("SShRepository: checkExistence called: " + filePath);
+        ChannelExec channel = null;
+        channel = getExecChannel(session);
+        String fullCmd = replaceArgument(existCommand, filePath);
+        channel.setCommand(fullCmd);
+        StringBuffer stdOut = new StringBuffer();
+        StringBuffer stdErr = new StringBuffer();
+        readSessionOutput(channel, stdOut, stdErr);
+        return channel.getExitStatus() == 0;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.ivy.repository.Repository#get(java.lang.String, java.io.File)
+     */
+    public void get(String source, File destination) throws IOException {
+        Message.debug("SShRepository:get called: " + source + " to "
+                + destination.getCanonicalPath());
+        if (destination.getParentFile() != null) {
+            destination.getParentFile().mkdirs();
+        }
+        Session session = getSession(source);
+
+        URI sourceUri = null;
+        try {
+            sourceUri = new URI(source);
+        } catch (URISyntaxException e) {
+            IOException ioe = new IOException("The uri '" + source + "' is not valid!");
+            ioe.initCause(e);
+            throw ioe;
+        }
+
+        try {
+            Scp myCopy = new Scp(session);
+            myCopy.get(sourceUri.getPath(), destination.getCanonicalPath());
+        } catch (IOException e) {
+            if (session != null) {
+                releaseSession(session, source);
+            }
+            throw e;
+        } catch (RemoteScpException e) {
+            throw new IOException(e.getMessage());
+        }
+    }
+
+    /**
+     * sets the list command to use for a directory listing listing must be only the filename and
+     * each filename on a separate line
+     * 
+     * @param cmd
+     *            to use. default is "ls -1"
+     */
+    public void setListCommand(String cmd) {
+        this.listCommand = cmd.trim();
+    }
+
+    /**
+     * @return the list command to use
+     */
+    public String getListCommand() {
+        return listCommand;
+    }
+
+    /**
+     * @return the createDirCommand
+     */
+    public String getCreateDirCommand() {
+        return createDirCommand;
+    }
+
+    /**
+     * @param createDirCommand
+     *            the createDirCommand to set
+     */
+    public void setCreateDirCommand(String createDirCommand) {
+        this.createDirCommand = createDirCommand;
+    }
+
+    /**
+     * @return the existCommand
+     */
+    public String getExistCommand() {
+        return existCommand;
+    }
+
+    /**
+     * @param existCommand
+     *            the existCommand to set
+     */
+    public void setExistCommand(String existCommand) {
+        this.existCommand = existCommand;
+    }
+
+    /**
+     * The file separator is the separator to use on the target system On a unix system it is '/',
+     * but I don't know, how this is solved on different ssh implementations. Using the default
+     * might be fine
+     * 
+     * @param fileSeparator
+     *            The fileSeparator to use. default '/'
+     */
+    public void setFileSeparator(char fileSeparator) {
+        this.fileSeparator = fileSeparator;
+    }
+
+    /**
+     * A four digit string (e.g., 0644, see "man chmod", "man open") specifying the permissions of
+     * the published files.
+     */
+    public void setPublishPermissions(String permissions) {
+        this.publishPermissions = permissions;
+    }
+
+    /**
+     * return ssh as scheme use the Resolver type name here? would be nice if it would be static, so
+     * we could use SshResolver.getTypeName()
+     */
+    protected String getRepositoryScheme() {
+        return "ssh";
+    }
+
+    /**
+     * Not really streaming...need to implement a proper streaming approach?
+     * 
+     * @param resource
+     *            to stream
+     * @return InputStream of the resource data
+     */
+    public InputStream openStream(SshResource resource) throws IOException {
+        Session session = getSession(resource.getName());
+        Scp scp = new Scp(session);
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+        try {
+            scp.get(resource.getName(), os);
+        } catch (IOException e) {
+            if (session != null) {
+                releaseSession(session, resource.getName());
+            }
+            throw e;
+        } catch (RemoteScpException e) {
+            throw new IOException(e.getMessage());
+        }
+        return new ByteArrayInputStream(os.toByteArray());
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/repository/ssh/SshResource.java b/src/java/org/apache/ivy/plugins/repository/ssh/SshResource.java
new file mode 100644
index 0000000..7fc797e
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/repository/ssh/SshResource.java
@@ -0,0 +1,139 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.repository.ssh;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.util.Message;
+
+/**
+ * Resource for SSH Ivy Repository
+ */
+public class SshResource implements Resource {
+
+    private boolean resolved = false;
+
+    private String uri = null;
+
+    private boolean bExists = false;
+
+    private long len = 0;
+
+    private long lastModified = 0;
+
+    private SshRepository repository = null;
+
+    public SshResource() {
+        resolved = true;
+    }
+
+    public SshResource(SshRepository repository, String uri) {
+        this.uri = uri;
+        this.repository = repository;
+        resolved = false;
+    }
+
+    public SshResource(SshRepository repository, String uri, boolean bExists, long len,
+            long lastModified) {
+        this.uri = uri;
+        this.bExists = bExists;
+        this.len = len;
+        this.lastModified = lastModified;
+        this.repository = repository;
+        resolved = true;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.ivy.repository.Resource#exists()
+     */
+    public boolean exists() {
+        if (!resolved) {
+            resolve();
+        }
+        return bExists;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.ivy.repository.Resource#getContentLength()
+     */
+    public long getContentLength() {
+        if (!resolved) {
+            resolve();
+        }
+        return len;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.ivy.repository.Resource#getLastModified()
+     */
+    public long getLastModified() {
+        if (!resolved) {
+            resolve();
+        }
+        return lastModified;
+    }
+
+    private void resolve() {
+        Message.debug("SShResource: resolving " + uri);
+        SshResource res = repository.resolveResource(uri);
+        len = res.getContentLength();
+        lastModified = res.getLastModified();
+        bExists = res.exists();
+        resolved = true;
+        Message.debug("SShResource: resolved " + this);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.ivy.repository.Resource#getName()
+     */
+    public String getName() {
+        return uri;
+    }
+
+    public String toString() {
+        StringBuffer buffer = new StringBuffer();
+        buffer.append("SshResource:");
+        buffer.append(uri);
+        buffer.append(" (");
+        buffer.append(len);
+        buffer.append(")]");
+        return buffer.toString();
+    }
+
+    public boolean isLocal() {
+        return false;
+    }
+
+    public Resource clone(String cloneName) {
+        return new SshResource(repository, cloneName);
+    }
+
+    public InputStream openStream() throws IOException {
+        return repository.openStream(this);
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/repository/url/ChainedRepository.java b/src/java/org/apache/ivy/plugins/repository/url/ChainedRepository.java
new file mode 100644
index 0000000..417b9a5
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/repository/url/ChainedRepository.java
@@ -0,0 +1,115 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.repository.url;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.ivy.plugins.repository.AbstractRepository;
+import org.apache.ivy.plugins.repository.BasicResource;
+import org.apache.ivy.plugins.repository.Repository;
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.util.Message;
+
+public class ChainedRepository extends AbstractRepository {
+
+    private List/* Repository */repositories;
+
+    public void setRepositories(List/* Repository */repositories) {
+        this.repositories = repositories;
+    }
+
+    public Resource getResource(String source) throws IOException {
+        Iterator it = repositories.iterator();
+        while (it.hasNext()) {
+            Repository repository = (Repository) it.next();
+            logTry(repository);
+            try {
+                Resource r = repository.getResource(source);
+                if (r != null && r.exists()) {
+                    logSuccess(repository);
+                    return r;
+                }
+            } catch (Exception e) {
+                logFailed(repository, e);
+            }
+        }
+        // resource that basically doesn't exists
+        return new BasicResource(source, false, 0, 0, true);
+    }
+
+    public void get(String source, File destination) throws IOException {
+        Iterator it = repositories.iterator();
+        while (it.hasNext()) {
+            Repository repository = (Repository) it.next();
+            logTry(repository);
+            boolean ok = false;
+            try {
+                repository.get(source, destination);
+                ok = true;
+            } catch (Exception e) {
+                logFailed(repository, e);
+            }
+            if (ok) {
+                logSuccess(repository);
+                return;
+            }
+        }
+        throw newIOEFail("copy " + source + " into " + destination);
+    }
+
+    public List list(String parent) throws IOException {
+        Iterator it = repositories.iterator();
+        while (it.hasNext()) {
+            Repository repository = (Repository) it.next();
+            logTry(repository);
+            try {
+                List list = repository.list(parent);
+                if (list != null) {
+                    logSuccess(repository);
+                    return list;
+                }
+            } catch (Exception e) {
+                logFailed(repository, e);
+            }
+        }
+        throw newIOEFail("list contents in " + parent);
+    }
+
+    private void logTry(Repository repository) {
+        Message.debug("Mirrored repository " + getName() + ": trying " + repository.getName());
+    }
+
+    private void logFailed(Repository repository, Exception e) {
+        Message.warn("Mirrored repository " + getName() + ": " + repository.getName()
+                + " is not available", e);
+        Message.warn("Trying the next one in the mirror list...");
+    }
+
+    private void logSuccess(Repository repository) {
+        Message.debug("Mirrored repository " + getName() + ": success with " + repository.getName());
+    }
+
+    private IOException newIOEFail(String action) {
+        return new IOException("Mirrored repository " + getName() + ": fail to " + action
+                + " with every listed mirror");
+    }
+
+}
diff --git a/src/java/org/apache/ivy/plugins/repository/url/URLRepository.java b/src/java/org/apache/ivy/plugins/repository/url/URLRepository.java
new file mode 100644
index 0000000..098ccc3
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/repository/url/URLRepository.java
@@ -0,0 +1,141 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.repository.url;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+
+import org.apache.ivy.plugins.repository.AbstractRepository;
+import org.apache.ivy.plugins.repository.RepositoryCopyProgressListener;
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.plugins.repository.TransferEvent;
+import org.apache.ivy.util.FileUtil;
+import org.apache.ivy.util.url.ApacheURLLister;
+
+public class URLRepository extends AbstractRepository {
+    private RepositoryCopyProgressListener progress = new RepositoryCopyProgressListener(this);
+
+    private Map resourcesCache = new HashMap();
+
+    public Resource getResource(String source) throws IOException {
+        Resource res = (Resource) resourcesCache.get(source);
+        if (res == null) {
+            res = new URLResource(new URL(source));
+            resourcesCache.put(source, res);
+        }
+        return res;
+    }
+
+    public void get(String source, File destination) throws IOException {
+        fireTransferInitiated(getResource(source), TransferEvent.REQUEST_GET);
+        try {
+            Resource res = getResource(source);
+            long totalLength = res.getContentLength();
+            if (totalLength > 0) {
+                progress.setTotalLength(new Long(totalLength));
+            }
+            FileUtil.copy(new URL(source), destination, progress);
+        } catch (IOException ex) {
+            fireTransferError(ex);
+            throw ex;
+        } catch (RuntimeException ex) {
+            fireTransferError(ex);
+            throw ex;
+        } finally {
+            progress.setTotalLength(null);
+        }
+    }
+
+    public void put(File source, String destination, boolean overwrite) throws IOException {
+        if (!overwrite && getResource(destination).exists()) {
+            throw new IOException("destination file exists and overwrite == false");
+        }
+
+        fireTransferInitiated(getResource(destination), TransferEvent.REQUEST_PUT);
+        try {
+            long totalLength = source.length();
+            if (totalLength > 0) {
+                progress.setTotalLength(new Long(totalLength));
+            }
+            FileUtil.copy(source, new URL(destination), progress);
+        } catch (IOException ex) {
+            fireTransferError(ex);
+            throw ex;
+        } catch (RuntimeException ex) {
+            fireTransferError(ex);
+            throw ex;
+        } finally {
+            progress.setTotalLength(null);
+        }
+    }
+
+    private ApacheURLLister lister = new ApacheURLLister();
+
+    public List list(String parent) throws IOException {
+        if (parent.startsWith("http")) {
+            List urls = lister.listAll(new URL(parent));
+            if (urls != null) {
+                List ret = new ArrayList(urls.size());
+                for (ListIterator iter = urls.listIterator(); iter.hasNext();) {
+                    URL url = (URL) iter.next();
+                    ret.add(url.toExternalForm());
+                }
+                return ret;
+            }
+        } else if (parent.startsWith("file")) {
+            String path;
+            try {
+                URI uri = new URI(parent);
+                if (uri.isOpaque()) {
+                    path = uri.getSchemeSpecificPart();
+                } else {
+                    path = uri.getPath();
+                }
+            } catch (URISyntaxException e) {
+                IOException ioe = new IOException("Couldn't list content of '" + parent + "'");
+                ioe.initCause(e);
+                throw ioe;
+            }
+
+            File file = new File(path);
+            if (file.exists() && file.isDirectory()) {
+                String[] files = file.list();
+                List ret = new ArrayList(files.length);
+                URL context = path.endsWith("/") ? new URL(parent) : new URL(parent + "/");
+                for (int i = 0; i < files.length; i++) {
+                    ret.add(new URL(context, files[i]).toExternalForm());
+                }
+                return ret;
+            } else {
+                return Collections.EMPTY_LIST;
+            }
+
+        }
+        return null;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/plugins/repository/url/URLResource.java b/src/java/org/apache/ivy/plugins/repository/url/URLResource.java
new file mode 100644
index 0000000..c687308
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/repository/url/URLResource.java
@@ -0,0 +1,116 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.repository.url;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+
+import org.apache.ivy.plugins.repository.LocalizableResource;
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.util.url.URLHandler.URLInfo;
+import org.apache.ivy.util.url.URLHandlerRegistry;
+
+public class URLResource implements LocalizableResource {
+    private URL url;
+
+    private boolean init = false;
+
+    private long lastModified;
+
+    private long contentLength;
+
+    private boolean exists;
+
+    public URLResource(URL url) {
+        this.url = url;
+    }
+
+    public String getName() {
+        return url.toExternalForm();
+    }
+
+    public Resource clone(String cloneName) {
+        try {
+            return new URLResource(new URL(cloneName));
+        } catch (MalformedURLException e) {
+            throw new IllegalArgumentException(
+                    "bad clone name provided: not suitable for an URLResource: " + cloneName);
+        }
+    }
+
+    public long getLastModified() {
+        if (!init) {
+            init();
+        }
+        return lastModified;
+    }
+
+    private void init() {
+        URLInfo info = URLHandlerRegistry.getDefault().getURLInfo(url);
+        contentLength = info.getContentLength();
+        lastModified = info.getLastModified();
+        exists = info.isReachable();
+        init = true;
+    }
+
+    public long getContentLength() {
+        if (!init) {
+            init();
+        }
+        return contentLength;
+    }
+
+    public boolean exists() {
+        if (!init) {
+            init();
+        }
+        return exists;
+    }
+
+    public URL getURL() {
+        return url;
+    }
+
+    public String toString() {
+        return getName();
+    }
+
+    public boolean isLocal() {
+        return url.getProtocol().equals("file");
+    }
+
+    public InputStream openStream() throws IOException {
+        return URLHandlerRegistry.getDefault().openStream(url);
+    }
+
+    public File getFile() {
+        if (!isLocal()) {
+            throw new IllegalStateException("Cannot get the local file for the not local resource "
+                    + url);
+        }
+        try {
+            return new File(url.toURI());
+        } catch (URISyntaxException e) {
+            return new File(url.getPath());
+        }
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/repository/vfs/VfsRepository.java b/src/java/org/apache/ivy/plugins/repository/vfs/VfsRepository.java
new file mode 100644
index 0000000..aead7cf
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/repository/vfs/VfsRepository.java
@@ -0,0 +1,216 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.ivy.plugins.repository.vfs;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.commons.vfs.FileContent;
+import org.apache.commons.vfs.FileObject;
+import org.apache.commons.vfs.FileSystemException;
+import org.apache.commons.vfs.FileSystemManager;
+import org.apache.commons.vfs.FileType;
+import org.apache.commons.vfs.impl.StandardFileSystemManager;
+import org.apache.ivy.plugins.repository.AbstractRepository;
+import org.apache.ivy.plugins.repository.RepositoryCopyProgressListener;
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.plugins.repository.TransferEvent;
+import org.apache.ivy.util.CopyProgressListener;
+import org.apache.ivy.util.FileUtil;
+import org.apache.ivy.util.Message;
+
+/**
+ * Implementation of a VFS repository
+ */
+public class VfsRepository extends AbstractRepository {
+    /**
+     * Name of the resource defining the Ivy VFS Repo configuration.
+     */
+    private static final String IVY_VFS_CONFIG = "ivy_vfs.xml";
+
+    private StandardFileSystemManager manager = null;
+
+    private final CopyProgressListener progress = new RepositoryCopyProgressListener(this);
+
+    /**
+     * Create a new Ivy VFS Repository Instance
+     */
+    public VfsRepository() {
+    }
+
+    private FileSystemManager getVFSManager() throws IOException {
+        synchronized (this) {
+            if (manager == null) {
+                manager = createVFSManager();
+            }
+        }
+        return manager;
+    }
+
+    private StandardFileSystemManager createVFSManager() throws IOException {
+        StandardFileSystemManager result = null;
+        try {
+            /*
+             * The DefaultFileSystemManager gets its configuration from the jakarta-vfs-common
+             * implementation which includes the res and tmp schemes which are of no use to use
+             * here. Using StandardFileSystemManager lets us specify which schemes to support as
+             * well as providing a mechanism to change this support without recompilation.
+             */
+            result = new StandardFileSystemManager() {
+                protected void configurePlugins() throws FileSystemException {
+                    // disable automatic loading potential unsupported extensions
+                }
+            };
+            result.setConfiguration(getClass().getResource(IVY_VFS_CONFIG));
+            result.init();
+
+            // Generate and print a list of available schemes
+            Message.verbose("Available VFS schemes...");
+            String[] schemes = result.getSchemes();
+            Arrays.sort(schemes);
+            for (int i = 0; i < schemes.length; i++) {
+                Message.verbose("VFS Supported Scheme: " + schemes[i]);
+            }
+        } catch (FileSystemException e) {
+            /*
+             * If our attempt to initialize a VFS Repository fails we log the failure but continue
+             * on. Given that an Ivy instance may involve numerous different repository types, it
+             * seems overly cautious to throw a runtime exception on the initialization failure of
+             * just one repository type.
+             */
+            Message.error("Unable to initialize VFS repository manager!");
+            Message.error(e.getLocalizedMessage());
+            IOException error = new IOException(e.getLocalizedMessage());
+            error.initCause(e);
+            throw error;
+        }
+
+        return result;
+    }
+
+    protected void finalize() {
+        if (manager != null) {
+            manager.close();
+            manager = null;
+        }
+    }
+
+    /**
+     * Get a VfsResource
+     * 
+     * @param source
+     *            a <code>String</code> identifying a VFS Resource
+     * @throws <code>IOException</code> on failure
+     * @see "Supported File Systems in the jakarta-commons-vfs documentation"
+     */
+    public Resource getResource(String vfsURI) throws IOException {
+        return new VfsResource(vfsURI, getVFSManager());
+    }
+
+    /**
+     * Transfer a VFS Resource from the repository to the local file system.
+     * 
+     * @param srcVfsURI
+     *            a <code>String</code> identifying the VFS resource to be fetched
+     * @param destination
+     *            a <code>File</code> identifying the destination file
+     * @throws <code>IOException</code> on failure
+     * @see "Supported File Systems in the jakarta-commons-vfs documentation"
+     */
+    public void get(String srcVfsURI, File destination) throws IOException {
+        VfsResource src = new VfsResource(srcVfsURI, getVFSManager());
+        fireTransferInitiated(src, TransferEvent.REQUEST_GET);
+        try {
+            FileContent content = src.getContent();
+            if (content == null) {
+                throw new IllegalArgumentException("invalid vfs uri " + srcVfsURI
+                        + ": no content found");
+            }
+            FileUtil.copy(content.getInputStream(), destination, progress);
+        } catch (IOException ex) {
+            fireTransferError(ex);
+            throw ex;
+        } catch (RuntimeException ex) {
+            fireTransferError(ex);
+            throw ex;
+        }
+    }
+
+    /**
+     * Return a listing of the contents of a parent directory. Listing is a set of strings
+     * representing VFS URIs.
+     * 
+     * @param vfsURI
+     *            providing identifying a VFS provided resource
+     * @throws IOException
+     *             on failure.
+     * @see "Supported File Systems in the jakarta-commons-vfs documentation"
+     */
+    public List list(String vfsURI) throws IOException {
+        ArrayList list = new ArrayList();
+        Message.debug("list called for URI" + vfsURI);
+        FileObject resourceImpl = getVFSManager().resolveFile(vfsURI);
+        Message.debug("resourceImpl=" + resourceImpl.toString());
+        Message.debug("resourceImpl.exists()" + resourceImpl.exists());
+        Message.debug("resourceImpl.getType()" + resourceImpl.getType());
+        Message.debug("FileType.FOLDER" + FileType.FOLDER);
+        if ((resourceImpl != null) && resourceImpl.exists()
+                && (resourceImpl.getType() == FileType.FOLDER)) {
+            FileObject[] children = resourceImpl.getChildren();
+            for (int i = 0; i < children.length; i++) {
+                FileObject child = children[i];
+                Message.debug("child " + i + child.getName().getURI());
+                list.add(VfsResource.normalize(child.getName().getURI()));
+            }
+        }
+        return list;
+    }
+
+    /**
+     * Transfer an Ivy resource to a VFS repository
+     * 
+     * @param source
+     *            a <code>File</code> indentifying the local file to transfer to the repository
+     * @param vfsURI
+     *            a <code>String</code> identifying the destination VFS Resource.
+     * @param overwrite
+     *            whether to overwrite an existing resource.
+     * @throws <code>IOException</code> on failure.
+     * @see "Supported File Systems in the jakarta-commons-vfs documentation"
+     */
+    public void put(File source, String vfsURI, boolean overwrite) throws IOException {
+        VfsResource dest = new VfsResource(vfsURI, getVFSManager());
+        fireTransferInitiated(dest, TransferEvent.REQUEST_PUT);
+        if (dest.physicallyExists() && !overwrite) {
+            throw new IOException("Cannot copy. Destination file: " + dest.getName()
+                    + " exists and overwrite not set.");
+        }
+        if (dest.getContent() == null) {
+            throw new IllegalArgumentException("invalid vfs uri " + vfsURI
+                    + " to put data to: resource has no content");
+        }
+
+        FileUtil.copy(new FileInputStream(source), dest.getContent().getOutputStream(), progress);
+    }
+
+}
diff --git a/src/java/org/apache/ivy/plugins/repository/vfs/VfsResource.java b/src/java/org/apache/ivy/plugins/repository/vfs/VfsResource.java
new file mode 100644
index 0000000..5cd7cc2
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/repository/vfs/VfsResource.java
@@ -0,0 +1,206 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.repository.vfs;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.vfs.FileContent;
+import org.apache.commons.vfs.FileObject;
+import org.apache.commons.vfs.FileSystemException;
+import org.apache.commons.vfs.FileSystemManager;
+import org.apache.commons.vfs.FileType;
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.plugins.resolver.VfsResolver;
+import org.apache.ivy.util.Message;
+
+/**
+ * VFS implementation of the Resource interface
+ */
+public class VfsResource implements Resource {
+    private String vfsURI;
+
+    private FileSystemManager fsManager;
+
+    private transient boolean init = false;
+
+    private transient boolean exists;
+
+    private transient long lastModified;
+
+    private transient long contentLength;
+
+    private transient FileContent content = null;
+
+    private transient FileObject resourceImpl;
+
+    // Constructor
+    public VfsResource(String vfsURI, FileSystemManager fsManager) {
+        this.vfsURI = vfsURI;
+        this.fsManager = fsManager;
+        this.init = false;
+    }
+
+    private void init() {
+        if (!init) {
+            try {
+                resourceImpl = fsManager.resolveFile(vfsURI);
+                content = resourceImpl.getContent();
+
+                exists = resourceImpl.exists();
+                lastModified = content.getLastModifiedTime();
+                contentLength = content.getSize();
+            } catch (FileSystemException e) {
+                Message.debug(e);
+                Message.verbose(e.getLocalizedMessage());
+                exists = false;
+                lastModified = 0;
+                contentLength = 0;
+            }
+
+            init = true;
+        }
+    }
+
+    /**
+     * Get a list of direct descendents of the given resource. Note that attempts to get a list of
+     * children does <emphasize>not</emphasize> result in an error. Instead an error message is
+     * logged and an empty ArrayList returned.
+     * 
+     * @return A <code>ArrayList</code> of VFSResources
+     */
+    public List getChildren() {
+        init();
+        ArrayList list = new ArrayList();
+        try {
+            if ((resourceImpl != null) && resourceImpl.exists()
+                    && (resourceImpl.getType() == FileType.FOLDER)) {
+                FileObject[] children = resourceImpl.getChildren();
+                for (int i = 0; i < children.length; i++) {
+                    FileObject child = children[i];
+                    list.add(normalize(child.getName().getURI()));
+                }
+            }
+        } catch (IOException e) {
+            Message.debug(e);
+            Message.verbose(e.getLocalizedMessage());
+        }
+        return list;
+    }
+
+    public FileContent getContent() {
+        init();
+        return content;
+    }
+
+    /**
+     * Get the name of the resource.
+     * 
+     * @return a <code>String</code> representing the Resource URL.
+     */
+    public String getName() {
+        return normalize(vfsURI);
+    }
+
+    public Resource clone(String cloneName) {
+        return new VfsResource(cloneName, fsManager);
+    }
+
+    /**
+     * The VFS FileName getURI method seems to have a bug in it where file: URIs will have 4 forward
+     * slashes instead of 3.
+     * 
+     * @param vfsURI
+     * @return a normalized <class>String</class> representing the VFS URI
+     */
+    public static String normalize(String vfsURI) {
+        if (vfsURI == null) {
+            return "";
+        }
+
+        if (vfsURI.startsWith("file:////")) {
+            vfsURI = vfsURI.replaceFirst("////", "///");
+        }
+        return vfsURI;
+    }
+
+    /**
+     * Get the last modification time of the resource.
+     * 
+     * @return a <code>long</code> indicating last modified time.
+     */
+    public long getLastModified() {
+        init();
+        return lastModified;
+    }
+
+    /**
+     * Get the size of the resource
+     * 
+     * @return a <code>long</code> representing the size of the resource (in bytes).
+     */
+    public long getContentLength() {
+        init();
+        return contentLength;
+    }
+
+    /**
+     * Flag indicating whether a resource is available for querying
+     * 
+     * @return <code>true</code> if the resource is available for querying, <code>false</code>
+     *         otherwise.
+     */
+    public boolean exists() {
+        init();
+        return exists;
+    }
+
+    /**
+     * Return a flag indicating whether a provided VFS resource physically exists
+     * 
+     * @return <code>true</code> if the resource physically exists, <code>false</code> otherwise.
+     */
+    public boolean physicallyExists() {
+        // TODO: there is no need for this method anymore, replace it by calling exists();
+        init();
+
+        try {
+            return resourceImpl.exists();
+            // originally I only checked for a FileSystemException. I expanded it to
+            // include all exceptions when I found it would throw a NPE exception when the query was
+            // run on non-wellformed VFS URI.
+        } catch (Exception e) {
+            Message.verbose("Fail to check the existance of the resource " + getName(), e);
+            return false;
+        }
+    }
+
+    public String toString() {
+        return VfsResolver.prepareForDisplay(getName());
+    }
+
+    public boolean isLocal() {
+        return getName().startsWith("file:");
+    }
+
+    public InputStream openStream() throws IOException {
+        return getContent().getInputStream();
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/repository/vfs/ivy_vfs.xml b/src/java/org/apache/ivy/plugins/repository/vfs/ivy_vfs.xml
new file mode 100644
index 0000000..347d73e
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/repository/vfs/ivy_vfs.xml
@@ -0,0 +1,50 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<!-- For all intents and purposes this is the providers.xml file included
+     in the jakarta-vfs-common distribution with the res and tmp schemas removed.
+ -->
+ <providers>
+    <default-provider class-name="org.apache.commons.vfs.provider.url.UrlFileProvider">
+    </default-provider>
+    <provider class-name="org.apache.commons.vfs.provider.local.DefaultLocalFileProvider">
+        <scheme name="file"/>
+    </provider>
+    <provider class-name="org.apache.commons.vfs.provider.ftp.FtpFileProvider">
+        <scheme name="ftp"/>
+        <if-available class-name="org.apache.commons.net.ftp.FTPFile"/>
+    </provider>
+    <!-- FIXME: not available anymore in commons-vfs but in commons-vfs-sandbox
+    <provider class-name="org.apache.commons.vfs.provider.smb.SmbFileProvider">
+        <scheme name="smb"/>
+        <if-available class-name="jcifs.smb.SmbFile"/>
+    </provider>
+    -->
+    <!--
+    <provider class-name="org.apache.commons.vfs.provider.http.HttpFileProvider">
+        <scheme name="http"/>
+        <if-available class-name="org.apache.commons.httpclient.HttpClient"/>
+    </provider>
+    -->
+    
+    <provider class-name="org.apache.commons.vfs.provider.sftp.SftpFileProvider">
+        <scheme name="sftp"/>
+        <if-available class-name="javax.crypto.Cipher"/>
+        <if-available class-name="com.jcraft.jsch.JSch"/>
+    </provider>
+</providers>
diff --git a/src/java/org/apache/ivy/plugins/repository/vsftp/VsftpRepository.java b/src/java/org/apache/ivy/plugins/repository/vsftp/VsftpRepository.java
new file mode 100644
index 0000000..13f773f
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/repository/vsftp/VsftpRepository.java
@@ -0,0 +1,745 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.repository.vsftp;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Pattern;
+
+import org.apache.ivy.Ivy;
+import org.apache.ivy.core.IvyContext;
+import org.apache.ivy.core.IvyThread;
+import org.apache.ivy.core.event.IvyEvent;
+import org.apache.ivy.core.event.IvyListener;
+import org.apache.ivy.core.event.resolve.EndResolveEvent;
+import org.apache.ivy.plugins.repository.AbstractRepository;
+import org.apache.ivy.plugins.repository.BasicResource;
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.plugins.repository.TransferEvent;
+import org.apache.ivy.util.Checks;
+import org.apache.ivy.util.Message;
+
+/**
+ * Repository using SecureCRT vsftp command line program to access an sftp repository This is
+ * especially useful to leverage the gssapi authentication supported by SecureCRT. In caseswhere
+ * usual sftp is enough, prefer the 100% java solution of sftp repository. This requires SecureCRT
+ * to be in the PATH. Tested with SecureCRT 5.0.5
+ */
+public class VsftpRepository extends AbstractRepository {
+    private static final int LS_DATE_INDEX4 = 7;
+
+    private static final int LS_DATE_INDEX3 = 6;
+
+    private static final int LS_DATE_INDEX2 = 5;
+
+    private static final int LS_DATE_INDEX1 = 4;
+
+    private static final int LS_SIZE_INDEX = 3;
+
+    private static final int LS_PARTS_NUMBER = 9;
+
+    private static final int DISCONNECT_COMMAND_TIMEOUT = 300;
+
+    private static final int REUSE_CONNECTION_SLEEP_TIME = 10;
+
+    private static final int READER_ALIVE_SLEEP_TIME = 100;
+
+    private static final int MAX_READER_ALIVE_ATTEMPT = 5;
+
+    private static final int ERROR_SLEEP_TIME = 30;
+
+    private static final int PROMPT_SLEEP_TIME = 50;
+
+    private static final int MAX_READ_PROMPT_ATTEMPT = 5;
+
+    private static final int GET_JOIN_MAX_TIME = 100;
+
+    private static final int DEFAULT_REUSE_CONNECTION_TIME = 300000;
+
+    // reuse connection during 5 minutes by default
+
+    private static final int DEFAULT_READ_TIMEOUT = 30000;
+
+    private static final String PROMPT = "vsftp> ";
+
+    private static final SimpleDateFormat FORMAT = new SimpleDateFormat("MMM dd, yyyy HH:mm",
+            Locale.US);
+
+    private String host;
+
+    private String username;
+
+    private String authentication = "gssapi";
+
+    private Reader in;
+
+    private Reader err;
+
+    private PrintWriter out;
+
+    private volatile StringBuffer errors = new StringBuffer();
+
+    private long readTimeout = DEFAULT_READ_TIMEOUT;
+
+    private long reuseConnection = DEFAULT_REUSE_CONNECTION_TIME;
+
+    private volatile long lastCommand;
+
+    private volatile boolean inCommand;
+
+    private Process process;
+
+    private Thread connectionCleaner;
+
+    private Thread errorsReader;
+
+    private volatile long errorsLastUpdateTime;
+
+    private Ivy ivy = null;
+
+    public Resource getResource(String source) throws IOException {
+        initIvy();
+        return new VsftpResource(this, source);
+    }
+
+    private void initIvy() {
+        ivy = IvyContext.getContext().getIvy();
+    }
+
+    protected Resource getInitResource(String source) throws IOException {
+        try {
+            return lslToResource(source, sendCommand("ls -l " + source, true, true));
+        } catch (IOException ex) {
+            cleanup(ex);
+            throw ex;
+        } finally {
+            cleanup();
+        }
+    }
+
+    public void get(final String source, File destination) throws IOException {
+        initIvy();
+        try {
+            fireTransferInitiated(getResource(source), TransferEvent.REQUEST_GET);
+            File destDir = destination.getParentFile();
+            if (destDir != null) {
+                sendCommand("lcd " + destDir.getAbsolutePath());
+            }
+            if (destination.exists()) {
+                destination.delete();
+            }
+
+            int index = source.lastIndexOf('/');
+            String srcName = index == -1 ? source : source.substring(index + 1);
+            final File to = destDir == null ? Checks.checkAbsolute(srcName, "source") : new File(
+                    destDir, srcName);
+
+            final IOException[] ex = new IOException[1];
+            Thread get = new IvyThread() {
+                public void run() {
+                    initContext();
+                    try {
+                        sendCommand("get " + source, getExpectedDownloadMessage(source, to), 0);
+                    } catch (IOException e) {
+                        ex[0] = e;
+                    }
+                }
+            };
+            get.start();
+
+            long prevLength = 0;
+            long lastUpdate = System.currentTimeMillis();
+            long timeout = readTimeout;
+            while (get.isAlive()) {
+                checkInterrupted();
+                long length = to.exists() ? to.length() : 0;
+                if (length > prevLength) {
+                    fireTransferProgress(length - prevLength);
+                    lastUpdate = System.currentTimeMillis();
+                    prevLength = length;
+                } else {
+                    if (System.currentTimeMillis() - lastUpdate > timeout) {
+                        Message.verbose("download hang for more than " + timeout
+                                + "ms. Interrupting.");
+                        get.interrupt();
+                        if (to.exists()) {
+                            to.delete();
+                        }
+                        throw new IOException(source + " download timeout from " + getHost());
+                    }
+                }
+                try {
+                    get.join(GET_JOIN_MAX_TIME);
+                } catch (InterruptedException e) {
+                    if (to.exists()) {
+                        to.delete();
+                    }
+                    return;
+                }
+            }
+            if (ex[0] != null) {
+                if (to.exists()) {
+                    to.delete();
+                }
+                throw ex[0];
+            }
+
+            to.renameTo(destination);
+            fireTransferCompleted(destination.length());
+        } catch (IOException ex) {
+            fireTransferError(ex);
+            cleanup(ex);
+            throw ex;
+        } finally {
+            cleanup();
+        }
+    }
+
+    public List list(String parent) throws IOException {
+        initIvy();
+        try {
+            if (!parent.endsWith("/")) {
+                parent = parent + "/";
+            }
+            String response = sendCommand("ls -l " + parent, true, true);
+            if (response.startsWith("ls")) {
+                return null;
+            }
+            String[] lines = response.split("\n");
+            List ret = new ArrayList(lines.length);
+            for (int i = 0; i < lines.length; i++) {
+                while (lines[i].endsWith("\r") || lines[i].endsWith("\n")) {
+                    lines[i] = lines[i].substring(0, lines[i].length() - 1);
+                }
+                if (lines[i].trim().length() != 0) {
+                    ret.add(parent + lines[i].substring(lines[i].lastIndexOf(' ') + 1));
+                }
+            }
+            return ret;
+        } catch (IOException ex) {
+            cleanup(ex);
+            throw ex;
+        } finally {
+            cleanup();
+        }
+    }
+
+    public void put(File source, String destination, boolean overwrite) throws IOException {
+        initIvy();
+        try {
+            if (getResource(destination).exists()) {
+                if (overwrite) {
+                    sendCommand("rm " + destination, getExpectedRemoveMessage(destination));
+                } else {
+                    return;
+                }
+            }
+            int index = destination.lastIndexOf('/');
+            String destDir = null;
+            if (index != -1) {
+                destDir = destination.substring(0, index);
+                mkdirs(destDir);
+                sendCommand("cd " + destDir);
+            }
+            String to = destDir != null ? destDir + "/" + source.getName() : source.getName();
+            sendCommand("put " + source.getAbsolutePath(), getExpectedUploadMessage(source, to), 0);
+            sendCommand("mv " + to + " " + destination);
+        } catch (IOException ex) {
+            cleanup(ex);
+            throw ex;
+        } finally {
+            cleanup();
+        }
+    }
+
+    private void mkdirs(String destDir) throws IOException {
+        if (dirExists(destDir)) {
+            return;
+        }
+        if (destDir.endsWith("/")) {
+            destDir = destDir.substring(0, destDir.length() - 1);
+        }
+        int index = destDir.lastIndexOf('/');
+        if (index != -1) {
+            mkdirs(destDir.substring(0, index));
+        }
+        sendCommand("mkdir " + destDir);
+    }
+
+    private boolean dirExists(String dir) throws IOException {
+        return !sendCommand("ls " + dir, true).startsWith("ls: ");
+    }
+
+    protected String sendCommand(String command) throws IOException {
+        return sendCommand(command, false, readTimeout);
+    }
+
+    protected void sendCommand(String command, Pattern expectedResponse) throws IOException {
+        sendCommand(command, expectedResponse, readTimeout);
+    }
+
+    /**
+     * The behaviour of vsftp with some commands is to log the resulting message on the error
+     * stream, even if everything is ok. So it's quite difficult if there was an error or not. Hence
+     * we compare the response with the expected message and deal with it. The problem is that this
+     * is very specific to the version of vsftp used for the test, That's why expected messages are
+     * obtained using overridable protected methods.
+     */
+    protected void sendCommand(String command, Pattern expectedResponse, long timeout)
+            throws IOException {
+        String response = sendCommand(command, true, timeout);
+        if (!expectedResponse.matcher(response).matches()) {
+            Message.debug("invalid response from server:");
+            Message.debug("expected: '" + expectedResponse + "'");
+            Message.debug("was:      '" + response + "'");
+            throw new IOException(response);
+        }
+    }
+
+    protected String sendCommand(String command, boolean sendErrorAsResponse) throws IOException {
+        return sendCommand(command, sendErrorAsResponse, readTimeout);
+    }
+
+    protected String sendCommand(String command, boolean sendErrorAsResponse, boolean single)
+            throws IOException {
+        return sendCommand(command, sendErrorAsResponse, single, readTimeout);
+    }
+
+    protected String sendCommand(String command, boolean sendErrorAsResponse, long timeout)
+            throws IOException {
+        return sendCommand(command, sendErrorAsResponse, false, timeout);
+    }
+
+    protected String sendCommand(String command, boolean sendErrorAsResponse, boolean single,
+            long timeout) throws IOException {
+        single = false; // use of alone commands does not work properly due to a long delay between
+        // end of process and end of stream...
+
+        checkInterrupted();
+        inCommand = true;
+        errorsLastUpdateTime = 0;
+        synchronized (this) {
+            if (!single || in != null) {
+                ensureConnectionOpened();
+                Message.debug("sending command '" + command + "' to " + getHost());
+                updateLastCommandTime();
+                out.println(command);
+                out.flush();
+            } else {
+                sendSingleCommand(command);
+            }
+        }
+
+        try {
+            return readResponse(sendErrorAsResponse, timeout);
+        } finally {
+            inCommand = false;
+            if (single) {
+                closeConnection();
+            }
+        }
+    }
+
+    protected String readResponse(boolean sendErrorAsResponse) throws IOException {
+        return readResponse(sendErrorAsResponse, readTimeout);
+    }
+
+    protected synchronized String readResponse(final boolean sendErrorAsResponse, long timeout)
+            throws IOException {
+        final StringBuffer response = new StringBuffer();
+        final IOException[] exc = new IOException[1];
+        final boolean[] done = new boolean[1];
+        Runnable r = new Runnable() {
+            public void run() {
+                synchronized (VsftpRepository.this) {
+                    try {
+                        int c;
+                        boolean getPrompt = false;
+                        // the reading is done in a for loop making five attempts to read the stream
+                        // if we do not reach the next prompt
+                        for (int attempts = 0; !getPrompt && attempts < MAX_READ_PROMPT_ATTEMPT; attempts++) {
+                            while ((c = in.read()) != -1) {
+                                attempts = 0; // we manage to read something, reset numer of
+                                // attempts
+                                response.append((char) c);
+                                if (response.length() >= PROMPT.length()
+                                        && response.substring(response.length() - PROMPT.length(),
+                                            response.length()).equals(PROMPT)) {
+                                    response.setLength(response.length() - PROMPT.length());
+                                    getPrompt = true;
+                                    break;
+                                }
+                            }
+                            if (!getPrompt) {
+                                try {
+                                    Thread.sleep(PROMPT_SLEEP_TIME);
+                                } catch (InterruptedException e) {
+                                    break;
+                                }
+                            }
+                        }
+                        if (getPrompt) {
+                            // wait enough for error stream to be fully read
+                            if (errorsLastUpdateTime == 0) {
+                                // no error written yet, but it may be pending...
+                                errorsLastUpdateTime = lastCommand;
+                            }
+
+                            while ((System.currentTimeMillis() - errorsLastUpdateTime) < PROMPT_SLEEP_TIME) {
+                                try {
+                                    Thread.sleep(ERROR_SLEEP_TIME);
+                                } catch (InterruptedException e) {
+                                    break;
+                                }
+                            }
+                        }
+                        if (errors.length() > 0) {
+                            if (sendErrorAsResponse) {
+                                response.append(errors);
+                                errors.setLength(0);
+                            } else {
+                                throw new IOException(chomp(errors).toString());
+                            }
+                        }
+                        chomp(response);
+                        done[0] = true;
+                    } catch (IOException e) {
+                        exc[0] = e;
+                    } finally {
+                        VsftpRepository.this.notify();
+                    }
+                }
+            }
+        };
+        Thread reader = null;
+        if (timeout == 0) {
+            r.run();
+        } else {
+            reader = new IvyThread(r);
+            reader.start();
+            try {
+                wait(timeout);
+            } catch (InterruptedException e) {
+                // nothing to do
+            }
+        }
+        updateLastCommandTime();
+        if (exc[0] != null) {
+            throw exc[0];
+        } else if (!done[0]) {
+            if (reader != null && reader.isAlive()) {
+                reader.interrupt();
+                for (int i = 0; i < MAX_READER_ALIVE_ATTEMPT && reader.isAlive(); i++) {
+                    try {
+                        Thread.sleep(READER_ALIVE_SLEEP_TIME);
+                    } catch (InterruptedException e) {
+                        break;
+                    }
+                }
+                if (reader.isAlive()) {
+                    reader.stop(); // no way to interrupt it non abruptly
+                }
+            }
+            throw new IOException("connection timeout to " + getHost());
+        } else {
+            if ("Not connected.".equals(response.toString())) {
+                Message.info("vsftp connection to " + getHost() + " reset");
+                closeConnection();
+                throw new IOException("not connected to " + getHost());
+            }
+            Message.debug("received response '" + response + "' from " + getHost());
+            return response.toString();
+        }
+    }
+
+    private synchronized void sendSingleCommand(String command) throws IOException {
+        exec(getSingleCommand(command));
+    }
+
+    protected synchronized void ensureConnectionOpened() throws IOException {
+        if (in == null) {
+            Message.verbose("connecting to " + getUsername() + "@" + getHost() + "... ");
+            String connectionCommand = getConnectionCommand();
+            exec(connectionCommand);
+
+            try {
+                readResponse(false); // waits for first prompt
+
+                if (reuseConnection > 0) {
+                    connectionCleaner = new IvyThread() {
+                        public void run() {
+                            initContext();
+                            try {
+                                long sleep = REUSE_CONNECTION_SLEEP_TIME;
+                                while (in != null && sleep > 0) {
+                                    sleep(sleep);
+                                    sleep = reuseConnection
+                                            - (System.currentTimeMillis() - lastCommand);
+                                    if (inCommand) {
+                                        sleep = sleep <= 0 ? reuseConnection : sleep;
+                                    }
+                                }
+                            } catch (InterruptedException e) {
+                                // nothing to do
+                            }
+                            disconnect();
+                        }
+                    };
+                    connectionCleaner.start();
+                }
+
+                if (ivy != null) {
+                    ivy.getEventManager().addIvyListener(new IvyListener() {
+                        public void progress(IvyEvent event) {
+                            disconnect();
+                            event.getSource().removeIvyListener(this);
+                        }
+                    }, EndResolveEvent.NAME);
+                }
+
+            } catch (IOException ex) {
+                closeConnection();
+                throw new IOException("impossible to connect to " + getUsername() + "@" + getHost()
+                        + " using " + getAuthentication() + ": " + ex.getMessage());
+            }
+            Message.verbose("connected to " + getHost());
+        }
+    }
+
+    private void updateLastCommandTime() {
+        lastCommand = System.currentTimeMillis();
+    }
+
+    private void exec(String command) throws IOException {
+        Message.debug("launching '" + command + "'");
+        process = Runtime.getRuntime().exec(command);
+        in = new InputStreamReader(process.getInputStream());
+        err = new InputStreamReader(process.getErrorStream());
+        out = new PrintWriter(process.getOutputStream());
+
+        errorsReader = new IvyThread() {
+            public void run() {
+                initContext();
+                int c;
+                try {
+                    // CheckStyle:InnerAssignment OFF
+                    while (err != null && (c = err.read()) != -1) {
+                        errors.append((char) c);
+                        errorsLastUpdateTime = System.currentTimeMillis();
+                    }
+                    // CheckStyle:InnerAssignment ON
+                } catch (IOException e) {
+                    // nothing to do
+                }
+            }
+        };
+        errorsReader.start();
+    }
+
+    private void checkInterrupted() {
+        if (ivy != null) {
+            ivy.checkInterrupted();
+        }
+    }
+
+    /**
+     * Called whenever an api level method end
+     */
+    private void cleanup(Exception ex) {
+        if (ex.getMessage().equals("connection timeout to " + getHost())) {
+            closeConnection();
+        } else {
+            disconnect();
+        }
+    }
+
+    /**
+     * Called whenever an api level method end
+     */
+    private void cleanup() {
+        if (reuseConnection == 0) {
+            disconnect();
+        }
+    }
+
+    public synchronized void disconnect() {
+        if (in != null) {
+            Message.verbose("disconnecting from " + getHost() + "... ");
+            try {
+                sendCommand("exit", false, DISCONNECT_COMMAND_TIMEOUT);
+            } catch (IOException e) {
+                // nothing I can do
+            } finally {
+                closeConnection();
+                Message.verbose("disconnected of " + getHost());
+            }
+        }
+    }
+
+    private synchronized void closeConnection() {
+        if (connectionCleaner != null) {
+            connectionCleaner.interrupt();
+        }
+        if (errorsReader != null) {
+            errorsReader.interrupt();
+        }
+        try {
+            process.destroy();
+        } catch (Exception ex) {
+            // nothing I can do
+        }
+        try {
+            in.close();
+        } catch (Exception e) {
+            // nothing I can do
+        }
+        try {
+            err.close();
+        } catch (Exception e) {
+            // nothing I can do
+        }
+        try {
+            out.close();
+        } catch (Exception e) {
+            // nothing I can do
+        }
+
+        connectionCleaner = null;
+        errorsReader = null;
+        process = null;
+        in = null;
+        out = null;
+        err = null;
+        Message.debug("connection to " + getHost() + " closed");
+    }
+
+    /**
+     * Parses a ls -l line and transforms it in a resource
+     * 
+     * @param file
+     * @param responseLine
+     * @return
+     */
+    protected Resource lslToResource(String file, String responseLine) {
+        if (responseLine == null || responseLine.startsWith("ls")) {
+            return new BasicResource(file, false, 0, 0, false);
+        } else {
+            String[] parts = responseLine.split("\\s+");
+            if (parts.length != LS_PARTS_NUMBER) {
+                Message.debug("unrecognized ls format: " + responseLine);
+                return new BasicResource(file, false, 0, 0, false);
+            } else {
+                try {
+                    long contentLength = Long.parseLong(parts[LS_SIZE_INDEX]);
+                    String date = parts[LS_DATE_INDEX1] + " " + parts[LS_DATE_INDEX2] + " "
+                            + parts[LS_DATE_INDEX3] + " " + parts[LS_DATE_INDEX4];
+                    return new BasicResource(file, true, contentLength, FORMAT.parse(date)
+                            .getTime(), false);
+                } catch (Exception ex) {
+                    Message.warn("impossible to parse server response: " + responseLine, ex);
+                    return new BasicResource(file, false, 0, 0, false);
+                }
+            }
+        }
+    }
+
+    protected String getSingleCommand(String command) {
+        return "vsh -noprompt -auth " + authentication + " " + username + "@" + host + " "
+                + command;
+    }
+
+    protected String getConnectionCommand() {
+        return "vsftp -noprompt -auth " + authentication + " " + username + "@" + host;
+    }
+
+    protected Pattern getExpectedDownloadMessage(String source, File to) {
+        return Pattern.compile("Downloading " + to.getName() + " from [^\\s]+");
+    }
+
+    protected Pattern getExpectedRemoveMessage(String destination) {
+        return Pattern.compile("Removing [^\\s]+");
+    }
+
+    protected Pattern getExpectedUploadMessage(File source, String to) {
+        return Pattern.compile("Uploading " + source.getName() + " to [^\\s]+");
+    }
+
+    public String getAuthentication() {
+        return authentication;
+    }
+
+    public void setAuthentication(String authentication) {
+        this.authentication = authentication;
+    }
+
+    public String getHost() {
+        return host;
+    }
+
+    public void setHost(String host) {
+        this.host = host;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    private static StringBuffer chomp(StringBuffer str) {
+        if (str == null || str.length() == 0) {
+            return str;
+        }
+        while ("\n".equals(str.substring(str.length() - 1))
+                || "\r".equals(str.substring(str.length() - 1))) {
+            str.setLength(str.length() - 1);
+        }
+        return str;
+    }
+
+    public String toString() {
+        return getName() + " " + getUsername() + "@" + getHost() + " (" + getAuthentication() + ")";
+    }
+
+    /**
+     * Sets the reuse connection time. The same connection will be reused if the time here does not
+     * last between two commands. O indicates that the connection should never be reused
+     * 
+     * @param time
+     */
+    public void setReuseConnection(long time) {
+        this.reuseConnection = time;
+    }
+
+    public long getReadTimeout() {
+        return readTimeout;
+    }
+
+    public void setReadTimeout(long readTimeout) {
+        this.readTimeout = readTimeout;
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/repository/vsftp/VsftpResource.java b/src/java/org/apache/ivy/plugins/repository/vsftp/VsftpResource.java
new file mode 100644
index 0000000..d28adb1
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/repository/vsftp/VsftpResource.java
@@ -0,0 +1,57 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.repository.vsftp;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.ivy.plugins.repository.LazyResource;
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.util.Message;
+
+public class VsftpResource extends LazyResource {
+    private VsftpRepository repository;
+
+    public VsftpResource(VsftpRepository repository, String file) {
+        super(file);
+        this.repository = repository;
+    }
+
+    protected void init() {
+        try {
+            init(repository.getInitResource(getName()));
+        } catch (IOException e) {
+            Message.debug(e);
+            Message.verbose(e.toString());
+        }
+    }
+
+    public InputStream openStream() throws IOException {
+        throw new UnsupportedOperationException(
+                "vsftp resource does not support openStream operation");
+    }
+
+    public Resource clone(String cloneName) {
+        try {
+            return repository.getResource(cloneName);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+}
diff --git a/src/java/org/apache/ivy/plugins/resolver/AbstractPatternsBasedResolver.java b/src/java/org/apache/ivy/plugins/resolver/AbstractPatternsBasedResolver.java
new file mode 100644
index 0000000..a0f205b
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/resolver/AbstractPatternsBasedResolver.java
@@ -0,0 +1,366 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.resolver;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DefaultArtifact;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.resolve.ResolveData;
+import org.apache.ivy.core.settings.IvyPattern;
+import org.apache.ivy.plugins.matcher.Matcher;
+import org.apache.ivy.plugins.resolver.util.ResolvedResource;
+import org.apache.ivy.plugins.resolver.util.ResourceMDParser;
+import org.apache.ivy.util.Message;
+
+/**
+ *
+ */
+public abstract class AbstractPatternsBasedResolver extends BasicResolver {
+
+    private List ivyPatterns = new ArrayList(); // List (String pattern)
+
+    private List artifactPatterns = new ArrayList(); // List (String pattern)
+
+    private boolean m2compatible = false;
+
+    public AbstractPatternsBasedResolver() {
+    }
+
+    public ResolvedResource findIvyFileRef(DependencyDescriptor dd, ResolveData data) {
+        ModuleRevisionId mrid = dd.getDependencyRevisionId();
+        if (isM2compatible()) {
+            mrid = convertM2IdForResourceSearch(mrid);
+        }
+        return findResourceUsingPatterns(mrid, ivyPatterns,
+            DefaultArtifact.newIvyArtifact(mrid, data.getDate()), getRMDParser(dd, data),
+            data.getDate());
+    }
+
+    public ResolvedResource findArtifactRef(Artifact artifact, Date date) {
+        ModuleRevisionId mrid = artifact.getModuleRevisionId();
+        if (isM2compatible()) {
+            mrid = convertM2IdForResourceSearch(mrid);
+        }
+        return findResourceUsingPatterns(mrid, artifactPatterns, artifact,
+            getDefaultRMDParser(artifact.getModuleRevisionId().getModuleId()), date);
+    }
+
+    public ResolvedResource findResource(ResolvedResource[] rress, ResourceMDParser rmdparser,
+            ModuleRevisionId mrid, Date date) {
+        if (isM2compatible()) {
+            // convert 'M2'-organisation back to 'Ivy'-organisation
+            mrid = convertM2ResourceSearchIdToNormal(mrid);
+        }
+        return super.findResource(rress, rmdparser, mrid, date);
+    }
+
+    protected ResolvedResource findResourceUsingPatterns(ModuleRevisionId moduleRevision,
+            List patternList, Artifact artifact, ResourceMDParser rmdparser, Date date) {
+        List resolvedResources = new ArrayList();
+        Set foundRevisions = new HashSet();
+        boolean dynamic = getSettings().getVersionMatcher().isDynamic(moduleRevision);
+        boolean stop = false;
+        for (Iterator iter = patternList.iterator(); iter.hasNext() && !stop;) {
+            String pattern = (String) iter.next();
+            ResolvedResource rres = findResourceUsingPattern(moduleRevision, pattern, artifact,
+                rmdparser, date);
+            if ((rres != null) && !foundRevisions.contains(rres.getRevision())) {
+                // only add the first found ResolvedResource for each revision
+                foundRevisions.add(rres.getRevision());
+                resolvedResources.add(rres);
+                stop = !dynamic; // stop iterating if we are not searching a dynamic revision
+            }
+        }
+
+        if (resolvedResources.size() > 1) {
+            ResolvedResource[] rress = (ResolvedResource[]) resolvedResources
+                    .toArray(new ResolvedResource[resolvedResources.size()]);
+            return findResource(rress, rmdparser, moduleRevision, date);
+        } else if (resolvedResources.size() == 1) {
+            return (ResolvedResource) resolvedResources.get(0);
+        } else {
+            return null;
+        }
+    }
+
+    protected abstract ResolvedResource findResourceUsingPattern(ModuleRevisionId mrid,
+            String pattern, Artifact artifact, ResourceMDParser rmdparser, Date date);
+
+    protected Collection findNames(Map tokenValues, String token) {
+        Collection names = new HashSet();
+        names.addAll(findIvyNames(tokenValues, token));
+        if (isAllownomd()) {
+            names.addAll(findArtifactNames(tokenValues, token));
+        }
+        return names;
+    }
+
+    protected Collection findIvyNames(Map tokenValues, String token) {
+        Collection names = new HashSet();
+        tokenValues = new HashMap(tokenValues);
+        tokenValues.put(IvyPatternHelper.ARTIFACT_KEY, "ivy");
+        tokenValues.put(IvyPatternHelper.TYPE_KEY, "ivy");
+        tokenValues.put(IvyPatternHelper.EXT_KEY, "xml");
+        if (isM2compatible()) {
+            convertM2TokenValuesForResourceSearch(tokenValues);
+        }
+        findTokenValues(names, getIvyPatterns(), tokenValues, token);
+        filterNames(names);
+        return names;
+    }
+
+    protected Collection findArtifactNames(Map tokenValues, String token) {
+        Collection names = new HashSet();
+        tokenValues = new HashMap(tokenValues);
+        tokenValues
+                .put(IvyPatternHelper.ARTIFACT_KEY, tokenValues.get(IvyPatternHelper.MODULE_KEY));
+        tokenValues.put(IvyPatternHelper.TYPE_KEY, "jar");
+        tokenValues.put(IvyPatternHelper.EXT_KEY, "jar");
+        if (isM2compatible()) {
+            convertM2TokenValuesForResourceSearch(tokenValues);
+        }
+        findTokenValues(names, getArtifactPatterns(), tokenValues, token);
+        filterNames(names);
+        return names;
+    }
+
+    public Map[] listTokenValues(String[] tokens, Map criteria) {
+        Set result = new LinkedHashSet();
+
+        // use ivy patterns
+        List ivyPatterns = getIvyPatterns();
+        Map tokenValues = new HashMap(criteria);
+        tokenValues.put(IvyPatternHelper.TYPE_KEY, "ivy");
+        tokenValues.put(IvyPatternHelper.EXT_KEY, getModuleDescriptorExtension());
+        if (isM2compatible()) {
+            convertM2TokenValuesForResourceSearch(tokenValues);
+        }
+        for (Iterator it = ivyPatterns.iterator(); it.hasNext();) {
+            String ivyPattern = (String) it.next();
+            result.addAll(resolveTokenValues(tokens, ivyPattern, tokenValues, false));
+        }
+
+        if (isAllownomd()) {
+            List artifactPatterns = getArtifactPatterns();
+            tokenValues = new HashMap(criteria);
+            tokenValues.put(IvyPatternHelper.TYPE_KEY, "jar");
+            tokenValues.put(IvyPatternHelper.EXT_KEY, "jar");
+            if (isM2compatible()) {
+                convertM2TokenValuesForResourceSearch(tokenValues);
+            }
+            for (Iterator it = artifactPatterns.iterator(); it.hasNext();) {
+                String artifactPattern = (String) it.next();
+                result.addAll(resolveTokenValues(tokens, artifactPattern, tokenValues, true));
+            }
+        }
+
+        return (Map[]) result.toArray(new Map[result.size()]);
+    }
+
+    protected String getModuleDescriptorExtension() {
+        return "xml";
+    }
+
+    private Set resolveTokenValues(String[] tokens, String pattern, Map criteria, boolean noMd) {
+        Set result = new LinkedHashSet();
+        Set tokenSet = new HashSet(Arrays.asList(tokens));
+
+        Map tokenValues = new HashMap();
+        for (Iterator it = criteria.entrySet().iterator(); it.hasNext();) {
+            Map.Entry entry = (Entry) it.next();
+            Object key = entry.getKey();
+            Object value = entry.getValue();
+            if (value instanceof String) {
+                tokenValues.put(key, value);
+            }
+        }
+
+        if (tokenSet.isEmpty()) {
+            // no more tokens to resolve
+            result.add(tokenValues);
+            return result;
+        }
+
+        String partiallyResolvedPattern = IvyPatternHelper.substituteTokens(pattern, tokenValues);
+        String token = IvyPatternHelper.getFirstToken(partiallyResolvedPattern);
+        if ((token == null) && exist(partiallyResolvedPattern)) {
+            // no more tokens to resolve
+            result.add(tokenValues);
+            return result;
+        }
+
+        tokenSet.remove(token);
+
+        Matcher matcher = null;
+        Object criteriaForToken = criteria.get(token);
+        if (criteriaForToken instanceof Matcher) {
+            matcher = (Matcher) criteriaForToken;
+        }
+
+        String[] values = listTokenValues(partiallyResolvedPattern, token);
+        if (values == null) {
+            return result;
+        }
+
+        List vals = new ArrayList(Arrays.asList(values));
+        filterNames(vals);
+
+        for (Iterator it = vals.iterator(); it.hasNext();) {
+            String value = (String) it.next();
+            if ((matcher != null) && !matcher.matches(value)) {
+                continue;
+            }
+
+            tokenValues.put(token, value);
+            String moreResolvedPattern = IvyPatternHelper.substituteTokens(
+                partiallyResolvedPattern, tokenValues);
+
+            Map newCriteria = new HashMap(criteria);
+            newCriteria.put(token, value);
+            if (noMd && "artifact".equals(token)) {
+                newCriteria.put("module", value);
+            } else if (noMd && "module".equals(token)) {
+                newCriteria.put("artifact", value);
+            }
+            result.addAll(resolveTokenValues(
+                (String[]) tokenSet.toArray(new String[tokenSet.size()]), moreResolvedPattern,
+                newCriteria, noMd));
+        }
+
+        return result;
+    }
+
+    protected abstract String[] listTokenValues(String pattern, String token);
+
+    protected abstract boolean exist(String path);
+
+    protected void findTokenValues(Collection names, List patterns, Map tokenValues, String token) {
+        // to be overridden by subclasses wanting to have listing features
+    }
+
+    /**
+     * example of pattern : ~/Workspace/[module]/[module].ivy.xml
+     * 
+     * @param pattern
+     */
+    public void addIvyPattern(String pattern) {
+        ivyPatterns.add(pattern);
+    }
+
+    public void addArtifactPattern(String pattern) {
+        artifactPatterns.add(pattern);
+    }
+
+    public List getIvyPatterns() {
+        return Collections.unmodifiableList(ivyPatterns);
+    }
+
+    public List getArtifactPatterns() {
+        return Collections.unmodifiableList(artifactPatterns);
+    }
+
+    protected void setIvyPatterns(List patterns) {
+        ivyPatterns = patterns;
+    }
+
+    protected void setArtifactPatterns(List patterns) {
+        artifactPatterns = patterns;
+    }
+
+    /*
+     * Methods respecting ivy conf method specifications
+     */
+    public void addConfiguredIvy(IvyPattern p) {
+        ivyPatterns.add(p.getPattern());
+    }
+
+    public void addConfiguredArtifact(IvyPattern p) {
+        artifactPatterns.add(p.getPattern());
+    }
+
+    public void dumpSettings() {
+        super.dumpSettings();
+        Message.debug("\t\tm2compatible: " + isM2compatible());
+        Message.debug("\t\tivy patterns:");
+        for (ListIterator iter = getIvyPatterns().listIterator(); iter.hasNext();) {
+            String p = (String) iter.next();
+            Message.debug("\t\t\t" + p);
+        }
+        Message.debug("\t\tartifact patterns:");
+        for (ListIterator iter = getArtifactPatterns().listIterator(); iter.hasNext();) {
+            String p = (String) iter.next();
+            Message.debug("\t\t\t" + p);
+        }
+    }
+
+    public boolean isM2compatible() {
+        return m2compatible;
+    }
+
+    public void setM2compatible(boolean compatible) {
+        m2compatible = compatible;
+    }
+
+    protected ModuleRevisionId convertM2ResourceSearchIdToNormal(ModuleRevisionId mrid) {
+        if (mrid.getOrganisation() == null || mrid.getOrganisation().indexOf('/') == -1) {
+            return mrid;
+        }
+        return ModuleRevisionId.newInstance(mrid.getOrganisation().replace('/', '.'),
+            mrid.getName(), mrid.getBranch(), mrid.getRevision(),
+            mrid.getQualifiedExtraAttributes());
+    }
+
+    protected ModuleRevisionId convertM2IdForResourceSearch(ModuleRevisionId mrid) {
+        if (mrid.getOrganisation() == null || mrid.getOrganisation().indexOf('.') == -1) {
+            return mrid;
+        }
+        return ModuleRevisionId.newInstance(mrid.getOrganisation().replace('.', '/'),
+            mrid.getName(), mrid.getBranch(), mrid.getRevision(),
+            mrid.getQualifiedExtraAttributes());
+    }
+
+    protected String convertM2OrganizationForResourceSearch(String org) {
+        return org.replace('.', '/');
+    }
+
+    protected void convertM2TokenValuesForResourceSearch(Map tokenValues) {
+        if (tokenValues.get(IvyPatternHelper.ORGANISATION_KEY) instanceof String) {
+            tokenValues.put(IvyPatternHelper.ORGANISATION_KEY,
+                convertM2OrganizationForResourceSearch((String) tokenValues
+                        .get(IvyPatternHelper.ORGANISATION_KEY)));
+        }
+    }
+
+}
diff --git a/src/java/org/apache/ivy/plugins/resolver/AbstractResolver.java b/src/java/org/apache/ivy/plugins/resolver/AbstractResolver.java
new file mode 100644
index 0000000..7f0d75f
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/resolver/AbstractResolver.java
@@ -0,0 +1,606 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.resolver;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.Map;
+
+import org.apache.ivy.core.IvyContext;
+import org.apache.ivy.core.LogOptions;
+import org.apache.ivy.core.RelativeUrlResolver;
+import org.apache.ivy.core.cache.ArtifactOrigin;
+import org.apache.ivy.core.cache.CacheDownloadOptions;
+import org.apache.ivy.core.cache.CacheMetadataOptions;
+import org.apache.ivy.core.cache.DownloadListener;
+import org.apache.ivy.core.cache.RepositoryCacheManager;
+import org.apache.ivy.core.cache.ResolutionCacheManager;
+import org.apache.ivy.core.event.EventManager;
+import org.apache.ivy.core.event.download.EndArtifactDownloadEvent;
+import org.apache.ivy.core.event.download.NeedArtifactEvent;
+import org.apache.ivy.core.event.download.StartArtifactDownloadEvent;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.module.status.StatusManager;
+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.IvyNode;
+import org.apache.ivy.core.resolve.ResolveData;
+import org.apache.ivy.core.resolve.ResolveOptions;
+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.core.settings.Validatable;
+import org.apache.ivy.plugins.conflict.ConflictManager;
+import org.apache.ivy.plugins.latest.ArtifactInfo;
+import org.apache.ivy.plugins.latest.LatestStrategy;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+import org.apache.ivy.plugins.namespace.NameSpaceHelper;
+import org.apache.ivy.plugins.namespace.Namespace;
+import org.apache.ivy.plugins.parser.ParserSettings;
+import org.apache.ivy.plugins.resolver.ChainResolver.ResolvedModuleRevisionArtifactInfo;
+import org.apache.ivy.plugins.resolver.util.HasLatestStrategy;
+import org.apache.ivy.plugins.resolver.util.ResolvedResource;
+import org.apache.ivy.util.Checks;
+import org.apache.ivy.util.Message;
+
+/**
+ * This abstract resolver only provides handling for resolver name
+ */
+public abstract class AbstractResolver implements DependencyResolver, HasLatestStrategy,
+        Validatable {
+
+    /**
+     * True if parsed ivy files should be validated against xsd, false if they should not, null if
+     * default behavior should be used
+     */
+    private Boolean validate = null;
+
+    private String name;
+
+    private ResolverSettings settings;
+
+    private EventManager eventManager = null; // may remain null
+
+    /**
+     * The latest strategy to use to find latest among several artifacts
+     */
+    private LatestStrategy latestStrategy;
+
+    private String latestStrategyName;
+
+    /**
+     * The namespace to which this resolver belongs
+     */
+    private Namespace namespace;
+
+    private String namespaceName;
+
+    private String cacheManagerName;
+
+    private RepositoryCacheManager repositoryCacheManager;
+
+    // used to store default values for nested cache
+    private String changingMatcherName;
+
+    private String changingPattern;
+
+    private Boolean checkmodified;
+
+    public ResolverSettings getSettings() {
+        return settings;
+    }
+
+    public ParserSettings getParserSettings() {
+        return new ResolverParserSettings();
+    }
+
+    public void setSettings(ResolverSettings ivy) {
+        settings = ivy;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    /**
+     * this method should remove sensitive information from a location to be displayed in a log
+     * 
+     * @param name
+     *            location
+     * @return location with sensitive data replaced by stars
+     */
+    public String hidePassword(String name) {
+        return name;
+    }
+
+    protected boolean doValidate(ResolveData data) {
+        if (validate != null) {
+            return validate.booleanValue();
+        } else {
+            return data.isValidate();
+        }
+    }
+
+    public boolean isValidate() {
+        return validate == null ? true : validate.booleanValue();
+    }
+
+    public void setValidate(boolean validate) {
+        this.validate = Boolean.valueOf(validate);
+    }
+
+    protected void checkInterrupted() {
+        IvyContext.getContext().getIvy().checkInterrupted();
+    }
+
+    public void reportFailure() {
+        Message.verbose("no failure report implemented by " + getName());
+    }
+
+    public void reportFailure(Artifact art) {
+        Message.verbose("no failure report implemented by " + getName());
+    }
+
+    public String[] listTokenValues(String token, Map otherTokenValues) {
+        return new String[0];
+    }
+
+    public Map[] listTokenValues(String[] tokens, Map criteria) {
+        return new Map[0];
+    }
+
+    public OrganisationEntry[] listOrganisations() {
+        return new OrganisationEntry[0];
+    }
+
+    public ModuleEntry[] listModules(OrganisationEntry org) {
+        return new ModuleEntry[0];
+    }
+
+    public RevisionEntry[] listRevisions(ModuleEntry module) {
+        return new RevisionEntry[0];
+    }
+
+    public String toString() {
+        return getName();
+    }
+
+    public void dumpSettings() {
+        Message.verbose("\t" + getName() + " [" + getTypeName() + "]");
+        Message.debug("\t\tcache: " + cacheManagerName);
+    }
+
+    public String getTypeName() {
+        return getClass().getName();
+    }
+
+    /**
+     * Default implementation downloads the artifact without taking advantage of its location
+     */
+    public ArtifactDownloadReport download(ArtifactOrigin artifact, DownloadOptions options) {
+        DownloadReport r = download(new Artifact[] {artifact.getArtifact()}, options);
+        return r.getArtifactReport(artifact.getArtifact());
+    }
+
+    public boolean exists(Artifact artifact) {
+        return locate(artifact) != null;
+    }
+
+    /**
+     * Default implementation actually download the artifact Subclasses should overwrite this to
+     * avoid the download
+     */
+    public ArtifactOrigin locate(Artifact artifact) {
+        DownloadReport dr = download(new Artifact[] {artifact}, new DownloadOptions());
+        if (dr == null) {
+            /*
+             * according to IVY-831, it seems that this actually happen sometime, while the contract
+             * of DependencyResolver says that it should never return null
+             */
+            throw new IllegalStateException("null download report returned by " + getName() + " ("
+                    + getClass().getName() + ")" + " when trying to download " + artifact);
+        }
+        ArtifactDownloadReport adr = dr.getArtifactReport(artifact);
+        return adr.getDownloadStatus() == DownloadStatus.FAILED ? null : adr.getArtifactOrigin();
+    }
+
+    public LatestStrategy getLatestStrategy() {
+        if (latestStrategy == null) {
+            initLatestStrategyFromSettings();
+        }
+        return latestStrategy;
+    }
+
+    private void initLatestStrategyFromSettings() {
+        if (getSettings() != null) {
+            if (latestStrategyName != null && !"default".equals(latestStrategyName)) {
+                latestStrategy = getSettings().getLatestStrategy(latestStrategyName);
+                if (latestStrategy == null) {
+                    throw new IllegalStateException("unknown latest strategy '"
+                            + latestStrategyName + "'");
+                }
+            } else {
+                latestStrategy = getSettings().getDefaultLatestStrategy();
+                Message.debug(getName() + ": no latest strategy defined: using default");
+            }
+        } else {
+            throw new IllegalStateException("no ivy instance found: "
+                    + "impossible to get a latest strategy without ivy instance");
+        }
+    }
+
+    public void setLatestStrategy(LatestStrategy latestStrategy) {
+        this.latestStrategy = latestStrategy;
+    }
+
+    public void setLatest(String strategyName) {
+        latestStrategyName = strategyName;
+    }
+
+    public String getLatest() {
+        if (latestStrategyName == null) {
+            latestStrategyName = "default";
+        }
+        return latestStrategyName;
+    }
+
+    public Namespace getNamespace() {
+        if (namespace == null) {
+            initNamespaceFromSettings();
+        }
+        return namespace;
+    }
+
+    private void initNamespaceFromSettings() {
+        if (getSettings() != null) {
+            if (namespaceName != null) {
+                namespace = getSettings().getNamespace(namespaceName);
+                if (namespace == null) {
+                    throw new IllegalStateException("unknown namespace '" + namespaceName + "'");
+                }
+            } else {
+                namespace = getSettings().getSystemNamespace();
+                Message.debug(getName() + ": no namespace defined: using system");
+            }
+        } else {
+            Message.verbose(getName()
+                    + ": no namespace defined nor ivy instance: using system namespace");
+            namespace = Namespace.SYSTEM_NAMESPACE;
+        }
+    }
+
+    public void setNamespace(String namespaceName) {
+        this.namespaceName = namespaceName;
+    }
+
+    // Namespace conversion methods
+    protected ModuleDescriptor toSystem(ModuleDescriptor md) {
+        return NameSpaceHelper.toSystem(md, getNamespace());
+    }
+
+    protected Artifact fromSystem(Artifact artifact) {
+        return NameSpaceHelper.transform(artifact, getNamespace().getFromSystemTransformer());
+    }
+
+    protected Artifact toSystem(Artifact artifact) {
+        return NameSpaceHelper.transform(artifact, getNamespace().getToSystemTransformer());
+    }
+
+    protected MetadataArtifactDownloadReport toSystem(MetadataArtifactDownloadReport report) {
+        return NameSpaceHelper.transform(report, getNamespace().getToSystemTransformer());
+    }
+
+    protected ResolvedModuleRevision toSystem(ResolvedModuleRevision rmr) {
+        return NameSpaceHelper.toSystem(rmr, getNamespace());
+    }
+
+    protected ModuleRevisionId toSystem(ModuleRevisionId resolvedMrid) {
+        return getNamespace().getToSystemTransformer().transform(resolvedMrid);
+    }
+
+    protected DependencyDescriptor fromSystem(DependencyDescriptor dd) {
+        return NameSpaceHelper.transform(dd, getNamespace().getFromSystemTransformer(), true);
+    }
+
+    protected DependencyDescriptor toSystem(DependencyDescriptor dd) {
+        return NameSpaceHelper.transform(dd, getNamespace().getToSystemTransformer(), true);
+    }
+
+    protected IvyNode getSystemNode(ResolveData data, ModuleRevisionId resolvedMrid) {
+        return data.getNode(toSystem(resolvedMrid));
+    }
+
+    protected ResolvedModuleRevision findModuleInCache(DependencyDescriptor dd, ResolveData data) {
+        return findModuleInCache(dd, data, false);
+    }
+
+    protected ResolvedModuleRevision findModuleInCache(DependencyDescriptor dd, ResolveData data,
+            boolean anyResolver) {
+        ResolvedModuleRevision rmr = getRepositoryCacheManager().findModuleInCache(dd,
+            dd.getDependencyRevisionId(), getCacheOptions(data), anyResolver ? null : getName());
+        if (rmr == null) {
+            return null;
+        }
+        if (data.getReport() != null
+                && data.isBlacklisted(data.getReport().getConfiguration(), rmr.getId())) {
+            Message.verbose("\t" + getName() + ": found revision in cache: " + rmr.getId()
+                    + " for " + dd + ", but it is blacklisted");
+            return null;
+        }
+        return rmr;
+    }
+
+    public void setChangingMatcher(String changingMatcherName) {
+        this.changingMatcherName = changingMatcherName;
+    }
+
+    protected String getChangingMatcherName() {
+        return changingMatcherName;
+    }
+
+    public void setChangingPattern(String changingPattern) {
+        this.changingPattern = changingPattern;
+    }
+
+    protected String getChangingPattern() {
+        return changingPattern;
+    }
+
+    public void setCheckmodified(boolean check) {
+        checkmodified = Boolean.valueOf(check);
+    }
+
+    public RepositoryCacheManager getRepositoryCacheManager() {
+        if (repositoryCacheManager == null) {
+            initRepositoryCacheManagerFromSettings();
+        }
+        return repositoryCacheManager;
+    }
+
+    private void initRepositoryCacheManagerFromSettings() {
+        if (cacheManagerName == null) {
+            repositoryCacheManager = settings.getDefaultRepositoryCacheManager();
+            if (repositoryCacheManager == null) {
+                throw new IllegalStateException(
+                        "no default cache manager defined with current settings");
+            }
+        } else {
+            repositoryCacheManager = settings.getRepositoryCacheManager(cacheManagerName);
+            if (repositoryCacheManager == null) {
+                throw new IllegalStateException("unknown cache manager '" + cacheManagerName
+                        + "'. Available caches are "
+                        + Arrays.asList(settings.getRepositoryCacheManagers()));
+            }
+        }
+    }
+
+    public void setRepositoryCacheManager(RepositoryCacheManager repositoryCacheManager) {
+        this.cacheManagerName = repositoryCacheManager.getName();
+        this.repositoryCacheManager = repositoryCacheManager;
+    }
+
+    public void setCache(String cacheName) {
+        cacheManagerName = cacheName;
+    }
+
+    public void setEventManager(EventManager eventManager) {
+        this.eventManager = eventManager;
+    }
+
+    public EventManager getEventManager() {
+        return eventManager;
+    }
+
+    public void validate() {
+        initRepositoryCacheManagerFromSettings();
+        initNamespaceFromSettings();
+        initLatestStrategyFromSettings();
+    }
+
+    protected CacheMetadataOptions getCacheOptions(ResolveData data) {
+        return (CacheMetadataOptions) new CacheMetadataOptions()
+                .setChangingMatcherName(getChangingMatcherName())
+                .setChangingPattern(getChangingPattern())
+                .setCheckTTL(!data.getOptions().isUseCacheOnly())
+                .setCheckmodified(
+                    data.getOptions().isUseCacheOnly() ? Boolean.FALSE : checkmodified)
+                .setValidate(doValidate(data)).setNamespace(getNamespace())
+                .setForce(data.getOptions().isRefresh())
+                .setListener(getDownloadListener(getDownloadOptions(data.getOptions())));
+    }
+
+    protected CacheDownloadOptions getCacheDownloadOptions(DownloadOptions options) {
+        CacheDownloadOptions cacheDownloadOptions = new CacheDownloadOptions();
+        cacheDownloadOptions.setListener(getDownloadListener(options));
+        return cacheDownloadOptions;
+    }
+
+    protected DownloadOptions getDownloadOptions(ResolveOptions options) {
+        return (DownloadOptions) new DownloadOptions().setLog(options.getLog());
+    }
+
+    public void abortPublishTransaction() throws IOException {
+        /* Default implementation is a no-op */
+    }
+
+    public void commitPublishTransaction() throws IOException {
+        /* Default implementation is a no-op */
+    }
+
+    public void beginPublishTransaction(ModuleRevisionId module, boolean overwrite)
+            throws IOException {
+        /* Default implementation is a no-op */
+    }
+
+    private DownloadListener getDownloadListener(final DownloadOptions options) {
+        return new DownloadListener() {
+            public void needArtifact(RepositoryCacheManager cache, Artifact artifact) {
+                if (eventManager != null) {
+                    eventManager
+                            .fireIvyEvent(new NeedArtifactEvent(AbstractResolver.this, artifact));
+                }
+            }
+
+            public void startArtifactDownload(RepositoryCacheManager cache, ResolvedResource rres,
+                    Artifact artifact, ArtifactOrigin origin) {
+                if (artifact.isMetadata() || LogOptions.LOG_QUIET.equals(options.getLog())) {
+                    Message.verbose("downloading " + rres.getResource() + " ...");
+                } else {
+                    Message.info("downloading " + rres.getResource() + " ...");
+                }
+                if (eventManager != null) {
+                    eventManager.fireIvyEvent(new StartArtifactDownloadEvent(AbstractResolver.this,
+                            artifact, origin));
+                }
+            }
+
+            public void endArtifactDownload(RepositoryCacheManager cache, Artifact artifact,
+                    ArtifactDownloadReport adr, File archiveFile) {
+                if (eventManager != null) {
+                    eventManager.fireIvyEvent(new EndArtifactDownloadEvent(AbstractResolver.this,
+                            artifact, adr, archiveFile));
+                }
+            }
+        };
+    }
+
+    /**
+     * Returns true if rmr1 is after rmr2, using the latest strategy to determine which is the
+     * latest
+     * 
+     * @param rmr1
+     * @param rmr2
+     * @return
+     */
+    protected boolean isAfter(ResolvedModuleRevision rmr1, ResolvedModuleRevision rmr2, Date date) {
+        ArtifactInfo[] ais = new ArtifactInfo[] {new ResolvedModuleRevisionArtifactInfo(rmr1),
+                new ResolvedModuleRevisionArtifactInfo(rmr2)};
+        return getLatestStrategy().findLatest(ais, date) == ais[0];
+    }
+
+    protected ResolvedModuleRevision checkLatest(DependencyDescriptor dd,
+            ResolvedModuleRevision newModuleFound, ResolveData data) {
+        Checks.checkNotNull(dd, "dd");
+        Checks.checkNotNull(data, "data");
+
+        // check if latest is asked and compare to return the most recent
+        ResolvedModuleRevision previousModuleFound = data.getCurrentResolvedModuleRevision();
+        String newModuleDesc = describe(newModuleFound);
+        Message.debug("\tchecking " + newModuleDesc + " against " + describe(previousModuleFound));
+        if (previousModuleFound == null) {
+            Message.debug("\tmodule revision kept as first found: " + newModuleDesc);
+            saveModuleRevisionIfNeeded(dd, newModuleFound);
+            return newModuleFound;
+        } else if (isAfter(newModuleFound, previousModuleFound, data.getDate())) {
+            Message.debug("\tmodule revision kept as younger: " + newModuleDesc);
+            saveModuleRevisionIfNeeded(dd, newModuleFound);
+            return newModuleFound;
+        } else if (!newModuleFound.getDescriptor().isDefault()
+                && previousModuleFound.getDescriptor().isDefault()) {
+            Message.debug("\tmodule revision kept as better (not default): " + newModuleDesc);
+            saveModuleRevisionIfNeeded(dd, newModuleFound);
+            return newModuleFound;
+        } else {
+            Message.debug("\tmodule revision discarded as older: " + newModuleDesc);
+            return previousModuleFound;
+        }
+    }
+
+    protected void saveModuleRevisionIfNeeded(DependencyDescriptor dd,
+            ResolvedModuleRevision newModuleFound) {
+        if (newModuleFound != null
+                && getSettings().getVersionMatcher().isDynamic(dd.getDependencyRevisionId())) {
+            getRepositoryCacheManager().saveResolvedRevision(dd.getDependencyRevisionId(),
+                newModuleFound.getId().getRevision());
+        }
+    }
+
+    private String describe(ResolvedModuleRevision rmr) {
+        if (rmr == null) {
+            return "[none]";
+        }
+        return rmr.getId() + (rmr.getDescriptor().isDefault() ? "[default]" : "") + " from "
+                + rmr.getResolver().getName();
+    }
+
+    private class ResolverParserSettings implements ParserSettings {
+
+        public ConflictManager getConflictManager(String name) {
+            return AbstractResolver.this.getSettings().getConflictManager(name);
+        }
+
+        public Namespace getContextNamespace() {
+            return AbstractResolver.this.getNamespace();
+        }
+
+        public String getDefaultBranch(ModuleId moduleId) {
+            return AbstractResolver.this.getSettings().getDefaultBranch(moduleId);
+        }
+
+        public PatternMatcher getMatcher(String matcherName) {
+            return AbstractResolver.this.getSettings().getMatcher(matcherName);
+        }
+
+        public Namespace getNamespace(String namespace) {
+            return AbstractResolver.this.getSettings().getNamespace(namespace);
+        }
+
+        public RelativeUrlResolver getRelativeUrlResolver() {
+            return AbstractResolver.this.getSettings().getRelativeUrlResolver();
+        }
+
+        public ResolutionCacheManager getResolutionCacheManager() {
+            return AbstractResolver.this.getSettings().getResolutionCacheManager();
+        }
+
+        public DependencyResolver getResolver(ModuleRevisionId mRevId) {
+            return AbstractResolver.this.getSettings().getResolver(mRevId);
+        }
+
+        public StatusManager getStatusManager() {
+            return AbstractResolver.this.getSettings().getStatusManager();
+        }
+
+        public File resolveFile(String filename) {
+            return AbstractResolver.this.getSettings().resolveFile(filename);
+        }
+
+        public Map substitute(Map strings) {
+            return AbstractResolver.this.getSettings().substitute(strings);
+        }
+
+        public String substitute(String value) {
+            return AbstractResolver.this.getSettings().substitute(value);
+        }
+
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/resolver/AbstractSshBasedResolver.java b/src/java/org/apache/ivy/plugins/resolver/AbstractSshBasedResolver.java
new file mode 100644
index 0000000..f85ce86
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/resolver/AbstractSshBasedResolver.java
@@ -0,0 +1,147 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.resolver;
+
+import java.io.File;
+
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.ivy.plugins.repository.ssh.AbstractSshBasedRepository;
+
+/**
+ * Abstract base class for all resolvers using SSH All necessary connection parameters can be set
+ * here via attributes. However all attributes defined in the pattern url of the resolver will have
+ * higher priority and will overwrite the values given here. To specify connection parameters in the
+ * pattern, you have to specify a full url and not just a path as pattern. e.g.
+ * pattern="/path/to/my/repos/[artifact].[ext]" will use all connection parameters from this class
+ * e.g. pattern="ssh://myserver.com/path/to/my/repos/[artifact].[ext]" will use all parameters from
+ * this class with the exception of the host, which will be "myserver.com" e.g.
+ * pattern="sftp://user:geheim@myserver.com:8022/path/to/my/repos/[artifact].[ext]" will use only
+ * the keyFile and keyFilePassword from this class (if needed). Rest will come from the url.
+ */
+public abstract class AbstractSshBasedResolver extends RepositoryResolver {
+
+    private boolean passfileSet = false;
+
+    public AbstractSshBasedResolver() {
+        super();
+    }
+
+    private AbstractSshBasedRepository getSshBasedRepository() {
+        return ((AbstractSshBasedRepository) getRepository());
+    }
+
+    /**
+     * Sets the location of the Public Key file to use for authentication
+     * 
+     * @param filePath
+     *            full file path name
+     */
+    public void setKeyFile(File filePath) {
+        getSshBasedRepository().setKeyFile(filePath);
+    }
+
+    /**
+     * Determines whether a local SSH agent may be used for authentication
+     * 
+     * @param allowedAgentUse
+     *            true if an agent may be used if available
+     */
+    public void setAllowedAgentUse(boolean allowedAgentUse) {
+        getSshBasedRepository().setAllowedAgentUse(allowedAgentUse);
+    }
+
+    /**
+     * Optional password file. If set the repository will use it as an encypted property file, to
+     * load username and passwd entries, and to store them if the user choose to do so. Defaults to
+     * user.dir/.ivy/[host].sftp.passwd, set it to null to disable this feature.
+     */
+    public void setPassfile(File passfile) {
+        getSshBasedRepository().setPassFile(passfile);
+        passfileSet = true;
+    }
+
+    public void setSettings(IvySettings settings) {
+        super.setSettings(settings);
+        if (!passfileSet) {
+            getSshBasedRepository().setPassFile(
+                new File(settings.getDefaultIvyUserDir(), getSshBasedRepository().getHost()
+                        + ".ssh.passwd"));
+        }
+    }
+
+    /**
+     * Sets the password to authenticate the user if password based login is used if no password is
+     * set and password based login is used, user will be prompted for it the password can also be
+     * set by using a full url for the pattern, like
+     * "sftp://user:password@myserver.com/path/to/repos/[artifact].[ext]"
+     * 
+     * @param password
+     *            to use
+     */
+    public void setUserPassword(String password) {
+        getSshBasedRepository().setUserPassword(password);
+    }
+
+    /**
+     * Sets the password to use for decrypting key file (if it is encrypted) if no password is set
+     * and the keyfile is encrypted, the user will be prompted for the password if the keyfile is
+     * passwordless, this parameter will be ignored if given
+     * 
+     * @param password
+     *            to use
+     */
+    public void setKeyFilePassword(String password) {
+        getSshBasedRepository().setKeyFilePassword(password);
+    }
+
+    /**
+     * sets the user to use for the ssh communication the user can also be set by using a full url
+     * for the pattern, like "ssh://user@myserver.com/path/to/repos/[artifact].[ext]"
+     * 
+     * @param user
+     *            on the target system
+     */
+    public void setUser(String user) {
+        getSshBasedRepository().setUser(user);
+    }
+
+    /**
+     * sets the host to use for the ssh communication the host can also be set by using a full url
+     * for the pattern, like "ssh://myserver.com/path/to/repos/[artifact].[ext]"
+     * 
+     * @param host
+     *            of the target system
+     */
+    public void setHost(String host) {
+        getSshBasedRepository().setHost(host);
+    }
+
+    /**
+     * sets the port to use for the ssh communication port 22 is default the port can also be set by
+     * using a full url for the pattern, like
+     * "sftp://myserver.com:8022/path/to/repos/[artifact].[ext]"
+     * 
+     * @param port
+     *            of the target system
+     */
+    public void setPort(int port) {
+        getSshBasedRepository().setPort(port);
+    }
+
+    public abstract String getTypeName();
+}
diff --git a/src/java/org/apache/ivy/plugins/resolver/BasicResolver.java b/src/java/org/apache/ivy/plugins/resolver/BasicResolver.java
new file mode 100644
index 0000000..ce1309d
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/resolver/BasicResolver.java
@@ -0,0 +1,1164 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.resolver;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.ivy.core.IvyContext;
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.core.LogOptions;
+import org.apache.ivy.core.cache.ArtifactOrigin;
+import org.apache.ivy.core.cache.ModuleDescriptorWriter;
+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.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleId;
+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.report.DownloadStatus;
+import org.apache.ivy.core.report.MetadataArtifactDownloadReport;
+import org.apache.ivy.core.resolve.DownloadOptions;
+import org.apache.ivy.core.resolve.IvyNode;
+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.conflict.ConflictManager;
+import org.apache.ivy.plugins.namespace.Namespace;
+import org.apache.ivy.plugins.parser.ModuleDescriptorParser;
+import org.apache.ivy.plugins.parser.ModuleDescriptorParserRegistry;
+import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorWriter;
+import org.apache.ivy.plugins.repository.ArtifactResourceResolver;
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.plugins.repository.ResourceDownloader;
+import org.apache.ivy.plugins.repository.file.FileRepository;
+import org.apache.ivy.plugins.repository.file.FileResource;
+import org.apache.ivy.plugins.repository.url.URLRepository;
+import org.apache.ivy.plugins.repository.url.URLResource;
+import org.apache.ivy.plugins.resolver.util.MDResolvedResource;
+import org.apache.ivy.plugins.resolver.util.ResolvedResource;
+import org.apache.ivy.plugins.resolver.util.ResourceMDParser;
+import org.apache.ivy.plugins.version.VersionMatcher;
+import org.apache.ivy.util.Checks;
+import org.apache.ivy.util.ChecksumHelper;
+import org.apache.ivy.util.HostUtil;
+import org.apache.ivy.util.Message;
+
+/**
+ *
+ */
+public abstract class BasicResolver extends AbstractResolver {
+    public static final String DESCRIPTOR_OPTIONAL = "optional";
+
+    public static final String DESCRIPTOR_REQUIRED = "required";
+
+    /**
+     * Exception thrown internally in getDependency to indicate a dependency is unresolved.
+     * <p>
+     * Due to the contract of getDependency, this exception is never thrown publicly, but rather
+     * converted in a message (either error or verbose) and returning null
+     * </p>
+     */
+    private static class UnresolvedDependencyException extends RuntimeException {
+        private boolean error;
+
+        /**
+         * Dependency has not been resolved. This is not an error and won't log any message.
+         */
+        public UnresolvedDependencyException() {
+            this("", false);
+        }
+
+        /**
+         * Dependency has not been resolved. This is an error and will log a message.
+         */
+        public UnresolvedDependencyException(String message) {
+            this(message, true);
+        }
+
+        /**
+         * Dependency has not been resolved. The boolean tells if it is an error or not, a message
+         * will be logged if non empty.
+         */
+        public UnresolvedDependencyException(String message, boolean error) {
+            super(message);
+            this.error = error;
+        }
+
+        public boolean isError() {
+            return error;
+        }
+    }
+
+    public static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyyMMddHHmmss");
+
+    private String workspaceName;
+
+    /**
+     * True if the files resolved are dependent of the environment from which they have been
+     * resolved, false otherwise. In general, relative paths are dependent of the environment, and
+     * absolute paths including machine reference are not.
+     */
+    private boolean envDependent = true;
+
+    private List ivyattempts = new ArrayList();
+
+    private Map artattempts = new HashMap();
+
+    private boolean checkconsistency = true;
+
+    private boolean allownomd = true;
+
+    private boolean force = false;
+
+    private String checksums = null;
+
+    private URLRepository extartifactrep = new URLRepository(); // used only to download
+
+    // external artifacts
+
+    public BasicResolver() {
+        workspaceName = HostUtil.getLocalHostName();
+    }
+
+    public String getWorkspaceName() {
+        return workspaceName;
+    }
+
+    public void setWorkspaceName(String workspaceName) {
+        this.workspaceName = workspaceName;
+    }
+
+    public boolean isEnvDependent() {
+        return envDependent;
+    }
+
+    public void setEnvDependent(boolean envDependent) {
+        this.envDependent = envDependent;
+    }
+
+    public ResolvedModuleRevision getDependency(DependencyDescriptor dd, ResolveData data)
+            throws ParseException {
+        IvyContext context = IvyContext.pushNewCopyContext();
+        try {
+            ResolvedModuleRevision mr = data.getCurrentResolvedModuleRevision();
+            if (mr != null) {
+                if (shouldReturnResolvedModule(dd, mr)) {
+                    return mr;
+                }
+            }
+
+            if (isForce()) {
+                dd = dd.clone(ModuleRevisionId.newInstance(dd.getDependencyRevisionId(),
+                    "latest.integration"));
+            }
+            DependencyDescriptor systemDd = dd;
+            DependencyDescriptor nsDd = fromSystem(dd);
+            context.setDependencyDescriptor(systemDd);
+            context.setResolveData(data);
+
+            clearIvyAttempts();
+            clearArtifactAttempts();
+            ModuleRevisionId systemMrid = systemDd.getDependencyRevisionId();
+            ModuleRevisionId nsMrid = nsDd.getDependencyRevisionId();
+
+            checkRevision(systemMrid);
+
+            boolean isDynamic = getAndCheckIsDynamic(systemMrid);
+
+            // we first search for the dependency in cache
+            ResolvedModuleRevision rmr = null;
+            rmr = findModuleInCache(systemDd, data);
+            if (rmr != null) {
+                if (rmr.getDescriptor().isDefault() && rmr.getResolver() != this) {
+                    Message.verbose("\t" + getName() + ": found revision in cache: " + systemMrid
+                            + " (resolved by " + rmr.getResolver().getName()
+                            + "): but it's a default one, maybe we can find a better one");
+                } else if (isForce() && rmr.getResolver() != this) {
+                    Message.verbose("\t" + getName() + ": found revision in cache: " + systemMrid
+                            + " (resolved by " + rmr.getResolver().getName()
+                            + "): but we are in force mode, let's try to find one ourself");
+                } else {
+                    Message.verbose("\t" + getName() + ": revision in cache: " + systemMrid);
+                    return checkLatest(systemDd, checkForcedResolvedModuleRevision(rmr), data);
+                }
+            }
+            if (data.getOptions().isUseCacheOnly()) {
+                throw new UnresolvedDependencyException("\t" + getName()
+                        + " (useCacheOnly) : no ivy file found for " + systemMrid, false);
+            }
+
+            checkInterrupted();
+
+            ResolvedResource ivyRef = findIvyFileRef(nsDd, data);
+            checkInterrupted();
+
+            // get module descriptor
+            ModuleDescriptor nsMd;
+            ModuleDescriptor systemMd = null;
+            if (ivyRef == null) {
+                if (!isAllownomd()) {
+                    throw new UnresolvedDependencyException("\t" + getName()
+                            + ": no ivy file found for " + systemMrid, false);
+                }
+                nsMd = DefaultModuleDescriptor.newDefaultInstance(nsMrid,
+                    nsDd.getAllDependencyArtifacts());
+                ResolvedResource artifactRef = findFirstArtifactRef(nsMd, nsDd, data);
+                checkInterrupted();
+                if (artifactRef == null) {
+                    throw new UnresolvedDependencyException("\t" + getName()
+                            + ": no ivy file nor artifact found for " + systemMrid, false);
+                } else {
+                    long lastModified = artifactRef.getLastModified();
+                    if (lastModified != 0 && nsMd instanceof DefaultModuleDescriptor) {
+                        ((DefaultModuleDescriptor) nsMd).setLastModified(lastModified);
+                    }
+                    Message.verbose("\t" + getName() + ": no ivy file found for " + systemMrid
+                            + ": using default data");
+                    if (isDynamic) {
+                        nsMd.setResolvedModuleRevisionId(ModuleRevisionId.newInstance(nsMrid,
+                            artifactRef.getRevision()));
+                    }
+                    systemMd = toSystem(nsMd);
+                    MetadataArtifactDownloadReport madr = new MetadataArtifactDownloadReport(
+                            systemMd.getMetadataArtifact());
+                    madr.setDownloadStatus(DownloadStatus.NO);
+                    madr.setSearched(true);
+                    rmr = new ResolvedModuleRevision(this, this, systemMd, madr, isForce());
+                    getRepositoryCacheManager().cacheModuleDescriptor(this, artifactRef,
+                        toSystem(dd), systemMd.getAllArtifacts()[0], null, getCacheOptions(data));
+                }
+            } else {
+                if (ivyRef instanceof MDResolvedResource) {
+                    rmr = ((MDResolvedResource) ivyRef).getResolvedModuleRevision();
+                }
+                if (rmr == null) {
+                    rmr = parse(ivyRef, systemDd, data);
+                    if (rmr == null) {
+                        throw new UnresolvedDependencyException();
+                    }
+                }
+                if (!rmr.getReport().isDownloaded() && rmr.getReport().getLocalFile() != null) {
+                    return checkLatest(systemDd, checkForcedResolvedModuleRevision(rmr), data);
+                } else {
+                    nsMd = rmr.getDescriptor();
+
+                    // check descriptor data is in sync with resource revision and names
+                    systemMd = toSystem(nsMd);
+                    if (isCheckconsistency()) {
+                        checkDescriptorConsistency(systemMrid, systemMd, ivyRef);
+                        checkDescriptorConsistency(nsMrid, nsMd, ivyRef);
+                    } else {
+                        if (systemMd instanceof DefaultModuleDescriptor) {
+                            DefaultModuleDescriptor defaultMd = (DefaultModuleDescriptor) systemMd;
+                            ModuleRevisionId revision = getRevision(ivyRef, systemMrid, systemMd);
+                            defaultMd.setModuleRevisionId(revision);
+                            defaultMd.setResolvedModuleRevisionId(revision);
+                        } else {
+                            Message.warn("consistency disabled with instance of non DefaultModuleDescriptor..."
+                                    + " module info can't be updated, so consistency check will be done");
+                            checkDescriptorConsistency(nsMrid, nsMd, ivyRef);
+                            checkDescriptorConsistency(systemMrid, systemMd, ivyRef);
+                        }
+                    }
+                    rmr = new ResolvedModuleRevision(this, this, systemMd,
+                            toSystem(rmr.getReport()), isForce());
+                }
+            }
+
+            resolveAndCheckRevision(systemMd, systemMrid, ivyRef, isDynamic);
+            resolveAndCheckPublicationDate(systemDd, systemMd, systemMrid, data);
+            checkNotConvertedExclusionRule(systemMd, ivyRef, data);
+
+            if (ivyRef == null || ivyRef.getResource() != null) {
+                cacheModuleDescriptor(systemMd, systemMrid, ivyRef, rmr);
+            }
+
+            return checkLatest(systemDd, checkForcedResolvedModuleRevision(rmr), data);
+        } catch (UnresolvedDependencyException ex) {
+            if (ex.getMessage().length() > 0) {
+                if (ex.isError()) {
+                    Message.error(ex.getMessage());
+                } else {
+                    Message.verbose(ex.getMessage());
+                }
+            }
+            return data.getCurrentResolvedModuleRevision();
+        } finally {
+            IvyContext.popContext();
+        }
+    }
+
+    protected boolean shouldReturnResolvedModule(DependencyDescriptor dd, ResolvedModuleRevision mr) {
+        // a resolved module revision has already been found by a prior dependency resolver
+        // let's see if it should be returned and bypass this resolver
+
+        ModuleRevisionId mrid = dd.getDependencyRevisionId();
+        boolean isDynamic = getSettings().getVersionMatcher().isDynamic(mrid);
+        boolean shouldReturn = mr.isForce();
+        shouldReturn |= !isDynamic && !mr.getDescriptor().isDefault();
+        shouldReturn &= !isForce();
+
+        return shouldReturn;
+    }
+
+    private ResolvedModuleRevision checkForcedResolvedModuleRevision(ResolvedModuleRevision rmr) {
+        if (rmr == null) {
+            return null;
+        }
+        if (!isForce() || rmr.isForce()) {
+            return rmr;
+        }
+        return new ResolvedModuleRevision(rmr.getResolver(), rmr.getArtifactResolver(),
+                rmr.getDescriptor(), rmr.getReport(), true);
+    }
+
+    private void cacheModuleDescriptor(ModuleDescriptor systemMd, ModuleRevisionId systemMrid,
+            ResolvedResource ivyRef, ResolvedModuleRevision rmr) {
+        RepositoryCacheManager cacheManager = getRepositoryCacheManager();
+
+        final ModuleDescriptorParser parser = systemMd.getParser();
+
+        // the metadata artifact which was used to cache the original metadata file
+        Artifact requestedMetadataArtifact = ivyRef == null ? systemMd.getMetadataArtifact()
+                : parser.getMetadataArtifact(
+                    ModuleRevisionId.newInstance(systemMrid, systemMd.getRevision()),
+                    ivyRef.getResource());
+
+        cacheManager.originalToCachedModuleDescriptor(this, ivyRef, requestedMetadataArtifact, rmr,
+            new ModuleDescriptorWriter() {
+                public void write(ResolvedResource originalMdResource, ModuleDescriptor md,
+                        File src, File dest) throws IOException, ParseException {
+                    if (originalMdResource == null) {
+                        // a basic ivy file is written containing default data
+                        XmlModuleDescriptorWriter.write(md, dest);
+                    } else {
+                        // copy and update ivy file from source to cache
+                        parser.toIvyFile(new FileInputStream(src),
+                            originalMdResource.getResource(), dest, md);
+                        long repLastModified = originalMdResource.getLastModified();
+                        if (repLastModified > 0) {
+                            dest.setLastModified(repLastModified);
+                        }
+                    }
+                }
+            });
+    }
+
+    private void checkNotConvertedExclusionRule(ModuleDescriptor systemMd, ResolvedResource ivyRef,
+            ResolveData data) {
+        if (!getNamespace().equals(Namespace.SYSTEM_NAMESPACE) && !systemMd.isDefault()
+                && data.getSettings().logNotConvertedExclusionRule()
+                && systemMd instanceof DefaultModuleDescriptor) {
+            DefaultModuleDescriptor dmd = (DefaultModuleDescriptor) systemMd;
+            if (dmd.isNamespaceUseful()) {
+                Message.warn("the module descriptor " + ivyRef.getResource()
+                        + " has information which can't be converted into "
+                        + "the system namespace. "
+                        + "It will require the availability of the namespace '"
+                        + getNamespace().getName() + "' to be fully usable.");
+            }
+        }
+    }
+
+    private void resolveAndCheckPublicationDate(DependencyDescriptor systemDd,
+            ModuleDescriptor systemMd, ModuleRevisionId systemMrid, ResolveData data) {
+        // resolve and check publication date
+        if (data.getDate() != null) {
+            long pubDate = getPublicationDate(systemMd, systemDd, data);
+            if (pubDate > data.getDate().getTime()) {
+                throw new UnresolvedDependencyException("\t" + getName()
+                        + ": unacceptable publication date => was=" + new Date(pubDate)
+                        + " required=" + data.getDate());
+            } else if (pubDate == -1) {
+                throw new UnresolvedDependencyException("\t" + getName()
+                        + ": impossible to guess publication date: artifact missing for "
+                        + systemMrid);
+            }
+            systemMd.setResolvedPublicationDate(new Date(pubDate));
+        }
+    }
+
+    protected void checkModuleDescriptorRevision(ModuleDescriptor systemMd,
+            ModuleRevisionId systemMrid) {
+        if (!getSettings().getVersionMatcher().accept(systemMrid, systemMd)) {
+            throw new UnresolvedDependencyException("\t" + getName()
+                    + ": unacceptable revision => was="
+                    + systemMd.getResolvedModuleRevisionId().getRevision() + " required="
+                    + systemMrid.getRevision());
+        }
+    }
+
+    private boolean getAndCheckIsDynamic(ModuleRevisionId systemMrid) {
+        boolean isDynamic = getSettings().getVersionMatcher().isDynamic(systemMrid);
+        if (isDynamic && !acceptLatest()) {
+            throw new UnresolvedDependencyException("dynamic revisions not handled by "
+                    + getClass().getName() + ". impossible to resolve " + systemMrid);
+        }
+        return isDynamic;
+    }
+
+    private void checkRevision(ModuleRevisionId systemMrid) {
+        // check revision
+        int index = systemMrid.getRevision().indexOf("@");
+        if (index != -1 && !systemMrid.getRevision().substring(index + 1).equals(workspaceName)) {
+            throw new UnresolvedDependencyException("\t" + getName() + ": unhandled revision => "
+                    + systemMrid.getRevision());
+        }
+    }
+
+    private void resolveAndCheckRevision(ModuleDescriptor systemMd,
+            ModuleRevisionId dependencyConstraint, ResolvedResource ivyRef, boolean isDynamic) {
+        // we get the resolved module revision id from the descriptor: it may contain extra
+        // attributes that were not included in the dependency constraint
+        ModuleRevisionId resolvedMrid = systemMd.getResolvedModuleRevisionId();
+        if (resolvedMrid.getRevision() == null || resolvedMrid.getRevision().length() == 0
+                || resolvedMrid.getRevision().startsWith("working@")) {
+            if (!isDynamic) {
+                resolvedMrid = ModuleRevisionId.newInstance(resolvedMrid,
+                    dependencyConstraint.getRevision());
+            } else if (ivyRef == null) {
+                resolvedMrid = systemMd.getMetadataArtifact().getModuleRevisionId();
+            } else if (ivyRef.getRevision() == null || ivyRef.getRevision().length() == 0) {
+                resolvedMrid = ModuleRevisionId.newInstance(resolvedMrid, "working@" + getName());
+            } else {
+                resolvedMrid = ModuleRevisionId.newInstance(resolvedMrid, ivyRef.getRevision());
+            }
+        }
+        if (isDynamic) {
+            Message.verbose("\t\t[" + toSystem(resolvedMrid).getRevision() + "] "
+                    + dependencyConstraint.getModuleId());
+        }
+        systemMd.setResolvedModuleRevisionId(resolvedMrid);
+        checkModuleDescriptorRevision(systemMd, dependencyConstraint);
+    }
+
+    private ModuleRevisionId getRevision(ResolvedResource ivyRef, ModuleRevisionId askedMrid,
+            ModuleDescriptor md) throws ParseException {
+        Map allAttributes = new HashMap();
+        allAttributes.putAll(md.getQualifiedExtraAttributes());
+        allAttributes.putAll(askedMrid.getQualifiedExtraAttributes());
+
+        String revision = ivyRef.getRevision();
+        if (revision == null) {
+            Message.debug("no revision found in reference for " + askedMrid);
+            if (getSettings().getVersionMatcher().isDynamic(askedMrid)) {
+                if (md.getModuleRevisionId().getRevision() == null) {
+                    revision = "working@" + getName();
+                } else {
+                    Message.debug("using " + askedMrid);
+                    revision = askedMrid.getRevision();
+                }
+            } else {
+                Message.debug("using " + askedMrid);
+                revision = askedMrid.getRevision();
+            }
+        }
+
+        return ModuleRevisionId.newInstance(askedMrid.getOrganisation(), askedMrid.getName(),
+            askedMrid.getBranch(), revision, allAttributes);
+    }
+
+    public ResolvedModuleRevision parse(final ResolvedResource mdRef, DependencyDescriptor dd,
+            ResolveData data) throws ParseException {
+
+        DependencyDescriptor nsDd = dd;
+        dd = toSystem(nsDd);
+
+        ModuleRevisionId mrid = dd.getDependencyRevisionId();
+        ModuleDescriptorParser parser = ModuleDescriptorParserRegistry.getInstance().getParser(
+            mdRef.getResource());
+        if (parser == null) {
+            Message.warn("no module descriptor parser available for " + mdRef.getResource());
+            return null;
+        }
+        Message.verbose("\t" + getName() + ": found md file for " + mrid);
+        Message.verbose("\t\t=> " + mdRef);
+        Message.debug("\tparser = " + parser);
+
+        ModuleRevisionId resolvedMrid = mrid;
+
+        // first check if this dependency has not yet been resolved
+        if (getSettings().getVersionMatcher().isDynamic(mrid)) {
+            resolvedMrid = ModuleRevisionId.newInstance(mrid, mdRef.getRevision());
+            IvyNode node = data.getNode(resolvedMrid);
+            if (node != null && node.getModuleRevision() != null) {
+                // this revision has already be resolved : return it
+                if (node.getDescriptor() != null && node.getDescriptor().isDefault()) {
+                    Message.verbose("\t" + getName() + ": found already resolved revision: "
+                            + resolvedMrid
+                            + ": but it's a default one, maybe we can find a better one");
+                } else {
+                    Message.verbose("\t" + getName() + ": revision already resolved: "
+                            + resolvedMrid);
+                    node.getModuleRevision().getReport().setSearched(true);
+                    return node.getModuleRevision();
+                }
+            }
+        }
+
+        Artifact moduleArtifact = parser.getMetadataArtifact(resolvedMrid, mdRef.getResource());
+        return getRepositoryCacheManager().cacheModuleDescriptor(this, mdRef, dd, moduleArtifact,
+            downloader, getCacheOptions(data));
+    }
+
+    protected ResourceMDParser getRMDParser(final DependencyDescriptor dd, final ResolveData data) {
+        return new ResourceMDParser() {
+            public MDResolvedResource parse(Resource resource, String rev) {
+                try {
+                    ResolvedModuleRevision rmr = BasicResolver.this.parse(new ResolvedResource(
+                            resource, rev), dd, data);
+                    if (rmr == null) {
+                        return null;
+                    } else {
+                        return new MDResolvedResource(resource, rev, rmr);
+                    }
+                } catch (ParseException e) {
+                    Message.warn("Failed to parse the file '" + resource + "'", e);
+                    return null;
+                }
+            }
+
+        };
+    }
+
+    protected ResourceMDParser getDefaultRMDParser(final ModuleId mid) {
+        return new ResourceMDParser() {
+            public MDResolvedResource parse(Resource resource, String rev) {
+                DefaultModuleDescriptor md = DefaultModuleDescriptor
+                        .newDefaultInstance(new ModuleRevisionId(mid, rev));
+                MetadataArtifactDownloadReport madr = new MetadataArtifactDownloadReport(
+                        md.getMetadataArtifact());
+                madr.setDownloadStatus(DownloadStatus.NO);
+                madr.setSearched(true);
+                return new MDResolvedResource(resource, rev, new ResolvedModuleRevision(
+                        BasicResolver.this, BasicResolver.this, md, madr, isForce()));
+            }
+        };
+    }
+
+    // private boolean isResolved(ResolveData data, ModuleRevisionId mrid) {
+    // IvyNode node = getSystemNode(data, mrid);
+    // return node != null && node.getModuleRevision() != null;
+    // }
+    //
+    private void checkDescriptorConsistency(ModuleRevisionId mrid, ModuleDescriptor md,
+            ResolvedResource ivyRef) throws ParseException {
+        boolean ok = true;
+        StringBuffer errors = new StringBuffer();
+        if (!mrid.getOrganisation().equals(md.getModuleRevisionId().getOrganisation())) {
+            Message.error("\t" + getName() + ": bad organisation found in " + ivyRef.getResource()
+                    + ": expected='" + mrid.getOrganisation() + "' found='"
+                    + md.getModuleRevisionId().getOrganisation() + "'");
+            errors.append("bad organisation: expected='" + mrid.getOrganisation() + "' found='"
+                    + md.getModuleRevisionId().getOrganisation() + "'; ");
+            ok = false;
+        }
+        if (!mrid.getName().equals(md.getModuleRevisionId().getName())) {
+            Message.error("\t" + getName() + ": bad module name found in " + ivyRef.getResource()
+                    + ": expected='" + mrid.getName() + " found='"
+                    + md.getModuleRevisionId().getName() + "'");
+            errors.append("bad module name: expected='" + mrid.getName() + "' found='"
+                    + md.getModuleRevisionId().getName() + "'; ");
+            ok = false;
+        }
+        if (mrid.getBranch() != null
+                && !mrid.getBranch().equals(md.getModuleRevisionId().getBranch())) {
+            Message.error("\t" + getName() + ": bad branch name found in " + ivyRef.getResource()
+                    + ": expected='" + mrid.getBranch() + " found='"
+                    + md.getModuleRevisionId().getBranch() + "'");
+            errors.append("bad branch name: expected='" + mrid.getBranch() + "' found='"
+                    + md.getModuleRevisionId().getBranch() + "'; ");
+            ok = false;
+        }
+        if (ivyRef.getRevision() != null && !ivyRef.getRevision().startsWith("working@")
+                && !mrid.getRevision().equals(md.getModuleRevisionId().getRevision())) {
+            ModuleRevisionId expectedMrid = ModuleRevisionId.newInstance(mrid, mrid.getRevision());
+            if (!getSettings().getVersionMatcher().accept(expectedMrid, md)) {
+                Message.error("\t" + getName() + ": bad revision found in " + ivyRef.getResource()
+                        + ": expected='" + ivyRef.getRevision() + " found='"
+                        + md.getModuleRevisionId().getRevision() + "'");
+                errors.append("bad revision: expected='" + ivyRef.getRevision() + "' found='"
+                        + md.getModuleRevisionId().getRevision() + "'; ");
+                ok = false;
+            }
+        }
+        if (!getSettings().getStatusManager().isStatus(md.getStatus())) {
+            Message.error("\t" + getName() + ": bad status found in " + ivyRef.getResource()
+                    + ": '" + md.getStatus() + "'");
+            errors.append("bad status: '" + md.getStatus() + "'; ");
+            ok = false;
+        }
+        for (Iterator it = mrid.getExtraAttributes().entrySet().iterator(); it.hasNext();) {
+            Entry extra = (Entry) it.next();
+            if (extra.getValue() != null
+                    && !extra.getValue().equals(md.getExtraAttribute((String) extra.getKey()))) {
+                String errorMsg = "bad " + extra.getKey() + " found in " + ivyRef.getResource()
+                        + ": expected='" + extra.getValue() + "' found='"
+                        + md.getExtraAttribute((String) extra.getKey()) + "'";
+                Message.error("\t" + getName() + ": " + errorMsg);
+                errors.append(errorMsg + ";");
+                ok = false;
+            }
+        }
+        if (!ok) {
+            throw new ParseException("inconsistent module descriptor file found in '"
+                    + ivyRef.getResource() + "': " + errors, 0);
+        }
+    }
+
+    /**
+     * When the resolver has many choices, this function helps choosing one
+     * 
+     * @param rress
+     *            the list of resolved resource which the resolver found to fit the requirement
+     * @param rmdparser
+     *            the parser of module descriptor
+     * @param mrid
+     *            the module being resolved
+     * @param date
+     *            the current date
+     * @return the selected resource
+     */
+    public ResolvedResource findResource(ResolvedResource[] rress, ResourceMDParser rmdparser,
+            ModuleRevisionId mrid, Date date) {
+        String name = getName();
+        VersionMatcher versionMatcher = getSettings().getVersionMatcher();
+
+        ResolvedResource found = null;
+        List sorted = getLatestStrategy().sort(rress);
+        List rejected = new ArrayList();
+        List foundBlacklisted = new ArrayList();
+        IvyContext context = IvyContext.getContext();
+
+        for (ListIterator iter = sorted.listIterator(sorted.size()); iter.hasPrevious();) {
+            ResolvedResource rres = (ResolvedResource) iter.previous();
+            // we start by filtering based on information already available,
+            // even though we don't even know if the resource actually exist.
+            // But checking for existence is most of the time more costly than checking
+            // name, blacklisting and first level version matching
+            if (filterNames(new ArrayList(Collections.singleton(rres.getRevision()))).isEmpty()) {
+                Message.debug("\t" + name + ": filtered by name: " + rres);
+                continue;
+            }
+            ModuleRevisionId foundMrid = ModuleRevisionId.newInstance(mrid, rres.getRevision());
+
+            ResolveData data = context.getResolveData();
+            if (data != null && data.getReport() != null
+                    && data.isBlacklisted(data.getReport().getConfiguration(), foundMrid)) {
+                Message.debug("\t" + name + ": blacklisted: " + rres);
+                rejected.add(rres.getRevision() + " (blacklisted)");
+                foundBlacklisted.add(foundMrid);
+                continue;
+            }
+
+            if (!versionMatcher.accept(mrid, foundMrid)) {
+                Message.debug("\t" + name + ": rejected by version matcher: " + rres);
+                rejected.add(rres.getRevision());
+                continue;
+            }
+            if (rres.getResource() != null && !rres.getResource().exists()) {
+                Message.debug("\t" + name + ": unreachable: " + rres + "; res="
+                        + rres.getResource());
+                rejected.add(rres.getRevision() + " (unreachable)");
+                continue;
+            }
+            if ((date != null && rres.getLastModified() > date.getTime())) {
+                Message.verbose("\t" + name + ": too young: " + rres);
+                rejected.add(rres.getRevision() + " (" + rres.getLastModified() + ")");
+                continue;
+            }
+            if (versionMatcher.needModuleDescriptor(mrid, foundMrid)) {
+                ResolvedResource r = rmdparser.parse(rres.getResource(), rres.getRevision());
+                if (r == null) {
+                    Message.debug("\t" + name + ": impossible to get module descriptor resource: "
+                            + rres);
+                    rejected.add(rres.getRevision() + " (no or bad MD)");
+                    continue;
+                }
+                ModuleDescriptor md = ((MDResolvedResource) r).getResolvedModuleRevision()
+                        .getDescriptor();
+                if (md.isDefault()) {
+                    Message.debug("\t" + name + ": default md rejected by version matcher"
+                            + "requiring module descriptor: " + rres);
+                    rejected.add(rres.getRevision() + " (MD)");
+                    continue;
+                } else if (!versionMatcher.accept(mrid, md)) {
+                    Message.debug("\t" + name + ": md rejected by version matcher: " + rres);
+                    rejected.add(rres.getRevision() + " (MD)");
+                    continue;
+                } else {
+                    found = r;
+                }
+            } else {
+                found = rres;
+            }
+
+            if (found != null) {
+                break;
+            }
+        }
+        if (found == null && !rejected.isEmpty()) {
+            logAttempt(rejected.toString());
+        }
+        if (found == null && !foundBlacklisted.isEmpty()) {
+            // all acceptable versions have been blacklisted, this means that an unsolvable conflict
+            // has been found
+            DependencyDescriptor dd = context.getDependencyDescriptor();
+            IvyNode parentNode = context.getResolveData().getNode(dd.getParentRevisionId());
+            ConflictManager cm = parentNode.getConflictManager(mrid.getModuleId());
+            cm.handleAllBlacklistedRevisions(dd, foundBlacklisted);
+        }
+
+        return found;
+    }
+
+    /**
+     * Filters names before returning them in the findXXXNames or findTokenValues method.
+     * <p>
+     * Remember to call the super implementation when overriding this method.
+     * </p>
+     * 
+     * @param names
+     *            the list to filter.
+     * @return the filtered list
+     */
+    protected Collection filterNames(Collection names) {
+        getSettings().filterIgnore(names);
+        return names;
+    }
+
+    protected void clearIvyAttempts() {
+        ivyattempts.clear();
+        clearArtifactAttempts();
+    }
+
+    protected void logIvyAttempt(String attempt) {
+        ivyattempts.add(attempt);
+        Message.verbose("\t\ttried " + attempt);
+    }
+
+    protected void logArtifactAttempt(Artifact art, String attempt) {
+        List attempts = (List) artattempts.get(art);
+        if (attempts == null) {
+            attempts = new ArrayList();
+            artattempts.put(art, attempts);
+        }
+        attempts.add(attempt);
+        Message.verbose("\t\ttried " + attempt);
+    }
+
+    protected void logAttempt(String attempt) {
+        Artifact currentArtifact = (Artifact) IvyContext.getContext().get(getName() + ".artifact");
+        if (currentArtifact != null) {
+            logArtifactAttempt(currentArtifact, attempt);
+        } else {
+            logIvyAttempt(attempt);
+        }
+    }
+
+    public void reportFailure() {
+        Message.warn("==== " + getName() + ": tried");
+        for (ListIterator iter = ivyattempts.listIterator(); iter.hasNext();) {
+            String m = (String) iter.next();
+            Message.warn("  " + m);
+        }
+        for (Iterator iter = artattempts.keySet().iterator(); iter.hasNext();) {
+            Artifact art = (Artifact) iter.next();
+            List attempts = (List) artattempts.get(art);
+            if (attempts != null) {
+                Message.warn("  -- artifact " + art + ":");
+                for (ListIterator iterator = attempts.listIterator(); iterator.hasNext();) {
+                    String m = (String) iterator.next();
+                    Message.warn("  " + m);
+                }
+            }
+        }
+    }
+
+    public void reportFailure(Artifact art) {
+        Message.warn("==== " + getName() + ": tried");
+        List attempts = (List) artattempts.get(art);
+        if (attempts != null) {
+            for (ListIterator iter = attempts.listIterator(); iter.hasNext();) {
+                String m = (String) iter.next();
+                Message.warn("  " + m);
+            }
+        }
+    }
+
+    protected boolean acceptLatest() {
+        return true;
+    }
+
+    public DownloadReport download(Artifact[] artifacts, DownloadOptions options) {
+        RepositoryCacheManager cacheManager = getRepositoryCacheManager();
+
+        clearArtifactAttempts();
+        DownloadReport dr = new DownloadReport();
+        for (int i = 0; i < artifacts.length; i++) {
+            ArtifactDownloadReport adr = cacheManager.download(artifacts[i],
+                artifactResourceResolver, downloader, getCacheDownloadOptions(options));
+            if (DownloadStatus.FAILED == adr.getDownloadStatus()) {
+                if (!ArtifactDownloadReport.MISSING_ARTIFACT.equals(adr.getDownloadDetails())) {
+                    Message.warn("\t" + adr);
+                }
+            } else if (DownloadStatus.NO == adr.getDownloadStatus()) {
+                Message.verbose("\t" + adr);
+            } else if (LogOptions.LOG_QUIET.equals(options.getLog())) {
+                Message.verbose("\t" + adr);
+            } else {
+                Message.info("\t" + adr);
+            }
+            dr.addArtifactReport(adr);
+            checkInterrupted();
+        }
+        return dr;
+    }
+
+    protected void clearArtifactAttempts() {
+        artattempts.clear();
+    }
+
+    public ArtifactDownloadReport download(final ArtifactOrigin origin, DownloadOptions options) {
+        Checks.checkNotNull(origin, "origin");
+        return getRepositoryCacheManager().download(origin.getArtifact(),
+            new ArtifactResourceResolver() {
+                public ResolvedResource resolve(Artifact artifact) {
+                    try {
+                        Resource resource = getResource(origin.getLocation());
+                        if (resource == null) {
+                            return null;
+                        }
+                        String revision = origin.getArtifact().getModuleRevisionId().getRevision();
+                        return new ResolvedResource(resource, revision);
+                    } catch (IOException e) {
+                        Message.debug(e);
+                        return null;
+                    }
+                }
+            }, downloader, getCacheDownloadOptions(options));
+    }
+
+    protected abstract Resource getResource(String source) throws IOException;
+
+    public boolean exists(Artifact artifact) {
+        ResolvedResource artifactRef = getArtifactRef(artifact, null);
+        if (artifactRef != null) {
+            return artifactRef.getResource().exists();
+        }
+        return false;
+    }
+
+    public ArtifactOrigin locate(Artifact artifact) {
+        ArtifactOrigin origin = getRepositoryCacheManager().getSavedArtifactOrigin(
+            toSystem(artifact));
+        if (!ArtifactOrigin.isUnknown(origin)) {
+            return origin;
+        }
+        ResolvedResource artifactRef = getArtifactRef(artifact, null);
+        if (artifactRef != null && artifactRef.getResource().exists()) {
+            return new ArtifactOrigin(artifact, artifactRef.getResource().isLocal(), artifactRef
+                    .getResource().getName());
+        }
+        return null;
+    }
+
+    protected long getPublicationDate(ModuleDescriptor md, DependencyDescriptor dd, ResolveData data) {
+        if (md.getPublicationDate() != null) {
+            return md.getPublicationDate().getTime();
+        }
+        ResolvedResource artifactRef = findFirstArtifactRef(md, dd, data);
+        if (artifactRef != null) {
+            return artifactRef.getLastModified();
+        }
+        return -1;
+    }
+
+    public String toString() {
+        return getName();
+    }
+
+    public String[] listTokenValues(String token, Map otherTokenValues) {
+        Collection ret = findNames(otherTokenValues, token);
+        return (String[]) ret.toArray(new String[ret.size()]);
+    }
+
+    public OrganisationEntry[] listOrganisations() {
+        Collection names = findNames(Collections.EMPTY_MAP, IvyPatternHelper.ORGANISATION_KEY);
+        OrganisationEntry[] ret = new OrganisationEntry[names.size()];
+        int i = 0;
+        for (Iterator iter = names.iterator(); iter.hasNext(); i++) {
+            String org = (String) iter.next();
+            ret[i] = new OrganisationEntry(this, org);
+        }
+        return ret;
+    }
+
+    public ModuleEntry[] listModules(OrganisationEntry org) {
+        Map tokenValues = new HashMap();
+        tokenValues.put(IvyPatternHelper.ORGANISATION_KEY, org.getOrganisation());
+        Collection names = findNames(tokenValues, IvyPatternHelper.MODULE_KEY);
+        ModuleEntry[] ret = new ModuleEntry[names.size()];
+        int i = 0;
+        for (Iterator iter = names.iterator(); iter.hasNext(); i++) {
+            String name = (String) iter.next();
+            ret[i] = new ModuleEntry(org, name);
+        }
+        return ret;
+    }
+
+    public RevisionEntry[] listRevisions(ModuleEntry mod) {
+        Map tokenValues = new HashMap();
+        tokenValues.put(IvyPatternHelper.ORGANISATION_KEY, mod.getOrganisation());
+        tokenValues.put(IvyPatternHelper.MODULE_KEY, mod.getModule());
+        Collection names = findNames(tokenValues, IvyPatternHelper.REVISION_KEY);
+        RevisionEntry[] ret = new RevisionEntry[names.size()];
+        int i = 0;
+        for (Iterator iter = names.iterator(); iter.hasNext(); i++) {
+            String name = (String) iter.next();
+            ret[i] = new RevisionEntry(mod, name);
+        }
+        return ret;
+    }
+
+    protected abstract Collection findNames(Map tokenValues, String token);
+
+    protected ResolvedResource findFirstArtifactRef(ModuleDescriptor md, DependencyDescriptor dd,
+            ResolveData data) {
+        ResolvedResource ret = null;
+        String[] conf = md.getConfigurationsNames();
+        for (int i = 0; i < conf.length; i++) {
+            Artifact[] artifacts = md.getArtifacts(conf[i]);
+            for (int j = 0; j < artifacts.length; j++) {
+                ret = getArtifactRef(artifacts[j], data.getDate());
+                if (ret != null) {
+                    return ret;
+                }
+            }
+        }
+        return null;
+    }
+
+    protected long getAndCheck(Resource resource, File dest) throws IOException {
+        long size = get(resource, dest);
+        String[] checksums = getChecksumAlgorithms();
+        boolean checked = false;
+        for (int i = 0; i < checksums.length && !checked; i++) {
+            checked = check(resource, dest, checksums[i]);
+        }
+        return size;
+    }
+
+    /**
+     * Checks the given resource checksum if a checksum resource exists.
+     * 
+     * @param resource
+     *            the resource to check
+     * @param dest
+     *            the file where the resource has been downloaded
+     * @param algorithm
+     *            the checksum algorithm to use
+     * @return true if the checksum has been successfully checked, false if the checksum wasn't
+     *         available
+     * @throws IOException
+     *             if a checksum exist but do not match the downloaded file checksum
+     */
+    private boolean check(Resource resource, File dest, String algorithm) throws IOException {
+        if (!ChecksumHelper.isKnownAlgorithm(algorithm)) {
+            throw new IllegalArgumentException("Unknown checksum algorithm: " + algorithm);
+        }
+
+        Resource csRes = resource.clone(resource.getName() + "." + algorithm);
+        if (csRes.exists()) {
+            Message.debug(algorithm + " file found for " + resource + ": checking...");
+            File csFile = File.createTempFile("ivytmp", algorithm);
+            try {
+                get(csRes, csFile);
+                try {
+                    ChecksumHelper.check(dest, csFile, algorithm);
+                    Message.verbose(algorithm + " OK for " + resource);
+                    return true;
+                } catch (IOException ex) {
+                    dest.delete();
+                    throw ex;
+                }
+            } finally {
+                csFile.delete();
+            }
+        } else {
+            return false;
+        }
+    }
+
+    protected ResolvedResource getArtifactRef(Artifact artifact, Date date) {
+        IvyContext.getContext().set(getName() + ".artifact", artifact);
+        try {
+            ResolvedResource ret = findArtifactRef(artifact, date);
+            if (ret == null && artifact.getUrl() != null) {
+                URL url = artifact.getUrl();
+                Message.verbose("\tusing url for " + artifact + ": " + url);
+                logArtifactAttempt(artifact, url.toExternalForm());
+                Resource resource;
+                if ("file".equals(url.getProtocol())) {
+                    File f;
+                    try {
+                        f = new File(new URI(url.toExternalForm()));
+                    } catch (URISyntaxException e) {
+                        // unexpected, try to get the best of it
+                        f = new File(url.getPath());
+                    }
+                    resource = new FileResource(new FileRepository(), f);
+                } else {
+                    resource = new URLResource(url);
+                }
+                ret = new ResolvedResource(resource, artifact.getModuleRevisionId().getRevision());
+            }
+            return ret;
+        } finally {
+            IvyContext.getContext().set(getName() + ".artifact", null);
+        }
+    }
+
+    public ResolvedResource doFindArtifactRef(Artifact artifact, Date date) {
+        return findArtifactRef(artifact, date);
+    }
+
+    protected abstract ResolvedResource findArtifactRef(Artifact artifact, Date date);
+
+    protected abstract long get(Resource resource, File dest) throws IOException;
+
+    public boolean isCheckconsistency() {
+        return checkconsistency;
+    }
+
+    public void setCheckconsistency(boolean checkConsitency) {
+        checkconsistency = checkConsitency;
+    }
+
+    public void setForce(boolean force) {
+        this.force = force;
+    }
+
+    public boolean isForce() {
+        return force;
+    }
+
+    public boolean isAllownomd() {
+        return allownomd;
+    }
+
+    public void setAllownomd(boolean b) {
+        Message.deprecated("allownomd is deprecated, please use descriptor=\""
+                + (b ? DESCRIPTOR_OPTIONAL : DESCRIPTOR_REQUIRED) + "\" instead");
+        allownomd = b;
+    }
+
+    /**
+     * Sets the module descriptor presence rule. Should be one of {@link #DESCRIPTOR_REQUIRED} or
+     * {@link #DESCRIPTOR_OPTIONAL}.
+     * 
+     * @param descriptorRule
+     *            the descriptor rule to use with this resolver.
+     */
+    public void setDescriptor(String descriptorRule) {
+        if (DESCRIPTOR_REQUIRED.equals(descriptorRule)) {
+            allownomd = false;
+        } else if (DESCRIPTOR_OPTIONAL.equals(descriptorRule)) {
+            allownomd = true;
+        } else {
+            throw new IllegalArgumentException("unknown descriptor rule '" + descriptorRule
+                    + "'. Allowed rules are: "
+                    + Arrays.asList(new String[] {DESCRIPTOR_REQUIRED, DESCRIPTOR_OPTIONAL}));
+        }
+    }
+
+    public String[] getChecksumAlgorithms() {
+        String csDef = checksums == null ? getSettings().getVariable("ivy.checksums") : checksums;
+        if (csDef == null) {
+            return new String[0];
+        }
+        // csDef is a comma separated list of checksum algorithms to use with this resolver
+        // we parse and return it as a String[]
+        String[] checksums = csDef.split(",");
+        List algos = new ArrayList();
+        for (int i = 0; i < checksums.length; i++) {
+            String cs = checksums[i].trim();
+            if (!"".equals(cs) && !"none".equals(cs)) {
+                algos.add(cs);
+            }
+        }
+        return (String[]) algos.toArray(new String[algos.size()]);
+    }
+
+    public void setChecksums(String checksums) {
+        this.checksums = checksums;
+    }
+
+    private final ArtifactResourceResolver artifactResourceResolver = new ArtifactResourceResolver() {
+        public ResolvedResource resolve(Artifact artifact) {
+            artifact = fromSystem(artifact);
+            return getArtifactRef(artifact, null);
+        }
+    };
+
+    private final ResourceDownloader downloader = new ResourceDownloader() {
+        public void download(Artifact artifact, Resource resource, File dest) throws IOException {
+            if (dest.exists()) {
+                dest.delete();
+            }
+            File part = new File(dest.getAbsolutePath() + ".part");
+            if (resource.getName().equals(String.valueOf(artifact.getUrl()))) {
+                if (part.getParentFile() != null) {
+                    part.getParentFile().mkdirs();
+                }
+                extartifactrep.get(resource.getName(), part);
+            } else {
+                getAndCheck(resource, part);
+            }
+            if (!part.renameTo(dest)) {
+                throw new IOException("impossible to move part file to definitive one: " + part
+                        + " -> " + dest);
+            }
+
+        }
+    };
+
+}
diff --git a/src/java/org/apache/ivy/plugins/resolver/BintrayResolver.java b/src/java/org/apache/ivy/plugins/resolver/BintrayResolver.java
new file mode 100644
index 0000000..c8d7b26
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/resolver/BintrayResolver.java
@@ -0,0 +1,81 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.resolver;
+
+/**
+ * BintrayResolver is a resolver which can be used to resolve dependencies found in the Bintray
+ * artifacts repository.
+ */
+ at SuppressWarnings("ClassTooDeepInInheritanceTree")
+public class BintrayResolver extends IBiblioResolver {
+
+    private static final String JCENTER = "https://jcenter.bintray.com/";
+
+    private static final String DL_BINTRAY = "https://dl.bintray.com/";
+
+    private static final String DEFAULT_NAME = "bintray/jcenter";
+
+    private String subject;
+
+    private String repo;
+
+    private boolean isNameUpdatable; // Whether resolver's name was not originally specified and can
+                                     // be updated.
+
+    public BintrayResolver() {
+        setRoot(JCENTER);
+        updateName(DEFAULT_NAME);
+        setM2compatible(true);
+        setUsepoms(true);
+        setUseMavenMetadata(true);
+    }
+
+    public void setSubject(String subject) {
+        this.subject = subject;
+        updateRoot();
+    }
+
+    public void setRepo(String repo) {
+        this.repo = repo;
+        updateRoot();
+    }
+
+    @SuppressWarnings("MethodWithMultipleReturnPoints")
+    private void updateRoot() {
+        if (isEmpty(subject) || isEmpty(repo)) {
+            return;
+        }
+
+        setRoot(String.format("%s%s/%s/", DL_BINTRAY, subject, repo));
+        updateName(String.format("bintray/%s/%s", subject, repo));
+    }
+
+    private void updateName(String defaultName) {
+        if (isEmpty(defaultName)) {
+            throw new IllegalArgumentException("Default resolver name must not be null or empty");
+        }
+        if (isEmpty(getName()) || isNameUpdatable) {
+            isNameUpdatable = true;
+            setName(defaultName);
+        }
+    }
+
+    private boolean isEmpty(String s) {
+        return ((s == null) || (s.trim().length() < 1));
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/resolver/CacheResolver.java b/src/java/org/apache/ivy/plugins/resolver/CacheResolver.java
new file mode 100644
index 0000000..27ac887
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/resolver/CacheResolver.java
@@ -0,0 +1,185 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.resolver;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.ParseException;
+import java.util.ArrayList;
+
+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.report.DownloadStatus;
+import org.apache.ivy.core.resolve.DownloadOptions;
+import org.apache.ivy.core.resolve.IvyNode;
+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.repository.file.FileResource;
+import org.apache.ivy.plugins.resolver.util.ResolvedResource;
+import org.apache.ivy.util.Message;
+
+public class CacheResolver extends FileSystemResolver {
+    public CacheResolver() {
+    }
+
+    public CacheResolver(ResolverSettings settings) {
+        setSettings(settings);
+        setName("cache");
+    }
+
+    public ResolvedModuleRevision getDependency(DependencyDescriptor dd, ResolveData data)
+            throws ParseException {
+        clearIvyAttempts();
+
+        ModuleRevisionId mrid = dd.getDependencyRevisionId();
+        // check revision
+
+        ResolvedModuleRevision rmr = getRepositoryCacheManager().findModuleInCache(dd, mrid,
+            getCacheOptions(data), null);
+        if (rmr != null) {
+            Message.verbose("\t" + getName() + ": revision in cache: " + mrid);
+            return rmr;
+        } else if (!getSettings().getVersionMatcher().isDynamic(mrid)) {
+            Message.verbose("\t" + getName() + ": no ivy file in cache found for " + mrid);
+            return null;
+        } else {
+            ensureConfigured();
+            ResolvedResource ivyRef = findIvyFileRef(dd, data);
+            if (ivyRef != null) {
+                Message.verbose("\t" + getName() + ": found ivy file in cache for " + mrid);
+                Message.verbose("\t\t=> " + ivyRef);
+
+                ModuleRevisionId resolvedMrid = ModuleRevisionId.newInstance(mrid,
+                    ivyRef.getRevision());
+                IvyNode node = data.getNode(resolvedMrid);
+                if (node != null && node.getModuleRevision() != null) {
+                    // this revision has already be resolved : return it
+                    Message.verbose("\t" + getName() + ": revision already resolved: "
+                            + resolvedMrid);
+                    return node.getModuleRevision();
+                }
+                rmr = getRepositoryCacheManager().findModuleInCache(
+                    dd.clone(ModuleRevisionId.newInstance(dd.getDependencyRevisionId(),
+                        ivyRef.getRevision())), dd.getDependencyRevisionId(),
+                    getCacheOptions(data), null);
+                if (rmr != null) {
+                    Message.verbose("\t" + getName() + ": revision in cache: " + resolvedMrid);
+                    return rmr;
+                } else {
+                    Message.error("\t" + getName()
+                            + ": inconsistent cache: clean it and resolve again");
+                    return null;
+                }
+            } else {
+                Message.verbose("\t" + getName() + ": no ivy file in cache found for " + mrid);
+                return null;
+            }
+        }
+    }
+
+    public DownloadReport download(Artifact[] artifacts, DownloadOptions options) {
+        ensureConfigured();
+        clearArtifactAttempts();
+        DownloadReport dr = new DownloadReport();
+        for (int i = 0; i < artifacts.length; i++) {
+            final ArtifactDownloadReport adr = new ArtifactDownloadReport(artifacts[i]);
+            dr.addArtifactReport(adr);
+            ResolvedResource artifactRef = getArtifactRef(artifacts[i], null);
+            if (artifactRef != null) {
+                Message.verbose("\t[NOT REQUIRED] " + artifacts[i]);
+                ArtifactOrigin origin = new ArtifactOrigin(artifacts[i], true, artifactRef
+                        .getResource().getName());
+                File archiveFile = ((FileResource) artifactRef.getResource()).getFile();
+                adr.setDownloadStatus(DownloadStatus.NO);
+                adr.setSize(archiveFile.length());
+                adr.setArtifactOrigin(origin);
+                adr.setLocalFile(archiveFile);
+            } else {
+                adr.setDownloadStatus(DownloadStatus.FAILED);
+            }
+        }
+        return dr;
+    }
+
+    public boolean exists(Artifact artifact) {
+        ensureConfigured();
+        return super.exists(artifact);
+    }
+
+    public ArtifactOrigin locate(Artifact artifact) {
+        ensureConfigured();
+        return super.locate(artifact);
+    }
+
+    public void publish(Artifact artifact, File src, boolean overwrite) throws IOException {
+        ensureConfigured();
+        super.publish(artifact, src, overwrite);
+    }
+
+    public OrganisationEntry[] listOrganisations() {
+        ensureConfigured();
+        return super.listOrganisations();
+    }
+
+    public ModuleEntry[] listModules(OrganisationEntry org) {
+        ensureConfigured();
+        return super.listModules(org);
+    }
+
+    public RevisionEntry[] listRevisions(ModuleEntry module) {
+        ensureConfigured();
+        return super.listRevisions(module);
+    }
+
+    public void dumpSettings() {
+        Message.verbose("\t" + getName() + " [cache]");
+    }
+
+    private void ensureConfigured() {
+        if (getIvyPatterns().isEmpty()) {
+            setIvyPatterns(new ArrayList());
+            setArtifactPatterns(new ArrayList());
+            RepositoryCacheManager[] caches = getSettings().getRepositoryCacheManagers();
+            for (int i = 0; i < caches.length; i++) {
+                if (caches[i] instanceof DefaultRepositoryCacheManager) {
+                    DefaultRepositoryCacheManager c = (DefaultRepositoryCacheManager) caches[i];
+                    addIvyPattern(c.getBasedir().getAbsolutePath() + "/" + c.getIvyPattern());
+                    addArtifactPattern(c.getBasedir().getAbsolutePath() + "/"
+                            + c.getArtifactPattern());
+                } else {
+                    Message.verbose(caches[i]
+                            + ": cache implementation is not a DefaultRepositoryCacheManager:"
+                            + " unable to configure cache resolver with it");
+                }
+            }
+        }
+    }
+
+    public String getTypeName() {
+        return "cache";
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/resolver/ChainResolver.java b/src/java/org/apache/ivy/plugins/resolver/ChainResolver.java
new file mode 100644
index 0000000..63b6c1a
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/resolver/ChainResolver.java
@@ -0,0 +1,349 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.resolver;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.ivy.core.cache.ArtifactOrigin;
+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.report.DownloadStatus;
+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.latest.ArtifactInfo;
+import org.apache.ivy.plugins.latest.LatestStrategy;
+import org.apache.ivy.plugins.resolver.util.HasLatestStrategy;
+import org.apache.ivy.plugins.resolver.util.ResolvedResource;
+import org.apache.ivy.util.Message;
+import org.apache.ivy.util.StringUtils;
+
+/**
+ *
+ */
+public class ChainResolver extends AbstractResolver {
+    public static class ResolvedModuleRevisionArtifactInfo implements ArtifactInfo {
+        private ResolvedModuleRevision rmr;
+
+        public ResolvedModuleRevisionArtifactInfo(ResolvedModuleRevision rmr) {
+            this.rmr = rmr;
+        }
+
+        public String getRevision() {
+            return rmr.getId().getRevision();
+        }
+
+        public long getLastModified() {
+            return rmr.getPublicationDate().getTime();
+        }
+
+    }
+
+    private boolean returnFirst = false;
+
+    private List chain = new ArrayList();
+
+    private boolean dual;
+
+    public void add(DependencyResolver resolver) {
+        chain.add(resolver);
+    }
+
+    public ResolvedModuleRevision getDependency(DependencyDescriptor dd, ResolveData data)
+            throws ParseException {
+        data = new ResolveData(data, doValidate(data));
+
+        List errors = new ArrayList();
+
+        ResolvedModuleRevision resolved = data.getCurrentResolvedModuleRevision();
+        ResolvedModuleRevision mr = resolved;
+
+        if (mr == null) {
+            Message.verbose(getName() + ": Checking cache for: " + dd);
+            mr = findModuleInCache(dd, data, true);
+            if (mr != null) {
+                Message.verbose(getName() + ": module revision found in cache: " + mr.getId());
+                mr = forcedRevision(mr);
+            }
+        }
+
+        for (Iterator iter = chain.iterator(); iter.hasNext();) {
+            DependencyResolver resolver = (DependencyResolver) iter.next();
+            LatestStrategy oldLatest = setLatestIfRequired(resolver, getLatestStrategy());
+            try {
+                ResolvedModuleRevision previouslyResolved = mr;
+                data.setCurrentResolvedModuleRevision(previouslyResolved);
+                mr = resolver.getDependency(dd, data);
+                if (mr != previouslyResolved && isReturnFirst()) {
+                    mr = forcedRevision(mr);
+                }
+            } catch (Exception ex) {
+                Message.verbose("problem occurred while resolving " + dd + " with " + resolver, ex);
+                errors.add(ex);
+            } finally {
+                if (oldLatest != null) {
+                    setLatest(resolver, oldLatest);
+                }
+            }
+            checkInterrupted();
+        }
+        if (mr == null && !errors.isEmpty()) {
+            if (errors.size() == 1) {
+                Exception ex = (Exception) errors.get(0);
+                if (ex instanceof RuntimeException) {
+                    throw (RuntimeException) ex;
+                } else if (ex instanceof ParseException) {
+                    throw (ParseException) ex;
+                } else {
+                    throw new RuntimeException(ex.toString(), ex);
+                }
+            } else {
+                StringBuffer err = new StringBuffer();
+                for (Iterator iter = errors.iterator(); iter.hasNext();) {
+                    Exception ex = (Exception) iter.next();
+                    err.append("\t").append(StringUtils.getErrorMessage(ex)).append("\n");
+                }
+                err.setLength(err.length() - 1);
+                throw new RuntimeException("several problems occurred while resolving " + dd
+                        + ":\n" + err);
+            }
+        }
+        if (resolved == mr) {
+            // nothing has actually been resolved here, we don't need to touch the returned rmr
+            return resolved;
+        }
+        return resolvedRevision(mr);
+    }
+
+    private ResolvedModuleRevision resolvedRevision(ResolvedModuleRevision mr) {
+        if (isDual() && mr != null) {
+            return new ResolvedModuleRevision(mr.getResolver(), this, mr.getDescriptor(),
+                    mr.getReport(), mr.isForce());
+        } else {
+            return mr;
+        }
+    }
+
+    private ResolvedModuleRevision forcedRevision(ResolvedModuleRevision rmr) {
+        if (rmr == null) {
+            return null;
+        }
+        return new ResolvedModuleRevision(rmr.getResolver(), rmr.getArtifactResolver(),
+                rmr.getDescriptor(), rmr.getReport(), true);
+    }
+
+    private LatestStrategy setLatestIfRequired(DependencyResolver resolver,
+            LatestStrategy latestStrategy) {
+        String latestName = getLatestStrategyName(resolver);
+        if (latestName != null && !"default".equals(latestName)) {
+            LatestStrategy oldLatest = getLatest(resolver);
+            setLatest(resolver, latestStrategy);
+            return oldLatest;
+        } else {
+            return null;
+        }
+    }
+
+    public ResolvedResource findIvyFileRef(DependencyDescriptor dd, ResolveData data) {
+        for (Iterator iter = chain.iterator(); iter.hasNext();) {
+            DependencyResolver resolver = (DependencyResolver) iter.next();
+            ResolvedResource result = resolver.findIvyFileRef(dd, data);
+            if (result != null) {
+                return result;
+            }
+        }
+
+        return null;
+    }
+
+    public Map[] listTokenValues(String[] tokens, Map criteria) {
+        Set result = new HashSet();
+        for (Iterator iter = chain.iterator(); iter.hasNext();) {
+            DependencyResolver resolver = (DependencyResolver) iter.next();
+            Map[] temp = resolver.listTokenValues(tokens, new HashMap(criteria));
+            result.addAll(Arrays.asList(temp));
+        }
+
+        return (Map[]) result.toArray(new Map[result.size()]);
+    }
+
+    public void reportFailure() {
+        for (Iterator iter = chain.iterator(); iter.hasNext();) {
+            DependencyResolver resolver = (DependencyResolver) iter.next();
+            resolver.reportFailure();
+        }
+    }
+
+    public void reportFailure(Artifact art) {
+        for (Iterator iter = chain.iterator(); iter.hasNext();) {
+            DependencyResolver resolver = (DependencyResolver) iter.next();
+            resolver.reportFailure(art);
+        }
+    }
+
+    public DownloadReport download(Artifact[] artifacts, DownloadOptions options) {
+        List artifactsToDownload = new ArrayList(Arrays.asList(artifacts));
+        DownloadReport report = new DownloadReport();
+        for (Iterator iter = chain.iterator(); iter.hasNext() && !artifactsToDownload.isEmpty();) {
+            DependencyResolver resolver = (DependencyResolver) iter.next();
+            DownloadReport r = resolver.download(
+                (Artifact[]) artifactsToDownload.toArray(new Artifact[artifactsToDownload.size()]),
+                options);
+            ArtifactDownloadReport[] adr = r.getArtifactsReports();
+            for (int i = 0; i < adr.length; i++) {
+                if (adr[i].getDownloadStatus() != DownloadStatus.FAILED) {
+                    artifactsToDownload.remove(adr[i].getArtifact());
+                    report.addArtifactReport(adr[i]);
+                }
+            }
+        }
+        for (Iterator iter = artifactsToDownload.iterator(); iter.hasNext();) {
+            Artifact art = (Artifact) iter.next();
+            ArtifactDownloadReport adr = new ArtifactDownloadReport(art);
+            adr.setDownloadStatus(DownloadStatus.FAILED);
+            report.addArtifactReport(adr);
+        }
+        return report;
+    }
+
+    public List getResolvers() {
+        return chain;
+    }
+
+    public void publish(Artifact artifact, File src, boolean overwrite) throws IOException {
+
+        getFirstResolver().publish(artifact, src, overwrite);
+    }
+
+    public void abortPublishTransaction() throws IOException {
+        getFirstResolver().abortPublishTransaction();
+    }
+
+    public void beginPublishTransaction(ModuleRevisionId module, boolean overwrite)
+            throws IOException {
+        getFirstResolver().beginPublishTransaction(module, overwrite);
+    }
+
+    public void commitPublishTransaction() throws IOException {
+        getFirstResolver().commitPublishTransaction();
+    }
+
+    private DependencyResolver getFirstResolver() {
+        if (chain.isEmpty()) {
+            throw new IllegalStateException("invalid chain resolver with no sub resolver");
+        }
+        return ((DependencyResolver) chain.get(0));
+    }
+
+    public boolean isReturnFirst() {
+        return returnFirst;
+    }
+
+    public void setReturnFirst(boolean returnFirst) {
+        this.returnFirst = returnFirst;
+    }
+
+    public void dumpSettings() {
+        Message.verbose("\t" + getName() + " [chain] " + chain);
+        Message.debug("\t\treturn first: " + isReturnFirst());
+        Message.debug("\t\tdual: " + isDual());
+        for (Iterator iter = chain.iterator(); iter.hasNext();) {
+            DependencyResolver r = (DependencyResolver) iter.next();
+            Message.debug("\t\t-> " + r.getName());
+        }
+    }
+
+    public boolean exists(Artifact artifact) {
+        for (Iterator iter = chain.iterator(); iter.hasNext();) {
+            DependencyResolver resolver = (DependencyResolver) iter.next();
+            if (resolver.exists(artifact)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public ArtifactOrigin locate(Artifact artifact) {
+        for (Iterator iter = chain.iterator(); iter.hasNext();) {
+            DependencyResolver resolver = (DependencyResolver) iter.next();
+            ArtifactOrigin origin = resolver.locate(artifact);
+            if (!ArtifactOrigin.isUnknown(origin)) {
+                return origin;
+            }
+        }
+        return ArtifactOrigin.unkwnown(artifact);
+    }
+
+    public ArtifactDownloadReport download(ArtifactOrigin artifact, DownloadOptions options) {
+        for (Iterator iter = chain.iterator(); iter.hasNext();) {
+            DependencyResolver resolver = (DependencyResolver) iter.next();
+            ArtifactDownloadReport adr = resolver.download(artifact, options);
+            if (adr.getDownloadStatus() != DownloadStatus.FAILED) {
+                return adr;
+            }
+        }
+        ArtifactDownloadReport adr = new ArtifactDownloadReport(artifact.getArtifact());
+        adr.setDownloadStatus(DownloadStatus.FAILED);
+        return adr;
+    }
+
+    private static void setLatest(DependencyResolver resolver, LatestStrategy latest) {
+        if (resolver instanceof HasLatestStrategy) {
+            HasLatestStrategy r = (HasLatestStrategy) resolver;
+            r.setLatestStrategy(latest);
+        }
+    }
+
+    private static LatestStrategy getLatest(DependencyResolver resolver) {
+        if (resolver instanceof HasLatestStrategy) {
+            HasLatestStrategy r = (HasLatestStrategy) resolver;
+            return r.getLatestStrategy();
+        }
+        return null;
+    }
+
+    private static String getLatestStrategyName(DependencyResolver resolver) {
+        if (resolver instanceof HasLatestStrategy) {
+            HasLatestStrategy r = (HasLatestStrategy) resolver;
+            return r.getLatest();
+        }
+        return null;
+    }
+
+    public void setDual(boolean b) {
+        dual = b;
+    }
+
+    public boolean isDual() {
+        return dual;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/plugins/resolver/DependencyResolver.java b/src/java/org/apache/ivy/plugins/resolver/DependencyResolver.java
new file mode 100644
index 0000000..c1ee376
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/resolver/DependencyResolver.java
@@ -0,0 +1,210 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.resolver;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.ParseException;
+import java.util.Map;
+
+import org.apache.ivy.core.cache.ArtifactOrigin;
+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.util.ResolvedResource;
+
+/**
+ *
+ */
+public interface DependencyResolver {
+    String getName();
+
+    /**
+     * Should only be used by configurator
+     * 
+     * @param name
+     *            the new name of the resolver
+     */
+    void setName(String name);
+
+    /**
+     * Resolve a module by id, getting its module descriptor and resolving the revision if it's a
+     * latest one (i.e. a revision uniquely identifying the revision of a module in the current
+     * environment - If this revision is not able to identify uniquely the revision of the module
+     * outside of the current environment, then the resolved revision must begin by ##)
+     * 
+     * @throws ParseException
+     */
+    ResolvedModuleRevision getDependency(DependencyDescriptor dd, ResolveData data)
+            throws ParseException;
+
+    /**
+     * Finds the module descriptor for the specified <tt>DependencyDescriptor</tt>. If this resolver
+     * can't find the module descriptor, <tt>null</tt> is returned.
+     * 
+     * @param dd
+     *            the dependency descriptor
+     * @param data
+     *            the resolve data
+     * @return the module descriptor, or <tt>null</tt>
+     */
+    ResolvedResource findIvyFileRef(DependencyDescriptor dd, ResolveData data);
+
+    /**
+     * Download artifacts with specified DownloadOptions.
+     * <p>
+     * The resolver will always make a best effort, and do not stop when an artifact is not
+     * available. It rather continue to attempt to download other requested artifacts, and report
+     * what has been done in the returned DownloadReport.
+     * </p>
+     * <p>
+     * The returned DownloadReport is never <code>null</code>, and always contain an
+     * {@link ArtifactDownloadReport} for each requested Artifact.
+     * </p>
+     * 
+     * @param artifacts
+     *            an array of artifacts to download. Must not be <code>null</code>.
+     * @param options
+     *            options to apply for this download. Must not be <code>null</code>.
+     * @return a DownloadReport with details about each Artifact download.
+     */
+    DownloadReport download(Artifact[] artifacts, DownloadOptions options);
+
+    /**
+     * Download an artifact according to the given DownloadOptions.
+     * <p>
+     * This methods is an alternative to {@link #download(Artifact[], DownloadOptions)}, which
+     * locates and downloads a set of artifacts. This method uses an {@link ArtifactOrigin}, and as
+     * such is only used to materialize an already located Artifact.
+     * </p>
+     * 
+     * @param artifact
+     *            the location of the artifact to download. Must not be <code>null</code>.
+     * @param options
+     *            options to apply for this download. Must not be <code>null</code>.
+     * @return a report detailing how the download has gone, is never <code>null</code>.
+     */
+    ArtifactDownloadReport download(ArtifactOrigin artifact, DownloadOptions options);
+
+    /**
+     * Returns <code>true</code> if the given artifact can be located by this resolver and actually
+     * exist.
+     * 
+     * @param artifact
+     *            the artifact which should be tested.
+     * @return <code>true</code> if the given artifact can be located by this resolver and actually
+     *         exist.
+     */
+    boolean exists(Artifact artifact);
+
+    /**
+     * Locates the given artifact and returns its location if it can be located by this resolver and
+     * if it actually exists, or <code>null</code> in other cases.
+     * 
+     * @param artifact
+     *            the artifact which should be located
+     * @return the artifact location, or <code>null</code> if it can't be located by this resolver
+     *         or doesn't exist.
+     */
+    ArtifactOrigin locate(Artifact artifact);
+
+    void publish(Artifact artifact, File src, boolean overwrite) throws IOException;
+
+    void beginPublishTransaction(ModuleRevisionId module, boolean overwrite) throws IOException;
+
+    void abortPublishTransaction() throws IOException;
+
+    void commitPublishTransaction() throws IOException;
+
+    /**
+     * Reports last resolve failure as Messages
+     */
+    void reportFailure();
+
+    /**
+     * Reports last artifact download failure as Messages
+     * 
+     * @param art
+     */
+    void reportFailure(Artifact art);
+
+    // listing methods, enable to know what is available from this resolver
+    // the listing methods must only list entries directly
+    // available from them, no recursion is needed as long as sub resolvers
+    // are registered in ivy too.
+
+    /**
+     * List all the values the given token can take if other tokens are set as described in the
+     * otherTokenValues map. For instance, if token = "revision" and the map contains
+     * "organisation"->"foo" "module"->"bar" The results will be the list of revisions of the module
+     * bar from the org foo.
+     * <p>
+     * Note that listing does not take into account namespaces, and return raw information without
+     * any namespace transformation. The caller is responsible for calling namespace transformation
+     * with the Namespace returned by {@link #getNamespace()}.
+     * </p>
+     */
+    String[] listTokenValues(String token, Map otherTokenValues);
+
+    /**
+     * Same as {@link #listTokenValues(String, Map)} but more generic.
+     * 
+     * @param tokens
+     *            the tokens of the query
+     * @param criteria
+     *            the token which have values
+     * @return the list of token values (Map<Strin, String>[]), must not be <code>null</code>
+     */
+    Map[] listTokenValues(String[] tokens, Map criteria);
+
+    OrganisationEntry[] listOrganisations();
+
+    ModuleEntry[] listModules(OrganisationEntry org);
+
+    RevisionEntry[] listRevisions(ModuleEntry module);
+
+    /**
+     * Returns the namespace associated with this resolver.
+     * 
+     * @return the namespace associated with this resolver.
+     */
+    Namespace getNamespace();
+
+    void dumpSettings();
+
+    void setSettings(ResolverSettings settings);
+
+    /**
+     * Returns the {@link RepositoryCacheManager} used to manage the repository cache associated
+     * with this dependency resolver.
+     * 
+     * @return the {@link RepositoryCacheManager} used to manage the repository cache associated
+     *         with this dependency resolver.
+     */
+    RepositoryCacheManager getRepositoryCacheManager();
+}
diff --git a/src/java/org/apache/ivy/plugins/resolver/DualResolver.java b/src/java/org/apache/ivy/plugins/resolver/DualResolver.java
new file mode 100644
index 0000000..e23b9ff
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/resolver/DualResolver.java
@@ -0,0 +1,218 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.resolver;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.ParseException;
+import java.util.Arrays;
+
+import org.apache.ivy.core.cache.ArtifactOrigin;
+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.plugins.resolver.util.ResolvedResource;
+import org.apache.ivy.util.Message;
+
+/**
+ * DualResolver is used to resolve dependencies with one dependency revolver, called ivy resolver,
+ * and then download artifacts found in the resolved dependencies from a second dependency resolver,
+ * called artifact resolver. It is especially useful with resolvers using repository where there is
+ * a lot of artifact, but no ivy file, like the maven ibiblio repository. If no ivy file is found by
+ * the ivy resolver, the artifact resolver is used to check if there is artifact corresponding to
+ * the request (with default ivy file). For artifact download, however, only the artifact resolver
+ * is used. Exactly two resolvers should be added to this resolver for it to work properly. The
+ * first resolver added if the ivy resolver, the second is the artifact one.
+ */
+public class DualResolver extends AbstractResolver {
+    public static final String DESCRIPTOR_OPTIONAL = "optional";
+
+    public static final String DESCRIPTOR_REQUIRED = "required";
+
+    private DependencyResolver ivyResolver;
+
+    private DependencyResolver artifactResolver;
+
+    private boolean allownomd = true;
+
+    public void add(DependencyResolver resolver) {
+        if (ivyResolver == null) {
+            ivyResolver = resolver;
+        } else if (artifactResolver == null) {
+            artifactResolver = resolver;
+        } else {
+            throw new IllegalStateException(
+                    "exactly two resolvers must be added: ivy(1) and artifact(2) one");
+        }
+    }
+
+    public ResolvedModuleRevision getDependency(DependencyDescriptor dd, ResolveData data)
+            throws ParseException {
+        if (ivyResolver == null || artifactResolver == null) {
+            throw new IllegalStateException(
+                    "exactly two resolvers must be added: ivy(1) and artifact(2) one");
+        }
+        ResolvedModuleRevision resolved = data.getCurrentResolvedModuleRevision();
+
+        data = new ResolveData(data, doValidate(data));
+        final ResolvedModuleRevision mr = ivyResolver.getDependency(dd, data);
+        if (mr == null) {
+            checkInterrupted();
+            if (isAllownomd()) {
+                Message.verbose("ivy resolver didn't find " + dd
+                        + ": trying with artifact resolver");
+                return artifactResolver.getDependency(dd, data);
+            } else {
+                return null;
+            }
+        } else {
+            if (mr == resolved) {
+                // nothing has actually been resolved here, we don't need to touch the returned rmr
+                return mr;
+            }
+            return new ResolvedModuleRevision(mr.getResolver(), this, mr.getDescriptor(),
+                    mr.getReport(), mr.isForce());
+        }
+    }
+
+    public ResolvedResource findIvyFileRef(DependencyDescriptor dd, ResolveData data) {
+        return ivyResolver.findIvyFileRef(dd, data);
+    }
+
+    public void reportFailure() {
+        ivyResolver.reportFailure();
+        artifactResolver.reportFailure();
+    }
+
+    public void reportFailure(Artifact art) {
+        ivyResolver.reportFailure(art);
+        artifactResolver.reportFailure(art);
+    }
+
+    public DownloadReport download(Artifact[] artifacts, DownloadOptions options) {
+        return artifactResolver.download(artifacts, options);
+    }
+
+    public DependencyResolver getArtifactResolver() {
+        return artifactResolver;
+    }
+
+    public void setArtifactResolver(DependencyResolver artifactResolver) {
+        this.artifactResolver = artifactResolver;
+    }
+
+    public DependencyResolver getIvyResolver() {
+        return ivyResolver;
+    }
+
+    public void setIvyResolver(DependencyResolver ivyResolver) {
+        this.ivyResolver = ivyResolver;
+    }
+
+    public void publish(Artifact artifact, File src, boolean overwrite) throws IOException {
+        if ("ivy".equals(artifact.getType())) {
+            ivyResolver.publish(artifact, src, overwrite);
+        } else {
+            artifactResolver.publish(artifact, src, overwrite);
+        }
+    }
+
+    public void abortPublishTransaction() throws IOException {
+        ivyResolver.abortPublishTransaction();
+        artifactResolver.abortPublishTransaction();
+    }
+
+    public void beginPublishTransaction(ModuleRevisionId module, boolean overwrite)
+            throws IOException {
+        ivyResolver.beginPublishTransaction(module, overwrite);
+        artifactResolver.beginPublishTransaction(module, overwrite);
+    }
+
+    public void commitPublishTransaction() throws IOException {
+        ivyResolver.commitPublishTransaction();
+        artifactResolver.commitPublishTransaction();
+    }
+
+    public void dumpSettings() {
+        if (ivyResolver == null || artifactResolver == null) {
+            throw new IllegalStateException(
+                    "exactly two resolvers must be added: ivy(1) and artifact(2) one");
+        }
+        Message.verbose("\t" + getName() + " [dual " + ivyResolver.getName() + " "
+                + artifactResolver.getName() + "]");
+    }
+
+    public boolean exists(Artifact artifact) {
+        if (artifact.isMetadata()) {
+            return ivyResolver.exists(artifact);
+        } else {
+            return artifactResolver.exists(artifact);
+        }
+    }
+
+    public ArtifactOrigin locate(Artifact artifact) {
+        if (artifact.isMetadata()) {
+            return ivyResolver.locate(artifact);
+        } else {
+            return artifactResolver.locate(artifact);
+        }
+    }
+
+    public ArtifactDownloadReport download(ArtifactOrigin artifact, DownloadOptions options) {
+        if (artifact.getArtifact().isMetadata()) {
+            return ivyResolver.download(artifact, options);
+        } else {
+            return artifactResolver.download(artifact, options);
+        }
+    }
+
+    public boolean isAllownomd() {
+        return allownomd;
+    }
+
+    public void setAllownomd(boolean allownomd) {
+        Message.deprecated("allownomd is deprecated, please use descriptor=\""
+                + (allownomd ? DESCRIPTOR_OPTIONAL : DESCRIPTOR_REQUIRED) + "\" instead");
+        this.allownomd = allownomd;
+    }
+
+    /**
+     * Sets the module descriptor presence rule. Should be one of {@link #DESCRIPTOR_REQUIRED} or
+     * {@link #DESCRIPTOR_OPTIONAL}.
+     * 
+     * @param descriptorRule
+     *            the descriptor rule to use with this resolver.
+     */
+    public void setDescriptor(String descriptorRule) {
+        if (DESCRIPTOR_REQUIRED.equals(descriptorRule)) {
+            allownomd = false;
+        } else if (DESCRIPTOR_OPTIONAL.equals(descriptorRule)) {
+            allownomd = true;
+        } else {
+            throw new IllegalArgumentException("unknown descriptor rule '" + descriptorRule
+                    + "'. Allowed rules are: "
+                    + Arrays.asList(new String[] {DESCRIPTOR_REQUIRED, DESCRIPTOR_OPTIONAL}));
+        }
+    }
+
+}
diff --git a/src/java/org/apache/ivy/plugins/resolver/FileSystemResolver.java b/src/java/org/apache/ivy/plugins/resolver/FileSystemResolver.java
new file mode 100644
index 0000000..a7cee6b
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/resolver/FileSystemResolver.java
@@ -0,0 +1,323 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.resolver;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.settings.IvyPattern;
+import org.apache.ivy.plugins.repository.file.FileRepository;
+import org.apache.ivy.util.Checks;
+import org.apache.ivy.util.Message;
+
+/**
+ */
+public class FileSystemResolver extends RepositoryResolver {
+
+    private static final String TRANSACTION_DESTINATION_SUFFIX = ".part";
+
+    private static final Pattern TRANSACTION_PATTERN = Pattern
+            .compile("(.*[/\\\\]\\[revision\\])([/\\\\].+)");
+
+    /**
+     * Transactional mode.
+     * 
+     * auto: use transaction if possible, only log verbose message if not true: always use
+     * transaction, fail if not supported false: never use transactions
+     */
+    private String transactional = "auto"; // one of 'auto', 'true' or 'false'
+
+    /**
+     * When set indicates if this resolver supports transaction
+     */
+    private Boolean supportTransaction;
+
+    /**
+     * The pattern leading to the directory where files are published before being moved at the end
+     * of a transaction
+     */
+    private String baseTransactionPattern;
+
+    /**
+     * Map between actual patterns and patterns used during the transaction to put files in a
+     * temporary directory
+     */
+    private Map/* <String,String> */fullTransactionPatterns = new HashMap();
+
+    /**
+     * Location where files are published during the transaction
+     */
+    private File transactionTempDir;
+
+    /**
+     * Location where files should end up at the end of the transaction
+     */
+    private File transactionDestDir;
+
+    public FileSystemResolver() {
+        setRepository(new FileRepository());
+    }
+
+    public String getTypeName() {
+        return "file";
+    }
+
+    public boolean isLocal() {
+        return getFileRepository().isLocal();
+    }
+
+    public void setLocal(boolean local) {
+        getFileRepository().setLocal(local);
+    }
+
+    private FileRepository getFileRepository() {
+        return (FileRepository) getRepository();
+    }
+
+    protected String getDestination(String pattern, Artifact artifact, ModuleRevisionId mrid) {
+        if (supportTransaction() && isTransactionStarted()) {
+
+            String destPattern = (String) fullTransactionPatterns.get(pattern);
+            if (destPattern == null) {
+                throw new IllegalArgumentException(
+                        "unsupported pattern for publish destination pattern: " + pattern
+                                + ". supported patterns: " + fullTransactionPatterns.keySet());
+            }
+            return IvyPatternHelper.substitute(destPattern, mrid, artifact);
+        } else {
+            return super.getDestination(pattern, artifact, mrid);
+        }
+    }
+
+    private boolean isTransactionStarted() {
+        return transactionTempDir != null;
+    }
+
+    public void abortPublishTransaction() throws IOException {
+        if (supportTransaction()) {
+            if (isTransactionStarted()) {
+                try {
+                    getFileRepository().delete(transactionTempDir);
+                    Message.info("\tpublish aborted: deleted " + transactionTempDir);
+                } finally {
+                    closeTransaction();
+                }
+            } else {
+                Message.info("\tpublish aborted: nothing was started");
+            }
+        }
+    }
+
+    public void commitPublishTransaction() throws IOException {
+        if (supportTransaction()) {
+            if (!isTransactionStarted()) {
+                throw new IllegalStateException("no current transaction!");
+            }
+            if (transactionDestDir.exists()) {
+                throw new IOException(
+                        "impossible to commit transaction: transaction destination directory "
+                                + "already exists: "
+                                + transactionDestDir
+                                + "\npossible cause: usage of identifying tokens after the revision token");
+            }
+            try {
+                getFileRepository().move(transactionTempDir, transactionDestDir);
+
+                Message.info("\tpublish commited: moved " + transactionTempDir + " \n\t\tto "
+                        + transactionDestDir);
+            } catch (IOException ex) {
+                IOException commitEx;
+                try {
+                    getFileRepository().delete(transactionTempDir);
+                    commitEx = new IOException("publish transaction commit error for "
+                            + transactionDestDir + ": rolled back");
+                } catch (IOException deleteEx) {
+                    commitEx = new IOException("publish transaction commit error for "
+                            + transactionDestDir + ": rollback impossible either, "
+                            + "please remove " + transactionTempDir + " manually");
+                }
+                commitEx.initCause(ex);
+                throw commitEx;
+            } finally {
+                closeTransaction();
+            }
+        }
+    }
+
+    public void beginPublishTransaction(ModuleRevisionId module, boolean overwrite)
+            throws IOException {
+        if (supportTransaction()) {
+            if (isTransactionStarted()) {
+                throw new IllegalStateException("a transaction is already started and not closed!");
+            }
+            if (overwrite) {
+                unsupportedTransaction("overwrite transaction not supported yet");
+            } else {
+                initTransaction(module);
+                if (transactionDestDir.exists()) {
+                    unsupportedTransaction("transaction destination directory already exists: "
+                            + transactionDestDir
+                            + "\npossible cause: usage of identifying tokens after the revision token");
+                    closeTransaction();
+                } else {
+                    Message.verbose("\tstarting transaction: publish during transaction will be done in \n\t\t"
+                            + transactionTempDir
+                            + "\n\tand on commit moved to \n\t\t"
+                            + transactionDestDir);
+                }
+            }
+        }
+    }
+
+    protected Collection filterNames(Collection values) {
+        if (supportTransaction()) {
+            values = super.filterNames(values);
+            for (Iterator iterator = values.iterator(); iterator.hasNext();) {
+                String v = (String) iterator.next();
+                if (v.endsWith(TRANSACTION_DESTINATION_SUFFIX)) {
+                    iterator.remove();
+                }
+            }
+            return values;
+        } else {
+            return super.filterNames(values);
+        }
+    }
+
+    public boolean supportTransaction() {
+        if ("false".equals(transactional)) {
+            return false;
+        }
+        checkSupportTransaction();
+        return supportTransaction.booleanValue();
+    }
+
+    private void closeTransaction() {
+        transactionTempDir = null;
+        transactionDestDir = null;
+    }
+
+    private void checkSupportTransaction() {
+        if (supportTransaction == null) {
+            supportTransaction = Boolean.FALSE;
+            List ivyPatterns = getIvyPatterns();
+            List artifactPatterns = getArtifactPatterns();
+
+            if (ivyPatterns.size() > 0) {
+                String pattern = (String) ivyPatterns.get(0);
+                Matcher m = TRANSACTION_PATTERN.matcher(pattern);
+                if (!m.matches()) {
+                    unsupportedTransaction("ivy pattern does not use revision as a directory");
+                    return;
+                } else {
+                    baseTransactionPattern = m.group(1);
+                    fullTransactionPatterns.put(pattern, m.group(1)
+                            + TRANSACTION_DESTINATION_SUFFIX + m.group(2));
+                }
+            }
+            if (artifactPatterns.size() > 0) {
+                String pattern = (String) artifactPatterns.get(0);
+                Matcher m = TRANSACTION_PATTERN.matcher(pattern);
+                if (!m.matches()) {
+                    unsupportedTransaction("artifact pattern does not use revision as a directory");
+                    return;
+                } else if (baseTransactionPattern != null) {
+                    if (!baseTransactionPattern.equals(m.group(1))) {
+                        unsupportedTransaction("ivy pattern and artifact pattern "
+                                + "do not use the same directory for revision");
+                        return;
+                    } else {
+                        fullTransactionPatterns.put(pattern, m.group(1)
+                                + TRANSACTION_DESTINATION_SUFFIX + m.group(2));
+                    }
+                } else {
+                    baseTransactionPattern = m.group(1);
+                    fullTransactionPatterns.put(pattern, m.group(1)
+                            + TRANSACTION_DESTINATION_SUFFIX + m.group(2));
+                }
+            }
+            supportTransaction = Boolean.TRUE;
+        }
+    }
+
+    private void unsupportedTransaction(String msg) {
+        String fullMsg = getName() + " do not support transaction. " + msg;
+        if ("true".equals(transactional)) {
+            throw new IllegalStateException(fullMsg
+                    + ". Set transactional attribute to 'auto' or 'false' or fix the problem.");
+        } else {
+            Message.verbose(fullMsg);
+            supportTransaction = Boolean.FALSE;
+        }
+    }
+
+    private void initTransaction(ModuleRevisionId module) {
+        ModuleRevisionId mrid = module;
+        if (isM2compatible()) {
+            mrid = convertM2IdForResourceSearch(module);
+        }
+
+        transactionTempDir = Checks.checkAbsolute(
+            IvyPatternHelper.substitute(
+                baseTransactionPattern,
+                ModuleRevisionId.newInstance(mrid, mrid.getRevision()
+                        + TRANSACTION_DESTINATION_SUFFIX)), "baseTransactionPattern");
+        transactionDestDir = Checks.checkAbsolute(
+            IvyPatternHelper.substitute(baseTransactionPattern, mrid), "baseTransactionPattern");
+    }
+
+    public String getTransactional() {
+        return transactional;
+    }
+
+    public void setTransactional(String transactional) {
+        this.transactional = transactional;
+    }
+
+    public void addConfiguredIvy(IvyPattern p) {
+        File file = Checks.checkAbsolute(p.getPattern(), "ivy pattern");
+        p.setPattern(file.getAbsolutePath());
+        super.addConfiguredIvy(p);
+    }
+
+    public void addIvyPattern(String pattern) {
+        File file = Checks.checkAbsolute(pattern, "ivy pattern");
+        super.addIvyPattern(file.getAbsolutePath());
+    }
+
+    public void addConfiguredArtifact(IvyPattern p) {
+        File file = Checks.checkAbsolute(p.getPattern(), "artifact pattern");
+        p.setPattern(file.getAbsolutePath());
+        super.addConfiguredArtifact(p);
+    }
+
+    public void addArtifactPattern(String pattern) {
+        File file = Checks.checkAbsolute(pattern, "artifact pattern");
+        super.addArtifactPattern(file.getAbsolutePath());
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/resolver/IBiblioResolver.java b/src/java/org/apache/ivy/plugins/resolver/IBiblioResolver.java
new file mode 100644
index 0000000..8072e3f
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/resolver/IBiblioResolver.java
@@ -0,0 +1,554 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.resolver;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.core.cache.ArtifactOrigin;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DefaultArtifact;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+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.matcher.PatternMatcher;
+import org.apache.ivy.plugins.repository.Repository;
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.plugins.resolver.util.ResolvedResource;
+import org.apache.ivy.util.ContextualSAXHandler;
+import org.apache.ivy.util.Message;
+import org.apache.ivy.util.XMLHelper;
+import org.xml.sax.SAXException;
+
+/**
+ * IBiblioResolver is a resolver which can be used to resolve dependencies found in the ibiblio
+ * maven repository, or similar repositories.
+ * <p>
+ * For more flexibility with url and patterns, see
+ * {@link org.apache.ivy.plugins.resolver.URLResolver}.
+ */
+public class IBiblioResolver extends URLResolver {
+    private static final String M2_PER_MODULE_PATTERN = "[revision]/[artifact]-[revision](-[classifier]).[ext]";
+
+    private static final String M2_PATTERN = "[organisation]/[module]/" + M2_PER_MODULE_PATTERN;
+
+    public static final String DEFAULT_PATTERN = "[module]/[type]s/[artifact]-[revision].[ext]";
+
+    public static final String DEFAULT_ROOT = "http://www.ibiblio.org/maven/";
+
+    public static final String DEFAULT_M2_ROOT = "https://repo1.maven.org/maven2/";
+
+    private String root = null;
+
+    private String pattern = null;
+
+    // use poms if m2 compatible is true
+    private boolean usepoms = true;
+
+    // use maven-metadata.xml is exists to list revisions
+    private boolean useMavenMetadata = true;
+
+    public IBiblioResolver() {
+        // SNAPSHOT revisions are changing revisions
+        setChangingMatcher(PatternMatcher.REGEXP);
+        setChangingPattern(".*-SNAPSHOT");
+    }
+
+    public ResolvedResource findIvyFileRef(DependencyDescriptor dd, ResolveData data) {
+        if (isM2compatible() && isUsepoms()) {
+            ModuleRevisionId mrid = dd.getDependencyRevisionId();
+            mrid = convertM2IdForResourceSearch(mrid);
+
+            ResolvedResource rres = null;
+            if (dd.getDependencyRevisionId().getRevision().endsWith("SNAPSHOT")) {
+                rres = findSnapshotDescriptor(dd, data, mrid);
+                if (rres != null) {
+                    return rres;
+                }
+            }
+
+            rres = findResourceUsingPatterns(mrid, getIvyPatterns(),
+                DefaultArtifact.newPomArtifact(mrid, data.getDate()), getRMDParser(dd, data),
+                data.getDate());
+            return rres;
+        } else {
+            return null;
+        }
+    }
+
+    public ResolvedResource findArtifactRef(Artifact artifact, Date date) {
+        ensureConfigured(getSettings());
+        ModuleRevisionId mrid = artifact.getModuleRevisionId();
+        if (isM2compatible()) {
+            mrid = convertM2IdForResourceSearch(mrid);
+        }
+        ResolvedResource rres = null;
+        if (artifact.getId().getRevision().endsWith("SNAPSHOT") && isM2compatible()) {
+            rres = findSnapshotArtifact(artifact, date, mrid);
+            if (rres != null) {
+                return rres;
+            }
+        }
+        return findResourceUsingPatterns(mrid, getArtifactPatterns(), artifact,
+            getDefaultRMDParser(artifact.getModuleRevisionId().getModuleId()), date);
+    }
+
+    private ResolvedResource findSnapshotArtifact(Artifact artifact, Date date,
+            ModuleRevisionId mrid) {
+        String rev = findSnapshotVersion(mrid);
+        if (rev != null) {
+            // replace the revision token in file name with the resolved revision
+            String pattern = getWholePattern().replaceFirst("\\-\\[revision\\]", "-" + rev);
+            return findResourceUsingPattern(mrid, pattern, artifact, getDefaultRMDParser(artifact
+                    .getModuleRevisionId().getModuleId()), date);
+        }
+        return null;
+    }
+
+    private ResolvedResource findSnapshotDescriptor(DependencyDescriptor dd, ResolveData data,
+            ModuleRevisionId mrid) {
+        String rev = findSnapshotVersion(mrid);
+        if (rev != null) {
+            // here it would be nice to be able to store the resolved snapshot version, to avoid
+            // having to follow the same process to download artifacts
+
+            Message.verbose("[" + rev + "] " + mrid);
+
+            // replace the revision token in file name with the resolved revision
+            String pattern = getWholePattern().replaceFirst("\\-\\[revision\\]", "-" + rev);
+            return findResourceUsingPattern(mrid, pattern,
+                DefaultArtifact.newPomArtifact(mrid, data.getDate()), getRMDParser(dd, data),
+                data.getDate());
+        }
+        return null;
+    }
+
+    private String findSnapshotVersion(ModuleRevisionId mrid) {
+        if (!isM2compatible()) {
+            return null;
+        }
+
+        if (shouldUseMavenMetadata(getWholePattern())) {
+            InputStream metadataStream = null;
+            try {
+                String metadataLocation = IvyPatternHelper.substitute(root
+                        + "[organisation]/[module]/[revision]/maven-metadata.xml", mrid);
+                Resource metadata = getRepository().getResource(metadataLocation);
+                if (metadata.exists()) {
+                    metadataStream = metadata.openStream();
+                    final StringBuffer timestamp = new StringBuffer();
+                    final StringBuffer buildNumer = new StringBuffer();
+                    XMLHelper.parse(metadataStream, null, new ContextualSAXHandler() {
+                        public void endElement(String uri, String localName, String qName)
+                                throws SAXException {
+                            if ("metadata/versioning/snapshot/timestamp".equals(getContext())) {
+                                timestamp.append(getText());
+                            }
+                            if ("metadata/versioning/snapshot/buildNumber".equals(getContext())) {
+                                buildNumer.append(getText());
+                            }
+                            super.endElement(uri, localName, qName);
+                        }
+                    }, null);
+                    if (timestamp.length() > 0) {
+                        // we have found a timestamp, so this is a snapshot unique version
+                        String rev = mrid.getRevision();
+                        rev = rev.substring(0, rev.length() - "SNAPSHOT".length());
+                        rev = rev + timestamp.toString() + "-" + buildNumer.toString();
+
+                        return rev;
+                    }
+                } else {
+                    Message.verbose("\tmaven-metadata not available: " + metadata);
+                }
+            } catch (IOException e) {
+                Message.verbose("impossible to access maven metadata file, ignored", e);
+            } catch (SAXException e) {
+                Message.verbose("impossible to parse maven metadata file, ignored", e);
+            } catch (ParserConfigurationException e) {
+                Message.verbose("impossible to parse maven metadata file, ignored", e);
+            } finally {
+                if (metadataStream != null) {
+                    try {
+                        metadataStream.close();
+                    } catch (IOException e) {
+                        // ignored
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    public void setM2compatible(boolean m2compatible) {
+        super.setM2compatible(m2compatible);
+        if (m2compatible) {
+            if (root == null) {
+                root = DEFAULT_M2_ROOT;
+            }
+            if (pattern == null) {
+                pattern = M2_PATTERN;
+            }
+            updateWholePattern();
+        }
+    }
+
+    public void ensureConfigured(ResolverSettings settings) {
+        if (settings != null && (root == null || pattern == null)) {
+            if (root == null) {
+                String root = settings.getVariable("ivy.ibiblio.default.artifact.root");
+                if (root != null) {
+                    this.root = root;
+                } else {
+                    settings.configureRepositories(true);
+                    this.root = settings.getVariable("ivy.ibiblio.default.artifact.root");
+                }
+            }
+            if (pattern == null) {
+                String pattern = settings.getVariable("ivy.ibiblio.default.artifact.pattern");
+                if (pattern != null) {
+                    this.pattern = pattern;
+                } else {
+                    settings.configureRepositories(false);
+                    this.pattern = settings.getVariable("ivy.ibiblio.default.artifact.pattern");
+                }
+            }
+            updateWholePattern();
+        }
+    }
+
+    protected String getModuleDescriptorExtension() {
+        return "pom";
+    }
+
+    private String getWholePattern() {
+        return root + pattern;
+    }
+
+    public String getPattern() {
+        return pattern;
+    }
+
+    public void setPattern(String pattern) {
+        if (pattern == null) {
+            throw new NullPointerException("pattern must not be null");
+        }
+        this.pattern = pattern;
+        ensureConfigured(getSettings());
+        updateWholePattern();
+    }
+
+    public String getRoot() {
+        return root;
+    }
+
+    /**
+     * Sets the root of the maven like repository. The maven like repository is necessarily an http
+     * repository.
+     * 
+     * @param root
+     *            the root of the maven like repository
+     * @throws IllegalArgumentException
+     *             if root does not start with "http://"
+     */
+    public void setRoot(String root) {
+        if (root == null) {
+            throw new NullPointerException("root must not be null");
+        }
+        if (!root.endsWith("/")) {
+            this.root = root + "/";
+        } else {
+            this.root = root;
+        }
+        ensureConfigured(getSettings());
+        updateWholePattern();
+    }
+
+    private void updateWholePattern() {
+        if (isM2compatible() && isUsepoms()) {
+            setIvyPatterns(Collections.singletonList(getWholePattern()));
+        } else {
+            setIvyPatterns(Collections.EMPTY_LIST);
+        }
+        setArtifactPatterns(Collections.singletonList(getWholePattern()));
+    }
+
+    public void publish(Artifact artifact, File src) {
+        throw new UnsupportedOperationException("publish not supported by IBiblioResolver");
+    }
+
+    // we do not allow to list organisations on ibiblio, nor modules in ibiblio 1
+    public String[] listTokenValues(String token, Map otherTokenValues) {
+        if (IvyPatternHelper.ORGANISATION_KEY.equals(token)) {
+            return new String[0];
+        }
+        if (IvyPatternHelper.MODULE_KEY.equals(token) && !isM2compatible()) {
+            return new String[0];
+        }
+        ensureConfigured(getSettings());
+        return super.listTokenValues(token, otherTokenValues);
+    }
+
+    protected String[] listTokenValues(String pattern, String token) {
+        if (IvyPatternHelper.ORGANISATION_KEY.equals(token)) {
+            return new String[0];
+        }
+        if (IvyPatternHelper.MODULE_KEY.equals(token) && !isM2compatible()) {
+            return new String[0];
+        }
+        ensureConfigured(getSettings());
+
+        // let's see if we should use maven metadata for this listing...
+        if (IvyPatternHelper.REVISION_KEY.equals(token)
+                && shouldUseMavenMetadata(getWholePattern())) {
+            // now we must use metadata if available
+            /*
+             * we substitute tokens with ext token only in the m2 per module pattern, to match has
+             * has been done in the given pattern
+             */
+            String partiallyResolvedM2PerModulePattern = IvyPatternHelper.substituteTokens(
+                M2_PER_MODULE_PATTERN, Collections.singletonMap(IvyPatternHelper.EXT_KEY, "pom"));
+            if (pattern.endsWith(partiallyResolvedM2PerModulePattern)) {
+                /*
+                 * the given pattern already contain resolved org and module, we just have to
+                 * replace the per module pattern at the end by 'maven-metadata.xml' to have the
+                 * maven metadata file location
+                 */
+                String metadataLocation = pattern.substring(0,
+                    pattern.lastIndexOf(partiallyResolvedM2PerModulePattern))
+                        + "maven-metadata.xml";
+                List revs = listRevisionsWithMavenMetadata(getRepository(), metadataLocation);
+                if (revs != null) {
+                    return (String[]) revs.toArray(new String[revs.size()]);
+                }
+            } else {
+                /*
+                 * this is probably because the given pattern has been substituted with jar ext, if
+                 * this resolver has optional module descriptors. But since we have to use maven
+                 * metadata, we don't care about this case, maven metadata has already been used
+                 * when looking for revisions with the pattern substituted with ext=xml for the
+                 * "ivy" pattern.
+                 */
+                return new String[0];
+            }
+        }
+        return super.listTokenValues(pattern, token);
+    }
+
+    public OrganisationEntry[] listOrganisations() {
+        return new OrganisationEntry[0];
+    }
+
+    public ModuleEntry[] listModules(OrganisationEntry org) {
+        if (isM2compatible()) {
+            ensureConfigured(getSettings());
+            return super.listModules(org);
+        }
+        return new ModuleEntry[0];
+    }
+
+    public RevisionEntry[] listRevisions(ModuleEntry mod) {
+        ensureConfigured(getSettings());
+        return super.listRevisions(mod);
+    }
+
+    protected ResolvedResource[] listResources(Repository repository, ModuleRevisionId mrid,
+            String pattern, Artifact artifact) {
+        if (shouldUseMavenMetadata(pattern)) {
+            List revs = listRevisionsWithMavenMetadata(repository, mrid.getModuleId()
+                    .getAttributes());
+            if (revs != null) {
+                Message.debug("\tfound revs: " + revs);
+                List rres = new ArrayList();
+                for (Iterator iter = revs.iterator(); iter.hasNext();) {
+                    String rev = (String) iter.next();
+                    ModuleRevisionId historicalMrid = ModuleRevisionId.newInstance(mrid, rev);
+
+                    String patternForRev = pattern;
+                    if (rev.endsWith("SNAPSHOT")) {
+                        String snapshotVersion = findSnapshotVersion(historicalMrid);
+                        if (snapshotVersion != null) {
+                            patternForRev = pattern.replaceFirst("\\-\\[revision\\]", "-"
+                                    + snapshotVersion);
+                        }
+                    }
+                    String resolvedPattern = IvyPatternHelper.substitute(patternForRev,
+                        historicalMrid, artifact);
+                    try {
+                        Resource res = repository.getResource(resolvedPattern);
+                        if (res != null) {
+                            // we do not test if the resource actually exist here, it would cause
+                            // a lot of checks which are not always necessary depending on the usage
+                            // which is done of the returned ResolvedResource array
+                            rres.add(new ResolvedResource(res, rev));
+                        }
+                    } catch (IOException e) {
+                        Message.warn(
+                            "impossible to get resource from name listed by maven-metadata.xml:"
+                                    + rres, e);
+                    }
+                }
+                return (ResolvedResource[]) rres.toArray(new ResolvedResource[rres.size()]);
+            } else {
+                // maven metadata not available or something went wrong,
+                // use default listing capability
+                return super.listResources(repository, mrid, pattern, artifact);
+            }
+        } else {
+            return super.listResources(repository, mrid, pattern, artifact);
+        }
+    }
+
+    private List listRevisionsWithMavenMetadata(Repository repository, Map tokenValues) {
+        String metadataLocation = IvyPatternHelper.substituteTokens(root
+                + "[organisation]/[module]/maven-metadata.xml", tokenValues);
+        return listRevisionsWithMavenMetadata(repository, metadataLocation);
+    }
+
+    private List listRevisionsWithMavenMetadata(Repository repository, String metadataLocation) {
+        List revs = null;
+        InputStream metadataStream = null;
+        try {
+            Resource metadata = repository.getResource(metadataLocation);
+            if (metadata.exists()) {
+                Message.verbose("\tlisting revisions from maven-metadata: " + metadata);
+                final List metadataRevs = new ArrayList();
+                metadataStream = metadata.openStream();
+                XMLHelper.parse(metadataStream, null, new ContextualSAXHandler() {
+                    public void endElement(String uri, String localName, String qName)
+                            throws SAXException {
+                        if ("metadata/versioning/versions/version".equals(getContext())) {
+                            metadataRevs.add(getText().trim());
+                        }
+                        super.endElement(uri, localName, qName);
+                    }
+                }, null);
+                revs = metadataRevs;
+            } else {
+                Message.verbose("\tmaven-metadata not available: " + metadata);
+            }
+        } catch (IOException e) {
+            Message.verbose("impossible to access maven metadata file, ignored", e);
+        } catch (SAXException e) {
+            Message.verbose("impossible to parse maven metadata file, ignored", e);
+        } catch (ParserConfigurationException e) {
+            Message.verbose("impossible to parse maven metadata file, ignored", e);
+        } finally {
+            if (metadataStream != null) {
+                try {
+                    metadataStream.close();
+                } catch (IOException e) {
+                    // ignored
+                }
+            }
+        }
+        return revs;
+    }
+
+    protected void findTokenValues(Collection names, List patterns, Map tokenValues, String token) {
+        if (IvyPatternHelper.REVISION_KEY.equals(token)) {
+            if (shouldUseMavenMetadata(getWholePattern())) {
+                List revs = listRevisionsWithMavenMetadata(getRepository(), tokenValues);
+                if (revs != null) {
+                    names.addAll(filterNames(revs));
+                    return;
+                }
+            }
+        }
+        super.findTokenValues(names, patterns, tokenValues, token);
+    }
+
+    private boolean shouldUseMavenMetadata(String pattern) {
+        return isUseMavenMetadata() && isM2compatible() && pattern.endsWith(M2_PATTERN);
+    }
+
+    public String getTypeName() {
+        return "ibiblio";
+    }
+
+    // override some methods to ensure configuration
+    public ResolvedModuleRevision getDependency(DependencyDescriptor dd, ResolveData data)
+            throws ParseException {
+        ensureConfigured(data.getSettings());
+        return super.getDependency(dd, data);
+    }
+
+    public DownloadReport download(Artifact[] artifacts, DownloadOptions options) {
+        ensureConfigured(getSettings());
+        return super.download(artifacts, options);
+    }
+
+    public boolean exists(Artifact artifact) {
+        ensureConfigured(getSettings());
+        return super.exists(artifact);
+    }
+
+    public ArtifactOrigin locate(Artifact artifact) {
+        ensureConfigured(getSettings());
+        return super.locate(artifact);
+    }
+
+    public List getArtifactPatterns() {
+        ensureConfigured(getSettings());
+        return super.getArtifactPatterns();
+    }
+
+    public boolean isUsepoms() {
+        return usepoms;
+    }
+
+    public void setUsepoms(boolean usepoms) {
+        this.usepoms = usepoms;
+        updateWholePattern();
+    }
+
+    public boolean isUseMavenMetadata() {
+        return useMavenMetadata;
+    }
+
+    public void setUseMavenMetadata(boolean useMavenMetadata) {
+        this.useMavenMetadata = useMavenMetadata;
+    }
+
+    public void dumpSettings() {
+        ensureConfigured(getSettings());
+        super.dumpSettings();
+        Message.debug("\t\troot: " + getRoot());
+        Message.debug("\t\tpattern: " + getPattern());
+        Message.debug("\t\tusepoms: " + usepoms);
+        Message.debug("\t\tuseMavenMetadata: " + useMavenMetadata);
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/resolver/IvyRepResolver.java b/src/java/org/apache/ivy/plugins/resolver/IvyRepResolver.java
new file mode 100644
index 0000000..12e1e2b
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/resolver/IvyRepResolver.java
@@ -0,0 +1,309 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.resolver;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.core.cache.ArtifactOrigin;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+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.resolver.util.ResolvedResource;
+import org.apache.ivy.util.Message;
+import org.apache.ivy.util.XMLHelper;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * IvyRepResolver is a resolver which can be used to resolve dependencies found in the ivy official
+ * repository for ivy files and ibiblio maven repository for the artifacts, or similar repositories.
+ * For more flexibility with url and patterns, see
+ * {@link org.apache.ivy.plugins.resolver.URLResolver}.
+ */
+public class IvyRepResolver extends URLResolver {
+    public static final String DEFAULT_IVYPATTERN = "[organisation]/[module]/ivy-[revision].xml";
+
+    public static final String DEFAULT_IVYROOT = "http://ivyrep.jayasoft.org/";
+
+    private String ivyroot = null;
+
+    private String ivypattern = null;
+
+    private String artroot = null;
+
+    private String artpattern = null;
+
+    public IvyRepResolver() {
+    }
+
+    private void ensureArtifactConfigured(ResolverSettings settings) {
+        if (settings != null && (artroot == null || artpattern == null)) {
+            if (artroot == null) {
+                String root = settings.getVariable("ivy.ivyrep.default.artifact.root");
+                if (root != null) {
+                    artroot = root;
+                } else {
+                    settings.configureRepositories(true);
+                    artroot = settings.getVariable("ivy.ivyrep.default.artifact.root");
+                }
+            }
+            if (artpattern == null) {
+                String pattern = settings.getVariable("ivy.ivyrep.default.artifact.pattern");
+                if (pattern != null) {
+                    artpattern = pattern;
+                } else {
+                    settings.configureRepositories(false);
+                    artpattern = settings.getVariable("ivy.ivyrep.default.artifact.pattern");
+                }
+            }
+            updateWholeArtPattern();
+        }
+    }
+
+    private void ensureIvyConfigured(ResolverSettings settings) {
+        if (settings != null && (ivyroot == null || ivypattern == null)) {
+            if (ivyroot == null) {
+                String root = settings.getVariable("ivy.ivyrep.default.ivy.root");
+                if (root != null) {
+                    ivyroot = root;
+                } else {
+                    throw new IllegalStateException("ivyroot is mandatory on IvyRepResolver. "
+                            + "Make sure to set it in your settings, before setting ivypattern "
+                            + "if you wish to set ivypattern too.");
+                }
+            }
+            if (ivypattern == null) {
+                String pattern = settings.getVariable("ivy.ivyrep.default.ivy.pattern");
+                if (pattern != null) {
+                    ivypattern = pattern;
+                } else {
+                    settings.configureRepositories(false);
+                    ivypattern = settings.getVariable("ivy.ivyrep.default.ivy.pattern");
+                }
+            }
+            updateWholeIvyPattern();
+        }
+    }
+
+    private String getWholeIvyPattern() {
+        if (ivyroot == null || ivypattern == null) {
+            return null;
+        }
+        return ivyroot + ivypattern;
+    }
+
+    private String getWholeArtPattern() {
+        return artroot + artpattern;
+    }
+
+    public String getIvypattern() {
+        return ivypattern;
+    }
+
+    public void setIvypattern(String pattern) {
+        if (pattern == null) {
+            throw new NullPointerException("pattern must not be null");
+        }
+        ivypattern = pattern;
+        ensureIvyConfigured(getSettings());
+        updateWholeIvyPattern();
+    }
+
+    public String getIvyroot() {
+        return ivyroot;
+    }
+
+    /**
+     * Sets the root of the maven like repository. The maven like repository is necessarily an http
+     * repository.
+     * 
+     * @param root
+     *            the root of the maven like repository
+     * @throws IllegalArgumentException
+     *             if root does not start with "http://"
+     */
+    public void setIvyroot(String root) {
+        if (root == null) {
+            throw new NullPointerException("root must not be null");
+        }
+        if (!root.endsWith("/")) {
+            ivyroot = root + "/";
+        } else {
+            ivyroot = root;
+        }
+        ensureIvyConfigured(getSettings());
+        updateWholeIvyPattern();
+    }
+
+    public void setM2compatible(boolean m2compatible) {
+        if (m2compatible) {
+            throw new IllegalArgumentException(
+                    "ivyrep does not support maven2 compatibility. "
+                            + "Please use ibiblio resolver instead, or even url or filesystem resolvers for"
+                            + " more specific needs.");
+        }
+    }
+
+    private void updateWholeIvyPattern() {
+        setIvyPatterns(Collections.singletonList(getWholeIvyPattern()));
+    }
+
+    private void updateWholeArtPattern() {
+        setArtifactPatterns(Collections.singletonList(getWholeArtPattern()));
+    }
+
+    public void publish(Artifact artifact, File src) {
+        throw new UnsupportedOperationException("publish not supported by IBiblioResolver");
+    }
+
+    public String getArtroot() {
+        return artroot;
+    }
+
+    public String getArtpattern() {
+        return artpattern;
+    }
+
+    public void setArtpattern(String pattern) {
+        if (pattern == null) {
+            throw new NullPointerException("pattern must not be null");
+        }
+        artpattern = pattern;
+        ensureArtifactConfigured(getSettings());
+        updateWholeArtPattern();
+    }
+
+    public void setArtroot(String root) {
+        if (root == null) {
+            throw new NullPointerException("root must not be null");
+        }
+        if (!root.endsWith("/")) {
+            artroot = root + "/";
+        } else {
+            artroot = root;
+        }
+        ensureArtifactConfigured(getSettings());
+        updateWholeArtPattern();
+    }
+
+    public OrganisationEntry[] listOrganisations() {
+        ensureIvyConfigured(getSettings());
+        try {
+            URL content = new URL(ivyroot + "content.xml");
+            final List ret = new ArrayList();
+            XMLHelper.parse(content, null, new DefaultHandler() {
+                public void startElement(String uri, String localName, String qName,
+                        org.xml.sax.Attributes attributes) throws SAXException {
+                    if ("organisation".equals(qName)) {
+                        String org = attributes.getValue("name");
+                        if (org != null) {
+                            ret.add(new OrganisationEntry(IvyRepResolver.this, org));
+                        }
+                    }
+                }
+            });
+            return (OrganisationEntry[]) ret.toArray(new OrganisationEntry[ret.size()]);
+        } catch (MalformedURLException e) {
+            // ???
+        } catch (Exception e) {
+            Message.warn("unable to parse content.xml file on ivyrep", e);
+        }
+        return super.listOrganisations();
+    }
+
+    // overwrite parent to use only ivy patterns (and not artifact ones, cause ibiblio is too slow
+    // to answer)
+    public ModuleEntry[] listModules(OrganisationEntry org) {
+        ensureIvyConfigured(getSettings());
+        Map tokenValues = new HashMap();
+        tokenValues.put(IvyPatternHelper.ORGANISATION_KEY, org.getOrganisation());
+        Collection names = findIvyNames(tokenValues, IvyPatternHelper.MODULE_KEY);
+        ModuleEntry[] ret = new ModuleEntry[names.size()];
+        int i = 0;
+        for (Iterator iter = names.iterator(); iter.hasNext(); i++) {
+            String name = (String) iter.next();
+            ret[i] = new ModuleEntry(org, name);
+        }
+        return ret;
+    }
+
+    public RevisionEntry[] listRevisions(ModuleEntry mod) {
+        ensureIvyConfigured(getSettings());
+        ensureArtifactConfigured(getSettings());
+        return super.listRevisions(mod);
+    }
+
+    public String getTypeName() {
+        return "ivyrep";
+    }
+
+    // override some methods to ensure configuration
+    public ResolvedModuleRevision getDependency(DependencyDescriptor dd, ResolveData data)
+            throws ParseException {
+        ensureIvyConfigured(data.getSettings());
+        return super.getDependency(dd, data);
+    }
+
+    public ResolvedResource findArtifactRef(Artifact artifact, Date date) {
+        ensureArtifactConfigured(getSettings());
+        return super.findArtifactRef(artifact, date);
+    }
+
+    public DownloadReport download(Artifact[] artifacts, DownloadOptions options) {
+        ensureArtifactConfigured(getSettings());
+        return super.download(artifacts, options);
+    }
+
+    public boolean exists(Artifact artifact) {
+        ensureArtifactConfigured(getSettings());
+        return super.exists(artifact);
+    }
+
+    public ArtifactOrigin locate(Artifact artifact) {
+        ensureArtifactConfigured(getSettings());
+        return super.locate(artifact);
+    }
+
+    public List getIvyPatterns() {
+        ensureIvyConfigured(getSettings());
+        return super.getIvyPatterns();
+    }
+
+    public List getArtifactPatterns() {
+        ensureArtifactConfigured(getSettings());
+        return super.getArtifactPatterns();
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/resolver/JarResolver.java b/src/java/org/apache/ivy/plugins/resolver/JarResolver.java
new file mode 100644
index 0000000..f305cf6
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/resolver/JarResolver.java
@@ -0,0 +1,101 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.resolver;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.jar.JarFile;
+
+import org.apache.ivy.core.cache.CacheResourceOptions;
+import org.apache.ivy.core.event.EventManager;
+import org.apache.ivy.core.report.ArtifactDownloadReport;
+import org.apache.ivy.core.report.DownloadStatus;
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.plugins.repository.jar.JarRepository;
+import org.apache.ivy.plugins.repository.url.URLRepository;
+import org.apache.ivy.plugins.repository.url.URLResource;
+
+public class JarResolver extends RepositoryResolver {
+
+    private URL url;
+
+    public JarResolver() {
+        setRepository(new JarRepository());
+    }
+
+    public String getTypeName() {
+        return "jar";
+    }
+
+    public void setFile(String jarFile) {
+        setJarFile(new File(jarFile));
+    }
+
+    public void setUrl(String jarUrl) {
+        try {
+            url = new URL(jarUrl);
+        } catch (MalformedURLException e) {
+            throw new RuntimeException("the jar repository " + getName()
+                    + " has an malformed url : " + jarUrl + " (" + e.getMessage() + ")");
+        }
+    }
+
+    public JarRepository getJarRepository() {
+        return (JarRepository) super.getRepository();
+    }
+
+    private void setJarFile(File jarLocalFile) {
+        JarFile jar;
+        try {
+            jar = new JarFile(jarLocalFile);
+        } catch (IOException e) {
+            throw new RuntimeException("the jar repository " + getName() + " could not be read ("
+                    + e.getMessage() + ")", e);
+        }
+        getJarRepository().setJarFile(jar);
+    }
+
+    public void setSettings(ResolverSettings settings) {
+        super.setSettings(settings);
+        // let's resolve the url
+        if (url != null) {
+            ArtifactDownloadReport report;
+            EventManager eventManager = getEventManager();
+            try {
+                if (eventManager != null) {
+                    getRepository().addTransferListener(eventManager);
+                }
+                Resource jarResource = new URLResource(url);
+                CacheResourceOptions options = new CacheResourceOptions();
+                report = getRepositoryCacheManager().downloadRepositoryResource(jarResource,
+                    "jarrepository", "jar", "jar", options, new URLRepository());
+            } finally {
+                if (eventManager != null) {
+                    getRepository().removeTransferListener(eventManager);
+                }
+            }
+            if (report.getDownloadStatus() == DownloadStatus.FAILED) {
+                throw new RuntimeException("The jar file " + url.toExternalForm()
+                        + " could not be downloaded (" + report.getDownloadDetails() + ")");
+            }
+            setJarFile(report.getLocalFile());
+        }
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/resolver/MirroredURLResolver.java b/src/java/org/apache/ivy/plugins/resolver/MirroredURLResolver.java
new file mode 100644
index 0000000..09777ad
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/resolver/MirroredURLResolver.java
@@ -0,0 +1,115 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.resolver;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.ivy.core.cache.CacheResourceOptions;
+import org.apache.ivy.core.report.ArtifactDownloadReport;
+import org.apache.ivy.osgi.repo.RelativeURLRepository;
+import org.apache.ivy.plugins.repository.url.ChainedRepository;
+import org.apache.ivy.plugins.repository.url.URLRepository;
+import org.apache.ivy.plugins.repository.url.URLResource;
+import org.apache.ivy.util.Message;
+
+public class MirroredURLResolver extends RepositoryResolver {
+
+    private URL mirrorListUrl;
+
+    public MirroredURLResolver() {
+        setRepository(new ChainedRepository());
+    }
+
+    public void setMirrorListUrl(URL mirrorListUrl) {
+        this.mirrorListUrl = mirrorListUrl;
+    }
+
+    private void setupMirrors() {
+        File mirrorListFile = downloadMirrorList();
+        List mirrorBaseUrls;
+        try {
+            mirrorBaseUrls = readMirrorList(mirrorListFile);
+        } catch (IOException e) {
+            throw new IllegalStateException("The mirror list could not be read from "
+                    + mirrorListUrl + " (" + e.getMessage() + ")");
+        }
+        List/* <Repository> */repositories = new ArrayList();
+        Iterator it = mirrorBaseUrls.iterator();
+        while (it.hasNext()) {
+            String baseUrl = (String) it.next();
+            URL url = null;
+            try {
+                url = new URL(baseUrl);
+            } catch (MalformedURLException e) {
+                Message.warn("In the mirror list from " + mirrorListUrl
+                        + ", an incorrect url has been found and will then not be used: " + baseUrl);
+            }
+            if (url != null) {
+                RelativeURLRepository repo = new RelativeURLRepository(url);
+                repositories.add(repo);
+            }
+        }
+        ((ChainedRepository) getRepository()).setRepositories(repositories);
+    }
+
+    private File downloadMirrorList() {
+        URLRepository urlRepository = new URLRepository();
+        if (getEventManager() != null) {
+            urlRepository.addTransferListener(getEventManager());
+        }
+        URLResource mirrorResource = new URLResource(mirrorListUrl);
+        CacheResourceOptions options = new CacheResourceOptions();
+        ArtifactDownloadReport report = getRepositoryCacheManager().downloadRepositoryResource(
+            mirrorResource, "mirrorlist", "text", "txt", options, urlRepository);
+        return report.getLocalFile();
+    }
+
+    private List/* <String> */readMirrorList(File mirrorListFile) throws IOException {
+        BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(
+                mirrorListFile)));
+        List/* <String> */list = new ArrayList();
+        try {
+            String line = in.readLine();
+            while (line != null) {
+                list.add(line);
+                line = in.readLine();
+            }
+        } finally {
+            in.close();
+        }
+        return list;
+    }
+
+    public String getTypeName() {
+        return "mirroredurl";
+    }
+
+    public void validate() {
+        super.validate();
+        setupMirrors();
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/resolver/RepositoryResolver.java b/src/java/org/apache/ivy/plugins/resolver/RepositoryResolver.java
new file mode 100644
index 0000000..59dbf67
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/resolver/RepositoryResolver.java
@@ -0,0 +1,347 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.resolver;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.core.event.EventManager;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DefaultArtifact;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.report.DownloadReport;
+import org.apache.ivy.core.resolve.DownloadOptions;
+import org.apache.ivy.plugins.parser.ModuleDescriptorParser;
+import org.apache.ivy.plugins.parser.ModuleDescriptorParserRegistry;
+import org.apache.ivy.plugins.repository.AbstractRepository;
+import org.apache.ivy.plugins.repository.Repository;
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.plugins.resolver.util.ResolvedResource;
+import org.apache.ivy.plugins.resolver.util.ResolverHelper;
+import org.apache.ivy.plugins.resolver.util.ResourceMDParser;
+import org.apache.ivy.plugins.signer.SignatureGenerator;
+import org.apache.ivy.plugins.version.VersionMatcher;
+import org.apache.ivy.util.ChecksumHelper;
+import org.apache.ivy.util.FileUtil;
+import org.apache.ivy.util.Message;
+
+/**
+ *
+ */
+public class RepositoryResolver extends AbstractPatternsBasedResolver {
+
+    private Repository repository;
+
+    private Boolean alwaysCheckExactRevision = null;
+
+    private String signerName = null;
+
+    public RepositoryResolver() {
+    }
+
+    public Repository getRepository() {
+        return repository;
+    }
+
+    public void setRepository(Repository repository) {
+        this.repository = repository;
+    }
+
+    public void setName(String name) {
+        super.setName(name);
+        if (repository instanceof AbstractRepository) {
+            ((AbstractRepository) repository).setName(name);
+        }
+    }
+
+    public void setSigner(String signerName) {
+        this.signerName = signerName;
+    }
+
+    protected ResolvedResource findResourceUsingPattern(ModuleRevisionId mrid, String pattern,
+            Artifact artifact, ResourceMDParser rmdparser, Date date) {
+        String name = getName();
+        VersionMatcher versionMatcher = getSettings().getVersionMatcher();
+        try {
+            if (!versionMatcher.isDynamic(mrid) || isAlwaysCheckExactRevision()) {
+                String resourceName = IvyPatternHelper.substitute(pattern, mrid, artifact);
+                Message.debug("\t trying " + resourceName);
+                logAttempt(resourceName);
+                Resource res = repository.getResource(resourceName);
+                boolean reachable = res.exists();
+                if (reachable) {
+                    String revision;
+                    if (pattern.indexOf(IvyPatternHelper.REVISION_KEY) == -1) {
+                        if ("ivy".equals(artifact.getType()) || "pom".equals(artifact.getType())) {
+                            // we can't determine the revision from the pattern, get it
+                            // from the moduledescriptor itself
+                            File temp = File.createTempFile("ivy", artifact.getExt());
+                            temp.deleteOnExit();
+                            repository.get(res.getName(), temp);
+                            ModuleDescriptorParser parser = ModuleDescriptorParserRegistry
+                                    .getInstance().getParser(res);
+                            ModuleDescriptor md = parser.parseDescriptor(getParserSettings(), temp
+                                    .toURI().toURL(), res, false);
+                            revision = md.getRevision();
+                            if ((revision == null) || (revision.length() == 0)) {
+                                revision = "working@" + name;
+                            }
+                        } else {
+                            revision = "working@" + name;
+                        }
+                    } else {
+                        revision = mrid.getRevision();
+                    }
+                    return new ResolvedResource(res, revision);
+                } else if (versionMatcher.isDynamic(mrid)) {
+                    return findDynamicResourceUsingPattern(rmdparser, mrid, pattern, artifact, date);
+                } else {
+                    Message.debug("\t" + name + ": resource not reachable for " + mrid + ": res="
+                            + res);
+                    return null;
+                }
+            } else {
+                return findDynamicResourceUsingPattern(rmdparser, mrid, pattern, artifact, date);
+            }
+        } catch (IOException ex) {
+            throw new RuntimeException(name + ": unable to get resource for " + mrid + ": res="
+                    + IvyPatternHelper.substitute(pattern, mrid, artifact) + ": " + ex, ex);
+        } catch (ParseException ex) {
+            throw new RuntimeException(name + ": unable to get resource for " + mrid + ": res="
+                    + IvyPatternHelper.substitute(pattern, mrid, artifact) + ": " + ex, ex);
+        }
+    }
+
+    private ResolvedResource findDynamicResourceUsingPattern(ResourceMDParser rmdparser,
+            ModuleRevisionId mrid, String pattern, Artifact artifact, Date date) {
+        String name = getName();
+        logAttempt(IvyPatternHelper.substitute(
+            pattern,
+            ModuleRevisionId.newInstance(mrid,
+                IvyPatternHelper.getTokenString(IvyPatternHelper.REVISION_KEY)), artifact));
+        ResolvedResource[] rress = listResources(repository, mrid, pattern, artifact);
+        if (rress == null) {
+            Message.debug("\t" + name + ": unable to list resources for " + mrid + ": pattern="
+                    + pattern);
+            return null;
+        } else {
+            ResolvedResource found = findResource(rress, rmdparser, mrid, date);
+            if (found == null) {
+                Message.debug("\t" + name + ": no resource found for " + mrid + ": pattern="
+                        + pattern);
+            }
+            return found;
+        }
+    }
+
+    protected Resource getResource(String source) throws IOException {
+        return repository.getResource(source);
+    }
+
+    /**
+     * List all revisions as resolved resources for the given artifact in the given repository using
+     * the given pattern, and using the given mrid except its revision.
+     * 
+     * @param repository
+     *            the repository in which revisions should be located
+     * @param mrid
+     *            the module revision id to look for (except revision)
+     * @param pattern
+     *            the pattern to use to locate the revisions
+     * @param artifact
+     *            the artifact to find
+     * @return an array of ResolvedResource, all pointing to a different revision of the given
+     *         Artifact.
+     */
+    protected ResolvedResource[] listResources(Repository repository, ModuleRevisionId mrid,
+            String pattern, Artifact artifact) {
+        return ResolverHelper.findAll(repository, mrid, pattern, artifact);
+    }
+
+    protected long get(Resource resource, File dest) throws IOException {
+        Message.verbose("\t" + getName() + ": downloading " + resource.getName());
+        Message.debug("\t\tto " + dest);
+        if (dest.getParentFile() != null) {
+            dest.getParentFile().mkdirs();
+        }
+        repository.get(resource.getName(), dest);
+        return dest.length();
+    }
+
+    public void publish(Artifact artifact, File src, boolean overwrite) throws IOException {
+        String destPattern;
+        if ("ivy".equals(artifact.getType()) && !getIvyPatterns().isEmpty()) {
+            destPattern = (String) getIvyPatterns().get(0);
+        } else if (!getArtifactPatterns().isEmpty()) {
+            destPattern = (String) getArtifactPatterns().get(0);
+        } else {
+            throw new IllegalStateException("impossible to publish " + artifact + " using " + this
+                    + ": no artifact pattern defined");
+        }
+        // Check for m2 compatibility
+        ModuleRevisionId mrid = artifact.getModuleRevisionId();
+        if (isM2compatible()) {
+            mrid = convertM2IdForResourceSearch(mrid);
+        }
+
+        String dest = getDestination(destPattern, artifact, mrid);
+
+        put(artifact, src, dest, overwrite);
+        Message.info("\tpublished " + artifact.getName() + " to "
+                + hidePassword(repository.standardize(dest)));
+    }
+
+    protected String getDestination(String pattern, Artifact artifact, ModuleRevisionId mrid) {
+        return IvyPatternHelper.substitute(pattern, mrid, artifact);
+    }
+
+    protected void put(Artifact artifact, File src, String dest, boolean overwrite)
+            throws IOException {
+        // verify the checksum algorithms before uploading artifacts!
+        String[] checksums = getChecksumAlgorithms();
+        for (int i = 0; i < checksums.length; i++) {
+            if (!ChecksumHelper.isKnownAlgorithm(checksums[i])) {
+                throw new IllegalArgumentException("Unknown checksum algorithm: " + checksums[i]);
+            }
+        }
+
+        repository.put(artifact, src, dest, overwrite);
+        for (int i = 0; i < checksums.length; i++) {
+            putChecksum(artifact, src, dest, overwrite, checksums[i]);
+        }
+
+        if (signerName != null) {
+            putSignature(artifact, src, dest, overwrite);
+        }
+    }
+
+    protected void putChecksum(Artifact artifact, File src, String dest, boolean overwrite,
+            String algorithm) throws IOException {
+        File csFile = File.createTempFile("ivytemp", algorithm);
+        try {
+            FileUtil.copy(new ByteArrayInputStream(ChecksumHelper.computeAsString(src, algorithm)
+                    .getBytes()), csFile, null);
+            repository.put(
+                DefaultArtifact.cloneWithAnotherTypeAndExt(artifact, algorithm, artifact.getExt()
+                        + "." + algorithm), csFile, dest + "." + algorithm, overwrite);
+        } finally {
+            csFile.delete();
+        }
+    }
+
+    protected void putSignature(Artifact artifact, File src, String dest, boolean overwrite)
+            throws IOException {
+        SignatureGenerator gen = getSettings().getSignatureGenerator(signerName);
+        if (gen == null) {
+            throw new IllegalArgumentException("Couldn't sign the artifacts! "
+                    + "Unknown signer name: '" + signerName + "'");
+        }
+
+        File tempFile = File.createTempFile("ivytemp", gen.getExtension());
+
+        try {
+            gen.sign(src, tempFile);
+            repository.put(
+                DefaultArtifact.cloneWithAnotherTypeAndExt(artifact, gen.getExtension(),
+                    artifact.getExt() + "." + gen.getExtension()), tempFile,
+                dest + "." + gen.getExtension(), overwrite);
+        } finally {
+            tempFile.delete();
+        }
+    }
+
+    public DownloadReport download(Artifact[] artifacts, DownloadOptions options) {
+        EventManager eventManager = getEventManager();
+        try {
+            if (eventManager != null) {
+                repository.addTransferListener(eventManager);
+            }
+            return super.download(artifacts, options);
+        } finally {
+            if (eventManager != null) {
+                repository.removeTransferListener(eventManager);
+            }
+        }
+    }
+
+    protected void findTokenValues(Collection names, List patterns, Map tokenValues, String token) {
+        for (Iterator iter = patterns.iterator(); iter.hasNext();) {
+            String pattern = (String) iter.next();
+            String partiallyResolvedPattern = IvyPatternHelper.substituteTokens(pattern,
+                tokenValues);
+            String[] values = ResolverHelper.listTokenValues(repository, partiallyResolvedPattern,
+                token);
+            if (values != null) {
+                names.addAll(filterNames(new ArrayList(Arrays.asList(values))));
+            }
+        }
+    }
+
+    protected String[] listTokenValues(String pattern, String token) {
+        return ResolverHelper.listTokenValues(repository, pattern, token);
+    }
+
+    protected boolean exist(String path) {
+        try {
+            Resource resource = repository.getResource(path);
+            return resource.exists();
+        } catch (IOException e) {
+            Message.debug(e);
+            return false;
+        }
+    }
+
+    public String getTypeName() {
+        return "repository";
+    }
+
+    public void dumpSettings() {
+        super.dumpSettings();
+        Message.debug("\t\trepository: " + getRepository());
+    }
+
+    public void setSettings(ResolverSettings settings) {
+        super.setSettings(settings);
+        if (settings != null) {
+            if (alwaysCheckExactRevision == null) {
+                alwaysCheckExactRevision = Boolean.valueOf(settings
+                        .getVariable("ivy.default.always.check.exact.revision"));
+            }
+        }
+    }
+
+    public boolean isAlwaysCheckExactRevision() {
+        return alwaysCheckExactRevision == null ? true : alwaysCheckExactRevision.booleanValue();
+    }
+
+    public void setAlwaysCheckExactRevision(boolean alwaysCheckExactRevision) {
+        this.alwaysCheckExactRevision = Boolean.valueOf(alwaysCheckExactRevision);
+    }
+
+}
diff --git a/src/java/org/apache/ivy/plugins/resolver/ResolverSettings.java b/src/java/org/apache/ivy/plugins/resolver/ResolverSettings.java
new file mode 100644
index 0000000..5401801
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/resolver/ResolverSettings.java
@@ -0,0 +1,58 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.resolver;
+
+import java.util.Collection;
+
+import org.apache.ivy.core.cache.RepositoryCacheManager;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.plugins.latest.LatestStrategy;
+import org.apache.ivy.plugins.namespace.Namespace;
+import org.apache.ivy.plugins.parser.ParserSettings;
+import org.apache.ivy.plugins.signer.SignatureGenerator;
+import org.apache.ivy.plugins.version.VersionMatcher;
+
+public interface ResolverSettings extends ParserSettings {
+
+    LatestStrategy getLatestStrategy(String latestStrategyName);
+
+    LatestStrategy getDefaultLatestStrategy();
+
+    RepositoryCacheManager getRepositoryCacheManager(String name);
+
+    RepositoryCacheManager getDefaultRepositoryCacheManager();
+
+    RepositoryCacheManager[] getRepositoryCacheManagers();
+
+    Namespace getNamespace(String namespaceName);
+
+    Namespace getSystemNamespace();
+
+    String getVariable(String string);
+
+    void configureRepositories(boolean b);
+
+    VersionMatcher getVersionMatcher();
+
+    String getResolveMode(ModuleId moduleId);
+
+    void filterIgnore(Collection names);
+
+    SignatureGenerator getSignatureGenerator(String name);
+
+}
diff --git a/src/java/org/apache/ivy/plugins/resolver/SFTPResolver.java b/src/java/org/apache/ivy/plugins/resolver/SFTPResolver.java
new file mode 100644
index 0000000..190d150
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/resolver/SFTPResolver.java
@@ -0,0 +1,41 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.resolver;
+
+import org.apache.ivy.plugins.repository.sftp.SFTPRepository;
+
+/**
+ * This resolver is able to work with any sftp server. It supports listing and publishing. The
+ * server host should absolutely be set using setHost. basedir defaults to . port default to 22
+ * username and password will be prompted using a dialog box if not set. So if you are in an
+ * headless environment, provide username and password.
+ */
+public class SFTPResolver extends AbstractSshBasedResolver {
+
+    public SFTPResolver() {
+        setRepository(new SFTPRepository());
+    }
+
+    public String getTypeName() {
+        return "sftp";
+    }
+
+    public SFTPRepository getSFTPRepository() {
+        return (SFTPRepository) getRepository();
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/resolver/SshResolver.java b/src/java/org/apache/ivy/plugins/resolver/SshResolver.java
new file mode 100644
index 0000000..8a4f859
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/resolver/SshResolver.java
@@ -0,0 +1,89 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.resolver;
+
+import org.apache.ivy.plugins.repository.ssh.SshRepository;
+
+/**
+ * Resolver for SSH resolver for ivy
+ */
+public class SshResolver extends AbstractSshBasedResolver {
+
+    public SshResolver() {
+        setRepository(new SshRepository());
+    }
+
+    /**
+     * A four digit string (e.g., 0644, see "man chmod", "man open") specifying the permissions of
+     * the published files.
+     */
+    public void setPublishPermissions(String permissions) {
+        ((SshRepository) getRepository()).setPublishPermissions(permissions);
+    }
+
+    /**
+     * sets the path separator used on the target system. Not sure if this is used or if '/' is used
+     * on all implementation. default is to use '/'
+     * 
+     * @param sep
+     *            file separator to use on the target system
+     */
+    public void setFileSeparator(String sep) {
+        if (sep == null || sep.length() != 1) {
+            throw new IllegalArgumentException(
+                    "File Separator has to be a single character and not " + sep);
+        }
+        ((SshRepository) getRepository()).setFileSeparator(sep.trim().charAt(0));
+    }
+
+    /**
+     * set the command to get a directory listing the command has to be a shell command working on
+     * the target system and has to produce a listing of filenames, with each filename on a new line
+     * the term %arg can be used in the command to substitue the path to be listed (e.g.
+     * "ls -1 %arg | grep -v CVS" to get a listing without CVS directory) if %arg is not part of the
+     * command, the path will be appended to the command default is: "ls -1"
+     */
+    public void setListCommand(String cmd) {
+        ((SshRepository) getRepository()).setListCommand(cmd);
+    }
+
+    /**
+     * set the command to check for existence of a file the command has to be a shell command
+     * working on the target system and has to create an exit status of 0 for an existent file and
+     * <> 0 for a non existing file given as argument the term %arg can be used in the command to
+     * substitue the path to be listed if %arg is not part of the command, the path will be appended
+     * to the command default is: "ls"
+     */
+    public void setExistCommand(String cmd) {
+        ((SshRepository) getRepository()).setExistCommand(cmd);
+    }
+
+    /**
+     * set the command to create a directory on the target system the command has to be a shell
+     * command working on the target system and has to create a directory with the given argument
+     * the term %arg can be used in the command to substitue the path to be listed if %arg is not
+     * part of the command, the path will be appended to the command default is: "mkdir"
+     */
+    public void setCreateDirCommand(String cmd) {
+        ((SshRepository) getRepository()).setExistCommand(cmd);
+    }
+
+    public String getTypeName() {
+        return "ssh";
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/resolver/URLResolver.java b/src/java/org/apache/ivy/plugins/resolver/URLResolver.java
new file mode 100644
index 0000000..7afacdc
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/resolver/URLResolver.java
@@ -0,0 +1,34 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.resolver;
+
+import org.apache.ivy.plugins.repository.url.URLRepository;
+
+/**
+ * This resolver is able to work with any URLs, it handles latest revisions with file and http urls
+ * only, and it does not handle publishing
+ */
+public class URLResolver extends RepositoryResolver {
+    public URLResolver() {
+        setRepository(new URLRepository());
+    }
+
+    public String getTypeName() {
+        return "url";
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/resolver/VfsResolver.java b/src/java/org/apache/ivy/plugins/resolver/VfsResolver.java
new file mode 100644
index 0000000..a897a36
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/resolver/VfsResolver.java
@@ -0,0 +1,61 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.resolver;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.ivy.plugins.repository.vfs.VfsRepository;
+
+/**
+ *
+ */
+public class VfsResolver extends RepositoryResolver {
+    private static final Pattern URL_PATTERN = Pattern.compile("[a-z]*://(.+):(.+)@.*");
+
+    private static final int PASSWORD_GROUP = 2;
+
+    public VfsResolver() {
+        setRepository(new VfsRepository());
+    }
+
+    public String getTypeName() {
+        return "vfs";
+    }
+
+    public String hidePassword(String name) {
+        return prepareForDisplay(name);
+    }
+
+    public static String prepareForDisplay(String name) {
+        StringBuffer s = new StringBuffer(name);
+        Matcher m = URL_PATTERN.matcher(s);
+        if (m.matches()) {
+            final String password = m.group(PASSWORD_GROUP);
+            final int passwordposi = s.indexOf(password);
+            StringBuffer stars = new StringBuffer(password);
+            for (int posi = 0; posi < password.length(); posi++) {
+                stars.setCharAt(posi, '*');
+            }
+            String replacement = stars.toString();
+            s = s.replace(passwordposi, passwordposi + password.length(), replacement);
+        }
+        return s.toString();
+
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/resolver/VsftpResolver.java b/src/java/org/apache/ivy/plugins/resolver/VsftpResolver.java
new file mode 100644
index 0000000..e2c4e24
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/resolver/VsftpResolver.java
@@ -0,0 +1,74 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.resolver;
+
+import org.apache.ivy.plugins.repository.vsftp.VsftpRepository;
+
+/**
+ * This resolver uses SecureCRT vsft to access an sftp server. It supports listing and publishing.
+ * The server host should absolutely be set using setHost, so does the username.
+ */
+public class VsftpResolver extends RepositoryResolver {
+    public VsftpResolver() {
+        setRepository(new VsftpRepository());
+    }
+
+    public String getTypeName() {
+        return "vsftp";
+    }
+
+    public VsftpRepository getVsftpRepository() {
+        return (VsftpRepository) getRepository();
+    }
+
+    public void disconnect() {
+        getVsftpRepository().disconnect();
+    }
+
+    public String getAuthentication() {
+        return getVsftpRepository().getAuthentication();
+    }
+
+    public String getHost() {
+        return getVsftpRepository().getHost();
+    }
+
+    public String getUsername() {
+        return getVsftpRepository().getUsername();
+    }
+
+    public void setAuthentication(String authentication) {
+        getVsftpRepository().setAuthentication(authentication);
+    }
+
+    public void setHost(String host) {
+        getVsftpRepository().setHost(host);
+    }
+
+    public void setUsername(String username) {
+        getVsftpRepository().setUsername(username);
+    }
+
+    public void setReuseConnection(long time) {
+        getVsftpRepository().setReuseConnection(time);
+    }
+
+    public void setReadTimeout(long readTimeout) {
+        getVsftpRepository().setReadTimeout(readTimeout);
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/resolver/packager/BuiltFileResource.java b/src/java/org/apache/ivy/plugins/resolver/packager/BuiltFileResource.java
new file mode 100644
index 0000000..ce8912a
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/resolver/packager/BuiltFileResource.java
@@ -0,0 +1,85 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.resolver.packager;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.plugins.repository.Resource;
+
+/**
+ * Represents an artifact built by a {@link PackagerResolver}.
+ */
+public class BuiltFileResource implements Resource {
+
+    /**
+     * Where the build file should put built artifacts (relative to the build directory). Value is:
+     * * {@value}
+     */
+    public static final String BUILT_ARTIFACT_PATTERN = "artifacts/[type]s/[artifact].[ext]";
+
+    private final File file;
+
+    public BuiltFileResource(File file) {
+        this.file = file;
+    }
+
+    public BuiltFileResource(File dir, Artifact artifact) {
+        this(new File(dir, IvyPatternHelper.substitute(BUILT_ARTIFACT_PATTERN, artifact)));
+    }
+
+    public String getName() {
+        return file.toURI().toString();
+    }
+
+    public Resource clone(String name) {
+        return new BuiltFileResource(new File(name));
+    }
+
+    public long getLastModified() {
+        return file.lastModified();
+    }
+
+    public long getContentLength() {
+        return file.length();
+    }
+
+    public boolean exists() {
+        return file.exists();
+    }
+
+    public String toString() {
+        return getName();
+    }
+
+    public File getFile() {
+        return file;
+    }
+
+    public boolean isLocal() {
+        return false;
+    }
+
+    public InputStream openStream() throws IOException {
+        return new FileInputStream(file);
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/resolver/packager/PackagerCacheEntry.java b/src/java/org/apache/ivy/plugins/resolver/packager/PackagerCacheEntry.java
new file mode 100644
index 0000000..a7fe87a
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/resolver/packager/PackagerCacheEntry.java
@@ -0,0 +1,242 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.resolver.packager;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.plugins.resolver.util.ResolvedResource;
+import org.apache.ivy.util.FileUtil;
+import org.apache.ivy.util.Message;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.BuildLogger;
+import org.apache.tools.ant.DefaultLogger;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.ProjectHelper;
+
+/**
+ * Represents one entry in the cache of a {@link PackagerResolver}.
+ */
+public class PackagerCacheEntry {
+
+    private final ModuleRevisionId mr;
+
+    private final File dir;
+
+    private final File resourceCache;
+
+    private final String resourceURL;
+
+    private final boolean validate;
+
+    private final boolean preserve;
+
+    private final boolean restricted;
+
+    private final boolean verbose;
+
+    private final boolean quiet;
+
+    private boolean built;
+
+    // CheckStyle:ParameterNumber OFF
+    public PackagerCacheEntry(ModuleRevisionId mr, File rootDir, File resourceCache,
+            String resourceURL, boolean validate, boolean preserve, boolean restricted,
+            boolean verbose, boolean quiet) {
+        this.mr = mr;
+        this.dir = getSubdir(rootDir, this.mr);
+        this.resourceCache = resourceCache;
+        this.resourceURL = resourceURL;
+        this.validate = validate;
+        this.preserve = preserve;
+        this.restricted = restricted;
+        this.verbose = verbose;
+        this.quiet = quiet;
+    }
+
+    // CheckStyle:ParameterNumber ON
+
+    /**
+     * Attempt to build this entry.
+     * 
+     * @param packagerResource
+     *            packager metadata resource
+     * @param properties
+     *            a map of properties to pass to the child Ant build responsible for dependency
+     *            packaging
+     * 
+     * @throws IllegalStateException
+     *             if this entry has already been built
+     */
+    public synchronized void build(Resource packagerResource, Map properties) throws IOException {
+        // Sanity check
+        if (this.built) {
+            throw new IllegalStateException("build in directory `" + this.dir
+                    + "' already completed");
+        }
+
+        // Remove work directory if it exists (e.g. left over from last time)
+        if (this.dir.exists()) {
+            if (!cleanup()) {
+                throw new IOException("can't remove directory `" + this.dir + "'");
+            }
+        }
+
+        // Create work directory
+        if (!this.dir.mkdirs()) {
+            throw new IOException("can't create directory `" + this.dir + "'");
+        }
+
+        // Write out packager XML
+        InputStream packagerXML = packagerResource.openStream();
+        saveFile("packager.xml", packagerXML);
+
+        // Write packager XSLT
+        saveFile("packager.xsl");
+
+        // Write packager XSD
+        saveFile("packager-1.0.xsd");
+
+        // Write master Ant build file
+        saveFile("build.xml");
+
+        // Execute the Ant build file
+        Project project = new Project();
+        project.init();
+        project.setUserProperty("ant.file", new File(dir, "build.xml").getAbsolutePath());
+        ProjectHelper.configureProject(project, new File(dir, "build.xml"));
+        project.setBaseDir(dir);
+
+        // Configure logging verbosity
+        BuildLogger logger = new DefaultLogger();
+        logger.setMessageOutputLevel(this.verbose ? Project.MSG_VERBOSE
+                : this.quiet ? Project.MSG_WARN : Project.MSG_INFO);
+        logger.setOutputPrintStream(System.out);
+        logger.setErrorPrintStream(System.err);
+        project.addBuildListener(logger);
+
+        // Set properties
+        project.setUserProperty("ivy.packager.organisation", ""
+                + this.mr.getModuleId().getOrganisation());
+        project.setUserProperty("ivy.packager.module", "" + this.mr.getModuleId().getName());
+        project.setUserProperty("ivy.packager.revision", "" + this.mr.getRevision());
+        project.setUserProperty("ivy.packager.branch", "" + this.mr.getBranch());
+        if (this.resourceCache != null) {
+            project.setUserProperty("ivy.packager.resourceCache",
+                "" + this.resourceCache.getCanonicalPath());
+        }
+        if (this.resourceURL != null) {
+            project.setUserProperty("ivy.packager.resourceURL", "" + getResourceURL());
+        }
+        if (this.validate) {
+            project.setUserProperty("ivy.packager.validate", "true");
+        }
+        project.setUserProperty("ivy.packager.restricted", "" + this.restricted);
+        project.setUserProperty("ivy.packager.quiet", String.valueOf(quiet));
+        if (properties != null) {
+            for (Iterator it = properties.entrySet().iterator(); it.hasNext();) {
+                Entry entry = (Entry) it.next();
+                project.setUserProperty((String) entry.getKey(), (String) entry.getValue());
+            }
+        }
+
+        // Execute task
+        Message.verbose("performing packager resolver build in " + this.dir);
+        try {
+            project.executeTarget("build");
+            this.built = true;
+        } catch (BuildException e) {
+            Message.verbose("packager resolver build failed: " + e);
+            throw e;
+        }
+    }
+
+    /**
+     * Has this entry been successfully built?
+     */
+    public synchronized boolean isBuilt() {
+        return this.built;
+    }
+
+    /**
+     * Get a built artifact.
+     * 
+     * @throws IllegalStateException
+     *             if this entry's built has not (yet) completed successfully
+     */
+    public ResolvedResource getBuiltArtifact(Artifact artifact) {
+        if (!this.built) {
+            throw new IllegalStateException("build in directory `" + this.dir
+                    + "' has not yet successfully completed");
+        }
+        return new ResolvedResource(new BuiltFileResource(this.dir, artifact),
+                this.mr.getRevision());
+    }
+
+    public synchronized boolean cleanup() {
+        this.built = false;
+        return FileUtil.forceDelete(this.dir);
+    }
+
+    protected void saveFile(String name, InputStream input) throws IOException {
+        FileUtil.copy(input, new File(this.dir, name), null);
+    }
+
+    protected void saveFile(String name) throws IOException {
+        InputStream input = getClass().getResourceAsStream(name);
+        if (input == null) {
+            throw new IOException("can't find resource `" + name + "'");
+        }
+        saveFile(name, input);
+    }
+
+    // @Override
+    protected void finalize() throws Throwable {
+        try {
+            if (!this.preserve) {
+                cleanup();
+            }
+        } finally {
+            super.finalize();
+        }
+    }
+
+    private String getResourceURL() {
+        String baseURL = IvyPatternHelper.substitute(resourceURL, mr.getOrganisation(),
+            mr.getName(), mr.getRevision(), null, null, null, null,
+            mr.getQualifiedExtraAttributes(), null);
+        int slash = baseURL.lastIndexOf('/');
+        if (slash != -1) {
+            baseURL = baseURL.substring(0, slash + 1);
+        }
+        return baseURL;
+    }
+
+    private static File getSubdir(File rootDir, ModuleRevisionId mr) {
+        return new File(rootDir, mr.getOrganisation() + File.separatorChar + mr.getName()
+                + File.separatorChar + mr.getRevision());
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/resolver/packager/PackagerResolver.java b/src/java/org/apache/ivy/plugins/resolver/packager/PackagerResolver.java
new file mode 100644
index 0000000..6a12ba1
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/resolver/packager/PackagerResolver.java
@@ -0,0 +1,255 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.resolver.packager;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+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.ivy.plugins.resolver.URLResolver;
+import org.apache.ivy.plugins.resolver.util.ResolvedResource;
+import org.apache.ivy.util.FileUtil;
+import org.apache.ivy.util.Message;
+
+/**
+ * Resolver that performs a "build" operation to resolve artifacts.
+ * 
+ * <p>
+ * The resolver is configured with a base URL, from which the "ivy.xml" and "packager.xml" files are
+ * resolved. The latter file contains instructions describing how to build the actual artifacts.
+ */
+public class PackagerResolver extends URLResolver {
+
+    private static final String PACKAGER_ARTIFACT_NAME = "packager";
+
+    private static final String PACKAGER_ARTIFACT_TYPE = "packager";
+
+    private static final String PACKAGER_ARTIFACT_EXT = "xml";
+
+    private final HashMap/* <ModuleRevisionId, PackagerCacheEntry> */packagerCache = new HashMap();
+
+    private File buildRoot;
+
+    private File resourceCache;
+
+    private String resourceURL;
+
+    private Map/* <String,String> */properties = new LinkedHashMap();
+
+    private boolean validate = true;
+
+    private boolean preserve;
+
+    private boolean restricted = true;
+
+    private boolean verbose;
+
+    private boolean quiet;
+
+    public PackagerResolver() {
+        Runtime.getRuntime().addShutdownHook(new Thread() {
+            public void run() {
+                clearCache();
+            }
+        });
+    }
+
+    protected synchronized void clearCache() {
+        if (this.preserve) {
+            return;
+        }
+        for (Iterator i = packagerCache.values().iterator(); i.hasNext();) {
+            PackagerCacheEntry entry = (PackagerCacheEntry) i.next();
+            entry.cleanup();
+        }
+        packagerCache.clear();
+        if (this.buildRoot != null) {
+            FileUtil.forceDelete(this.buildRoot);
+        }
+    }
+
+    /**
+     * Set root directory under which builds take place.
+     */
+    public void setBuildRoot(File buildRoot) {
+        this.buildRoot = buildRoot;
+    }
+
+    /**
+     * Returns root directory under which builds take place.
+     */
+    public File getBuildRoot() {
+        return buildRoot;
+    }
+
+    /**
+     * Set resource cache directory.
+     */
+    public void setResourceCache(File resourceCache) {
+        this.resourceCache = resourceCache;
+    }
+
+    /**
+     * Get resource cache directory.
+     */
+    public File getResourceCache() {
+        return resourceCache;
+    }
+
+    /**
+     * Set base resource override URL pattern.
+     */
+    public void setResourceURL(String resourceURL) {
+        this.resourceURL = resourceURL;
+    }
+
+    /**
+     * Set pattern for locating "packager.xml" files.
+     */
+    public void setPackagerPattern(String pattern) {
+        ArrayList list = new ArrayList();
+        list.add(pattern);
+        setArtifactPatterns(list);
+    }
+
+    /**
+     * Set whether to preserve build directories. Default is false.
+     */
+    public void setPreserveBuildDirectories(boolean preserve) {
+        this.preserve = preserve;
+    }
+
+    /**
+     * Set whether to enable restricted mode. Default is true.
+     */
+    public void setRestricted(boolean restricted) {
+        this.restricted = restricted;
+    }
+
+    /**
+     * Set whether to run ant with the -verbose flag. Default is false.
+     */
+    public void setVerbose(boolean verbose) {
+        this.verbose = verbose;
+    }
+
+    /**
+     * Set whether to run ant with the -quiet flag. Default is false.
+     */
+    public void setQuiet(boolean quiet) {
+        this.quiet = quiet;
+    }
+
+    /**
+     * Set whether to validate downloaded packager.xml files. Default is true.
+     */
+    public void setValidate(boolean validate) {
+        this.validate = validate;
+    }
+
+    public void setAllownomd(boolean b) {
+        Message.error("allownomd not supported by resolver " + this);
+    }
+
+    public void setDescriptor(String rule) {
+        if (DESCRIPTOR_OPTIONAL.equals(rule)) {
+            Message.error("descriptor=\"" + DESCRIPTOR_OPTIONAL + "\" not supported by resolver "
+                    + this);
+            return;
+        }
+        super.setDescriptor(rule);
+    }
+
+    /**
+     * Sets a property to be passed to the child Ant build responsible for packaging the dependency.
+     * 
+     * @param propertyKey
+     *            the property to pass
+     * @param propertyValue
+     *            the value of the property to pass
+     */
+    public void setProperty(String propertyKey, String propertyValue) {
+        properties.put(propertyKey, propertyValue);
+    }
+
+    // @Override
+    public void validate() {
+        super.validate();
+        if (this.buildRoot == null) {
+            throw new IllegalStateException("no buildRoot specified");
+        }
+        if (getArtifactPatterns().size() == 0) {
+            throw new IllegalStateException("no packager pattern specified");
+        }
+    }
+
+    // @Override
+    public synchronized ResolvedResource findArtifactRef(Artifact artifact, Date date) {
+
+        // For our special packager.xml file, defer to superclass
+        if (PACKAGER_ARTIFACT_NAME.equals(artifact.getName())
+                && PACKAGER_ARTIFACT_TYPE.equals(artifact.getType())
+                && PACKAGER_ARTIFACT_EXT.equals(artifact.getExt())) {
+            return super.findArtifactRef(artifact, date);
+        }
+
+        // Check the cache
+        ModuleRevisionId mr = artifact.getModuleRevisionId();
+        PackagerCacheEntry entry = (PackagerCacheEntry) packagerCache.get(mr);
+
+        // Ignore invalid entries
+        if (entry != null && !entry.isBuilt()) {
+            packagerCache.remove(mr);
+            entry.cleanup();
+            entry = null;
+        }
+
+        // Build the artifacts (if not done already)
+        if (entry == null) {
+            ResolvedResource packager = findArtifactRef(new DefaultArtifact(mr, null,
+                    PACKAGER_ARTIFACT_NAME, PACKAGER_ARTIFACT_TYPE, PACKAGER_ARTIFACT_EXT), date);
+            if (packager == null) {
+                return null;
+            }
+            entry = new PackagerCacheEntry(mr, this.buildRoot, this.resourceCache,
+                    this.resourceURL, this.validate, this.preserve, this.restricted, this.verbose,
+                    this.quiet);
+            try {
+                entry.build(packager.getResource(), properties);
+            } catch (IOException e) {
+                throw new RuntimeException("can't build artifact " + artifact, e);
+            }
+            packagerCache.put(mr, entry);
+        }
+
+        // Return reference to desired artifact
+        return entry.getBuiltArtifact(artifact);
+    }
+
+    public String getTypeName() {
+        return "packager";
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/resolver/packager/build.xml b/src/java/org/apache/ivy/plugins/resolver/packager/build.xml
new file mode 100644
index 0000000..f112fec
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/resolver/packager/build.xml
@@ -0,0 +1,58 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.
+-->
+<project name="packager" default="build">
+
+    <!-- Validate packager.xml -->
+    <target name="validate" if="ivy.packager.validate">
+        <xmlvalidate failonerror="true" warn="yes" lenient="no">
+            <attribute name="http://xml.org/sax/features/namespaces" value="true"/>
+            <attribute name="http://apache.org/xml/features/validation/schema" value="true"/>
+            <attribute name="http://apache.org/xml/features/validation/schema-full-checking" value="true"/>
+            <property name="http://apache.org/xml/properties/schema/external-noNamespaceSchemaLocation" value="packager-1.0.xsd"/>
+            <fileset dir=".">
+                <include name="packager.xml"/>
+            </fileset>
+        </xmlvalidate>
+    </target>
+
+    <!-- Set ${resourceURL} (case 1) -->
+    <target name="setResourceURL1" if="ivy.packager.resourceURL">
+        <property name="resourceURL" value="${ivy.packager.resourceURL}"/>
+    </target>
+
+    <!-- Set ${resourceURL} (case 2) -->
+    <target name="setResourceURL2" unless="ivy.packager.resourceURL">
+        <property name="resourceURL" value=""/>
+    </target>
+
+    <!-- Apply XSLT to generate ant build file -->
+    <target name="style">
+        <xslt style="packager.xsl" in="packager.xml" out="packager-output.xml">
+            <param name="resourceURL" expression="${resourceURL}"/>
+            <param name="restricted" expression="${ivy.packager.restricted}"/>
+        	<param name="quiet" expression="${ivy.packager.quiet}"/>
+        </xslt>
+    </target>
+
+    <!-- Main build target: invoke generated build file -->
+    <target name="build" depends="validate, setResourceURL1, setResourceURL2, style">
+        <ant antfile="packager-output.xml"/>
+    </target>
+
+</project>
diff --git a/src/java/org/apache/ivy/plugins/resolver/packager/packager-1.0.xsd b/src/java/org/apache/ivy/plugins/resolver/packager/packager-1.0.xsd
new file mode 100644
index 0000000..394cc87
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/resolver/packager/packager-1.0.xsd
@@ -0,0 +1,101 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.
+-->
+<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+
+    <xsd:element name="packager-module">
+        <xsd:complexType>
+            <xsd:sequence>
+                <xsd:choice maxOccurs="unbounded">
+                    <xsd:element name="property">
+                        <xsd:complexType>
+                            <xsd:attribute name="name" type="xsd:string" use="required"/>
+                            <xsd:attribute name="value" type="xsd:string" use="required"/>
+                        </xsd:complexType>
+                    </xsd:element>
+                    <xsd:element name="resource">
+                        <xsd:complexType>
+                            <xsd:sequence minOccurs="0" maxOccurs="unbounded">
+                                <xsd:any processContents="lax"/>    <!-- XXX refine me -->
+                            </xsd:sequence>
+                            <xsd:attribute name="url" type="xsd:anyURI" use="required"/>
+                            <xsd:attribute name="sha1" type="sha1Type" use="required"/>
+                            <xsd:attribute name="dest" type="xsd:string"/>
+                            <xsd:attribute name="tofile" type="xsd:string"/>
+                            <xsd:attribute name="type" type="archiveType"/>
+                            <xsd:attribute name="filename" type="xsd:string"/>
+                        </xsd:complexType>
+                    </xsd:element>
+                    <xsd:element name="m2resource">
+                        <xsd:complexType>
+                            <xsd:sequence minOccurs="0" maxOccurs="unbounded">
+                                <xsd:element name="artifact">
+                                    <xsd:complexType>
+                                        <xsd:attribute name="ext" type="xsd:string"/>
+                                        <xsd:attribute name="classifier" type="xsd:string"/>
+                                        <xsd:attribute name="sha1" type="sha1Type" use="required"/>
+                                        <xsd:attribute name="dest" type="xsd:string"/>
+                                        <xsd:attribute name="tofile" type="xsd:string"/>
+                                        <xsd:attribute name="type" type="archiveType"/>
+                                    </xsd:complexType>
+                                </xsd:element>
+                            </xsd:sequence>
+                            <xsd:attribute name="groupId" type="xsd:string"/>
+                            <xsd:attribute name="artifactId" type="xsd:string"/>
+                            <xsd:attribute name="version" type="xsd:string"/>
+                            <xsd:attribute name="repo" type="xsd:anyURI"/>
+                        </xsd:complexType>
+                    </xsd:element>
+                    <xsd:element name="build">
+                        <xsd:complexType>
+                            <xsd:sequence minOccurs="0" maxOccurs="unbounded">
+                                <xsd:any processContents="lax"/>    <!-- XXX refine me -->
+                            </xsd:sequence>
+                        </xsd:complexType>
+                    </xsd:element>
+                </xsd:choice>
+            </xsd:sequence>
+            <xsd:attribute name="version" type="versionType" use="required"/>
+        </xsd:complexType>
+    </xsd:element>
+
+    <xsd:simpleType name="sha1Type">
+        <xsd:restriction base="xsd:string">
+            <xsd:pattern value="[0-9a-f]{40}"/>
+        </xsd:restriction>
+    </xsd:simpleType>
+
+    <xsd:simpleType name="versionType">
+        <xsd:restriction base="xsd:string">
+            <xsd:enumeration value="1.0"/>
+        </xsd:restriction>
+    </xsd:simpleType>
+
+    <xsd:simpleType name="archiveType">
+        <xsd:restriction base="xsd:string">
+            <xsd:enumeration value="zip"/>
+            <xsd:enumeration value="war"/>
+            <xsd:enumeration value="jar"/>
+            <xsd:enumeration value="tar"/>
+            <xsd:enumeration value="tgz"/>
+            <xsd:enumeration value="tar.gz"/>
+            <xsd:enumeration value="tar.bz2"/>
+        </xsd:restriction>
+    </xsd:simpleType>
+
+</xsd:schema>
diff --git a/src/java/org/apache/ivy/plugins/resolver/packager/packager.xsl b/src/java/org/apache/ivy/plugins/resolver/packager/packager.xsl
new file mode 100644
index 0000000..f42e7d3
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/resolver/packager/packager.xsl
@@ -0,0 +1,499 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT 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:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+
+    <xsl:output encoding="UTF-8" method="xml" indent="yes" media-type="text/xml"/>
+
+    <xsl:param name="resourceURL"/>
+    <xsl:param name="restricted"/>
+    <xsl:param name="quiet"/>
+
+    <xsl:variable name="maven2repo" select="'https://repo1.maven.org/maven2/'"/>
+
+    <xsl:template match="/packager-module">
+        <xsl:comment> GENERATED FILE - DO NOT EDIT </xsl:comment>
+        <project name="packager" default="build">
+            <xsl:apply-templates select="property"/>
+
+            <xsl:apply-templates select="resource | m2resource"/>
+
+            <!-- First, download and extract all resources -->
+            <target name="resources">
+                <xsl:attribute name="depends">
+                    <xsl:for-each select="resource | m2resource/artifact">
+                        <xsl:if test="position() > 1">
+                            <xsl:value-of select="', '"/>
+                        </xsl:if>
+                        <xsl:value-of select="concat('resource.', generate-id())"/>
+                    </xsl:for-each>
+                </xsl:attribute>
+            </target>
+
+            <!-- Second, put all artifacts into place under artifacts/ -->
+            <target name="build" depends="resources">
+                <mkdir dir="artifacts/jars"/>
+                <mkdir dir="artifacts/sources"/>
+                <mkdir dir="artifacts/javadocs"/>
+                <!-- ...add some other common artifact types here... -->
+                <xsl:apply-templates select="build/*"/>
+            </target>
+        </project>
+    </xsl:template>
+
+    <!-- Properties -->
+    <xsl:template match="/packager-module/property">
+        <xsl:copy-of select="."/>
+    </xsl:template>
+
+    <!-- The allowed build actions in restricted mode -->
+    <xsl:template match="/packager-module/build/copy" priority="1"><xsl:copy-of select="."/></xsl:template>
+    <xsl:template match="/packager-module/build/jar" priority="1"><xsl:copy-of select="."/></xsl:template>
+    <xsl:template match="/packager-module/build/mkdir" priority="1"><xsl:copy-of select="."/></xsl:template>
+    <xsl:template match="/packager-module/build/move" priority="1"><xsl:copy-of select="."/></xsl:template>
+    <xsl:template match="/packager-module/build/tar" priority="1"><xsl:copy-of select="."/></xsl:template>
+    <xsl:template match="/packager-module/build/unjar" priority="1"><xsl:copy-of select="."/></xsl:template>
+    <xsl:template match="/packager-module/build/untar" priority="1"><xsl:copy-of select="."/></xsl:template>
+    <xsl:template match="/packager-module/build/unwar" priority="1"><xsl:copy-of select="."/></xsl:template>
+    <xsl:template match="/packager-module/build/unzip" priority="1"><xsl:copy-of select="."/></xsl:template>
+    <xsl:template match="/packager-module/build/war" priority="1"><xsl:copy-of select="."/></xsl:template>
+    <xsl:template match="/packager-module/build/zip" priority="1"><xsl:copy-of select="."/></xsl:template>
+
+    <!-- Allow other build actions when restricted="false", otherwise generate error -->
+    <xsl:template match="/packager-module/build/*">
+        <xsl:choose>
+            <xsl:when test="$restricted = 'false'">
+                <xsl:copy-of select="."/>
+            </xsl:when>
+            <xsl:otherwise>
+                <xsl:message terminate="yes">build tag <<xsl:value-of select="name()"/>> not allowed in restricted mode</xsl:message>
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+    <!-- Resource definitions -->
+    <xsl:template match="/packager-module/resource">
+
+        <!-- Convert URL into simple filename -->
+        <target name="setfilename.{generate-id()}">
+            <xsl:choose>
+                <xsl:when test="@filename">
+                    <property name="filename.{generate-id()}" value="{@filename}"/>
+                </xsl:when>
+                <xsl:otherwise>
+                    <basename property="filename.{generate-id()}" file="{@url}"/>
+                </xsl:otherwise>
+            </xsl:choose>
+        </target>
+
+        <!-- Generate list of URLs to try -->
+        <xsl:variable name="urls">
+            <xsl:call-template name="concat">
+                <xsl:with-param name="nodes" select="@url | url/@href"/>
+            </xsl:call-template>
+        </xsl:variable>
+
+        <!-- Get resource -->
+        <xsl:call-template name="resource">
+            <xsl:with-param name="urls" select="$urls"/>
+            <xsl:with-param name="type" select="@type"/>
+            <xsl:with-param name="csum" select="@sha1"/>
+            <xsl:with-param name="dest" select="@dest"/>
+            <xsl:with-param name="tofile" select="@tofile"/>
+            <xsl:with-param name="filename" select="concat('${filename.', generate-id(), '}')"/>
+            <xsl:with-param name="depends" select="concat('setfilename.', generate-id())"/>
+        </xsl:call-template>
+    </xsl:template>
+
+    <!-- Maven2 resources -->
+    <xsl:template match="/packager-module/m2resource">
+
+        <!-- Convert groupId into URL directories, where dots become slashes -->
+        <xsl:variable name="groupdirs" select="concat('groupdirs.', generate-id())"/>
+        <target name="setgroupdirs.{generate-id()}">
+            <xsl:variable name="groupId">
+                <xsl:choose>
+                    <xsl:when test="@groupId">
+                        <xsl:value-of select="@groupId"/>
+                    </xsl:when>
+                    <xsl:otherwise>
+                        <xsl:value-of select="'${ivy.packager.organisation}'"/>
+                    </xsl:otherwise>
+                </xsl:choose>
+            </xsl:variable>
+            <pathconvert property="{$groupdirs}" dirsep="/">
+                <path location="{$groupId}"/>
+                <mapper type="unpackage" to="*">
+                    <xsl:attribute name="from">
+                        <xsl:value-of select="'${basedir}${file.separator}*'"/>
+                    </xsl:attribute>
+                </mapper>
+            </pathconvert>
+        </target>
+
+        <!-- Get maven2 artifactId (or use default) -->
+        <xsl:variable name="artifactId">
+            <xsl:choose>
+                <xsl:when test="@artifactId">
+                    <xsl:value-of select="@artifactId"/>
+                </xsl:when>
+                <xsl:otherwise>
+                    <xsl:value-of select="'${ivy.packager.module}'"/>
+                </xsl:otherwise>
+            </xsl:choose>
+        </xsl:variable>
+
+        <!-- Get maven2 version (or use default) -->
+        <xsl:variable name="version">
+            <xsl:choose>
+                <xsl:when test="@version">
+                    <xsl:value-of select="@version"/>
+                </xsl:when>
+                <xsl:otherwise>
+                    <xsl:value-of select="'${ivy.packager.revision}'"/>
+                </xsl:otherwise>
+            </xsl:choose>
+        </xsl:variable>
+
+        <!-- Get maven2 repository URL (or use default) -->
+        <xsl:variable name="repourl">
+            <xsl:choose>
+                <xsl:when test="@repo and substring(@repo, string-length(@repo) - 1) = '/'">
+                    <xsl:value-of select="@repo"/>
+                </xsl:when>
+                <xsl:when test="@repo">
+                    <xsl:value-of select="concat(@repo, '/')"/>
+                </xsl:when>
+                <xsl:otherwise>
+                    <xsl:value-of select="$maven2repo"/>
+                </xsl:otherwise>
+            </xsl:choose>
+        </xsl:variable>
+
+        <!-- Compose directory in the maven2 repository -->
+        <xsl:variable name="m2dir" select="concat($repourl, '${', $groupdirs, '}/', $artifactId, '/', $version, '/')"/>
+
+        <!-- Iterate over artifacts -->
+        <xsl:for-each select="artifact">
+
+            <!-- Get classifier (or use default) -->
+            <xsl:variable name="classifier">
+                <xsl:choose>
+                    <xsl:when test="@classifier">
+                        <xsl:value-of select="concat('-', @classifier)"/>
+                    </xsl:when>
+                    <xsl:otherwise/>
+                </xsl:choose>
+            </xsl:variable>
+
+            <!-- Get classifier (or use default) -->
+            <xsl:variable name="suffix">
+                <xsl:choose>
+                    <xsl:when test="@ext">
+                        <xsl:value-of select="concat('.', @ext)"/>
+                    </xsl:when>
+                    <xsl:otherwise>
+                        <xsl:value-of select="'.jar'"/>
+                    </xsl:otherwise>
+                </xsl:choose>
+            </xsl:variable>
+
+            <!-- Compose to get filename, then complete URL -->
+            <xsl:variable name="filename" select="concat($artifactId, '-', $version, $classifier, $suffix)"/>
+            <xsl:variable name="url" select="concat($m2dir, $filename)"/>
+
+            <!-- Get resource -->
+            <xsl:call-template name="resource">
+                <xsl:with-param name="urls" select="$url"/>
+                <xsl:with-param name="csum" select="@sha1"/>
+                <xsl:with-param name="dest" select="@dest"/>
+                <xsl:with-param name="tofile" select="@tofile"/>
+                <xsl:with-param name="type" select="@type"/>
+                <xsl:with-param name="filename" select="$filename"/>
+                <xsl:with-param name="depends" select="concat('setgroupdirs.', generate-id(..))"/>
+            </xsl:call-template>
+        </xsl:for-each>
+    </xsl:template>
+
+    <!-- Download and optionally unpack a resource -->
+    <xsl:template name="resource">
+        <xsl:param name="urls"/>
+        <xsl:param name="type"/>
+        <xsl:param name="csum"/>
+        <xsl:param name="dest"/>
+        <xsl:param name="tofile"/>
+        <xsl:param name="filename"/>
+        <xsl:param name="depends"/>
+
+        <!-- Figure out which directory to download into (at runtime) -->
+        <target name="genresdir.1.{generate-id()}" unless="ivy.packager.resourceCache">
+            <property name="resdir.{generate-id()}" value="tempdir.{generate-id()}"/>
+        </target>
+        <target name="genresdir.2.{generate-id()}" if="ivy.packager.resourceCache">
+            <property name="resdir.{generate-id()}">
+                <xsl:attribute name="value">${ivy.packager.resourceCache}</xsl:attribute>
+            </property>
+            <echo level="info">
+                <xsl:attribute name="message">using resource cache: ${ivy.packager.resourceCache}</xsl:attribute>
+            </echo>
+        </target>
+        <xsl:variable name="resdir" select="concat('${resdir.', generate-id(), '}')"/>
+        <xsl:variable name="downloadfile" select="concat($resdir, '${file.separator}', $filename)"/>
+
+        <!-- Create directory for the downloaded resource -->
+        <target name="checkdownload.0.{generate-id()}" depends="{$depends}, genresdir.1.{generate-id()}, genresdir.2.{generate-id()}">
+            <mkdir dir="{$resdir}"/>
+            <condition property="alreadydownloaded.{generate-id()}">
+                <and>
+                    <available file="{$downloadfile}"/>
+                    <checksum file="{$downloadfile}" algorithm="SHA" property="{$csum}"/>
+                </and>
+            </condition>
+        </target>
+
+        <!-- Prepend URL list with resourceURL if configured -->
+        <xsl:variable name="urls2">
+            <xsl:choose>
+                <xsl:when test="$resourceURL">
+                    <xsl:value-of select="concat($resourceURL, $filename, ' ', $urls)"/>
+                </xsl:when>
+                <xsl:otherwise>
+                    <xsl:value-of select="$urls"/>
+                </xsl:otherwise>
+            </xsl:choose>
+        </xsl:variable>
+
+        <!-- Try to download each of the URLs in order -->
+        <xsl:call-template name="downloads">
+            <xsl:with-param name="urls" select="$urls2"/>
+            <xsl:with-param name="filename" select="$filename"/>
+            <xsl:with-param name="destfile" select="$downloadfile"/>
+            <xsl:with-param name="csum" select="$csum"/>
+        </xsl:call-template>
+
+        <!-- Unpack or just copy the file to its destination -->
+        <target name="resource.{generate-id()}" depends="download.{generate-id()}">
+            <xsl:choose>
+                <xsl:when test="$tofile">
+                    <copy file="{$downloadfile}" tofile="{$tofile}"/>
+                </xsl:when>
+                <xsl:otherwise>
+                    <xsl:variable name="realdest">
+                        <xsl:choose>
+                            <xsl:when test="$dest">
+                                <xsl:value-of select="$dest"/>
+                            </xsl:when>
+                            <xsl:otherwise>archive</xsl:otherwise>
+                        </xsl:choose>
+                    </xsl:variable>
+                    <xsl:choose>
+                        <xsl:when test="$type = 'none'">
+                            <copy file="{$downloadfile}" todir="{$realdest}"/>
+                        </xsl:when>
+                        <xsl:otherwise>
+                            <xsl:variable name="actualtype">
+                                <xsl:choose>
+                                    <xsl:when test="$type">
+                                        <xsl:value-of select="$type"/>
+                                    </xsl:when>
+                                    <xsl:when test="@filename">
+                                        <xsl:call-template name="archiveType">
+                                            <xsl:with-param name="file" select="@filename"/>
+                                        </xsl:call-template>
+                                    </xsl:when>
+                                    <xsl:otherwise>
+                                        <xsl:call-template name="archiveType">
+                                            <xsl:with-param name="file" select="$urls"/>
+                                        </xsl:call-template>
+                                    </xsl:otherwise>
+                                </xsl:choose>
+                            </xsl:variable>
+                            <xsl:call-template name="unpack">
+                                <xsl:with-param name="file" select="$downloadfile"/>
+                                <xsl:with-param name="dir" select="$realdest"/>
+                                <xsl:with-param name="type" select="$actualtype"/>
+                            </xsl:call-template>
+                        </xsl:otherwise>
+                    </xsl:choose>
+                </xsl:otherwise>
+            </xsl:choose>
+        </target>
+    </xsl:template>
+
+    <!-- Download resource, trying one or more URLs in order -->
+    <xsl:template name="downloads">
+        <xsl:param name="urls"/>
+        <xsl:param name="filename"/>
+        <xsl:param name="destfile"/>
+        <xsl:param name="csum"/>
+        <xsl:param name="index" select="1"/>
+
+        <!-- Parse out first URL from whitespace-separated list -->
+        <xsl:variable name="nurls" select="normalize-space($urls)"/>
+        <xsl:variable name="isFirstURL" select="$index = 1"/>
+        <xsl:variable name="isLastURL" select="not(contains($nurls, ' '))"/>
+        <xsl:variable name="url">
+            <xsl:choose>
+                <xsl:when test="not($isLastURL)">
+                    <xsl:value-of select="substring-before($nurls, ' ')"/>
+                </xsl:when>
+                <xsl:otherwise>
+                    <xsl:value-of select="$nurls"/>
+                </xsl:otherwise>
+            </xsl:choose>
+        </xsl:variable>
+        <xsl:variable name="rurls" select="substring-after($nurls, ' ')"/>
+
+        <!-- Attempt download using this URL; allow download to fail if this is not the last URL -->
+        <target name="download.{$index}.{generate-id()}" depends="checkdownload.{$index - 1}.{generate-id()}"
+          unless="alreadydownloaded.{generate-id()}">
+            <get src="{$url}" dest="{$destfile}" verbose="{string($quiet = 'false')}" ignoreerrors="{string(not($isLastURL))}"/>
+        </target>
+
+        <!-- Check whether download attempt was successful -->
+        <target name="checkdownload.{$index}.{generate-id()}" depends="download.{$index}.{generate-id()}"
+          unless="alreadydownloaded.{generate-id()}">
+            <condition property="alreadydownloaded.{generate-id()}">
+                <and>
+                    <available file="{$destfile}"/>
+                    <checksum file="{$destfile}" algorithm="SHA" property="{$csum}"/>
+                </and>
+            </condition>
+        </target>
+
+        <!-- Do final check after last attempt, or recurse -->
+        <xsl:choose>
+            <xsl:when test="$isLastURL">
+                <target name="download.{generate-id()}" depends="checkdownload.{$index}.{generate-id()}"
+                  unless="alreadydownloaded.{generate-id()}">
+                    <fail message="Unable to download {$filename} from any configured URL">
+                        <condition>
+                            <not>
+                                <available file="{$destfile}"/>
+                            </not>
+                        </condition>
+                    </fail>
+                    <fail message="SHA1 checksum verification for {$filename} failed!"/>
+                </target>
+            </xsl:when>
+            <xsl:otherwise>
+                <xsl:call-template name="downloads">
+                    <xsl:with-param name="urls" select="$rurls"/>
+                    <xsl:with-param name="filename" select="$filename"/>
+                    <xsl:with-param name="destfile" select="$destfile"/>
+                    <xsl:with-param name="csum" select="$csum"/>
+                    <xsl:with-param name="index" select="$index + 1"/>
+                </xsl:call-template>
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+    <!-- Unpack an archive -->
+    <xsl:template name="unpack">
+        <xsl:param name="file"/>
+        <xsl:param name="dir"/>
+        <xsl:param name="type"/>
+
+        <!-- Get nested includes, excludes, etc. -->
+        <xsl:variable name="includes" select="*[name() != 'url']"/>
+
+        <!-- Unpack -->
+        <mkdir dir="{$dir}"/>
+        <xsl:choose>
+
+            <!-- ZIP type files -->
+            <xsl:when test="$type = 'zip' or $type = 'war' or $type = 'jar'">
+                <unzip src="{$file}" dest="{$dir}">
+                    <xsl:if test="$includes">
+                        <patternset>
+                            <xsl:copy-of select="$includes"/>
+                        </patternset>
+                    </xsl:if>
+                </unzip>
+            </xsl:when>
+
+            <!-- TAR files, optionally compressed -->
+            <xsl:when test="starts-with($type, 'tar')">
+                <untar src="{$file}" dest="{$dir}">
+                    <xsl:choose>
+                        <xsl:when test="$type = 'tar.gz'">
+                            <xsl:attribute name="compression">gzip</xsl:attribute>
+                        </xsl:when>
+                        <xsl:when test="$type = 'tar.bz2'">
+                            <xsl:attribute name="compression">bzip2</xsl:attribute>
+                        </xsl:when>
+                    </xsl:choose>
+                    <xsl:if test="$includes">
+                        <patternset>
+                            <xsl:copy-of select="$includes"/>
+                        </patternset>
+                    </xsl:if>
+                </untar>
+            </xsl:when>
+            <xsl:otherwise>
+                <xsl:message terminate="yes">
+                    <xsl:value-of select="concat('ERROR: unknown archive type "', $type, '"
')"/>
+                    <xsl:value-of select="'Please set the "type" attribute to one of: zip, tar, tar.gz, or tar.bz2.
'"/>
+                </xsl:message>
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+    <!-- Determine type of archive automatically based on filename -->
+    <xsl:template name="archiveType">
+        <xsl:param name="file"/>
+        <xsl:choose>
+            <xsl:when test="substring($file, string-length($file) - 3) = '.jar'">jar</xsl:when>
+            <xsl:when test="substring($file, string-length($file) - 3) = '.war'">war</xsl:when>
+            <xsl:when test="substring($file, string-length($file) - 3) = '.zip'">zip</xsl:when>
+            <xsl:when test="substring($file, string-length($file) - 3) = '.tar'">tar</xsl:when>
+            <xsl:when test="substring($file, string-length($file) - 3) = '.tgz'">tar.gz</xsl:when>
+            <xsl:when test="substring($file, string-length($file) - 6) = '.tar.gz'">tar.gz</xsl:when>
+            <xsl:when test="substring($file, string-length($file) - 7) = '.tar.bz2'">tar.bz2</xsl:when>
+            <xsl:otherwise>
+                <xsl:message terminate="yes">
+                    <xsl:value-of select="concat('ERROR: cannot determine type of archive: ', $file, '
')"/>
+                    <xsl:value-of select="'Please set the "type" attribute to one of: zip, tar, tar.gz, or tar.bz2.
'"/>
+                </xsl:message>
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+    <!-- Concatenate nodes separated by spaces -->
+    <xsl:template name="concat">
+        <xsl:param name="nodes"/>
+        <xsl:choose>
+            <xsl:when test="count($nodes) <= 1">
+                <xsl:value-of select="string($nodes)"/>
+            </xsl:when>
+            <xsl:otherwise>
+                <xsl:value-of select="concat(string($nodes[1]), ' ')"/>
+                <xsl:call-template name="concat">
+                    <xsl:with-param name="nodes" select="$nodes[position() > 1]"/>
+                </xsl:call-template>
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+    <!-- Ignore anything unexpected -->
+    <xsl:template match="*">
+        <xsl:message terminate="no">ignoring unexpected XML node <<xsl:value-of select="name()"/>></xsl:message>
+    </xsl:template>
+    <xsl:template match="@*|node()"/>
+
+</xsl:transform>
diff --git a/src/java/org/apache/ivy/plugins/resolver/util/ApacheHttpURLLister.java b/src/java/org/apache/ivy/plugins/resolver/util/ApacheHttpURLLister.java
new file mode 100644
index 0000000..90d68b0
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/resolver/util/ApacheHttpURLLister.java
@@ -0,0 +1,40 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.resolver.util;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.List;
+
+import org.apache.ivy.util.url.ApacheURLLister;
+
+public class ApacheHttpURLLister implements URLLister {
+    private ApacheURLLister lister = new ApacheURLLister();
+
+    public boolean accept(String pattern) {
+        return pattern.startsWith("http");
+    }
+
+    public List listAll(URL url) throws IOException {
+        return lister.listAll(url);
+    }
+
+    public String toString() {
+        return "apache http lister";
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/resolver/util/FileURLLister.java b/src/java/org/apache/ivy/plugins/resolver/util/FileURLLister.java
new file mode 100644
index 0000000..26cd04d
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/resolver/util/FileURLLister.java
@@ -0,0 +1,69 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.resolver.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class FileURLLister implements URLLister {
+    private File basedir;
+
+    public FileURLLister() {
+        this(null);
+    }
+
+    public FileURLLister(File baseDir) {
+        this.basedir = baseDir;
+    }
+
+    public boolean accept(String pattern) {
+        return pattern.startsWith("file");
+    }
+
+    public List listAll(URL url) throws IOException {
+        String path;
+        try {
+            path = new File(new URI(url.toExternalForm())).getPath();
+        } catch (URISyntaxException e) {
+            // unexpected try to get the best of it
+            path = url.getPath();
+        }
+        File file = basedir == null ? new File(path) : new File(basedir, path);
+        if (file.exists() && file.isDirectory()) {
+            String[] files = file.list();
+            List ret = new ArrayList(files.length);
+            URL context = url.getPath().endsWith("/") ? url : new URL(url.toExternalForm() + "/");
+            for (int i = 0; i < files.length; i++) {
+                ret.add(new URL(context, files[i]));
+            }
+            return ret;
+        } else {
+            return Collections.EMPTY_LIST;
+        }
+    }
+
+    public String toString() {
+        return "file lister";
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/resolver/util/HasLatestStrategy.java b/src/java/org/apache/ivy/plugins/resolver/util/HasLatestStrategy.java
new file mode 100644
index 0000000..ea4b27d
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/resolver/util/HasLatestStrategy.java
@@ -0,0 +1,28 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.resolver.util;
+
+import org.apache.ivy.plugins.latest.LatestStrategy;
+
+public interface HasLatestStrategy {
+    public LatestStrategy getLatestStrategy();
+
+    public void setLatestStrategy(LatestStrategy latestStrategy);
+
+    public String getLatest();
+}
diff --git a/src/java/org/apache/ivy/plugins/resolver/util/MDResolvedResource.java b/src/java/org/apache/ivy/plugins/resolver/util/MDResolvedResource.java
new file mode 100644
index 0000000..d14fe10
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/resolver/util/MDResolvedResource.java
@@ -0,0 +1,35 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.resolver.util;
+
+import org.apache.ivy.core.resolve.ResolvedModuleRevision;
+import org.apache.ivy.plugins.repository.Resource;
+
+public class MDResolvedResource extends ResolvedResource {
+    private ResolvedModuleRevision rmr;
+
+    public MDResolvedResource(Resource res, String rev, ResolvedModuleRevision rmr) {
+        super(res, rev);
+        this.rmr = rmr;
+    }
+
+    public ResolvedModuleRevision getResolvedModuleRevision() {
+        return rmr;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/plugins/resolver/util/ResolvedResource.java b/src/java/org/apache/ivy/plugins/resolver/util/ResolvedResource.java
new file mode 100644
index 0000000..e655112
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/resolver/util/ResolvedResource.java
@@ -0,0 +1,48 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.resolver.util;
+
+import org.apache.ivy.plugins.latest.ArtifactInfo;
+import org.apache.ivy.plugins.repository.Resource;
+
+public class ResolvedResource implements ArtifactInfo {
+    private Resource res;
+
+    private String rev;
+
+    public ResolvedResource(Resource res, String rev) {
+        this.res = res;
+        this.rev = rev;
+    }
+
+    public String getRevision() {
+        return rev;
+    }
+
+    public Resource getResource() {
+        return res;
+    }
+
+    public String toString() {
+        return res + " (" + rev + ")";
+    }
+
+    public long getLastModified() {
+        return getResource().getLastModified();
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/resolver/util/ResolverHelper.java b/src/java/org/apache/ivy/plugins/resolver/util/ResolverHelper.java
new file mode 100644
index 0000000..91411a9
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/resolver/util/ResolverHelper.java
@@ -0,0 +1,332 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.resolver.util;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.plugins.repository.Repository;
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.util.Message;
+
+public final class ResolverHelper {
+    private ResolverHelper() {
+    }
+
+    // lists all the values a token can take in a pattern, as listed by a given url lister
+    public static String[] listTokenValues(Repository rep, String pattern, String token) {
+        String fileSep = rep.getFileSeparator();
+        pattern = rep.standardize(pattern);
+        String tokenString = IvyPatternHelper.getTokenString(token);
+        int index = pattern.indexOf(tokenString);
+        if (index == -1) {
+            Message.verbose("unable to list " + token + " in " + pattern
+                    + ": token not found in pattern");
+            return null;
+        }
+        if (((pattern.length() <= index + tokenString.length()) || fileSep.equals(pattern
+                .substring(index + tokenString.length(), index + tokenString.length() + 1)))
+                && (index == 0 || fileSep.equals(pattern.substring(index - 1, index)))) {
+            // the searched token is a whole name
+            String root = pattern.substring(0, index);
+            return listAll(rep, root);
+        } else {
+            int slashIndex = pattern.substring(0, index).lastIndexOf(fileSep);
+            String root = slashIndex == -1 ? "" : pattern.substring(0, slashIndex);
+
+            try {
+                Message.debug("\tusing " + rep + " to list all in " + root);
+                String[] all = listAll(rep, root);
+                if (all != null) {
+                    Message.debug("\t\tfound " + all.length + " urls");
+                    List<String> ret = new ArrayList<String>(all.length);
+                    int endNameIndex = pattern.indexOf(fileSep, slashIndex + 1);
+                    String namePattern;
+                    if (endNameIndex != -1) {
+                        namePattern = pattern.substring(slashIndex + 1, endNameIndex);
+                    } else {
+                        namePattern = pattern.substring(slashIndex + 1);
+                    }
+                    namePattern = namePattern.replaceAll("\\.", "\\\\.");
+                    namePattern = IvyPatternHelper.substituteToken(namePattern, token, "(.+)");
+                    Pattern p = Pattern.compile(namePattern);
+                    for (String path : all) {
+                        Matcher m = p.matcher(path);
+                        if (m.matches()) {
+                            String value = m.group(1);
+                            ret.add(value);
+                        }
+                    }
+                    Message.debug("\t\t" + ret.size() + " matched " + pattern);
+                    return (String[]) ret.toArray(new String[ret.size()]);
+                } else {
+                    return null;
+                }
+            } catch (Exception e) {
+                Message.warn("problem while listing resources in " + root + " with " + rep, e);
+                return null;
+            }
+        }
+    }
+
+    public static String[] listAll(Repository rep, String parent) {
+        try {
+            String fileSep = rep.getFileSeparator();
+            Message.debug("\tusing " + rep + " to list all in " + parent);
+            List all = rep.list(parent);
+            if (all != null) {
+                Message.debug("\t\tfound " + all.size() + " resources");
+                List names = new ArrayList(all.size());
+                for (Iterator iter = all.iterator(); iter.hasNext();) {
+                    String path = (String) iter.next();
+                    if (path.endsWith(fileSep)) {
+                        path = path.substring(0, path.length() - 1);
+                    }
+                    int slashIndex = path.lastIndexOf(fileSep);
+                    names.add(path.substring(slashIndex + 1));
+                }
+                return (String[]) names.toArray(new String[names.size()]);
+            } else {
+                Message.debug("\t\tno resources found");
+                return null;
+            }
+        } catch (IOException e) {
+            Message.verbose("problem while listing resources in " + parent + " with " + rep, e);
+            return null;
+        } catch (Exception e) {
+            Message.warn("problem while listing resources in " + parent + " with " + rep, e);
+            return null;
+        }
+    }
+
+    public static ResolvedResource[] findAll(Repository rep, ModuleRevisionId mrid, String pattern,
+            Artifact artifact) {
+        // substitute all but revision
+        String partiallyResolvedPattern = IvyPatternHelper.substitute(
+            pattern,
+            ModuleRevisionId.newInstance(mrid,
+                IvyPatternHelper.getTokenString(IvyPatternHelper.REVISION_KEY)), artifact);
+        Message.debug("\tlisting all in " + partiallyResolvedPattern);
+
+        String[] revs = listTokenValues(rep, partiallyResolvedPattern,
+            IvyPatternHelper.REVISION_KEY);
+        if (revs != null) {
+            Message.debug("\tfound revs: " + Arrays.asList(revs));
+            List ret = new ArrayList(revs.length);
+            for (int i = 0; i < revs.length; i++) {
+                String rres = IvyPatternHelper.substituteToken(partiallyResolvedPattern,
+                    IvyPatternHelper.REVISION_KEY, revs[i]);
+                try {
+                    Resource res = rep.getResource(rres);
+                    if (res != null) {
+                        // we do not test if the resource actually exist here, it would cause
+                        // a lot of checks which are not always necessary depending on the usage
+                        // which is done of the returned ResolvedResource array
+                        ret.add(new ResolvedResource(res, revs[i]));
+                    }
+                } catch (IOException e) {
+                    Message.warn("impossible to get resource from name listed by repository: "
+                            + rres, e);
+                }
+            }
+            if (revs.length != ret.size()) {
+                Message.debug("\tfound resolved res: " + ret);
+            }
+            return (ResolvedResource[]) ret.toArray(new ResolvedResource[ret.size()]);
+        } else if (partiallyResolvedPattern.indexOf("[" + IvyPatternHelper.REVISION_KEY + "]") == -1) {
+            // the partially resolved pattern is completely resolved, check the resource
+            try {
+                Resource res = rep.getResource(partiallyResolvedPattern);
+                if (res.exists()) {
+                    Message.debug("\tonly one resource found without real listing: "
+                            + "using and defining it as working@" + rep.getName() + " revision: "
+                            + res.getName());
+                    return new ResolvedResource[] {new ResolvedResource(res, "working@"
+                            + rep.getName())};
+                }
+            } catch (IOException e) {
+                Message.debug("\timpossible to get resource from name listed by repository: "
+                        + partiallyResolvedPattern, e);
+            }
+            Message.debug("\tno revision found");
+        }
+        return null;
+    }
+
+    // public static ResolvedResource[] findAll(Repository rep, ModuleRevisionId mrid, String
+    // pattern, Artifact artifact, VersionMatcher versionMatcher, ResourceMDParser mdParser) {
+    // // substitute all but revision
+    // String partiallyResolvedPattern = IvyPatternHelper.substitute(pattern, new
+    // ModuleRevisionId(mrid.getModuleId(),
+    // IvyPatternHelper.getTokenString(IvyPatternHelper.REVISION_KEY), mrid.getExtraAttributes()),
+    // artifact);
+    // Message.debug("\tlisting all in "+partiallyResolvedPattern);
+    //
+    // String[] revs = listTokenValues(rep, partiallyResolvedPattern,
+    // IvyPatternHelper.REVISION_KEY);
+    // if (revs != null) {
+    // Message.debug("\tfound revs: "+Arrays.asList(revs));
+    // List ret = new ArrayList(revs.length);
+    // String rres = null;
+    // for (int i = 0; i < revs.length; i++) {
+    // ModuleRevisionId foundMrid = new ModuleRevisionId(mrid.getModuleId(), revs[i],
+    // mrid.getExtraAttributes());
+    // if (versionMatcher.accept(mrid, foundMrid)) {
+    // rres = IvyPatternHelper.substituteToken(partiallyResolvedPattern,
+    // IvyPatternHelper.REVISION_KEY, revs[i]);
+    // try {
+    // ResolvedResource resolvedResource;
+    // if (versionMatcher.needModuleDescriptor(mrid, foundMrid)) {
+    // resolvedResource = mdParser.parse(rep.getResource(rres), revs[i]);
+    // if (!versionMatcher.accept(mrid,
+    // ((MDResolvedResource)resolvedResource).getResolvedModuleRevision().getDescriptor())) {
+    // continue;
+    // }
+    // } else {
+    // resolvedResource = new ResolvedResource(rep.getResource(rres), revs[i]);
+    // }
+    // ret.add(resolvedResource);
+    // } catch (IOException e) {
+    // Message.warn("impossible to get resource from name listed by repository: "+rres+":
+    // "+e.getMessage());
+    // }
+    // }
+    // }
+    // if (revs.length != ret.size()) {
+    // Message.debug("\tfound resolved res: "+ret);
+    // }
+    // return (ResolvedResource[])ret.toArray(new ResolvedResource[ret.size()]);
+    // } else {
+    // // maybe the partially resolved pattern is completely resolved ?
+    // try {
+    // Resource res = rep.getResource(partiallyResolvedPattern);
+    // if (res.exists()) {
+    // Message.debug("\tonly one resource found without real listing: using and defining it as
+    // working@"+rep.getName()+" revision: "+res.getName());
+    // return new ResolvedResource[] {new ResolvedResource(res, "working@"+rep.getName())};
+    // }
+    // } catch (IOException e) {
+    // Message.debug("\timpossible to get resource from name listed by repository:
+    // "+partiallyResolvedPattern+": "+e.getMessage());
+    // }
+    // Message.debug("\tno revision found");
+    // }
+    // return null;
+    // }
+
+    // lists all the values a token can take in a pattern, as listed by a given url lister
+    public static String[] listTokenValues(URLLister lister, String pattern, String token) {
+        pattern = standardize(pattern);
+        if (lister.accept(pattern)) {
+            String tokenString = IvyPatternHelper.getTokenString(token);
+            int index = pattern.indexOf(tokenString);
+            if (index == -1) {
+                Message.verbose("unable to list " + token + " in " + pattern
+                        + ": token not found in pattern");
+                return null;
+            }
+            if (((pattern.length() <= index + tokenString.length()) || "/".equals(pattern
+                    .substring(index + tokenString.length(), index + tokenString.length() + 1)))
+                    && (index == 0 || "/".equals(pattern.substring(index - 1, index)))) {
+                // the searched token is a whole name
+                String root = pattern.substring(0, index);
+                try {
+                    return listAll(lister, new URL(root));
+                } catch (MalformedURLException e) {
+                    Message.warn("malformed url from pattern root: " + root + ": " + e.getMessage());
+                    return null;
+                }
+            } else {
+                int slashIndex = pattern.substring(0, index).lastIndexOf('/');
+                String root = slashIndex == -1 ? "" : pattern.substring(0, slashIndex);
+
+                try {
+                    Message.debug("\tusing " + lister + " to list all in " + root);
+                    List all = lister.listAll(new URL(root));
+                    Message.debug("\t\tfound " + all.size() + " urls");
+                    List ret = new ArrayList(all.size());
+                    int endNameIndex = pattern.indexOf('/', slashIndex + 1);
+                    String namePattern;
+                    if (endNameIndex != -1) {
+                        namePattern = pattern.substring(slashIndex + 1, endNameIndex);
+                    } else {
+                        namePattern = pattern.substring(slashIndex + 1);
+                    }
+                    String acceptNamePattern = ".*"
+                            + IvyPatternHelper.substituteToken(namePattern, token, "([^/]+)")
+                            + ".*";
+                    Pattern p = Pattern.compile(acceptNamePattern.toString());
+                    for (Iterator iter = all.iterator(); iter.hasNext();) {
+                        URL url = (URL) iter.next();
+                        String path = standardize(url.getPath());
+                        Matcher m = p.matcher(path);
+                        if (m.matches()) {
+                            String value = m.group(1);
+                            ret.add(value);
+                        }
+                    }
+                    Message.debug("\t\t" + ret.size() + " matched " + pattern);
+                    return (String[]) ret.toArray(new String[ret.size()]);
+                } catch (Exception e) {
+                    Message.warn("problem while listing files in " + root, e);
+                    return null;
+                }
+            }
+        }
+        return null;
+    }
+
+    private static String standardize(String path) {
+        return path.replace('\\', '/');
+    }
+
+    public static String[] listAll(URLLister lister, URL root) {
+        try {
+            if (lister.accept(root.toExternalForm())) {
+                Message.debug("\tusing " + lister + " to list all in " + root);
+                List all = lister.listAll(root);
+                Message.debug("\t\tfound " + all.size() + " urls");
+                List names = new ArrayList(all.size());
+                for (Iterator iter = all.iterator(); iter.hasNext();) {
+                    URL dir = (URL) iter.next();
+                    String path = dir.getPath();
+                    if (path.endsWith("/")) {
+                        path = path.substring(0, path.length() - 1);
+                    }
+                    int slashIndex = path.lastIndexOf('/');
+                    names.add(path.substring(slashIndex + 1));
+                }
+                return (String[]) names.toArray(new String[names.size()]);
+            }
+            return null;
+        } catch (Exception e) {
+            Message.warn("problem while listing directories in " + root, e);
+            return null;
+        }
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/resolver/util/ResourceMDParser.java b/src/java/org/apache/ivy/plugins/resolver/util/ResourceMDParser.java
new file mode 100644
index 0000000..211f8c7
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/resolver/util/ResourceMDParser.java
@@ -0,0 +1,34 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.resolver.util;
+
+import org.apache.ivy.plugins.repository.Resource;
+
+public interface ResourceMDParser {
+    /**
+     * Parses the module descriptor designed by the given resource.
+     * 
+     * @param resource
+     *            the resource at which the module descriptor is located
+     * @param rev
+     *            the revision at which the module descriptor should be
+     * @return the parsed module descriptor as a {@link MDResolvedResource}, or <code>null</code> if
+     *         parsing has failed or if the resource is not available.
+     */
+    MDResolvedResource parse(Resource resource, String rev);
+}
diff --git a/src/java/org/apache/ivy/plugins/resolver/util/URLLister.java b/src/java/org/apache/ivy/plugins/resolver/util/URLLister.java
new file mode 100644
index 0000000..6e7258a
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/resolver/util/URLLister.java
@@ -0,0 +1,35 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.resolver.util;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.List;
+
+public interface URLLister {
+    /**
+     * Indicates if this lister is able to list urls with the given pattern. In general, only
+     * protocol is used.
+     * 
+     * @param pattern
+     * @return
+     */
+    boolean accept(String pattern);
+
+    List listAll(URL url) throws IOException;
+}
diff --git a/src/java/org/apache/ivy/plugins/signer/SignatureGenerator.java b/src/java/org/apache/ivy/plugins/signer/SignatureGenerator.java
new file mode 100644
index 0000000..bf81e45
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/signer/SignatureGenerator.java
@@ -0,0 +1,31 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.signer;
+
+import java.io.File;
+import java.io.IOException;
+
+public interface SignatureGenerator {
+
+    String getName();
+
+    void sign(File src, File dest) throws IOException;
+
+    String getExtension();
+
+}
diff --git a/src/java/org/apache/ivy/plugins/signer/bouncycastle/OpenPGPSignatureGenerator.java b/src/java/org/apache/ivy/plugins/signer/bouncycastle/OpenPGPSignatureGenerator.java
new file mode 100644
index 0000000..af7beae
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/signer/bouncycastle/OpenPGPSignatureGenerator.java
@@ -0,0 +1,185 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.signer.bouncycastle;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Security;
+import java.security.SignatureException;
+import java.util.Iterator;
+
+import org.apache.ivy.plugins.signer.SignatureGenerator;
+import org.bouncycastle.bcpg.ArmoredOutputStream;
+import org.bouncycastle.bcpg.BCPGOutputStream;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPPrivateKey;
+import org.bouncycastle.openpgp.PGPSecretKey;
+import org.bouncycastle.openpgp.PGPSecretKeyRing;
+import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
+import org.bouncycastle.openpgp.PGPSignature;
+import org.bouncycastle.openpgp.PGPSignatureGenerator;
+import org.bouncycastle.openpgp.PGPUtil;
+
+public class OpenPGPSignatureGenerator implements SignatureGenerator {
+
+    private static final long MASK = 0xFFFFFFFFL;
+
+    static {
+        Security.addProvider(new BouncyCastleProvider());
+    }
+
+    private String name;
+
+    private String secring;
+
+    private String password;
+
+    private String keyId;
+
+    private PGPSecretKey pgpSec;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getExtension() {
+        return "asc";
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    public void setSecring(String secring) {
+        this.secring = secring;
+    }
+
+    public void setKeyId(String keyId) {
+        if (!"auto".equals(keyId)) {
+            this.keyId = keyId;
+        }
+    }
+
+    public void sign(File src, File dest) throws IOException {
+        OutputStream out = null;
+        InputStream in = null;
+        InputStream keyIn = null;
+
+        try {
+            if (secring == null) {
+                secring = System.getProperty("user.home") + "/.gnupg/secring.gpg";
+            }
+
+            if (pgpSec == null) {
+                keyIn = new FileInputStream(secring);
+                pgpSec = readSecretKey(keyIn);
+            }
+
+            PGPPrivateKey pgpPrivKey = pgpSec.extractPrivateKey(password.toCharArray(),
+                BouncyCastleProvider.PROVIDER_NAME);
+            PGPSignatureGenerator sGen = new PGPSignatureGenerator(pgpSec.getPublicKey()
+                    .getAlgorithm(), PGPUtil.SHA1, BouncyCastleProvider.PROVIDER_NAME);
+            sGen.initSign(PGPSignature.BINARY_DOCUMENT, pgpPrivKey);
+
+            in = new FileInputStream(src);
+            out = new BCPGOutputStream(new ArmoredOutputStream(new FileOutputStream(dest)));
+
+            int ch = 0;
+            while ((ch = in.read()) >= 0) {
+                sGen.update((byte) ch);
+            }
+
+            sGen.generate().encode(out);
+        } catch (SignatureException e) {
+            IOException ioexc = new IOException();
+            ioexc.initCause(e);
+            throw ioexc;
+        } catch (PGPException e) {
+            IOException ioexc = new IOException();
+            ioexc.initCause(e);
+            throw ioexc;
+        } catch (NoSuchAlgorithmException e) {
+            IOException ioexc = new IOException();
+            ioexc.initCause(e);
+            throw ioexc;
+        } catch (NoSuchProviderException e) {
+            IOException ioexc = new IOException();
+            ioexc.initCause(e);
+            throw ioexc;
+        } finally {
+            if (out != null) {
+                try {
+                    out.close();
+                } catch (IOException e) {
+                }
+            }
+            if (in != null) {
+                try {
+                    in.close();
+                } catch (IOException e) {
+                }
+            }
+            if (keyIn != null) {
+                try {
+                    keyIn.close();
+                } catch (IOException e) {
+                }
+            }
+        }
+    }
+
+    private PGPSecretKey readSecretKey(InputStream in) throws IOException, PGPException {
+        in = PGPUtil.getDecoderStream(in);
+        PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(in);
+
+        PGPSecretKey key = null;
+        for (Iterator it = pgpSec.getKeyRings(); key == null && it.hasNext();) {
+            PGPSecretKeyRing kRing = (PGPSecretKeyRing) it.next();
+
+            for (Iterator it2 = kRing.getSecretKeys(); key == null && it2.hasNext();) {
+                PGPSecretKey k = (PGPSecretKey) it2.next();
+                if ((keyId == null) && k.isSigningKey()) {
+                    key = k;
+                }
+                if ((keyId != null)
+                        && (Long.valueOf(keyId, 16).longValue() == (k.getKeyID() & MASK))) {
+                    key = k;
+                }
+            }
+        }
+
+        if (key == null) {
+            throw new IllegalArgumentException("Can't find encryption key"
+                    + (keyId != null ? " '" + keyId + "' " : " ") + "in key ring.");
+        }
+
+        return key;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/plugins/trigger/AbstractTrigger.java b/src/java/org/apache/ivy/plugins/trigger/AbstractTrigger.java
new file mode 100644
index 0000000..c4e02a6
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/trigger/AbstractTrigger.java
@@ -0,0 +1,83 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.trigger;
+
+import org.apache.ivy.core.IvyContext;
+import org.apache.ivy.core.event.IvyEvent;
+import org.apache.ivy.core.event.IvyEventFilter;
+import org.apache.ivy.core.event.IvyListener;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+import org.apache.ivy.util.filter.Filter;
+
+/**
+ * Base class for easy trigger implementation. This base class takes of the event filtering part,
+ * the only method to implement in subclasses is {@link IvyListener#progress(IvyEvent)} which should
+ * do whatever the trigger needs to do when the event occurs. This method will only be called when
+ * an event matching the trigger filter occurs.
+ * 
+ * @since 1.4
+ */
+public abstract class AbstractTrigger implements Trigger {
+    private Filter filter;
+
+    private String event;
+
+    private String expression;
+
+    private String matcher = PatternMatcher.EXACT;
+
+    public Filter getEventFilter() {
+        if (filter == null) {
+            filter = createFilter();
+        }
+        return filter;
+    }
+
+    private Filter createFilter() {
+        return new IvyEventFilter(getEvent(), getFilter(), getPatternMatcher());
+    }
+
+    private PatternMatcher getPatternMatcher() {
+        return IvyContext.getContext().getSettings().getMatcher(matcher);
+    }
+
+    public String getEvent() {
+        return event;
+    }
+
+    public void setEvent(String event) {
+        this.event = event;
+    }
+
+    public String getFilter() {
+        return expression;
+    }
+
+    public void setFilter(String filterExpression) {
+        expression = filterExpression;
+    }
+
+    public String getMatcher() {
+        return matcher;
+    }
+
+    public void setMatcher(String matcher) {
+        this.matcher = matcher;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/plugins/trigger/LogTrigger.java b/src/java/org/apache/ivy/plugins/trigger/LogTrigger.java
new file mode 100644
index 0000000..08d4d1a
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/trigger/LogTrigger.java
@@ -0,0 +1,133 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.trigger;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.core.event.IvyEvent;
+import org.apache.ivy.core.resolve.ResolveProcessException;
+import org.apache.ivy.util.Message;
+
+/**
+ * A trigger performing logging.
+ * <p>
+ * The implementation is widely inspired by Ant Echo task.
+ * </p>
+ */
+public class LogTrigger extends AbstractTrigger {
+    private static final String LINE_SEPARATOR = System.getProperty("line.separator");
+
+    private String message = "";
+
+    private File file = null;
+
+    private boolean append = true;
+
+    /** encoding; set to null or empty means 'default' */
+    private String encoding = "";
+
+    public void progress(IvyEvent event) {
+        log(IvyPatternHelper.substituteVariables(message, event.getAttributes()));
+    }
+
+    /**
+     * Logs the given message.
+     * 
+     * @param message
+     *            the message to log
+     */
+    protected void log(String message) {
+        if (file == null) {
+            Message.info(message);
+        } else {
+            Writer out = null;
+            try {
+                // we add a line separator here for consistency with Message.info which creates a
+                // new line each time.
+                // we use the system dependent line separator to ease reading the log file
+                message += LINE_SEPARATOR;
+                String filename = file.getAbsolutePath();
+                if (encoding == null || encoding.length() == 0) {
+                    out = new FileWriter(filename, append);
+                } else {
+                    out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filename,
+                            append), encoding));
+                }
+                out.write(message, 0, message.length());
+            } catch (IOException e) {
+                throw new ResolveProcessException(e);
+            } finally {
+                if (out != null) {
+                    try {
+                        out.close();
+                    } catch (IOException e) {
+                        throw new ResolveProcessException(e);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Message to write.
+     * 
+     * @param msg
+     *            Sets the value for the message variable.
+     */
+    public void setMessage(String msg) {
+        this.message = msg;
+    }
+
+    /**
+     * File to write to.
+     * 
+     * @param file
+     *            the file to write to, if not set, echo to standard Ivy logging
+     */
+    public void setFile(File file) {
+        this.file = file;
+    }
+
+    /**
+     * If true, append to existing file.
+     * 
+     * @param append
+     *            if true, append to existing file, default is false.
+     */
+    public void setAppend(boolean append) {
+        this.append = append;
+    }
+
+    /**
+     * Declare the encoding to use when outputting to a file; Use "" for the platform's default
+     * encoding.
+     * 
+     * @param encoding
+     *            the character encoding to use.
+     */
+    public void setEncoding(String encoding) {
+        this.encoding = encoding;
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/trigger/Trigger.java b/src/java/org/apache/ivy/plugins/trigger/Trigger.java
new file mode 100644
index 0000000..b8987e9
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/trigger/Trigger.java
@@ -0,0 +1,25 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.trigger;
+
+import org.apache.ivy.core.event.IvyListener;
+import org.apache.ivy.util.filter.Filter;
+
+public interface Trigger extends IvyListener {
+    Filter getEventFilter();
+}
diff --git a/src/java/org/apache/ivy/plugins/version/AbstractVersionMatcher.java b/src/java/org/apache/ivy/plugins/version/AbstractVersionMatcher.java
new file mode 100644
index 0000000..e3100cb
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/version/AbstractVersionMatcher.java
@@ -0,0 +1,78 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.version;
+
+import java.util.Comparator;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.ivy.plugins.IvySettingsAware;
+import org.apache.ivy.util.Checks;
+
+public abstract class AbstractVersionMatcher implements VersionMatcher, IvySettingsAware {
+    private String name;
+
+    private IvySettings settings;
+
+    public AbstractVersionMatcher() {
+    }
+
+    public AbstractVersionMatcher(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public boolean needModuleDescriptor(ModuleRevisionId askedMrid, ModuleRevisionId foundMrid) {
+        return false;
+    }
+
+    public boolean accept(ModuleRevisionId askedMrid, ModuleDescriptor foundMD) {
+        return accept(askedMrid, foundMD.getResolvedModuleRevisionId());
+    }
+
+    /**
+     * This method should be overriden in most cases, because it uses the default contract to return
+     * 1 when it's not possible to know which revision is greater.
+     */
+    public int compare(ModuleRevisionId askedMrid, ModuleRevisionId foundMrid,
+            Comparator staticComparator) {
+        return 0;
+    }
+
+    public String toString() {
+        return getName();
+    }
+
+    public IvySettings getSettings() {
+        return settings;
+    }
+
+    public void setSettings(IvySettings settings) {
+        Checks.checkNotNull(settings, "settings");
+        this.settings = settings;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/plugins/version/ChainVersionMatcher.java b/src/java/org/apache/ivy/plugins/version/ChainVersionMatcher.java
new file mode 100644
index 0000000..fae5ca7
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/version/ChainVersionMatcher.java
@@ -0,0 +1,155 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.version;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.ivy.plugins.IvySettingsAware;
+import org.apache.ivy.util.Checks;
+
+/**
+ * An implementation of {@link VersionMatcher} chaining several version matchers, and implemeting
+ * the {@link VersionMatcher} interface by returning results from the first matcher in the chain
+ * accepting the version.
+ */
+public class ChainVersionMatcher extends AbstractVersionMatcher {
+    /**
+     * The list of version matchers in the chain. This list will be queried in order, so the last
+     * matcher will be used only if no other matcher accept the revision before.
+     */
+    private List/* <VersionMatcher> */matchers = new LinkedList();
+
+    /**
+     * Unique Constructor.
+     */
+    public ChainVersionMatcher() {
+        super("chain");
+    }
+
+    /**
+     * Adds a {@link VersionMatcher} to the chain.
+     * 
+     * @param matcher
+     *            the version matcher to add. Must not be null
+     */
+    public void add(VersionMatcher matcher) {
+        Checks.checkNotNull(matcher, "matcher");
+        matchers.add(0, matcher);
+        if (getSettings() != null && matcher instanceof IvySettingsAware) {
+            ((IvySettingsAware) matcher).setSettings(getSettings());
+        }
+    }
+
+    /**
+     * Sets the settings this matcher will use, and set to the matcher in the chain which implements
+     * {@link IvySettingsAware}.
+     * 
+     * @param settings
+     *            the settings to use in the whole chain. Must not be null.
+     */
+    public void setSettings(IvySettings settings) {
+        super.setSettings(settings);
+        for (Iterator iter = matchers.iterator(); iter.hasNext();) {
+            VersionMatcher matcher = (VersionMatcher) iter.next();
+            if (matcher instanceof IvySettingsAware) {
+                ((IvySettingsAware) matcher).setSettings(settings);
+            }
+        }
+    }
+
+    /**
+     * Returns the list of matchers in the chain.
+     * <p>
+     * The list is returned as an unmodifiable view on the actual list of matchers, and will thus
+     * reflect futher changes made in the chain.
+     * 
+     * @return the list of matchers in the chain. Is never null.
+     */
+    public List getMatchers() {
+        return Collections.unmodifiableList(matchers);
+    }
+
+    public boolean isDynamic(ModuleRevisionId askedMrid) {
+        Checks.checkNotNull(askedMrid, "askedMrid");
+        for (Iterator iter = matchers.iterator(); iter.hasNext();) {
+            VersionMatcher matcher = (VersionMatcher) iter.next();
+            if (matcher.isDynamic(askedMrid)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public int compare(ModuleRevisionId askedMrid, ModuleRevisionId foundMrid,
+            Comparator staticComparator) {
+        Checks.checkNotNull(askedMrid, "askedMrid");
+        Checks.checkNotNull(foundMrid, "foundMrid");
+        Checks.checkNotNull(staticComparator, "staticComparator");
+        for (Iterator iter = matchers.iterator(); iter.hasNext();) {
+            VersionMatcher matcher = (VersionMatcher) iter.next();
+            if (matcher.isDynamic(askedMrid)) {
+                return matcher.compare(askedMrid, foundMrid, staticComparator);
+            }
+        }
+        throw new IllegalArgumentException(
+                "impossible to compare revisions: askedMrid is not dynamic: " + askedMrid);
+    }
+
+    public boolean accept(ModuleRevisionId askedMrid, ModuleRevisionId foundMrid) {
+        Checks.checkNotNull(askedMrid, "askedMrid");
+        Checks.checkNotNull(foundMrid, "foundMrid");
+        for (Iterator iter = matchers.iterator(); iter.hasNext();) {
+            VersionMatcher matcher = (VersionMatcher) iter.next();
+            if (!iter.hasNext() || matcher.isDynamic(askedMrid)) {
+                return matcher.accept(askedMrid, foundMrid);
+            }
+        }
+        return false;
+    }
+
+    public boolean needModuleDescriptor(ModuleRevisionId askedMrid, ModuleRevisionId foundMrid) {
+        Checks.checkNotNull(askedMrid, "askedMrid");
+        Checks.checkNotNull(foundMrid, "foundMrid");
+        for (Iterator iter = matchers.iterator(); iter.hasNext();) {
+            VersionMatcher matcher = (VersionMatcher) iter.next();
+            if (!iter.hasNext() || matcher.isDynamic(askedMrid)) {
+                return matcher.needModuleDescriptor(askedMrid, foundMrid);
+            }
+        }
+        return false;
+    }
+
+    public boolean accept(ModuleRevisionId askedMrid, ModuleDescriptor foundMD) {
+        Checks.checkNotNull(askedMrid, "askedMrid");
+        Checks.checkNotNull(foundMD, "foundMD");
+        for (Iterator iter = matchers.iterator(); iter.hasNext();) {
+            VersionMatcher matcher = (VersionMatcher) iter.next();
+            if (!iter.hasNext() || matcher.isDynamic(askedMrid)) {
+                return matcher.accept(askedMrid, foundMD);
+            }
+        }
+        return false;
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/version/ExactVersionMatcher.java b/src/java/org/apache/ivy/plugins/version/ExactVersionMatcher.java
new file mode 100644
index 0000000..6b861e3
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/version/ExactVersionMatcher.java
@@ -0,0 +1,35 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.version;
+
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+
+public class ExactVersionMatcher extends AbstractVersionMatcher {
+
+    public ExactVersionMatcher() {
+        super("exact");
+    }
+
+    public boolean isDynamic(ModuleRevisionId askedMrid) {
+        return false;
+    }
+
+    public boolean accept(ModuleRevisionId askedMrid, ModuleRevisionId foundMrid) {
+        return askedMrid.getRevision().equals(foundMrid.getRevision());
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/version/LatestVersionMatcher.java b/src/java/org/apache/ivy/plugins/version/LatestVersionMatcher.java
new file mode 100644
index 0000000..ba5b153
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/version/LatestVersionMatcher.java
@@ -0,0 +1,62 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.version;
+
+import java.util.Comparator;
+import java.util.List;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.module.status.Status;
+import org.apache.ivy.core.module.status.StatusManager;
+
+public class LatestVersionMatcher extends AbstractVersionMatcher {
+    public LatestVersionMatcher() {
+        super("latest");
+    }
+
+    public boolean isDynamic(ModuleRevisionId askedMrid) {
+        return askedMrid.getRevision().startsWith("latest.");
+    }
+
+    public boolean accept(ModuleRevisionId askedMrid, ModuleRevisionId foundMrid) {
+        return true;
+    }
+
+    public boolean needModuleDescriptor(ModuleRevisionId askedMrid, ModuleRevisionId foundMrid) {
+        List statuses = StatusManager.getCurrent().getStatuses();
+        Status lowest = (Status) statuses.get(statuses.size() - 1);
+        String latestLowest = "latest." + lowest.getName();
+        return !latestLowest.equals(askedMrid.getRevision());
+    }
+
+    public boolean accept(ModuleRevisionId askedMrid, ModuleDescriptor foundMD) {
+        String askedStatus = askedMrid.getRevision().substring("latest.".length());
+        return StatusManager.getCurrent().getPriority(askedStatus) >= StatusManager.getCurrent()
+                .getPriority(foundMD.getStatus());
+    }
+
+    /**
+     * If we don't need a module descriptor we can consider the dynamic revision to be greater. If
+     * we need a module descriptor then we can't know which one is greater and return 0.
+     */
+    public int compare(ModuleRevisionId askedMrid, ModuleRevisionId foundMrid,
+            Comparator staticComparator) {
+        return needModuleDescriptor(askedMrid, foundMrid) ? 0 : 1;
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/version/Match.java b/src/java/org/apache/ivy/plugins/version/Match.java
new file mode 100644
index 0000000..85465cc
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/version/Match.java
@@ -0,0 +1,136 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.version;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+import org.apache.ivy.core.IvyContext;
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.plugins.matcher.Matcher;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+
+/**
+ * 
+ */
+public class Match {
+    private String revision;
+
+    private String pattern;
+
+    private String args;
+
+    private String matcher;
+
+    public String getArgs() {
+        return args;
+    }
+
+    public void setArgs(String args) {
+        this.args = args;
+    }
+
+    public String getMatcher() {
+        return matcher;
+    }
+
+    public void setMatcher(String matcher) {
+        this.matcher = matcher;
+    }
+
+    public String getPattern() {
+        return pattern;
+    }
+
+    public void setPattern(String pattern) {
+        this.pattern = pattern;
+    }
+
+    public String getRevision() {
+        return revision;
+    }
+
+    public void setRevision(String revision) {
+        this.revision = revision;
+    }
+
+    public Matcher getPatternMatcher(ModuleRevisionId askedMrid) {
+        String revision = askedMrid.getRevision();
+
+        String[] args = split(getArgs());
+        String[] argValues = getRevisionArgs(revision);
+
+        if (args.length != argValues.length) {
+            return new NoMatchMatcher();
+        }
+
+        Map variables = new HashMap();
+        for (int i = 0; i < args.length; i++) {
+            variables.put(args[i], argValues[i]);
+        }
+
+        String pattern = getPattern();
+        pattern = IvyPatternHelper.substituteVariables(pattern, variables);
+
+        PatternMatcher pMatcher = IvyContext.getContext().getSettings().getMatcher(matcher);
+        return pMatcher.getMatcher(pattern);
+    }
+
+    private String[] getRevisionArgs(String revision) {
+        int bracketStartIndex = revision.indexOf('(');
+        if (bracketStartIndex == -1) {
+            return new String[0];
+        }
+
+        int bracketEndIndex = revision.indexOf(')');
+        if (bracketEndIndex <= (bracketStartIndex + 1)) {
+            return new String[0];
+        }
+
+        String args = revision.substring(bracketStartIndex + 1, bracketEndIndex);
+        return split(args);
+    }
+
+    private static String[] split(String string) {
+        if (string == null) {
+            return new String[0];
+        }
+
+        StringTokenizer tokenizer = new StringTokenizer(string, ", ");
+        List tokens = new ArrayList();
+        while (tokenizer.hasMoreTokens()) {
+            tokens.add(tokenizer.nextToken());
+        }
+
+        return (String[]) tokens.toArray(new String[tokens.size()]);
+    }
+
+    private static class NoMatchMatcher implements Matcher {
+        public boolean isExact() {
+            return false;
+        }
+
+        public boolean matches(String str) {
+            return false;
+        }
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/version/PatternVersionMatcher.java b/src/java/org/apache/ivy/plugins/version/PatternVersionMatcher.java
new file mode 100644
index 0000000..66f51a2
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/version/PatternVersionMatcher.java
@@ -0,0 +1,99 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.version;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.plugins.matcher.Matcher;
+
+/**
+ * 
+ */
+public class PatternVersionMatcher extends AbstractVersionMatcher {
+
+    private List matches = new ArrayList();
+
+    private Map revisionMatches = new HashMap(); // revision -> list of Match instances
+
+    private boolean init = false;
+
+    public void addMatch(Match match) {
+        matches.add(match);
+    }
+
+    private void init() {
+        if (!init) {
+            for (Iterator it = matches.iterator(); it.hasNext();) {
+                Match match = (Match) it.next();
+                List revMatches = (List) revisionMatches.get(match.getRevision());
+                if (revMatches == null) {
+                    revMatches = new ArrayList();
+                    revisionMatches.put(match.getRevision(), revMatches);
+                }
+                revMatches.add(match);
+            }
+            init = true;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean accept(ModuleRevisionId askedMrid, ModuleRevisionId foundMrid) {
+        init();
+        boolean accept = false;
+
+        String revision = askedMrid.getRevision();
+        int bracketIndex = revision.indexOf('(');
+        if (bracketIndex > 0) {
+            revision = revision.substring(0, bracketIndex);
+        }
+
+        List revMatches = (List) revisionMatches.get(revision);
+
+        if (revMatches != null) {
+            Iterator it = revMatches.iterator();
+            while (!accept && it.hasNext()) {
+                Match match = (Match) it.next();
+                Matcher matcher = match.getPatternMatcher(askedMrid);
+                accept = matcher.matches(foundMrid.getRevision());
+            }
+        }
+
+        return accept;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isDynamic(ModuleRevisionId askedMrid) {
+        init();
+        String revision = askedMrid.getRevision();
+        int bracketIndex = revision.indexOf('(');
+        if (bracketIndex > 0) {
+            revision = revision.substring(0, bracketIndex);
+        }
+        return revisionMatches.containsKey(revision);
+    }
+
+}
diff --git a/src/java/org/apache/ivy/plugins/version/SubVersionMatcher.java b/src/java/org/apache/ivy/plugins/version/SubVersionMatcher.java
new file mode 100644
index 0000000..b8e04cb
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/version/SubVersionMatcher.java
@@ -0,0 +1,46 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.version;
+
+import java.util.Comparator;
+
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+
+public class SubVersionMatcher extends AbstractVersionMatcher {
+    public SubVersionMatcher() {
+        super("sub-version");
+    }
+
+    public boolean isDynamic(ModuleRevisionId askedMrid) {
+        return askedMrid.getRevision().endsWith("+");
+    }
+
+    public boolean accept(ModuleRevisionId askedMrid, ModuleRevisionId foundMrid) {
+        String prefix = askedMrid.getRevision().substring(0, askedMrid.getRevision().length() - 1);
+        return foundMrid.getRevision().startsWith(prefix);
+    }
+
+    public int compare(ModuleRevisionId askedMrid, ModuleRevisionId foundMrid,
+            Comparator staticComparator) {
+        if (foundMrid.getRevision().startsWith(
+            askedMrid.getRevision().substring(0, askedMrid.getRevision().length() - 1))) {
+            return 1;
+        }
+        return staticComparator.compare(askedMrid, foundMrid);
+    }
+}
diff --git a/src/java/org/apache/ivy/plugins/version/VersionMatcher.java b/src/java/org/apache/ivy/plugins/version/VersionMatcher.java
new file mode 100644
index 0000000..ffbc976
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/version/VersionMatcher.java
@@ -0,0 +1,110 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.version;
+
+import java.util.Comparator;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+
+/**
+ * This interface defines a version matcher, i.e. a class able to tell if the revision asked by a
+ * module for a dependency is dynamic (i.e. need to find all revisions to find the good one among
+ * them) and if a found revision matches the asked one.
+ * <p>
+ * Two ways of matching are possible:
+ * <ul>
+ * <li>based on the module revision only (known as ModuleRevisionId)</li>
+ * <li>based on the parsed module descriptor</li>
+ * </ul>
+ * The second being much more time consuming than the first, the version matcher should tell if it
+ * needs such parsing or not using the
+ * {@link #needModuleDescriptor(ModuleRevisionId, ModuleRevisionId)} method. Anyway, the first way
+ * is always used, and if a revision is not accepted using the first method, the module descriptor
+ * won't be parsed. Therefore if a version matcher uses only module descriptors to accept a revision
+ * or not it should always return true to
+ * {@link #needModuleDescriptor(ModuleRevisionId, ModuleRevisionId)} and
+ * {@link #accept(ModuleRevisionId, ModuleDescriptor)}.
+ */
+public interface VersionMatcher {
+    /**
+     * Indicates if the given asked ModuleRevisionId should be considered as dynamic for the current
+     * VersionMatcher or not.
+     * 
+     * @param askedMrid
+     *            the dependency module revision id as asked by a module
+     * @return true if this revision is considered as a dynamic one, false otherwise
+     */
+    public boolean isDynamic(ModuleRevisionId askedMrid);
+
+    /**
+     * Indicates if this version matcher considers that the module revision found matches the asked
+     * one.
+     * 
+     * @param askedMrid
+     * @param foundMrid
+     * @return
+     */
+    public boolean accept(ModuleRevisionId askedMrid, ModuleRevisionId foundMrid);
+
+    /**
+     * Indicates if this VersionMatcher needs module descriptors to determine if a module revision
+     * matches the asked one. Note that returning true in this method may imply big performance
+     * issues.
+     * 
+     * @return
+     */
+    public boolean needModuleDescriptor(ModuleRevisionId askedMrid, ModuleRevisionId foundMrid);
+
+    /**
+     * Indicates if this version matcher considers that the module found matches the asked one. This
+     * method can be called even needModuleDescriptor(ModuleRevisionId askedMrid, ModuleRevisionId
+     * foundMrid) returns false, so it is required to implement it in any case, a usual default
+     * implementation being: return accept(askedMrid, foundMD.getResolvedModuleRevisionId());
+     * 
+     * @param askedMrid
+     * @param foundMD
+     * @return
+     */
+    public boolean accept(ModuleRevisionId askedMrid, ModuleDescriptor foundMD);
+
+    /**
+     * Compares a dynamic revision (askedMrid) with a static one (foundMrid) to indicate which one
+     * should be considered the greater. If there is not enough information to know which one is the
+     * greater, the dynamic one should be considered greater and this method should return 0. This
+     * method should never be called with a askdeMrid for which isDynamic returns false.
+     * 
+     * @param askedMrid
+     *            the dynamic revision to compare
+     * @param foundMrid
+     *            the static revision to compare
+     * @param staticComparator
+     *            a comparator which can be used to compare static revisions
+     * @return 0 if it's not possible to know which one is greater, greater than 0 if askedMrid
+     *         should be considered greater, lower than 0 if it can't be consider greater
+     */
+    public int compare(ModuleRevisionId askedMrid, ModuleRevisionId foundMrid,
+            Comparator staticComparator);
+
+    /**
+     * Returns the version matcher name identifying this version matcher
+     * 
+     * @return the version matcher name identifying this version matcher
+     */
+    public String getName();
+}
diff --git a/src/java/org/apache/ivy/plugins/version/VersionRangeMatcher.java b/src/java/org/apache/ivy/plugins/version/VersionRangeMatcher.java
new file mode 100644
index 0000000..7c59a3a
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/version/VersionRangeMatcher.java
@@ -0,0 +1,246 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.version;
+
+import java.util.Comparator;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.plugins.latest.ArtifactInfo;
+import org.apache.ivy.plugins.latest.LatestStrategy;
+
+/**
+ * Matches version ranges: [1.0,2.0] matches all versions greater or equal to 1.0 and lower or equal
+ * to 2.0 [1.0,2.0[ matches all versions greater or equal to 1.0 and lower than 2.0 ]1.0,2.0]
+ * matches all versions greater than 1.0 and lower or equal to 2.0 ]1.0,2.0[ matches all versions
+ * greater than 1.0 and lower than 2.0 [1.0,) matches all versions greater or equal to 1.0 ]1.0,)
+ * matches all versions greater than 1.0 (,2.0] matches all versions lower or equal to 2.0 (,2.0[
+ * matches all versions lower than 2.0 This class uses a latest strategy to compare revisions. If
+ * none is set, it uses the default one of the ivy instance set through setIvy(). If neither a
+ * latest strategy nor a ivy instance is set, an IllegalStateException will be thrown when calling
+ * accept(). Note that it can't work with latest time strategy, cause no time is known for the
+ * limits of the range. Therefore only purely revision based LatestStrategy can be used.
+ */
+public class VersionRangeMatcher extends AbstractVersionMatcher {
+    // todo: check these constants
+    private static final String OPEN_INC = "[";
+
+    private static final String OPEN_EXC = "]";
+
+    private static final String OPEN_EXC_MAVEN = "(";
+
+    private static final String CLOSE_INC = "]";
+
+    private static final String CLOSE_EXC = "[";
+
+    private static final String CLOSE_EXC_MAVEN = ")";
+
+    private static final String LOWER_INFINITE = "(";
+
+    private static final String UPPER_INFINITE = ")";
+
+    private static final String SEPARATOR = ",";
+
+    // following patterns are built upon constants above and should not be modified
+    private static final String OPEN_INC_PATTERN = "\\" + OPEN_INC;
+
+    private static final String OPEN_EXC_PATTERN = "\\" + OPEN_EXC + "\\" + OPEN_EXC_MAVEN;
+
+    private static final String CLOSE_INC_PATTERN = "\\" + CLOSE_INC;
+
+    private static final String CLOSE_EXC_PATTERN = "\\" + CLOSE_EXC + "\\" + CLOSE_EXC_MAVEN;
+
+    private static final String LI_PATTERN = "\\" + LOWER_INFINITE;
+
+    private static final String UI_PATTERN = "\\" + UPPER_INFINITE;
+
+    private static final String SEP_PATTERN = "\\s*\\" + SEPARATOR + "\\s*";
+
+    private static final String OPEN_PATTERN = "[" + OPEN_INC_PATTERN + OPEN_EXC_PATTERN + "]";
+
+    private static final String CLOSE_PATTERN = "[" + CLOSE_INC_PATTERN + CLOSE_EXC_PATTERN + "]";
+
+    private static final String ANY_NON_SPECIAL_PATTERN = "[^\\s" + SEPARATOR + OPEN_INC_PATTERN
+            + OPEN_EXC_PATTERN + CLOSE_INC_PATTERN + CLOSE_EXC_PATTERN + LI_PATTERN + UI_PATTERN
+            + "]";
+
+    private static final String FINITE_PATTERN = OPEN_PATTERN + "\\s*(" + ANY_NON_SPECIAL_PATTERN
+            + "+)" + SEP_PATTERN + "(" + ANY_NON_SPECIAL_PATTERN + "+)\\s*" + CLOSE_PATTERN;
+
+    private static final String LOWER_INFINITE_PATTERN = LI_PATTERN + SEP_PATTERN + "("
+            + ANY_NON_SPECIAL_PATTERN + "+)\\s*" + CLOSE_PATTERN;
+
+    private static final String UPPER_INFINITE_PATTERN = OPEN_PATTERN + "\\s*("
+            + ANY_NON_SPECIAL_PATTERN + "+)" + SEP_PATTERN + UI_PATTERN;
+
+    private static final Pattern FINITE_RANGE = Pattern.compile(FINITE_PATTERN);
+
+    private static final Pattern LOWER_INFINITE_RANGE = Pattern.compile(LOWER_INFINITE_PATTERN);
+
+    private static final Pattern UPPER_INFINITE_RANGE = Pattern.compile(UPPER_INFINITE_PATTERN);
+
+    private static final Pattern ALL_RANGE = Pattern.compile(FINITE_PATTERN + "|"
+            + LOWER_INFINITE_PATTERN + "|" + UPPER_INFINITE_PATTERN);
+
+    private final class MRIDArtifactInfo implements ArtifactInfo {
+        private ModuleRevisionId mrid;
+
+        public MRIDArtifactInfo(ModuleRevisionId id) {
+            mrid = id;
+        }
+
+        public long getLastModified() {
+            return 0;
+        }
+
+        public String getRevision() {
+            return mrid.getRevision();
+        }
+    }
+
+    private final Comparator comparator = new Comparator() {
+        public int compare(Object o1, Object o2) {
+            if (o1.equals(o2)) {
+                return 0;
+            }
+            ArtifactInfo art1 = new MRIDArtifactInfo((ModuleRevisionId) o1);
+            ArtifactInfo art2 = new MRIDArtifactInfo((ModuleRevisionId) o2);
+            ArtifactInfo art = getLatestStrategy()
+                    .findLatest(new ArtifactInfo[] {art1, art2}, null);
+            return art == art1 ? -1 : 1;
+        }
+    };
+
+    private LatestStrategy latestStrategy;
+
+    private String latestStrategyName = "default";
+
+    public VersionRangeMatcher() {
+        super("version-range");
+    }
+
+    public VersionRangeMatcher(String name) {
+        super(name);
+    }
+
+    public VersionRangeMatcher(String name, LatestStrategy strategy) {
+        super(name);
+        this.latestStrategy = strategy;
+    }
+
+    public boolean isDynamic(ModuleRevisionId askedMrid) {
+        String revision = askedMrid.getRevision();
+        return ALL_RANGE.matcher(revision).matches();
+    }
+
+    public boolean accept(ModuleRevisionId askedMrid, ModuleRevisionId foundMrid) {
+        String revision = askedMrid.getRevision();
+        Matcher m;
+        m = FINITE_RANGE.matcher(revision);
+        if (m.matches()) {
+            String lower = m.group(1);
+            String upper = m.group(2);
+            return isUpper(askedMrid, lower, foundMrid, revision.startsWith(OPEN_INC))
+                    && isLower(askedMrid, upper, foundMrid, revision.endsWith(CLOSE_INC));
+        }
+        m = LOWER_INFINITE_RANGE.matcher(revision);
+        if (m.matches()) {
+            String upper = m.group(1);
+            return isLower(askedMrid, upper, foundMrid, revision.endsWith(CLOSE_INC));
+        }
+        m = UPPER_INFINITE_RANGE.matcher(revision);
+        if (m.matches()) {
+            String lower = m.group(1);
+            return isUpper(askedMrid, lower, foundMrid, revision.startsWith(OPEN_INC));
+        }
+        return false;
+    }
+
+    private boolean isLower(ModuleRevisionId askedMrid, String revision,
+            ModuleRevisionId foundMrid, boolean inclusive) {
+        ModuleRevisionId mRevId = ModuleRevisionId.newInstance(askedMrid, revision);
+        int result = comparator.compare(mRevId, foundMrid);
+        return result <= (inclusive ? 0 : -1);
+    }
+
+    private boolean isUpper(ModuleRevisionId askedMrid, String revision,
+            ModuleRevisionId foundMrid, boolean inclusive) {
+        ModuleRevisionId mRevId = ModuleRevisionId.newInstance(askedMrid, revision);
+        int result = comparator.compare(mRevId, foundMrid);
+        return result >= (inclusive ? 0 : 1);
+    }
+
+    public int compare(ModuleRevisionId askedMrid, ModuleRevisionId foundMrid,
+            Comparator staticComparator) {
+        String revision = askedMrid.getRevision();
+        Matcher m;
+        m = UPPER_INFINITE_RANGE.matcher(revision);
+        if (m.matches()) {
+            // no upper limit, the dynamic revision can always be considered greater
+            return 1;
+        }
+        String upper;
+        m = FINITE_RANGE.matcher(revision);
+        if (m.matches()) {
+            upper = m.group(2);
+        } else {
+            m = LOWER_INFINITE_RANGE.matcher(revision);
+            if (m.matches()) {
+                upper = m.group(1);
+            } else {
+                throw new IllegalArgumentException(
+                        "impossible to compare: askedMrid is not a dynamic revision: " + askedMrid);
+            }
+        }
+        int c = staticComparator.compare(ModuleRevisionId.newInstance(askedMrid, upper), foundMrid);
+        // if the comparison consider them equal, we must return -1, because we can't consider the
+        // dynamic revision to be greater. Otherwise we can safeely return the result of the static
+        // comparison
+        return c == 0 ? -1 : c;
+    }
+
+    public LatestStrategy getLatestStrategy() {
+        if (latestStrategy == null) {
+            if (getSettings() == null) {
+                throw new IllegalStateException(
+                        "no ivy instance nor latest strategy configured in version range matcher "
+                                + this);
+            }
+            if (latestStrategyName == null) {
+                throw new IllegalStateException(
+                        "null latest strategy defined in version range matcher " + this);
+            }
+            latestStrategy = getSettings().getLatestStrategy(latestStrategyName);
+            if (latestStrategy == null) {
+                throw new IllegalStateException("unknown latest strategy '" + latestStrategyName
+                        + "' configured in version range matcher " + this);
+            }
+        }
+        return latestStrategy;
+    }
+
+    public void setLatestStrategy(LatestStrategy latestStrategy) {
+        this.latestStrategy = latestStrategy;
+    }
+
+    public void setLatest(String latestStrategyName) {
+        this.latestStrategyName = latestStrategyName;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/tools/analyser/DependencyAnalyser.java b/src/java/org/apache/ivy/tools/analyser/DependencyAnalyser.java
new file mode 100644
index 0000000..1b45040
--- /dev/null
+++ b/src/java/org/apache/ivy/tools/analyser/DependencyAnalyser.java
@@ -0,0 +1,24 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.tools.analyser;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+
+public interface DependencyAnalyser {
+    public ModuleDescriptor[] analyze(JarModule[] modules);
+}
diff --git a/src/java/org/apache/ivy/tools/analyser/JarJarDependencyAnalyser.java b/src/java/org/apache/ivy/tools/analyser/JarJarDependencyAnalyser.java
new file mode 100644
index 0000000..81736b1
--- /dev/null
+++ b/src/java/org/apache/ivy/tools/analyser/JarJarDependencyAnalyser.java
@@ -0,0 +1,95 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.tools.analyser;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.util.Message;
+
+public class JarJarDependencyAnalyser implements DependencyAnalyser {
+    private File jarjarjarLocation;
+
+    public JarJarDependencyAnalyser(File jarjarjarLocation) {
+        this.jarjarjarLocation = jarjarjarLocation;
+    }
+
+    public ModuleDescriptor[] analyze(JarModule[] modules) {
+
+        StringBuffer jarjarCmd = new StringBuffer("java -jar \"").append(
+            jarjarjarLocation.getAbsolutePath()).append("\" --find --level=jar ");
+        Map jarModulesMap = new HashMap();
+        Map mds = new HashMap();
+
+        for (int i = 0; i < modules.length; i++) {
+            jarModulesMap.put(modules[i].getJar().getAbsolutePath(), modules[i]);
+            DefaultModuleDescriptor md = DefaultModuleDescriptor.newBasicInstance(
+                modules[i].getMrid(), new Date(modules[i].getJar().lastModified()));
+            mds.put(modules[i].getMrid(), md);
+            jarjarCmd.append("\"").append(modules[i].getJar().getAbsolutePath()).append("\"");
+            if (i + 1 < modules.length) {
+                jarjarCmd.append(File.pathSeparator);
+            }
+        }
+
+        Message.verbose("jarjar command: " + jarjarCmd);
+
+        try {
+            Process p = Runtime.getRuntime().exec(jarjarCmd.toString());
+            BufferedReader r = new BufferedReader(new InputStreamReader(p.getInputStream()));
+            String line;
+            while ((line = r.readLine()) != null) {
+                String[] deps = line.split(" -> ");
+                JarModule module = (JarModule) jarModulesMap.get(deps[0]);
+                JarModule dependency = (JarModule) jarModulesMap.get(deps[1]);
+
+                if (module.getMrid().getModuleId().equals(dependency.getMrid().getModuleId())) {
+                    continue;
+                }
+                Message.verbose(module.getMrid() + " depends on " + dependency.getMrid());
+
+                DefaultModuleDescriptor md = (DefaultModuleDescriptor) mds.get(module.getMrid());
+
+                DefaultDependencyDescriptor dd = new DefaultDependencyDescriptor(md,
+                        dependency.getMrid(), false, false, true);
+                dd.addDependencyConfiguration(ModuleDescriptor.DEFAULT_CONFIGURATION,
+                    ModuleDescriptor.DEFAULT_CONFIGURATION);
+                md.addDependency(dd);
+            }
+        } catch (IOException e) {
+            Message.debug(e);
+        }
+        return (ModuleDescriptor[]) mds.values().toArray(new ModuleDescriptor[mds.values().size()]);
+    }
+
+    public static void main(String[] args) {
+        JarJarDependencyAnalyser a = new JarJarDependencyAnalyser(new File(
+                "D:/temp/test2/jarjar-0.7.jar"));
+        a.analyze(new JarModuleFinder(
+                "D:/temp/test2/ivyrep/[organisation]/[module]/[revision]/[artifact].[ext]")
+                .findJarModules());
+    }
+}
diff --git a/src/java/org/apache/ivy/tools/analyser/JarModule.java b/src/java/org/apache/ivy/tools/analyser/JarModule.java
new file mode 100644
index 0000000..35605fe
--- /dev/null
+++ b/src/java/org/apache/ivy/tools/analyser/JarModule.java
@@ -0,0 +1,46 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.tools.analyser;
+
+import java.io.File;
+
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+
+public class JarModule {
+    private ModuleRevisionId mrid;
+
+    private File jar;
+
+    public JarModule(ModuleRevisionId mrid, File jar) {
+        this.mrid = mrid;
+        this.jar = jar;
+    }
+
+    public File getJar() {
+        return jar;
+    }
+
+    public ModuleRevisionId getMrid() {
+        return mrid;
+    }
+
+    public String toString() {
+        return jar + " " + mrid;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/tools/analyser/JarModuleFinder.java b/src/java/org/apache/ivy/tools/analyser/JarModuleFinder.java
new file mode 100644
index 0000000..8cbd262
--- /dev/null
+++ b/src/java/org/apache/ivy/tools/analyser/JarModuleFinder.java
@@ -0,0 +1,80 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.tools.analyser;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.plugins.resolver.util.FileURLLister;
+import org.apache.ivy.plugins.resolver.util.ResolverHelper;
+import org.apache.ivy.plugins.resolver.util.URLLister;
+import org.apache.ivy.util.Message;
+
+public class JarModuleFinder {
+    private String pattern;
+
+    private String filePattern;
+
+    public JarModuleFinder(String pattern) {
+        this.pattern = "file:///" + pattern;
+        this.filePattern = pattern;
+    }
+
+    public JarModule[] findJarModules() {
+        List ret = new ArrayList();
+        URLLister lister = new FileURLLister();
+        try {
+            String[] orgs = ResolverHelper.listTokenValues(lister, pattern, "organisation");
+            for (int i = 0; i < orgs.length; i++) {
+                String orgPattern = IvyPatternHelper.substituteToken(pattern,
+                    IvyPatternHelper.ORGANISATION_KEY, orgs[i]);
+                String[] modules = ResolverHelper.listTokenValues(lister, orgPattern, "module");
+                for (int j = 0; j < modules.length; j++) {
+                    String modPattern = IvyPatternHelper.substituteToken(orgPattern,
+                        IvyPatternHelper.MODULE_KEY, modules[j]);
+                    String[] revs = ResolverHelper.listTokenValues(lister, modPattern, "revision");
+                    for (int k = 0; k < revs.length; k++) {
+                        File jar = new File(IvyPatternHelper.substitute(filePattern, orgs[i],
+                            modules[j], revs[k], modules[j], "jar", "jar"));
+                        if (jar.exists()) {
+                            ret.add(new JarModule(ModuleRevisionId.newInstance(orgs[i], modules[j],
+                                revs[k]), jar));
+                        }
+                    }
+                }
+            }
+
+        } catch (Exception e) {
+            Message.debug(e);
+            // TODO: handle exception
+        }
+        return (JarModule[]) ret.toArray(new JarModule[ret.size()]);
+    }
+
+    public static void main(String[] args) {
+        JarModule[] mods = new JarModuleFinder(
+                "D:/temp/test2/ivyrep/[organisation]/[module]/[revision]/[artifact].[ext]")
+                .findJarModules();
+        for (int i = 0; i < mods.length; i++) {
+            System.out.println(mods[i]);
+        }
+    }
+}
diff --git a/src/java/org/apache/ivy/tools/analyser/RepositoryAnalyser.java b/src/java/org/apache/ivy/tools/analyser/RepositoryAnalyser.java
new file mode 100644
index 0000000..aa34a45
--- /dev/null
+++ b/src/java/org/apache/ivy/tools/analyser/RepositoryAnalyser.java
@@ -0,0 +1,60 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.tools.analyser;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.core.module.descriptor.DefaultArtifact;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorWriter;
+import org.apache.ivy.util.Message;
+
+public class RepositoryAnalyser {
+    public void analyse(String pattern, DependencyAnalyser depAnalyser) {
+        JarModuleFinder finder = new JarModuleFinder(pattern);
+        ModuleDescriptor[] mds = depAnalyser.analyze(finder.findJarModules());
+        Message.info("found " + mds.length + " modules");
+        for (int i = 0; i < mds.length; i++) {
+            File ivyFile = new File(IvyPatternHelper.substitute(
+                pattern,
+                DefaultArtifact.newIvyArtifact(mds[i].getModuleRevisionId(),
+                    mds[i].getPublicationDate())));
+            try {
+                Message.info("generating " + ivyFile);
+                XmlModuleDescriptorWriter.write(mds[i], ivyFile);
+            } catch (IOException e) {
+                Message.debug(e);
+            }
+        }
+    }
+
+    public static void main(String[] args) {
+        if (args.length != 2) {
+            System.out
+                    .println("usage: ivyanalyser path/to/jarjar.jar absolute-ivy-repository-pattern");
+            return;
+        }
+        String jarjarLocation = args[0];
+        String pattern = args[1];
+
+        JarJarDependencyAnalyser a = new JarJarDependencyAnalyser(new File(jarjarLocation));
+        new RepositoryAnalyser().analyse(pattern, a);
+    }
+}
diff --git a/src/java/org/apache/ivy/util/AbstractMessageLogger.java b/src/java/org/apache/ivy/util/AbstractMessageLogger.java
new file mode 100644
index 0000000..118db66
--- /dev/null
+++ b/src/java/org/apache/ivy/util/AbstractMessageLogger.java
@@ -0,0 +1,199 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * An abstract base class to ease {@link MessageLogger} implementation.
+ */
+public abstract class AbstractMessageLogger implements MessageLogger {
+    private List problems = new ArrayList();
+
+    private List warns = new ArrayList();
+
+    private List errors = new ArrayList();
+
+    private boolean showProgress = true;
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.ivy.util.MessageLogger#debug(java.lang.String)
+     */
+    public void debug(String msg) {
+        log(msg, Message.MSG_DEBUG);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.ivy.util.MessageLogger#verbose(java.lang.String)
+     */
+    public void verbose(String msg) {
+        log(msg, Message.MSG_VERBOSE);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.ivy.util.MessageLogger#deprecated(java.lang.String)
+     */
+    public void deprecated(String msg) {
+        log("DEPRECATED: " + msg, Message.MSG_WARN);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.ivy.util.MessageLogger#info(java.lang.String)
+     */
+    public void info(String msg) {
+        log(msg, Message.MSG_INFO);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.ivy.util.MessageLogger#info(java.lang.String)
+     */
+    public void rawinfo(String msg) {
+        rawlog(msg, Message.MSG_INFO);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.ivy.util.MessageLogger#warn(java.lang.String)
+     */
+    public void warn(String msg) {
+        log("WARN: " + msg, Message.MSG_VERBOSE);
+        problems.add("WARN:  " + msg);
+        getWarns().add(msg);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.ivy.util.MessageLogger#error(java.lang.String)
+     */
+    public void error(String msg) {
+        // log in verbose mode because message is appended as a problem, and will be
+        // logged at the end at error level
+        log("ERROR: " + msg, Message.MSG_VERBOSE);
+        problems.add("\tERROR: " + msg);
+        getErrors().add(msg);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.ivy.util.MessageLogger#getProblems()
+     */
+    public List getProblems() {
+        return problems;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.ivy.util.MessageLogger#sumupProblems()
+     */
+    public void sumupProblems() {
+        MessageLoggerHelper.sumupProblems(this);
+        clearProblems();
+    }
+
+    public void clearProblems() {
+        problems.clear();
+        warns.clear();
+        errors.clear();
+    }
+
+    public List getErrors() {
+        return errors;
+    }
+
+    public List getWarns() {
+        return warns;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.ivy.util.MessageLogger#progress()
+     */
+    public void progress() {
+        if (showProgress) {
+            doProgress();
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.ivy.util.MessageLogger#endProgress()
+     */
+    public void endProgress() {
+        endProgress("");
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.ivy.util.MessageLogger#endProgress(java.lang.String)
+     */
+    public void endProgress(String msg) {
+        if (showProgress) {
+            doEndProgress(msg);
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.ivy.util.MessageLogger#isShowProgress()
+     */
+    public boolean isShowProgress() {
+        return showProgress;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.ivy.util.MessageLogger#setShowProgress(boolean)
+     */
+    public void setShowProgress(boolean progress) {
+        showProgress = progress;
+    }
+
+    /**
+     * Indicates a progression for a long running task
+     */
+    protected abstract void doProgress();
+
+    /**
+     * Indicates the end of a long running task
+     * 
+     * @param msg
+     *            the message associated with long running task end.
+     */
+    protected abstract void doEndProgress(String msg);
+
+}
diff --git a/src/java/org/apache/ivy/util/Checks.java b/src/java/org/apache/ivy/util/Checks.java
new file mode 100644
index 0000000..6b9e927
--- /dev/null
+++ b/src/java/org/apache/ivy/util/Checks.java
@@ -0,0 +1,61 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util;
+
+import java.io.File;
+
+/**
+ * Utility class used to perform some checks.
+ */
+public final class Checks {
+    private Checks() {
+    }
+
+    /**
+     * Checks that an object is not null, and throw an exception if the object is null.
+     * 
+     * @param o
+     *            the object to check
+     * @param objectName
+     *            the name of the object to check. This name will be used in the exception message.
+     * @throws IllegalArgumentException
+     *             if the object is null
+     */
+    public static void checkNotNull(Object o, String objectName) {
+        if (o == null) {
+            throw new IllegalArgumentException(objectName + " must not be null");
+        }
+    }
+
+    public static File checkAbsolute(File f, String fileName) {
+        checkNotNull(f, fileName);
+        if (!f.isAbsolute()) {
+            throw new IllegalArgumentException(fileName + " must be absolute: " + f.getPath());
+        }
+        return FileUtil.normalize(f.getPath());
+    }
+
+    public static File checkAbsolute(String path, String fileName) {
+        checkNotNull(path, fileName);
+        File f = new File(path);
+        if (!f.isAbsolute()) {
+            throw new IllegalArgumentException(fileName + " must be absolute: " + path);
+        }
+        return FileUtil.normalize(f.getPath());
+    }
+}
diff --git a/src/java/org/apache/ivy/util/ChecksumHelper.java b/src/java/org/apache/ivy/util/ChecksumHelper.java
new file mode 100644
index 0000000..56aa936
--- /dev/null
+++ b/src/java/org/apache/ivy/util/ChecksumHelper.java
@@ -0,0 +1,168 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+public final class ChecksumHelper {
+
+    private static final int BUFFER_SIZE = 2048;
+
+    private static Map algorithms = new HashMap();
+    static {
+        algorithms.put("md5", "MD5");
+        algorithms.put("sha1", "SHA-1");
+    }
+
+    /**
+     * Checks the checksum of the given file against the given checksumFile, and throws an
+     * IOException if the checksum is not compliant
+     * 
+     * @param dest
+     *            the file to test
+     * @param checksumFile
+     *            the file containing the expected checksum
+     * @param algorithm
+     *            the checksum algorithm to use
+     * @throws IOException
+     *             if an IO problem occur whle reading files or if the checksum is not compliant
+     */
+    public static void check(File dest, File checksumFile, String algorithm) throws IOException {
+        String csFileContent = FileUtil
+                .readEntirely(new BufferedReader(new FileReader(checksumFile))).trim()
+                .toLowerCase(Locale.US);
+        String expected;
+        if (csFileContent.indexOf(' ') > -1
+                && (csFileContent.startsWith("md") || csFileContent.startsWith("sha"))) {
+            int lastSpaceIndex = csFileContent.lastIndexOf(' ');
+            expected = csFileContent.substring(lastSpaceIndex + 1);
+        } else {
+            int spaceIndex = csFileContent.indexOf(' ');
+            if (spaceIndex != -1) {
+                expected = csFileContent.substring(0, spaceIndex);
+
+                // IVY-1155: support some strange formats like this one:
+                // https://repo1.maven.org/maven2/org/apache/pdfbox/fontbox/0.8.0-incubator/fontbox-0.8.0-incubator.jar.md5
+                if (expected.endsWith(":")) {
+                    StringBuffer result = new StringBuffer();
+                    char[] chars = csFileContent.substring(spaceIndex + 1).toCharArray();
+                    for (int i = 0; i < chars.length; i++) {
+                        if (!Character.isWhitespace(chars[i])) {
+                            result.append(chars[i]);
+                        }
+                    }
+                    expected = result.toString();
+                }
+            } else {
+                expected = csFileContent;
+            }
+        }
+
+        String computed = computeAsString(dest, algorithm).trim().toLowerCase(Locale.US);
+        if (!expected.equals(computed)) {
+            throw new IOException("invalid " + algorithm + ": expected=" + expected + " computed="
+                    + computed);
+        }
+    }
+
+    public static String computeAsString(File f, String algorithm) throws IOException {
+        return byteArrayToHexString(compute(f, algorithm));
+    }
+
+    private static byte[] compute(File f, String algorithm) throws IOException {
+        InputStream is = new FileInputStream(f);
+
+        try {
+            MessageDigest md = getMessageDigest(algorithm);
+            md.reset();
+
+            byte[] buf = new byte[BUFFER_SIZE];
+            int len = 0;
+            while ((len = is.read(buf)) != -1) {
+                md.update(buf, 0, len);
+            }
+            return md.digest();
+        } finally {
+            is.close();
+        }
+    }
+
+    public static boolean isKnownAlgorithm(String algorithm) {
+        return algorithms.containsKey(algorithm);
+    }
+
+    private static MessageDigest getMessageDigest(String algorithm) {
+        String mdAlgorithm = (String) algorithms.get(algorithm);
+        if (mdAlgorithm == null) {
+            throw new IllegalArgumentException("unknown algorithm " + algorithm);
+        }
+        try {
+            return MessageDigest.getInstance(mdAlgorithm);
+        } catch (NoSuchAlgorithmException e) {
+            throw new IllegalArgumentException("unknown algorithm " + algorithm);
+        }
+    }
+
+    // byte to hex string converter
+    private static final char[] CHARS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a',
+            'b', 'c', 'd', 'e', 'f'};
+
+    /**
+     * Convert a byte[] array to readable string format. This makes the "hex" readable!
+     * 
+     * @return result String buffer in String format
+     * @param in
+     *            byte[] buffer to convert to string format
+     */
+    public static String byteArrayToHexString(byte[] in) {
+        byte ch = 0x00;
+
+        if (in == null || in.length <= 0) {
+            return null;
+        }
+
+        StringBuffer out = new StringBuffer(in.length * 2);
+
+        // CheckStyle:MagicNumber OFF
+        for (int i = 0; i < in.length; i++) {
+            ch = (byte) (in[i] & 0xF0); // Strip off high nibble
+            ch = (byte) (ch >>> 4); // shift the bits down
+            ch = (byte) (ch & 0x0F); // must do this is high order bit is on!
+
+            out.append(CHARS[ch]); // convert the nibble to a String Character
+            ch = (byte) (in[i] & 0x0F); // Strip off low nibble
+            out.append(CHARS[ch]); // convert the nibble to a String Character
+        }
+        // CheckStyle:MagicNumber ON
+
+        return out.toString();
+    }
+
+    private ChecksumHelper() {
+    }
+}
diff --git a/src/java/org/apache/ivy/util/CollectionUtils.java b/src/java/org/apache/ivy/util/CollectionUtils.java
new file mode 100644
index 0000000..d82e414
--- /dev/null
+++ b/src/java/org/apache/ivy/util/CollectionUtils.java
@@ -0,0 +1,33 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+public class CollectionUtils {
+
+    public static List toList(Iterator iterator) {
+        List list = new ArrayList();
+        while (iterator.hasNext()) {
+            list.add(iterator.next());
+        }
+        return list;
+    }
+}
diff --git a/src/java/org/apache/ivy/util/ConfigurationUtils.java b/src/java/org/apache/ivy/util/ConfigurationUtils.java
new file mode 100644
index 0000000..1b5a637
--- /dev/null
+++ b/src/java/org/apache/ivy/util/ConfigurationUtils.java
@@ -0,0 +1,96 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import org.apache.ivy.core.module.descriptor.Configuration;
+import org.apache.ivy.core.module.descriptor.Configuration.Visibility;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+
+/**
+ * Class containing several utility methods for working with configurations.
+ */
+public final class ConfigurationUtils {
+
+    /**
+     * Private constructor to avoid instantiation of this class.
+     */
+    private ConfigurationUtils() {
+    }
+
+    /**
+     * Replace the wildcards in the given configuration array, by the name of the given
+     * ModuleDescriptor
+     * 
+     * The supported wildcards are:
+     * <ul>
+     * <li><b><tt>*</tt> :</b> all configurations</li>
+     * <li><b><tt>*(public)</tt> :</b> all public configurations</li>
+     * <li><b><tt>*(private)</tt> :</b> all private configurations</li>
+     * </ul>
+     * If the given array of configurations is <code>null</code>, all configurations from the given
+     * module descriptor are returned, including if this array is empty.
+     * 
+     * @param confs
+     *            the configurations, can contain wildcards
+     * @param md
+     *            the configurations where the wildcards are replaced
+     * @return
+     */
+    public static String[] replaceWildcards(String[] confs, ModuleDescriptor md) {
+        if (confs == null) {
+            return md.getConfigurationsNames();
+        }
+
+        Set result = new LinkedHashSet();
+        Set excluded = new LinkedHashSet();
+        for (int i = 0; i < confs.length; i++) {
+            if ("*".equals(confs[i])) {
+                result.addAll(Arrays.asList(md.getConfigurationsNames()));
+            } else if ("*(public)".equals(confs[i])) {
+                Configuration[] all = md.getConfigurations();
+                for (int j = 0; j < all.length; j++) {
+                    if (all[j].getVisibility().equals(Visibility.PUBLIC)) {
+                        result.add(all[j].getName());
+                    }
+                }
+            } else if ("*(private)".equals(confs[i])) {
+                Configuration[] all = md.getConfigurations();
+                for (int j = 0; j < all.length; j++) {
+                    if (all[j].getVisibility().equals(Visibility.PRIVATE)) {
+                        result.add(all[j].getName());
+                    }
+                }
+            } else if (confs[i].startsWith("!")) {
+                excluded.add(confs[i].substring(1));
+            } else {
+                result.add(confs[i]);
+            }
+        }
+        for (Iterator iter = excluded.iterator(); iter.hasNext();) {
+            result.remove(iter.next());
+        }
+
+        return (String[]) result.toArray(new String[result.size()]);
+    }
+
+}
diff --git a/src/java/org/apache/ivy/util/Configurator.java b/src/java/org/apache/ivy/util/Configurator.java
new file mode 100644
index 0000000..9513070
--- /dev/null
+++ b/src/java/org/apache/ivy/util/Configurator.java
@@ -0,0 +1,764 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util;
+
+import java.io.File;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Stack;
+
+import org.apache.ivy.core.IvyPatternHelper;
+
+/**
+ * Ant 1.6.1 like Configurator
+ * <p>
+ * This configurator is used to configure elements (initialised with setRoot) using the behaviour
+ * defined by ant for its tasks.
+ * <p>
+ * Example (based on <a href="http://ant.apache.org/manual/develop.html#writingowntask">Ant
+ * Example</a>):
+ * 
+ * <pre>
+ * Configurator conf = new Configurator();
+ * conf.typeDef("buildpath", "Sample$BuildPath");
+ * conf.typeDef("xinterface", "Sample$XInterface");
+ * Sample.MyFileSelector mfs = new Sample.MyFileSelector();
+ * conf.setRoot(mfs);
+ * conf.startCreateChild("buildpath");
+ * conf.setAttribute("path", ".");
+ * conf.setAttribute("url", "abc");
+ * conf.startCreateChild("xinterface");
+ * conf.setAttribute("count", "4");
+ * conf.endCreateChild(); // xinterface
+ * conf.endCreateChild(); // buildpath
+ * </pre>
+ */
+public class Configurator {
+    public static class Macro {
+        private MacroDef macrodef;
+
+        private Map attValues = new HashMap();
+
+        private Map macroRecords = new HashMap();
+
+        public Macro(MacroDef def) {
+            macrodef = def;
+        }
+
+        public void defineAttribute(String attributeName, String value) {
+            if (macrodef.getAttribute(attributeName) == null) {
+                throw new IllegalArgumentException("undeclared attribute " + attributeName
+                        + " on macro " + macrodef.getName());
+            }
+            attValues.put(attributeName, value);
+        }
+
+        public MacroRecord recordCreateChild(String name) {
+            MacroRecord macroRecord = new MacroRecord(name);
+            List records = (List) macroRecords.get(name);
+            if (records == null) {
+                records = new ArrayList();
+                macroRecords.put(name, records);
+            }
+            records.add(macroRecord);
+            return macroRecord;
+        }
+
+        public Object play(Configurator conf) {
+            return macrodef.play(conf, attValues, macroRecords);
+        }
+
+    }
+
+    public static class Attribute {
+        private String name;
+
+        private String defaultValue;
+
+        public String getDefault() {
+            return defaultValue;
+        }
+
+        public void setDefault(String default1) {
+            defaultValue = default1;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public void setName(String name) {
+            this.name = name;
+        }
+    }
+
+    public static class Element {
+        private String name;
+
+        private boolean optional = false;
+
+        public String getName() {
+            return name;
+        }
+
+        public void setName(String name) {
+            this.name = name;
+        }
+
+        public boolean isOptional() {
+            return optional;
+        }
+
+        public void setOptional(boolean optional) {
+            this.optional = optional;
+        }
+    }
+
+    public static class MacroRecord {
+        private String name;
+
+        private Map attributes = new LinkedHashMap();
+
+        private List children = new ArrayList();
+
+        private Object object;
+
+        public MacroRecord(String name) {
+            this.name = name;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public void recordAttribute(String name, String value) {
+            attributes.put(name, value);
+        }
+
+        public MacroRecord recordChild(String name) {
+            MacroRecord child = new MacroRecord(name);
+            children.add(child);
+            return child;
+        }
+
+        public MacroRecord recordChild(String name, Object object) {
+            MacroRecord child = recordChild(name);
+            child.object = object;
+            return child;
+        }
+
+        public Map getAttributes() {
+            return attributes;
+        }
+
+        public List getChildren() {
+            return children;
+        }
+
+        public Object getObject() {
+            return object;
+        }
+    }
+
+    public static class MacroDef {
+        private String name;
+
+        private Map attributes = new HashMap();
+
+        private Map elements = new HashMap();
+
+        private MacroRecord macroRecord;
+
+        public MacroDef(String macroName) {
+            name = macroName;
+        }
+
+        public Attribute getAttribute(String attributeName) {
+            return (Attribute) attributes.get(attributeName);
+        }
+
+        public Object play(Configurator conf, Map attValues, Map macroRecords) {
+            for (Iterator iter = attributes.values().iterator(); iter.hasNext();) {
+                Attribute att = (Attribute) iter.next();
+                String val = (String) attValues.get(att.getName());
+                if (val == null) {
+                    if (att.getDefault() == null) {
+                        throw new IllegalArgumentException("attribute " + att.getName()
+                                + " is required in " + getName());
+                    } else {
+                        attValues.put(att.getName(), att.getDefault());
+                    }
+                }
+            }
+            return play(conf, macroRecord, attValues, macroRecords);
+        }
+
+        private Object play(Configurator conf, MacroRecord macroRecord, Map attValues,
+                Map childrenRecords) {
+            if (macroRecord.getObject() != null) {
+                // this is a recorded reference, we can add the referenced object directly
+                conf.addChild(macroRecord.getName(), macroRecord.getObject());
+                conf.endCreateChild();
+                return macroRecord.getObject();
+            }
+            conf.startCreateChild(macroRecord.getName());
+            Map attributes = macroRecord.getAttributes();
+            for (Iterator iter = attributes.keySet().iterator(); iter.hasNext();) {
+                String attName = (String) iter.next();
+                String attValue = replaceParam((String) attributes.get(attName), attValues);
+                conf.setAttribute(attName, attValue);
+            }
+            for (Iterator iter = macroRecord.getChildren().iterator(); iter.hasNext();) {
+                MacroRecord child = (MacroRecord) iter.next();
+                Element elt = (Element) elements.get(child.getName());
+                if (elt != null) {
+                    List elements = (List) childrenRecords.get(child.getName());
+                    if (elements != null) {
+                        for (Iterator iterator = elements.iterator(); iterator.hasNext();) {
+                            MacroRecord element = (MacroRecord) iterator.next();
+                            for (Iterator it2 = element.getChildren().iterator(); it2.hasNext();) {
+                                MacroRecord r = (MacroRecord) it2.next();
+                                play(conf, r, attValues, Collections.EMPTY_MAP);
+                            }
+                        }
+                    } else if (!elt.isOptional()) {
+                        throw new IllegalArgumentException(
+                                "non optional element is not specified: " + elt.getName()
+                                        + " in macro " + getName());
+                    }
+                    continue;
+                }
+                play(conf, child, attValues, childrenRecords);
+            }
+            return conf.endCreateChild();
+        }
+
+        private String replaceParam(String string, Map attValues) {
+            return IvyPatternHelper.substituteParams(string, attValues);
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public void addConfiguredAttribute(Attribute att) {
+            attributes.put(att.getName(), att);
+        }
+
+        public void addConfiguredElement(Element elt) {
+            elements.put(elt.getName(), elt);
+        }
+
+        public Macro createMacro() {
+            return new Macro(this);
+        }
+
+        public void addAttribute(String attName, String attDefaultValue) {
+            Attribute att = new Attribute();
+            att.setName(attName);
+            att.setDefault(attDefaultValue);
+            addConfiguredAttribute(att);
+        }
+
+        public void addElement(String elementName, boolean optional) {
+            Element elt = new Element();
+            elt.setName(elementName);
+            elt.setOptional(optional);
+            addConfiguredElement(elt);
+        }
+
+        public MacroRecord recordCreateChild(String name) {
+            macroRecord = new MacroRecord(name);
+            return macroRecord;
+        }
+    }
+
+    private static class ObjectDescriptor {
+        private Object obj;
+
+        private String objName;
+
+        private Map createMethods = new HashMap();
+
+        private Map addMethods = new HashMap();
+
+        private Map addConfiguredMethods = new HashMap();
+
+        private Map setMethods = new HashMap();
+
+        private Map typeAddMethods = new HashMap();
+
+        private Map typeAddConfiguredMethods = new HashMap();
+
+        public ObjectDescriptor(Object object, String objName) {
+            obj = object;
+            this.objName = objName;
+            Method[] methods = object.getClass().getMethods();
+            for (int i = 0; i < methods.length; i++) {
+                Method m = methods[i];
+                if (m.getName().startsWith("create") && m.getParameterTypes().length == 0
+                        && !Void.TYPE.equals(m.getReturnType())) {
+                    String name = StringUtils
+                            .uncapitalize(m.getName().substring("create".length()));
+                    if (name.length() == 0) {
+                        continue;
+                    }
+                    addCreateMethod(name, m);
+                } else if (m.getName().startsWith("addConfigured")
+                        && m.getParameterTypes().length == 1 && Void.TYPE.equals(m.getReturnType())) {
+                    String name = StringUtils.uncapitalize(m.getName().substring(
+                        "addConfigured".length()));
+                    if (name.length() == 0) {
+                        addAddConfiguredMethod(m);
+                    }
+                    addAddConfiguredMethod(name, m);
+                } else if (m.getName().startsWith("add")
+                        && !m.getName().startsWith("addConfigured")
+                        && m.getParameterTypes().length == 1 && Void.TYPE.equals(m.getReturnType())) {
+                    String name = StringUtils.uncapitalize(m.getName().substring("add".length()));
+                    if (name.length() == 0) {
+                        addAddMethod(m);
+                    }
+                    addAddMethod(name, m);
+                } else if (m.getName().startsWith("set") && m.getParameterTypes().length == 1
+                        && Void.TYPE.equals(m.getReturnType())) {
+                    String name = StringUtils.uncapitalize(m.getName().substring("set".length()));
+                    if (name.length() == 0) {
+                        continue;
+                    }
+                    addSetMethod(name, m);
+                }
+            }
+        }
+
+        public void addCreateMethod(String name, Method m) {
+            createMethods.put(name, m);
+        }
+
+        public void addAddMethod(String name, Method m) {
+            addMethods.put(name, m);
+        }
+
+        public void addAddConfiguredMethod(String name, Method m) {
+            addConfiguredMethods.put(name, m);
+        }
+
+        private void addAddMethod(Method m) {
+            typeAddMethods.put(m.getParameterTypes()[0], m);
+        }
+
+        private void addAddConfiguredMethod(Method m) {
+            typeAddConfiguredMethods.put(m.getParameterTypes()[0], m);
+        }
+
+        public void addSetMethod(String name, Method m) {
+            Method current = (Method) setMethods.get(name);
+            if (current != null && current.getParameterTypes()[0] == String.class) {
+                // setter methods with String attribute take precedence
+                return;
+            }
+            setMethods.put(name, m);
+        }
+
+        public Object getObject() {
+            return obj;
+        }
+
+        public Method getCreateMethod(String name) {
+            return (Method) createMethods.get(name);
+        }
+
+        public Method getAddMethod(String name) {
+            return (Method) addMethods.get(name);
+        }
+
+        public Method getAddConfiguredMethod(String name) {
+            return (Method) addConfiguredMethods.get(name);
+        }
+
+        public Method getAddMethod(Class type) {
+            return getTypeMatchingMethod(type, typeAddMethods);
+        }
+
+        public Method getAddConfiguredMethod(Class type) {
+            return getTypeMatchingMethod(type, typeAddConfiguredMethods);
+        }
+
+        private Method getTypeMatchingMethod(Class type, Map typeMethods) {
+            Method m = (Method) typeMethods.get(type);
+            if (m != null) {
+                return m;
+            }
+            for (Iterator iter = typeMethods.keySet().iterator(); iter.hasNext();) {
+                Class clss = (Class) iter.next();
+                if (clss.isAssignableFrom(type)) {
+                    return (Method) typeMethods.get(clss);
+                }
+            }
+            return null;
+        }
+
+        public Method getSetMethod(String name) {
+            return (Method) setMethods.get(name);
+        }
+
+        public String getObjectName() {
+            return objName;
+        }
+    }
+
+    private FileResolver fileResolver = FileResolver.DEFAULT;
+
+    private Map typedefs = new HashMap();
+
+    private Map macrodefs = new HashMap();
+
+    // stack in which the top is current configured object descriptor
+    private Stack objectStack = new Stack();
+
+    private static final List TRUE_VALUES = Arrays.asList(new String[] {"true", "yes", "on"});
+
+    public void typeDef(String name, String className) throws ClassNotFoundException {
+        typeDef(name, Class.forName(className));
+    }
+
+    public void typeDef(String name, Class clazz) {
+        typedefs.put(name, clazz);
+    }
+
+    public void setRoot(Object root) {
+        if (root == null) {
+            throw new NullPointerException();
+        }
+        objectStack.clear();
+        setCurrent(root, null);
+    }
+
+    public void clear() {
+        objectStack.clear();
+    }
+
+    private void setCurrent(Object object, String name) {
+        objectStack.push(new ObjectDescriptor(object, name));
+    }
+
+    public Object startCreateChild(String name) {
+        if (objectStack.isEmpty()) {
+            throw new IllegalStateException("set root before creating child");
+        }
+        ObjectDescriptor parentOD = (ObjectDescriptor) objectStack.peek();
+        Object parent = parentOD.getObject();
+        if (parent instanceof MacroDef) {
+            if (!"attribute".equals(name) && !"element".equals(name)) {
+                MacroRecord record = ((MacroDef) parent).recordCreateChild(name);
+                setCurrent(record, name);
+                return record;
+            }
+        }
+        if (parent instanceof Macro) {
+            MacroRecord record = ((Macro) parent).recordCreateChild(name);
+            setCurrent(record, name);
+            return record;
+        }
+        if (parent instanceof MacroRecord) {
+            MacroRecord record = ((MacroRecord) parent).recordChild(name);
+            setCurrent(record, name);
+            return record;
+        }
+        Object child = null;
+        MacroDef macrodef = (MacroDef) macrodefs.get(name);
+        if (macrodef != null) {
+            Macro macro = macrodef.createMacro();
+            setCurrent(macro, name);
+            return macro;
+        }
+        Class childClass = (Class) typedefs.get(name);
+        Method addChild = null;
+        try {
+            if (childClass != null) {
+                return addChild(parentOD, childClass, name, null);
+            } else {
+                addChild = parentOD.getCreateMethod(name);
+                if (addChild != null) {
+                    child = addChild.invoke(parent, new Object[0]);
+                    setCurrent(child, name);
+                    return child;
+                }
+                addChild = parentOD.getAddMethod(name);
+                if (addChild != null) {
+                    childClass = addChild.getParameterTypes()[0];
+                    child = childClass.newInstance();
+                    addChild.invoke(parent, new Object[] {child});
+                    setCurrent(child, name);
+                    return child;
+                }
+                addChild = parentOD.getAddConfiguredMethod(name);
+                if (addChild != null) {
+                    childClass = addChild.getParameterTypes()[0];
+                    if (Map.class == childClass) {
+                        child = new HashMap();
+                    } else {
+                        child = childClass.newInstance();
+                    }
+                    setCurrent(child, name);
+                    return child;
+                }
+            }
+        } catch (InstantiationException ex) {
+            throw new IllegalArgumentException("no default constructor on " + childClass
+                    + " for adding " + name + " on " + parent.getClass());
+        } catch (Exception ex) {
+            IllegalArgumentException iae = new IllegalArgumentException("bad method found for "
+                    + name + " on " + parent.getClass());
+            iae.initCause(ex);
+            throw iae;
+        }
+        throw new IllegalArgumentException("no appropriate method found for adding " + name
+                + " on " + parent.getClass());
+    }
+
+    public void addChild(String name, Object child) {
+        if (objectStack.isEmpty()) {
+            throw new IllegalStateException("set root before creating child");
+        }
+        ObjectDescriptor parentOD = (ObjectDescriptor) objectStack.peek();
+        try {
+            addChild(parentOD, child.getClass(), name, child);
+        } catch (InstantiationException ex) {
+            throw new IllegalArgumentException("no default constructor on " + child.getClass()
+                    + " for adding " + name + " on " + parentOD.getObject().getClass());
+        } catch (Exception ex) {
+            IllegalArgumentException iae = new IllegalArgumentException("bad method found for "
+                    + name + " on " + parentOD.getObject().getClass());
+            iae.initCause(ex);
+            throw iae;
+        }
+    }
+
+    private Object addChild(ObjectDescriptor parentOD, Class childClass, String name, Object child)
+            throws InstantiationException, IllegalAccessException, InvocationTargetException {
+        Object parent = parentOD.getObject();
+        if (parent instanceof MacroRecord) {
+            MacroRecord record = (MacroRecord) parent;
+            MacroRecord recordChild = record.recordChild(name, child);
+            setCurrent(recordChild, name);
+            return recordChild;
+        }
+        Method addChild = parentOD.getAddMethod(childClass);
+        if (addChild != null) {
+            if (child == null) {
+                child = childClass.newInstance();
+            }
+            addChild.invoke(parent, new Object[] {child});
+            setCurrent(child, name);
+            return child;
+        }
+        addChild = parentOD.getAddConfiguredMethod(childClass);
+        if (addChild != null) {
+            if (child == null) {
+                if (Map.class == childClass) {
+                    child = new HashMap();
+                } else {
+                    child = childClass.newInstance();
+                }
+            }
+            setCurrent(child, name);
+            return child;
+        }
+        throw new IllegalArgumentException("no appropriate method found for adding " + name
+                + " on " + parent.getClass());
+    }
+
+    public boolean isTopLevelMacroRecord() {
+        if (objectStack.isEmpty()) {
+            return false;
+        }
+        ObjectDescriptor od = (ObjectDescriptor) objectStack.peek();
+        return (od.getObject() instanceof MacroDef);
+    }
+
+    public void setAttribute(String attributeName, String value) {
+        if (objectStack.isEmpty()) {
+            throw new IllegalStateException("set root before setting attribute");
+        }
+        ObjectDescriptor od = (ObjectDescriptor) objectStack.peek();
+        if (od.getObject() instanceof Macro) {
+            ((Macro) od.getObject()).defineAttribute(attributeName, value);
+            return;
+        }
+        if (od.getObject() instanceof MacroRecord) {
+            ((MacroRecord) od.getObject()).recordAttribute(attributeName, value);
+            return;
+        }
+        Method m = od.getSetMethod(attributeName);
+        if (m == null) {
+            if (od.getObject() instanceof Map) {
+                ((Map) od.getObject()).put(attributeName, value);
+                return;
+            }
+            throw new IllegalArgumentException("no set method found for " + attributeName + " on "
+                    + od.getObject().getClass());
+        }
+        Object convertedValue = null;
+        Class paramClass = m.getParameterTypes()[0];
+        try {
+            if (paramClass.equals(String.class)) {
+                convertedValue = value;
+            } else if (paramClass.equals(Boolean.class) || paramClass.equals(boolean.class)) {
+                convertedValue = Boolean.valueOf(TRUE_VALUES.contains(value));
+            } else if (paramClass.equals(Character.class) || paramClass.equals(char.class)) {
+                convertedValue = new Character(value.length() > 0 ? value.charAt(0) : ' ');
+            } else if (paramClass.equals(Short.class) || paramClass.equals(short.class)) {
+                convertedValue = Short.valueOf(value);
+            } else if (paramClass.equals(Integer.class) || paramClass.equals(int.class)) {
+                convertedValue = Integer.valueOf(value);
+            } else if (paramClass.equals(Long.class) || paramClass.equals(long.class)) {
+                convertedValue = Long.valueOf(value);
+            } else if (paramClass.equals(Class.class)) {
+                convertedValue = Class.forName(value);
+            } else if (paramClass.equals(File.class)) {
+                convertedValue = fileResolver.resolveFile(value, od.getObjectName() + "."
+                        + attributeName);
+            } else {
+                convertedValue = paramClass.getConstructor(new Class[] {String.class}).newInstance(
+                    new Object[] {value});
+            }
+        } catch (Exception ex) {
+            IllegalArgumentException iae = new IllegalArgumentException("impossible to convert "
+                    + value + " to " + paramClass + " for setting " + attributeName + " on "
+                    + od.getObject().getClass() + ": " + ex.getMessage());
+            iae.initCause(ex);
+            throw iae;
+        }
+        try {
+            m.invoke(od.getObject(), new Object[] {convertedValue});
+        } catch (Exception ex) {
+            IllegalArgumentException iae = new IllegalArgumentException("impossible to set "
+                    + attributeName + " to " + convertedValue + " on " + od.getObject().getClass());
+            iae.initCause(ex);
+            throw iae;
+        }
+    }
+
+    public void addText(String text) {
+        if (objectStack.isEmpty()) {
+            throw new IllegalStateException("set root before adding text");
+        }
+        ObjectDescriptor od = (ObjectDescriptor) objectStack.peek();
+        try {
+            od.getObject().getClass().getMethod("addText", new Class[] {String.class})
+                    .invoke(od.getObject(), new Object[] {text});
+        } catch (Exception ex) {
+            IllegalArgumentException iae = new IllegalArgumentException(
+                    "impossible to add text on " + od.getObject().getClass());
+            iae.initCause(ex);
+            throw iae;
+        }
+    }
+
+    /**
+     * @return the finished child
+     */
+    public Object endCreateChild() {
+        if (objectStack.isEmpty()) {
+            throw new IllegalStateException("set root before ending child");
+        }
+        ObjectDescriptor od = (ObjectDescriptor) objectStack.pop();
+        if (objectStack.isEmpty()) {
+            objectStack.push(od); // back to previous state
+            throw new IllegalStateException("cannot end root");
+        }
+        if (od.getObject() instanceof Macro) {
+            return ((Macro) od.getObject()).play(this);
+        }
+        ObjectDescriptor parentOD = (ObjectDescriptor) objectStack.peek();
+        String name = od.getObjectName();
+        Class childClass = (Class) typedefs.get(name);
+        Method m = null;
+        if (childClass != null) {
+            m = parentOD.getAddConfiguredMethod(childClass);
+        } else {
+            m = parentOD.getAddConfiguredMethod(name);
+        }
+        try {
+            if (m != null) {
+                m.invoke(parentOD.getObject(), new Object[] {od.getObject()});
+            }
+            return od.getObject();
+        } catch (Exception ex) {
+            IllegalArgumentException iae = new IllegalArgumentException(
+                    "impossible to add configured child for " + name + " on "
+                            + parentOD.getObject().getClass() + ": "
+                            + StringUtils.getErrorMessage(ex));
+            iae.initCause(ex);
+            throw iae;
+        }
+    }
+
+    public Object getCurrent() {
+        return objectStack.isEmpty() ? null : ((ObjectDescriptor) objectStack.peek()).getObject();
+    }
+
+    public int getDepth() {
+        return objectStack.size();
+    }
+
+    public MacroDef startMacroDef(String macroName) {
+        MacroDef macroDef = new MacroDef(macroName);
+        setCurrent(macroDef, macroName);
+        return macroDef;
+    }
+
+    public void addMacroAttribute(String attName, String attDefaultValue) {
+        ((MacroDef) getCurrent()).addAttribute(attName, attDefaultValue);
+    }
+
+    public void addMacroElement(String elementName, boolean optional) {
+        ((MacroDef) getCurrent()).addElement(elementName, optional);
+    }
+
+    public void endMacroDef() {
+        addConfiguredMacrodef(((MacroDef) getCurrent()));
+        objectStack.pop();
+    }
+
+    public void addConfiguredMacrodef(MacroDef macrodef) {
+        macrodefs.put(macrodef.getName(), macrodef);
+    }
+
+    public Class getTypeDef(String name) {
+        return (Class) typedefs.get(name);
+    }
+
+    public FileResolver getFileResolver() {
+        return fileResolver;
+    }
+
+    public void setFileResolver(FileResolver fileResolver) {
+        Checks.checkNotNull(fileResolver, "fileResolver");
+        this.fileResolver = fileResolver;
+    }
+}
diff --git a/src/java/org/apache/ivy/util/ContextualSAXHandler.java b/src/java/org/apache/ivy/util/ContextualSAXHandler.java
new file mode 100644
index 0000000..daf85c0
--- /dev/null
+++ b/src/java/org/apache/ivy/util/ContextualSAXHandler.java
@@ -0,0 +1,64 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util;
+
+import java.util.Iterator;
+import java.util.Stack;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+public class ContextualSAXHandler extends DefaultHandler {
+
+    private Stack contextStack = new Stack();
+
+    private StringBuffer buffer = new StringBuffer();
+
+    public void characters(char[] ch, int start, int length) throws SAXException {
+        buffer.append(ch, start, length);
+    }
+
+    public void startElement(String uri, String localName, String qName, Attributes attributes)
+            throws SAXException {
+        contextStack.push(qName);
+        buffer.setLength(0);
+    }
+
+    public void endElement(String uri, String localName, String qName) throws SAXException {
+        contextStack.pop();
+        buffer.setLength(0);
+    }
+
+    protected String getContext() {
+        StringBuffer buf = new StringBuffer();
+        for (Iterator iter = contextStack.iterator(); iter.hasNext();) {
+            String ctx = (String) iter.next();
+            buf.append(ctx).append("/");
+        }
+        if (buf.length() > 0) {
+            buf.setLength(buf.length() - 1);
+        }
+        return buf.toString();
+    }
+
+    protected String getText() {
+        return buffer.toString();
+    }
+
+}
diff --git a/src/java/org/apache/ivy/util/CopyProgressEvent.java b/src/java/org/apache/ivy/util/CopyProgressEvent.java
new file mode 100644
index 0000000..c71e116
--- /dev/null
+++ b/src/java/org/apache/ivy/util/CopyProgressEvent.java
@@ -0,0 +1,60 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util;
+
+/**
+ * Event reporting a stream copy progression
+ */
+public class CopyProgressEvent {
+    private long totalReadBytes;
+
+    private byte[] buffer;
+
+    private int readBytes;
+
+    public CopyProgressEvent() {
+    }
+
+    public CopyProgressEvent(byte[] buffer, int read, long total) {
+        update(buffer, read, total);
+    }
+
+    public CopyProgressEvent(byte[] buffer, long total) {
+        update(buffer, 0, total);
+    }
+
+    protected CopyProgressEvent update(byte[] buffer, int read, long total) {
+        this.buffer = buffer;
+        this.readBytes = read;
+        this.totalReadBytes = total;
+        return this;
+    }
+
+    public long getTotalReadBytes() {
+        return totalReadBytes;
+    }
+
+    public byte[] getBuffer() {
+        return buffer;
+    }
+
+    public int getReadBytes() {
+        return readBytes;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/util/CopyProgressListener.java b/src/java/org/apache/ivy/util/CopyProgressListener.java
new file mode 100644
index 0000000..2dbaa2e
--- /dev/null
+++ b/src/java/org/apache/ivy/util/CopyProgressListener.java
@@ -0,0 +1,29 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util;
+
+/**
+ * Listen to copy progression
+ */
+public interface CopyProgressListener {
+    void start(CopyProgressEvent evt);
+
+    void progress(CopyProgressEvent evt);
+
+    void end(CopyProgressEvent evt);
+}
diff --git a/src/java/org/apache/ivy/util/Credentials.java b/src/java/org/apache/ivy/util/Credentials.java
new file mode 100644
index 0000000..682e35a
--- /dev/null
+++ b/src/java/org/apache/ivy/util/Credentials.java
@@ -0,0 +1,102 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util;
+
+/**
+ * 
+ */
+public class Credentials {
+    private String realm;
+
+    private String host;
+
+    private String userName;
+
+    private String passwd;
+
+    public Credentials(String realm, String host, String userName, String passwd) {
+        this.realm = realm;
+        this.host = host;
+        this.userName = userName;
+        this.passwd = passwd;
+    }
+
+    public String getHost() {
+        return host;
+    }
+
+    public String getPasswd() {
+        return passwd;
+    }
+
+    public String getRealm() {
+        return realm;
+    }
+
+    public String getUserName() {
+        return userName;
+    }
+
+    public static String buildKey(String realm, String host) {
+        if (realm == null || "".equals(realm.trim())) {
+            return host;
+        } else {
+            return realm + "@" + host;
+        }
+    }
+
+    /**
+     * Return a string that can be used for debug purpose. It contains only stars for each password
+     * character.
+     */
+    public String toString() {
+        return getKey() + " " + getUserName() + "/" + getPasswdAsStars();
+    }
+
+    private String getPasswdAsStars() {
+        if (passwd == null) {
+            return null;
+        }
+        StringBuffer sb = new StringBuffer();
+        for (int i = passwd.length(); i > 0; i--) {
+            sb.append('*');
+        }
+        return sb.toString();
+    }
+
+    public boolean equals(Object o) {
+        if (o == null) {
+            return false;
+        }
+
+        if (o instanceof Credentials) {
+            Credentials c = (Credentials) o;
+            return getKey().equals(c.getKey());
+        }
+
+        return false;
+    }
+
+    public int hashCode() {
+        return getKey().hashCode();
+    }
+
+    public String getKey() {
+        return buildKey(realm, host);
+    }
+}
diff --git a/src/java/org/apache/ivy/util/CredentialsUtil.java b/src/java/org/apache/ivy/util/CredentialsUtil.java
new file mode 100644
index 0000000..68a0ba8
--- /dev/null
+++ b/src/java/org/apache/ivy/util/CredentialsUtil.java
@@ -0,0 +1,167 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util;
+
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Properties;
+
+import javax.swing.ImageIcon;
+import javax.swing.JCheckBox;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPasswordField;
+import javax.swing.JTextField;
+
+import org.apache.ivy.Ivy;
+
+public final class CredentialsUtil {
+
+    private static final class CredentialPanel extends JPanel {
+        private static final int FIELD_LENGTH = 20;
+
+        private JTextField userNameField = new JTextField(FIELD_LENGTH);
+
+        private JTextField passwordField = new JPasswordField(FIELD_LENGTH);
+
+        private JCheckBox rememberDataCB = new JCheckBox("remember my information");
+
+        CredentialPanel(Credentials credentials, File passfile) {
+            GridBagLayout layout = new GridBagLayout();
+            setLayout(layout);
+            GridBagConstraints c = new GridBagConstraints();
+            c.insets = new Insets(2, 2, 2, 2);
+
+            c.gridx = 1;
+            c.gridheight = 1;
+            c.gridwidth = 2;
+            String prompt = credentials.getRealm() != null ? "Enter username and password for \""
+                    + credentials.getRealm() + "\" at " + credentials.getHost()
+                    : "Enter username and password for " + credentials.getHost();
+            add(new JLabel(prompt), c);
+
+            c.gridy = 1;
+            c.gridwidth = 1;
+
+            add(new JLabel("username: "), c);
+            c.gridx = 2;
+            add(userNameField, c);
+            c.gridx = 1;
+            c.gridy++;
+
+            if (credentials.getUserName() != null) {
+                userNameField.setText(credentials.getUserName());
+            }
+
+            if (credentials.getPasswd() == null) {
+                add(new JLabel("passwd:  "), c);
+                c.gridx = 2;
+                add(passwordField, c);
+                c.gridx = 1;
+                c.gridy++;
+            } else {
+                passwordField.setText(credentials.getPasswd());
+            }
+
+            if (passfile != null) {
+                c.gridwidth = 2;
+                add(rememberDataCB, c);
+                c.gridy++;
+            }
+            c.gridwidth = 2;
+            add(new JLabel(), c); // spacer
+
+        }
+    }
+
+    public static Credentials promptCredentials(Credentials c, File passfile) {
+        c = loadPassfile(c, passfile);
+        if (c.getUserName() != null && c.getPasswd() != null) {
+            return c;
+        }
+        CredentialPanel credentialPanel = new CredentialPanel(c, passfile);
+        if (JOptionPane.showOptionDialog(null, credentialPanel, c.getHost() + " credentials",
+            JOptionPane.OK_CANCEL_OPTION, 0, new ImageIcon(Ivy.class.getResource("logo.png")),
+            null, new Integer(JOptionPane.OK_OPTION)) == JOptionPane.OK_OPTION) {
+            String username = credentialPanel.userNameField.getText();
+            String passwd = credentialPanel.passwordField.getText();
+            if (credentialPanel.rememberDataCB.isSelected()) {
+                Properties props = new EncrytedProperties();
+                props.setProperty("username", username);
+                props.setProperty("passwd", passwd);
+                FileOutputStream fos = null;
+                try {
+                    fos = new FileOutputStream(passfile);
+                    props.store(fos, "");
+                } catch (Exception e) {
+                    Message.warn("error occurred while saving password file " + passfile, e);
+                } finally {
+                    if (fos != null) {
+                        try {
+                            fos.close();
+                        } catch (Exception e) {
+                            // ignored
+                        }
+                    }
+                }
+            }
+            c = new Credentials(c.getRealm(), c.getHost(), username, passwd);
+        }
+        return c;
+    }
+
+    public static Credentials loadPassfile(Credentials c, File passfile) {
+        if (passfile != null && passfile.exists()) {
+            Properties props = new EncrytedProperties();
+            FileInputStream fis = null;
+            try {
+                fis = new FileInputStream(passfile);
+                props.load(fis);
+                String username = c.getUserName();
+                String passwd = c.getPasswd();
+                if (username == null) {
+                    username = props.getProperty("username");
+                }
+                if (passwd == null) {
+                    passwd = props.getProperty("passwd");
+                }
+                return new Credentials(c.getRealm(), c.getHost(), username, passwd);
+            } catch (IOException e) {
+                Message.warn("error occurred while loading password file " + passfile, e);
+            } finally {
+                if (fis != null) {
+                    try {
+                        fis.close();
+                    } catch (IOException e) {
+                        // ignored
+                    }
+                }
+            }
+        }
+        return c;
+    }
+
+    private CredentialsUtil() {
+    }
+}
diff --git a/src/java/org/apache/ivy/util/DateUtil.java b/src/java/org/apache/ivy/util/DateUtil.java
new file mode 100644
index 0000000..fe66e1d
--- /dev/null
+++ b/src/java/org/apache/ivy/util/DateUtil.java
@@ -0,0 +1,42 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+public class DateUtil {
+
+    private DateUtil() {
+        // Utility class
+    }
+
+    public static final String DATE_FORMAT_PATTERN = "yyyyMMddHHmmss";
+
+    public static String format(Date date) {
+        SimpleDateFormat format = new SimpleDateFormat(DATE_FORMAT_PATTERN);
+        return format.format(date);
+    }
+
+    public static Date parse(String date) throws ParseException {
+        SimpleDateFormat format = new SimpleDateFormat(DATE_FORMAT_PATTERN);
+        return format.parse(date);
+    }
+
+}
diff --git a/src/java/org/apache/ivy/util/DefaultMessageLogger.java b/src/java/org/apache/ivy/util/DefaultMessageLogger.java
new file mode 100644
index 0000000..7a88a36
--- /dev/null
+++ b/src/java/org/apache/ivy/util/DefaultMessageLogger.java
@@ -0,0 +1,51 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util;
+
+public class DefaultMessageLogger extends AbstractMessageLogger {
+    private int level = Message.MSG_INFO;
+
+    /**
+     * @param level
+     */
+    public DefaultMessageLogger(int level) {
+        this.level = level;
+    }
+
+    public void log(String msg, int level) {
+        if (level <= this.level) {
+            System.out.println(msg);
+        }
+    }
+
+    public void rawlog(String msg, int level) {
+        log(msg, level);
+    }
+
+    public void doProgress() {
+        System.out.print(".");
+    }
+
+    public void doEndProgress(String msg) {
+        System.out.println(msg);
+    }
+
+    public int getLevel() {
+        return level;
+    }
+}
diff --git a/src/java/org/apache/ivy/util/EncrytedProperties.java b/src/java/org/apache/ivy/util/EncrytedProperties.java
new file mode 100644
index 0000000..8d38fef
--- /dev/null
+++ b/src/java/org/apache/ivy/util/EncrytedProperties.java
@@ -0,0 +1,65 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Properties;
+
+/**
+ * An implementation of Properties which stores the values encrypted. The use is transparent from
+ * the user point of view (use as any Properties instance), except that get, put and putAll do not
+ * handle encryption/decryption. This means that get returns the encrypted value, while put and
+ * putAll puts given values without encrypting them. It this thus recommended to void using them,
+ * use setProperty and getProperty instead.
+ */
+public class EncrytedProperties extends Properties {
+
+    public EncrytedProperties() {
+        super();
+    }
+
+    public synchronized Object setProperty(String key, String value) {
+        return StringUtils.decrypt((String) super.setProperty(key, StringUtils.encrypt(value)));
+    }
+
+    public String getProperty(String key) {
+        return StringUtils.decrypt(super.getProperty(key));
+    }
+
+    public String getProperty(String key, String defaultValue) {
+        return StringUtils.decrypt(super.getProperty(key, StringUtils.encrypt(defaultValue)));
+    }
+
+    public boolean containsValue(Object value) {
+        return super.containsValue(StringUtils.encrypt((String) value));
+    }
+
+    public synchronized boolean contains(Object value) {
+        return super.contains(StringUtils.encrypt((String) value));
+    }
+
+    public Collection values() {
+        List ret = new ArrayList(super.values());
+        for (int i = 0; i < ret.size(); i++) {
+            ret.set(i, StringUtils.decrypt((String) ret.get(i)));
+        }
+        return ret;
+    }
+}
diff --git a/src/java/org/apache/ivy/util/FileResolver.java b/src/java/org/apache/ivy/util/FileResolver.java
new file mode 100644
index 0000000..2827519
--- /dev/null
+++ b/src/java/org/apache/ivy/util/FileResolver.java
@@ -0,0 +1,44 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util;
+
+import java.io.File;
+
+public interface FileResolver {
+    public static final FileResolver DEFAULT = new FileResolver() {
+        public File resolveFile(String path, String filename) {
+            return new File(path);
+        }
+    };
+
+    /**
+     * Return the canonical form of a path, or raise an {@link IllegalArgumentException} if the path
+     * is not valid for this {@link FileResolver}.
+     * <p>
+     * 
+     * @param path
+     *            The path of the file to resolve. Must not be <code>null</code>.
+     * @param fileName
+     *            The name of the file to resolve. This is used only for exception message if any.
+     *            Must not be <code>null</code>.
+     * 
+     * @return the resolved File.
+     * 
+     */
+    File resolveFile(String path, String filename);
+}
diff --git a/src/java/org/apache/ivy/util/FileUtil.java b/src/java/org/apache/ivy/util/FileUtil.java
new file mode 100644
index 0000000..7a7be31
--- /dev/null
+++ b/src/java/org/apache/ivy/util/FileUtil.java
@@ -0,0 +1,731 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Stack;
+import java.util.StringTokenizer;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Pack200;
+import java.util.jar.Pack200.Unpacker;
+import java.util.regex.Pattern;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.ZipInputStream;
+
+import org.apache.ivy.util.url.URLHandlerRegistry;
+
+/**
+ * Utility class used to deal with file related operations, like copy, full reading, symlink, ...
+ */
+public final class FileUtil {
+
+    private FileUtil() {
+        // Utility class
+    }
+
+    // according to tests by users, 64kB seems to be a good value for the buffer used during copy
+    // further improvements could be obtained using NIO API
+    private static final int BUFFER_SIZE = 64 * 1024;
+
+    private static final byte[] EMPTY_BUFFER = new byte[0];
+
+    private static final Pattern ALLOWED_PATH_PATTERN = Pattern.compile("[\\w-./\\\\:~ %\\(\\)]+");
+
+    public static void symlinkInMass(Map/* <File, File> */destToSrcMap, boolean overwrite)
+            throws IOException {
+
+        // This pattern could be more forgiving if somebody wanted it to be...
+        // ...but this should satisfy 99+% of all needs, without letting unsafe operations be done.
+        // If you paths is not supported, you then skip this mass option.
+        // NOTE: A space inside the path is allowed (I can't control other programmers who like them
+        // in their working directory names)...
+        // but trailing spaces on file names will be checked otherwise and refused.
+        try {
+            StringBuffer sb = new StringBuffer();
+
+            Iterator keyItr = destToSrcMap.entrySet().iterator();
+            while (keyItr.hasNext()) {
+                Entry/* <File, File> */entry = (Entry) keyItr.next();
+                File destFile = (File) entry.getKey();
+                File srcFile = (File) entry.getValue();
+                if (!ALLOWED_PATH_PATTERN.matcher(srcFile.getAbsolutePath()).matches()) {
+                    throw new IOException("Unsafe file to 'mass' symlink: '"
+                            + srcFile.getAbsolutePath() + "'");
+                }
+                if (!ALLOWED_PATH_PATTERN.matcher(destFile.getAbsolutePath()).matches()) {
+                    throw new IOException("Unsafe file to 'mass' symlink to: '"
+                            + destFile.getAbsolutePath() + "'");
+                }
+
+                // Add to our buffer of commands
+                sb.append("ln -s -f \"" + srcFile.getAbsolutePath() + "\"  \""
+                        + destFile.getAbsolutePath() + "\";");
+                if (keyItr.hasNext()) {
+                    sb.append("\n");
+                }
+            }
+
+            String commands = sb.toString();
+            // Run the buffer of commands we have built.
+            Runtime runtime = Runtime.getRuntime();
+            Message.verbose("executing \"sh\" of:\n\t" + commands.replaceAll("\n", "\n\t"));
+            Process process = runtime.exec("sh");
+            OutputStream os = process.getOutputStream();
+            os.write(commands.getBytes("UTF-8"));
+            os.flush();
+            os.close();
+
+            if (process.waitFor() != 0) {
+                InputStream errorStream = process.getErrorStream();
+                InputStreamReader isr = new InputStreamReader(errorStream);
+                BufferedReader br = new BufferedReader(isr);
+
+                StringBuffer error = new StringBuffer();
+                String line;
+                while ((line = br.readLine()) != null) {
+                    error.append(line);
+                    error.append('\n');
+                }
+
+                throw new IOException("error running ln commands with 'sh':\n" + error);
+            }
+        } catch (InterruptedException x) {
+            Thread.currentThread().interrupt();
+        }
+    }
+
+    public static void symlink(File src, File dest, CopyProgressListener l, boolean overwrite)
+            throws IOException {
+        if (!prepareCopy(src, dest, overwrite)) {
+            return;
+        }
+        try {
+            Runtime runtime = Runtime.getRuntime();
+            Message.verbose("executing 'ln -s -f " + src.getAbsolutePath() + " " + dest.getPath()
+                    + "'");
+            Process process = runtime.exec(new String[] {"ln", "-s", "-f", src.getAbsolutePath(),
+                    dest.getPath()});
+
+            if (process.waitFor() != 0) {
+                InputStream errorStream = process.getErrorStream();
+                InputStreamReader isr = new InputStreamReader(errorStream);
+                BufferedReader br = new BufferedReader(isr);
+
+                StringBuffer error = new StringBuffer();
+                String line;
+                while ((line = br.readLine()) != null) {
+                    error.append(line);
+                    error.append('\n');
+                }
+
+                throw new IOException("error symlinking " + src + " to " + dest + ":\n" + error);
+            }
+
+            // check if the creation of the symbolic link was successful
+            if (!dest.exists()) {
+                throw new IOException("error symlinking: " + dest + " doesn't exists");
+            }
+
+            // check if the result is a true symbolic link
+            if (dest.getAbsolutePath().equals(dest.getCanonicalPath())) {
+                dest.delete(); // just make sure we do delete the invalid symlink!
+                throw new IOException("error symlinking: " + dest + " isn't a symlink");
+            }
+        } catch (IOException e) {
+            Message.verbose("symlink failed; falling back to copy", e);
+            copy(src, dest, l, overwrite);
+        } catch (InterruptedException x) {
+            Thread.currentThread().interrupt();
+        }
+    }
+
+    public static boolean copy(File src, File dest, CopyProgressListener l) throws IOException {
+        return copy(src, dest, l, false);
+    }
+
+    public static boolean prepareCopy(File src, File dest, boolean overwrite) throws IOException {
+        if (src.isDirectory()) {
+            if (dest.exists()) {
+                if (!dest.isDirectory()) {
+                    throw new IOException("impossible to copy: destination is not a directory: "
+                            + dest);
+                }
+            } else {
+                dest.mkdirs();
+            }
+            return true;
+        }
+        // else it is a file copy
+        if (dest.exists()) {
+            if (!dest.isFile()) {
+                throw new IOException("impossible to copy: destination is not a file: " + dest);
+            }
+            if (overwrite) {
+                if (!dest.canWrite()) {
+                    dest.delete();
+                } // if dest is writable, the copy will overwrite it without requiring a delete
+            } else {
+                Message.verbose(dest + " already exists, nothing done");
+                return false;
+            }
+        }
+        if (dest.getParentFile() != null) {
+            dest.getParentFile().mkdirs();
+        }
+        return true;
+    }
+
+    public static boolean copy(File src, File dest, CopyProgressListener l, boolean overwrite)
+            throws IOException {
+        if (!prepareCopy(src, dest, overwrite)) {
+            return false;
+        }
+        if (src.isDirectory()) {
+            return deepCopy(src, dest, l, overwrite);
+        }
+        // else it is a file copy
+        copy(new FileInputStream(src), dest, l);
+        long srcLen = src.length();
+        long destLen = dest.length();
+        if (srcLen != destLen) {
+            dest.delete();
+            throw new IOException("size of source file " + src.toString() + "(" + srcLen
+                    + ") differs from size of dest file " + dest.toString() + "(" + destLen
+                    + ") - please retry");
+        }
+        dest.setLastModified(src.lastModified());
+        return true;
+    }
+
+    public static boolean deepCopy(File src, File dest, CopyProgressListener l, boolean overwrite)
+            throws IOException {
+        // the list of files which already exist in the destination folder
+        List/* <File> */existingChild = Collections.EMPTY_LIST;
+        if (dest.exists()) {
+            if (!dest.isDirectory()) {
+                // not expected type, remove
+                dest.delete();
+                // and create a folder
+                dest.mkdirs();
+                dest.setLastModified(src.lastModified());
+            } else {
+                // existing folder, gather existing children
+                File[] children = dest.listFiles();
+                if (children != null) {
+                    existingChild = Arrays.asList(children);
+                }
+            }
+        } else {
+            dest.mkdirs();
+            dest.setLastModified(src.lastModified());
+        }
+        // copy files one by one
+        File[] toCopy = src.listFiles();
+        if (toCopy != null) {
+            for (int i = 0; i < toCopy.length; i++) {
+                // compute the destination file
+                File childDest = new File(dest, toCopy[i].getName());
+                // if file existing, 'mark' it as taken care of
+                existingChild.remove(childDest);
+                if (toCopy[i].isDirectory()) {
+                    deepCopy(toCopy[i], childDest, l, overwrite);
+                } else {
+                    copy(toCopy[i], childDest, l, overwrite);
+                }
+            }
+        }
+        // some file exist in the destination but not in the source: delete them
+        for (int i = 0; i < existingChild.size(); i++) {
+            forceDelete((File) existingChild.get(i));
+        }
+        return true;
+    }
+
+    public static void copy(URL src, File dest, CopyProgressListener l) throws IOException {
+        URLHandlerRegistry.getDefault().download(src, dest, l);
+    }
+
+    public static void copy(File src, URL dest, CopyProgressListener l) throws IOException {
+        URLHandlerRegistry.getDefault().upload(src, dest, l);
+    }
+
+    public static void copy(InputStream src, File dest, CopyProgressListener l) throws IOException {
+        if (dest.getParentFile() != null) {
+            dest.getParentFile().mkdirs();
+        }
+        copy(src, new FileOutputStream(dest), l);
+    }
+
+    public static void copy(InputStream src, OutputStream dest, CopyProgressListener l)
+            throws IOException {
+        copy(src, dest, l, true);
+    }
+
+    public static void copy(InputStream src, OutputStream dest, CopyProgressListener l,
+            boolean autoClose) throws IOException {
+        CopyProgressEvent evt = null;
+        if (l != null) {
+            evt = new CopyProgressEvent();
+        }
+        try {
+            byte[] buffer = new byte[BUFFER_SIZE];
+            int c;
+            long total = 0;
+
+            if (l != null) {
+                l.start(evt);
+            }
+            while ((c = src.read(buffer)) != -1) {
+                if (Thread.currentThread().isInterrupted()) {
+                    throw new IOException("transfer interrupted");
+                }
+                dest.write(buffer, 0, c);
+                total += c;
+                if (l != null) {
+                    l.progress(evt.update(buffer, c, total));
+                }
+            }
+
+            if (l != null) {
+                evt.update(EMPTY_BUFFER, 0, total);
+            }
+
+            try {
+                dest.flush();
+            } catch (IOException ex) {
+                // ignore
+            }
+
+            // close the streams
+            if (autoClose) {
+                src.close();
+                dest.close();
+            }
+        } finally {
+            if (autoClose) {
+                try {
+                    src.close();
+                } catch (IOException ex) {
+                    // ignore
+                }
+                try {
+                    dest.close();
+                } catch (IOException ex) {
+                    // ignore
+                }
+            }
+        }
+
+        if (l != null) {
+            l.end(evt);
+        }
+    }
+
+    /**
+     * Reads the whole BufferedReader line by line, using \n as line separator for each line.
+     * <p>
+     * Note that this method will add a final \n to the last line even though there is no new line
+     * character at the end of last line in the original reader.
+     * </p>
+     * <p>
+     * The BufferedReader is closed when this method returns.
+     * </p>
+     * 
+     * @param in
+     *            the {@link BufferedReader} to read from
+     * @return a String with the whole content read from the {@link BufferedReader}
+     * @throws IOException
+     *             if an IO problems occur during reading
+     */
+    public static String readEntirely(BufferedReader in) throws IOException {
+        try {
+            StringBuffer buf = new StringBuffer();
+
+            String line = in.readLine();
+            while (line != null) {
+                buf.append(line + "\n");
+                line = in.readLine();
+            }
+            return buf.toString();
+        } finally {
+            in.close();
+        }
+    }
+
+    /**
+     * Reads the entire content of the file and returns it as a String.
+     * 
+     * @param f
+     *            the file to read from
+     * @return a String with the file content
+     * @throws IOException
+     *             if an IO problems occurs during reading
+     */
+    public static String readEntirely(File f) throws IOException {
+        return readEntirely(new FileInputStream(f));
+    }
+
+    /**
+     * Reads the entire content of the {@link InputStream} and returns it as a String.
+     * <p>
+     * The input stream is closed when this method returns.
+     * </p>
+     * 
+     * @param is
+     *            the {@link InputStream} to read from
+     * @return a String with the input stream content
+     * @throws IOException
+     *             if an IO problems occurs during reading
+     */
+    public static String readEntirely(InputStream is) throws IOException {
+        try {
+            StringBuffer sb = new StringBuffer();
+            byte[] buffer = new byte[BUFFER_SIZE];
+            int c;
+
+            while ((c = is.read(buffer)) != -1) {
+                sb.append(new String(buffer, 0, c));
+            }
+            return sb.toString();
+        } finally {
+            is.close();
+        }
+    }
+
+    public static String concat(String dir, String file) {
+        return dir + "/" + file;
+    }
+
+    /**
+     * Recursively delete file
+     * 
+     * @param file
+     *            the file to delete
+     * @return true if the deletion completed successfully (ie if the file does not exist on the
+     *         filesystem after this call), false if a deletion was not performed successfully.
+     */
+    public static boolean forceDelete(File file) {
+        if (!file.exists()) {
+            return true;
+        }
+        if (file.isDirectory()) {
+            File[] files = file.listFiles();
+            if (files != null) {
+                for (int i = 0; i < files.length; i++) {
+                    if (!forceDelete(files[i])) {
+                        return false;
+                    }
+                }
+            }
+        }
+        return file.delete();
+    }
+
+    /**
+     * Returns a list of Files composed of all directories being parent of file and child of root +
+     * file and root themselves. Example: getPathFiles(new File("test"), new
+     * File("test/dir1/dir2/file.txt")) => {new File("test/dir1"), new File("test/dir1/dir2"), new
+     * File("test/dir1/dir2/file.txt") } Note that if root is not an ancester of file, or if root is
+     * null, all directories from the file system root will be returned.
+     */
+    public static List getPathFiles(File root, File file) {
+        List ret = new ArrayList();
+        while (file != null && !file.getAbsolutePath().equals(root.getAbsolutePath())) {
+            ret.add(file);
+            file = file.getParentFile();
+        }
+        if (root != null) {
+            ret.add(root);
+        }
+        Collections.reverse(ret);
+        return ret;
+    }
+
+    /**
+     * Returns a collection of all Files being contained in the given directory, recursively,
+     * including directories.
+     * 
+     * @param dir
+     *            The directory from which all files, including files in subdirectory) are
+     *            extracted.
+     * @param ignore
+     *            a Collection of filenames which must be excluded from listing
+     * @return A collectoin containing all the files of the given directory and it's subdirectories.
+     */
+    public static Collection listAll(File dir, Collection ignore) {
+        return listAll(dir, new ArrayList(), ignore);
+    }
+
+    private static Collection listAll(File file, Collection list, Collection ignore) {
+        if (ignore.contains(file.getName())) {
+            return list;
+        }
+
+        if (file.exists()) {
+            list.add(file);
+        }
+        if (file.isDirectory()) {
+            File[] files = file.listFiles();
+            for (int i = 0; i < files.length; i++) {
+                listAll(files[i], list, ignore);
+            }
+        }
+        return list;
+    }
+
+    public static File resolveFile(File file, String filename) {
+        File result = new File(filename);
+        if (!result.isAbsolute()) {
+            result = new File(file, filename);
+        }
+
+        return normalize(result.getPath());
+    }
+
+    // ////////////////////////////////////////////
+    // The following code comes from Ant FileUtils
+    // ////////////////////////////////////////////
+
+    /**
+     * "Normalize" the given absolute path.
+     * 
+     * <p>
+     * This includes:
+     * <ul>
+     * <li>Uppercase the drive letter if there is one.</li>
+     * <li>Remove redundant slashes after the drive spec.</li>
+     * <li>Resolve all ./, .\, ../ and ..\ sequences.</li>
+     * <li>DOS style paths that start with a drive letter will have \ as the separator.</li>
+     * </ul>
+     * Unlike {@link File#getCanonicalPath()} this method specifically does not resolve symbolic
+     * links.
+     * 
+     * @param path
+     *            the path to be normalized.
+     * @return the normalized version of the path.
+     * 
+     * @throws java.lang.NullPointerException
+     *             if path is null.
+     */
+    public static File normalize(final String path) {
+        Stack s = new Stack();
+        String[] dissect = dissect(path);
+        s.push(dissect[0]);
+
+        StringTokenizer tok = new StringTokenizer(dissect[1], File.separator);
+        while (tok.hasMoreTokens()) {
+            String thisToken = tok.nextToken();
+            if (".".equals(thisToken)) {
+                continue;
+            }
+            if ("..".equals(thisToken)) {
+                if (s.size() < 2) {
+                    // Cannot resolve it, so skip it.
+                    return new File(path);
+                }
+                s.pop();
+            } else { // plain component
+                s.push(thisToken);
+            }
+        }
+        StringBuffer sb = new StringBuffer();
+        for (int i = 0; i < s.size(); i++) {
+            if (i > 1) {
+                // not before the filesystem root and not after it, since root
+                // already contains one
+                sb.append(File.separatorChar);
+            }
+            sb.append(s.elementAt(i));
+        }
+        return new File(sb.toString());
+    }
+
+    /**
+     * Dissect the specified absolute path.
+     * 
+     * @param path
+     *            the path to dissect.
+     * @return String[] {root, remaining path}.
+     * @throws java.lang.NullPointerException
+     *             if path is null.
+     * @since Ant 1.7
+     */
+    private static String[] dissect(String path) {
+        char sep = File.separatorChar;
+        path = path.replace('/', sep).replace('\\', sep);
+
+        // // make sure we are dealing with an absolute path
+        // if (!isAbsolutePath(path)) {
+        // throw new BuildException(path + " is not an absolute path");
+        // }
+        String root = null;
+        int colon = path.indexOf(':');
+        if (colon > 0) { // && (ON_DOS || ON_NETWARE)) {
+
+            int next = colon + 1;
+            root = path.substring(0, next);
+            char[] ca = path.toCharArray();
+            root += sep;
+            // remove the initial separator; the root has it.
+            next = (ca[next] == sep) ? next + 1 : next;
+
+            StringBuffer sbPath = new StringBuffer();
+            // Eliminate consecutive slashes after the drive spec:
+            for (int i = next; i < ca.length; i++) {
+                if (ca[i] != sep || ca[i - 1] != sep) {
+                    sbPath.append(ca[i]);
+                }
+            }
+            path = sbPath.toString();
+        } else if (path.length() > 1 && path.charAt(1) == sep) {
+            // UNC drive
+            int nextsep = path.indexOf(sep, 2);
+            nextsep = path.indexOf(sep, nextsep + 1);
+            root = (nextsep > 2) ? path.substring(0, nextsep + 1) : path;
+            path = path.substring(root.length());
+        } else {
+            root = File.separator;
+            path = path.substring(1);
+        }
+        return new String[] {root, path};
+    }
+
+    /**
+     * Get the length of the file, or the sum of the children lengths if it is a directory
+     * 
+     * @param file
+     * @return
+     */
+    public static long getFileLength(File file) {
+        long l = 0;
+        if (file.isDirectory()) {
+            File[] files = file.listFiles();
+            if (files != null) {
+                for (int i = 0; i < files.length; i++) {
+                    l += getFileLength(files[i]);
+                }
+            }
+        } else {
+            l = file.length();
+        }
+        return l;
+    }
+
+    public static InputStream unwrapPack200(InputStream packed) throws IOException {
+        BufferedInputStream buffered = new BufferedInputStream(packed);
+        buffered.mark(4);
+        byte[] magic = new byte[4];
+        buffered.read(magic, 0, 4);
+        buffered.reset();
+
+        InputStream in = buffered;
+
+        if (magic[0] == (byte) 0x1F && magic[1] == (byte) 0x8B && magic[2] == (byte) 0x08) {
+            // this is a gziped pack200
+            in = new GZIPInputStream(in);
+        }
+
+        Unpacker unpacker = Pack200.newUnpacker();
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        JarOutputStream jar = new JarOutputStream(baos);
+        unpacker.unpack(new UncloseInputStream(in), jar);
+        jar.close();
+        return new ByteArrayInputStream(baos.toByteArray());
+    }
+
+    /**
+     * Wrap an input stream and do not close the stream on call to close(). Used to avoid closing a
+     * {@link ZipInputStream} used with {@link Unpacker#unpack(File, JarOutputStream)}
+     */
+    private static final class UncloseInputStream extends InputStream {
+
+        private InputStream wrapped;
+
+        public UncloseInputStream(InputStream wrapped) {
+            this.wrapped = wrapped;
+        }
+
+        public void close() throws IOException {
+            // do not close
+        }
+
+        public int read() throws IOException {
+            return wrapped.read();
+        }
+
+        public int hashCode() {
+            return wrapped.hashCode();
+        }
+
+        public int read(byte[] b) throws IOException {
+            return wrapped.read(b);
+        }
+
+        public boolean equals(Object obj) {
+            return wrapped.equals(obj);
+        }
+
+        public int read(byte[] b, int off, int len) throws IOException {
+            return wrapped.read(b, off, len);
+        }
+
+        public long skip(long n) throws IOException {
+            return wrapped.skip(n);
+        }
+
+        public String toString() {
+            return wrapped.toString();
+        }
+
+        public int available() throws IOException {
+            return wrapped.available();
+        }
+
+        public void mark(int readlimit) {
+            wrapped.mark(readlimit);
+        }
+
+        public void reset() throws IOException {
+            wrapped.reset();
+        }
+
+        public boolean markSupported() {
+            return wrapped.markSupported();
+        }
+
+    }
+}
diff --git a/src/java/org/apache/ivy/util/HexEncoder.java b/src/java/org/apache/ivy/util/HexEncoder.java
new file mode 100644
index 0000000..436f1b1
--- /dev/null
+++ b/src/java/org/apache/ivy/util/HexEncoder.java
@@ -0,0 +1,39 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util;
+
+/**
+ * Simple encoder of byte arrays into String array using only the hexadecimal alphabet
+ */
+public class HexEncoder {
+
+    private static final char[] ALPHABET = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a',
+            'b', 'c', 'd', 'e', 'f'};
+
+    public static String encode(byte[] packet) {
+        StringBuffer chars = new StringBuffer(16);
+        for (int i = 0; i < packet.length; i++) {
+            int highBits = (packet[i] & 0xF0) >> 4;
+            int lowBits = packet[i] & 0x0F;
+            chars.append(ALPHABET[highBits]);
+            chars.append(ALPHABET[lowBits]);
+        }
+        return chars.toString();
+    }
+
+}
diff --git a/src/java/org/apache/ivy/util/HostUtil.java b/src/java/org/apache/ivy/util/HostUtil.java
new file mode 100644
index 0000000..ae4a41b
--- /dev/null
+++ b/src/java/org/apache/ivy/util/HostUtil.java
@@ -0,0 +1,53 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * This class contains basic helper methods for the Host.
+ * 
+ */
+public final class HostUtil {
+
+    private static String localHostName = null;
+
+    /**
+     * This default constructor is to hide this class from initialization through other classes.
+     */
+    private HostUtil() {
+    }
+
+    /**
+     * This method returns the name of the current Host, if this name cannot be determined,
+     * localhost will be returned.
+     * 
+     * @return The name of the current "local" Host.
+     */
+    public static String getLocalHostName() {
+        if (localHostName == null) {
+            try {
+                localHostName = InetAddress.getLocalHost().getHostName();
+            } catch (UnknownHostException e) {
+                localHostName = "localhost";
+            }
+        }
+        return localHostName;
+    }
+}
diff --git a/src/java/org/apache/ivy/util/MemoryUtil.java b/src/java/org/apache/ivy/util/MemoryUtil.java
new file mode 100644
index 0000000..fef4482
--- /dev/null
+++ b/src/java/org/apache/ivy/util/MemoryUtil.java
@@ -0,0 +1,90 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util;
+
+/**
+ * Memory related utilities.
+ */
+public final class MemoryUtil {
+    private static final int SAMPLING_SIZE = 100;
+
+    private static final int SLEEP_TIME = 100;
+
+    private MemoryUtil() {
+    }
+
+    /**
+     * Returns the approximate size of a default instance of the given class.
+     * 
+     * @param clazz
+     *            the class to evaluate.
+     * @return the estimated size of instance, in bytes.
+     */
+    public static long sizeOf(Class clazz) {
+        long size = 0;
+        Object[] objects = new Object[SAMPLING_SIZE];
+        try {
+            clazz.newInstance();
+            long startingMemoryUse = getUsedMemory();
+            for (int i = 0; i < objects.length; i++) {
+                objects[i] = clazz.newInstance();
+            }
+            long endingMemoryUse = getUsedMemory();
+            float approxSize = (endingMemoryUse - startingMemoryUse) / (float) objects.length;
+            size = Math.round(approxSize);
+        } catch (Exception e) {
+            Message.warn("Couldn't instantiate " + clazz, e);
+        }
+        return size;
+    }
+
+    /**
+     * Returns the currently used memory, after calling garbage collector and waiting enough to get
+     * maximal chance it is actually called. But since {@link Runtime#gc()} is only advisory,
+     * results returned by this method should be treated as rough approximation only.
+     * 
+     * @return the currently used memory, in bytes.
+     */
+    public static long getUsedMemory() {
+        gc();
+        long totalMemory = Runtime.getRuntime().totalMemory();
+        gc();
+        long freeMemory = Runtime.getRuntime().freeMemory();
+        long usedMemory = totalMemory - freeMemory;
+        return usedMemory;
+    }
+
+    private static void gc() {
+        try {
+            System.gc();
+            Thread.sleep(SLEEP_TIME);
+            System.runFinalization();
+            Thread.sleep(SLEEP_TIME);
+            System.gc();
+            Thread.sleep(SLEEP_TIME);
+            System.runFinalization();
+            Thread.sleep(SLEEP_TIME);
+        } catch (Exception e) {
+            Message.debug(e);
+        }
+    }
+
+    public static void main(String[] args) throws ClassNotFoundException {
+        System.out.println(sizeOf(Class.forName(args[0])));
+    }
+}
\ No newline at end of file
diff --git a/src/java/org/apache/ivy/util/Message.java b/src/java/org/apache/ivy/util/Message.java
new file mode 100644
index 0000000..1e6b67f
--- /dev/null
+++ b/src/java/org/apache/ivy/util/Message.java
@@ -0,0 +1,218 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util;
+
+import java.util.List;
+
+import org.apache.ivy.Ivy;
+import org.apache.ivy.core.IvyContext;
+
+/**
+ * Logging utility class.
+ * <p>
+ * This class provides static methods for easy access to the current logger in {@link IvyContext}.
+ * </p>
+ * <p>
+ * To configure logging, you should use the methods provided by the {@link MessageLoggerEngine}
+ * associated with the {@link Ivy} engine.
+ * </p>
+ */
+public final class Message {
+    // messages level copied from ant project, to avoid dependency on ant
+    /** Message priority of "error". */
+    public static final int MSG_ERR = 0;
+
+    /** Message priority of "warning". */
+    public static final int MSG_WARN = 1;
+
+    /** Message priority of "information". */
+    public static final int MSG_INFO = 2;
+
+    /** Message priority of "verbose". */
+    public static final int MSG_VERBOSE = 3;
+
+    /** Message priority of "debug". */
+    public static final int MSG_DEBUG = 4;
+
+    private static boolean showedInfo = false;
+
+    private static MessageLogger defaultLogger = new DefaultMessageLogger(Message.MSG_INFO);
+
+    /**
+     * Returns the current default logger.
+     * 
+     * @return the current default logger; is never <code>null</code>.
+     */
+    public static MessageLogger getDefaultLogger() {
+        return defaultLogger;
+    }
+
+    /**
+     * Change the default logger used when no other logger is currently configured
+     * 
+     * @param logger
+     *            the new default logger, must not be <code>null</code>
+     */
+    public static void setDefaultLogger(MessageLogger logger) {
+        Checks.checkNotNull(logger, "logger");
+        defaultLogger = logger;
+    }
+
+    private static MessageLogger getLogger() {
+        return IvyContext.getContext().getMessageLogger();
+    }
+
+    public static void showInfo() {
+        if (!showedInfo) {
+            info(":: Apache Ivy " + Ivy.getIvyVersion() + " - " + Ivy.getIvyDate() + " :: "
+                    + Ivy.getIvyHomeURL() + " ::");
+            showedInfo = true;
+        }
+    }
+
+    public static void debug(String msg) {
+        getLogger().debug(msg);
+    }
+
+    public static void verbose(String msg) {
+        getLogger().verbose(msg);
+    }
+
+    public static void info(String msg) {
+        getLogger().info(msg);
+    }
+
+    public static void rawinfo(String msg) {
+        getLogger().rawinfo(msg);
+    }
+
+    public static void deprecated(String msg) {
+        getLogger().deprecated(msg);
+    }
+
+    public static void warn(String msg) {
+        getLogger().warn(msg);
+    }
+
+    public static void error(String msg) {
+        getLogger().error(msg);
+    }
+
+    public static void log(int logLevel, String msg) {
+        switch (logLevel) {
+            case MSG_DEBUG:
+                debug(msg);
+                break;
+            case MSG_VERBOSE:
+                verbose(msg);
+                break;
+            case MSG_INFO:
+                info(msg);
+                break;
+            case MSG_WARN:
+                warn(msg);
+                break;
+            case MSG_ERR:
+                error(msg);
+                break;
+            default:
+                throw new IllegalArgumentException("Unknown log level " + logLevel);
+        }
+    }
+
+    public static List<String> getProblems() {
+        return getLogger().getProblems();
+    }
+
+    public static void sumupProblems() {
+        getLogger().sumupProblems();
+    }
+
+    public static void progress() {
+        getLogger().progress();
+    }
+
+    public static void endProgress() {
+        getLogger().endProgress();
+    }
+
+    public static void endProgress(String msg) {
+        getLogger().endProgress(msg);
+    }
+
+    public static boolean isShowProgress() {
+        return getLogger().isShowProgress();
+    }
+
+    public static void setShowProgress(boolean progress) {
+        getLogger().setShowProgress(progress);
+    }
+
+    private Message() {
+    }
+
+    public static void debug(String message, Throwable t) {
+        if (t == null) {
+            debug(message);
+        } else {
+            debug(message + " (" + t.getClass().getName() + ": " + t.getMessage() + ")");
+            debug(t);
+        }
+    }
+
+    public static void verbose(String message, Throwable t) {
+        if (t == null) {
+            verbose(message);
+        } else {
+            verbose(message + " (" + t.getClass().getName() + ": " + t.getMessage() + ")");
+            debug(t);
+        }
+    }
+
+    public static void info(String message, Throwable t) {
+        if (t == null) {
+            info(message);
+        } else {
+            info(message + " (" + t.getClass().getName() + ": " + t.getMessage() + ")");
+            debug(t);
+        }
+    }
+
+    public static void warn(String message, Throwable t) {
+        if (t == null) {
+            warn(message);
+        } else {
+            warn(message + " (" + t.getClass().getName() + ": " + t.getMessage() + ")");
+            debug(t);
+        }
+    }
+
+    public static void error(String message, Throwable t) {
+        if (t == null) {
+            error(message);
+        } else {
+            error(message + " (" + t.getClass().getName() + ": " + t.getMessage() + ")");
+            debug(t);
+        }
+    }
+
+    public static void debug(Throwable t) {
+        debug(StringUtils.getStackTrace(t));
+    }
+
+}
diff --git a/src/java/org/apache/ivy/util/MessageLogger.java b/src/java/org/apache/ivy/util/MessageLogger.java
new file mode 100644
index 0000000..0c09efe
--- /dev/null
+++ b/src/java/org/apache/ivy/util/MessageLogger.java
@@ -0,0 +1,108 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util;
+
+import java.util.List;
+
+/**
+ * A MessageLogger is used to log messages.
+ * <p>
+ * Where the messages are logged is depending on the implementation.
+ * </p>
+ * <p>
+ * This interface provides both level specific methods ({@link #info(String)}, {@link #warn(String)}
+ * , ...) and generic methods ({@link #log(String, int)}, {@link #rawlog(String, int)}). Note that
+ * calling level specific methods is usually not equivalent to calling the generic method with the
+ * corresponding level. Indeed, for warn and error level, the implementation will actually log the
+ * message at a lower level (usually {@link Message#MSG_VERBOSE}) and log the message at the actual
+ * level only when {@link #sumupProblems()} is called.
+ * </p>
+ * 
+ * @see Message
+ */
+public interface MessageLogger {
+    /**
+     * Logs a message at the given level.
+     * <p>
+     * <code>level</code> constants are defined in the {@link Message} class.
+     * </p>
+     * 
+     * @param msg
+     *            the message to log
+     * @param level
+     *            the level at which the message should be logged.
+     * @see Message#MSG_DEBUG
+     * @see Message#MSG_VERBOSE
+     * @see Message#MSG_INFO
+     * @see Message#MSG_WARN
+     * @see Message#MSG_ERROR
+     */
+    public abstract void log(String msg, int level);
+
+    /**
+     * Same as {@link #log(String, int)}, but without adding any contextual information to the
+     * message.
+     * 
+     * @param msg
+     *            the message to log
+     * @param level
+     *            the level at which the message should be logged.
+     */
+    public abstract void rawlog(String msg, int level);
+
+    public abstract void debug(String msg);
+
+    public abstract void verbose(String msg);
+
+    public abstract void deprecated(String msg);
+
+    public abstract void info(String msg);
+
+    public abstract void rawinfo(String msg);
+
+    public abstract void warn(String msg);
+
+    public abstract void error(String msg);
+
+    public abstract List<String> getProblems();
+
+    public abstract List<String> getWarns();
+
+    public abstract List<String> getErrors();
+
+    /**
+     * Clears the list of problems, warns and errors.
+     */
+    public abstract void clearProblems();
+
+    /**
+     * Sumup all problems encountered so far, and clear them.
+     */
+    public abstract void sumupProblems();
+
+    public abstract void progress();
+
+    public abstract void endProgress();
+
+    public abstract void endProgress(String msg);
+
+    public abstract boolean isShowProgress();
+
+    public abstract void setShowProgress(boolean progress);
+
+}
\ No newline at end of file
diff --git a/src/java/org/apache/ivy/util/MessageLoggerEngine.java b/src/java/org/apache/ivy/util/MessageLoggerEngine.java
new file mode 100644
index 0000000..21bc72f
--- /dev/null
+++ b/src/java/org/apache/ivy/util/MessageLoggerEngine.java
@@ -0,0 +1,207 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Stack;
+
+/**
+ * A {@link MessageLogger} implementation delegating the work to the current top logger on a stack.
+ * <p>
+ * When the logger stack is empty, it delegates the work to a default logger, which by default is
+ * the {@link Message#getDefaultLogger()}.
+ * </p>
+ * <p>
+ * {@link #pushLogger(MessageLogger)} should be called to delegate to a new logger, and
+ * {@link #popLogger()} should be called when the context of this logger is finished.
+ * </p>
+ */
+public class MessageLoggerEngine implements MessageLogger {
+    private final ThreadLocal/* <Stack<MessageLogger>> */loggerStacks = new ThreadLocal();
+
+    private MessageLogger defaultLogger = null;
+
+    private List problems = new ArrayList();
+
+    private List warns = new ArrayList();
+
+    private List errors = new ArrayList();
+
+    private Stack getLoggerStack() {
+        Stack stack = (Stack) loggerStacks.get();
+        if (stack == null) {
+            stack = new Stack();
+            loggerStacks.set(stack);
+        }
+        return stack;
+    }
+
+    public MessageLoggerEngine() {
+    }
+
+    /**
+     * Sets the logger used when the stack is empty.
+     * 
+     * @param defaultLogger
+     *            the logger to use when the stack is empty.
+     */
+    public void setDefaultLogger(MessageLogger defaultLogger) {
+        this.defaultLogger = defaultLogger;
+    }
+
+    /**
+     * Push a logger on the stack.
+     * 
+     * @param logger
+     *            the logger to push. Must not be <code>null</code>.
+     */
+    public void pushLogger(MessageLogger logger) {
+        Checks.checkNotNull(logger, "logger");
+        getLoggerStack().push(logger);
+    }
+
+    /**
+     * Pops a logger from the logger stack.
+     * <p>
+     * Does nothing if the logger stack is empty
+     * </p>
+     */
+    public void popLogger() {
+        if (!getLoggerStack().isEmpty()) {
+            getLoggerStack().pop();
+        }
+    }
+
+    /**
+     * Returns the current logger, or the default one if there is no logger in the stack
+     * 
+     * @return the current logger, or the default one if there is no logger in the stack
+     */
+    public MessageLogger peekLogger() {
+        if (getLoggerStack().isEmpty()) {
+            return getDefaultLogger();
+        }
+        return (MessageLogger) getLoggerStack().peek();
+    }
+
+    private MessageLogger getDefaultLogger() {
+        // we don't store the logger returned by Message.getDefaultLogger() to always stay in sync
+        // as long as our default logger has not been set explicitly with setDefaultLogger()
+        return defaultLogger == null ? Message.getDefaultLogger() : defaultLogger;
+    }
+
+    // consolidated methods
+    public void warn(String msg) {
+        peekLogger().warn(msg);
+        problems.add("WARN:  " + msg);
+        warns.add(msg);
+    }
+
+    public void error(String msg) {
+        peekLogger().error(msg);
+        problems.add("\tERROR: " + msg);
+        errors.add(msg);
+    }
+
+    public List getErrors() {
+        return errors;
+    }
+
+    public List getProblems() {
+        return problems;
+    }
+
+    public List getWarns() {
+        return warns;
+    }
+
+    public void sumupProblems() {
+        MessageLoggerHelper.sumupProblems(this);
+        clearProblems();
+    }
+
+    public void clearProblems() {
+        getDefaultLogger().clearProblems();
+        for (Iterator iter = getLoggerStack().iterator(); iter.hasNext();) {
+            MessageLogger l = (MessageLogger) iter.next();
+            l.clearProblems();
+        }
+        problems.clear();
+        errors.clear();
+        warns.clear();
+    }
+
+    public void setShowProgress(boolean progress) {
+        getDefaultLogger().setShowProgress(progress);
+        // updates all loggers in the stack
+        for (Iterator iter = getLoggerStack().iterator(); iter.hasNext();) {
+            MessageLogger l = (MessageLogger) iter.next();
+            l.setShowProgress(progress);
+        }
+    }
+
+    public boolean isShowProgress() {
+        // testing the default logger is enough, all loggers should be in sync
+        return getDefaultLogger().isShowProgress();
+    }
+
+    // delegation methods
+
+    public void debug(String msg) {
+        peekLogger().debug(msg);
+    }
+
+    public void deprecated(String msg) {
+        peekLogger().deprecated(msg);
+    }
+
+    public void endProgress() {
+        peekLogger().endProgress();
+    }
+
+    public void endProgress(String msg) {
+        peekLogger().endProgress(msg);
+    }
+
+    public void info(String msg) {
+        peekLogger().info(msg);
+    }
+
+    public void rawinfo(String msg) {
+        peekLogger().rawinfo(msg);
+    }
+
+    public void log(String msg, int level) {
+        peekLogger().log(msg, level);
+    }
+
+    public void progress() {
+        peekLogger().progress();
+    }
+
+    public void rawlog(String msg, int level) {
+        peekLogger().rawlog(msg, level);
+    }
+
+    public void verbose(String msg) {
+        peekLogger().verbose(msg);
+    }
+
+}
diff --git a/src/java/org/apache/ivy/util/MessageLoggerHelper.java b/src/java/org/apache/ivy/util/MessageLoggerHelper.java
new file mode 100644
index 0000000..c06f14a
--- /dev/null
+++ b/src/java/org/apache/ivy/util/MessageLoggerHelper.java
@@ -0,0 +1,55 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util;
+
+import java.util.Iterator;
+import java.util.List;
+
+public final class MessageLoggerHelper {
+    public static void sumupProblems(MessageLogger logger) {
+        List myProblems = logger.getProblems();
+        if (myProblems.size() > 0) {
+            List myWarns = logger.getWarns();
+            List myErrors = logger.getErrors();
+            logger.info(""); // new line on info to isolate error summary
+            if (!myErrors.isEmpty()) {
+                logger.log(":: problems summary ::", Message.MSG_ERR);
+            } else {
+                logger.log(":: problems summary ::", Message.MSG_WARN);
+            }
+            if (myWarns.size() > 0) {
+                logger.log(":::: WARNINGS", Message.MSG_WARN);
+                for (Iterator iter = myWarns.iterator(); iter.hasNext();) {
+                    String msg = (String) iter.next();
+                    logger.log("\t" + msg + "\n", Message.MSG_WARN);
+                }
+            }
+            if (myErrors.size() > 0) {
+                logger.log(":::: ERRORS", Message.MSG_ERR);
+                for (Iterator iter = myErrors.iterator(); iter.hasNext();) {
+                    String msg = (String) iter.next();
+                    logger.log("\t" + msg + "\n", Message.MSG_ERR);
+                }
+            }
+            logger.info("\n:: USE VERBOSE OR DEBUG MESSAGE LEVEL FOR MORE DETAILS");
+        }
+    }
+
+    private MessageLoggerHelper() {
+    }
+}
diff --git a/src/java/org/apache/ivy/util/PropertiesFile.java b/src/java/org/apache/ivy/util/PropertiesFile.java
new file mode 100644
index 0000000..f4d2048
--- /dev/null
+++ b/src/java/org/apache/ivy/util/PropertiesFile.java
@@ -0,0 +1,75 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Properties;
+
+/**
+ * A simple Properties extension easing the loading and saving of data
+ */
+public class PropertiesFile extends Properties {
+    private File file;
+
+    private String header;
+
+    public PropertiesFile(File file, String header) {
+        this.file = file;
+        this.header = header;
+        if (file.exists()) {
+            FileInputStream fis = null;
+            try {
+                fis = new FileInputStream(file);
+                load(fis);
+            } catch (Exception ex) {
+                Message.warn("exception occurred while reading properties file " + file, ex);
+            }
+            try {
+                if (fis != null) {
+                    fis.close();
+                }
+            } catch (IOException e) {
+                // ignored
+            }
+        }
+    }
+
+    public void save() {
+        FileOutputStream fos = null;
+        try {
+            if (file.getParentFile() != null && !file.getParentFile().exists()) {
+                file.getParentFile().mkdirs();
+            }
+            fos = new FileOutputStream(file);
+            store(fos, header);
+        } catch (Exception ex) {
+            Message.warn("exception occurred while writing properties file " + file, ex);
+        }
+        try {
+            if (fos != null) {
+                fos.close();
+            }
+        } catch (IOException e) {
+            // ignored
+        }
+    }
+
+}
diff --git a/src/java/org/apache/ivy/util/StringUtils.java b/src/java/org/apache/ivy/util/StringUtils.java
new file mode 100644
index 0000000..c60efb7
--- /dev/null
+++ b/src/java/org/apache/ivy/util/StringUtils.java
@@ -0,0 +1,192 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Locale;
+
+/**
+ * Convenient class used only for uncapitalization Usually use commons lang but here we do not want
+ * to have such a dependency for only one feature
+ */
+public final class StringUtils {
+
+    private StringUtils() {
+        // Utility class
+    }
+
+    public static String uncapitalize(String string) {
+        if (string == null || string.length() == 0) {
+            return string;
+        }
+        if (string.length() == 1) {
+            return string.toLowerCase(Locale.US);
+        }
+        return string.substring(0, 1).toLowerCase(Locale.US) + string.substring(1);
+    }
+
+    /**
+     * Returns the error message associated with the given Throwable. The error message returned
+     * will try to be as precise as possible, handling cases where e.getMessage() is not meaningful,
+     * like {@link NullPointerException} for instance.
+     * 
+     * @param t
+     *            the throwable to get the error message from
+     * @return the error message of the given exception
+     */
+    public static String getErrorMessage(Throwable t) {
+        if (t == null) {
+            return "";
+        }
+        if (t instanceof InvocationTargetException) {
+            InvocationTargetException ex = (InvocationTargetException) t;
+            t = ex.getTargetException();
+        }
+        String errMsg = t instanceof RuntimeException ? t.getMessage() : t.toString();
+        if (errMsg == null || errMsg.length() == 0 || "null".equals(errMsg)) {
+            errMsg = t.getClass().getName() + " at " + t.getStackTrace()[0].toString();
+        }
+        return errMsg;
+    }
+
+    /**
+     * Returns the exception stack trace as a String.
+     * 
+     * @param e
+     *            the exception to get the stack trace from.
+     * @return the exception stack trace
+     */
+    public static String getStackTrace(Throwable e) {
+        if (e == null) {
+            return "";
+        }
+        StringWriter sw = new StringWriter();
+        PrintWriter printWriter = new PrintWriter(sw, true);
+        e.printStackTrace(printWriter);
+        return sw.getBuffer().toString();
+    }
+
+    /**
+     * Joins the given object array in one string, each separated by the given separator.
+     * 
+     * Example:
+     * 
+     * <pre>
+     * join(new String[] {"one", "two", "three"}, ", ") -> "one, two, three"
+     * </pre>
+     * 
+     * @param objs
+     *            The array of objects (<code>toString()</code> is used).
+     * @param sep
+     *            The separator to use.
+     * @return The concatinated string.
+     */
+    public static String join(Object[] objs, String sep) {
+        StringBuffer buf = new StringBuffer();
+        for (int i = 0; i < objs.length; i++) {
+            buf.append(objs[i]).append(sep);
+        }
+        if (objs.length > 0) {
+            buf.setLength(buf.length() - sep.length()); // delete sep
+        }
+        return buf.toString();
+    }
+
+    // basic string codec (same algo as CVS passfile, inspired by ant CVSPass class
+    /** Array contain char conversion data */
+    private static final char[] SHIFTS = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
+            17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 114, 120, 53, 79, 96, 109,
+            72, 108, 70, 64, 76, 67, 116, 74, 68, 87, 111, 52, 75, 119, 49, 34, 82, 81, 95, 65,
+            112, 86, 118, 110, 122, 105, 41, 57, 83, 43, 46, 102, 40, 89, 38, 103, 45, 50, 42, 123,
+            91, 35, 125, 55, 54, 66, 124, 126, 59, 47, 92, 71, 115, 78, 88, 107, 106, 56, 36, 121,
+            117, 104, 101, 100, 69, 73, 99, 63, 94, 93, 39, 37, 61, 48, 58, 113, 32, 90, 44, 98,
+            60, 51, 33, 97, 62, 77, 84, 80, 85, 223, 225, 216, 187, 166, 229, 189, 222, 188, 141,
+            249, 148, 200, 184, 136, 248, 190, 199, 170, 181, 204, 138, 232, 218, 183, 255, 234,
+            220, 247, 213, 203, 226, 193, 174, 172, 228, 252, 217, 201, 131, 230, 197, 211, 145,
+            238, 161, 179, 160, 212, 207, 221, 254, 173, 202, 146, 224, 151, 140, 196, 205, 130,
+            135, 133, 143, 246, 192, 159, 244, 239, 185, 168, 215, 144, 139, 165, 180, 157, 147,
+            186, 214, 176, 227, 231, 219, 169, 175, 156, 206, 198, 129, 164, 150, 210, 154, 177,
+            134, 127, 182, 128, 158, 208, 162, 132, 167, 209, 149, 241, 153, 251, 237, 236, 171,
+            195, 243, 233, 253, 240, 194, 250, 191, 155, 142, 137, 245, 235, 163, 242, 178, 152};
+
+    /**
+     * Encrypt the given string in a way which anybody having access to this method algorithm can
+     * easily decrypt. This is useful only to avoid clear string storage in a file for example, but
+     * shouldn't be considered as a real mean of security. This only works with simple characters
+     * (char < 256).
+     * 
+     * @param str
+     *            the string to encrypt
+     * @return the encrypted version of the string
+     */
+    public static final String encrypt(String str) {
+        if (str == null) {
+            return null;
+        }
+        StringBuffer buf = new StringBuffer();
+        for (int i = 0; i < str.length(); i++) {
+            char c = str.charAt(i);
+            if (c >= SHIFTS.length) {
+                throw new IllegalArgumentException(
+                        "encrypt method can only be used with simple characters. '" + c
+                                + "' not allowed");
+            }
+            buf.append(SHIFTS[c]);
+        }
+        return buf.toString();
+    }
+
+    /**
+     * Decrypts a string encrypted with encrypt.
+     * 
+     * @param str
+     *            the encrypted string to decrypt
+     * @return The decrypted string.
+     */
+    public static final String decrypt(String str) {
+        if (str == null) {
+            return null;
+        }
+        StringBuffer buf = new StringBuffer();
+        for (int i = 0; i < str.length(); i++) {
+            buf.append(decrypt(str.charAt(i)));
+        }
+        return buf.toString();
+    }
+
+    private static char decrypt(char c) {
+        for (char i = 0; i < SHIFTS.length; i++) {
+            if (SHIFTS[i] == c) {
+                return i;
+            }
+        }
+        throw new IllegalArgumentException("Impossible to decrypt '" + c
+                + "'. Unhandled character.");
+    }
+
+    public static String repeat(String str, int count) {
+        StringBuffer sb = new StringBuffer();
+        for (int i = 0; i < count; i++) {
+            sb.append(str);
+        }
+        return sb.toString();
+    }
+
+}
diff --git a/src/java/org/apache/ivy/util/XMLHelper.java b/src/java/org/apache/ivy/util/XMLHelper.java
new file mode 100644
index 0000000..8f64ad8
--- /dev/null
+++ b/src/java/org/apache/ivy/util/XMLHelper.java
@@ -0,0 +1,243 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.apache.ivy.util.url.URLHandlerRegistry;
+import org.w3c.dom.Document;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXNotRecognizedException;
+import org.xml.sax.ext.LexicalHandler;
+import org.xml.sax.helpers.DefaultHandler;
+
+public abstract class XMLHelper {
+
+    static final String JAXP_SCHEMA_LANGUAGE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
+
+    static final String JAXP_SCHEMA_SOURCE = "http://java.sun.com/xml/jaxp/properties/schemaSource";
+
+    static final String XERCES_LOAD_EXTERNAL_DTD = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
+
+    static final String XML_NAMESPACE_PREFIXES = "http://xml.org/sax/features/namespace-prefixes";
+
+    static final String W3C_XML_SCHEMA = "http://www.w3.org/2001/XMLSchema";
+
+    private static boolean canUseSchemaValidation = true;
+
+    private static Boolean canDisableExternalDtds = null;
+
+    private static SAXParser newSAXParser(URL schema, InputStream schemaStream,
+            boolean loadExternalDtds) throws ParserConfigurationException, SAXException {
+        SAXParserFactory parserFactory = SAXParserFactory.newInstance();
+        parserFactory.setNamespaceAware(true);
+        parserFactory.setValidating(canUseSchemaValidation && (schema != null));
+        if (!loadExternalDtds && canDisableExternalDtds(parserFactory)) {
+            parserFactory.setFeature(XERCES_LOAD_EXTERNAL_DTD, false);
+        }
+        SAXParser parser = parserFactory.newSAXParser();
+
+        if (canUseSchemaValidation && (schema != null)) {
+            try {
+                parser.setProperty(JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA);
+                parser.setProperty(JAXP_SCHEMA_SOURCE, schemaStream);
+            } catch (SAXNotRecognizedException ex) {
+                Message.warn("problem while setting JAXP validating property on SAXParser... "
+                        + "XML validation will not be done", ex);
+                canUseSchemaValidation = false;
+                parserFactory.setValidating(false);
+                parser = parserFactory.newSAXParser();
+            }
+        }
+
+        parser.getXMLReader().setFeature(XML_NAMESPACE_PREFIXES, true);
+        return parser;
+    }
+
+    private static boolean canDisableExternalDtds(SAXParserFactory parserFactory) {
+        if (canDisableExternalDtds == null) {
+            try {
+                parserFactory.getFeature(XERCES_LOAD_EXTERNAL_DTD);
+                canDisableExternalDtds = Boolean.TRUE;
+            } catch (Exception ex) {
+                canDisableExternalDtds = Boolean.FALSE;
+            }
+        }
+        return canDisableExternalDtds.booleanValue();
+    }
+
+    /**
+     * Convert an URL to a valid systemId according to RFC 2396.
+     */
+    public static String toSystemId(URL url) {
+        try {
+            return new URI(url.toExternalForm()).toASCIIString();
+        } catch (URISyntaxException e) {
+            return url.toExternalForm();
+        }
+    }
+
+    // IMPORTANT: validation errors are only notified to the given handler, and
+    // do not cause exception
+    // implement warning error and fatalError methods in handler to be informed
+    // of validation errors
+    public static void parse(URL xmlURL, URL schema, DefaultHandler handler) throws SAXException,
+            IOException, ParserConfigurationException {
+        parse(xmlURL, schema, handler, null);
+    }
+
+    public static void parse(URL xmlURL, URL schema, DefaultHandler handler, LexicalHandler lHandler)
+            throws SAXException, IOException, ParserConfigurationException {
+        InputStream xmlStream = URLHandlerRegistry.getDefault().openStream(xmlURL);
+        try {
+            InputSource inSrc = new InputSource(xmlStream);
+            inSrc.setSystemId(toSystemId(xmlURL));
+            parse(inSrc, schema, handler, lHandler);
+        } finally {
+            try {
+                xmlStream.close();
+            } catch (IOException e) {
+                // ignored
+            }
+        }
+    }
+
+    public static void parse(InputStream xmlStream, URL schema, DefaultHandler handler,
+            LexicalHandler lHandler) throws SAXException, IOException, ParserConfigurationException {
+        parse(new InputSource(xmlStream), schema, handler, lHandler);
+    }
+
+    public static void parse(InputSource xmlStream, URL schema, DefaultHandler handler,
+            LexicalHandler lHandler) throws SAXException, IOException, ParserConfigurationException {
+        parse(xmlStream, schema, handler, lHandler, true);
+    }
+
+    public static void parse(InputSource xmlStream, URL schema, DefaultHandler handler,
+            LexicalHandler lHandler, boolean loadExternalDtds) throws SAXException, IOException,
+            ParserConfigurationException {
+        InputStream schemaStream = null;
+        try {
+            if (schema != null) {
+                schemaStream = URLHandlerRegistry.getDefault().openStream(schema);
+            }
+            SAXParser parser = XMLHelper.newSAXParser(schema, schemaStream, loadExternalDtds);
+
+            if (lHandler != null) {
+                try {
+                    parser.setProperty("http://xml.org/sax/properties/lexical-handler", lHandler);
+                } catch (SAXException ex) {
+                    Message.warn("problem while setting the lexical handler property on SAXParser",
+                        ex);
+                    // continue without the lexical handler
+                }
+            }
+
+            parser.parse(xmlStream, handler);
+        } finally {
+            if (schemaStream != null) {
+                try {
+                    schemaStream.close();
+                } catch (IOException ex) {
+                    // ignored
+                }
+            }
+        }
+    }
+
+    public static boolean canUseSchemaValidation() {
+        return canUseSchemaValidation;
+    }
+
+    /**
+     * Escapes invalid XML characters in the given character data using XML entities. For the
+     * moment, only the following characters are being escaped: (<), (&), (') and (").
+     * 
+     * Remark: we don't escape the (>) character to keep the readability of the configuration
+     * mapping! The XML spec only requires that the (&) and (<) characters are being escaped inside
+     * character data.
+     * 
+     * @param text
+     *            the character data to escape
+     * @return the escaped character data
+     */
+    public static String escape(String text) {
+        if (text == null) {
+            return null;
+        }
+
+        StringBuffer result = new StringBuffer(text.length());
+
+        char[] chars = text.toCharArray();
+        for (int i = 0; i < chars.length; i++) {
+            switch (chars[i]) {
+                case '&':
+                    result.append("&");
+                    break;
+                case '<':
+                    result.append("<");
+                    break;
+                case '\'':
+                    result.append("'");
+                    break;
+                case '\"':
+                    result.append(""");
+                    break;
+                default:
+                    result.append(chars[i]);
+            }
+        }
+
+        return result.toString();
+    }
+
+    public static Document parseToDom(InputSource source, EntityResolver entityResolver)
+            throws IOException, SAXException {
+        DocumentBuilder docBuilder = getDocBuilder(entityResolver);
+        return docBuilder.parse(source);
+    }
+
+    public static DocumentBuilder getDocBuilder(EntityResolver entityResolver) {
+        try {
+            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+            factory.setValidating(false);
+            DocumentBuilder docBuilder = factory.newDocumentBuilder();
+            if (entityResolver != null) {
+                docBuilder.setEntityResolver(entityResolver);
+            }
+            return docBuilder;
+        } catch (ParserConfigurationException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private XMLHelper() {
+    }
+
+}
diff --git a/src/java/org/apache/ivy/util/cli/CommandLine.java b/src/java/org/apache/ivy/util/cli/CommandLine.java
new file mode 100644
index 0000000..64e42b8
--- /dev/null
+++ b/src/java/org/apache/ivy/util/cli/CommandLine.java
@@ -0,0 +1,58 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util.cli;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class CommandLine {
+    private Map/* <String, String[]> */optionValues = new HashMap();
+
+    private String[] leftOverArgs;
+
+    void addOptionValues(String option, String[] values) {
+        optionValues.put(option, values);
+    }
+
+    void setLeftOverArgs(String[] args) {
+        leftOverArgs = args;
+    }
+
+    public boolean hasOption(String option) {
+        return optionValues.containsKey(option);
+    }
+
+    public String getOptionValue(String option) {
+        String[] values = getOptionValues(option);
+        return values == null || values.length == 0 ? null : values[0];
+    }
+
+    public String getOptionValue(String option, String defaultValue) {
+        String value = getOptionValue(option);
+        return value == null ? defaultValue : value;
+    }
+
+    public String[] getOptionValues(String option) {
+        return (String[]) optionValues.get(option);
+    }
+
+    public String[] getLeftOverArgs() {
+        return leftOverArgs;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/util/cli/CommandLineParser.java b/src/java/org/apache/ivy/util/cli/CommandLineParser.java
new file mode 100644
index 0000000..7b2212d
--- /dev/null
+++ b/src/java/org/apache/ivy/util/cli/CommandLineParser.java
@@ -0,0 +1,141 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util.cli;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.ivy.util.StringUtils;
+
+public class CommandLineParser {
+    private static final int MIN_DESC_WIDTH = 40;
+
+    private static final int MAX_SPEC_WIDTH = 30;
+
+    private Map/* <String, Option> */options = new LinkedHashMap();
+
+    private Map/* <String, List<Option>> */categories = new LinkedHashMap();
+
+    public CommandLineParser() {
+    }
+
+    public CommandLineParser addCategory(String category) {
+        categories.put(category, new ArrayList());
+        return this;
+    }
+
+    public CommandLineParser addOption(Option option) {
+        options.put(option.getName(), option);
+        if (!categories.isEmpty()) {
+            ((List) categories.values().toArray()[categories.values().size() - 1]).add(option);
+        }
+        return this;
+    }
+
+    public CommandLine parse(String[] args) throws ParseException {
+        CommandLine line = new CommandLine();
+
+        int index = args.length;
+        ListIterator iterator = Arrays.asList(args).listIterator();
+        while (iterator.hasNext()) {
+            String arg = (String) iterator.next();
+            if ("--".equals(arg)) {
+                // skip this argument and stop looping
+                index = iterator.nextIndex();
+                break;
+            }
+
+            if (!arg.startsWith("-")) {
+                index = iterator.previousIndex();
+                break;
+            }
+
+            Option option = (Option) options.get(arg.substring(1));
+            if (option == null) {
+                throw new ParseException("Unrecognized option: " + arg);
+            }
+            line.addOptionValues(arg.substring(1), option.parse(iterator));
+        }
+
+        // left over args
+        String[] leftOverArgs = new String[args.length - index];
+        System.arraycopy(args, index, leftOverArgs, 0, leftOverArgs.length);
+        line.setLeftOverArgs(leftOverArgs);
+
+        return line;
+    }
+
+    public void printHelp(PrintWriter pw, int width, String command, boolean showDeprecated) {
+        pw.println("usage: " + command);
+        // compute the largest option spec
+        int specWidth = 0;
+        for (Iterator iterator = options.values().iterator(); iterator.hasNext();) {
+            Option option = (Option) iterator.next();
+            if (option.isDeprecated() && !showDeprecated) {
+                continue;
+            }
+            specWidth = Math.min(MAX_SPEC_WIDTH, Math.max(specWidth, option.getSpec().length()));
+        }
+
+        // print options help
+        for (Iterator iterator = categories.entrySet().iterator(); iterator.hasNext();) {
+            Entry entry = (Entry) iterator.next();
+            String category = (String) entry.getKey();
+            pw.println("==== " + category);
+            List/* <Option> */options = (List) entry.getValue();
+            for (Iterator it = options.iterator(); it.hasNext();) {
+                Option option = (Option) it.next();
+                if (option.isDeprecated() && !showDeprecated) {
+                    continue;
+                }
+                // print option spec: option name + argument names
+                String spec = option.getSpec();
+                pw.print(" " + spec);
+                int specLength = spec.length() + 1;
+                pw.print(StringUtils.repeat(" ", specWidth - specLength));
+
+                // print description
+                StringBuffer desc = new StringBuffer((option.isDeprecated() ? "DEPRECATED: " : "")
+                        + option.getDescription());
+                int count = Math.min(desc.length(), width - Math.max(specLength, specWidth));
+                // see if we have enough space to start on the same line as the spec
+                if (count > MIN_DESC_WIDTH || desc.length() + specLength < width) {
+                    pw.print(desc.substring(0, count));
+                    desc.delete(0, count);
+                }
+                pw.println();
+
+                // print remaining description
+                while (desc.length() > 0) {
+                    pw.print(StringUtils.repeat(" ", specWidth));
+                    count = Math.min(desc.length(), width - specWidth);
+                    pw.println(desc.substring(0, count));
+                    desc.delete(0, count);
+                }
+            }
+            pw.println();
+        }
+    }
+}
diff --git a/src/java/org/apache/ivy/util/cli/Option.java b/src/java/org/apache/ivy/util/cli/Option.java
new file mode 100644
index 0000000..da91565
--- /dev/null
+++ b/src/java/org/apache/ivy/util/cli/Option.java
@@ -0,0 +1,124 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util.cli;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ListIterator;
+
+public class Option {
+    private String name;
+
+    private String[] args;
+
+    private String description;
+
+    private boolean required;
+
+    private boolean countArgs;
+
+    private boolean deprecated;
+
+    Option(String name, String[] args, String description, boolean required, boolean countArgs,
+            boolean deprecated) {
+        this.name = name;
+        this.args = args;
+        this.description = description;
+        this.required = required;
+        this.countArgs = countArgs;
+        this.deprecated = deprecated;
+        if (required) {
+            throw new UnsupportedOperationException("required option not supported yet");
+        }
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String[] getArgs() {
+        return args;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public boolean isRequired() {
+        return required;
+    }
+
+    public boolean isCountArgs() {
+        return countArgs;
+    }
+
+    public boolean isDeprecated() {
+        return deprecated;
+    }
+
+    String[] parse(ListIterator iterator) throws ParseException {
+        if (isCountArgs()) {
+            String[] values = new String[args.length];
+            for (int i = 0; i < values.length; i++) {
+                if (!iterator.hasNext()) {
+                    missingArgument(i);
+                }
+                values[i] = (String) iterator.next();
+                if (values[i].startsWith("-")) {
+                    missingArgument(i);
+                }
+            }
+            return values;
+        } else {
+            List values = new ArrayList();
+            while (iterator.hasNext()) {
+                String value = (String) iterator.next();
+                if (value.startsWith("-")) {
+                    iterator.previous();
+                    break;
+                }
+                values.add(value);
+            }
+            return (String[]) values.toArray(new String[values.size()]);
+        }
+    }
+
+    private void missingArgument(int i) throws ParseException {
+        if (i == 0) {
+            throw new ParseException("no argument for: " + name);
+        } else {
+            throw new ParseException("missing argument for: " + name + ". Expected: "
+                    + getArgsSpec());
+        }
+    }
+
+    public String getSpec() {
+        return "-" + name + " " + getArgsSpec();
+    }
+
+    private String getArgsSpec() {
+        if (args.length == 0) {
+            return "";
+        }
+        StringBuffer sb = new StringBuffer();
+        for (int i = 0; i < args.length; i++) {
+            sb.append("<").append(args[i]).append("> ");
+        }
+        return sb.toString();
+    }
+}
diff --git a/src/java/org/apache/ivy/util/cli/OptionBuilder.java b/src/java/org/apache/ivy/util/cli/OptionBuilder.java
new file mode 100644
index 0000000..59f3250
--- /dev/null
+++ b/src/java/org/apache/ivy/util/cli/OptionBuilder.java
@@ -0,0 +1,69 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util.cli;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class OptionBuilder {
+    private String name;
+
+    private List/* <String> */args = new ArrayList();
+
+    private String description = "";
+
+    private boolean required = false;
+
+    private boolean countArgs = true;
+
+    private boolean deprecated = false;
+
+    public OptionBuilder(String name) {
+        this.name = name;
+    }
+
+    public OptionBuilder required(boolean required) {
+        this.required = required;
+        return this;
+    }
+
+    public OptionBuilder description(String description) {
+        this.description = description;
+        return this;
+    }
+
+    public OptionBuilder arg(String argName) {
+        this.args.add(argName);
+        return this;
+    }
+
+    public OptionBuilder countArgs(boolean countArgs) {
+        this.countArgs = countArgs;
+        return this;
+    }
+
+    public OptionBuilder deprecated() {
+        this.deprecated = true;
+        return this;
+    }
+
+    public Option create() {
+        return new Option(name, (String[]) args.toArray(new String[args.size()]), description,
+                required, countArgs, deprecated);
+    }
+}
diff --git a/src/java/org/apache/ivy/util/cli/ParseException.java b/src/java/org/apache/ivy/util/cli/ParseException.java
new file mode 100644
index 0000000..93fe6dc
--- /dev/null
+++ b/src/java/org/apache/ivy/util/cli/ParseException.java
@@ -0,0 +1,24 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util.cli;
+
+public class ParseException extends Exception {
+    public ParseException(String reason) {
+        super(reason);
+    }
+}
diff --git a/src/java/org/apache/ivy/util/extendable/DefaultExtendableItem.java b/src/java/org/apache/ivy/util/extendable/DefaultExtendableItem.java
new file mode 100644
index 0000000..5f47699
--- /dev/null
+++ b/src/java/org/apache/ivy/util/extendable/DefaultExtendableItem.java
@@ -0,0 +1,38 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util.extendable;
+
+import java.util.Map;
+
+/**
+ * An item which is meant to be extended, i.e. defined using extra attributes
+ */
+public class DefaultExtendableItem extends UnmodifiableExtendableItem {
+    public DefaultExtendableItem() {
+        this(null, null);
+    }
+
+    public DefaultExtendableItem(Map stdAttributes, Map extraAttributes) {
+        super(stdAttributes, extraAttributes);
+    }
+
+    public void setExtraAttribute(String attName, String attValue) {
+        super.setExtraAttribute(attName, attValue);
+    }
+
+}
diff --git a/src/java/org/apache/ivy/util/extendable/ExtendableItem.java b/src/java/org/apache/ivy/util/extendable/ExtendableItem.java
new file mode 100644
index 0000000..91fb129
--- /dev/null
+++ b/src/java/org/apache/ivy/util/extendable/ExtendableItem.java
@@ -0,0 +1,78 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util.extendable;
+
+import java.util.Map;
+
+public interface ExtendableItem {
+    /**
+     * Gets the value of an attribute Can be used to access the value of a standard attribute (like
+     * organisation, revision) or of an extra attribute.
+     * 
+     * @param attName
+     *            the name of the attribute to get
+     * @return the value of the attribute, null if the attribute doesn't exist
+     */
+    String getAttribute(String attName);
+
+    /**
+     * Gets the value of an extra attribute Can be used only to access the value of an extra
+     * attribute, not a standard one (like organisation, revision)
+     * 
+     * @param attName
+     *            the name of the extra attribute to get. This name can be either qualified or
+     *            unqualified.
+     * @return the value of the attribute, null if the attribute doesn't exist
+     */
+    String getExtraAttribute(String attName);
+
+    /**
+     * Returns a Map of all attributes of this extendable item, including standard and extra ones.
+     * The Map keys are attribute names as Strings, and values are corresponding attribute values
+     * (as String too). Extra attributes are included in unqualified form only.
+     * 
+     * @return A Map instance containing all the attributes and their values.
+     */
+    Map getAttributes();
+
+    /**
+     * Returns a Map of all extra attributes of this extendable item. The Map keys are
+     * <b>unqualified</b> attribute names as Strings, and values are corresponding attribute values
+     * (as String too)
+     * 
+     * @return A Map instance containing all the extra attributes and their values.
+     * @see #getQualifiedExtraAttributes()
+     */
+    Map getExtraAttributes();
+
+    /**
+     * Returns a Map of all extra attributes of this extendable item.
+     * <p>
+     * The Map keys are <b>qualified</b> attribute names as Strings, and values are corresponding
+     * attribute values (as String too).
+     * </p>
+     * <p>
+     * An attribute name is qualified with a namespace exactly the same way xml attributes are
+     * qualified. Thus qualified attribute names are of the form <code>prefix:name</code>
+     * </p>
+     * 
+     * @return A Map instance containing all the extra attributes and their values.
+     * @see #getExtraAttributes()
+     */
+    Map getQualifiedExtraAttributes();
+}
diff --git a/src/java/org/apache/ivy/util/extendable/ExtendableItemHelper.java b/src/java/org/apache/ivy/util/extendable/ExtendableItemHelper.java
new file mode 100644
index 0000000..cfb4340
--- /dev/null
+++ b/src/java/org/apache/ivy/util/extendable/ExtendableItemHelper.java
@@ -0,0 +1,74 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util.extendable;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.ivy.plugins.parser.ParserSettings;
+import org.xml.sax.Attributes;
+
+public final class ExtendableItemHelper {
+    private ExtendableItemHelper() {
+    }
+
+    public static Map getExtraAttributes(Attributes attributes, String prefix) {
+        Map ret = new HashMap();
+        for (int i = 0; i < attributes.getLength(); i++) {
+            if (attributes.getQName(i).startsWith(prefix)) {
+                ret.put(attributes.getQName(i).substring(prefix.length()), attributes.getValue(i));
+            }
+        }
+        return ret;
+    }
+
+    /**
+     * Extract from the XML attribute the extra Ivy ones
+     * 
+     * @param settings
+     * @param attributes
+     * @param ignoredAttNames
+     *            the XML attributes names which are not extra but Ivy core ones
+     * @return
+     */
+    public static Map getExtraAttributes(ParserSettings settings, Attributes attributes,
+            String[] ignoredAttNames) {
+        Map ret = new HashMap();
+        Collection ignored = Arrays.asList(ignoredAttNames);
+        for (int i = 0; i < attributes.getLength(); i++) {
+            if (!ignored.contains(attributes.getQName(i))) {
+                ret.put(attributes.getQName(i), settings.substitute(attributes.getValue(i)));
+            }
+        }
+        return ret;
+    }
+
+    public static void fillExtraAttributes(ParserSettings settings, DefaultExtendableItem item,
+            Attributes attributes, String[] ignoredAttNames) {
+        Map att = getExtraAttributes(settings, attributes, ignoredAttNames);
+        for (Iterator iter = att.keySet().iterator(); iter.hasNext();) {
+            String attName = (String) iter.next();
+            String attValue = (String) att.get(attName);
+            item.setExtraAttribute(attName, attValue);
+        }
+    }
+
+}
diff --git a/src/java/org/apache/ivy/util/extendable/UnmodifiableExtendableItem.java b/src/java/org/apache/ivy/util/extendable/UnmodifiableExtendableItem.java
new file mode 100644
index 0000000..6342569
--- /dev/null
+++ b/src/java/org/apache/ivy/util/extendable/UnmodifiableExtendableItem.java
@@ -0,0 +1,97 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util.extendable;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+
+public class UnmodifiableExtendableItem implements ExtendableItem {
+    private final Map attributes = new HashMap();
+
+    private final Map unmodifiableAttributesView = Collections.unmodifiableMap(attributes);
+
+    private final Map extraAttributes = new HashMap();
+
+    private final Map unmodifiableExtraAttributesView = Collections
+            .unmodifiableMap(extraAttributes);
+
+    /*
+     * this is the only place where extra attributes are stored in qualified form. In all other maps
+     * they are stored unqualified.
+     */
+    private final Map qualifiedExtraAttributes = new HashMap();
+
+    private final Map unmodifiableQualifiedExtraAttributesView = Collections
+            .unmodifiableMap(qualifiedExtraAttributes);
+
+    public UnmodifiableExtendableItem(Map stdAttributes, Map extraAttributes) {
+        if (stdAttributes != null) {
+            this.attributes.putAll(stdAttributes);
+        }
+        if (extraAttributes != null) {
+            for (Iterator iter = extraAttributes.entrySet().iterator(); iter.hasNext();) {
+                Entry extraAtt = (Entry) iter.next();
+                setExtraAttribute((String) extraAtt.getKey(), (String) extraAtt.getValue());
+            }
+        }
+    }
+
+    public String getAttribute(String attName) {
+        return (String) attributes.get(attName);
+    }
+
+    public String getExtraAttribute(String attName) {
+        String v = (String) qualifiedExtraAttributes.get(attName);
+        if (v == null) {
+            v = (String) extraAttributes.get(attName);
+        }
+        return v;
+    }
+
+    protected void setExtraAttribute(String attName, String attValue) {
+        qualifiedExtraAttributes.put(attName, attValue);
+
+        // unqualify att name if required
+        int index = attName.indexOf(':');
+        if (index != -1) {
+            attName = attName.substring(index + 1);
+        }
+        extraAttributes.put(attName, attValue);
+        attributes.put(attName, attValue);
+    }
+
+    protected void setStandardAttribute(String attName, String attValue) {
+        attributes.put(attName, attValue);
+    }
+
+    public Map getAttributes() {
+        return unmodifiableAttributesView;
+    }
+
+    public Map getExtraAttributes() {
+        return unmodifiableExtraAttributesView;
+    }
+
+    public Map getQualifiedExtraAttributes() {
+        return unmodifiableQualifiedExtraAttributesView;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/util/filter/AndFilter.java b/src/java/org/apache/ivy/util/filter/AndFilter.java
new file mode 100644
index 0000000..f747469
--- /dev/null
+++ b/src/java/org/apache/ivy/util/filter/AndFilter.java
@@ -0,0 +1,41 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util.filter;
+
+public class AndFilter implements Filter {
+    private Filter op1;
+
+    private Filter op2;
+
+    public AndFilter(Filter op1, Filter op2) {
+        this.op1 = op1;
+        this.op2 = op2;
+    }
+
+    public Filter getOp1() {
+        return op1;
+    }
+
+    public Filter getOp2() {
+        return op2;
+    }
+
+    public boolean accept(Object o) {
+        return op1.accept(o) && op2.accept(o);
+    }
+}
diff --git a/src/java/org/apache/ivy/util/filter/ArtifactTypeFilter.java b/src/java/org/apache/ivy/util/filter/ArtifactTypeFilter.java
new file mode 100644
index 0000000..932a891
--- /dev/null
+++ b/src/java/org/apache/ivy/util/filter/ArtifactTypeFilter.java
@@ -0,0 +1,39 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util.filter;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+
+public class ArtifactTypeFilter implements Filter {
+    private Collection acceptedTypes;
+
+    public ArtifactTypeFilter(Collection acceptedTypes) {
+        this.acceptedTypes = new ArrayList(acceptedTypes);
+    }
+
+    public boolean accept(Object o) {
+        if (!(o instanceof Artifact)) {
+            return false;
+        }
+        Artifact art = (Artifact) o;
+        return acceptedTypes.contains(art.getType());
+    }
+}
diff --git a/src/java/org/apache/ivy/util/filter/Filter.java b/src/java/org/apache/ivy/util/filter/Filter.java
new file mode 100644
index 0000000..2950e14
--- /dev/null
+++ b/src/java/org/apache/ivy/util/filter/Filter.java
@@ -0,0 +1,22 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util.filter;
+
+public interface Filter {
+    boolean accept(Object o);
+}
diff --git a/src/java/org/apache/ivy/util/filter/FilterHelper.java b/src/java/org/apache/ivy/util/filter/FilterHelper.java
new file mode 100644
index 0000000..9d59ccd
--- /dev/null
+++ b/src/java/org/apache/ivy/util/filter/FilterHelper.java
@@ -0,0 +1,82 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util.filter;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+public final class FilterHelper {
+    private FilterHelper() {
+    }
+
+    public static final Filter NO_FILTER = NoFilter.INSTANCE;
+
+    public static Filter getArtifactTypeFilter(String types) {
+        if (types == null || types.trim().equals("*")) {
+            return NO_FILTER;
+        }
+        String[] t = types.split(",");
+        return getArtifactTypeFilter(t);
+    }
+
+    public static Filter getArtifactTypeFilter(String[] types) {
+        if (types == null || types.length == 0) {
+            return NO_FILTER;
+        }
+        List acceptedTypes = new ArrayList(types.length);
+        for (int i = 0; i < types.length; i++) {
+            String current = types[i].trim();
+            if ("*".equals(current)) {
+                return NO_FILTER;
+            }
+            acceptedTypes.add(current);
+        }
+        return new ArtifactTypeFilter(acceptedTypes);
+    }
+
+    /**
+     * Returns a new collection containing only the items from the given collectoin, which are
+     * accepted by the filter.
+     * 
+     * @param col
+     *            The collection to filter.
+     * @param filter
+     *            The filter to use.
+     * @return A new collection instance containing the only the instance accepted by the filter.
+     * 
+     * <br />
+     *         Comment: We could have used <a
+     *         href="http://jakarta.apache.org/commons/collections/">Commons-Collections</a>
+     *         facility for this. If we accepted to add dependencies on third party jars.
+     */
+    public static Collection filter(Collection col, Filter filter) {
+        if (filter == null) {
+            return col;
+        }
+        Collection ret = new ArrayList(col);
+        for (Iterator iter = ret.iterator(); iter.hasNext();) {
+            Object element = iter.next();
+            if (!filter.accept(element)) {
+                iter.remove();
+            }
+        }
+        return ret;
+    }
+}
diff --git a/src/java/org/apache/ivy/util/filter/NoFilter.java b/src/java/org/apache/ivy/util/filter/NoFilter.java
new file mode 100644
index 0000000..92dbbe5
--- /dev/null
+++ b/src/java/org/apache/ivy/util/filter/NoFilter.java
@@ -0,0 +1,33 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util.filter;
+
+public final class NoFilter implements Filter {
+    public static final Filter INSTANCE = new NoFilter();
+
+    private NoFilter() {
+    }
+
+    public boolean accept(Object o) {
+        return true;
+    }
+
+    public String toString() {
+        return "NoFilter";
+    }
+}
diff --git a/src/java/org/apache/ivy/util/filter/NotFilter.java b/src/java/org/apache/ivy/util/filter/NotFilter.java
new file mode 100644
index 0000000..7d55004
--- /dev/null
+++ b/src/java/org/apache/ivy/util/filter/NotFilter.java
@@ -0,0 +1,34 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util.filter;
+
+public class NotFilter implements Filter {
+    private Filter op;
+
+    public NotFilter(Filter op) {
+        this.op = op;
+    }
+
+    public Filter getOp() {
+        return op;
+    }
+
+    public boolean accept(Object o) {
+        return !op.accept(o);
+    }
+}
diff --git a/src/java/org/apache/ivy/util/filter/OrFilter.java b/src/java/org/apache/ivy/util/filter/OrFilter.java
new file mode 100644
index 0000000..46f1571
--- /dev/null
+++ b/src/java/org/apache/ivy/util/filter/OrFilter.java
@@ -0,0 +1,41 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util.filter;
+
+public class OrFilter implements Filter {
+    private Filter op1;
+
+    private Filter op2;
+
+    public OrFilter(Filter op1, Filter op2) {
+        this.op1 = op1;
+        this.op2 = op2;
+    }
+
+    public Filter getOp1() {
+        return op1;
+    }
+
+    public Filter getOp2() {
+        return op2;
+    }
+
+    public boolean accept(Object o) {
+        return op1.accept(o) || op2.accept(o);
+    }
+}
diff --git a/src/java/org/apache/ivy/util/url/AbstractURLHandler.java b/src/java/org/apache/ivy/util/url/AbstractURLHandler.java
new file mode 100644
index 0000000..2029724
--- /dev/null
+++ b/src/java/org/apache/ivy/util/url/AbstractURLHandler.java
@@ -0,0 +1,176 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util.url;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.regex.Pattern;
+import java.util.zip.DataFormatException;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.Inflater;
+import java.util.zip.InflaterInputStream;
+import org.apache.ivy.Ivy;
+
+public abstract class AbstractURLHandler implements URLHandler {
+
+    private static final Pattern ESCAPE_PATTERN = Pattern.compile("%25([0-9a-fA-F][0-9a-fA-F])");
+
+    // the request method to use. TODO: don't use a static here
+    private static int requestMethod = REQUEST_METHOD_HEAD;
+
+    public boolean isReachable(URL url) {
+        return getURLInfo(url).isReachable();
+    }
+
+    public boolean isReachable(URL url, int timeout) {
+        return getURLInfo(url, timeout).isReachable();
+    }
+
+    public long getContentLength(URL url) {
+        return getURLInfo(url).getContentLength();
+    }
+
+    public long getContentLength(URL url, int timeout) {
+        return getURLInfo(url, timeout).getContentLength();
+    }
+
+    public long getLastModified(URL url) {
+        return getURLInfo(url).getLastModified();
+    }
+
+    public long getLastModified(URL url, int timeout) {
+        return getURLInfo(url, timeout).getLastModified();
+    }
+
+    protected String getUserAgent() {
+        String userAgent = System.getProperty("http.agent");
+        if (userAgent == null) {
+            userAgent = "Apache Ivy/" + Ivy.getIvyVersion();
+        }
+        return userAgent;
+    }
+
+    protected void validatePutStatusCode(URL dest, int statusCode, String statusMessage)
+            throws IOException {
+        switch (statusCode) {
+            case HttpURLConnection.HTTP_OK:
+                /* intentional fallthrough */
+            case HttpURLConnection.HTTP_CREATED:
+                /* intentional fallthrough */
+            case HttpURLConnection.HTTP_ACCEPTED:
+                /* intentional fallthrough */
+            case HttpURLConnection.HTTP_NO_CONTENT:
+                break;
+            case HttpURLConnection.HTTP_UNAUTHORIZED:
+                /* intentional fallthrough */
+            case HttpURLConnection.HTTP_FORBIDDEN:
+                throw new IOException("Access to URL " + dest + " was refused by the server"
+                        + (statusMessage == null ? "" : ": " + statusMessage));
+            default:
+                throw new IOException("PUT operation to URL " + dest + " failed with status code "
+                        + statusCode + (statusMessage == null ? "" : ": " + statusMessage));
+        }
+    }
+
+    public void setRequestMethod(int requestMethod) {
+        AbstractURLHandler.requestMethod = requestMethod;
+    }
+
+    public int getRequestMethod() {
+        return requestMethod;
+    }
+
+    protected String normalizeToString(URL url) throws IOException {
+        if (!"http".equals(url.getProtocol()) && !"https".equals(url.getProtocol())) {
+            return url.toExternalForm();
+        }
+
+        try {
+            URI uri = new URI(url.getProtocol(), url.getAuthority(), url.getPath(), url.getQuery(),
+                    url.getRef());
+
+            // it is possible that the original url was already (partial) escaped,
+            // so we must unescape all '%' followed by 2 hexadecimals...
+            String uriString = uri.normalize().toASCIIString();
+
+            // manually escape the '+' character
+            uriString = uriString.replaceAll("\\+", "%2B");
+
+            return ESCAPE_PATTERN.matcher(uriString).replaceAll("%$1");
+        } catch (URISyntaxException e) {
+            IOException ioe = new MalformedURLException("Couldn't convert '" + url.toString()
+                    + "' to a valid URI");
+            ioe.initCause(e);
+            throw ioe;
+        }
+    }
+
+    protected URL normalizeToURL(URL url) throws IOException {
+        if (!"http".equals(url.getProtocol()) && !"https".equals(url.getProtocol())) {
+            return url;
+        }
+
+        return new URL(normalizeToString(url));
+    }
+
+    protected InputStream getDecodingInputStream(String encoding, InputStream in)
+            throws IOException {
+        InputStream result = null;
+
+        if ("gzip".equals(encoding) || "x-gzip".equals(encoding)) {
+            result = new GZIPInputStream(in);
+        } else if ("deflate".equals(encoding)) {
+            // There seems to be 2 variants of the "deflate"-encoding.
+            // I couldn't find a way to auto-detect which variant was
+            // used, so as (a not really good) work-around we try do
+            // decompress the first 100 bytes using the "zlib"-variant.
+            BufferedInputStream bStream = new BufferedInputStream(in);
+            bStream.mark(100);
+            byte[] bytes = new byte[100];
+            int nbBytes = bStream.read(bytes);
+            bStream.reset();
+
+            Inflater inflater = new Inflater();
+            inflater.setInput(bytes, 0, nbBytes);
+            try {
+                inflater.inflate(new byte[1000]);
+
+                // no error decompressing the first 100 bytes, so we
+                // assume the "zlib"-variant was used.
+                result = new InflaterInputStream(bStream);
+            } catch (DataFormatException e) {
+                // there was an error decompressing the first 100 bytes,
+                // so we assume the "gzip/raw"-variant was used.
+                result = new InflaterInputStream(bStream, new Inflater(true));
+            } finally {
+                inflater.end();
+            }
+        } else {
+            result = in;
+        }
+
+        return result;
+    }
+
+}
diff --git a/src/java/org/apache/ivy/util/url/ApacheURLLister.java b/src/java/org/apache/ivy/util/url/ApacheURLLister.java
new file mode 100644
index 0000000..e2e7dea
--- /dev/null
+++ b/src/java/org/apache/ivy/util/url/ApacheURLLister.java
@@ -0,0 +1,209 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util.url;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.ivy.util.FileUtil;
+import org.apache.ivy.util.Message;
+import org.apache.ivy.util.url.URLHandler.URLInfo;
+
+/**
+ * Utility class which helps to list urls under a given url. This has been tested with Apache 1.3.33
+ * server listing, as the one used at ibiblio, and with Apache 2.0.53 server listing, as the one on
+ * mirrors.sunsite.dk.
+ */
+public class ApacheURLLister {
+    // ~ Static variables/initializers ------------------------------------------
+
+    private static final Pattern PATTERN = Pattern.compile(
+        "<a[^>]*href=\"([^\"]*)\"[^>]*>(?:<[^>]+>)*?([^<>]+?)(?:<[^>]+>)*?</a>",
+        Pattern.CASE_INSENSITIVE);
+
+    // ~ Methods ----------------------------------------------------------------
+
+    /**
+     * Returns a list of sub urls of the given url. The returned list is a list of URL.
+     * 
+     * @param url
+     *            The base URL from which to retrieve the listing.
+     * @return a list of sub urls of the given url.
+     * @throws IOException
+     *             If an error occures retrieving the HTML.
+     */
+    public List listAll(URL url) throws IOException {
+        return retrieveListing(url, true, true);
+    }
+
+    /**
+     * Returns a list of sub 'directories' of the given url. The returned list is a list of URL.
+     * 
+     * @param url
+     *            The base URL from which to retrieve the listing.
+     * @return a list of sub 'directories' of the given url.
+     * @throws IOException
+     *             If an error occures retrieving the HTML.
+     */
+    public List listDirectories(URL url) throws IOException {
+        return retrieveListing(url, false, true);
+    }
+
+    /**
+     * Returns a list of sub 'files' (in opposition to directories) of the given url. The returned
+     * list is a list of URL.
+     * 
+     * @param url
+     *            The base URL from which to retrieve the listing.
+     * @return a list of sub 'files' of the given url.
+     * @throws IOException
+     *             If an error occures retrieving the HTML.
+     */
+    public List listFiles(URL url) throws IOException {
+        return retrieveListing(url, true, false);
+    }
+
+    /**
+     * Retrieves a {@link List} of {@link URL}s corresponding to the files and/or directories found
+     * at the supplied base URL.
+     * 
+     * @param url
+     *            The base URL from which to retrieve the listing.
+     * @param includeFiles
+     *            If true include files in the returned list.
+     * @param includeDirectories
+     *            If true include directories in the returned list.
+     * @return A {@link List} of {@link URL}s.
+     * @throws IOException
+     *             If an error occures retrieving the HTML.
+     */
+    public List retrieveListing(URL url, boolean includeFiles, boolean includeDirectories)
+            throws IOException {
+        List urlList = new ArrayList();
+
+        // add trailing slash for relative urls
+        if (!url.getPath().endsWith("/") && !url.getPath().endsWith(".html")) {
+            url = new URL(url.getProtocol(), url.getHost(), url.getPort(), url.getPath() + "/");
+        }
+
+        URLHandler urlHandler = URLHandlerRegistry.getDefault();
+        URLInfo urlInfo = urlHandler.getURLInfo(url);
+        if (urlInfo == URLHandler.UNAVAILABLE) {
+            return urlList; // not found => return empty list
+        }
+        // here, urlInfo is valid
+        String charset = urlInfo.getBodyCharset();
+
+        InputStream contentStream = urlHandler.openStream(url);
+        BufferedReader r = null;
+        if (charset == null) {
+            r = new BufferedReader(new InputStreamReader(contentStream));
+        } else {
+            r = new BufferedReader(new InputStreamReader(contentStream, charset));
+        }
+
+        String htmlText = FileUtil.readEntirely(r);
+
+        Matcher matcher = PATTERN.matcher(htmlText);
+
+        while (matcher.find()) {
+            // get the href text and the displayed text
+            String href = matcher.group(1);
+            String text = matcher.group(2);
+
+            if ((href == null) || (text == null)) {
+                // the groups were not found (shouldn't happen, really)
+                continue;
+            }
+
+            text = text.trim();
+
+            // handle complete URL listings
+            if (href.startsWith("http:") || href.startsWith("https:")) {
+                try {
+                    href = new URL(href).getPath();
+                    if (!href.startsWith(url.getPath())) {
+                        // ignore URLs which aren't children of the base URL
+                        continue;
+                    }
+                    href = href.substring(url.getPath().length());
+                } catch (Exception ignore) {
+                    // incorrect URL, ignore
+                    continue;
+                }
+            }
+
+            if (href.startsWith("../")) {
+                // we are only interested in sub-URLs, not parent URLs, so skip this one
+                continue;
+            }
+
+            // absolute href: convert to relative one
+            if (href.startsWith("/")) {
+                int slashIndex = href.substring(0, href.length() - 1).lastIndexOf('/');
+                href = href.substring(slashIndex + 1);
+            }
+
+            // relative to current href: convert to simple relative one
+            if (href.startsWith("./")) {
+                href = href.substring("./".length());
+            }
+
+            // exclude those where they do not match
+            // href will never be truncated, text may be truncated by apache
+            if (text.endsWith("..>")) {
+                // text is probably truncated, we can only check if the href starts with text
+                if (!href.startsWith(text.substring(0, text.length() - 3))) {
+                    continue;
+                }
+            } else if (text.endsWith("..>")) {
+                // text is probably truncated, we can only check if the href starts with text
+                if (!href.startsWith(text.substring(0, text.length() - 6))) {
+                    continue;
+                }
+            } else {
+                // text is not truncated, so it must match the url after stripping optional
+                // trailing slashes
+                String strippedHref = href.endsWith("/") ? href.substring(0, href.length() - 1)
+                        : href;
+                String strippedText = text.endsWith("/") ? text.substring(0, text.length() - 1)
+                        : text;
+                if (!strippedHref.equalsIgnoreCase(strippedText)) {
+                    continue;
+                }
+            }
+
+            boolean directory = href.endsWith("/");
+
+            if ((directory && includeDirectories) || (!directory && includeFiles)) {
+                URL child = new URL(url, href);
+                urlList.add(child);
+                Message.debug("ApacheURLLister found URL=[" + child + "].");
+            }
+        }
+
+        return urlList;
+    }
+}
diff --git a/src/java/org/apache/ivy/util/url/BasicURLHandler.java b/src/java/org/apache/ivy/util/url/BasicURLHandler.java
new file mode 100644
index 0000000..72641ef
--- /dev/null
+++ b/src/java/org/apache/ivy/util/url/BasicURLHandler.java
@@ -0,0 +1,330 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util.url;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.UnknownHostException;
+
+import org.apache.ivy.Ivy;
+import org.apache.ivy.util.CopyProgressListener;
+import org.apache.ivy.util.FileUtil;
+import org.apache.ivy.util.Message;
+
+/**
+ * 
+ */
+public class BasicURLHandler extends AbstractURLHandler {
+
+    private static final int BUFFER_SIZE = 64 * 1024;
+
+    private static final class HttpStatus {
+        static final int SC_OK = 200;
+
+        static final int SC_PROXY_AUTHENTICATION_REQUIRED = 407;
+
+        private HttpStatus() {
+        }
+    }
+
+    public URLInfo getURLInfo(URL url) {
+        return getURLInfo(url, 0);
+    }
+
+    public URLInfo getURLInfo(URL url, int timeout) {
+        // Install the IvyAuthenticator
+        if ("http".equals(url.getProtocol()) || "https".equals(url.getProtocol())) {
+            IvyAuthenticator.install();
+        }
+
+        URLConnection con = null;
+        try {
+            url = normalizeToURL(url);
+            con = url.openConnection();
+            con.setRequestProperty("User-Agent", getUserAgent());
+            if (con instanceof HttpURLConnection) {
+                HttpURLConnection httpCon = (HttpURLConnection) con;
+                if (getRequestMethod() == URLHandler.REQUEST_METHOD_HEAD) {
+                    httpCon.setRequestMethod("HEAD");
+                }
+                if (checkStatusCode(url, httpCon)) {
+                    String bodyCharset = getCharSetFromContentType(con.getContentType());
+                    return new URLInfo(true, httpCon.getContentLength(), con.getLastModified(),
+                            bodyCharset);
+                }
+            } else {
+                int contentLength = con.getContentLength();
+                if (contentLength <= 0) {
+                    return UNAVAILABLE;
+                } else {
+                    // TODO: not HTTP... maybe we *don't* want to default to ISO-8559-1 here?
+                    String bodyCharset = getCharSetFromContentType(con.getContentType());
+                    return new URLInfo(true, contentLength, con.getLastModified(), bodyCharset);
+                }
+            }
+        } catch (UnknownHostException e) {
+            Message.warn("Host " + e.getMessage() + " not found. url=" + url);
+            Message.info("You probably access the destination server through "
+                    + "a proxy server that is not well configured.");
+        } catch (IOException e) {
+            Message.error("Server access error at url " + url, e);
+        } finally {
+            disconnect(con);
+        }
+        return UNAVAILABLE;
+    }
+
+    /**
+     * Extract the charset from the Content-Type header string, or default to ISO-8859-1 as per
+     * rfc2616-sec3.html#sec3.7.1 .
+     * 
+     * @param contentType
+     *            the Content-Type header string
+     * @return the charset as specified in the content type, or ISO-8859-1 if unspecified.
+     */
+    public static String getCharSetFromContentType(String contentType) {
+
+        String charSet = null;
+
+        if (contentType != null) {
+            String[] elements = contentType.split(";");
+            for (int i = 0; i < elements.length; i++) {
+                String element = elements[i].trim();
+                if (element.toLowerCase().startsWith("charset=")) {
+                    charSet = element.substring("charset=".length());
+                }
+            }
+        }
+
+        if (charSet == null || charSet.length() == 0) {
+            // default to ISO-8859-1 as per rfc2616-sec3.html#sec3.7.1
+            charSet = "ISO-8859-1";
+        }
+
+        return charSet;
+    }
+
+    private boolean checkStatusCode(URL url, HttpURLConnection con) throws IOException {
+        int status = con.getResponseCode();
+        if (status == HttpStatus.SC_OK) {
+            return true;
+        }
+
+        // IVY-1328: some servers return a 204 on a HEAD request
+        if ("HEAD".equals(con.getRequestMethod()) && (status == 204)) {
+            return true;
+        }
+
+        Message.debug("HTTP response status: " + status + " url=" + url);
+        if (status == HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED) {
+            Message.warn("Your proxy requires authentication.");
+        } else if (String.valueOf(status).startsWith("4")) {
+            Message.verbose("CLIENT ERROR: " + con.getResponseMessage() + " url=" + url);
+        } else if (String.valueOf(status).startsWith("5")) {
+            Message.error("SERVER ERROR: " + con.getResponseMessage() + " url=" + url);
+        }
+        return false;
+    }
+
+    public InputStream openStream(URL url) throws IOException {
+        // Install the IvyAuthenticator
+        if ("http".equals(url.getProtocol()) || "https".equals(url.getProtocol())) {
+            IvyAuthenticator.install();
+        }
+
+        URLConnection conn = null;
+        try {
+            url = normalizeToURL(url);
+            conn = url.openConnection();
+            conn.setRequestProperty("User-Agent", getUserAgent());
+            conn.setRequestProperty("Accept-Encoding", "gzip,deflate");
+            if (conn instanceof HttpURLConnection) {
+                HttpURLConnection httpCon = (HttpURLConnection) conn;
+                if (!checkStatusCode(url, httpCon)) {
+                    throw new IOException("The HTTP response code for " + url
+                            + " did not indicate a success." + " See log for more detail.");
+                }
+            }
+            InputStream inStream = getDecodingInputStream(conn.getContentEncoding(),
+                conn.getInputStream());
+            ByteArrayOutputStream outStream = new ByteArrayOutputStream();
+
+            byte[] buffer = new byte[BUFFER_SIZE];
+            int len;
+            while ((len = inStream.read(buffer)) > 0) {
+                outStream.write(buffer, 0, len);
+            }
+            return new ByteArrayInputStream(outStream.toByteArray());
+        } finally {
+            disconnect(conn);
+        }
+    }
+
+    public void download(URL src, File dest, CopyProgressListener l) throws IOException {
+        // Install the IvyAuthenticator
+        if ("http".equals(src.getProtocol()) || "https".equals(src.getProtocol())) {
+            IvyAuthenticator.install();
+        }
+
+        URLConnection srcConn = null;
+        try {
+            src = normalizeToURL(src);
+            srcConn = src.openConnection();
+            srcConn.setRequestProperty("User-Agent", getUserAgent());
+            srcConn.setRequestProperty("Accept-Encoding", "gzip,deflate");
+            if (srcConn instanceof HttpURLConnection) {
+                HttpURLConnection httpCon = (HttpURLConnection) srcConn;
+                if (!checkStatusCode(src, httpCon)) {
+                    throw new IOException("The HTTP response code for " + src
+                            + " did not indicate a success." + " See log for more detail.");
+                }
+            }
+
+            // do the download
+            InputStream inStream = getDecodingInputStream(srcConn.getContentEncoding(),
+                srcConn.getInputStream());
+            FileUtil.copy(inStream, dest, l);
+
+            // check content length only if content was not encoded
+            if (srcConn.getContentEncoding() == null) {
+                int contentLength = srcConn.getContentLength();
+                if (contentLength != -1 && dest.length() != contentLength) {
+                    dest.delete();
+                    throw new IOException(
+                            "Downloaded file size doesn't match expected Content Length for " + src
+                                    + ". Please retry.");
+                }
+            }
+
+            // update modification date
+            long lastModified = srcConn.getLastModified();
+            if (lastModified > 0) {
+                dest.setLastModified(lastModified);
+            }
+        } finally {
+            disconnect(srcConn);
+        }
+    }
+
+    public void upload(File source, URL dest, CopyProgressListener l) throws IOException {
+        if (!"http".equals(dest.getProtocol()) && !"https".equals(dest.getProtocol())) {
+            throw new UnsupportedOperationException(
+                    "URL repository only support HTTP PUT at the moment");
+        }
+
+        // Install the IvyAuthenticator
+        IvyAuthenticator.install();
+
+        HttpURLConnection conn = null;
+        try {
+            dest = normalizeToURL(dest);
+            conn = (HttpURLConnection) dest.openConnection();
+            conn.setDoOutput(true);
+            conn.setRequestMethod("PUT");
+            conn.setRequestProperty("User-Agent", getUserAgent());
+            conn.setRequestProperty("Content-type", "application/octet-stream");
+            conn.setRequestProperty("Content-length", Long.toString(source.length()));
+            conn.setInstanceFollowRedirects(true);
+
+            InputStream in = new FileInputStream(source);
+            try {
+                OutputStream os = conn.getOutputStream();
+                FileUtil.copy(in, os, l);
+            } finally {
+                try {
+                    in.close();
+                } catch (IOException e) {
+                    /* ignored */
+                }
+            }
+            validatePutStatusCode(dest, conn.getResponseCode(), conn.getResponseMessage());
+        } finally {
+            disconnect(conn);
+        }
+    }
+
+    private void disconnect(URLConnection con) {
+        if (con instanceof HttpURLConnection) {
+            if (!"HEAD".equals(((HttpURLConnection) con).getRequestMethod())) {
+                // We must read the response body before disconnecting!
+                // Cfr. http://java.sun.com/j2se/1.5.0/docs/guide/net/http-keepalive.html
+                // [quote]Do not abandon a connection by ignoring the response body. Doing
+                // so may results in idle TCP connections.[/quote]
+                readResponseBody((HttpURLConnection) con);
+            }
+
+            ((HttpURLConnection) con).disconnect();
+        } else if (con != null) {
+            try {
+                con.getInputStream().close();
+            } catch (IOException e) {
+                // ignored
+            }
+        }
+    }
+
+    /**
+     * Read and ignore the response body.
+     */
+    private void readResponseBody(HttpURLConnection conn) {
+        byte[] buffer = new byte[BUFFER_SIZE];
+
+        InputStream inStream = null;
+        try {
+            inStream = conn.getInputStream();
+            while (inStream.read(buffer) > 0) {
+                // Skip content
+            }
+        } catch (IOException e) {
+            // ignore
+        } finally {
+            if (inStream != null) {
+                try {
+                    inStream.close();
+                } catch (IOException e) {
+                    // ignore
+                }
+            }
+        }
+
+        InputStream errStream = conn.getErrorStream();
+        if (errStream != null) {
+            try {
+                while (errStream.read(buffer) > 0) {
+                    // Skip content
+                }
+            } catch (IOException e) {
+                // ignore
+            } finally {
+                try {
+                    errStream.close();
+                } catch (IOException e) {
+                    // ignore
+                }
+            }
+        }
+    }
+}
diff --git a/src/java/org/apache/ivy/util/url/CredentialsStore.java b/src/java/org/apache/ivy/util/url/CredentialsStore.java
new file mode 100644
index 0000000..50b973c
--- /dev/null
+++ b/src/java/org/apache/ivy/util/url/CredentialsStore.java
@@ -0,0 +1,64 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util.url;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.ivy.util.Credentials;
+import org.apache.ivy.util.Message;
+
+/**
+ * 
+ */
+public final class CredentialsStore {
+    /**
+     * A Map of Credentials objects keyed by the 'key' of the Credentials.
+     */
+    private static final Map KEYRING = new HashMap();
+
+    private static final Set SECURED_HOSTS = new HashSet();
+
+    public static final CredentialsStore INSTANCE = new CredentialsStore();
+
+    private CredentialsStore() {
+    }
+
+    public void addCredentials(String realm, String host, String userName, String passwd) {
+        if (userName == null) {
+            return;
+        }
+        Credentials c = new Credentials(realm, host, userName, passwd);
+        Message.debug("credentials added: " + c);
+        KEYRING.put(c.getKey(), c);
+        SECURED_HOSTS.add(host);
+    }
+
+    public Credentials getCredentials(String realm, String host) {
+        String key = Credentials.buildKey(realm, host);
+        Message.debug("try to get credentials for: " + key);
+        return (Credentials) KEYRING.get(key);
+    }
+
+    public boolean hasCredentials(String host) {
+        return SECURED_HOSTS.contains(host);
+    }
+
+}
diff --git a/src/java/org/apache/ivy/util/url/HttpClientHandler.java b/src/java/org/apache/ivy/util/url/HttpClientHandler.java
new file mode 100644
index 0000000..64d4a25
--- /dev/null
+++ b/src/java/org/apache/ivy/util/url/HttpClientHandler.java
@@ -0,0 +1,431 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util.url;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+import org.apache.commons.httpclient.Credentials;
+import org.apache.commons.httpclient.Header;
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.HttpException;
+import org.apache.commons.httpclient.HttpMethodBase;
+import org.apache.commons.httpclient.HttpStatus;
+import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
+import org.apache.commons.httpclient.NTCredentials;
+import org.apache.commons.httpclient.auth.AuthPolicy;
+import org.apache.commons.httpclient.auth.AuthScheme;
+import org.apache.commons.httpclient.auth.AuthScope;
+import org.apache.commons.httpclient.auth.CredentialsNotAvailableException;
+import org.apache.commons.httpclient.auth.CredentialsProvider;
+import org.apache.commons.httpclient.methods.GetMethod;
+import org.apache.commons.httpclient.methods.HeadMethod;
+import org.apache.commons.httpclient.methods.PutMethod;
+import org.apache.commons.httpclient.methods.RequestEntity;
+import org.apache.commons.httpclient.params.HttpMethodParams;
+import org.apache.ivy.Ivy;
+import org.apache.ivy.util.CopyProgressListener;
+import org.apache.ivy.util.FileUtil;
+import org.apache.ivy.util.HostUtil;
+import org.apache.ivy.util.Message;
+
+/**
+ *
+ */
+public class HttpClientHandler extends AbstractURLHandler {
+    private static final SimpleDateFormat LAST_MODIFIED_FORMAT = new SimpleDateFormat(
+            "EEE, d MMM yyyy HH:mm:ss z", Locale.US);
+
+    // proxy configuration: obtain from system properties
+    private int proxyPort;
+
+    private String proxyHost = null;
+
+    private String proxyUserName = null;
+
+    private String proxyPasswd = null;
+
+    private HttpClientHelper httpClientHelper;
+
+    private static HttpClient httpClient;
+
+    public HttpClientHandler() {
+        configureProxy();
+    }
+
+    private void configureProxy() {
+        proxyHost = System.getProperty("http.proxyHost");
+        // TODO constant is better ...
+        if (useProxy()) {
+            proxyPort = Integer.parseInt(System.getProperty("http.proxyPort", "80"));
+            proxyUserName = System.getProperty("http.proxyUser");
+            proxyPasswd = System.getProperty("http.proxyPassword");
+            // It seems there is no equivalent in HttpClient for
+            // 'http.nonProxyHosts' property
+            Message.verbose("proxy configured: host=" + proxyHost + " port=" + proxyPort + " user="
+                    + proxyUserName);
+        } else {
+            Message.verbose("no proxy configured");
+        }
+    }
+
+    public InputStream openStream(URL url) throws IOException {
+        GetMethod get = doGet(url, 0);
+        if (!checkStatusCode(url, get)) {
+            get.releaseConnection();
+            throw new IOException("The HTTP response code for " + url
+                    + " did not indicate a success." + " See log for more detail.");
+        }
+
+        Header encoding = get.getResponseHeader("Content-Encoding");
+        return getDecodingInputStream(encoding == null ? null : encoding.getValue(),
+            get.getResponseBodyAsStream());
+    }
+
+    public void download(URL src, File dest, CopyProgressListener l) throws IOException {
+        GetMethod get = doGet(src, 0);
+        try {
+            // We can only figure the content we got is want we want if the status is success.
+            if (!checkStatusCode(src, get)) {
+                throw new IOException("The HTTP response code for " + src
+                        + " did not indicate a success." + " See log for more detail.");
+            }
+
+            Header encoding = get.getResponseHeader("Content-Encoding");
+            InputStream is = getDecodingInputStream(encoding == null ? null : encoding.getValue(),
+                get.getResponseBodyAsStream());
+            FileUtil.copy(is, dest, l);
+            dest.setLastModified(getLastModified(get));
+        } finally {
+            get.releaseConnection();
+        }
+    }
+
+    public void upload(File src, URL dest, CopyProgressListener l) throws IOException {
+        HttpClient client = getClient();
+
+        PutMethod put = new PutMethod(normalizeToString(dest));
+        put.setDoAuthentication(useAuthentication(dest) || useProxyAuthentication());
+        put.getParams().setBooleanParameter("http.protocol.expect-continue", true);
+        try {
+            put.setRequestEntity(new FileRequestEntity(src));
+            int statusCode = client.executeMethod(put);
+            validatePutStatusCode(dest, statusCode, null);
+        } finally {
+            put.releaseConnection();
+        }
+    }
+
+    public URLInfo getURLInfo(URL url) {
+        return getURLInfo(url, 0);
+    }
+
+    public URLInfo getURLInfo(URL url, int timeout) {
+        HttpMethodBase method = null;
+        try {
+            if (getRequestMethod() == URLHandler.REQUEST_METHOD_HEAD) {
+                method = doHead(url, timeout);
+            } else {
+                method = doGet(url, timeout);
+            }
+            if (checkStatusCode(url, method)) {
+                return new URLInfo(true, getResponseContentLength(method), getLastModified(method),
+                        method.getRequestCharSet());
+            }
+        } catch (HttpException e) {
+            Message.error("HttpClientHandler: " + e.getMessage() + ":" + e.getReasonCode() + "="
+                    + e.getReason() + " url=" + url);
+        } catch (UnknownHostException e) {
+            Message.warn("Host " + e.getMessage() + " not found. url=" + url);
+            Message.info("You probably access the destination server through "
+                    + "a proxy server that is not well configured.");
+        } catch (IOException e) {
+            Message.error("HttpClientHandler: " + e.getMessage() + " url=" + url);
+        } catch (IllegalArgumentException e) {
+            // thrown by HttpClient to indicate the URL is not valid, this happens for instance
+            // when trying to download a dynamic version (cfr IVY-390)
+        } finally {
+            if (method != null) {
+                method.releaseConnection();
+            }
+        }
+        return UNAVAILABLE;
+    }
+
+    private boolean checkStatusCode(URL url, HttpMethodBase method) throws IOException {
+        int status = method.getStatusCode();
+        if (status == HttpStatus.SC_OK) {
+            return true;
+        }
+
+        // IVY-1328: some servers return a 204 on a HEAD request
+        if ("HEAD".equals(method.getName()) && (status == 204)) {
+            return true;
+        }
+
+        Message.debug("HTTP response status: " + status + " url=" + url);
+        if (status == HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED) {
+            Message.warn("Your proxy requires authentication.");
+        } else if (String.valueOf(status).startsWith("4")) {
+            Message.verbose("CLIENT ERROR: " + method.getStatusText() + " url=" + url);
+        } else if (String.valueOf(status).startsWith("5")) {
+            Message.error("SERVER ERROR: " + method.getStatusText() + " url=" + url);
+        }
+
+        return false;
+    }
+
+    private long getLastModified(HttpMethodBase method) {
+        Header header = method.getResponseHeader("last-modified");
+        if (header != null) {
+            String lastModified = header.getValue();
+            try {
+                return LAST_MODIFIED_FORMAT.parse(lastModified).getTime();
+            } catch (ParseException e) {
+                // ignored
+            }
+            return System.currentTimeMillis();
+        } else {
+            return System.currentTimeMillis();
+        }
+    }
+
+    private long getResponseContentLength(HttpMethodBase head) {
+        return getHttpClientHelper().getResponseContentLength(head);
+    }
+
+    private HttpClientHelper getHttpClientHelper() {
+        if (httpClientHelper == null) {
+            // use commons httpclient 3.0 if available
+            try {
+                HttpMethodBase.class.getMethod("getResponseContentLength", new Class[0]);
+                httpClientHelper = new HttpClientHelper3x();
+                Message.verbose("using commons httpclient 3.x helper");
+            } catch (SecurityException e) {
+                Message.verbose("unable to get access to getResponseContentLength of "
+                        + "commons-httpclient HeadMethod. Please use commons-httpclient 3.0 or "
+                        + "use ivy with sufficient security permissions.");
+                Message.verbose("exception: " + e.getMessage());
+                httpClientHelper = new HttpClientHelper2x();
+                Message.verbose("using commons httpclient 2.x helper");
+            } catch (NoSuchMethodException e) {
+                httpClientHelper = new HttpClientHelper2x();
+                Message.verbose("using commons httpclient 2.x helper");
+            }
+        }
+        return httpClientHelper;
+    }
+
+    public int getHttpClientMajorVersion() {
+        HttpClientHelper helper = getHttpClientHelper();
+        return helper.getHttpClientMajorVersion();
+    }
+
+    private GetMethod doGet(URL url, int timeout) throws IOException {
+        HttpClient client = getClient();
+        client.setTimeout(timeout);
+
+        GetMethod get = new GetMethod(normalizeToString(url));
+        get.setDoAuthentication(useAuthentication(url) || useProxyAuthentication());
+        get.setRequestHeader("Accept-Encoding", "gzip,deflate");
+        client.executeMethod(get);
+        return get;
+    }
+
+    private HeadMethod doHead(URL url, int timeout) throws IOException {
+        HttpClient client = getClient();
+        client.setTimeout(timeout);
+
+        HeadMethod head = new HeadMethod(normalizeToString(url));
+        head.setDoAuthentication(useAuthentication(url) || useProxyAuthentication());
+        client.executeMethod(head);
+        return head;
+    }
+
+    private HttpClient getClient() {
+        if (httpClient == null) {
+            final MultiThreadedHttpConnectionManager connManager = new MultiThreadedHttpConnectionManager();
+            httpClient = new HttpClient(connManager);
+
+            Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+                public void run() {
+                    connManager.shutdown();
+                }
+            }));
+
+            List authPrefs = new ArrayList(3);
+            authPrefs.add(AuthPolicy.DIGEST);
+            authPrefs.add(AuthPolicy.BASIC);
+            authPrefs.add(AuthPolicy.NTLM); // put it at the end to give less priority (IVY-213)
+            httpClient.getParams().setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY, authPrefs);
+
+            if (useProxy()) {
+                httpClient.getHostConfiguration().setProxy(proxyHost, proxyPort);
+                if (useProxyAuthentication()) {
+                    httpClient.getState().setProxyCredentials(
+                        new AuthScope(proxyHost, proxyPort, AuthScope.ANY_REALM),
+                        createCredentials(proxyUserName, proxyPasswd));
+                }
+            }
+
+            // user-agent
+            httpClient.getParams().setParameter(HttpMethodParams.USER_AGENT,
+                getUserAgent());
+
+            // authentication
+            httpClient.getParams().setParameter(CredentialsProvider.PROVIDER,
+                new IvyCredentialsProvider());
+        }
+
+        return httpClient;
+    }
+
+    private boolean useProxy() {
+        return proxyHost != null && proxyHost.trim().length() > 0;
+    }
+
+    private boolean useAuthentication(URL url) {
+        return CredentialsStore.INSTANCE.hasCredentials(url.getHost());
+    }
+
+    private boolean useProxyAuthentication() {
+        return (proxyUserName != null && proxyUserName.trim().length() > 0);
+    }
+
+    private static final class HttpClientHelper3x implements HttpClientHelper {
+        private static final int VERSION = 3;
+
+        private HttpClientHelper3x() {
+        }
+
+        public long getResponseContentLength(HttpMethodBase method) {
+            return method.getResponseContentLength();
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public int getHttpClientMajorVersion() {
+            return VERSION;
+        }
+    }
+
+    private static final class HttpClientHelper2x implements HttpClientHelper {
+        private static final int VERSION = 2;
+
+        private HttpClientHelper2x() {
+        }
+
+        public long getResponseContentLength(HttpMethodBase method) {
+            Header header = method.getResponseHeader("Content-Length");
+            if (header != null) {
+                try {
+                    return Integer.parseInt(header.getValue());
+                } catch (NumberFormatException e) {
+                    Message.verbose("Invalid content-length value: " + e.getMessage());
+                }
+            }
+            return 0;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public int getHttpClientMajorVersion() {
+            return VERSION;
+        }
+    }
+
+    public interface HttpClientHelper {
+        long getResponseContentLength(HttpMethodBase method);
+
+        int getHttpClientMajorVersion();
+    }
+
+    private static class IvyCredentialsProvider implements CredentialsProvider {
+
+        public Credentials getCredentials(AuthScheme scheme, String host, int port, boolean proxy)
+                throws CredentialsNotAvailableException {
+            String realm = scheme.getRealm();
+
+            org.apache.ivy.util.Credentials c = CredentialsStore.INSTANCE.getCredentials(realm,
+                host);
+            if (c != null) {
+                return createCredentials(c.getUserName(), c.getPasswd());
+            }
+
+            return null;
+        }
+    }
+
+    private static Credentials createCredentials(String username, String password) {
+        String user;
+        String domain;
+
+        int backslashIndex = username.indexOf('\\');
+        if (backslashIndex >= 0) {
+            user = username.substring(backslashIndex + 1);
+            domain = username.substring(0, backslashIndex);
+        } else {
+            user = username;
+            domain = System.getProperty("http.auth.ntlm.domain", "");
+        }
+
+        return new NTCredentials(user, password, HostUtil.getLocalHostName(), domain);
+    }
+
+    private static class FileRequestEntity implements RequestEntity {
+        private File file;
+
+        public FileRequestEntity(File file) {
+            this.file = file;
+        }
+
+        public long getContentLength() {
+            return file.length();
+        }
+
+        public String getContentType() {
+            return null;
+        }
+
+        public boolean isRepeatable() {
+            return true;
+        }
+
+        public void writeRequest(OutputStream out) throws IOException {
+            InputStream instream = new FileInputStream(file);
+            try {
+                FileUtil.copy(instream, out, null, false);
+            } finally {
+                instream.close();
+            }
+        }
+    }
+
+}
diff --git a/src/java/org/apache/ivy/util/url/IvyAuthenticator.java b/src/java/org/apache/ivy/util/url/IvyAuthenticator.java
new file mode 100644
index 0000000..a529ad2
--- /dev/null
+++ b/src/java/org/apache/ivy/util/url/IvyAuthenticator.java
@@ -0,0 +1,157 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util.url;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.net.Authenticator;
+import java.net.PasswordAuthentication;
+
+import org.apache.ivy.util.Credentials;
+import org.apache.ivy.util.Message;
+
+/**
+ * 
+ */
+public final class IvyAuthenticator extends Authenticator {
+
+    private Authenticator original;
+
+    private static boolean securityWarningLogged = false;
+
+    /**
+     * Private c'tor to prevent instantiation.
+     */
+    private IvyAuthenticator(Authenticator original) {
+        this.original = original;
+    }
+
+    /**
+     * Installs an <tt>IvyAuthenticator</tt> as default <tt>Authenticator</tt>. Call this method
+     * before opening HTTP(S) connections to enable Ivy authentication.
+     */
+    public static void install() {
+        // We will try to use the original authenticator as backup authenticator.
+        // Since there is no getter available, so try to use some reflection to
+        // obtain it. If that doesn't work, assume there is no original authenticator
+        Authenticator original = null;
+
+        try {
+            Field f = Authenticator.class.getDeclaredField("theAuthenticator");
+            f.setAccessible(true);
+            original = (Authenticator) f.get(null);
+        } catch (Throwable t) {
+            Message.debug("Error occurred while getting the original authenticator: "
+                    + t.getMessage());
+        }
+
+        if (!(original instanceof IvyAuthenticator)) {
+            try {
+                Authenticator.setDefault(new IvyAuthenticator(original));
+            } catch (SecurityException e) {
+                if (!securityWarningLogged) {
+                    securityWarningLogged = true;
+                    Message.warn("Not enough permissions to set the IvyAuthenticator. "
+                            + "HTTP(S) authentication will be disabled!");
+                }
+            }
+        }
+    }
+
+    // API ******************************************************************
+
+    // Overriding Authenticator *********************************************
+
+    protected PasswordAuthentication getPasswordAuthentication() {
+        PasswordAuthentication result = null;
+
+        if (isProxyAuthentication()) {
+            String proxyUser = System.getProperty("http.proxyUser");
+            if ((proxyUser != null) && (proxyUser.trim().length() > 0)) {
+                String proxyPass = System.getProperty("http.proxyPassword", "");
+                Message.debug("authenicating to proxy server with username [" + proxyUser + "]");
+                result = new PasswordAuthentication(proxyUser, proxyPass.toCharArray());
+            }
+        } else {
+            Credentials c = CredentialsStore.INSTANCE.getCredentials(getRequestingPrompt(),
+                getRequestingHost());
+            Message.debug("authentication: k='"
+                    + Credentials.buildKey(getRequestingPrompt(), getRequestingHost()) + "' c='"
+                    + c + "'");
+            if (c != null) {
+                final String password = c.getPasswd() == null ? "" : c.getPasswd();
+                result = new PasswordAuthentication(c.getUserName(), password.toCharArray());
+            }
+        }
+
+        if ((result == null) && (original != null)) {
+            Authenticator.setDefault(original);
+            try {
+                result = Authenticator.requestPasswordAuthentication(getRequestingHost(),
+                    getRequestingSite(), getRequestingPort(), getRequestingProtocol(),
+                    getRequestingPrompt(), getRequestingScheme());
+            } finally {
+                Authenticator.setDefault(this);
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Checks if the current authentication request is for the proxy server. This functionality is
+     * not available in JDK1.4, so we check this in a very dirty way which is probably not very
+     * portable, but will work for the SUN 1.4 JDKs.
+     * 
+     * @return
+     */
+    private boolean isProxyAuthentication() {
+        try {
+            // we first try to invoke the getRequestorType() method which is a JDK1.5+ method
+            Method m = Authenticator.class.getDeclaredMethod("getRequestorType", null);
+            Object result = m.invoke(this, null);
+            return "PROXY".equals(String.valueOf(result));
+        } catch (NoSuchMethodException e) {
+            // do nothing, this is a JDK1.5+ method
+        } catch (Throwable t) {
+            Message.debug("Error occurred while checking if the authentication request is for the proxy server: "
+                    + t.getMessage());
+        }
+
+        // now we will do something very dirty and analyse the stack trace to see
+        // if this method is called from within the 'getHttpProxyAuthentication' method
+        // or the 'getServerAuthentication' method which are both part of the
+        // sun.net.www.protocol.http.HttpURLConnection class.
+        // This might not work on other 1.4 JVM's!
+        // This code should be removed when Ivy requires JDK1.5+
+        StackTraceElement[] stackTrace = (new Exception()).getStackTrace();
+        for (int i = 0; i < stackTrace.length; i++) {
+            if ("getHttpProxyAuthentication".equals(stackTrace[i].getMethodName())) {
+                return true;
+            }
+            if ("getServerAuthentication".equals(stackTrace[i].getMethodName())) {
+                return false;
+            }
+        }
+
+        // fallback to the Ivy 2.2.0 behavior
+        String proxyHost = System.getProperty("http.proxyHost");
+        return getRequestingHost().equals(proxyHost);
+    }
+
+}
diff --git a/src/java/org/apache/ivy/util/url/URLHandler.java b/src/java/org/apache/ivy/util/url/URLHandler.java
new file mode 100644
index 0000000..96b51d9
--- /dev/null
+++ b/src/java/org/apache/ivy/util/url/URLHandler.java
@@ -0,0 +1,173 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util.url;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+import org.apache.ivy.util.CopyProgressListener;
+
+/**
+ * This interface is responsible for handling some URL manipulation (stream opening, downloading,
+ * check reachability, ...).
+ */
+public interface URLHandler {
+
+    /**
+     * Using the slower REQUEST method for getting the basic URL infos. Use this when getting errors
+     * behind a problematic/special proxy or firewall chain.
+     */
+    public static final int REQUEST_METHOD_GET = 1;
+
+    /**
+     * Using the faster HEAD method for getting the basic URL infos. Works for most common networks.
+     */
+    public static final int REQUEST_METHOD_HEAD = 2;
+
+    public static class URLInfo {
+        private long contentLength;
+
+        private long lastModified;
+
+        private boolean available;
+
+        private String bodyCharset;
+
+        protected URLInfo(boolean available, long contentLength, long lastModified) {
+            this(available, contentLength, lastModified, null);
+        }
+
+        protected URLInfo(boolean available, long contentLength, long lastModified,
+                String bodyCharset) {
+            this.available = available;
+            this.contentLength = contentLength;
+            this.lastModified = lastModified;
+            this.bodyCharset = bodyCharset;
+        }
+
+        public boolean isReachable() {
+            return available;
+        }
+
+        public long getContentLength() {
+            return contentLength;
+        }
+
+        public long getLastModified() {
+            return lastModified;
+        }
+
+        public String getBodyCharset() {
+            return bodyCharset;
+        }
+    }
+
+    public static final URLInfo UNAVAILABLE = new URLInfo(false, 0, 0);
+
+    /**
+     * Please prefer getURLInfo when several infos are needed.
+     * 
+     * @param url
+     *            the url to check
+     * @return true if the target is reachable
+     */
+    public boolean isReachable(URL url);
+
+    /**
+     * Please prefer getURLInfo when several infos are needed.
+     * 
+     * @param url
+     *            the url to check
+     * @return true if the target is reachable
+     */
+    public boolean isReachable(URL url, int timeout);
+
+    /**
+     * Returns the length of the target if the given url is reachable, and without error code in
+     * case of http urls. Please prefer getURLInfo when several infos are needed.
+     * 
+     * @param url
+     *            the url to check
+     * @return the length of the target if available, 0 if not reachable
+     */
+    public long getContentLength(URL url);
+
+    /**
+     * Returns the length of the target if the given url is reachable, and without error code in
+     * case of http urls.
+     * 
+     * @param url
+     *            the url to check
+     * @param timeout
+     *            the maximum time before considering an url is not reachable a timeout of zero
+     *            indicates no timeout
+     * @return the length of the target if available, 0 if not reachable
+     */
+    public long getContentLength(URL url, int timeout);
+
+    /**
+     * Please prefer getURLInfo when several infos are needed.
+     * 
+     * @param url
+     *            the url to check
+     * @return last modified timestamp of the given url
+     */
+    public long getLastModified(URL url);
+
+    /**
+     * Please prefer getURLInfo when several infos are needed.
+     * 
+     * @param url
+     *            the url to check
+     * @return last modified timestamp of the given url
+     */
+    public long getLastModified(URL url, int timeout);
+
+    /**
+     * Returns the URLInfo of the given url or a {@link #UNAVAILABLE} instance, if the url is not
+     * reachable.
+     * 
+     * @param url
+     *            The url from which information is retrieved.
+     * @return The URLInfo extracted from the given url, or {@link #UNAVAILABLE} when the url is not
+     *         available.
+     */
+    public URLInfo getURLInfo(URL url);
+
+    /**
+     * never returns null, return UNAVAILABLE when url is not reachable
+     * 
+     * @param url
+     *            The url from which information is retrieved.
+     * @param timeout
+     *            The timeout in milliseconds.
+     * @return The URLInfo extracted from the given url, or {@link #UNAVAILABLE} when the url is not
+     *         available.
+     */
+    public URLInfo getURLInfo(URL url, int timeout);
+
+    public InputStream openStream(URL url) throws IOException;
+
+    public void download(URL src, File dest, CopyProgressListener l) throws IOException;
+
+    public void upload(File src, URL dest, CopyProgressListener l) throws IOException;
+
+    public void setRequestMethod(int requestMethod);
+}
diff --git a/src/java/org/apache/ivy/util/url/URLHandlerDispatcher.java b/src/java/org/apache/ivy/util/url/URLHandlerDispatcher.java
new file mode 100644
index 0000000..be15009
--- /dev/null
+++ b/src/java/org/apache/ivy/util/url/URLHandlerDispatcher.java
@@ -0,0 +1,109 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util.url;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.ivy.util.CopyProgressListener;
+
+/**
+ * This class is used to dispatch downloading requests
+ */
+public class URLHandlerDispatcher implements URLHandler {
+    private Map handlers = new HashMap();
+
+    private URLHandler defaultHandler = new BasicURLHandler();
+
+    public URLHandlerDispatcher() {
+    }
+
+    public boolean isReachable(URL url) {
+        return getHandler(url.getProtocol()).isReachable(url);
+    }
+
+    public boolean isReachable(URL url, int timeout) {
+        return getHandler(url.getProtocol()).isReachable(url, timeout);
+    }
+
+    public long getContentLength(URL url) {
+        return getHandler(url.getProtocol()).getContentLength(url);
+    }
+
+    public long getContentLength(URL url, int timeout) {
+        return getHandler(url.getProtocol()).getContentLength(url, timeout);
+    }
+
+    public long getLastModified(URL url) {
+        return getHandler(url.getProtocol()).getLastModified(url);
+    }
+
+    public long getLastModified(URL url, int timeout) {
+        return getHandler(url.getProtocol()).getLastModified(url, timeout);
+    }
+
+    public URLInfo getURLInfo(URL url) {
+        return getHandler(url.getProtocol()).getURLInfo(url);
+    }
+
+    public URLInfo getURLInfo(URL url, int timeout) {
+        return getHandler(url.getProtocol()).getURLInfo(url, timeout);
+    }
+
+    public InputStream openStream(URL url) throws IOException {
+        return getHandler(url.getProtocol()).openStream(url);
+    }
+
+    public void download(URL src, File dest, CopyProgressListener l) throws IOException {
+        getHandler(src.getProtocol()).download(src, dest, l);
+    }
+
+    public void upload(File src, URL dest, CopyProgressListener l) throws IOException {
+        getHandler(dest.getProtocol()).upload(src, dest, l);
+    }
+
+    public void setRequestMethod(int requestMethod) {
+        defaultHandler.setRequestMethod(requestMethod);
+        for (Iterator it = handlers.values().iterator(); it.hasNext();) {
+            URLHandler handler = (URLHandler) it.next();
+            handler.setRequestMethod(requestMethod);
+        }
+    }
+
+    public void setDownloader(String protocol, URLHandler downloader) {
+        handlers.put(protocol, downloader);
+    }
+
+    public URLHandler getHandler(String protocol) {
+        URLHandler downloader = (URLHandler) handlers.get(protocol);
+        return downloader == null ? defaultHandler : downloader;
+    }
+
+    public URLHandler getDefault() {
+        return defaultHandler;
+    }
+
+    public void setDefault(URLHandler default1) {
+        defaultHandler = default1;
+    }
+}
diff --git a/src/java/org/apache/ivy/util/url/URLHandlerRegistry.java b/src/java/org/apache/ivy/util/url/URLHandlerRegistry.java
new file mode 100644
index 0000000..21f10f1
--- /dev/null
+++ b/src/java/org/apache/ivy/util/url/URLHandlerRegistry.java
@@ -0,0 +1,73 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ivy.util.url;
+
+import org.apache.ivy.util.Message;
+
+/**
+ *
+ */
+public final class URLHandlerRegistry {
+    private URLHandlerRegistry() {
+    }
+
+    private static URLHandler defaultHandler = new BasicURLHandler();
+
+    public static URLHandler getDefault() {
+        return defaultHandler;
+    }
+
+    public static void setDefault(URLHandler def) {
+        defaultHandler = def;
+    }
+
+    /**
+     * This method is used to get appropriate http downloader dependening on Jakarta Commons
+     * HttpClient availability in classpath, or simply use jdk url handling in other cases.
+     * 
+     * @return most accurate http downloader
+     */
+    public static URLHandler getHttp() {
+        try {
+            Class.forName("org.apache.commons.httpclient.HttpClient");
+
+            // temporary fix for IVY-880: only use HttpClientHandler when
+            // http-client-3.x is available
+            Class.forName("org.apache.commons.httpclient.params.HttpClientParams");
+
+            Class handler = Class.forName("org.apache.ivy.util.url.HttpClientHandler");
+            Message.verbose("jakarta commons httpclient detected: using it for http downloading");
+            return (URLHandler) handler.newInstance();
+        } catch (ClassNotFoundException e) {
+            Message.verbose("jakarta commons httpclient not found: using jdk url handling");
+            return new BasicURLHandler();
+        } catch (NoClassDefFoundError e) {
+            Message.verbose("error occurred while loading jakarta commons httpclient: "
+                    + e.getMessage());
+            Message.verbose("Using jdk url handling instead.");
+            return new BasicURLHandler();
+        } catch (InstantiationException e) {
+            Message.verbose("couldn't instantiate HttpClientHandler: using jdk url handling");
+            return new BasicURLHandler();
+        } catch (IllegalAccessException e) {
+            Message.verbose("couldn't instantiate HttpClientHandler: using jdk url handling");
+            return new BasicURLHandler();
+        }
+    }
+
+}

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



More information about the pkg-java-commits mailing list