[jboss-modules] 02/04: Imported Upstream version 1.4.1.Final

Markus Koschany apo-guest at moszumanska.debian.org
Tue Mar 3 18:10:57 UTC 2015


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

apo-guest pushed a commit to branch master
in repository jboss-modules.

commit 7e2e8dba0e1f7bb5dc5f6bb3845a1856b69849d5
Author: Markus Koschany <apo at gambaru.de>
Date:   Tue Mar 3 11:46:22 2015 +0100

    Imported Upstream version 1.4.1.Final
---
 .gitignore                                         |   16 +
 LICENSE.txt                                        |  202 ++
 XPP3-LICENSE.txt                                   |   46 +
 pom.xml                                            |  139 +
 src/main/java/__redirected/__DatatypeFactory.java  |  213 ++
 .../__redirected/__DocumentBuilderFactory.java     |  200 ++
 src/main/java/__redirected/__JAXPRedirected.java   |   83 +
 src/main/java/__redirected/__RedirectedUtils.java  |  186 ++
 src/main/java/__redirected/__SAXParserFactory.java |  163 +
 src/main/java/__redirected/__SchemaFactory.java    |  185 ++
 .../java/__redirected/__TransformerFactory.java    |  215 ++
 src/main/java/__redirected/__XMLEventFactory.java  |  234 ++
 src/main/java/__redirected/__XMLInputFactory.java  |  220 ++
 src/main/java/__redirected/__XMLOutputFactory.java |  163 +
 src/main/java/__redirected/__XMLReaderFactory.java |  182 ++
 src/main/java/__redirected/__XPathFactory.java     |  145 +
 .../org/jboss/modules/AbstractLocalLoader.java     |   64 +
 .../org/jboss/modules/AbstractResourceLoader.java  |  139 +
 .../java/org/jboss/modules/AliasModuleSpec.java    |   38 +
 .../java/org/jboss/modules/AssertionSetting.java   |   32 +
 src/main/java/org/jboss/modules/CallerContext.java |   59 +
 .../org/jboss/modules/ClassLoaderLocalLoader.java  |  123 +
 .../org/jboss/modules/ClassPathModuleLoader.java   |  132 +
 src/main/java/org/jboss/modules/ClassSpec.java     |   96 +
 .../org/jboss/modules/ClassifyingModuleLoader.java |   84 +
 .../java/org/jboss/modules/ConcreteModuleSpec.java |   97 +
 .../org/jboss/modules/ConcurrentClassLoader.java   |  650 ++++
 .../modules/DefaultBootModuleLoaderHolder.java     |   60 +
 src/main/java/org/jboss/modules/Dependency.java    |  107 +
 .../java/org/jboss/modules/DependencySpec.java     |  482 +++
 .../org/jboss/modules/DependencyTreeViewer.java    |  127 +
 .../java/org/jboss/modules/FastCopyHashSet.java    |  566 ++++
 .../java/org/jboss/modules/FileEntryResource.java  |   98 +
 .../java/org/jboss/modules/FileResourceLoader.java |  366 +++
 .../jboss/modules/FilteredIterableLocalLoader.java |   58 +
 .../modules/FilteredIterableResourceLoader.java    |   66 +
 .../org/jboss/modules/FilteredLocalLoader.java     |   52 +
 .../org/jboss/modules/FilteredResourceLoader.java  |   60 +
 .../java/org/jboss/modules/IdentityHashSet.java    |  559 ++++
 .../org/jboss/modules/IterableLocalLoader.java     |   40 +
 .../org/jboss/modules/IterableModuleFinder.java    |   38 +
 .../org/jboss/modules/IterableResourceLoader.java  |   40 +
 src/main/java/org/jboss/modules/JDKPaths.java      |  125 +
 .../java/org/jboss/modules/JarEntryResource.java   |   58 +
 .../org/jboss/modules/JarFileResourceLoader.java   |  488 +++
 .../java/org/jboss/modules/JarModuleFinder.java    |  153 +
 .../java/org/jboss/modules/JarModuleLoader.java    |   67 +
 .../jboss/modules/LayeredModulePathFactory.java    |  263 ++
 src/main/java/org/jboss/modules/Linkage.java       |   81 +
 .../java/org/jboss/modules/LocalDependency.java    |   49 +
 src/main/java/org/jboss/modules/LocalLoader.java   |   59 +
 src/main/java/org/jboss/modules/LocalLoaders.java  |  105 +
 .../java/org/jboss/modules/LocalModuleFinder.java  |  213 ++
 .../java/org/jboss/modules/LocalModuleLoader.java  |   65 +
 src/main/java/org/jboss/modules/Main.java          |  561 ++++
 .../java/org/jboss/modules/MavenArtifactUtil.java  |  351 +++
 src/main/java/org/jboss/modules/MavenSettings.java |  102 +
 src/main/java/org/jboss/modules/Metrics.java       |   42 +
 .../modules/ModularContentHandlerFactory.java      |   92 +
 .../modules/ModularURLStreamHandlerFactory.java    |  126 +
 src/main/java/org/jboss/modules/Module.java        | 1503 +++++++++
 .../java/org/jboss/modules/ModuleClassLoader.java  |  823 +++++
 .../jboss/modules/ModuleClassLoaderDependency.java |   52 +
 .../jboss/modules/ModuleClassLoaderFactory.java    |   35 +
 .../java/org/jboss/modules/ModuleDependency.java   |   61 +
 .../org/jboss/modules/ModuleDependencySpec.java    |   75 +
 src/main/java/org/jboss/modules/ModuleFinder.java  |   36 +
 .../java/org/jboss/modules/ModuleIdentifier.java   |  276 ++
 .../java/org/jboss/modules/ModuleLoadError.java    |   76 +
 .../org/jboss/modules/ModuleLoadException.java     |   76 +
 src/main/java/org/jboss/modules/ModuleLoader.java  | 1013 ++++++
 .../org/jboss/modules/ModuleNotFoundException.java |   68 +
 src/main/java/org/jboss/modules/ModuleSpec.java    |  292 ++
 .../java/org/jboss/modules/ModuleXmlParser.java    | 1076 +++++++
 src/main/java/org/jboss/modules/ModuleXmlUtil.java |  155 +
 src/main/java/org/jboss/modules/ModulesPolicy.java |   79 +
 .../jboss/modules/NativeLibraryResourceLoader.java |  320 ++
 src/main/java/org/jboss/modules/PackageSpec.java   |  200 ++
 src/main/java/org/jboss/modules/PathUtils.java     |  269 ++
 src/main/java/org/jboss/modules/Paths.java         |   56 +
 .../java/org/jboss/modules/PropertyReadAction.java |   43 +
 .../org/jboss/modules/PropertyWriteAction.java     |   42 +
 src/main/java/org/jboss/modules/Resource.java      |   59 +
 .../java/org/jboss/modules/ResourceLoader.java     |   85 +
 .../java/org/jboss/modules/ResourceLoaderSpec.java |   69 +
 .../java/org/jboss/modules/ResourceLoaders.java    |  116 +
 .../java/org/jboss/modules/SecurityActions.java    |   69 +
 .../java/org/jboss/modules/StartTimeHolder.java    |   22 +
 src/main/java/org/jboss/modules/StreamUtil.java    |   92 +
 src/main/java/org/jboss/modules/URLResource.java   |   50 +
 .../org/jboss/modules/UnlockedReadHashMap.java     |  413 +++
 .../modules/_private/ModulesPrivateAccess.java     |   32 +
 .../jboss/modules/filter/AggregatePathFilter.java  |   83 +
 .../jboss/modules/filter/BooleanClassFilter.java   |   50 +
 .../jboss/modules/filter/BooleanPathFilter.java    |   54 +
 .../org/jboss/modules/filter/ChildPathFilter.java  |   51 +
 .../java/org/jboss/modules/filter/ClassFilter.java |   36 +
 .../org/jboss/modules/filter/ClassFilters.java     |   58 +
 .../org/jboss/modules/filter/EqualsPathFilter.java |   54 +
 .../org/jboss/modules/filter/GlobPathFilter.java   |  137 +
 .../jboss/modules/filter/InvertingPathFilter.java  |   65 +
 .../jboss/modules/filter/MultiplePathFilter.java   |   74 +
 .../modules/filter/MultiplePathFilterBuilder.java  |   79 +
 .../org/jboss/modules/filter/PathClassFilter.java  |   35 +
 .../java/org/jboss/modules/filter/PathFilter.java  |   52 +
 .../java/org/jboss/modules/filter/PathFilters.java |  338 ++
 .../org/jboss/modules/filter/SetPathFilter.java    |   67 +
 .../org/jboss/modules/filter/package-info.java     |   23 +
 .../org/jboss/modules/log/JDKModuleLogger.java     |  205 ++
 .../org/jboss/modules/log/ModuleLogRecord.java     |   95 +
 .../java/org/jboss/modules/log/ModuleLogger.java   |   62 +
 .../org/jboss/modules/log/NoopModuleLogger.java    |   98 +
 .../org/jboss/modules/log/StreamModuleLogger.java  |  160 +
 .../java/org/jboss/modules/log/package-info.java   |   24 +
 .../jboss/modules/management/DependencyInfo.java   |  134 +
 .../org/jboss/modules/management/ModuleInfo.java   |  122 +
 .../modules/management/ModuleLoaderMXBean.java     |  163 +
 .../jboss/modules/management/ObjectProperties.java |  168 +
 .../modules/management/ResourceLoaderInfo.java     |   62 +
 .../org/jboss/modules/management/package-info.java |   22 +
 src/main/java/org/jboss/modules/package-info.java  |   25 +
 .../org/jboss/modules/ref/PhantomReference.java    |   65 +
 src/main/java/org/jboss/modules/ref/Reapable.java  |   39 +
 src/main/java/org/jboss/modules/ref/Reaper.java    |   37 +
 src/main/java/org/jboss/modules/ref/Reference.java |   87 +
 .../java/org/jboss/modules/ref/References.java     |  182 ++
 .../java/org/jboss/modules/ref/SoftReference.java  |   73 +
 .../org/jboss/modules/ref/StrongReference.java     |   62 +
 .../java/org/jboss/modules/ref/WeakReference.java  |   73 +
 .../java/org/jboss/modules/ref/package-info.java   |   23 +
 .../security/FactoryPermissionCollection.java      |   84 +
 .../security/ImmediatePermissionFactory.java       |   46 +
 .../modules/security/LoadedPermissionFactory.java  |   73 +
 .../modules/security/ModularPermissionFactory.java |   89 +
 .../jboss/modules/security/PermissionFactory.java  |   36 +
 .../modules/security/UninitializedPermission.java  |   48 +
 src/main/java/org/jboss/modules/xml/MXParser.java  | 3305 ++++++++++++++++++++
 .../java/org/jboss/modules/xml/XmlPullParser.java  | 1133 +++++++
 .../jboss/modules/xml/XmlPullParserException.java  |   76 +
 src/main/resources/XPP3-LICENSE.txt                |   46 +
 src/main/resources/schema/module-1_0.xsd           |  462 +++
 src/main/resources/schema/module-1_1.xsd           |  582 ++++
 src/main/resources/schema/module-1_2.xsd           |  591 ++++
 src/main/resources/schema/module-1_3.xsd           |  623 ++++
 .../org/jboss/modules/AbstractModuleTestCase.java  |   90 +
 .../modules/AbstractResourceLoaderTestCase.java    |  181 ++
 .../java/org/jboss/modules/ClassFilteringTest.java |   98 +
 .../jboss/modules/ClassPathModuleLoaderTest.java   |   99 +
 .../jboss/modules/ConcurrentClassLoaderTest.java   |  128 +
 .../java/org/jboss/modules/ErrorHandlingTest.java  |   61 +
 .../org/jboss/modules/FileResourceLoaderTest.java  |   57 +
 .../modules/InstantiatePrivateAccessTest.java      |   62 +
 .../java/org/jboss/modules/JAXPModuleTest.java     | 1325 ++++++++
 .../org/jboss/modules/JarResourceLoaderTest.java   |  103 +
 .../org/jboss/modules/LayeredModulePathTest.java   |  621 ++++
 .../org/jboss/modules/LocalModuleLoaderTest.java   |   77 +
 .../java/org/jboss/modules/MavenResourceTest.java  |   87 +
 .../org/jboss/modules/ModuleClassLoaderTest.java   |  341 ++
 .../java/org/jboss/modules/ModuleExportTest.java   |  156 +
 .../org/jboss/modules/ModuleIdentifierTest.java    |   71 +
 .../java/org/jboss/modules/ModuleIteratorTest.java |  134 +
 .../java/org/jboss/modules/ModulePropertyTest.java |   50 +
 .../java/org/jboss/modules/PathFilterTest.java     |   72 +
 .../org/jboss/modules/SystemResourcesTest.java     |   62 +
 .../modules/ref/AbstractReapableReferenceTest.java |   34 +
 .../jboss/modules/ref/AbstractReferenceTest.java   |   81 +
 .../modules/ref/PhantomReferenceTestCase.java      |   96 +
 .../org/jboss/modules/ref/ReferencesTestCase.java  |  216 ++
 .../jboss/modules/ref/SoftReferenceTestCase.java   |   79 +
 .../jboss/modules/ref/StrongReferenceTestCase.java |   64 +
 src/test/java/org/jboss/modules/ref/Thing.java     |   35 +
 .../jboss/modules/ref/WeakReferenceTestCase.java   |   87 +
 .../java/org/jboss/modules/ref/util/Assert.java    |   82 +
 .../org/jboss/modules/ref/util/TestReaper.java     |   81 +
 src/test/java/org/jboss/modules/test/BarImpl.java  |   22 +
 src/test/java/org/jboss/modules/test/ClassA.java   |   25 +
 src/test/java/org/jboss/modules/test/ClassB.java   |   25 +
 src/test/java/org/jboss/modules/test/ClassC.java   |   25 +
 src/test/java/org/jboss/modules/test/ClassD.java   |   25 +
 .../java/org/jboss/modules/test/ImportedClass.java |   27 +
 .../org/jboss/modules/test/ImportedInterface.java  |   27 +
 .../java/org/jboss/modules/test/JAXPCaller.java    |  178 ++
 src/test/java/org/jboss/modules/test/QuxBar.java   |   21 +
 src/test/java/org/jboss/modules/test/QuxFoo.java   |   22 +
 src/test/java/org/jboss/modules/test/QuxImpl.java  |   22 +
 .../java/org/jboss/modules/test/TestClass.java     |   26 +
 .../org/jboss/modules/util/ModulesTestBase.java    |  158 +
 .../org/jboss/modules/util/TestModuleLoader.java   |   58 +
 .../org/jboss/modules/util/TestResourceLoader.java |  251 ++
 src/test/java/org/jboss/modules/util/Util.java     |   82 +
 src/test/resources/class-resources/file1.txt       |    0
 .../org/jboss/modules/test/file2.txt               |    0
 .../test/fileresourceloader/META-INF/MANIFEST.MF   |   10 +
 .../test/fileresourceloader/nested/nested.txt      |    1 +
 .../resources/test/fileresourceloader/test.txt     |    1 +
 .../layeredmodulepath/add-ons/a/shared/module.xml  |   29 +
 .../layeredmodulepath/add-ons/a/unique/module.xml  |   29 +
 .../layeredmodulepath/add-ons/b/shared/module.xml  |   29 +
 .../layeredmodulepath/add-ons/b/unique/module.xml  |   29 +
 .../layers/base/shared/module.xml                  |   29 +
 .../layers/base/unique/module.xml                  |   29 +
 .../layeredmodulepath/layers/mid/shared/module.xml |   29 +
 .../layeredmodulepath/layers/mid/unique/module.xml |   29 +
 .../layeredmodulepath/layers/top/shared/module.xml |   29 +
 .../layeredmodulepath/layers/top/unique/module.xml |   29 +
 .../test/layeredmodulepath/user/shared/module.xml  |   29 +
 .../test/layeredmodulepath/user/unique/module.xml  |   29 +
 .../services/javax.xml.datatype.DatatypeFactory    |   19 +
 .../javax.xml.parsers.DocumentBuilderFactory       |   19 +
 .../services/javax.xml.parsers.SAXParserFactory    |   19 +
 .../services/javax.xml.stream.XMLEventFactory      |   19 +
 .../services/javax.xml.stream.XMLInputFactory      |   19 +
 .../services/javax.xml.stream.XMLOutputFactory     |   19 +
 .../javax.xml.transform.TransformerFactory         |   19 +
 .../services/javax.xml.validation.SchemaFactory    |   19 +
 .../META-INF/services/javax.xml.xpath.XPathFactory |   19 +
 .../jaxp/META-INF/services/org.xml.sax.driver      |   19 +
 .../rootOne/META-INF/MANIFEST.MF                   |    9 +
 .../modulecontentloader/rootOne/nested/nested.txt  |    1 +
 .../test/modulecontentloader/rootOne/test.txt      |    1 +
 .../modulecontentloader/rootTwo/nested/nested.txt  |    1 +
 .../rootTwo/nestedTwo/nested.txt                   |    1 +
 .../test/modulecontentloader/rootTwo/testTwo.txt   |    1 +
 .../test/repo/test/bad-deps/main/module.xml        |   29 +
 .../test/repo/test/circular-deps-A/main/module.xml |   30 +
 .../test/repo/test/circular-deps-B/main/module.xml |   29 +
 .../test/repo/test/circular-deps-C/main/module.xml |   30 +
 .../test/repo/test/circular-deps-D/main/module.xml |   29 +
 .../META-INF/services/javax.ws.rs.ext.Providers    |   20 +
 .../repo/test/jaxrs-jaxb-provider/main/module.xml  |   30 +
 .../META-INF/services/javax.ws.rs.ext.Providers    |   20 +
 .../resources/test/repo/test/jaxrs/main/module.xml |   31 +
 .../resources/test/repo/test/maven/main/module.xml |   28 +
 .../test/repo/test/maven/non-main/module.xml       |   28 +
 .../repo/test/service/main/META-INF/services/dummy |   19 +
 .../test/repo/test/service/main/module.xml         |   29 +
 .../resources/test/repo/test/test/main/module.xml  |   30 +
 .../test/repo/test/with-deps/main/module.xml       |   29 +
 238 files changed, 34588 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..baebd03
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,16 @@
+# Ignore .svn metadata files
+.svn
+ 
+#Ignore Maven generated target folders
+target
+
+# Ignore eclipse files
+.project
+.classpath
+.settings
+
+# Ignore IDEA files
+*.iml
+*.ipr
+*.iws
+/.idea
\ No newline at end of file
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,202 @@
+
+                                 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.
diff --git a/XPP3-LICENSE.txt b/XPP3-LICENSE.txt
new file mode 100644
index 0000000..4cc7224
--- /dev/null
+++ b/XPP3-LICENSE.txt
@@ -0,0 +1,46 @@
+Indiana University Extreme! Lab Software License
+
+Version 1.1.1
+
+Copyright (c) 2002 Extreme! Lab, Indiana University. 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 end-user documentation included with the redistribution, if any, 
+   must include the following acknowledgment:
+
+  "This product includes software developed by the Indiana University 
+  Extreme! Lab (http://www.extreme.indiana.edu/)."
+
+Alternately, this acknowledgment may appear in the software itself, 
+if and wherever such third-party acknowledgments normally appear.
+
+4. The names "Indiana Univeristy" and "Indiana Univeristy Extreme! Lab" 
+must not be used to endorse or promote products derived from this 
+software without prior written permission. For written permission, 
+please contact http://www.extreme.indiana.edu/.
+
+5. Products derived from this software may not use "Indiana Univeristy" 
+name nor may "Indiana Univeristy" appear in their name, without prior 
+written permission of the Indiana University.
+
+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 THE AUTHORS, COPYRIGHT HOLDERS OR ITS CONTRIBUTORS
+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.
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..2d89857
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ JBoss, Home of Professional Open Source.
+  ~ Copyright 2010, Red Hat, Inc., and individual contributors
+  ~ as indicated by the @author tags. See the copyright.txt file in the
+  ~ distribution for a full listing of individual contributors.
+  ~
+  ~ This is free software; you can redistribute it and/or modify it
+  ~ under the terms of the GNU Lesser General Public License as
+  ~ published by the Free Software Foundation; either version 2.1 of
+  ~ the License, or (at your option) any later version.
+  ~
+  ~ This software is distributed in the hope that it will be useful,
+  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  ~ Lesser General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU Lesser General Public
+  ~ License along with this software; if not, write to the Free
+  ~ Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  ~ 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  -->
+
+<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>org.jboss.modules</groupId>
+    <artifactId>jboss-modules</artifactId>
+    <version>1.4.1.Final</version>
+    <name>JBoss Modules</name>
+
+    <parent>
+        <groupId>org.jboss</groupId>
+        <artifactId>jboss-parent</artifactId>
+        <version>15</version>
+    </parent>
+
+    <licenses>
+        <license>
+            <name>asl</name>
+            <url>http://repository.jboss.org/licenses/apache-2.0.txt</url>
+            <distribution>repo</distribution>
+        </license>
+    </licenses>
+
+    <properties>
+        <skip.enforcer>false</skip.enforcer>
+        <skip.compile>false</skip.compile>
+    </properties>
+
+    <build>
+        <plugins>
+            <!-- Compiler -->
+            <plugin>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <showDeprecation>true</showDeprecation>
+                    <showWarnings>true</showWarnings>
+                    <skipMain>${skip.compile}</skipMain>
+                    <skip>${skip.compile}</skip>
+                </configuration>
+            </plugin>
+
+            <!-- Surefire -->
+            <plugin>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <redirectTestOutputToFile>true</redirectTestOutputToFile>
+                    <trimStackTrace>false</trimStackTrace>
+                     <!-- Modules ignores Class-Path -->
+                    <useManifestOnlyJar>false</useManifestOnlyJar>
+                    <printSummary>true</printSummary>
+                    <includes>
+                        <include>**/*Test.java</include>
+                    </includes>
+                    <forkMode>always</forkMode>
+                </configuration>
+            </plugin>
+
+            <!-- JAR -->
+            <plugin>
+                <artifactId>maven-jar-plugin</artifactId>
+                <configuration>
+                    <archive>
+                        <manifest>
+                            <mainClass>org.jboss.modules.Main</mainClass>
+                        </manifest>
+                        <manifestEntries>
+                            <Jar-Version>${project.version}</Jar-Version>
+                            <Jar-Name>${project.artifactId}</Jar-Name>
+                        </manifestEntries>
+                    </archive>
+                </configuration>
+            </plugin>
+
+            <!-- Source -->
+            <plugin>
+                <artifactId>maven-source-plugin</artifactId>
+            </plugin>
+
+            <!-- Javadoc -->
+            <plugin>
+                <artifactId>maven-javadoc-plugin</artifactId>
+                <configuration>
+                    <doclet>net.gleamynode.apiviz.APIviz</doclet>
+                    <docletArtifact>
+                        <groupId>org.jboss.apiviz</groupId>
+                        <artifactId>apiviz</artifactId>
+                        <version>1.3.2.GA</version>
+                    </docletArtifact>
+                    <excludePackageNames>__redirected</excludePackageNames>
+                </configuration>
+            </plugin>
+
+            <!-- Enforcer -->
+            <plugin>
+                <artifactId>maven-enforcer-plugin</artifactId>
+                <configuration>
+                    <skip>${skip.enforcer}</skip>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+    <dependencies>
+        <dependency>
+            <groupId>org.jboss.shrinkwrap</groupId>
+            <artifactId>shrinkwrap-impl-base</artifactId>
+            <version>1.2.2</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.11</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/src/main/java/__redirected/__DatatypeFactory.java b/src/main/java/__redirected/__DatatypeFactory.java
new file mode 100644
index 0000000..601e025
--- /dev/null
+++ b/src/main/java/__redirected/__DatatypeFactory.java
@@ -0,0 +1,213 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package __redirected;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.GregorianCalendar;
+
+import javax.xml.datatype.DatatypeConfigurationException;
+import javax.xml.datatype.DatatypeFactory;
+import javax.xml.datatype.Duration;
+import javax.xml.datatype.XMLGregorianCalendar;
+
+import org.jboss.modules.ModuleIdentifier;
+import org.jboss.modules.ModuleLoader;
+
+/**
+ * A redirecting DatatypeFactory
+ *
+ * @author Jason T. Greene
+ */
+ at SuppressWarnings("unchecked")
+public final class __DatatypeFactory extends DatatypeFactory {
+    private static final Constructor<? extends DatatypeFactory> PLATFORM_FACTORY;
+    private static volatile Constructor<? extends DatatypeFactory> DEFAULT_FACTORY;
+
+    static {
+        Thread thread = Thread.currentThread();
+        ClassLoader old = thread.getContextClassLoader();
+
+        // Unfortunately we can not use null because of a stupid bug in the jdk JAXP factory finder.
+        // Lack of tccl causes the provider file discovery to fallback to the jaxp loader (bootclasspath)
+        // which is correct. However, after parsing it, it then disables the fallback for the loading of the class.
+        // Thus, the class can not be found.
+        //
+        // Work around the problem by using the System CL, although in the future we may want to just "inherit"
+        // the environment's TCCL
+        thread.setContextClassLoader(ClassLoader.getSystemClassLoader());
+        try {
+            if (System.getProperty(DatatypeFactory.class.getName(), "").equals(__DatatypeFactory.class.getName())) {
+                System.clearProperty(DatatypeFactory.class.getName());
+            }
+            DatatypeFactory factory = DatatypeFactory.newInstance();
+            try {
+                DEFAULT_FACTORY = PLATFORM_FACTORY = factory.getClass().getConstructor();
+            } catch (NoSuchMethodException e) {
+                throw __RedirectedUtils.wrapped(new NoSuchMethodError(e.getMessage()), e);
+            }
+            System.setProperty(DatatypeFactory.class.getName(), __DatatypeFactory.class.getName());
+        } catch (DatatypeConfigurationException e) {
+            throw new IllegalArgumentException("Problem configuring DatatypeFactory", e);
+        } finally {
+            thread.setContextClassLoader(old);
+        }
+    }
+
+    public static void changeDefaultFactory(ModuleIdentifier id, ModuleLoader loader) {
+        Class<? extends DatatypeFactory> clazz = __RedirectedUtils.loadProvider(id, DatatypeFactory.class, loader);
+        if (clazz != null) {
+            try {
+                DEFAULT_FACTORY = clazz.getConstructor();
+            } catch (NoSuchMethodException e) {
+                throw __RedirectedUtils.wrapped(new NoSuchMethodError(e.getMessage()), e);
+            }
+        }
+    }
+
+    public static void restorePlatformFactory() {
+        DEFAULT_FACTORY = PLATFORM_FACTORY;
+    }
+
+    /**
+     * Init method.
+     */
+    public static void init() {}
+
+    /**
+     * Construct a new instance.
+     */
+    public __DatatypeFactory() {
+        Constructor<? extends DatatypeFactory> factory = DEFAULT_FACTORY;
+        ClassLoader loader = Thread.currentThread().getContextClassLoader();
+        try {
+            if (loader != null) {
+                Class<? extends DatatypeFactory> provider = __RedirectedUtils.loadProvider(DatatypeFactory.class, loader);
+                if (provider != null)
+                    factory = provider.getConstructor();
+            }
+
+            actual = factory.newInstance();
+        } catch (InstantiationException e) {
+            throw __RedirectedUtils.wrapped(new InstantiationError(e.getMessage()), e);
+        } catch (IllegalAccessException e) {
+            throw __RedirectedUtils.wrapped(new IllegalAccessError(e.getMessage()), e);
+        } catch (InvocationTargetException e) {
+            throw __RedirectedUtils.rethrowCause(e);
+        } catch (NoSuchMethodException e) {
+            throw __RedirectedUtils.wrapped(new NoSuchMethodError(e.getMessage()), e);
+        }
+    }
+
+    private final DatatypeFactory actual;
+
+    public Duration newDuration(String lexicalRepresentation) {
+        return actual.newDuration(lexicalRepresentation);
+    }
+
+    public String toString() {
+        return actual.toString();
+    }
+
+    public Duration newDuration(long durationInMilliSeconds) {
+        return actual.newDuration(durationInMilliSeconds);
+    }
+
+    public Duration newDuration(boolean isPositive, BigInteger years, BigInteger months, BigInteger days, BigInteger hours,
+            BigInteger minutes, BigDecimal seconds) {
+        return actual.newDuration(isPositive, years, months, days, hours, minutes, seconds);
+    }
+
+    public Duration newDuration(boolean isPositive, int years, int months, int days, int hours, int minutes, int seconds) {
+        return actual.newDuration(isPositive, years, months, days, hours, minutes, seconds);
+    }
+
+    public Duration newDurationDayTime(String lexicalRepresentation) {
+        return actual.newDurationDayTime(lexicalRepresentation);
+    }
+
+    public Duration newDurationDayTime(long durationInMilliseconds) {
+        return actual.newDurationDayTime(durationInMilliseconds);
+    }
+
+    public Duration newDurationDayTime(boolean isPositive, BigInteger day, BigInteger hour, BigInteger minute, BigInteger second) {
+        return actual.newDurationDayTime(isPositive, day, hour, minute, second);
+    }
+
+    public Duration newDurationDayTime(boolean isPositive, int day, int hour, int minute, int second) {
+        return actual.newDurationDayTime(isPositive, day, hour, minute, second);
+    }
+
+    public Duration newDurationYearMonth(String lexicalRepresentation) {
+        return actual.newDurationYearMonth(lexicalRepresentation);
+    }
+
+    public Duration newDurationYearMonth(long durationInMilliseconds) {
+        return actual.newDurationYearMonth(durationInMilliseconds);
+    }
+
+    public Duration newDurationYearMonth(boolean isPositive, BigInteger year, BigInteger month) {
+        return actual.newDurationYearMonth(isPositive, year, month);
+    }
+
+    public Duration newDurationYearMonth(boolean isPositive, int year, int month) {
+        return actual.newDurationYearMonth(isPositive, year, month);
+    }
+
+    public XMLGregorianCalendar newXMLGregorianCalendar() {
+        return actual.newXMLGregorianCalendar();
+    }
+
+    public XMLGregorianCalendar newXMLGregorianCalendar(String lexicalRepresentation) {
+        return actual.newXMLGregorianCalendar(lexicalRepresentation);
+    }
+
+    public XMLGregorianCalendar newXMLGregorianCalendar(GregorianCalendar cal) {
+        return actual.newXMLGregorianCalendar(cal);
+    }
+
+    public XMLGregorianCalendar newXMLGregorianCalendar(BigInteger year, int month, int day, int hour, int minute, int second,
+            BigDecimal fractionalSecond, int timezone) {
+        return actual.newXMLGregorianCalendar(year, month, day, hour, minute, second, fractionalSecond, timezone);
+    }
+
+    public XMLGregorianCalendar newXMLGregorianCalendar(int year, int month, int day, int hour, int minute, int second,
+            int millisecond, int timezone) {
+        return actual.newXMLGregorianCalendar(year, month, day, hour, minute, second, millisecond, timezone);
+    }
+
+    public XMLGregorianCalendar newXMLGregorianCalendarDate(int year, int month, int day, int timezone) {
+        return actual.newXMLGregorianCalendarDate(year, month, day, timezone);
+    }
+
+    public XMLGregorianCalendar newXMLGregorianCalendarTime(int hours, int minutes, int seconds, int timezone) {
+        return actual.newXMLGregorianCalendarTime(hours, minutes, seconds, timezone);
+    }
+
+    public XMLGregorianCalendar newXMLGregorianCalendarTime(int hours, int minutes, int seconds, BigDecimal fractionalSecond,
+            int timezone) {
+        return actual.newXMLGregorianCalendarTime(hours, minutes, seconds, fractionalSecond, timezone);
+    }
+
+    public XMLGregorianCalendar newXMLGregorianCalendarTime(int hours, int minutes, int seconds, int milliseconds, int timezone) {
+        return actual.newXMLGregorianCalendarTime(hours, minutes, seconds, milliseconds, timezone);
+    }
+}
diff --git a/src/main/java/__redirected/__DocumentBuilderFactory.java b/src/main/java/__redirected/__DocumentBuilderFactory.java
new file mode 100644
index 0000000..55baa94
--- /dev/null
+++ b/src/main/java/__redirected/__DocumentBuilderFactory.java
@@ -0,0 +1,200 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package __redirected;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.validation.Schema;
+
+import org.jboss.modules.ModuleIdentifier;
+import org.jboss.modules.ModuleLoader;
+
+/**
+ * A redirecting DocumentBuilderFactory
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ * @author Jason T. Greene
+ */
+public final class __DocumentBuilderFactory extends DocumentBuilderFactory {
+    private static final Constructor<? extends DocumentBuilderFactory> PLATFORM_FACTORY;
+    private static volatile Constructor<? extends DocumentBuilderFactory> DEFAULT_FACTORY;
+
+    static {
+        Thread thread = Thread.currentThread();
+        ClassLoader old = thread.getContextClassLoader();
+
+        // Unfortunately we can not use null because of a stupid bug in the jdk JAXP factory finder.
+        // Lack of tccl causes the provider file discovery to fallback to the jaxp loader (bootclasspath)
+        // which is correct. However, after parsing it, it then disables the fallback for the loading of the class.
+        // Thus, the class can not be found.
+        //
+        // Work around the problem by using the System CL, although in the future we may want to just "inherit"
+        // the environment's TCCL
+        thread.setContextClassLoader(ClassLoader.getSystemClassLoader());
+        try {
+            if (System.getProperty(DocumentBuilderFactory.class.getName(), "").equals(__DocumentBuilderFactory.class.getName())) {
+                System.clearProperty(DocumentBuilderFactory.class.getName());
+            }
+            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+            try {
+                DEFAULT_FACTORY = PLATFORM_FACTORY = factory.getClass().getConstructor();
+            } catch (NoSuchMethodException e) {
+                throw __RedirectedUtils.wrapped(new NoSuchMethodError(e.getMessage()), e);
+            }
+            System.setProperty(DocumentBuilderFactory.class.getName(), __DocumentBuilderFactory.class.getName());
+        } finally {
+            thread.setContextClassLoader(old);
+        }
+    }
+
+    /**
+     * Init method.
+     */
+    public static void init() {}
+
+    public static void changeDefaultFactory(ModuleIdentifier id, ModuleLoader loader) {
+        Class<? extends DocumentBuilderFactory> clazz = __RedirectedUtils.loadProvider(id, DocumentBuilderFactory.class, loader);
+        if (clazz != null) {
+            try {
+                DEFAULT_FACTORY = clazz.getConstructor();
+            } catch (NoSuchMethodException e) {
+                throw __RedirectedUtils.wrapped(new NoSuchMethodError(e.getMessage()), e);
+            }
+        }
+    }
+
+    public static void restorePlatformFactory() {
+        DEFAULT_FACTORY = PLATFORM_FACTORY;
+    }
+
+    /**
+     * Construct a new instance.
+     */
+    public __DocumentBuilderFactory() {
+        Constructor<? extends DocumentBuilderFactory> factory = DEFAULT_FACTORY;
+        ClassLoader loader = Thread.currentThread().getContextClassLoader();
+        try {
+            if (loader != null) {
+                Class<? extends DocumentBuilderFactory> provider = __RedirectedUtils.loadProvider(DocumentBuilderFactory.class, loader);
+                if (provider != null)
+                    factory = provider.getConstructor();
+            }
+
+            actual = factory.newInstance();
+        } catch (InstantiationException e) {
+            throw __RedirectedUtils.wrapped(new InstantiationError(e.getMessage()), e);
+        } catch (IllegalAccessException e) {
+            throw __RedirectedUtils.wrapped(new IllegalAccessError(e.getMessage()), e);
+        } catch (InvocationTargetException e) {
+            throw __RedirectedUtils.rethrowCause(e);
+        } catch (NoSuchMethodException e) {
+            throw __RedirectedUtils.wrapped(new NoSuchMethodError(e.getMessage()), e);
+        }
+    }
+
+    private final DocumentBuilderFactory actual;
+
+    public DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
+        return actual.newDocumentBuilder();
+    }
+
+    public void setNamespaceAware(final boolean awareness) {
+        actual.setNamespaceAware(awareness);
+    }
+
+    public void setValidating(final boolean validating) {
+        actual.setValidating(validating);
+    }
+
+    public void setIgnoringElementContentWhitespace(final boolean whitespace) {
+        actual.setIgnoringElementContentWhitespace(whitespace);
+    }
+
+    public void setExpandEntityReferences(final boolean expandEntityRef) {
+        actual.setExpandEntityReferences(expandEntityRef);
+    }
+
+    public void setIgnoringComments(final boolean ignoreComments) {
+        actual.setIgnoringComments(ignoreComments);
+    }
+
+    public void setCoalescing(final boolean coalescing) {
+        actual.setCoalescing(coalescing);
+    }
+
+    public boolean isNamespaceAware() {
+        return actual.isNamespaceAware();
+    }
+
+    public boolean isValidating() {
+        return actual.isValidating();
+    }
+
+    public boolean isIgnoringElementContentWhitespace() {
+        return actual.isIgnoringElementContentWhitespace();
+    }
+
+    public boolean isExpandEntityReferences() {
+        return actual.isExpandEntityReferences();
+    }
+
+    public boolean isIgnoringComments() {
+        return actual.isIgnoringComments();
+    }
+
+    public boolean isCoalescing() {
+        return actual.isCoalescing();
+    }
+
+    public void setAttribute(final String name, final Object value) throws IllegalArgumentException {
+        actual.setAttribute(name, value);
+    }
+
+    public Object getAttribute(final String name) throws IllegalArgumentException {
+        return actual.getAttribute(name);
+    }
+
+    public void setFeature(final String name, final boolean value) throws ParserConfigurationException {
+        actual.setFeature(name, value);
+    }
+
+    public boolean getFeature(final String name) throws ParserConfigurationException {
+        return actual.getFeature(name);
+    }
+
+    public Schema getSchema() {
+        return actual.getSchema();
+    }
+
+    public void setSchema(final Schema schema) {
+        actual.setSchema(schema);
+    }
+
+    public void setXIncludeAware(final boolean state) {
+        actual.setXIncludeAware(state);
+    }
+
+    public boolean isXIncludeAware() {
+        return actual.isXIncludeAware();
+    }
+}
diff --git a/src/main/java/__redirected/__JAXPRedirected.java b/src/main/java/__redirected/__JAXPRedirected.java
new file mode 100644
index 0000000..00157f6
--- /dev/null
+++ b/src/main/java/__redirected/__JAXPRedirected.java
@@ -0,0 +1,83 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package __redirected;
+
+import org.jboss.modules.ModuleIdentifier;
+import org.jboss.modules.ModuleLoader;
+
+/**
+ * Executes common operations against ALL JAXP redirection classes.
+ *
+ * @author Jason T. Greene
+ */
+public class __JAXPRedirected {
+
+    /**
+     * Change all provided factories to the ones contained in the
+     * specified module using the standard META-INF/services lookup
+     * pattern.
+     *
+     * @param id the id for the jaxp module
+     * @param loader the loader containing the jaxp module
+     */
+    public static void changeAll(ModuleIdentifier id, ModuleLoader loader) {
+        __DocumentBuilderFactory.changeDefaultFactory(id, loader);
+        __SAXParserFactory.changeDefaultFactory(id, loader);
+        __TransformerFactory.changeDefaultFactory(id, loader);
+        __XPathFactory.changeDefaultFactory(id, loader);
+        __XMLEventFactory.changeDefaultFactory(id, loader);
+        __XMLInputFactory.changeDefaultFactory(id, loader);
+        __XMLOutputFactory.changeDefaultFactory(id, loader);
+        __DatatypeFactory.changeDefaultFactory(id, loader);
+        __SchemaFactory.changeDefaultFactory(id, loader);
+        __XMLReaderFactory.changeDefaultFactory(id, loader);
+    }
+
+    /**
+     * Restores all JAXP factories to the ones contained in the JDK
+     * system classpath.
+     */
+    public static void restorePlatformFactory() {
+        __DocumentBuilderFactory.restorePlatformFactory();
+        __SAXParserFactory.restorePlatformFactory();
+        __TransformerFactory.restorePlatformFactory();
+        __XPathFactory.restorePlatformFactory();
+        __XMLEventFactory.restorePlatformFactory();
+        __XMLInputFactory.restorePlatformFactory();
+        __XMLOutputFactory.restorePlatformFactory();
+        __DatatypeFactory.restorePlatformFactory();
+        __SchemaFactory.restorePlatformFactory();
+        __XMLReaderFactory.restorePlatformFactory();
+    }
+
+    /**
+     * Initializes the JAXP redirection system.
+     */
+    public static void initAll() {
+        __DocumentBuilderFactory.init();
+        __SAXParserFactory.init();
+        __TransformerFactory.init();
+        __XPathFactory.init();
+        __XMLEventFactory.init();
+        __XMLInputFactory.init();
+        __XMLOutputFactory.init();
+        __DatatypeFactory.init();
+        __SchemaFactory.init();
+        __XMLReaderFactory.init();
+    }
+}
diff --git a/src/main/java/__redirected/__RedirectedUtils.java b/src/main/java/__redirected/__RedirectedUtils.java
new file mode 100644
index 0000000..d1e126f
--- /dev/null
+++ b/src/main/java/__redirected/__RedirectedUtils.java
@@ -0,0 +1,186 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package __redirected;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.lang.reflect.UndeclaredThrowableException;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.jboss.modules.Module;
+import org.jboss.modules.ModuleClassLoader;
+import org.jboss.modules.ModuleIdentifier;
+import org.jboss.modules.ModuleLoadException;
+import org.jboss.modules.ModuleLoader;
+import org.jboss.modules.log.ModuleLogger;
+
+/**
+ * Common utilities for redirected factories
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ * @authore Jason T. Greene
+ */
+public final class __RedirectedUtils {
+
+    static ModuleLogger getModuleLogger() {
+        final SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            return AccessController.doPrivileged(new PrivilegedAction<ModuleLogger>() {
+                public ModuleLogger run() {
+                    return Module.getModuleLogger();
+                }
+            });
+        } else {
+            return Module.getModuleLogger();
+        }
+    }
+
+    static RuntimeException rethrowCause(Throwable t) throws Error {
+        try {
+            throw t.getCause();
+        } catch (Error e) {
+            throw e;
+        } catch (RuntimeException re) {
+            return re;
+        } catch (Throwable throwable) {
+            return new UndeclaredThrowableException(throwable);
+        }
+    }
+
+    static <E extends Throwable> E wrapped(E e, Throwable orig) {
+        Throwable cause = orig.getCause();
+        if (cause != null) {
+            e.initCause(cause);
+        }
+        e.setStackTrace(orig.getStackTrace());
+        return e;
+    }
+
+    static <T> Class<? extends T> loadProvider(ModuleIdentifier id, Class<T> intf, ModuleLoader moduleLoader) {
+        return loadProvider(id, intf, moduleLoader, null);
+    }
+
+    static <T> Class<? extends T> loadProvider(ModuleIdentifier id, Class<T> intf, ModuleLoader moduleLoader, String name) {
+        Module module;
+        try {
+            module = moduleLoader.loadModule(id);
+        } catch (ModuleLoadException e) {
+            getModuleLogger().providerUnloadable(id.toString(), null);
+            return null;
+        }
+
+        ModuleClassLoader classLoader = module.getClassLoader();
+        return loadProvider(intf, classLoader, name);
+    }
+
+    static <T> Class<? extends T> loadProvider(Class<T> intf, ClassLoader classLoader) {
+        return loadProvider(intf, classLoader, null);
+    }
+
+    static <T> Class<? extends T> loadProvider(Class<T> intf, ClassLoader classLoader, String name) {
+        List<String> names = findProviderClassNames(intf, classLoader, name);
+
+        if (names.isEmpty()) {
+            getModuleLogger().providerUnloadable("Not found", classLoader);
+            return null;
+        }
+
+        String clazzName = names.get(0);
+        try {
+            return classLoader.loadClass(clazzName).asSubclass(intf);
+        } catch (Exception ignore) {
+            getModuleLogger().providerUnloadable(clazzName, classLoader);
+            return null;
+        }
+    }
+
+    static <T> List<Class<? extends T>> loadProviders(Class<T> intf, ClassLoader classLoader) {
+        return loadProviders(intf, classLoader, null);
+    }
+
+    static <T> List<Class<? extends T>> loadProviders(Class<T> intf, ClassLoader classLoader, String name) {
+        List<String> names = findProviderClassNames(intf, classLoader, name);
+
+        if (names.size() < 1) {
+            getModuleLogger().providerUnloadable("Not found", classLoader);
+            return Collections.emptyList();
+        }
+
+        List<Class<? extends T>> classes = new ArrayList<Class<? extends T>>();
+
+        for (String className : names) {
+            try {
+                classes.add(classLoader.loadClass(className).asSubclass(intf));
+            } catch (Exception ignore) {
+                getModuleLogger().providerUnloadable(className, classLoader);
+            }
+        }
+
+        return classes;
+    }
+
+    static <T> List<String> findProviderClassNames(Class<T> intf, ClassLoader loader, String name) {
+        if (name == null)
+            name = intf.getName();
+
+        final InputStream stream = loader.getResourceAsStream("META-INF/services/" + name);
+        if (stream == null)
+            return Collections.emptyList();
+
+
+        List<String> list = new ArrayList<String>();
+        try {
+            final BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
+
+            String line;
+            while ((line = readLine(reader)) != null) {
+                final int i = line.indexOf('#');
+                if (i != -1) {
+                    line = line.substring(0, i);
+                }
+                line = line.trim();
+                if (line.length() == 0)
+                    continue;
+
+                list.add(line);
+            }
+        } finally {
+            try {
+                stream.close();
+            } catch (IOException ignored) {
+            }
+        }
+        return list;
+    }
+
+    private static String readLine(final BufferedReader reader) {
+        try {
+            return reader.readLine();
+        } catch (IOException ignore) {
+            return null;
+        }
+    }
+
+}
diff --git a/src/main/java/__redirected/__SAXParserFactory.java b/src/main/java/__redirected/__SAXParserFactory.java
new file mode 100644
index 0000000..c91b2ee
--- /dev/null
+++ b/src/main/java/__redirected/__SAXParserFactory.java
@@ -0,0 +1,163 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package __redirected;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+import javax.xml.validation.Schema;
+
+import org.jboss.modules.ModuleIdentifier;
+import org.jboss.modules.ModuleLoader;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXNotRecognizedException;
+import org.xml.sax.SAXNotSupportedException;
+
+/**
+ * A redirected SAXParserFactory
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ * @authore Jason T. Greene
+ */
+public final class __SAXParserFactory extends SAXParserFactory {
+    private static final Constructor<? extends SAXParserFactory> PLATFORM_FACTORY;
+    private static volatile Constructor<? extends SAXParserFactory> DEFAULT_FACTORY;
+
+    static {
+        Thread thread = Thread.currentThread();
+        ClassLoader old = thread.getContextClassLoader();
+
+        // Unfortunately we can not use null because of a stupid bug in the jdk JAXP factory finder.
+        // Lack of tccl causes the provider file discovery to fallback to the jaxp loader (bootclasspath)
+        // which is correct. However, after parsing it, it then disables the fallback for the loading of the class.
+        // Thus, the class can not be found.
+        //
+        // Work around the problem by using the System CL, although in the future we may want to just "inherit"
+        // the environment's TCCL
+        thread.setContextClassLoader(ClassLoader.getSystemClassLoader());
+        try {
+            if (System.getProperty(SAXParserFactory.class.getName(), "").equals(__SAXParserFactory.class.getName())) {
+                System.clearProperty(SAXParserFactory.class.getName());
+            }
+            SAXParserFactory factory = SAXParserFactory.newInstance();
+            try {
+               DEFAULT_FACTORY = PLATFORM_FACTORY = factory.getClass().getConstructor();
+            } catch (NoSuchMethodException e) {
+                throw __RedirectedUtils.wrapped(new NoSuchMethodError(e.getMessage()), e);
+            }
+            System.setProperty(SAXParserFactory.class.getName(), __SAXParserFactory.class.getName());
+        } finally {
+            thread.setContextClassLoader(old);
+        }
+    }
+
+    public static void changeDefaultFactory(ModuleIdentifier id, ModuleLoader loader) {
+        Class<? extends SAXParserFactory> clazz = __RedirectedUtils.loadProvider(id, SAXParserFactory.class, loader);
+        if (clazz != null) {
+            try {
+                DEFAULT_FACTORY = clazz.getConstructor();
+            } catch (NoSuchMethodException e) {
+                throw __RedirectedUtils.wrapped(new NoSuchMethodError(e.getMessage()), e);
+            }
+        }
+    }
+
+    public static void restorePlatformFactory() {
+        DEFAULT_FACTORY = PLATFORM_FACTORY;
+    }
+
+    /**
+     * Init method.
+     */
+    public static void init() {}
+
+    /**
+     * Construct a new instance.
+     */
+    public __SAXParserFactory() {
+        Constructor<? extends SAXParserFactory> factory = DEFAULT_FACTORY;
+        ClassLoader loader = Thread.currentThread().getContextClassLoader();
+        try {
+            if (loader != null) {
+                Class<? extends SAXParserFactory> provider = __RedirectedUtils.loadProvider(SAXParserFactory.class, loader);
+                if (provider != null)
+                    factory = provider.getConstructor();
+            }
+
+            actual = factory.newInstance();
+        } catch (InstantiationException e) {
+            throw __RedirectedUtils.wrapped(new InstantiationError(e.getMessage()), e);
+        } catch (IllegalAccessException e) {
+            throw __RedirectedUtils.wrapped(new IllegalAccessError(e.getMessage()), e);
+        } catch (InvocationTargetException e) {
+            throw __RedirectedUtils.rethrowCause(e);
+        } catch (NoSuchMethodException e) {
+            throw __RedirectedUtils.wrapped(new NoSuchMethodError(e.getMessage()), e);
+        }
+    }
+
+    private final SAXParserFactory actual;
+
+    public SAXParser newSAXParser() throws ParserConfigurationException, SAXException {
+        return actual.newSAXParser();
+    }
+
+    public void setNamespaceAware(final boolean awareness) {
+        actual.setNamespaceAware(awareness);
+    }
+
+    public void setValidating(final boolean validating) {
+        actual.setValidating(validating);
+    }
+
+    public boolean isNamespaceAware() {
+        return actual.isNamespaceAware();
+    }
+
+    public boolean isValidating() {
+        return actual.isValidating();
+    }
+
+    public void setFeature(final String name, final boolean value) throws ParserConfigurationException, SAXNotRecognizedException, SAXNotSupportedException {
+        actual.setFeature(name, value);
+    }
+
+    public boolean getFeature(final String name) throws ParserConfigurationException, SAXNotRecognizedException, SAXNotSupportedException {
+        return actual.getFeature(name);
+    }
+
+    public Schema getSchema() {
+        return actual.getSchema();
+    }
+
+    public void setSchema(final Schema schema) {
+        actual.setSchema(schema);
+    }
+
+    public void setXIncludeAware(final boolean state) {
+        actual.setXIncludeAware(state);
+    }
+
+    public boolean isXIncludeAware() {
+        return actual.isXIncludeAware();
+    }
+}
diff --git a/src/main/java/__redirected/__SchemaFactory.java b/src/main/java/__redirected/__SchemaFactory.java
new file mode 100644
index 0000000..b032dc4
--- /dev/null
+++ b/src/main/java/__redirected/__SchemaFactory.java
@@ -0,0 +1,185 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package __redirected;
+
+import java.io.File;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.net.URL;
+import java.util.List;
+
+import javax.xml.XMLConstants;
+import javax.xml.transform.Source;
+import javax.xml.validation.Schema;
+import javax.xml.validation.SchemaFactory;
+
+import org.jboss.modules.ModuleIdentifier;
+import org.jboss.modules.ModuleLoader;
+import org.w3c.dom.ls.LSResourceResolver;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXNotRecognizedException;
+import org.xml.sax.SAXNotSupportedException;
+
+/**
+ * A redirected SchemaFactory
+ *
+ * @author Jason T. Greene
+ */
+public final class __SchemaFactory extends SchemaFactory {
+    private static final Constructor<? extends SchemaFactory> PLATFORM_FACTORY;
+    private static volatile Constructor<? extends SchemaFactory> DEFAULT_FACTORY;
+
+    static {
+        Thread thread = Thread.currentThread();
+        ClassLoader old = thread.getContextClassLoader();
+
+        // Unfortunately we can not use null because of a stupid bug in the jdk JAXP factory finder.
+        // Lack of tccl causes the provider file discovery to fallback to the jaxp loader (bootclasspath)
+        // which is correct. However, after parsing it, it then disables the fallback for the loading of the class.
+        // Thus, the class can not be found.
+        //
+        // Work around the problem by using the System CL, although in the future we may want to just "inherit"
+        // the environment's TCCL
+        thread.setContextClassLoader(ClassLoader.getSystemClassLoader());
+        try {
+            if (System.getProperty(SchemaFactory.class.getName(), "").equals(__SchemaFactory.class.getName())) {
+                System.clearProperty(SchemaFactory.class.getName());
+            }
+            SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+            try {
+                DEFAULT_FACTORY = PLATFORM_FACTORY = factory.getClass().getConstructor();
+            } catch (NoSuchMethodException e) {
+                throw __RedirectedUtils.wrapped(new NoSuchMethodError(e.getMessage()), e);
+            }
+            System.setProperty(SchemaFactory.class.getName() + ":" + XMLConstants.W3C_XML_SCHEMA_NS_URI, __SchemaFactory.class.getName());
+        } finally {
+            thread.setContextClassLoader(old);
+        }
+    }
+
+    public static void changeDefaultFactory(ModuleIdentifier id, ModuleLoader loader) {
+        Class<? extends SchemaFactory> clazz = __RedirectedUtils.loadProvider(id, SchemaFactory.class, loader);
+        if (clazz != null) {
+            try {
+                DEFAULT_FACTORY = clazz.getConstructor();
+            } catch (NoSuchMethodException e) {
+                throw __RedirectedUtils.wrapped(new NoSuchMethodError(e.getMessage()), e);
+            }
+        }
+    }
+
+    public static void restorePlatformFactory() {
+        DEFAULT_FACTORY = PLATFORM_FACTORY;
+    }
+
+    /**
+     * Init method.
+     */
+    public static void init() {}
+
+    /**
+     * Construct a new instance.
+     */
+    public __SchemaFactory() {
+        Constructor<? extends SchemaFactory> factory = DEFAULT_FACTORY;
+        ClassLoader loader = Thread.currentThread().getContextClassLoader();
+        SchemaFactory foundInstance = null;
+        try {
+            if (loader != null) {
+                List<Class<? extends SchemaFactory>> providers = __RedirectedUtils.loadProviders(SchemaFactory.class, loader);
+                for (Class<? extends SchemaFactory> provider : providers) {
+                    SchemaFactory instance = provider.newInstance();
+                    if (instance.isSchemaLanguageSupported(XMLConstants.W3C_XML_SCHEMA_NS_URI)) {
+                        foundInstance = instance;
+                        break;
+                    }
+                }
+            }
+
+            actual = foundInstance != null ? foundInstance : factory.newInstance();
+
+        } catch (InstantiationException e) {
+            throw __RedirectedUtils.wrapped(new InstantiationError(e.getMessage()), e);
+        } catch (IllegalAccessException e) {
+            throw __RedirectedUtils.wrapped(new IllegalAccessError(e.getMessage()), e);
+        } catch (InvocationTargetException e) {
+            throw __RedirectedUtils.rethrowCause(e);
+        }
+    }
+
+
+    private final SchemaFactory actual;
+
+    public boolean isSchemaLanguageSupported(String objectModel) {
+        return actual.isSchemaLanguageSupported(objectModel);
+    }
+
+    public boolean getFeature(String name) throws SAXNotRecognizedException, SAXNotSupportedException {
+        return actual.getFeature(name);
+    }
+
+    public void setFeature(String name, boolean value) throws SAXNotSupportedException, SAXNotRecognizedException {
+        actual.setFeature(name, value);
+    }
+
+    public void setProperty(String name, Object object) throws SAXNotRecognizedException, SAXNotSupportedException {
+        actual.setProperty(name, object);
+    }
+
+    public Object getProperty(String name) throws SAXNotRecognizedException, SAXNotSupportedException {
+        return actual.getProperty(name);
+    }
+
+    public void setErrorHandler(ErrorHandler errorHandler) {
+        actual.setErrorHandler(errorHandler);
+    }
+
+    public ErrorHandler getErrorHandler() {
+        return actual.getErrorHandler();
+    }
+
+    public void setResourceResolver(LSResourceResolver resourceResolver) {
+        actual.setResourceResolver(resourceResolver);
+    }
+
+    public LSResourceResolver getResourceResolver() {
+        return actual.getResourceResolver();
+    }
+
+    public Schema newSchema(Source schema) throws SAXException {
+        return actual.newSchema(schema);
+    }
+
+    public Schema newSchema(File schema) throws SAXException {
+        return actual.newSchema(schema);
+    }
+
+    public Schema newSchema(URL schema) throws SAXException {
+        return actual.newSchema(schema);
+    }
+
+    public Schema newSchema(Source[] schemas) throws SAXException {
+        return actual.newSchema(schemas);
+    }
+
+    public Schema newSchema() throws SAXException {
+        return actual.newSchema();
+    }
+}
diff --git a/src/main/java/__redirected/__TransformerFactory.java b/src/main/java/__redirected/__TransformerFactory.java
new file mode 100644
index 0000000..fb65093
--- /dev/null
+++ b/src/main/java/__redirected/__TransformerFactory.java
@@ -0,0 +1,215 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package __redirected;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+
+import javax.xml.transform.ErrorListener;
+import javax.xml.transform.Source;
+import javax.xml.transform.Templates;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.URIResolver;
+import javax.xml.transform.sax.SAXTransformerFactory;
+import javax.xml.transform.sax.TemplatesHandler;
+import javax.xml.transform.sax.TransformerHandler;
+
+import org.jboss.modules.ModuleIdentifier;
+import org.jboss.modules.ModuleLoader;
+import org.xml.sax.XMLFilter;
+
+/**
+ * A redirected TransformerFactory
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ * @author Jason T. Greene
+ */
+public final class __TransformerFactory extends SAXTransformerFactory {
+    private static final Constructor<? extends TransformerFactory> PLATFORM_FACTORY;
+    private static volatile Constructor<? extends TransformerFactory> DEFAULT_FACTORY;
+
+    static {
+        Thread thread = Thread.currentThread();
+        ClassLoader old = thread.getContextClassLoader();
+
+        // Unfortunately we can not use null because of a stupid bug in the jdk JAXP factory finder.
+        // Lack of tccl causes the provider file discovery to fallback to the jaxp loader (bootclasspath)
+        // which is correct. However, after parsing it, it then disables the fallback for the loading of the class.
+        // Thus, the class can not be found.
+        //
+        // Work around the problem by using the System CL, although in the future we may want to just "inherit"
+        // the environment's TCCL
+        thread.setContextClassLoader(ClassLoader.getSystemClassLoader());
+        try {
+            if (System.getProperty(TransformerFactory.class.getName(), "").equals(__TransformerFactory.class.getName())) {
+                System.clearProperty(TransformerFactory.class.getName());
+            }
+            TransformerFactory factory = TransformerFactory.newInstance();
+            try {
+                DEFAULT_FACTORY = PLATFORM_FACTORY = factory.getClass().getConstructor();
+            } catch (NoSuchMethodException e) {
+                throw __RedirectedUtils.wrapped(new NoSuchMethodError(e.getMessage()), e);
+            }
+            System.setProperty(TransformerFactory.class.getName(), __TransformerFactory.class.getName());
+        } finally {
+            thread.setContextClassLoader(old);
+        }
+    }
+
+    public static void changeDefaultFactory(ModuleIdentifier id, ModuleLoader loader) {
+        Class<? extends TransformerFactory> clazz = __RedirectedUtils.loadProvider(id, TransformerFactory.class, loader);
+        if (clazz != null) {
+            try {
+                DEFAULT_FACTORY = clazz.getConstructor();
+            } catch (NoSuchMethodException e) {
+                throw __RedirectedUtils.wrapped(new NoSuchMethodError(e.getMessage()), e);
+            }
+        }
+    }
+
+    public static void restorePlatformFactory() {
+        DEFAULT_FACTORY = PLATFORM_FACTORY;
+    }
+
+    /**
+     * Init method.
+     */
+    public static void init() {}
+
+    /**
+     * Construct a new instance.
+     */
+    public __TransformerFactory() {
+        Constructor<? extends TransformerFactory> factory = DEFAULT_FACTORY;
+        ClassLoader loader = Thread.currentThread().getContextClassLoader();
+        try {
+            if (loader != null) {
+                Class<? extends TransformerFactory> provider = __RedirectedUtils.loadProvider(TransformerFactory.class, loader);
+                if (provider != null)
+                    factory = provider.getConstructor();
+            }
+
+            actual = factory.newInstance();
+            saxtual = (actual instanceof SAXTransformerFactory) ? (SAXTransformerFactory)actual : null;
+
+        } catch (InstantiationException e) {
+            throw __RedirectedUtils.wrapped(new InstantiationError(e.getMessage()), e);
+        } catch (IllegalAccessException e) {
+            throw __RedirectedUtils.wrapped(new IllegalAccessError(e.getMessage()), e);
+        } catch (InvocationTargetException e) {
+            throw __RedirectedUtils.rethrowCause(e);
+        } catch (NoSuchMethodException e) {
+            throw __RedirectedUtils.wrapped(new NoSuchMethodError(e.getMessage()), e);
+        }
+    }
+
+    private final TransformerFactory actual;
+    private final SAXTransformerFactory saxtual; // Snicker
+
+    public Transformer newTransformer(Source source) throws TransformerConfigurationException {
+        return actual.newTransformer(source);
+    }
+
+    public Transformer newTransformer() throws TransformerConfigurationException {
+        return actual.newTransformer();
+    }
+
+    public Templates newTemplates(Source source) throws TransformerConfigurationException {
+        return actual.newTemplates(source);
+    }
+
+    public String toString() {
+        return actual.toString();
+    }
+
+    public Source getAssociatedStylesheet(Source source, String media, String title, String charset)
+            throws TransformerConfigurationException {
+        return actual.getAssociatedStylesheet(source, media, title, charset);
+    }
+
+    public void setURIResolver(URIResolver resolver) {
+        actual.setURIResolver(resolver);
+    }
+
+    public URIResolver getURIResolver() {
+        return actual.getURIResolver();
+    }
+
+    public void setFeature(String name, boolean value) throws TransformerConfigurationException {
+        actual.setFeature(name, value);
+    }
+
+    public boolean getFeature(String name) {
+        return actual.getFeature(name);
+    }
+
+    public void setAttribute(String name, Object value) {
+        actual.setAttribute(name, value);
+    }
+
+    public Object getAttribute(String name) {
+        return actual.getAttribute(name);
+    }
+
+    public void setErrorListener(ErrorListener listener) {
+        actual.setErrorListener(listener);
+    }
+
+    public ErrorListener getErrorListener() {
+        return actual.getErrorListener();
+    }
+
+    public TransformerHandler newTransformerHandler(Source src) throws TransformerConfigurationException {
+        if (saxtual == null)
+            throw new TransformerConfigurationException("Provider is not a SAXTransformerFactory");
+        return saxtual.newTransformerHandler();
+    }
+
+    public TransformerHandler newTransformerHandler(Templates templates) throws TransformerConfigurationException {
+        if (saxtual == null)
+            throw new TransformerConfigurationException("Provider is not a SAXTransformerFactory");
+        return saxtual.newTransformerHandler(templates);
+    }
+
+    public TransformerHandler newTransformerHandler() throws TransformerConfigurationException {
+        if (saxtual == null)
+            throw new TransformerConfigurationException("Provider is not a SAXTransformerFactory");
+        return saxtual.newTransformerHandler();
+    }
+
+    public TemplatesHandler newTemplatesHandler() throws TransformerConfigurationException {
+        if (saxtual == null)
+            throw new TransformerConfigurationException("Provider is not a SAXTransformerFactory");
+        return saxtual.newTemplatesHandler();
+    }
+
+    public XMLFilter newXMLFilter(Source src) throws TransformerConfigurationException {
+        if (saxtual == null)
+            throw new TransformerConfigurationException("Provider is not a SAXTransformerFactory");
+        return saxtual.newXMLFilter(src);
+    }
+
+    public XMLFilter newXMLFilter(Templates templates) throws TransformerConfigurationException {
+        if (saxtual == null)
+            throw new TransformerConfigurationException("Provider is not a SAXTransformerFactory");
+        return saxtual.newXMLFilter(templates);
+    }
+}
diff --git a/src/main/java/__redirected/__XMLEventFactory.java b/src/main/java/__redirected/__XMLEventFactory.java
new file mode 100644
index 0000000..81d1a72
--- /dev/null
+++ b/src/main/java/__redirected/__XMLEventFactory.java
@@ -0,0 +1,234 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package __redirected;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Iterator;
+
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.namespace.QName;
+import javax.xml.stream.Location;
+import javax.xml.stream.XMLEventFactory;
+import javax.xml.stream.events.Attribute;
+import javax.xml.stream.events.Characters;
+import javax.xml.stream.events.Comment;
+import javax.xml.stream.events.DTD;
+import javax.xml.stream.events.EndDocument;
+import javax.xml.stream.events.EndElement;
+import javax.xml.stream.events.EntityDeclaration;
+import javax.xml.stream.events.EntityReference;
+import javax.xml.stream.events.Namespace;
+import javax.xml.stream.events.ProcessingInstruction;
+import javax.xml.stream.events.StartDocument;
+import javax.xml.stream.events.StartElement;
+
+import org.jboss.modules.ModuleIdentifier;
+import org.jboss.modules.ModuleLoader;
+
+/**
+ * A redirected XMLEventFactory
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ * @authore Jason T. Greene
+ */
+ at SuppressWarnings("unchecked")
+public final class __XMLEventFactory extends XMLEventFactory {
+    private static final Constructor<? extends XMLEventFactory> PLATFORM_FACTORY;
+    private static volatile Constructor<? extends XMLEventFactory> DEFAULT_FACTORY;
+
+    static {
+        Thread thread = Thread.currentThread();
+        ClassLoader old = thread.getContextClassLoader();
+
+        // Unfortunately we can not use null because of a stupid bug in the jdk JAXP factory finder.
+        // Lack of tccl causes the provider file discovery to fallback to the jaxp loader (bootclasspath)
+        // which is correct. However, after parsing it, it then disables the fallback for the loading of the class.
+        // Thus, the class can not be found.
+        //
+        // Work around the problem by using the System CL, although in the future we may want to just "inherit"
+        // the environment's TCCL
+        thread.setContextClassLoader(ClassLoader.getSystemClassLoader());
+        try {
+            if (System.getProperty(XMLEventFactory.class.getName(), "").equals(__XMLEventFactory.class.getName())) {
+                System.clearProperty(XMLEventFactory.class.getName());
+            }
+            XMLEventFactory factory = XMLEventFactory.newInstance();
+            try {
+                DEFAULT_FACTORY = PLATFORM_FACTORY = factory.getClass().getConstructor();
+            } catch (NoSuchMethodException e) {
+                throw __RedirectedUtils.wrapped(new NoSuchMethodError(e.getMessage()), e);
+            }
+            System.setProperty(XMLEventFactory.class.getName(), __XMLEventFactory.class.getName());
+        } finally {
+            thread.setContextClassLoader(old);
+        }
+    }
+
+    public static void changeDefaultFactory(ModuleIdentifier id, ModuleLoader loader) {
+        Class<? extends XMLEventFactory> clazz = __RedirectedUtils.loadProvider(id, XMLEventFactory.class, loader);
+        if (clazz != null) {
+            try {
+                DEFAULT_FACTORY = clazz.getConstructor();
+            } catch (NoSuchMethodException e) {
+                throw __RedirectedUtils.wrapped(new NoSuchMethodError(e.getMessage()), e);
+            }
+        }
+    }
+
+    public static void restorePlatformFactory() {
+        DEFAULT_FACTORY = PLATFORM_FACTORY;
+    }
+
+    /**
+     * Init method.
+     */
+    public static void init() {}
+
+    /**
+     * Construct a new instance.
+     */
+    public __XMLEventFactory() {
+        Constructor<? extends XMLEventFactory> factory = DEFAULT_FACTORY;
+        ClassLoader loader = Thread.currentThread().getContextClassLoader();
+        try {
+            if (loader != null) {
+                Class<? extends XMLEventFactory> provider = __RedirectedUtils.loadProvider(XMLEventFactory.class, loader);
+                if (provider != null)
+                    factory = provider.getConstructor();
+            }
+
+            actual = factory.newInstance();
+        } catch (InstantiationException e) {
+            throw __RedirectedUtils.wrapped(new InstantiationError(e.getMessage()), e);
+        } catch (IllegalAccessException e) {
+            throw __RedirectedUtils.wrapped(new IllegalAccessError(e.getMessage()), e);
+        } catch (InvocationTargetException e) {
+            throw __RedirectedUtils.rethrowCause(e);
+        } catch (NoSuchMethodException e) {
+            throw __RedirectedUtils.wrapped(new NoSuchMethodError(e.getMessage()), e);
+        }
+    }
+
+    private final XMLEventFactory actual;
+
+    public void setLocation(final Location location) {
+        actual.setLocation(location);
+    }
+
+    public Attribute createAttribute(final String prefix, final String namespaceURI, final String localName, final String value) {
+        return actual.createAttribute(prefix, namespaceURI, localName, value);
+    }
+
+    public Attribute createAttribute(final String localName, final String value) {
+        return actual.createAttribute(localName, value);
+    }
+
+    public Attribute createAttribute(final QName name, final String value) {
+        return actual.createAttribute(name, value);
+    }
+
+    public Namespace createNamespace(final String namespaceURI) {
+        return actual.createNamespace(namespaceURI);
+    }
+
+    public Namespace createNamespace(final String prefix, final String namespaceUri) {
+        return actual.createNamespace(prefix, namespaceUri);
+    }
+
+    public StartElement createStartElement(final QName name, final Iterator attributes, final Iterator namespaces) {
+        return actual.createStartElement(name, attributes, namespaces);
+    }
+
+    public StartElement createStartElement(final String prefix, final String namespaceUri, final String localName) {
+        return actual.createStartElement(prefix, namespaceUri, localName);
+    }
+
+    public StartElement createStartElement(final String prefix, final String namespaceUri, final String localName, final Iterator attributes, final Iterator namespaces) {
+        return actual.createStartElement(prefix, namespaceUri, localName, attributes, namespaces);
+    }
+
+    public StartElement createStartElement(final String prefix, final String namespaceUri, final String localName, final Iterator attributes, final Iterator namespaces, final NamespaceContext context) {
+        return actual.createStartElement(prefix, namespaceUri, localName, attributes, namespaces, context);
+    }
+
+    public EndElement createEndElement(final QName name, final Iterator namespaces) {
+        return actual.createEndElement(name, namespaces);
+    }
+
+    public EndElement createEndElement(final String prefix, final String namespaceUri, final String localName) {
+        return actual.createEndElement(prefix, namespaceUri, localName);
+    }
+
+    public EndElement createEndElement(final String prefix, final String namespaceUri, final String localName, final Iterator namespaces) {
+        return actual.createEndElement(prefix, namespaceUri, localName, namespaces);
+    }
+
+    public Characters createCharacters(final String content) {
+        return actual.createCharacters(content);
+    }
+
+    public Characters createCData(final String content) {
+        return actual.createCData(content);
+    }
+
+    public Characters createSpace(final String content) {
+        return actual.createSpace(content);
+    }
+
+    public Characters createIgnorableSpace(final String content) {
+        return actual.createIgnorableSpace(content);
+    }
+
+    public StartDocument createStartDocument() {
+        return actual.createStartDocument();
+    }
+
+    public StartDocument createStartDocument(final String encoding, final String version, final boolean standalone) {
+        return actual.createStartDocument(encoding, version, standalone);
+    }
+
+    public StartDocument createStartDocument(final String encoding, final String version) {
+        return actual.createStartDocument(encoding, version);
+    }
+
+    public StartDocument createStartDocument(final String encoding) {
+        return actual.createStartDocument(encoding);
+    }
+
+    public EndDocument createEndDocument() {
+        return actual.createEndDocument();
+    }
+
+    public EntityReference createEntityReference(final String name, final EntityDeclaration declaration) {
+        return actual.createEntityReference(name, declaration);
+    }
+
+    public Comment createComment(final String text) {
+        return actual.createComment(text);
+    }
+
+    public ProcessingInstruction createProcessingInstruction(final String target, final String data) {
+        return actual.createProcessingInstruction(target, data);
+    }
+
+    public DTD createDTD(final String dtd) {
+        return actual.createDTD(dtd);
+    }
+}
diff --git a/src/main/java/__redirected/__XMLInputFactory.java b/src/main/java/__redirected/__XMLInputFactory.java
new file mode 100644
index 0000000..4acfe3c
--- /dev/null
+++ b/src/main/java/__redirected/__XMLInputFactory.java
@@ -0,0 +1,220 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package __redirected;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+
+import javax.xml.stream.EventFilter;
+import javax.xml.stream.StreamFilter;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLReporter;
+import javax.xml.stream.XMLResolver;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+import javax.xml.stream.util.XMLEventAllocator;
+import javax.xml.transform.Source;
+
+import org.jboss.modules.ModuleIdentifier;
+import org.jboss.modules.ModuleLoader;
+
+/**
+ * A redirected XMLInputFactory
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ * @authore Jason T. Greene
+ */
+public final class __XMLInputFactory extends XMLInputFactory {
+    private static final Constructor<? extends XMLInputFactory> PLATFORM_FACTORY;
+    private static volatile Constructor<? extends XMLInputFactory> DEFAULT_FACTORY;
+
+    static {
+        Thread thread = Thread.currentThread();
+        ClassLoader old = thread.getContextClassLoader();
+
+        // Unfortunately we can not use null because of a stupid bug in the jdk JAXP factory finder.
+        // Lack of tccl causes the provider file discovery to fallback to the jaxp loader (bootclasspath)
+        // which is correct. However, after parsing it, it then disables the fallback for the loading of the class.
+        // Thus, the class can not be found.
+        //
+        // Work around the problem by using the System CL, although in the future we may want to just "inherit"
+        // the environment's TCCL
+        thread.setContextClassLoader(ClassLoader.getSystemClassLoader());
+        try {
+            if (System.getProperty(XMLInputFactory.class.getName(), "").equals(__XMLInputFactory .class.getName())) {
+                System.clearProperty(XMLInputFactory.class.getName());
+            }
+            XMLInputFactory factory = XMLInputFactory.newInstance();
+            try {
+                DEFAULT_FACTORY = PLATFORM_FACTORY = factory.getClass().getConstructor();
+            } catch (NoSuchMethodException e) {
+                throw __RedirectedUtils.wrapped(new NoSuchMethodError(e.getMessage()), e);
+            }
+            System.setProperty(XMLInputFactory.class.getName(), __XMLInputFactory.class.getName());
+        } finally {
+            thread.setContextClassLoader(old);
+        }
+    }
+
+    public static void changeDefaultFactory(ModuleIdentifier id, ModuleLoader loader) {
+        Class<? extends XMLInputFactory> clazz = __RedirectedUtils.loadProvider(id, XMLInputFactory.class, loader);
+        if (clazz != null) {
+            try {
+                DEFAULT_FACTORY = clazz.getConstructor();
+            } catch (NoSuchMethodException e) {
+                throw __RedirectedUtils.wrapped(new NoSuchMethodError(e.getMessage()), e);
+            }
+        }
+    }
+
+    /**
+     * Init method.
+     */
+    public static void init() {}
+
+    public static void restorePlatformFactory() {
+        DEFAULT_FACTORY = PLATFORM_FACTORY;
+    }
+
+    /**
+     * Construct a new instance.
+     */
+    public __XMLInputFactory() {
+        Constructor<? extends XMLInputFactory> factory = DEFAULT_FACTORY;
+        ClassLoader loader = Thread.currentThread().getContextClassLoader();
+        try {
+            if (loader != null) {
+                Class<? extends XMLInputFactory> provider = __RedirectedUtils.loadProvider(XMLInputFactory.class, loader);
+                if (provider != null)
+                    factory = provider.getConstructor();
+            }
+
+            actual = factory.newInstance();
+        } catch (InstantiationException e) {
+            throw __RedirectedUtils.wrapped(new InstantiationError(e.getMessage()), e);
+        } catch (IllegalAccessException e) {
+            throw __RedirectedUtils.wrapped(new IllegalAccessError(e.getMessage()), e);
+        } catch (InvocationTargetException e) {
+            throw __RedirectedUtils.rethrowCause(e);
+        } catch (NoSuchMethodException e) {
+            throw __RedirectedUtils.wrapped(new NoSuchMethodError(e.getMessage()), e);
+        }
+    }
+
+    private final XMLInputFactory actual;
+
+    public XMLStreamReader createXMLStreamReader(final Reader reader) throws XMLStreamException {
+        return actual.createXMLStreamReader(reader);
+    }
+
+    public XMLStreamReader createXMLStreamReader(final Source source) throws XMLStreamException {
+        return actual.createXMLStreamReader(source);
+    }
+
+    public XMLStreamReader createXMLStreamReader(final InputStream stream) throws XMLStreamException {
+        return actual.createXMLStreamReader(stream);
+    }
+
+    public XMLStreamReader createXMLStreamReader(final InputStream stream, final String encoding) throws XMLStreamException {
+        return actual.createXMLStreamReader(stream, encoding);
+    }
+
+    public XMLStreamReader createXMLStreamReader(final String systemId, final InputStream stream) throws XMLStreamException {
+        return actual.createXMLStreamReader(systemId, stream);
+    }
+
+    public XMLStreamReader createXMLStreamReader(final String systemId, final Reader reader) throws XMLStreamException {
+        return actual.createXMLStreamReader(systemId, reader);
+    }
+
+    public XMLEventReader createXMLEventReader(final Reader reader) throws XMLStreamException {
+        return actual.createXMLEventReader(reader);
+    }
+
+    public XMLEventReader createXMLEventReader(final String systemId, final Reader reader) throws XMLStreamException {
+        return actual.createXMLEventReader(systemId, reader);
+    }
+
+    public XMLEventReader createXMLEventReader(final XMLStreamReader reader) throws XMLStreamException {
+        return actual.createXMLEventReader(reader);
+    }
+
+    public XMLEventReader createXMLEventReader(final Source source) throws XMLStreamException {
+        return actual.createXMLEventReader(source);
+    }
+
+    public XMLEventReader createXMLEventReader(final InputStream stream) throws XMLStreamException {
+        return actual.createXMLEventReader(stream);
+    }
+
+    public XMLEventReader createXMLEventReader(final InputStream stream, final String encoding) throws XMLStreamException {
+        return actual.createXMLEventReader(stream, encoding);
+    }
+
+    public XMLEventReader createXMLEventReader(final String systemId, final InputStream stream) throws XMLStreamException {
+        return actual.createXMLEventReader(systemId, stream);
+    }
+
+    public XMLStreamReader createFilteredReader(final XMLStreamReader reader, final StreamFilter filter) throws XMLStreamException {
+        return actual.createFilteredReader(reader, filter);
+    }
+
+    public XMLEventReader createFilteredReader(final XMLEventReader reader, final EventFilter filter) throws XMLStreamException {
+        return actual.createFilteredReader(reader, filter);
+    }
+
+    public XMLResolver getXMLResolver() {
+        return actual.getXMLResolver();
+    }
+
+    public void setXMLResolver(final XMLResolver resolver) {
+        actual.setXMLResolver(resolver);
+    }
+
+    public XMLReporter getXMLReporter() {
+        return actual.getXMLReporter();
+    }
+
+    public void setXMLReporter(final XMLReporter reporter) {
+        actual.setXMLReporter(reporter);
+    }
+
+    public void setProperty(final String name, final Object value) throws IllegalArgumentException {
+        actual.setProperty(name, value);
+    }
+
+    public Object getProperty(final String name) throws IllegalArgumentException {
+        return actual.getProperty(name);
+    }
+
+    public boolean isPropertySupported(final String name) {
+        return actual.isPropertySupported(name);
+    }
+
+    public void setEventAllocator(final XMLEventAllocator allocator) {
+        actual.setEventAllocator(allocator);
+    }
+
+    public XMLEventAllocator getEventAllocator() {
+        return actual.getEventAllocator();
+    }
+}
diff --git a/src/main/java/__redirected/__XMLOutputFactory.java b/src/main/java/__redirected/__XMLOutputFactory.java
new file mode 100644
index 0000000..7470f9a
--- /dev/null
+++ b/src/main/java/__redirected/__XMLOutputFactory.java
@@ -0,0 +1,163 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package __redirected;
+
+import java.io.OutputStream;
+import java.io.Writer;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+
+import javax.xml.stream.XMLEventWriter;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import javax.xml.transform.Result;
+
+import org.jboss.modules.ModuleIdentifier;
+import org.jboss.modules.ModuleLoader;
+
+/**
+ * A redirected XMLOutputFactory
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ * @authore Jason T. Greene
+ */
+public final class __XMLOutputFactory extends XMLOutputFactory {
+    private static final Constructor<? extends XMLOutputFactory> PLATFORM_FACTORY;
+    private static volatile Constructor<? extends XMLOutputFactory> DEFAULT_FACTORY;
+
+    static {
+        Thread thread = Thread.currentThread();
+        ClassLoader old = thread.getContextClassLoader();
+
+        // Unfortunately we can not use null because of a stupid bug in the jdk JAXP factory finder.
+        // Lack of tccl causes the provider file discovery to fallback to the jaxp loader (bootclasspath)
+        // which is correct. However, after parsing it, it then disables the fallback for the loading of the class.
+        // Thus, the class can not be found.
+        //
+        // Work around the problem by using the System CL, although in the future we may want to just "inherit"
+        // the environment's TCCL
+        thread.setContextClassLoader(ClassLoader.getSystemClassLoader());
+        try {
+            if (System.getProperty(XMLOutputFactory.class.getName(), "").equals(__XMLOutputFactory.class.getName())) {
+                System.clearProperty(XMLOutputFactory.class.getName());
+            }
+            XMLOutputFactory factory = XMLOutputFactory.newInstance();
+            try {
+                DEFAULT_FACTORY = PLATFORM_FACTORY = factory.getClass().getConstructor();
+            } catch (NoSuchMethodException e) {
+                throw __RedirectedUtils.wrapped(new NoSuchMethodError(e.getMessage()), e);
+            }
+            System.setProperty(XMLOutputFactory.class.getName(), __XMLOutputFactory.class.getName());
+        } finally {
+            thread.setContextClassLoader(old);
+        }
+    }
+
+    public static void changeDefaultFactory(ModuleIdentifier id, ModuleLoader loader) {
+        Class<? extends XMLOutputFactory> clazz = __RedirectedUtils.loadProvider(id, XMLOutputFactory.class, loader);
+        if (clazz != null) {
+            try {
+                DEFAULT_FACTORY = clazz.getConstructor();
+            } catch (NoSuchMethodException e) {
+                throw __RedirectedUtils.wrapped(new NoSuchMethodError(e.getMessage()), e);
+            }
+        }
+    }
+
+    public static void restorePlatformFactory() {
+        DEFAULT_FACTORY = PLATFORM_FACTORY;
+    }
+
+    /**
+     * Init method.
+     */
+    public static void init() {}
+
+    /**
+     * Construct a new instance.
+     */
+    public __XMLOutputFactory() {
+        Constructor<? extends XMLOutputFactory> factory = DEFAULT_FACTORY;
+        ClassLoader loader = Thread.currentThread().getContextClassLoader();
+        try {
+            if (loader != null) {
+                Class<? extends XMLOutputFactory> provider = __RedirectedUtils.loadProvider(XMLOutputFactory.class, loader);
+                if (provider != null)
+                    factory = provider.getConstructor();
+            }
+
+            actual = factory.newInstance();
+        } catch (InstantiationException e) {
+            throw __RedirectedUtils.wrapped(new InstantiationError(e.getMessage()), e);
+        } catch (IllegalAccessException e) {
+            throw __RedirectedUtils.wrapped(new IllegalAccessError(e.getMessage()), e);
+        } catch (InvocationTargetException e) {
+            throw __RedirectedUtils.rethrowCause(e);
+        } catch (NoSuchMethodException e) {
+            throw __RedirectedUtils.wrapped(new NoSuchMethodError(e.getMessage()), e);
+        }
+    }
+
+    private final XMLOutputFactory actual;
+
+    public XMLStreamWriter createXMLStreamWriter(final Writer stream) throws XMLStreamException {
+        return actual.createXMLStreamWriter(stream);
+    }
+
+    public XMLStreamWriter createXMLStreamWriter(final OutputStream stream) throws XMLStreamException {
+        return actual.createXMLStreamWriter(stream);
+    }
+
+    public XMLStreamWriter createXMLStreamWriter(final OutputStream stream, final String encoding) throws XMLStreamException {
+        return actual.createXMLStreamWriter(stream, encoding);
+    }
+
+    public XMLStreamWriter createXMLStreamWriter(final Result result) throws XMLStreamException {
+        return actual.createXMLStreamWriter(result);
+    }
+
+    public XMLEventWriter createXMLEventWriter(final Result result) throws XMLStreamException {
+        return actual.createXMLEventWriter(result);
+    }
+
+    public XMLEventWriter createXMLEventWriter(final OutputStream stream) throws XMLStreamException {
+        return actual.createXMLEventWriter(stream);
+    }
+
+    public XMLEventWriter createXMLEventWriter(final OutputStream stream, final String encoding) throws XMLStreamException {
+        return actual.createXMLEventWriter(stream, encoding);
+    }
+
+    public XMLEventWriter createXMLEventWriter(final Writer stream) throws XMLStreamException {
+        return actual.createXMLEventWriter(stream);
+    }
+
+    public void setProperty(final String name, final Object value) throws IllegalArgumentException {
+        actual.setProperty(name, value);
+    }
+
+    public Object getProperty(final String name) throws IllegalArgumentException {
+        return actual.getProperty(name);
+    }
+
+    public boolean isPropertySupported(final String name) {
+        return actual.isPropertySupported(name);
+    }
+}
diff --git a/src/main/java/__redirected/__XMLReaderFactory.java b/src/main/java/__redirected/__XMLReaderFactory.java
new file mode 100644
index 0000000..7b5752c
--- /dev/null
+++ b/src/main/java/__redirected/__XMLReaderFactory.java
@@ -0,0 +1,182 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package __redirected;
+
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+
+import org.jboss.modules.ModuleIdentifier;
+import org.jboss.modules.ModuleLoader;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.DTDHandler;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXNotRecognizedException;
+import org.xml.sax.SAXNotSupportedException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.XMLReaderFactory;
+
+/**
+ * A redirected SAXParserFactory
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ * @authore Jason T. Greene
+ */
+public final class __XMLReaderFactory implements XMLReader {
+    private static final Constructor<? extends XMLReader> PLATFORM_FACTORY;
+    private static volatile Constructor<? extends XMLReader> DEFAULT_FACTORY;
+
+    private static final String SAX_DRIVER = "org.xml.sax.driver";
+
+    static {
+        Thread thread = Thread.currentThread();
+        ClassLoader old = thread.getContextClassLoader();
+
+        // Unfortunately we can not use null because of a stupid bug in the jdk JAXP factory finder.
+        // Lack of tccl causes the provider file discovery to fallback to the jaxp loader (bootclasspath)
+        // which is correct. However, after parsing it, it then disables the fallback for the loading of the class.
+        // Thus, the class can not be found.
+        //
+        // Work around the problem by using the System CL, although in the future we may want to just "inherit"
+        // the environment's TCCL
+        thread.setContextClassLoader(ClassLoader.getSystemClassLoader());
+        try {
+            if (System.getProperty(SAX_DRIVER, "").equals(__XMLReaderFactory.class.getName())) {
+                System.clearProperty(SAX_DRIVER);
+            }
+            XMLReader factory = XMLReaderFactory.createXMLReader();
+            try {
+               DEFAULT_FACTORY = PLATFORM_FACTORY = factory.getClass().getConstructor();
+            } catch (NoSuchMethodException e) {
+                throw __RedirectedUtils.wrapped(new NoSuchMethodError(e.getMessage()), e);
+            }
+            System.setProperty(SAX_DRIVER, __XMLReaderFactory.class.getName());
+        } catch (SAXException e) {
+             throw __RedirectedUtils.wrapped(new RuntimeException(e.getMessage()), e);
+        } finally {
+            thread.setContextClassLoader(old);
+        }
+    }
+
+    public static void changeDefaultFactory(ModuleIdentifier id, ModuleLoader loader) {
+        Class<? extends XMLReader> clazz = __RedirectedUtils.loadProvider(id, XMLReader.class, loader, SAX_DRIVER);
+        if (clazz != null) {
+            try {
+                DEFAULT_FACTORY = clazz.getConstructor();
+            } catch (NoSuchMethodException e) {
+                throw __RedirectedUtils.wrapped(new NoSuchMethodError(e.getMessage()), e);
+            }
+        }
+    }
+
+    public static void restorePlatformFactory() {
+        DEFAULT_FACTORY = PLATFORM_FACTORY;
+    }
+
+    /**
+     * Init method.
+     */
+    public static void init() {}
+
+    /**
+     * Construct a new instance.
+     */
+    public __XMLReaderFactory() {
+        Constructor<? extends XMLReader> factory = DEFAULT_FACTORY;
+        ClassLoader loader = Thread.currentThread().getContextClassLoader();
+        try {
+            if (loader != null) {
+                Class<? extends XMLReader> provider = __RedirectedUtils.loadProvider(XMLReader.class, loader, SAX_DRIVER);
+                if (provider != null)
+                    factory = provider.getConstructor();
+            }
+
+            actual = factory.newInstance();
+        } catch (InstantiationException e) {
+            throw __RedirectedUtils.wrapped(new InstantiationError(e.getMessage()), e);
+        } catch (IllegalAccessException e) {
+            throw __RedirectedUtils.wrapped(new IllegalAccessError(e.getMessage()), e);
+        } catch (InvocationTargetException e) {
+            throw __RedirectedUtils.rethrowCause(e);
+        } catch (NoSuchMethodException e) {
+            throw __RedirectedUtils.wrapped(new NoSuchMethodError(e.getMessage()), e);
+        }
+    }
+
+    private final XMLReader actual;
+
+    public boolean getFeature(String name) throws SAXNotRecognizedException, SAXNotSupportedException {
+        return actual.getFeature(name);
+    }
+
+    public void setFeature(String name, boolean value) throws SAXNotRecognizedException, SAXNotSupportedException {
+        actual.setFeature(name, value);
+    }
+
+    public Object getProperty(String name) throws SAXNotRecognizedException, SAXNotSupportedException {
+        return actual.getProperty(name);
+    }
+
+    public void setProperty(String name, Object value) throws SAXNotRecognizedException, SAXNotSupportedException {
+        actual.setProperty(name, value);
+    }
+
+    public void setEntityResolver(EntityResolver resolver) {
+        actual.setEntityResolver(resolver);
+    }
+
+    public EntityResolver getEntityResolver() {
+        return actual.getEntityResolver();
+    }
+
+    public void setDTDHandler(DTDHandler handler) {
+        actual.setDTDHandler(handler);
+    }
+
+    public DTDHandler getDTDHandler() {
+        return actual.getDTDHandler();
+    }
+
+    public void setContentHandler(ContentHandler handler) {
+        actual.setContentHandler(handler);
+    }
+
+    public ContentHandler getContentHandler() {
+        return actual.getContentHandler();
+    }
+
+    public void setErrorHandler(ErrorHandler handler) {
+        actual.setErrorHandler(handler);
+    }
+
+    public ErrorHandler getErrorHandler() {
+        return actual.getErrorHandler();
+    }
+
+    public void parse(InputSource input) throws IOException, SAXException {
+        actual.parse(input);
+    }
+
+    public void parse(String systemId) throws IOException, SAXException {
+        actual.parse(systemId);
+    }
+}
diff --git a/src/main/java/__redirected/__XPathFactory.java b/src/main/java/__redirected/__XPathFactory.java
new file mode 100644
index 0000000..f25b51f
--- /dev/null
+++ b/src/main/java/__redirected/__XPathFactory.java
@@ -0,0 +1,145 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package __redirected;
+
+import org.jboss.modules.ModuleIdentifier;
+import org.jboss.modules.ModuleLoader;
+
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathFactory;
+import javax.xml.xpath.XPathFactoryConfigurationException;
+import javax.xml.xpath.XPathFunctionResolver;
+import javax.xml.xpath.XPathVariableResolver;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.List;
+
+/**
+ * A redirected XPathFactory
+ *
+ * @author Jason T. Greene
+ */
+public final class __XPathFactory extends XPathFactory {
+    private static final Constructor<? extends XPathFactory> PLATFORM_FACTORY;
+    private static volatile Constructor<? extends XPathFactory> DEFAULT_FACTORY;
+
+    static {
+        Thread thread = Thread.currentThread();
+        ClassLoader old = thread.getContextClassLoader();
+
+        // Unfortunately we can not use null because of a stupid bug in the jdk JAXP factory finder.
+        // Lack of tccl causes the provider file discovery to fallback to the jaxp loader (bootclasspath)
+        // which is correct. However, after parsing it, it then disables the fallback for the loading of the class.
+        // Thus, the class can not be found.
+        //
+        // Work around the problem by using the System CL, although in the future we may want to just "inherit"
+        // the environment's TCCL
+        thread.setContextClassLoader(ClassLoader.getSystemClassLoader());
+        try {
+            if (System.getProperty(XPathFactory.class.getName(), "").equals(__XPathFactory.class.getName())) {
+                System.clearProperty(XPathFactory.class.getName());
+            }
+            XPathFactory factory = XPathFactory.newInstance();
+            try {
+                DEFAULT_FACTORY = PLATFORM_FACTORY = factory.getClass().getConstructor();
+            } catch (NoSuchMethodException e) {
+                throw __RedirectedUtils.wrapped(new NoSuchMethodError(e.getMessage()), e);
+            }
+            System.setProperty(XPathFactory.class.getName() + ":" + XPathFactory.DEFAULT_OBJECT_MODEL_URI, __XPathFactory.class.getName());
+        } finally {
+            thread.setContextClassLoader(old);
+        }
+    }
+
+    public static void changeDefaultFactory(ModuleIdentifier id, ModuleLoader loader) {
+        Class<? extends XPathFactory> clazz = __RedirectedUtils.loadProvider(id, XPathFactory.class, loader);
+        if (clazz != null) {
+            try {
+                DEFAULT_FACTORY = clazz.getConstructor();
+            } catch (NoSuchMethodException e) {
+                throw __RedirectedUtils.wrapped(new NoSuchMethodError(e.getMessage()), e);
+            }
+        }
+    }
+
+    public static void restorePlatformFactory() {
+        DEFAULT_FACTORY = PLATFORM_FACTORY;
+    }
+
+    /**
+     * Init method.
+     */
+    public static void init() {}
+
+    /**
+     * Construct a new instance.
+     */
+    public __XPathFactory() {
+        Constructor<? extends XPathFactory> factory = DEFAULT_FACTORY;
+        ClassLoader loader = Thread.currentThread().getContextClassLoader();
+        XPathFactory foundInstance = null;
+        try {
+            if (loader != null) {
+                List<Class<? extends XPathFactory>> providers = __RedirectedUtils.loadProviders(XPathFactory.class, loader);
+                for (Class<? extends XPathFactory> provider : providers) {
+                    XPathFactory instance = provider.newInstance();
+                    if (instance.isObjectModelSupported(XPathFactory.DEFAULT_OBJECT_MODEL_URI)) {
+                        foundInstance = instance;
+                        break;
+                    }
+                }
+            }
+
+            actual = foundInstance != null ? foundInstance : factory.newInstance();
+
+        } catch (InstantiationException e) {
+            throw __RedirectedUtils.wrapped(new InstantiationError(e.getMessage()), e);
+        } catch (IllegalAccessException e) {
+            throw __RedirectedUtils.wrapped(new IllegalAccessError(e.getMessage()), e);
+        } catch (InvocationTargetException e) {
+            throw __RedirectedUtils.rethrowCause(e);
+        }
+    }
+
+    private final XPathFactory actual;
+
+    public boolean isObjectModelSupported(String objectModel) {
+        return actual.isObjectModelSupported(objectModel);
+    }
+
+    public void setFeature(String name, boolean value) throws XPathFactoryConfigurationException {
+        actual.setFeature(name, value);
+    }
+
+    public boolean getFeature(String name) throws XPathFactoryConfigurationException {
+        return actual.getFeature(name);
+    }
+
+    public void setXPathVariableResolver(XPathVariableResolver resolver) {
+        actual.setXPathVariableResolver(resolver);
+    }
+
+    public void setXPathFunctionResolver(XPathFunctionResolver resolver) {
+        actual.setXPathFunctionResolver(resolver);
+    }
+
+    public XPath newXPath() {
+        return actual.newXPath();
+    }
+}
diff --git a/src/main/java/org/jboss/modules/AbstractLocalLoader.java b/src/main/java/org/jboss/modules/AbstractLocalLoader.java
new file mode 100644
index 0000000..9d198f9
--- /dev/null
+++ b/src/main/java/org/jboss/modules/AbstractLocalLoader.java
@@ -0,0 +1,64 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.util.List;
+
+/**
+ * An abstract local loader implementation.
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public abstract class AbstractLocalLoader implements LocalLoader {
+
+    /**
+     * Load a class which is locally defined by this loader.  Returns {@code null} by default.
+     *
+     * @param name the class name
+     * @param resolve {@code true} to resolve the class
+     *
+     * @return the class, or {@code null} if there is no local class with this name
+     */
+    public Class<?> loadClassLocal(final String name, final boolean resolve) {
+        return null;
+    }
+
+    /**
+     * Load a package which is locally defined by this loader.  Returns {@code null} by default.
+     *
+     * @param name the package name
+     *
+     * @return the package, or {@code null} if there is no local package with this name
+     */
+    public Package loadPackageLocal(final String name) {
+        return null;
+    }
+
+    /**
+     * Load a resource which is locally defined by this loader.  The given name is a path separated by "{@code /}"
+     * characters.  Returns {@code null} by default.
+     *
+     * @param name the resource path
+     *
+     * @return the resource or resources, or an empty list if there is no local resource with this name
+     */
+    public List<Resource> loadResourceLocal(final String name) {
+        return null;
+    }
+}
diff --git a/src/main/java/org/jboss/modules/AbstractResourceLoader.java b/src/main/java/org/jboss/modules/AbstractResourceLoader.java
new file mode 100644
index 0000000..6a09298
--- /dev/null
+++ b/src/main/java/org/jboss/modules/AbstractResourceLoader.java
@@ -0,0 +1,139 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jboss.modules;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+
+/**
+ * An abstract resource loader implementation.
+ *
+ * @author <a href="mailto:cdewolf at redhat.com">Carlo de Wolf</a>
+ */
+public abstract class AbstractResourceLoader implements ResourceLoader {
+    private static String getDefinedAttribute(Attributes.Name name, Attributes entryAttribute, Attributes mainAttribute) {
+        final String value = entryAttribute == null ? null : entryAttribute.getValue(name);
+        return value == null ? mainAttribute == null ? null : mainAttribute.getValue(name) : value;
+    }
+
+    /**
+     * Convenience method to get a package specification from a {@code Manifest}.
+     *
+     * @param name the (dot-separated) package name
+     * @param manifest the {@code Manifest} instance
+     * @param rootUrl the code source URL
+     * @return the package specification
+     */
+    protected static PackageSpec getPackageSpec(final String name, final Manifest manifest, final URL rootUrl) {
+        final PackageSpec spec = new PackageSpec();
+        if (manifest == null) {
+            return null;
+        }
+        final Attributes mainAttribute = manifest.getMainAttributes();
+        final String path = name.replace('.', '/').concat("/");
+        final Attributes entryAttribute = manifest.getAttributes(path);
+        spec.setSpecTitle(getDefinedAttribute(Attributes.Name.SPECIFICATION_TITLE, entryAttribute, mainAttribute));
+        spec.setSpecVersion(getDefinedAttribute(Attributes.Name.SPECIFICATION_VERSION, entryAttribute, mainAttribute));
+        spec.setSpecVendor(getDefinedAttribute(Attributes.Name.SPECIFICATION_VENDOR, entryAttribute, mainAttribute));
+        spec.setImplTitle(getDefinedAttribute(Attributes.Name.IMPLEMENTATION_TITLE, entryAttribute, mainAttribute));
+        spec.setImplVersion(getDefinedAttribute(Attributes.Name.IMPLEMENTATION_VERSION, entryAttribute, mainAttribute));
+        spec.setImplVendor(getDefinedAttribute(Attributes.Name.IMPLEMENTATION_VENDOR, entryAttribute, mainAttribute));
+        if (Boolean.parseBoolean(getDefinedAttribute(Attributes.Name.SEALED, entryAttribute, mainAttribute))) {
+            spec.setSealBase(rootUrl);
+        }
+        return spec;
+    }
+
+    /**
+     * Get the name of the root represented by this resource loader.  Returns an empty string by default.
+     *
+     * @return the name of the root
+     */
+    public String getRootName() {
+        return "";
+    }
+
+    /**
+     * Get the class specification for the given class name.  If no matching class is found, {@code null} is returned.
+     * Returns {@code null} by default.
+     *
+     * @param fileName the fileName of the class, e.g. for the class <code>org.jboss.modules.ResourceLoader</code> the
+     * fileName will be <code>org/jboss/modules/ResourceLoader.class</code>
+     *
+     * @return the class specification, or {@code null} if the named class is not found
+     *
+     * @throws java.io.IOException if an I/O error occurs
+     */
+    public ClassSpec getClassSpec(final String fileName) throws IOException {
+        return null;
+    }
+
+    /**
+     * Get the package specification for the given directory name.  Always returns a package specification; this method
+     * cannot be used to test for the existence of a package.  A package spec should always be acquired from the same
+     * resource loader which provided the class specification.  The directory name will always be specified using "{@code
+     * /}" separators.  Returns {@code null} by default.
+     *
+     * @param name the directory name
+     *
+     * @return the package specification
+     *
+     * @throws java.io.IOException if an I/O error occurs
+     */
+    public PackageSpec getPackageSpec(final String name) throws IOException {
+        return null;
+    }
+
+    /**
+     * Get a resource with the given name.  If no such resource is available, {@code null} is returned. The resource name
+     * will always be specified using "{@code /}" separators for the directory segments.  Returns {@code null} by default.
+     *
+     * @param name the resource name
+     *
+     * @return the resource, or {@code null} if it is not available
+     */
+    public Resource getResource(final String name) {
+        return null;
+    }
+
+    /**
+     * Get the absolute physical filesystem path for a library with the given name.  The resultant path should be
+     * path-separated using "{@code /}" characters.  Returns {@code null} by default.
+     *
+     * @param name the name
+     *
+     * @return the path or {@code null} if the library is not present
+     */
+    public String getLibrary(final String name) {
+        return null;
+    }
+
+    /**
+     * Get the collection of resource paths.  Called one time only when the resource loader is initialized.  The paths
+     * should use "{@code /}" characters to separate the path segments.  Returns an empty set by default.
+     *
+     * @return the resource paths
+     */
+    public Collection<String> getPaths() {
+        return Collections.emptySet();
+    }
+}
diff --git a/src/main/java/org/jboss/modules/AliasModuleSpec.java b/src/main/java/org/jboss/modules/AliasModuleSpec.java
new file mode 100644
index 0000000..c4eb1db
--- /dev/null
+++ b/src/main/java/org/jboss/modules/AliasModuleSpec.java
@@ -0,0 +1,38 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+/**
+ * A module specification for a module alias.
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public final class AliasModuleSpec extends ModuleSpec {
+
+    private final ModuleIdentifier aliasTarget;
+
+    AliasModuleSpec(final ModuleIdentifier moduleIdentifier, final ModuleIdentifier aliasTarget) {
+        super(moduleIdentifier);
+        this.aliasTarget = aliasTarget;
+    }
+
+    public ModuleIdentifier getAliasTarget() {
+        return aliasTarget;
+    }
+}
diff --git a/src/main/java/org/jboss/modules/AssertionSetting.java b/src/main/java/org/jboss/modules/AssertionSetting.java
new file mode 100644
index 0000000..6603e63
--- /dev/null
+++ b/src/main/java/org/jboss/modules/AssertionSetting.java
@@ -0,0 +1,32 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+/**
+ * The assertion setting for a package or class.
+ *
+ * @apiviz.exclude
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public enum AssertionSetting {
+    ENABLED,
+    DISABLED,
+    INHERIT,
+}
\ No newline at end of file
diff --git a/src/main/java/org/jboss/modules/CallerContext.java b/src/main/java/org/jboss/modules/CallerContext.java
new file mode 100644
index 0000000..3b4d06f
--- /dev/null
+++ b/src/main/java/org/jboss/modules/CallerContext.java
@@ -0,0 +1,59 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+/**
+ * Various methods for obtaining caller info.
+ *
+ * @author Jason T. Greene
+ */
+class CallerContext {
+
+    private CallerContext() {
+    }
+
+    private static final class Hack extends SecurityManager {
+        @Override
+        protected Class<?>[] getClassContext() {
+            return super.getClassContext();
+        }
+    }
+
+    private static Hack hack = AccessController.doPrivileged(new PrivilegedAction<Hack>() {
+        @Override
+        public Hack run() {
+            return new Hack();
+        }
+    });
+
+    static Class<?> getCallingClass() {
+        Class<?> stack[] = hack.getClassContext();
+        int i = 3;
+        while (stack[i] == stack[2]) {
+            // skip nested calls front the same class
+            if (++i >= stack.length)
+                return null;
+        }
+
+        return stack[i];
+    }
+}
diff --git a/src/main/java/org/jboss/modules/ClassLoaderLocalLoader.java b/src/main/java/org/jboss/modules/ClassLoaderLocalLoader.java
new file mode 100644
index 0000000..34ee4f2
--- /dev/null
+++ b/src/main/java/org/jboss/modules/ClassLoaderLocalLoader.java
@@ -0,0 +1,123 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.UndeclaredThrowableException;
+import java.net.URL;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+final class ClassLoaderLocalLoader implements LocalLoader {
+
+    static final ClassLoaderLocalLoader SYSTEM = new ClassLoaderLocalLoader(ClassLoaderLocalLoader.class.getClassLoader());
+
+    private static final Method getPackage;
+
+    private final ClassLoader classLoader;
+
+    static {
+        getPackage = AccessController.doPrivileged(new PrivilegedAction<Method>() {
+            public Method run() {
+                for (Method method : ClassLoader.class.getDeclaredMethods()) {
+                    if (method.getName().equals("getPackage")) {
+                        Class<?>[] parameterTypes = method.getParameterTypes();
+                        if (parameterTypes.length == 1 && parameterTypes[0] == String.class) {
+                            method.setAccessible(true);
+                            return method;
+                        }
+                    }
+                }
+                throw new IllegalStateException("No getPackage method found on ClassLoader");
+            }
+        });
+    }
+
+    /**
+     * Construct a new instance.
+     *
+     * @param classLoader the classloader to which we delegate
+     */
+    ClassLoaderLocalLoader(final ClassLoader classLoader) {
+        this.classLoader = classLoader;
+    }
+
+    // Public members
+
+    public Class<?> loadClassLocal(final String name, final boolean resolve) {
+        try {
+            return Class.forName(name, resolve, classLoader);
+        } catch (ClassNotFoundException e) {
+            final Throwable cause = e.getCause();
+            if (cause instanceof Error) {
+                throw (Error) cause;
+            } else if (cause instanceof RuntimeException) {
+                //unlikely
+                throw (RuntimeException) cause;
+            }
+            return null;
+        }
+    }
+
+    public Package loadPackageLocal(final String name) {
+        try {
+            return (Package) getPackage.invoke(classLoader, name);
+        } catch (IllegalAccessException e) {
+            throw new IllegalAccessError(e.getMessage());
+        } catch (InvocationTargetException e) {
+            try {
+                throw e.getCause();
+            } catch (RuntimeException re) {
+                throw re;
+            } catch (Error er) {
+                throw er;
+            } catch (Throwable throwable) {
+                throw new UndeclaredThrowableException(throwable);
+            }
+        }
+    }
+
+    public List<Resource> loadResourceLocal(final String name) {
+        final Enumeration<URL> urls;
+        ClassLoader classLoader = this.classLoader;
+        try {
+            if (classLoader == null) {
+                urls = ClassLoader.getSystemResources(name);
+            } else {
+                urls = classLoader.getResources(name);
+            }
+        } catch (IOException e) {
+            return Collections.emptyList();
+        }
+        final List<Resource> list = new ArrayList<Resource>();
+        while (urls.hasMoreElements()) {
+            list.add(new URLResource(urls.nextElement()));
+        }
+        return list;
+    }
+}
diff --git a/src/main/java/org/jboss/modules/ClassPathModuleLoader.java b/src/main/java/org/jboss/modules/ClassPathModuleLoader.java
new file mode 100644
index 0000000..472dc47
--- /dev/null
+++ b/src/main/java/org/jboss/modules/ClassPathModuleLoader.java
@@ -0,0 +1,132 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import org.jboss.modules.filter.PathFilters;
+
+import java.io.File;
+import java.security.AccessController;
+import java.util.jar.JarFile;
+
+/**
+ * Date: 06.05.2011
+ *
+ * @author <a href="mailto:jperkins at redhat.com">James R. Perkins</a>
+ */
+final class ClassPathModuleLoader extends ModuleLoader {
+
+    static final String[] NO_STRINGS = new String[0];
+    private final ModuleLoader delegateLoader;
+    private final String classPath;
+    private final String dependencies;
+    private final String mainClass;
+
+    ClassPathModuleLoader(final ModuleLoader delegateLoader, final String mainClass, String classPath, final String dependencies) {
+        this.delegateLoader = delegateLoader;
+        this.dependencies = dependencies;
+        if (isEmpty(classPath)) {
+            classPath = System.getenv().get("CLASSPATH");
+        }
+        this.classPath = classPath;
+        AccessController.doPrivileged(new PropertyWriteAction("java.class.path", classPath));
+        this.mainClass = mainClass;
+    }
+
+    private static boolean isEmpty(final String classPath) {
+        return classPath == null || classPath.isEmpty();
+    }
+
+    @Override
+    protected Module preloadModule(final ModuleIdentifier identifier) throws ModuleLoadException {
+        if (identifier.equals(ModuleIdentifier.CLASSPATH)) {
+            return loadModuleLocal(identifier);
+        } else if (delegateLoader != null) {
+            return preloadModule(identifier, delegateLoader);
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    protected ModuleSpec findModule(final ModuleIdentifier moduleIdentifier) throws ModuleLoadException {
+        ModuleSpec.Builder builder = ModuleSpec.build(moduleIdentifier);
+        builder.setMainClass(mainClass);
+        // Process the classpath
+        addClassPath(builder, classPath);
+        // Add the dependencies
+        final String[] dependencyEntries = (dependencies == null ? NO_STRINGS : dependencies.split(","));
+        for (String dependencyEntry : dependencyEntries) {
+            dependencyEntry = dependencyEntry.trim();
+            if (! dependencyEntry.isEmpty()) {
+                final ModuleIdentifier depModId = ModuleIdentifier.fromString(dependencyEntry);
+                final DependencySpec spec = DependencySpec.createModuleDependencySpec(
+                        PathFilters.getMetaInfSubdirectoriesWithoutMetaInfFilter(),
+                        PathFilters.rejectAll(),
+                        delegateLoader,
+                        depModId,
+                        false);
+                builder.addDependency(spec);
+            }
+        }
+        builder.addDependency(DependencySpec.createSystemDependencySpec(JDKPaths.JDK));
+        builder.addDependency(DependencySpec.createLocalDependencySpec());
+        return builder.create();
+    }
+
+    @Override
+    public String toString() {
+        return "Class path module loader for path '" + classPath + "'";
+    }
+
+    /**
+     * Adds the class path entries as dependencies on the builder.
+     *
+     * @param builder   the builder to add the dependency entries to.
+     * @param classPath the class path to process
+     *
+     * @throws ModuleLoadException if the class path entry is not found or the entry is a directory.
+     */
+    private void addClassPath(final ModuleSpec.Builder builder, final String classPath) throws ModuleLoadException {
+        String[] classPathEntries = (classPath == null ? NO_STRINGS : classPath.split(File.pathSeparator));
+        final File workingDir = new File(System.getProperty("user.dir"));
+        for (String entry : classPathEntries) {
+            if (!entry.isEmpty()) {
+                try {
+                    // Find the directory
+                    File root = new File(entry);
+                    if (! root.isAbsolute()) {
+                        root = new File(workingDir, root.getPath());
+                    }
+                    if (root.isFile()) {
+                        try {
+                            builder.addResourceRoot(ResourceLoaderSpec.createResourceLoaderSpec(ResourceLoaders.createJarResourceLoader(root.getParent(), new JarFile(root, true))));
+                        } catch (Exception e) {
+                            Module.log.trace(e, "Resource %s does not appear to be a valid JAR. Loaded as file resource.", root);
+                            builder.addResourceRoot(ResourceLoaderSpec.createResourceLoaderSpec(ResourceLoaders.createFileResourceLoader(entry, root)));
+                        }
+                    } else {
+                        builder.addResourceRoot(ResourceLoaderSpec.createResourceLoaderSpec(ResourceLoaders.createFileResourceLoader(entry, root)));
+                    }
+                } catch (Exception e) {
+                    throw new ModuleLoadException(String.format("File %s in class path not valid.", entry), e);
+                }
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/jboss/modules/ClassSpec.java b/src/main/java/org/jboss/modules/ClassSpec.java
new file mode 100644
index 0000000..108e2ed
--- /dev/null
+++ b/src/main/java/org/jboss/modules/ClassSpec.java
@@ -0,0 +1,96 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.security.CodeSource;
+
+/**
+ * A class definition specification.
+ *
+ * @apiviz.exclude
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public final class ClassSpec {
+    private byte[] bytes;
+    private CodeSource codeSource;
+    private AssertionSetting assertionSetting = AssertionSetting.INHERIT;
+
+    /**
+     * Construct a new instance.
+     */
+    public ClassSpec() {
+    }
+
+    /**
+     * Get the class file bytes.
+     *
+     * @return the class file bytes
+     */
+    public byte[] getBytes() {
+        return bytes;
+    }
+
+    /**
+     * Set the class file bytes.
+     *
+     * @param bytes the class file bytes
+     */
+    public void setBytes(final byte[] bytes) {
+        this.bytes = bytes;
+    }
+
+    /**
+     * Get the code source (should not be {@code null}).
+     *
+     * @return the code source
+     */
+    public CodeSource getCodeSource() {
+        return codeSource;
+    }
+
+    /**
+     * Set the code source (should not be {@code null}).
+     *
+     * @param codeSource the code source
+     */
+    public void setCodeSource(final CodeSource codeSource) {
+        this.codeSource = codeSource;
+    }
+
+    /**
+     * Get the class assertion setting.
+     *
+     * @return the assertion setting
+     */
+    public AssertionSetting getAssertionSetting() {
+        return assertionSetting;
+    }
+
+    /**
+     * Set the class assertion setting.
+     *
+     * @param assertionSetting the assertion setting
+     */
+    public void setAssertionSetting(final AssertionSetting assertionSetting) {
+        if (assertionSetting == null) {
+            throw new IllegalArgumentException("assertionSetting is null");
+        }
+        this.assertionSetting = assertionSetting;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/jboss/modules/ClassifyingModuleLoader.java b/src/main/java/org/jboss/modules/ClassifyingModuleLoader.java
new file mode 100644
index 0000000..70b266d
--- /dev/null
+++ b/src/main/java/org/jboss/modules/ClassifyingModuleLoader.java
@@ -0,0 +1,84 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A module loader which selects a delegate module loader based upon the prefix of the module name.  Longer
+ * names are matched first always.
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public final class ClassifyingModuleLoader extends ModuleLoader {
+    private volatile Map<String, ModuleLoader> delegates;
+    private final ModuleLoader defaultLoader;
+    private final String name;
+
+    /**
+     * Construct a new instance.  The given delegates map is copied.
+     *
+     * @param delegates the default delegates map to use
+     * @param defaultLoader the default loader to use if no delegate mapping exists
+     */
+    public ClassifyingModuleLoader(final String name, final Map<String, ModuleLoader> delegates, final ModuleLoader defaultLoader) {
+        super(true, false);
+        this.defaultLoader = defaultLoader;
+        this.delegates = new HashMap<String, ModuleLoader>(delegates);
+        this.name = name;
+    }
+
+    /** {@inheritDoc} */
+    protected Module preloadModule(final ModuleIdentifier moduleIdentifier) throws ModuleLoadException {
+        String name = moduleIdentifier.getName();
+        int idx;
+        final Map<String, ModuleLoader> delegates = this.delegates;
+        for (;;) {
+            final ModuleLoader loader = delegates.get(name);
+            if (loader != null) {
+                return preloadModule(moduleIdentifier, loader);
+            }
+            idx = name.lastIndexOf('.');
+            if (idx == -1) {
+                return preloadModule(moduleIdentifier, defaultLoader);
+            }
+            name = name.substring(0, idx);
+        }
+    }
+
+    /** {@inheritDoc} */
+    protected ModuleSpec findModule(final ModuleIdentifier moduleIdentifier) throws ModuleLoadException {
+        // We have no modules of our own!
+        return null;
+    }
+
+    /**
+     * Change the delegates map.  A copy is made of the given map.
+     *
+     * @param delegates the new delegates map to use
+     */
+    public void setDelegates(Map<String, ModuleLoader> delegates) {
+        this.delegates = new HashMap<String, ModuleLoader>(delegates);
+    }
+
+    public String toString() {
+        return String.format("Classifying Module Loader @%x \"%s\"", Integer.valueOf(hashCode()), name);
+    }
+}
diff --git a/src/main/java/org/jboss/modules/ConcreteModuleSpec.java b/src/main/java/org/jboss/modules/ConcreteModuleSpec.java
new file mode 100644
index 0000000..3dc5bd8
--- /dev/null
+++ b/src/main/java/org/jboss/modules/ConcreteModuleSpec.java
@@ -0,0 +1,97 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.lang.instrument.ClassFileTransformer;
+import java.security.PermissionCollection;
+import java.util.Map;
+
+/**
+ * A {@code Module} specification for a concrete module implementation.
+ *
+ * @apiviz.exclude
+ *
+ * @author <a href="mailto:jbailey at redhat.com">John Bailey</a>
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public final class ConcreteModuleSpec extends ModuleSpec {
+
+    private final String mainClass;
+    private final AssertionSetting assertionSetting;
+    private final ResourceLoaderSpec[] resourceLoaders;
+    private final DependencySpec[] dependencies;
+    private final LocalLoader fallbackLoader;
+    private final ModuleClassLoaderFactory moduleClassLoaderFactory;
+    private final ClassFileTransformer classFileTransformer;
+    private final Map<String, String> properties;
+    private final PermissionCollection permissionCollection;
+
+    ConcreteModuleSpec(final ModuleIdentifier moduleIdentifier, final String mainClass, final AssertionSetting assertionSetting, final ResourceLoaderSpec[] resourceLoaders, final DependencySpec[] dependencies, final LocalLoader fallbackLoader, final ModuleClassLoaderFactory moduleClassLoaderFactory, final ClassFileTransformer classFileTransformer, final Map<String, String> properties, final PermissionCollection permissionCollection) {
+        super(moduleIdentifier);
+        this.mainClass = mainClass;
+        this.assertionSetting = assertionSetting;
+        this.resourceLoaders = resourceLoaders;
+        this.dependencies = dependencies;
+        this.fallbackLoader = fallbackLoader;
+        this.moduleClassLoaderFactory = moduleClassLoaderFactory;
+        this.classFileTransformer = classFileTransformer;
+        this.properties = properties;
+        this.permissionCollection = permissionCollection;
+    }
+
+    public String getMainClass() {
+        return mainClass;
+    }
+
+    AssertionSetting getAssertionSetting() {
+        return assertionSetting;
+    }
+
+    ResourceLoaderSpec[] getResourceLoaders() {
+        return resourceLoaders;
+    }
+
+    DependencySpec[] getDependenciesInternal() {
+        return dependencies;
+    }
+
+    public DependencySpec[] getDependencies() {
+        return dependencies.length == 0 ? dependencies : dependencies.clone();
+    }
+
+    LocalLoader getFallbackLoader() {
+        return fallbackLoader;
+    }
+
+    ModuleClassLoaderFactory getModuleClassLoaderFactory() {
+        return moduleClassLoaderFactory;
+    }
+
+    ClassFileTransformer getClassFileTransformer() {
+        return classFileTransformer;
+    }
+
+    Map<String, String> getProperties() {
+        return properties;
+    }
+
+    PermissionCollection getPermissionCollection() {
+        return permissionCollection;
+    }
+}
diff --git a/src/main/java/org/jboss/modules/ConcurrentClassLoader.java b/src/main/java/org/jboss/modules/ConcurrentClassLoader.java
new file mode 100644
index 0000000..22c0183
--- /dev/null
+++ b/src/main/java/org/jboss/modules/ConcurrentClassLoader.java
@@ -0,0 +1,650 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Field;
+import java.net.URL;
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.PrivilegedExceptionAction;
+import java.security.ProtectionDomain;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Locale;
+import java.util.Queue;
+import sun.misc.Unsafe;
+
+/**
+ * A classloader which can delegate to multiple other classloaders without risk of deadlock.  A concurrent class loader
+ * should only ever be delegated to by another concurrent class loader; however a concurrent class loader <i>may</i>
+ * delegate to a standard hierarchical class loader.  In other words, holding a lock on another class loader while invoking
+ * a method on this class loader may cause an unexpected deadlock.
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public abstract class ConcurrentClassLoader extends ClassLoader {
+
+    private static final boolean LOCKLESS;
+    private static final boolean SAFE_JDK;
+    private static final boolean JDK7_PLUS;
+
+    private static final ClassLoader definingLoader = ConcurrentClassLoader.class.getClassLoader();
+
+    private static final ThreadLocal<Boolean> GET_PACKAGE_SUPPRESSOR = new ThreadLocal<Boolean>();
+
+    static {
+        boolean jdk7plus = false;
+        boolean parallelOk = true;
+        try {
+            jdk7plus = parallelOk = ClassLoader.registerAsParallelCapable();
+        } catch (Throwable ignored) {
+        }
+        if (! parallelOk) {
+            throw new Error("Failed to register " + ConcurrentClassLoader.class.getName() + " as parallel-capable");
+        }
+        /*
+         This resolves a known deadlock that can occur if one thread is in the process of defining a package as part of
+         defining a class, and another thread is defining the system package that can result in loading a class.  One holds
+         the Package.pkgs lock and one holds the Classloader lock.
+        */
+        Package.getPackages();
+        // Determine whether to run in lockless mode
+        boolean hasUnsafe = false;
+        // For 1.6, we require Unsafe to perform lockless stuff
+        if (! jdk7plus) try {
+            Class.forName("sun.misc.Unsafe", false, null);
+            hasUnsafe = true;
+        } catch (Throwable t) {
+            // ignored
+        }
+        final boolean isJRockit = AccessController.doPrivileged(new PropertyReadAction("java.vm.name", "")).toUpperCase(Locale.US).contains("JROCKIT");
+        // But the user is always right, so if they override, respect it
+        LOCKLESS = Boolean.parseBoolean(AccessController.doPrivileged(new PropertyReadAction("jboss.modules.lockless", Boolean.toString(! jdk7plus && hasUnsafe && ! isJRockit))));
+        // If the JDK has safe CL, set this flag
+        SAFE_JDK = Boolean.parseBoolean(AccessController.doPrivileged(new PropertyReadAction("jboss.modules.safe-jdk", Boolean.toString(jdk7plus || isJRockit))));
+        JDK7_PLUS = jdk7plus;
+    }
+
+    /**
+     * An empty enumeration, for subclasses to use if desired.
+     */
+    protected static final Enumeration<URL> EMPTY_ENUMERATION = Collections.enumeration(Collections.<URL>emptySet());
+
+    /**
+     * Construct a new instance with the given parent class loader, which must be a concurrent class loader, or {@code null}
+     * to create a root concurrent class loader.
+     *
+     * @param parent the parent class loader
+     */
+    protected ConcurrentClassLoader(final ConcurrentClassLoader parent) {
+        super(parent == null ? ConcurrentClassLoader.class.getClassLoader() : parent);
+        if (JDK7_PLUS) {
+            if (getClassLoadingLock("$TEST$") == this) {
+                throw new Error("Cannot instantiate non-parallel subclass");
+            }
+        }
+    }
+
+    /**
+     * Construct a new instance, using our class loader as the parent.
+     */
+    protected ConcurrentClassLoader() {
+        super(ConcurrentClassLoader.class.getClassLoader());
+        if (JDK7_PLUS) {
+            if (getClassLoadingLock("$TEST$") == this) {
+                throw new Error("Cannot instantiate non-parallel subclass");
+            }
+        }
+    }
+
+    /**
+     * Loads the class with the specified binary name.  Equivalent to calling {@link #loadClass(String, boolean) loadClass(className, false)}.
+     *
+     * @param className The binary name of the class
+     * @return the resulting {@code Class} instance
+     * @throws ClassNotFoundException if the class was not found
+     */
+    @Override
+    public final Class<?> loadClass(final String className) throws ClassNotFoundException {
+        return performLoadClass(className, false, false);
+    }
+
+    /**
+     * Loads the class with the specified binary name.
+     *
+     * @param className The binary name of the class
+     * @param resolve {@code true} if the class should be linked after loading
+     * @return the resulting {@code Class} instance
+     */
+    @Override
+    public final Class<?> loadClass(final String className, boolean resolve) throws ClassNotFoundException {
+        return performLoadClass(className, false, resolve);
+    }
+
+    /**
+     * Same as {@link #loadClass(String)}, except only exported classes will be considered.
+     *
+     * @param className the class name
+     * @return the class
+     * @throws ClassNotFoundException if the class isn't found
+     */
+    public final Class<?> loadExportedClass(final String className) throws ClassNotFoundException {
+        return performLoadClass(className, true, false);
+    }
+
+    /**
+     * Same as {@link #loadClass(String,boolean)}, except only exported classes will be considered.
+     *
+     * @param className the class name
+     * @param resolve {@code true} if the class should be linked after loading
+     * @return the class
+     * @throws ClassNotFoundException if the class isn't found
+     */
+    public final Class<?> loadExportedClass(final String className, boolean resolve) throws ClassNotFoundException {
+        return performLoadClass(className, true, resolve);
+    }
+
+    /**
+     * Find a class, possibly delegating to other loader(s).  This method should <b>never</b> synchronize across a
+     * delegation method call of any sort.  The default implementation always throws {@code ClassNotFoundException}.
+     * <p>
+     * If a class is to be defined by this method, it should be done via one of the atomic {@code defineOrLoadClass}
+     * methods rather than {@code defineClass()} in order to avoid spurious exceptions.
+     *
+     * @param className the class name
+     * @param exportsOnly {@code true} if only exported classes should be considered
+     * @param resolve {@code true} if the class should be linked after loading
+     * @return the class
+     * @throws ClassNotFoundException if the class is not found
+     */
+    protected Class<?> findClass(final String className, final boolean exportsOnly, final boolean resolve) throws ClassNotFoundException {
+        throw new ClassNotFoundException(className);
+    }
+
+    /**
+     * Atomically define or load the named class.  If the class is already defined, the existing class is returned.
+     *
+     * @param className the class name to define or load
+     * @param bytes the bytes to use to define the class
+     * @param off the offset into the byte array at which the class bytes begin
+     * @param len the number of bytes in the class
+     * @return the class
+     */
+    protected final Class<?> defineOrLoadClass(final String className, final byte[] bytes, int off, int len) {
+        try {
+            final Class<?> definedClass = defineClass(className, bytes, off, len);
+            return definedClass;
+        } catch (LinkageError e) {
+            final Class<?> loadedClass = findLoadedClass(className);
+            if (loadedClass != null) {
+                return loadedClass;
+            }
+            throw e;
+        }
+    }
+
+    /**
+     * Atomically define or load the named class.  If the class is already defined, the existing class is returned.
+     *
+     * @param className the class name to define or load
+     * @param bytes the bytes to use to define the class
+     * @param off the offset into the byte array at which the class bytes begin
+     * @param len the number of bytes in the class
+     * @param protectionDomain the protection domain for the defined class
+     * @return the class
+     */
+    protected final Class<?> defineOrLoadClass(final String className, final byte[] bytes, int off, int len, ProtectionDomain protectionDomain) {
+        try {
+            final Class<?> definedClass = defineClass(className, bytes, off, len, protectionDomain);
+            return definedClass;
+        } catch (LinkageError e) {
+            final Class<?> loadedClass = findLoadedClass(className);
+            if (loadedClass != null) {
+                return loadedClass;
+            }
+            throw e;
+        }
+    }
+
+    /**
+     * Implementation of {@link ClassLoader#findClass(String)}.
+     *
+     * @param className the class name
+     * @return the result of {@code findClass(className, false, false)}
+     */
+    protected final Class<?> findClass(final String className) throws ClassNotFoundException {
+        return findClass(className, false, false);
+    }
+
+    /**
+     * Finds the resource with the given name.  The name of a resource is a {@code '/'}-separated path name that
+     * identifies the resource.  If the resource name starts with {@code "java/"} then the parent class loader is used.
+     * Otherwise, this method delegates to {@link #findResource(String, boolean)}.
+     *
+     * @param name the name of the resource
+     * @return the resource URL, or {@code null} if no such resource exists or the invoker does not have adequate
+     * permission to access it
+     */
+    public final URL getResource(final String name) {
+        for (String s : Module.systemPaths) {
+            if (name.startsWith(s)) {
+                // Whatever loads jboss-modules.jar should have it's classes accessible.
+                // This could even be the bootclasspath, in which case CL is null, and we prefer the system CL
+                return definingLoader != null ? definingLoader.getResource(name) : ClassLoader.getSystemResource(name);
+            }
+        }
+        return findResource(name, false);
+    }
+
+    /**
+     * Finds all available resources with the given name.
+     *
+     * @see #getResource(String)
+     *
+     * @param name the resource name
+     * @return an enumeration over all the resource URLs; if no resources could be found, the enumeration will be empty
+     * @throws IOException if an I/O error occurs
+     */
+    public final Enumeration<URL> getResources(final String name) throws IOException {
+        for (String s : Module.systemPaths) {
+            if (name.startsWith(s)) {
+                return definingLoader != null ? definingLoader.getResources(name) : ClassLoader.getSystemResources(name);
+            }
+        }
+        return findResources(name, false);
+    }
+
+    /**
+     * Find the resource with the given name and exported status.
+     *
+     * @see #getResource(String)
+     *
+     * @param name the resource name
+     * @param exportsOnly {@code true} to consider only exported resources or {@code false} to consider all resources
+     * @return the resource URL
+     */
+    protected URL findResource(final String name, final boolean exportsOnly) {
+        return null;
+    }
+
+    /**
+     * Never used.  {@link ClassLoader#getResource(String)} and related methods can cause a loop condition
+     * when this method is implemented; use {@link #findResource(String, boolean)} instead.
+     *
+     * @param name ignored
+     * @return {@code null} always
+     */
+    protected final URL findResource(final String name) {
+        // Always return null so that we don't go into a loop from super.getResource*().
+        return null;
+    }
+
+    /**
+     * Finds the resources with the given name and exported status.
+     *
+     * @see #getResources(String)
+     *
+     * @param name the resource name
+     * @param exportsOnly {@code true} to consider only exported resources or {@code false} to consider all resources
+     * @return the resource enumeration
+     * @throws IOException if an I/O error occurs
+     */
+    protected Enumeration<URL> findResources(final String name, final boolean exportsOnly) throws IOException {
+        return EMPTY_ENUMERATION;
+    }
+
+    /**
+     * Never used.  {@link ClassLoader#getResources(String)} and related methods can cause a loop condition
+     * when this method is implemented; use {@link #findResources(String, boolean)} instead.  By default, returns
+     * an empty enumeration.
+     *
+     * @param name ignored
+     * @return an empty enumeration
+     */
+    protected final Enumeration<URL> findResources(final String name) {
+        return EMPTY_ENUMERATION;
+    }
+
+    /**
+     * Finds the resource with the given name and exported status, returning the resource content as a stream.
+     *
+     * @param name the resource name
+     * @param exportsOnly {@code true} to consider only exported resources or {@code false} to consider all resources
+     * @return the resource stream, or {@code null} if the resource is not found
+     */
+    protected InputStream findResourceAsStream(final String name, final boolean exportsOnly) {
+        final URL url = findResource(name, exportsOnly);
+        try {
+            return url == null ? null : url.openStream();
+        } catch (IOException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Returns an input stream for reading the specified resource.  If the resource starts with {@code "java/"}, then
+     * this method delegates to the parent class loader.  Otherwise, this method delegates to {@link #findResourceAsStream(String, boolean)}.
+     *
+     * @param name the resource name
+     * @return the resource stream, or {@code null} if the resource is not found
+     */
+    public final InputStream getResourceAsStream(final String name) {
+        for (String s : Module.systemPaths) {
+            if (name.startsWith(s)) {
+                return definingLoader != null ? definingLoader.getResourceAsStream(name) : ClassLoader.getSystemResourceAsStream(name);
+            }
+        }
+        return findResourceAsStream(name, false);
+    }
+
+    // Private members
+
+    /**
+     * Perform a class load operation.  If the class is in the package or a subpackage of a package in the system packages list,
+     * the parent class loader is used to load the class.  Otherwise, this method checks to see if the class loader
+     * object is locked; if so, it unlocks it and submits the request to the class loader thread.  Otherwise, it will
+     * load the class itself by delegating to {@link #findClass(String, boolean, boolean)}.
+     *
+     * @param className the class name
+     * @param exportsOnly {@code true} to consider only exported resources or {@code false} to consider all resources
+     * @param resolve {@code true} to resolve the loaded class
+     * @return the class returned by {@link #findClass(String, boolean, boolean)}
+     * @throws ClassNotFoundException if {@link #findClass(String, boolean, boolean)} throws this exception
+     */
+    private Class<?> performLoadClass(String className, boolean exportsOnly, final boolean resolve) throws ClassNotFoundException {
+
+        if (className == null) {
+            throw new IllegalArgumentException("name is null");
+        }
+        for (String s : Module.systemPackages) {
+            if (className.startsWith(s)) {
+                return definingLoader != null ? definingLoader.loadClass(className) : findSystemClass(className);
+            }
+        }
+        return performLoadClassChecked(className, exportsOnly, resolve);
+    }
+
+    /**
+     * Perform a class load operation.  This method checks to see if the class loader object is locked; if so, it
+     * unlocks it and submits the request to the class loader thread.  Otherwise, it will load the class itself by
+     * delegating to {@link #findClass(String, boolean, boolean)}.
+     * <p>
+     * If the {@code jboss.modules.unsafe-locks} system property is set to {@code true}, then rather than using the
+     * class loading thread, the lock is forcibly broken and the load retried.
+     *
+     * @param className the class name
+     * @param exportsOnly {@code true} to consider only exported resources or {@code false} to consider all resources
+     * @param resolve {@code true} to resolve the loaded class
+     * @return the class returned by {@link #findClass(String, boolean, boolean)}
+     * @throws ClassNotFoundException if {@link #findClass(String, boolean, boolean)} throws this exception
+     */
+    private Class<?> performLoadClassChecked(final String className, final boolean exportsOnly, final boolean resolve) throws ClassNotFoundException {
+        if (SAFE_JDK) {
+            return performLoadClassUnchecked(className, exportsOnly, resolve);
+        } else if (Thread.holdsLock(this)) {
+            if (LOCKLESS) {
+                final Unsafe unsafe = UnsafeHolder.UNSAFE;
+                unsafe.monitorExit(this);
+                try {
+                    return performLoadClassChecked(className, exportsOnly, resolve);
+                } finally {
+                    unsafe.monitorEnter(this);
+                }
+            }
+            if (Thread.currentThread() != LoaderThreadHolder.LOADER_THREAD) {
+                // Only the classloader thread may take this lock; use a condition to relinquish it
+                final LoadRequest req = new LoadRequest(className, resolve, exportsOnly, this, AccessController.getContext());
+                final Queue<LoadRequest> queue = LoaderThreadHolder.REQUEST_QUEUE;
+                synchronized (queue) {
+                    queue.add(req);
+                    queue.notify();
+                }
+                boolean intr = false;
+                try {
+                    while (!req.done) try {
+                        wait();
+                    } catch (InterruptedException e) {
+                        intr = true;
+                    }
+                } finally {
+                    if (intr) Thread.currentThread().interrupt();
+                }
+
+                final Class<?> result = req.result;
+                if (result == null) {
+                    final String message = req.message;
+                    throw new ClassNotFoundException(message == null ? className : message);
+                }
+                return result;
+            }
+        }
+        // no deadlock risk!  Either the lock isn't held, or we're inside the class loader thread.
+        return performLoadClassUnchecked(className, exportsOnly, resolve);
+    }
+
+    private Class<?> performLoadClassUnchecked(final String className, final boolean exportsOnly, final boolean resolve) throws ClassNotFoundException {
+        if (className.charAt(0) == '[') {
+            // Use Class.forName to load the array type
+            final Class<?> array = Class.forName(className, false, this);
+            if (resolve) {
+                resolveClass(array);
+            }
+            return array;
+        }
+        return findClass(className, exportsOnly, resolve);
+    }
+
+    private final UnlockedReadHashMap<String, Package> packages = new UnlockedReadHashMap<String, Package>(64);
+
+    /**
+     * Load a package which is visible to this class loader.
+     *
+     * @param name the package name
+     * @return the package, or {@code null} if no such package is visible to this class loader
+     */
+    protected final Package getPackage(final String name) {
+        final String packageName = name + ".";
+        for (String s : Module.systemPackages) {
+            if (packageName.startsWith(s)) {
+                return Package.getPackage(name);
+            }
+        }
+        if (GET_PACKAGE_SUPPRESSOR.get() == Boolean.TRUE) {
+            return null;
+        }
+        return getPackageByName(name);
+    }
+
+    /**
+     * Perform the actual work to load a package which is visible to this class loader.  By default, uses a simple
+     * parent-first delegation strategy.
+     *
+     * @param name the package name
+     * @return the package, or {@code null} if no such package is visible to this class loader
+     */
+    protected Package getPackageByName(final String name) {
+        final Package parentPackage = super.getPackage(name);
+        return parentPackage == null ? findLoadedPackage(name) : parentPackage;
+    }
+
+    /**
+     * Get all defined packages which are visible to this class loader.
+     *
+     * @return the packages
+     */
+    protected Package[] getPackages() {
+        ArrayList<Package> list = new ArrayList<Package>();
+        list.addAll(packages.values());
+        list.addAll(Arrays.asList(super.getPackages()));
+        return list.toArray(new Package[list.size()]);
+    }
+
+    /**
+     * Load a package from this class loader only.
+     *
+     * @param name the package name
+     * @return the package or {@code null} if no such package is defined by this class loader
+     */
+    protected final Package findLoadedPackage(final String name) {
+        return packages.get(name);
+    }
+
+    /**
+     * Defines a package by name in this <tt>ConcurrentClassLoader</tt>.  If the package was already defined, the
+     * existing package is returned instead.
+     *
+     * @param name the package name
+     * @param specTitle the specification title
+     * @param specVersion the specification version
+     * @param specVendor the specification vendor
+     * @param implTitle the implementation title
+     * @param implVersion the implementation version
+     * @param implVendor the implementation vendor
+     * @param sealBase if not {@code null}, then this package is sealed with respect to the given code source URL
+     *
+     * @return the newly defined package, or the existing one if one was already defined
+     */
+    protected Package definePackage(final String name, final String specTitle, final String specVersion, final String specVendor, final String implTitle, final String implVersion, final String implVendor, final URL sealBase) throws IllegalArgumentException {
+        ThreadLocal<Boolean> suppressor = GET_PACKAGE_SUPPRESSOR;
+        suppressor.set(Boolean.TRUE);
+        try {
+            Package existing = packages.get(name);
+            if (existing != null) {
+                return existing;
+            }
+            Package pkg = super.definePackage(name, specTitle, specVersion, specVendor, implTitle, implVersion, implVendor, sealBase);
+            existing = packages.putIfAbsent(name, pkg);
+            return existing != null ? existing : pkg;
+        } finally {
+            suppressor.remove();
+        }
+    }
+
+    static final class LoaderThreadHolder {
+
+        static final Thread LOADER_THREAD;
+        static final Queue<LoadRequest> REQUEST_QUEUE = new ArrayDeque<LoadRequest>();
+
+        static {
+            Thread thr = new LoaderThread();
+            thr.setName("ClassLoader Thread");
+            // This thread will always run as long as the VM is alive.
+            thr.setDaemon(true);
+            thr.start();
+            LOADER_THREAD = thr;
+        }
+
+        private LoaderThreadHolder() {
+        }
+    }
+
+    static class LoadRequest {
+        private final String className;
+        private final boolean resolve;
+        private final ConcurrentClassLoader requester;
+        private final AccessControlContext context;
+        Class<?> result;
+        String message;
+        private boolean exportsOnly;
+
+        boolean done;
+
+        LoadRequest(final String className, final boolean resolve, final boolean exportsOnly, final ConcurrentClassLoader requester, final AccessControlContext context) {
+            this.className = className;
+            this.resolve = resolve;
+            this.exportsOnly = exportsOnly;
+            this.requester = requester;
+            this.context = context;
+        }
+    }
+
+    static class LoaderThread extends Thread {
+
+        @Override
+        public void interrupt() {
+            // no interruption
+        }
+
+        @Override
+        public void run() {
+            final Queue<LoadRequest> queue = LoaderThreadHolder.REQUEST_QUEUE;
+            for (; ;) {
+                try {
+                    LoadRequest request;
+                    synchronized (queue) {
+                        while ((request = queue.poll()) == null) {
+                            queue.wait();
+                        }
+                    }
+
+                    final ConcurrentClassLoader loader = request.requester;
+                    Class<?> result = null;
+                    synchronized (loader) {
+                        try {
+                            final SecurityManager sm = System.getSecurityManager();
+                            if (sm != null) {
+                                final LoadRequest localRequest = request;
+                                result = AccessController.doPrivileged(new PrivilegedExceptionAction<Class<?>>() {
+                                    public Class<?> run() throws ClassNotFoundException {
+                                        return loader.performLoadClassChecked(localRequest.className, localRequest.exportsOnly, localRequest.resolve);
+                                    }
+                                }, request.context);
+                            } else try {
+                                result = loader.performLoadClassChecked(request.className, request.exportsOnly, request.resolve);
+                            } catch (ClassNotFoundException e) {
+                                request.message = e.getMessage();
+                            }
+                        } finally {
+                            // no matter what, the requester MUST be notified
+                            request.result = result;
+                            request.done = true;
+                            loader.notifyAll();
+                        }
+                    }
+                } catch (Throwable t) {
+                    // ignore
+                }
+            }
+        }
+    }
+
+    private static final class UnsafeHolder {
+        static Unsafe UNSAFE;
+
+        static {
+            try {
+                final Field field = Unsafe.class.getDeclaredField("theUnsafe");
+                field.setAccessible(true);
+                UNSAFE = (Unsafe) field.get(null);
+            } catch (IllegalAccessException e) {
+                throw new IllegalAccessError(e.getMessage());
+            } catch (NoSuchFieldException e) {
+                throw new NoSuchFieldError(e.getMessage());
+            }
+        }
+
+        private UnsafeHolder() {
+        }
+    }
+}
diff --git a/src/main/java/org/jboss/modules/DefaultBootModuleLoaderHolder.java b/src/main/java/org/jboss/modules/DefaultBootModuleLoaderHolder.java
new file mode 100644
index 0000000..96ace5d
--- /dev/null
+++ b/src/main/java/org/jboss/modules/DefaultBootModuleLoaderHolder.java
@@ -0,0 +1,60 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.lang.reflect.InvocationTargetException;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+final class DefaultBootModuleLoaderHolder {
+
+    static final ModuleLoader INSTANCE;
+
+    private DefaultBootModuleLoaderHolder() {
+    }
+
+    static {
+        INSTANCE = AccessController.doPrivileged(new PrivilegedAction<ModuleLoader>() {
+            public ModuleLoader run() {
+                final String loaderClass = System.getProperty("boot.module.loader", LocalModuleLoader.class.getName());
+                try {
+                    return Class.forName(loaderClass, true, DefaultBootModuleLoaderHolder.class.getClassLoader()).asSubclass(ModuleLoader.class).getConstructor().newInstance();
+                } catch (InstantiationException e) {
+                    throw new InstantiationError(e.getMessage());
+                } catch (IllegalAccessException e) {
+                    throw new IllegalAccessError(e.getMessage());
+                } catch (InvocationTargetException e) {
+                    try {
+                        throw e.getCause();
+                    } catch (RuntimeException cause) {
+                        throw cause;
+                    } catch (Error cause) {
+                        throw cause;
+                    } catch (Throwable t) {
+                        throw new Error(t);
+                    }
+                } catch (NoSuchMethodException e) {
+                    throw new NoSuchMethodError(e.getMessage());
+                } catch (ClassNotFoundException e) {
+                    throw new NoClassDefFoundError(e.getMessage());
+                }
+            }
+        });
+    }
+}
diff --git a/src/main/java/org/jboss/modules/Dependency.java b/src/main/java/org/jboss/modules/Dependency.java
new file mode 100644
index 0000000..2cdd413
--- /dev/null
+++ b/src/main/java/org/jboss/modules/Dependency.java
@@ -0,0 +1,107 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import org.jboss.modules.filter.ClassFilter;
+import org.jboss.modules.filter.ClassFilters;
+import org.jboss.modules.filter.PathFilter;
+import org.jboss.modules.filter.PathFilters;
+
+/**
+ * A dependency item.
+ *
+ * @author <a href="mailto:jbailey at redhat.com">John Bailey</a>
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+abstract class Dependency {
+
+    private final PathFilter exportFilter;
+    private final PathFilter importFilter;
+    private final PathFilter resourceExportFilter;
+    private final PathFilter resourceImportFilter;
+    private final ClassFilter classExportFilter;
+    private final ClassFilter classImportFilter;
+
+    Dependency(final PathFilter exportFilter, final PathFilter importFilter) {
+        this(exportFilter, importFilter, PathFilters.acceptAll(), PathFilters.acceptAll(), ClassFilters.acceptAll(), ClassFilters.acceptAll());
+    }
+
+    protected Dependency(final PathFilter exportFilter, final PathFilter importFilter, final PathFilter resourceExportFilter, final PathFilter resourceImportFilter, final ClassFilter classExportFilter, final ClassFilter classImportFilter) {
+        if (exportFilter == null) {
+            throw new IllegalArgumentException("exportFilter is null");
+        }
+        if (importFilter == null) {
+            throw new IllegalArgumentException("importFilter is null");
+        }
+        if (resourceExportFilter == null) {
+            throw new IllegalArgumentException("resourceExportFilter is null");
+        }
+        if (resourceImportFilter == null) {
+            throw new IllegalArgumentException("resourceImportFilter is null");
+        }
+        if (classExportFilter == null) {
+            throw new IllegalArgumentException("classExportFilter is null");
+        }
+        if (classImportFilter == null) {
+            throw new IllegalArgumentException("classImportFilter is null");
+        }
+        this.exportFilter = exportFilter;
+        this.importFilter = importFilter;
+        this.resourceExportFilter = resourceExportFilter;
+        this.resourceImportFilter = resourceImportFilter;
+        this.classExportFilter = classExportFilter;
+        this.classImportFilter = classImportFilter;
+    }
+
+    /**
+     * Get the export filter for this dependency.  This filter determines what imported paths are re-exported by this
+     * dependency.  All exported paths must also satisfy the import filter.
+     *
+     * @return the export filter
+     */
+    final PathFilter getExportFilter() {
+        return exportFilter;
+    }
+
+    /**
+     * Get the import filter for this dependency.  This filter determines what exported paths are imported from the
+     * dependency to the dependent.
+     *
+     * @return the import filter
+     */
+    final PathFilter getImportFilter() {
+        return importFilter;
+    }
+
+    final PathFilter getResourceExportFilter() {
+        return resourceExportFilter;
+    }
+
+    final PathFilter getResourceImportFilter() {
+        return resourceImportFilter;
+    }
+
+    final ClassFilter getClassExportFilter() {
+        return classExportFilter;
+    }
+
+    final ClassFilter getClassImportFilter() {
+        return classImportFilter;
+    }
+}
diff --git a/src/main/java/org/jboss/modules/DependencySpec.java b/src/main/java/org/jboss/modules/DependencySpec.java
new file mode 100644
index 0000000..3981199
--- /dev/null
+++ b/src/main/java/org/jboss/modules/DependencySpec.java
@@ -0,0 +1,482 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.util.Set;
+import org.jboss.modules.filter.ClassFilter;
+import org.jboss.modules.filter.ClassFilters;
+import org.jboss.modules.filter.PathFilter;
+import org.jboss.modules.filter.PathFilters;
+
+/**
+ * A dependency specification that represents a single dependency for a module.  The dependency can be on a local loader
+ * or another module, or on the target module's local loader.
+ *
+ * @apiviz.exclude
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ * @author <a href="mailto:jbailey at redhat.com">John Bailey</a>
+ * @author Jason T. Greene
+ */
+public abstract class DependencySpec {
+
+    final PathFilter importFilter;
+    final PathFilter exportFilter;
+    final PathFilter resourceImportFilter;
+    final PathFilter resourceExportFilter;
+    final ClassFilter classImportFilter;
+    final ClassFilter classExportFilter;
+
+    /**
+     * Get the dependency import filter.
+     *
+     * @return the import filter
+     */
+    public PathFilter getImportFilter() {
+        return importFilter;
+    }
+
+    /**
+     * Get the dependency export filter.
+     *
+     * @return the export filter
+     */
+    public PathFilter getExportFilter() {
+        return exportFilter;
+    }
+
+    /**
+     * Get the dependency resource import filter.
+     *
+     * @return the import filter
+     */
+    public PathFilter getResourceImportFilter() {
+        return resourceImportFilter;
+    }
+
+    /**
+     * Get the dependency resource export filter.
+     *
+     * @return the export filter
+     */
+    public PathFilter getResourceExportFilter() {
+        return resourceExportFilter;
+    }
+
+    /**
+     * Get the dependency class import filter.
+     *
+     * @return the class import filter
+     */
+    public ClassFilter getClassImportFilter() {
+        return classImportFilter;
+    }
+
+    /**
+     * Get the dependency class export filter.
+     *
+     * @return the class export filter
+     */
+    public ClassFilter getClassExportFilter() {
+        return classExportFilter;
+    }
+
+    DependencySpec(final PathFilter importFilter, final PathFilter exportFilter) {
+        this(importFilter, exportFilter, PathFilters.acceptAll(), PathFilters.acceptAll(), ClassFilters.acceptAll(), ClassFilters.acceptAll());
+    }
+
+    DependencySpec(final PathFilter importFilter, final PathFilter exportFilter, final PathFilter resourceImportFilter, final PathFilter resourceExportFilter, final ClassFilter classImportFilter, final ClassFilter classExportFilter) {
+        this.importFilter = importFilter;
+        this.exportFilter = exportFilter;
+        this.resourceImportFilter = resourceImportFilter;
+        this.resourceExportFilter = resourceExportFilter;
+        this.classImportFilter = classImportFilter;
+        this.classExportFilter = classExportFilter;
+    }
+
+    abstract Dependency getDependency(final Module module);
+
+    /**
+     * Create a dependency on the current module's local resources.  You should have at least one such dependency
+     * on any module which has its own resources.
+     *
+     * @return the dependency spec
+     */
+    public static DependencySpec createLocalDependencySpec() {
+        return createLocalDependencySpec(PathFilters.acceptAll(), PathFilters.acceptAll());
+    }
+
+    /**
+     * Create a dependency on the current module's local resources.  You should have at least one such dependency
+     * on any module which has its own resources.
+     *
+     * @param importFilter the import filter to apply
+     * @param exportFilter the export filter to apply
+     * @return the dependency spec
+     */
+    public static DependencySpec createLocalDependencySpec(final PathFilter importFilter, final PathFilter exportFilter) {
+        if (importFilter == null) {
+            throw new IllegalArgumentException("importFilter is null");
+        }
+        if (exportFilter == null) {
+            throw new IllegalArgumentException("exportFilter is null");
+        }
+        return new DependencySpec(importFilter, exportFilter) {
+            Dependency getDependency(final Module module) {
+                return new ModuleClassLoaderDependency(exportFilter, importFilter, module.getClassLoaderPrivate());
+            }
+
+            public String toString() {
+                return "dependency on local resources";
+            }
+        };
+    }
+
+    /**
+     * Create a dependency on the current module's local resources.  You should have at least one such dependency
+     * on any module which has its own resources.
+     *
+     * @param importFilter the import filter to apply
+     * @param exportFilter the export filter to apply
+     * @param resourceImportFilter the resource import filter to apply
+     * @param resourceExportFilter the resource export filter to apply
+     * @param classImportFilter the class import filter to apply
+     * @param classExportFilter the class export filter to apply
+     * @return the dependency spec
+     */
+    public static DependencySpec createLocalDependencySpec(final PathFilter importFilter, final PathFilter exportFilter, final PathFilter resourceImportFilter, final PathFilter resourceExportFilter, final ClassFilter classImportFilter, final ClassFilter classExportFilter) {
+        if (importFilter == null) {
+            throw new IllegalArgumentException("importFilter is null");
+        }
+        if (exportFilter == null) {
+            throw new IllegalArgumentException("exportFilter is null");
+        }
+        if (classImportFilter == null) {
+            throw new IllegalArgumentException("classImportFilter is null");
+        }
+        if (classExportFilter == null) {
+            throw new IllegalArgumentException("classExportFilter is null");
+        }
+        if (resourceImportFilter == null) {
+            throw new IllegalArgumentException("resourceImportFilter is null");
+        }
+        if (resourceExportFilter == null) {
+            throw new IllegalArgumentException("resourceExportFilter is null");
+        }
+        return new DependencySpec(importFilter, exportFilter, resourceImportFilter, resourceExportFilter, classImportFilter, classExportFilter) {
+            Dependency getDependency(final Module module) {
+                return new ModuleClassLoaderDependency(exportFilter, importFilter, resourceExportFilter, resourceImportFilter, classExportFilter, classImportFilter, module.getClassLoaderPrivate());
+            }
+
+            public String toString() {
+                return "dependency on filtered local resources";
+            }
+        };
+    }
+
+    /**
+     * Create a system dependency.
+     *
+     * @param loaderPaths the set of paths to use from the system class loader
+     * @return the dependency spec
+     */
+    public static DependencySpec createSystemDependencySpec(final Set<String> loaderPaths) {
+        return createLocalDependencySpec(ClassLoaderLocalLoader.SYSTEM, loaderPaths);
+    }
+
+    /**
+     * Create a system dependency.
+     *
+     * @param loaderPaths the set of paths to use from the system class loader
+     * @param export {@code true} if this is a fully re-exported dependency, {@code false} if it should not be exported
+     * @return the dependency spec
+     */
+    public static DependencySpec createSystemDependencySpec(final Set<String> loaderPaths, boolean export) {
+        return createLocalDependencySpec(ClassLoaderLocalLoader.SYSTEM, loaderPaths, export);
+    }
+
+    /**
+     * Create a system dependency.
+     *
+     * @param importFilter the import filter to apply
+     * @param exportFilter the export filter to apply
+     * @param loaderPaths the set of paths to use from the system class loader
+     * @return the dependency spec
+     */
+    public static DependencySpec createSystemDependencySpec(final PathFilter importFilter, final PathFilter exportFilter, final Set<String> loaderPaths) {
+        return createLocalDependencySpec(importFilter, exportFilter, ClassLoaderLocalLoader.SYSTEM, loaderPaths);
+    }
+
+    /**
+     * Create a dependency on the given class loader.
+     *
+     * @param classLoader the class loader
+     * @param loaderPaths the set of paths to use from this class loader
+     * @return the dependency spec
+     */
+    public static DependencySpec createClassLoaderDependencySpec(final ClassLoader classLoader, final Set<String> loaderPaths) {
+        return createLocalDependencySpec(new ClassLoaderLocalLoader(classLoader), loaderPaths);
+    }
+
+    /**
+     * Create a dependency on the given class loader.
+     *
+     * @param classLoader the class loader
+     * @param loaderPaths the set of paths to use from this class loader
+     * @param export {@code true} if this is a fully re-exported dependency, {@code false} if it should not be exported
+     * @return the dependency spec
+     */
+    public static DependencySpec createClassLoaderDependencySpec(final ClassLoader classLoader, final Set<String> loaderPaths, boolean export) {
+        return createLocalDependencySpec(new ClassLoaderLocalLoader(classLoader), loaderPaths, export);
+    }
+
+    /**
+     * Create a dependency on the given class loader.
+     *
+     * @param importFilter the import filter to apply
+     * @param exportFilter the export filter to apply
+     * @param classLoader the class loader
+     * @param loaderPaths the set of paths to use from this class loader
+     * @return the dependency spec
+     */
+    public static DependencySpec createClassLoaderDependencySpec(final PathFilter importFilter, final PathFilter exportFilter, final ClassLoader classLoader, final Set<String> loaderPaths) {
+        return createLocalDependencySpec(importFilter, exportFilter, PathFilters.acceptAll(), PathFilters.acceptAll(), ClassFilters.acceptAll(), ClassFilters.acceptAll(), new ClassLoaderLocalLoader(classLoader), loaderPaths);
+    }
+
+    /**
+     * Create a dependency on the given local loader.
+     *
+     * @param localLoader the local loader
+     * @param loaderPaths the set of paths that is exposed by the local loader
+     * @return the dependency spec
+     */
+    public static DependencySpec createLocalDependencySpec(final LocalLoader localLoader, final Set<String> loaderPaths) {
+        return createLocalDependencySpec(PathFilters.acceptAll(), PathFilters.rejectAll(), localLoader, loaderPaths);
+    }
+
+    /**
+     * Create a dependency on the given local loader.
+     *
+     * @param localLoader the local loader
+     * @param loaderPaths the set of paths that is exposed by the local loader
+     * @param export {@code true} if this is a fully re-exported dependency, {@code false} if it should not be exported
+     * @return the dependency spec
+     */
+    public static DependencySpec createLocalDependencySpec(final LocalLoader localLoader, final Set<String> loaderPaths, boolean export) {
+        return createLocalDependencySpec(PathFilters.acceptAll(), export ? PathFilters.getDefaultImportFilter() : PathFilters.rejectAll(), localLoader, loaderPaths);
+    }
+
+    /**
+     * Create a dependency on the given local loader.
+     *
+     * @param importFilter the import filter to apply
+     * @param exportFilter the export filter to apply
+     * @param localLoader the local loader
+     * @param loaderPaths the set of paths that is exposed by the local loader
+     * @return the dependency spec
+     */
+    public static DependencySpec createLocalDependencySpec(final PathFilter importFilter, final PathFilter exportFilter, final LocalLoader localLoader, final Set<String> loaderPaths) {
+        return createLocalDependencySpec(importFilter, exportFilter, PathFilters.acceptAll(), PathFilters.acceptAll(), ClassFilters.acceptAll(), ClassFilters.acceptAll(), localLoader, loaderPaths);
+    }
+
+    /**
+     * Create a dependency on the given local loader.
+     *
+     * @param importFilter the import filter to apply
+     * @param exportFilter the export filter to apply
+     * @param resourceImportFilter the resource import filter to apply
+     * @param resourceExportFilter the resource export filter to apply
+     * @param classImportFilter the class import filter to apply
+     * @param classExportFilter the class export filter to apply
+     * @param localLoader the local loader
+     * @param loaderPaths the set of paths that is exposed by the local loader
+     * @return the dependency spec
+     */
+    public static DependencySpec createLocalDependencySpec(final PathFilter importFilter, final PathFilter exportFilter, final PathFilter resourceImportFilter, final PathFilter resourceExportFilter, final ClassFilter classImportFilter, final ClassFilter classExportFilter, final LocalLoader localLoader, final Set<String> loaderPaths) {
+        if (importFilter == null) {
+            throw new IllegalArgumentException("importFilter is null");
+        }
+        if (exportFilter == null) {
+            throw new IllegalArgumentException("exportFilter is null");
+        }
+        if (localLoader == null) {
+            throw new IllegalArgumentException("localLoader is null");
+        }
+        if (loaderPaths == null) {
+            throw new IllegalArgumentException("loaderPaths is null");
+        }
+        if (classImportFilter == null) {
+            throw new IllegalArgumentException("classImportFilter is null");
+        }
+        if (classExportFilter == null) {
+            throw new IllegalArgumentException("classExportFilter is null");
+        }
+        if (resourceImportFilter == null) {
+            throw new IllegalArgumentException("resourceImportFilter is null");
+        }
+        if (resourceExportFilter == null) {
+            throw new IllegalArgumentException("resourceExportFilter is null");
+        }
+        return new DependencySpec(importFilter, exportFilter, resourceImportFilter, resourceExportFilter, classImportFilter, classExportFilter) {
+            Dependency getDependency(final Module module) {
+                return new LocalDependency(exportFilter, importFilter, resourceExportFilter, resourceImportFilter, classExportFilter, classImportFilter, localLoader, loaderPaths);
+            }
+
+            public String toString() {
+                return "dependency on local loader " + localLoader;
+            }
+        };
+    }
+
+    /**
+     * Create a dependency on the given module.
+     *
+     * @param identifier the module identifier
+     * @return the dependency spec
+     */
+    public static DependencySpec createModuleDependencySpec(final ModuleIdentifier identifier) {
+        return createModuleDependencySpec(identifier, false);
+    }
+
+    /**
+     * Create a dependency on the given module.
+     *
+     * @param identifier the module identifier
+     * @param export {@code true} if the dependency should be exported by default
+     * @return the dependency spec
+     */
+    public static DependencySpec createModuleDependencySpec(final ModuleIdentifier identifier, final boolean export) {
+        return createModuleDependencySpec(identifier, export, false);
+    }
+
+    /**
+     * Create a dependency on the given module.
+     *
+     * @param identifier the module identifier
+     * @param export {@code true} if this is a fully re-exported dependency, {@code false} if it should not be exported
+     * @param optional {@code true} if the dependency is optional, {@code false} if it is mandatory
+     * @return the dependency spec
+     */
+    public static DependencySpec createModuleDependencySpec(final ModuleIdentifier identifier, final boolean export, final boolean optional) {
+        return createModuleDependencySpec(PathFilters.getDefaultImportFilter(), export ? PathFilters.acceptAll() : PathFilters.rejectAll(), null, identifier, optional);
+    }
+
+    /**
+     * Create a dependency on the given module.
+     *
+     * @param moduleLoader the specific module loader from which the module should be acquired
+     * @param identifier the module identifier
+     * @param export {@code true} if this is a fully re-exported dependency, {@code false} if it should not be exported
+     * @return the dependency spec
+     */
+    public static DependencySpec createModuleDependencySpec(final ModuleLoader moduleLoader, final ModuleIdentifier identifier, final boolean export) {
+        return createModuleDependencySpec(PathFilters.getDefaultImportFilter(), export ? PathFilters.acceptAll() : PathFilters.rejectAll(), moduleLoader, identifier, false);
+    }
+
+    /**
+     * Create a dependency on the given module.
+     *
+     * @param moduleLoader the specific module loader from which the module should be acquired
+     * @param identifier the module identifier
+     * @param export {@code true} if this is a fully re-exported dependency, {@code false} if it should not be exported
+     * @param optional {@code true} if the dependency is optional, {@code false} if it is mandatory
+     * @return the dependency spec
+     */
+    public static DependencySpec createModuleDependencySpec(final ModuleLoader moduleLoader, final ModuleIdentifier identifier, final boolean export, final boolean optional) {
+        return createModuleDependencySpec(PathFilters.getDefaultImportFilter(), export ? PathFilters.acceptAll() : PathFilters.rejectAll(), moduleLoader, identifier, optional);
+    }
+
+    /**
+     * Create a dependency on the given module.
+     *
+     * @param exportFilter the export filter to apply
+     * @param identifier the module identifier
+     * @param optional {@code true} if the dependency is optional, {@code false} if it is mandatory
+     * @return the dependency spec
+     */
+    public static DependencySpec createModuleDependencySpec(final PathFilter exportFilter, final ModuleIdentifier identifier, final boolean optional) {
+        return createModuleDependencySpec(PathFilters.getDefaultImportFilter(), exportFilter, null, identifier, optional);
+    }
+
+    /**
+     * Create a dependency on the given module.
+     *
+     * @param exportFilter the export filter to apply
+     * @param moduleLoader the specific module loader from which the module should be acquired
+     * @param identifier the module identifier
+     * @param optional {@code true} if the dependency is optional, {@code false} if it is mandatory
+     * @return the dependency spec
+     */
+    public static DependencySpec createModuleDependencySpec(final PathFilter exportFilter, final ModuleLoader moduleLoader, final ModuleIdentifier identifier, final boolean optional) {
+        return createModuleDependencySpec(PathFilters.getDefaultImportFilter(), exportFilter, moduleLoader, identifier, optional);
+    }
+
+    /**
+     * Create a dependency on the given module.
+     *
+     * @param importFilter the import filter to apply
+     * @param exportFilter the export filter to apply
+     * @param moduleLoader the specific module loader from which the module should be acquired
+     * @param identifier the module identifier
+     * @param optional {@code true} if the dependency is optional, {@code false} if it is mandatory
+     * @return the dependency spec
+     */
+    public static DependencySpec createModuleDependencySpec(final PathFilter importFilter, final PathFilter exportFilter, final ModuleLoader moduleLoader, final ModuleIdentifier identifier, final boolean optional) {
+        return createModuleDependencySpec(importFilter, exportFilter, PathFilters.acceptAll(), PathFilters.acceptAll(), ClassFilters.acceptAll(), ClassFilters.acceptAll(), moduleLoader, identifier, optional);
+    }
+
+    /**
+     * Create a dependency on the given module.
+     *
+     * @param importFilter the import filter to apply
+     * @param exportFilter the export filter to apply
+     * @param resourceImportFilter the resource import filter to apply
+     * @param resourceExportFilter the resource export filter to apply
+     * @param classImportFilter the class import filter to apply
+     * @param classExportFilter the class export filter to apply
+     * @param moduleLoader the specific module loader from which the module should be acquired
+     * @param identifier the module identifier
+     * @param optional {@code true} if the dependency is optional, {@code false} if it is mandatory
+     * @return the dependency spec
+     */
+    public static DependencySpec createModuleDependencySpec(final PathFilter importFilter, final PathFilter exportFilter, final PathFilter resourceImportFilter, final PathFilter resourceExportFilter, final ClassFilter classImportFilter, final ClassFilter classExportFilter, final ModuleLoader moduleLoader, final ModuleIdentifier identifier, final boolean optional) {
+        if (importFilter == null) {
+            throw new IllegalArgumentException("importFilter is null");
+        }
+        if (exportFilter == null) {
+            throw new IllegalArgumentException("exportFilter is null");
+        }
+        if (identifier == null) {
+            throw new IllegalArgumentException("identifier is null");
+        }
+        if (classImportFilter == null) {
+            throw new IllegalArgumentException("classImportFilter is null");
+        }
+        if (classExportFilter == null) {
+            throw new IllegalArgumentException("classExportFilter is null");
+        }
+        if (resourceImportFilter == null) {
+            throw new IllegalArgumentException("resourceImportFilter is null");
+        }
+        if (resourceExportFilter == null) {
+            throw new IllegalArgumentException("resourceExportFilter is null");
+        }
+        return new ModuleDependencySpec(importFilter, exportFilter, resourceImportFilter, resourceExportFilter, classImportFilter, classExportFilter, moduleLoader, identifier, optional);
+    }
+}
diff --git a/src/main/java/org/jboss/modules/DependencyTreeViewer.java b/src/main/java/org/jboss/modules/DependencyTreeViewer.java
new file mode 100644
index 0000000..18db4ec
--- /dev/null
+++ b/src/main/java/org/jboss/modules/DependencyTreeViewer.java
@@ -0,0 +1,127 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import org.jboss.modules.filter.PathFilter;
+import org.jboss.modules.filter.PathFilters;
+
+/**
+ * A dependency tree viewer utility.  Prints out the dependency tree for a module.
+ */
+public final class DependencyTreeViewer {
+    private static <I, O extends I> O[] filtered(Class<O[]> oType, I... inputs) {
+        final I[] newArray = Arrays.copyOf(inputs, inputs.length);
+        int o = 0;
+        for (int i = 0; i < inputs.length; i ++) {
+            if (oType.getComponentType().isInstance(inputs[i])) {
+                newArray[o++] = (O) inputs[i];
+            }
+        }
+        return Arrays.copyOf(newArray, o, oType);
+    }
+
+    private static void print(PrintWriter out, String prefix, ModuleSpec spec, FastCopyHashSet<ModuleIdentifier> visited, File... roots) {
+        if (spec instanceof AliasModuleSpec) {
+            final AliasModuleSpec aliasModuleSpec = (AliasModuleSpec) spec;
+            out.print(" -> ");
+            final ModuleIdentifier aliasTarget = aliasModuleSpec.getAliasTarget();
+            out.println(aliasTarget);
+            if (visited.add(aliasTarget)) {
+                try {
+                    final ModuleSpec moduleSpec = LocalModuleFinder.parseModuleXmlFile(aliasTarget, null, roots);
+                    print(out, prefix, moduleSpec, visited);
+                } catch (IOException e) {
+                    out.println(e);
+                } catch (ModuleLoadException e) {
+                    out.println(e);
+                }
+            }
+        } else if (spec instanceof ConcreteModuleSpec) {
+            out.println();
+            final ConcreteModuleSpec concreteModuleSpec = (ConcreteModuleSpec) spec;
+            final DependencySpec[] dependencies = filtered(ModuleDependencySpec[].class, concreteModuleSpec.getDependencies());
+            for (int i = 0, dependenciesLength = dependencies.length; i < dependenciesLength; i++) {
+                print(out, prefix, dependencies[i], visited, i == dependenciesLength - 1, roots);
+            }
+        } else {
+            out.println();
+        }
+    }
+
+    private static void print(PrintWriter out, String prefix, DependencySpec spec, FastCopyHashSet<ModuleIdentifier> visited, final boolean last, final File... roots) {
+        if (spec instanceof ModuleDependencySpec) {
+            final ModuleDependencySpec moduleDependencySpec = (ModuleDependencySpec) spec;
+            final ModuleIdentifier identifier = moduleDependencySpec.getIdentifier();
+            out.print(prefix);
+            out.print(last ? '└' : '├');
+            out.print('─');
+            out.print(' ');
+            out.print(identifier);
+            if (moduleDependencySpec.isOptional()) {
+                out.print(" (optional)");
+            }
+            final PathFilter exportFilter = moduleDependencySpec.getExportFilter();
+            if (! exportFilter.equals(PathFilters.rejectAll())) {
+                out.print(" (exported)");
+            }
+            if (visited.add(identifier)) {
+                print(out, prefix + (last ? "   " : "│  "), identifier, visited, roots);
+            } else {
+                out.println();
+            }
+        }
+    }
+
+    private static void print(PrintWriter out, String prefix, ModuleIdentifier identifier, FastCopyHashSet<ModuleIdentifier> visited, final File... roots) {
+        final ModuleSpec moduleSpec;
+        try {
+            moduleSpec = LocalModuleFinder.parseModuleXmlFile(identifier, null, roots);
+            if (moduleSpec == null) {
+                out.println(" (not found)");
+            } else {
+                print(out, prefix, moduleSpec, visited, roots);
+            }
+        } catch (IOException e) {
+            out.print(" (");
+            out.print(e);
+            out.println(")");
+        } catch (ModuleLoadException e) {
+            out.print(" (");
+            out.print(e);
+            out.println(")");
+        }
+    }
+
+    /**
+     * Print the dependency tree for the given module with the given module root list.
+     *
+     * @param out the output stream to use
+     * @param identifier the identifier of the module to examine
+     * @param roots the module roots to search
+     */
+    public static void print(PrintWriter out, ModuleIdentifier identifier, final File... roots) {
+        out.print(identifier);
+        print(out, "", identifier, new FastCopyHashSet<ModuleIdentifier>(), roots);
+        out.flush();
+    }
+}
diff --git a/src/main/java/org/jboss/modules/FastCopyHashSet.java b/src/main/java/org/jboss/modules/FastCopyHashSet.java
new file mode 100644
index 0000000..4a1b2c7
--- /dev/null
+++ b/src/main/java/org/jboss/modules/FastCopyHashSet.java
@@ -0,0 +1,566 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.AbstractSet;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.ConcurrentModificationException;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+/**
+ * A HashSet that is optimized for fast shallow copies. If the copy-ctor is
+ * passed another FastCopyHashSet, or clone is called on this set, the shallow
+ * copy can be performed using little more than a single array copy. In order to
+ * accomplish this, immutable objects must be used internally, so update
+ * operations result in slightly more object churn than <code>HashSet</code>.
+ * <p/>
+ * Note: It is very important to use a smaller load factor than you normally
+ * would for HashSet, since the implementation is open-addressed with linear
+ * probing. With a 50% load-factor a get is expected to return in only 2 probes.
+ * However, a 90% load-factor is expected to return in around 50 probes.
+ *
+ * @author Jason T. Greene
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+class FastCopyHashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, Serializable {
+
+    /**
+     * Serialization ID
+     */
+    private static final long serialVersionUID = 10929568968762L;
+
+    /**
+     * Same default as HashMap, must be a power of 2
+     */
+    private static final int DEFAULT_CAPACITY = 64;
+
+    /**
+     * MAX_INT - 1
+     */
+    private static final int MAXIMUM_CAPACITY = 1 << 30;
+
+    /**
+     * 50%
+     */
+    private static final float DEFAULT_LOAD_FACTOR = 0x0.5p0f;
+
+    /**
+     * The open-addressed table
+     */
+    private transient E[] table;
+
+    /**
+     * The current number of key-value pairs
+     */
+    private transient int size;
+
+    /**
+     * The next resize
+     */
+    private transient int threshold;
+
+    /**
+     * The user defined load factor which defines when to resize
+     */
+    private final float loadFactor;
+
+    /**
+     * Counter used to detect changes made outside of an iterator
+     */
+    private transient int modCount;
+
+    /**
+     * Accumulated hash code
+     */
+    private transient int hashCode;
+
+    public FastCopyHashSet(int initialCapacity, float loadFactor) {
+        if (initialCapacity < 0)
+            throw new IllegalArgumentException("Can not have a negative size table!");
+
+        if (initialCapacity > MAXIMUM_CAPACITY)
+            initialCapacity = MAXIMUM_CAPACITY;
+
+        if (!(loadFactor > 0F && loadFactor <= 1F))
+            throw new IllegalArgumentException("Load factor must be greater than 0 and less than or equal to 1");
+
+        this.loadFactor = loadFactor;
+        init(initialCapacity, loadFactor);
+    }
+
+    public FastCopyHashSet(Set<? extends E> set) {
+        if (set instanceof FastCopyHashSet) {
+            FastCopyHashSet<? extends E> fast = (FastCopyHashSet<? extends E>) set;
+            table = fast.table.clone();
+            loadFactor = fast.loadFactor;
+            size = fast.size;
+            threshold = fast.threshold;
+            hashCode = fast.hashCode;
+        } else {
+            loadFactor = DEFAULT_LOAD_FACTOR;
+            init(set.size(), loadFactor);
+            addAll(set);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private void init(int initialCapacity, float loadFactor) {
+        int c = 1;
+        while (c < initialCapacity) c <<= 1;
+        threshold = (int) (c * loadFactor);
+
+        // Include the load factor when sizing the table for the first time
+        if (initialCapacity > threshold && c < MAXIMUM_CAPACITY) {
+            c <<= 1;
+            threshold = (int) (c * loadFactor);
+        }
+
+        table = (E[]) new Object[c];
+    }
+
+    public FastCopyHashSet(int initialCapacity) {
+        this(initialCapacity, DEFAULT_LOAD_FACTOR);
+    }
+
+    public FastCopyHashSet() {
+        this(DEFAULT_CAPACITY);
+    }
+
+    private int nextIndex(int index, int length) {
+        index = (index >= length - 1) ? 0 : index + 1;
+        return index;
+    }
+
+    private static int index(int hashCode, int length) {
+        return hashCode & (length - 1);
+    }
+
+    public int size() {
+        return size;
+    }
+
+    public boolean isEmpty() {
+        return size == 0;
+    }
+
+    public boolean contains(Object key) {
+        if (key == null) {
+            return false;
+        }
+
+        int hash = key.hashCode();
+        int length = table.length;
+        int index = index(hash, length);
+
+        for (int start = index; ;) {
+            E e = table[index];
+            if (e == null)
+                return false;
+
+            if (key.equals(e))
+                return true;
+
+            index = nextIndex(index, length);
+            if (index == start) // Full table
+                return false;
+        }
+    }
+
+    public boolean add(E key) {
+        if (key == null) {
+            throw new IllegalArgumentException("key is null");
+        }
+
+        E[] table = this.table;
+        int hash = key.hashCode();
+        int length = table.length;
+        int index = index(hash, length);
+
+        boolean f = false;
+        for (int start = index; ;) {
+            E e = table[index];
+            if (e == null)
+                break;
+
+            if (! f) {
+                f= true;
+            }
+            if (key.equals(e)) {
+                return false;
+            }
+
+            index = nextIndex(index, length);
+            if (index == start)
+                throw new IllegalStateException("Table is full!");
+        }
+
+        modCount++;
+        table[index] = key;
+        hashCode += key.hashCode();
+        if (++size >= threshold)
+            resize(length);
+
+        return true;
+    }
+
+
+    @SuppressWarnings("unchecked")
+    private void resize(int from) {
+        int newLength = from << 1;
+
+        // Can't get any bigger
+        if (newLength > MAXIMUM_CAPACITY || newLength <= from)
+            return;
+
+        E[] newTable = (E[]) new Object[newLength];
+        E[] old = table;
+
+        for (E e : old) {
+            if (e == null)
+                continue;
+
+            int index = index(e.hashCode(), newLength);
+            while (newTable[index] != null)
+                index = nextIndex(index, newLength);
+
+            newTable[index] = e;
+        }
+
+        threshold = (int) (loadFactor * newLength);
+        table = newTable;
+    }
+
+    public boolean addAll(Collection<? extends E> set) {
+        int size = set.size();
+        if (size == 0)
+            return false;
+
+        boolean changed = false;
+
+        for (E e : set) {
+            if (add(e)) {
+                changed = true;
+            }
+        }
+        return changed;
+    }
+
+    public boolean remove(Object key) {
+        E[] table = this.table;
+        int length = table.length;
+        int hash = key.hashCode();
+        int start = index(hash, length);
+
+        for (int index = start; ;) {
+            E e = table[index];
+            if (e == null)
+                return false;
+
+            if (key.equals(e)) {
+                table[index] = null;
+                hashCode -= hash;
+                relocate(index);
+                modCount++;
+                size--;
+                return true;
+            }
+
+            index = nextIndex(index, length);
+            if (index == start)
+                return false;
+        }
+    }
+
+    private void relocate(int start) {
+        E[] table = this.table;
+        int length = table.length;
+        int current = nextIndex(start, length);
+
+        for (; ;) {
+            E e = table[current];
+            if (e == null)
+                return;
+
+            // A Doug Lea variant of Knuth's Section 6.4 Algorithm R.
+            // This provides a non-recursive method of relocating
+            // entries to their optimal positions once a gap is created.
+            int prefer = index(e.hashCode(), length);
+            if ((current < prefer && (prefer <= start || start <= current))
+                    || (prefer <= start && start <= current)) {
+                table[start] = e;
+                table[current] = null;
+                start = current;
+            }
+
+            current = nextIndex(current, length);
+        }
+    }
+
+    public void clear() {
+        modCount++;
+        E[] table = this.table;
+        for (int i = 0; i < table.length; i++)
+            table[i] = null;
+
+        size = hashCode = 0;
+    }
+
+    @SuppressWarnings("unchecked")
+    public FastCopyHashSet<E> clone() {
+        try {
+            FastCopyHashSet<E> clone = (FastCopyHashSet<E>) super.clone();
+            clone.table = table.clone();
+            return clone;
+        }
+        catch (CloneNotSupportedException e) {
+            // should never happen
+            throw new IllegalStateException(e);
+        }
+    }
+
+    public Iterator<E> iterator() {
+        return new KeyIterator();
+    }
+
+    public void printDebugStats() {
+        int optimal = 0;
+        int total = 0;
+        int totalSkew = 0;
+        int maxSkew = 0;
+        for (int i = 0; i < table.length; i++) {
+            E e = table[i];
+            if (e != null) {
+
+                total++;
+                int target = index(e.hashCode(), table.length);
+                if (i == target)
+                    optimal++;
+                else {
+                    int skew = Math.abs(i - target);
+                    if (skew > maxSkew) maxSkew = skew;
+                    totalSkew += skew;
+                }
+
+            }
+        }
+
+        System.out.println(" Size:             " + size);
+        System.out.println(" Real Size:        " + total);
+        System.out.println(" Optimal:          " + optimal + " (" + (float) optimal * 100 / total + "%)");
+        System.out.println(" Average Distance: " + ((float) totalSkew / (total - optimal)));
+        System.out.println(" Max Distance:     " + maxSkew);
+    }
+
+    @SuppressWarnings("unchecked")
+    private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
+        s.defaultReadObject();
+
+        int size = s.readInt();
+
+        init(size, loadFactor);
+
+        for (int i = 0; i < size; i++) {
+            E key = (E) s.readObject();
+            putForCreate(key);
+        }
+
+        this.size = size;
+    }
+
+    @SuppressWarnings("unchecked")
+    private void putForCreate(E key) {
+        E[] table = this.table;
+        int hash = key.hashCode();
+        int length = table.length;
+        int index = index(hash, length);
+
+        E e = table[index];
+        while (e != null) {
+            index = nextIndex(index, length);
+            e = table[index];
+        }
+
+        table[index] = key;
+    }
+
+    private void writeObject(java.io.ObjectOutputStream s) throws IOException {
+        s.defaultWriteObject();
+        s.writeInt(size);
+
+        for (E e : table) {
+            if (e != null) {
+                s.writeObject(e);
+            }
+        }
+    }
+
+    public boolean containsAll(final Collection<?> c) {
+        final E[] table = this.table;
+        for (E e : table) {
+            if (e != null) {
+                if (! c.contains(e)) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    @SuppressWarnings("NonFinalFieldReferenceInEquals")
+    public boolean equals(final Object o) {
+        if (o == this)
+            return true;
+
+        if (! (o instanceof Set))
+            return false;
+        if (o instanceof FastCopyHashSet) {
+            final FastCopyHashSet<?> set = (FastCopyHashSet<?>) o;
+            if (hashCode != set.hashCode) {
+                return false;
+            }
+            if (table.length == set.table.length) {
+                return Arrays.equals(table, set.table);
+            }
+        }
+        Set<?> set = (Set<?>) o;
+        if (set.size() != size())
+            return false;
+        try {
+            return containsAll(set);
+        } catch (ClassCastException unused)   {
+            return false;
+        } catch (NullPointerException unused) {
+            return false;
+        }
+    }
+
+    @SuppressWarnings("NonFinalFieldReferencedInHashCode")
+    public int hashCode() {
+        return hashCode;
+    }
+
+    public Object[] getRawArray() {
+        return table;
+    }
+
+    private class KeyIterator implements Iterator<E> {
+
+        private int next = 0;
+        private int expectedCount = modCount;
+        private int current = -1;
+        private boolean hasNext;
+        private E[] table = FastCopyHashSet.this.table;
+
+        public E next() {
+            if (modCount != expectedCount)
+                throw new ConcurrentModificationException();
+
+            if (!hasNext && !hasNext())
+                throw new NoSuchElementException();
+
+            current = next++;
+            hasNext = false;
+
+            return table[current];
+        }
+
+        public boolean hasNext() {
+            if (hasNext == true)
+                return true;
+
+            E[] table = this.table;
+            for (int i = next; i < table.length; i++) {
+                if (table[i] != null) {
+                    next = i;
+                    return hasNext = true;
+                }
+            }
+
+            next = table.length;
+            return false;
+        }
+
+        @SuppressWarnings("unchecked")
+        public void remove() {
+            if (modCount != expectedCount)
+                throw new ConcurrentModificationException();
+
+            int current = this.current;
+            int delete = current;
+
+            if (current == -1)
+                throw new IllegalStateException();
+
+            // Invalidate current (prevents multiple remove)
+            this.current = -1;
+
+            // Start were we relocate
+            next = delete;
+
+            E[] table = this.table;
+            if (table != FastCopyHashSet.this.table) {
+                FastCopyHashSet.this.remove(table[delete]);
+                table[delete] = null;
+                expectedCount = modCount;
+                return;
+            }
+
+
+            int length = table.length;
+            int i = delete;
+
+            table[delete] = null;
+            size--;
+
+            for (; ;) {
+                i = nextIndex(i, length);
+                E e = table[i];
+                if (e == null)
+                    break;
+
+                int prefer = index(e.hashCode(), length);
+                if ((i < prefer && (prefer <= delete || delete <= i))
+                        || (prefer <= delete && delete <= i)) {
+                    // Snapshot the unseen portion of the table if we have
+                    // to relocate an entry that was already seen by this iterator
+                    if (i < current && current <= delete && table == FastCopyHashSet.this.table) {
+                        int remaining = length - current;
+                        E[] newTable = (E[]) new Object[remaining];
+                        System.arraycopy(table, current, newTable, 0, remaining);
+
+                        // Replace iterator's table.
+                        // Leave table local var pointing to the real table
+                        this.table = newTable;
+                        next = 0;
+                    }
+
+                    // Do the swap on the real table
+                    table[delete] = e;
+                    table[i] = null;
+                    delete = i;
+                }
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/jboss/modules/FileEntryResource.java b/src/main/java/org/jboss/modules/FileEntryResource.java
new file mode 100644
index 0000000..9e7e9d9
--- /dev/null
+++ b/src/main/java/org/jboss/modules/FileEntryResource.java
@@ -0,0 +1,98 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import static java.security.AccessController.doPrivileged;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.UndeclaredThrowableException;
+import java.net.URL;
+import java.security.AccessControlContext;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+
+/**
+ * A file entry resource.
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+final class FileEntryResource implements Resource {
+
+    private final String name;
+    private final File file;
+    private final URL url;
+    private final AccessControlContext context;
+
+    FileEntryResource(final String name, final File file, final URL url, final AccessControlContext context) {
+        this.name = name;
+        this.file = file;
+        this.url = url;
+        this.context = context;
+    }
+
+    public long getSize() {
+        final SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            return doPrivileged(new PrivilegedAction<Long>() {
+                public Long run() {
+                    return Long.valueOf(file.length());
+                }
+            }, context).longValue();
+        } else {
+            return file.length();
+        }
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public URL getURL() {
+        return url;
+    }
+
+    public InputStream openStream() throws IOException {
+        final SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            try {
+                return doPrivileged(new PrivilegedExceptionAction<FileInputStream>() {
+                    public FileInputStream run() throws IOException {
+                        return new FileInputStream(file);
+                    }
+                }, context);
+            } catch (PrivilegedActionException e) {
+                try {
+                    throw e.getException();
+                } catch (RuntimeException e1) {
+                    throw e1;
+                } catch (IOException e1) {
+                    throw e1;
+                } catch (Exception e1) {
+                    throw new UndeclaredThrowableException(e1);
+                }
+            }
+        } else {
+            return new FileInputStream(file);
+        }
+    }
+}
diff --git a/src/main/java/org/jboss/modules/FileResourceLoader.java b/src/main/java/org/jboss/modules/FileResourceLoader.java
new file mode 100644
index 0000000..d4b11f5
--- /dev/null
+++ b/src/main/java/org/jboss/modules/FileResourceLoader.java
@@ -0,0 +1,366 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import static java.security.AccessController.doPrivileged;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+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.OutputStreamWriter;
+import java.lang.reflect.UndeclaredThrowableException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.CodeSigner;
+import java.security.CodeSource;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.jar.Manifest;
+
+/**
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+final class FileResourceLoader extends NativeLibraryResourceLoader implements IterableResourceLoader {
+
+    private final String rootName;
+    private final Manifest manifest;
+    private final CodeSource codeSource;
+    private final AccessControlContext context;
+
+    FileResourceLoader(final String rootName, final File root, final AccessControlContext context) {
+        super(root);
+        if (root == null) {
+            throw new IllegalArgumentException("root is null");
+        }
+        if (rootName == null) {
+            throw new IllegalArgumentException("rootName is null");
+        }
+        if (context == null) {
+            throw new IllegalArgumentException("context is null");
+        }
+        this.rootName = rootName;
+        final File manifestFile = new File(root, "META-INF" + File.separatorChar + "MANIFEST.MF");
+        manifest = readManifestFile(manifestFile);
+        final URL rootUrl;
+        final SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            rootUrl = doPrivileged(new PrivilegedAction<URL>() {
+                public URL run() {
+                    try {
+                        return root.getAbsoluteFile().toURI().toURL();
+                    } catch (MalformedURLException e) {
+                        throw new IllegalArgumentException("Invalid root file specified", e);
+                    }
+                }
+            }, context);
+        } else try {
+            rootUrl = root.getAbsoluteFile().toURI().toURL();
+        } catch (MalformedURLException e) {
+            throw new IllegalArgumentException("Invalid root file specified", e);
+        }
+        this.context = context;
+        codeSource = new CodeSource(rootUrl, (CodeSigner[])null);
+    }
+
+    private static Manifest readManifestFile(final File manifestFile) {
+        try {
+            return manifestFile.exists() ? new Manifest(new FileInputStream(manifestFile)) : null;
+        } catch (IOException e) {
+            return null;
+        }
+    }
+
+    public String getRootName() {
+        return rootName;
+    }
+
+    public ClassSpec getClassSpec(final String fileName) throws IOException {
+        final SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            try {
+                return doPrivileged(new PrivilegedExceptionAction<ClassSpec>() {
+                    public ClassSpec run() throws IOException {
+                        return doGetClassSpec(fileName);
+                    }
+                }, context);
+            } catch (PrivilegedActionException e) {
+                try {
+                    throw e.getException();
+                } catch (IOException e1) {
+                    throw e1;
+                } catch (RuntimeException e1) {
+                    throw e1;
+                } catch (Exception e1) {
+                    throw new UndeclaredThrowableException(e1);
+                }
+            }
+        } else {
+            return doGetClassSpec(fileName);
+        }
+    }
+
+    private ClassSpec doGetClassSpec(final String fileName) throws IOException {
+        final File file = new File(getRoot(), fileName);
+        if (! file.exists()) {
+            return null;
+        }
+        final long size = file.length();
+        final ClassSpec spec = new ClassSpec();
+        spec.setCodeSource(codeSource);
+        final InputStream is = new FileInputStream(file);
+        try {
+            if (size <= (long) Integer.MAX_VALUE) {
+                final int castSize = (int) size;
+                byte[] bytes = new byte[castSize];
+                int a = 0, res;
+                while ((res = is.read(bytes, a, castSize - a)) > 0) {
+                    a += res;
+                }
+                // done
+                is.close();
+                spec.setBytes(bytes);
+                return spec;
+            } else {
+                throw new IOException("Resource is too large to be a valid class file");
+            }
+        } finally {
+            StreamUtil.safeClose(is);
+        }
+    }
+
+    public PackageSpec getPackageSpec(final String name) throws IOException {
+        final SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            try {
+                return doPrivileged(new PrivilegedExceptionAction<PackageSpec>() {
+                    public PackageSpec run() throws IOException {
+                        return getPackageSpec(name, manifest, getRoot().toURI().toURL());
+                    }
+                }, context);
+            } catch (PrivilegedActionException e) {
+                try {
+                    throw e.getException();
+                } catch (IOException e1) {
+                    throw e1;
+                } catch (RuntimeException e1) {
+                    throw e1;
+                } catch (Exception e1) {
+                    throw new UndeclaredThrowableException(e1);
+                }
+            }
+        } else {
+            return getPackageSpec(name, manifest, getRoot().toURI().toURL());
+        }
+    }
+
+    public Resource getResource(final String name) {
+        final String canonPath = PathUtils.canonicalize(PathUtils.relativize(name));
+        final File file = new File(getRoot(), canonPath);
+        final SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            return doPrivileged(new PrivilegedAction<Resource>() {
+                public Resource run() {
+                    if (!file.exists()) {
+                        return null;
+                    } else {
+                        try {
+                            return new FileEntryResource(canonPath, file, file.toURI().toURL(), context);
+                        } catch (MalformedURLException e) {
+                            return null;
+                        }
+                    }
+                }
+            }, context);
+        } else if (! file.exists()) {
+            return null;
+        } else {
+            try {
+                return new FileEntryResource(canonPath, file, file.toURI().toURL(), context);
+            } catch (MalformedURLException e) {
+                return null;
+            }
+        }
+    }
+
+    class Itr implements Iterator<Resource> {
+        private final String base;
+        private final String[] names;
+        private final boolean recursive;
+        private int i = 0;
+        private Itr nested;
+        private Resource next;
+
+        Itr(final String base, final String[] names, final boolean recursive) {
+            assert PathUtils.isRelative(base);
+            assert names != null && names.length > 0;
+            this.base = base;
+            this.names = names;
+            this.recursive = recursive;
+        }
+
+        public boolean hasNext() {
+            final String[] names = this.names;
+            if (next != null) {
+                return true;
+            }
+            final String base = this.base;
+            while (i < names.length) {
+                final String current = names[i];
+                final String full = base.isEmpty() ? current : base + "/" + current;
+                final File file = new File(getRoot(), full);
+                if (recursive && nested == null) {
+                    final String[] children = file.list();
+                    if (children != null && children.length > 0) {
+                        nested = new Itr(full, children, recursive);
+                    }
+                }
+                if (nested != null) {
+                    if (nested.hasNext()) {
+                        next = nested.next();
+                        return true;
+                    }
+                    nested = null;
+                }
+                i++;
+                if (file.isFile()) {
+                    try {
+                        next = new FileEntryResource(full, file, file.toURI().toURL(), context);
+                        return true;
+                    } catch (MalformedURLException ignored) {
+                    }
+                }
+            }
+            return false;
+        }
+
+        public Resource next() {
+            if (! hasNext()) {
+                throw new NoSuchElementException();
+            }
+            try {
+                return next;
+            } finally {
+                next = null;
+            }
+        }
+
+        public void remove() {
+        }
+    }
+
+    public Iterator<Resource> iterateResources(final String startPath, final boolean recursive) {
+        final String canonPath = PathUtils.canonicalize(PathUtils.relativize(startPath));
+        final File start = new File(getRoot(), canonPath);
+        final String[] children = start.list();
+        if (children == null || children.length == 0) {
+            return Collections.<Resource>emptySet().iterator();
+        }
+        return new Itr(canonPath, children, recursive);
+    }
+
+    public Collection<String> getPaths() {
+        final List<String> index = new ArrayList<String>();
+        final File indexFile = new File(getRoot().getPath() + ".index");
+        if (ResourceLoaders.USE_INDEXES) {
+            // First check for an index file
+            if (indexFile.exists()) {
+                try {
+                    final BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(indexFile)));
+                    try {
+                        String s;
+                        while ((s = r.readLine()) != null) {
+                            index.add(s.trim());
+                        }
+                        return index;
+                    } finally {
+                        // if exception is thrown, undo index creation
+                        r.close();
+                    }
+                } catch (IOException e) {
+                    index.clear();
+                }
+            }
+        }
+        // Manually build index, starting with the root path
+        index.add("");
+        buildIndex(index, getRoot(), "");
+        if (ResourceLoaders.WRITE_INDEXES) {
+            // Now try to write it
+            boolean ok = false;
+            try {
+                final FileOutputStream fos = new FileOutputStream(indexFile);
+                try {
+                    final OutputStreamWriter osw = new OutputStreamWriter(fos);
+                    try {
+                        final BufferedWriter writer = new BufferedWriter(osw);
+                        try {
+                            for (String name : index) {
+                                writer.write(name);
+                                writer.write('\n');
+                            }
+                            writer.close();
+                            osw.close();
+                            fos.close();
+                            ok = true;
+                        } finally {
+                            StreamUtil.safeClose(writer);
+                        }
+                    } finally {
+                        StreamUtil.safeClose(osw);
+                    }
+                } finally {
+                    StreamUtil.safeClose(fos);
+                }
+            } catch (IOException e) {
+                // failed, ignore
+            } finally {
+                if (! ok) {
+                    // well, we tried...
+                    indexFile.delete();
+                }
+            }
+        }
+        return index;
+    }
+
+    private void buildIndex(final List<String> index, final File root, final String pathBase) {
+        File[] files = root.listFiles();
+        if (files != null) for (File file : files) {
+            if (file.isDirectory()) {
+                index.add(pathBase + file.getName());
+                buildIndex(index, file, pathBase + file.getName() + "/");
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/jboss/modules/FilteredIterableLocalLoader.java b/src/main/java/org/jboss/modules/FilteredIterableLocalLoader.java
new file mode 100644
index 0000000..8d8ad9d
--- /dev/null
+++ b/src/main/java/org/jboss/modules/FilteredIterableLocalLoader.java
@@ -0,0 +1,58 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import org.jboss.modules.filter.ClassFilter;
+import org.jboss.modules.filter.PathFilter;
+import org.jboss.modules.filter.PathFilters;
+
+/**
+* @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+*/
+class FilteredIterableLocalLoader implements IterableLocalLoader {
+
+    private final ClassFilter classFilter;
+    private final IterableLocalLoader originalLoader;
+    private final PathFilter resourcePathFilter;
+
+    FilteredIterableLocalLoader(final ClassFilter classFilter, final PathFilter resourcePathFilter, final IterableLocalLoader originalLoader) {
+        this.classFilter = classFilter;
+        this.originalLoader = originalLoader;
+        this.resourcePathFilter = resourcePathFilter;
+    }
+
+    public Class<?> loadClassLocal(final String name, final boolean resolve) {
+        return classFilter.accept(name) ? originalLoader.loadClassLocal(name, resolve) : null;
+    }
+
+    public Package loadPackageLocal(final String name) {
+        return originalLoader.loadPackageLocal(name);
+    }
+
+    public List<Resource> loadResourceLocal(final String name) {
+        return resourcePathFilter.accept(name) ? originalLoader.loadResourceLocal(name) : Collections.<Resource>emptyList();
+    }
+
+    public Iterator<Resource> iterateResources(final String startPath, final boolean recursive) {
+        return PathFilters.filtered(resourcePathFilter, originalLoader.iterateResources(startPath, recursive));
+    }
+}
diff --git a/src/main/java/org/jboss/modules/FilteredIterableResourceLoader.java b/src/main/java/org/jboss/modules/FilteredIterableResourceLoader.java
new file mode 100644
index 0000000..3a3eec0
--- /dev/null
+++ b/src/main/java/org/jboss/modules/FilteredIterableResourceLoader.java
@@ -0,0 +1,66 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Iterator;
+import org.jboss.modules.filter.PathFilter;
+import org.jboss.modules.filter.PathFilters;
+
+final class FilteredIterableResourceLoader implements IterableResourceLoader {
+
+    private final PathFilter filter;
+    private final IterableResourceLoader loader;
+
+    FilteredIterableResourceLoader(final PathFilter filter, final IterableResourceLoader loader) {
+        this.filter = filter;
+        this.loader = loader;
+    }
+
+    public String getRootName() {
+        return loader.getRootName();
+    }
+
+    public ClassSpec getClassSpec(final String fileName) throws IOException {
+        final String canonicalFileName = PathUtils.canonicalize(PathUtils.relativize(fileName));
+        return filter.accept(canonicalFileName) ? loader.getClassSpec(canonicalFileName) : null;
+    }
+
+    public PackageSpec getPackageSpec(final String name) throws IOException {
+        return loader.getPackageSpec(PathUtils.canonicalize(PathUtils.relativize(name)));
+    }
+
+    public Resource getResource(final String name) {
+        final String canonicalFileName = PathUtils.canonicalize(PathUtils.relativize(name));
+        return filter.accept(canonicalFileName) ? loader.getResource(canonicalFileName) : null;
+    }
+
+    public String getLibrary(final String name) {
+        return loader.getLibrary(PathUtils.canonicalize(PathUtils.relativize(name)));
+    }
+
+    public Collection<String> getPaths() {
+        return loader.getPaths();
+    }
+
+    public Iterator<Resource> iterateResources(final String startPath, final boolean recursive) {
+        return PathFilters.filtered(filter, loader.iterateResources(PathUtils.relativize(PathUtils.canonicalize(startPath)), recursive));
+    }
+}
diff --git a/src/main/java/org/jboss/modules/FilteredLocalLoader.java b/src/main/java/org/jboss/modules/FilteredLocalLoader.java
new file mode 100644
index 0000000..8229834
--- /dev/null
+++ b/src/main/java/org/jboss/modules/FilteredLocalLoader.java
@@ -0,0 +1,52 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.util.Collections;
+import java.util.List;
+import org.jboss.modules.filter.ClassFilter;
+import org.jboss.modules.filter.PathFilter;
+
+/**
+* @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+*/
+class FilteredLocalLoader implements LocalLoader {
+
+    private final ClassFilter classFilter;
+    private final LocalLoader originalLoader;
+    private final PathFilter resourcePathFilter;
+
+    FilteredLocalLoader(final ClassFilter classFilter, final PathFilter resourcePathFilter, final LocalLoader originalLoader) {
+        this.classFilter = classFilter;
+        this.originalLoader = originalLoader;
+        this.resourcePathFilter = resourcePathFilter;
+    }
+
+    public Class<?> loadClassLocal(final String name, final boolean resolve) {
+        return classFilter.accept(name) ? originalLoader.loadClassLocal(name, resolve) : null;
+    }
+
+    public Package loadPackageLocal(final String name) {
+        return originalLoader.loadPackageLocal(name);
+    }
+
+    public List<Resource> loadResourceLocal(final String name) {
+        return resourcePathFilter.accept(name) ? originalLoader.loadResourceLocal(name) : Collections.<Resource>emptyList();
+    }
+}
diff --git a/src/main/java/org/jboss/modules/FilteredResourceLoader.java b/src/main/java/org/jboss/modules/FilteredResourceLoader.java
new file mode 100644
index 0000000..5f09bdf
--- /dev/null
+++ b/src/main/java/org/jboss/modules/FilteredResourceLoader.java
@@ -0,0 +1,60 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.io.IOException;
+import java.util.Collection;
+import org.jboss.modules.filter.PathFilter;
+
+final class FilteredResourceLoader implements ResourceLoader {
+
+    private final PathFilter filter;
+    private final ResourceLoader loader;
+
+    FilteredResourceLoader(final PathFilter filter, final ResourceLoader loader) {
+        this.filter = filter;
+        this.loader = loader;
+    }
+
+    public String getRootName() {
+        return loader.getRootName();
+    }
+
+    public ClassSpec getClassSpec(final String fileName) throws IOException {
+        final String canonicalFileName = PathUtils.canonicalize(PathUtils.relativize(fileName));
+        return filter.accept(canonicalFileName) ? loader.getClassSpec(canonicalFileName) : null;
+    }
+
+    public PackageSpec getPackageSpec(final String name) throws IOException {
+        return loader.getPackageSpec(PathUtils.canonicalize(PathUtils.relativize(name)));
+    }
+
+    public Resource getResource(final String name) {
+        final String canonicalFileName = PathUtils.canonicalize(PathUtils.relativize(name));
+        return filter.accept(canonicalFileName) ? loader.getResource(canonicalFileName) : null;
+    }
+
+    public String getLibrary(final String name) {
+        return loader.getLibrary(PathUtils.canonicalize(PathUtils.relativize(name)));
+    }
+
+    public Collection<String> getPaths() {
+        return loader.getPaths();
+    }
+}
diff --git a/src/main/java/org/jboss/modules/IdentityHashSet.java b/src/main/java/org/jboss/modules/IdentityHashSet.java
new file mode 100644
index 0000000..f21976b
--- /dev/null
+++ b/src/main/java/org/jboss/modules/IdentityHashSet.java
@@ -0,0 +1,559 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.lang.reflect.Array;
+import java.util.AbstractSet;
+import java.util.Collection;
+import java.util.ConcurrentModificationException;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+/**
+ * An identity based hash set. A number of properties apply to this set. It
+ * compares only using object identity, it supports null entries, it allocates
+ * little more than a single object array, and it can be copied quickly. If the
+ * copy-ctor is passed another IdentityHashSet, or clone is called on this set,
+ * the shallow copy can be performed using little more than a single array copy.
+ *
+ * Note: It is very important to use a smaller load factor than you normally
+ * would for HashSet, since the implementation is open-addressed with linear
+ * probing. With a 50% load-factor a get is expected to return in only 2 probes.
+ * However, a 90% load-factor is expected to return in around 50 probes.
+ *
+ * @author Jason T. Greene
+ */
+class IdentityHashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, Serializable {
+
+    /**
+     * Serialization ID
+     */
+    private static final long serialVersionUID = 10929568968762L;
+
+    /**
+     * Same default as HashMap, must be a power of 2
+     */
+    private static final int DEFAULT_CAPACITY = 8;
+
+    /**
+     * MAX_INT - 1
+     */
+    private static final int MAXIMUM_CAPACITY = 1 << 30;
+
+    /**
+     * 67%, just like IdentityHashMap
+     */
+    private static final float DEFAULT_LOAD_FACTOR = 0.67f;
+
+    /**
+     * The open-addressed table
+     */
+    private transient Object[] table;
+
+    /**
+     * The current number of key-value pairs
+     */
+    private transient int size;
+
+    /**
+     * The next resize
+     */
+    private transient int threshold;
+
+    /**
+     * The user defined load factor which defines when to resize
+     */
+    private final float loadFactor;
+
+    /**
+     * Counter used to detect changes made outside of an iterator
+     */
+    private transient int modCount;
+
+    public IdentityHashSet(int initialCapacity, float loadFactor) {
+        if (initialCapacity < 0)
+            throw new IllegalArgumentException("Can not have a negative size table!");
+
+        if (initialCapacity > MAXIMUM_CAPACITY)
+            initialCapacity = MAXIMUM_CAPACITY;
+
+        if (!(loadFactor > 0F && loadFactor <= 1F))
+            throw new IllegalArgumentException("Load factor must be greater than 0 and less than or equal to 1");
+
+        this.loadFactor = loadFactor;
+        init(initialCapacity, loadFactor);
+    }
+
+    @SuppressWarnings("unchecked")
+    public IdentityHashSet(Set<? extends E> set) {
+        if (set instanceof IdentityHashSet) {
+            IdentityHashSet<? extends E> fast = (IdentityHashSet<? extends E>) set;
+            table = fast.table.clone();
+            loadFactor = fast.loadFactor;
+            size = fast.size;
+            threshold = fast.threshold;
+        } else {
+            loadFactor = DEFAULT_LOAD_FACTOR;
+            init(set.size(), loadFactor);
+            addAll(set);
+        }
+    }
+
+    private void init(int initialCapacity, float loadFactor) {
+        int c = 1;
+        for (; c < initialCapacity; c <<= 1);
+        threshold = (int) (c * loadFactor);
+
+        // Include the load factor when sizing the table for the first time
+        if (initialCapacity > threshold && c < MAXIMUM_CAPACITY) {
+            c <<= 1;
+            threshold = (int) (c * loadFactor);
+        }
+
+        table = new Object[c];
+    }
+
+    public IdentityHashSet(int initialCapacity) {
+        this(initialCapacity, DEFAULT_LOAD_FACTOR);
+    }
+
+    public IdentityHashSet() {
+        this(DEFAULT_CAPACITY);
+    }
+
+    // The normal bit spreader...
+    private static int hash(Object o) {
+        int h = System.identityHashCode(o);
+        h ^= (h >>> 20) ^ (h >>> 12);
+        return h ^ (h >>> 7) ^ (h >>> 4);
+    }
+
+    private int nextIndex(int index, int length) {
+        index = (index >= length - 1) ? 0 : index + 1;
+        return index;
+    }
+
+    private static int index(int hashCode, int length) {
+        return hashCode & (length - 1);
+    }
+
+    public int size() {
+        return size;
+    }
+
+    public boolean isEmpty() {
+        return size == 0;
+    }
+
+    public boolean contains(Object entry) {
+        if (entry == null) return false;
+
+        int hash = hash(entry);
+        int length = table.length;
+        int index = index(hash, length);
+
+        for (int start = index;;) {
+            Object e = table[index];
+            if (e == null)
+                return false;
+
+            if (entry == e)
+                return true;
+
+            index = nextIndex(index, length);
+            if (index == start) // Full table
+                return false;
+        }
+    }
+
+    public boolean add(E entry) {
+        if (entry == null) {
+            throw new NullPointerException("entry is null");
+        }
+
+        Object[] table = this.table;
+        int hash = hash(entry);
+        int length = table.length;
+        int index = index(hash, length);
+
+        for (int start = index;;) {
+            Object e = table[index];
+            if (e == null)
+                break;
+
+            if (e == entry)
+                return false;
+
+            index = nextIndex(index, length);
+            if (index == start)
+                throw new IllegalStateException("Table is full!");
+        }
+
+        modCount++;
+        table[index] = entry;
+        if (++size >= threshold)
+            resize(length);
+
+        return true;
+    }
+
+    private void resize(int from) {
+        int newLength = from << 1;
+
+        // Can't get any bigger
+        if (newLength > MAXIMUM_CAPACITY || newLength <= from)
+            return;
+
+        Object[] newTable = new Object[newLength];
+        Object[] old = table;
+
+        for (Object e : old) {
+            if (e == null)
+                continue;
+
+            int index = index(hash(e), newLength);
+            while (newTable[index] != null)
+                index = nextIndex(index, newLength);
+
+            newTable[index] = e;
+        }
+
+        threshold = (int) (loadFactor * newLength);
+        table = newTable;
+    }
+
+    @SuppressWarnings({ "unchecked" })
+    public boolean addAll(Collection<? extends E> collection) {
+        int size = collection.size();
+        if (size == 0)
+            return false;
+
+        if (size > threshold) {
+            if (size > MAXIMUM_CAPACITY)
+                size = MAXIMUM_CAPACITY;
+
+            int length = table.length;
+            for (; length < size; length <<= 1);
+
+            resize(length);
+        }
+
+        boolean state = false;
+
+        if (collection instanceof IdentityHashSet) {
+            for (E e : ((E[]) (((IdentityHashSet<?>) collection).table)))
+                if (e != null) state |= add(e);
+        } else {
+            for (E e : collection)
+                state |= add(e);
+        }
+
+        return state;
+    }
+
+    public boolean remove(Object o) {
+        if (o == null) return false;
+
+        Object[] table = this.table;
+        int length = table.length;
+        int hash = hash(o);
+        int start = index(hash, length);
+
+        for (int index = start;;) {
+            Object e = table[index];
+            if (e == null)
+                return false;
+
+            if (e == o) {
+                table[index] = null;
+                relocate(index);
+                modCount++;
+                size--;
+                return true;
+            }
+
+            index = nextIndex(index, length);
+            if (index == start)
+                return false;
+        }
+    }
+
+    private void relocate(int start) {
+        Object[] table = this.table;
+        int length = table.length;
+        int current = nextIndex(start, length);
+
+        for (;;) {
+            Object e = table[current];
+            if (e == null)
+                return;
+
+            // A Doug Lea variant of Knuth's Section 6.4 Algorithm R.
+            // This provides a non-recursive method of relocating
+            // entries to their optimal positions once a gap is created.
+            int prefer = index(hash(e), length);
+            if ((current < prefer && (prefer <= start || start <= current)) || (prefer <= start && start <= current)) {
+                table[start] = e;
+                table[current] = null;
+                start = current;
+            }
+
+            current = nextIndex(current, length);
+        }
+    }
+
+    public void clear() {
+        modCount++;
+        Object[] table = this.table;
+        for (int i = 0; i < table.length; i++)
+            table[i] = null;
+
+        size = 0;
+    }
+
+    @SuppressWarnings("unchecked")
+    public IdentityHashSet<E> clone() {
+        try {
+            IdentityHashSet<E> clone = (IdentityHashSet<E>) super.clone();
+            clone.table = table.clone();
+            return clone;
+        } catch (CloneNotSupportedException e) {
+            // should never happen
+            throw new IllegalStateException(e);
+        }
+    }
+
+    /**
+     * Advanced method that returns a copy of the internal table. The resulting
+     * array will contain nulls at random places that must be skipped. In
+     * addition, it will not operate correctly if a null was inserted into the
+     * set. Use at your own risk....
+     *
+     * @return an array containing elements in this set along with randomly
+     *         placed nulls,
+     */
+    @SuppressWarnings({ "unchecked" })
+    public E[] toScatteredArray(E[] dummy) {
+        final E[] ret = (E[]) Array.newInstance(dummy.getClass().getComponentType(), table.length);
+        System.arraycopy((E[])table, 0, ret, 0, ret.length);
+
+        return ret;
+    }
+
+    /**
+     * Warning: this will crap out if the set contains a {@code null}.
+     *
+     * @param target the target to write to
+     * @param offs the offset into the target
+     * @param len the length to write
+     * @return the target array
+     */
+    @SuppressWarnings({ "unchecked" })
+    public E[] toArray(final E[] target, final int offs, final int len) {
+        assert len <= size;
+        final E[] table = (E[]) this.table;
+        E e;
+        final int last = offs + len;
+        for (int i = offs, j = 0; i < last; j ++) {
+            e = table[j];
+            if (e != null) {
+                target[i++] = e;
+            }
+        }
+        return target;
+    }
+
+    public void printDebugStats() {
+        int optimal = 0;
+        int total = 0;
+        int totalSkew = 0;
+        int maxSkew = 0;
+        for (int i = 0; i < table.length; i++) {
+            Object e = table[i];
+            if (e != null) {
+
+                total++;
+                int target = index(hash(e), table.length);
+                if (i == target)
+                    optimal++;
+                else {
+                    int skew = Math.abs(i - target);
+                    if (skew > maxSkew)
+                        maxSkew = skew;
+                    totalSkew += skew;
+                }
+
+            }
+        }
+
+        System.out.println(" Size:             " + size);
+        System.out.println(" Real Size:        " + total);
+        System.out.println(" Optimal:          " + optimal + " (" + (float) optimal * 100 / total + "%)");
+        System.out.println(" Average Distance: " + ((float) totalSkew / (total - optimal)));
+        System.out.println(" Max Distance:     " + maxSkew);
+    }
+
+    @SuppressWarnings("unchecked")
+    private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
+        s.defaultReadObject();
+
+        int size = s.readInt();
+
+        init(size, loadFactor);
+
+        for (int i = 0; i < size; i++) {
+            putForCreate((E) s.readObject());
+        }
+
+        this.size = size;
+    }
+
+    private void putForCreate(E entry) {
+
+        Object[] table = this.table;
+        int hash = hash(entry);
+        int length = table.length;
+        int index = index(hash, length);
+
+        Object e = table[index];
+        while (e != null) {
+            index = nextIndex(index, length);
+            e = table[index];
+        }
+
+        table[index] = entry;
+    }
+
+    private void writeObject(java.io.ObjectOutputStream s) throws IOException {
+        s.defaultWriteObject();
+        s.writeInt(size);
+
+        for (Object e : table) {
+            if (e != null) {
+                s.writeObject(e);
+            }
+        }
+    }
+
+    @Override
+    public Iterator<E> iterator() {
+        return new IdentityHashSetIterator();
+    }
+
+    private class IdentityHashSetIterator implements Iterator<E> {
+        private int next = 0;
+        private int expectedCount = modCount;
+        private int current = -1;
+        private boolean hasNext;
+        Object table[] = IdentityHashSet.this.table;
+
+        public boolean hasNext() {
+            if (hasNext == true)
+                return true;
+
+            Object table[] = this.table;
+            for (int i = next; i < table.length; i++) {
+                if (table[i] != null) {
+                    next = i;
+                    return hasNext = true;
+                }
+            }
+
+            next = table.length;
+            return false;
+        }
+
+        @SuppressWarnings("unchecked")
+        public E next() {
+            if (modCount != expectedCount)
+                throw new ConcurrentModificationException();
+
+            if (!hasNext && !hasNext())
+                throw new NoSuchElementException();
+
+            current = next++;
+            hasNext = false;
+
+            return (E) table[current];
+        }
+
+        public void remove() {
+            if (modCount != expectedCount)
+                throw new ConcurrentModificationException();
+
+            int current = this.current;
+            int delete = current;
+
+            if (current == -1)
+                throw new IllegalStateException();
+
+            // Invalidate current (prevents multiple remove)
+            this.current = -1;
+
+            // Start were we relocate
+            next = delete;
+
+            Object[] table = this.table;
+            if (table != IdentityHashSet.this.table) {
+                IdentityHashSet.this.remove(table[delete]);
+                table[delete] = null;
+                expectedCount = modCount;
+                return;
+            }
+
+            int length = table.length;
+            int i = delete;
+
+            table[delete] = null;
+            size--;
+
+            for (;;) {
+                i = nextIndex(i, length);
+                Object e = table[i];
+                if (e == null)
+                    break;
+
+                int prefer = index(hash(e), length);
+                if ((i < prefer && (prefer <= delete || delete <= i)) || (prefer <= delete && delete <= i)) {
+                    // Snapshot the unseen portion of the table if we have
+                    // to relocate an entry that was already seen by this
+                    // iterator
+                    if (i < current && current <= delete && table == IdentityHashSet.this.table) {
+                        int remaining = length - current;
+                        Object[] newTable = new Object[remaining];
+                        System.arraycopy(table, current, newTable, 0, remaining);
+
+                        // Replace iterator's table.
+                        // Leave table local var pointing to the real table
+                        this.table = newTable;
+                        next = 0;
+                    }
+
+                    // Do the swap on the real table
+                    table[delete] = e;
+                    table[i] = null;
+                    delete = i;
+                }
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/jboss/modules/IterableLocalLoader.java b/src/main/java/org/jboss/modules/IterableLocalLoader.java
new file mode 100644
index 0000000..39130b3
--- /dev/null
+++ b/src/main/java/org/jboss/modules/IterableLocalLoader.java
@@ -0,0 +1,40 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.util.Iterator;
+
+/**
+ * A local loader which can enumerate its contents.
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public interface IterableLocalLoader extends LocalLoader {
+
+    /**
+     * Enumerate all the resources under the given path.  The given path name is relative to the root
+     * of the resource loader.  If the path "escapes" the root via {@code ..}, such segments will be consumed.
+     * If the path is absolute, it will be converted to a relative path by dropping the leading {@code /}.
+     *
+     * @param startPath the path to search under
+     * @param recursive {@code true} to recursively descend into subdirectories, {@code false} to only read this path
+     * @return the resource iterator (possibly empty)
+     */
+    Iterator<Resource> iterateResources(String startPath, boolean recursive);
+}
diff --git a/src/main/java/org/jboss/modules/IterableModuleFinder.java b/src/main/java/org/jboss/modules/IterableModuleFinder.java
new file mode 100644
index 0000000..e5aa9f3
--- /dev/null
+++ b/src/main/java/org/jboss/modules/IterableModuleFinder.java
@@ -0,0 +1,38 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.util.Iterator;
+
+/**
+ * A module finder which is iterable.
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public interface IterableModuleFinder extends ModuleFinder {
+
+    /**
+     * Iterate the modules which can be located via this module finder.
+     *
+     * @param baseIdentifier the identifier to start with, or {@code null} to iterate all modules
+     * @param recursive {@code true} to find recursively nested modules, {@code false} to only find immediately nested modules
+     * @return an iterator for the modules in this module finder
+     */
+    Iterator<ModuleIdentifier> iterateModules(ModuleIdentifier baseIdentifier, boolean recursive);
+}
diff --git a/src/main/java/org/jboss/modules/IterableResourceLoader.java b/src/main/java/org/jboss/modules/IterableResourceLoader.java
new file mode 100644
index 0000000..2390850
--- /dev/null
+++ b/src/main/java/org/jboss/modules/IterableResourceLoader.java
@@ -0,0 +1,40 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.util.Iterator;
+
+/**
+ * A resource loader which has the ability to enumerate its contents.
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public interface IterableResourceLoader extends ResourceLoader {
+
+    /**
+     * Enumerate all the resources under the given path.  The given path name is relative to the root
+     * of the resource loader.  If the path "escapes" the root via {@code ..}, such segments will be consumed.
+     * If the path is absolute, it will be converted to a relative path by dropping the leading {@code /}.
+     *
+     * @param startPath the path to search under
+     * @param recursive {@code true} to recursively descend into subdirectories, {@code false} to only read this path
+     * @return the resource iterator (possibly empty)
+     */
+    Iterator<Resource> iterateResources(String startPath, boolean recursive);
+}
diff --git a/src/main/java/org/jboss/modules/JDKPaths.java b/src/main/java/org/jboss/modules/JDKPaths.java
new file mode 100644
index 0000000..c25c84c
--- /dev/null
+++ b/src/main/java/org/jboss/modules/JDKPaths.java
@@ -0,0 +1,125 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.io.File;
+import java.io.IOException;
+import java.security.AccessController;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Set;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * A utility class which maintains the set of JDK paths.  Makes certain assumptions about the disposition of the
+ * class loader used to load JBoss Modules; thus this class should only be used when booted up via the "-jar" or "-cp"
+ * switches.
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+final class JDKPaths {
+    static final Set<String> JDK;
+
+    static {
+        final Set<String> pathSet = new FastCopyHashSet<String>(1024);
+        final Set<String> jarSet = new FastCopyHashSet<String>(1024);
+        final String sunBootClassPath = AccessController.doPrivileged(new PropertyReadAction("sun.boot.class.path"));
+        final String javaClassPath = AccessController.doPrivileged(new PropertyReadAction("java.class.path"));
+        processClassPathItem(sunBootClassPath, jarSet, pathSet);
+        processClassPathItem(javaClassPath, jarSet, pathSet);
+        pathSet.add("org/jboss/modules");
+        pathSet.add("org/jboss/modules/filter");
+        pathSet.add("org/jboss/modules/log");
+        pathSet.add("org/jboss/modules/management");
+        pathSet.add("org/jboss/modules/ref");
+        JDK = Collections.unmodifiableSet(pathSet);
+    }
+
+    private JDKPaths() {
+    }
+
+    private static void processClassPathItem(final String classPath, final Set<String> jarSet, final Set<String> pathSet) {
+        if (classPath == null) return;
+        int s = 0, e;
+        do {
+            e = classPath.indexOf(File.pathSeparatorChar, s);
+            String item = e == -1 ? classPath.substring(s) : classPath.substring(s, e);
+            if (! jarSet.contains(item)) {
+                final File file = new File(item);
+                if (file.isDirectory()) {
+                    processDirectory0(pathSet, file);
+                } else {
+                    try {
+                        processJar(pathSet, file);
+                    } catch (IOException ex) {
+                        // ignore
+                    }
+                }
+            }
+            s = e + 1;
+        } while (e != -1);
+    }
+
+    static void processJar(final Set<String> pathSet, final File file) throws IOException {
+        final ZipFile zipFile = new ZipFile(file);
+        try {
+            final Enumeration<? extends ZipEntry> entries = zipFile.entries();
+            while (entries.hasMoreElements()) {
+                final ZipEntry entry = entries.nextElement();
+                final String name = entry.getName();
+                final int lastSlash = name.lastIndexOf('/');
+                if (lastSlash != -1) {
+                    pathSet.add(name.substring(0, lastSlash));
+                }
+            }
+            zipFile.close();
+        } finally {
+            StreamUtil.safeClose(zipFile);
+        }
+    }
+
+    static void processDirectory0(final Set<String> pathSet, final File file) {
+        for (File entry : file.listFiles()) {
+            if (entry.isDirectory()) {
+                processDirectory1(pathSet, entry, file.getPath());
+            } else {
+                final String parent = entry.getParent();
+                if (parent != null) pathSet.add(parent);
+            }
+        }
+    }
+
+    static void processDirectory1(final Set<String> pathSet, final File file, final String pathBase) {
+        for (File entry : file.listFiles()) {
+            if (entry.isDirectory()) {
+                processDirectory1(pathSet, entry, pathBase);
+            } else {
+                String packagePath = entry.getParent();
+                if (packagePath != null) {
+                    packagePath = packagePath.substring(pathBase.length()).replace('\\', '/');;
+                    if(packagePath.startsWith("/")) {
+                        packagePath = packagePath.substring(1);
+                    }
+                    pathSet.add(packagePath);
+                }
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/jboss/modules/JarEntryResource.java b/src/main/java/org/jboss/modules/JarEntryResource.java
new file mode 100644
index 0000000..e0e442c
--- /dev/null
+++ b/src/main/java/org/jboss/modules/JarEntryResource.java
@@ -0,0 +1,58 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+/**
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+final class JarEntryResource implements Resource {
+    private final JarFile jarFile;
+    private final JarEntry entry;
+    private final URL resourceURL;
+
+    JarEntryResource(final JarFile jarFile, final JarEntry entry, final URL resourceURL) {
+        this.jarFile = jarFile;
+        this.entry = entry;
+        this.resourceURL = resourceURL;
+    }
+
+    public String getName() {
+        return entry.getName();
+    }
+
+    public URL getURL() {
+        return resourceURL;
+    }
+
+    public InputStream openStream() throws IOException {
+        return jarFile.getInputStream(entry);
+    }
+
+    public long getSize() {
+        final long size = entry.getSize();
+        return size == -1 ? 0 : size;
+    }
+}
diff --git a/src/main/java/org/jboss/modules/JarFileResourceLoader.java b/src/main/java/org/jboss/modules/JarFileResourceLoader.java
new file mode 100644
index 0000000..aede193
--- /dev/null
+++ b/src/main/java/org/jboss/modules/JarFileResourceLoader.java
@@ -0,0 +1,488 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+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.OutputStreamWriter;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLStreamHandler;
+import java.security.CodeSigner;
+import java.security.CodeSource;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.TreeSet;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+/**
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ * @author Thomas.Diesler at jboss.com
+ */
+final class JarFileResourceLoader extends AbstractResourceLoader implements IterableResourceLoader {
+    private static final String INDEX_FILE = "META-INF/PATHS.LIST";
+
+    private final JarFile jarFile;
+    private final String rootName;
+    private final URL rootUrl;
+    private final String relativePath;
+    private final File fileOfJar;
+
+    // protected by {@code this}
+    private final Map<CodeSigners, CodeSource> codeSources = new HashMap<>();
+
+    JarFileResourceLoader(final String rootName, final JarFile jarFile) {
+        this(rootName, jarFile, null);
+    }
+
+    JarFileResourceLoader(final String rootName, final JarFile jarFile, final String relativePath) {
+        if (jarFile == null) {
+            throw new IllegalArgumentException("jarFile is null");
+        }
+        if (rootName == null) {
+            throw new IllegalArgumentException("rootName is null");
+        }
+        fileOfJar = new File(jarFile.getName());
+        this.jarFile = jarFile;
+        this.rootName = rootName;
+        final String realPath = relativePath == null ? null : PathUtils.canonicalize(relativePath);
+        this.relativePath = realPath;
+        try {
+            rootUrl = getJarURI(fileOfJar.toURI(), realPath).toURL();
+        } catch (URISyntaxException e) {
+            throw new IllegalArgumentException("Invalid root file specified", e);
+        } catch (MalformedURLException e) {
+            throw new IllegalArgumentException("Invalid root file specified", e);
+        }
+    }
+
+    private static URI getJarURI(final URI original, final String nestedPath) throws URISyntaxException {
+        final StringBuilder b = new StringBuilder();
+        b.append("file:");
+        assert original.getScheme().equals("file");
+        final String path = original.getPath();
+        assert path != null;
+        final String host = original.getHost();
+        if (host != null) {
+            final String userInfo = original.getRawUserInfo();
+            b.append("//");
+            if (userInfo != null) {
+                b.append(userInfo).append('@');
+            }
+            b.append(host);
+        }
+        b.append(path).append("!/");
+        if (nestedPath != null) {
+            b.append(nestedPath);
+        }
+        return new URI("jar", b.toString(), null);
+    }
+
+    public String getRootName() {
+        return rootName;
+    }
+
+    public synchronized ClassSpec getClassSpec(final String fileName) throws IOException {
+        final ClassSpec spec = new ClassSpec();
+        final JarEntry entry = getJarEntry(fileName);
+        if (entry == null) {
+            // no such entry
+            return null;
+        }
+        final long size = entry.getSize();
+        final InputStream is = jarFile.getInputStream(entry);
+        try {
+            if (size == -1) {
+                // size unknown
+                final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                final byte[] buf = new byte[16384];
+                int res;
+                while ((res = is.read(buf)) > 0) {
+                    baos.write(buf, 0, res);
+                }
+                // done
+                CodeSource codeSource = createCodeSource(entry);
+                baos.close();
+                is.close();
+                spec.setBytes(baos.toByteArray());
+                spec.setCodeSource(codeSource);
+                return spec;
+            } else if (size <= (long) Integer.MAX_VALUE) {
+                final int castSize = (int) size;
+                byte[] bytes = new byte[castSize];
+                int a = 0, res;
+                while ((res = is.read(bytes, a, castSize - a)) > 0) {
+                    a += res;
+                }
+                // consume remainder so that cert check doesn't fail in case of wonky JARs
+                while (is.read() != -1);
+                // done
+                CodeSource codeSource = createCodeSource(entry);
+                is.close();
+                spec.setBytes(bytes);
+                spec.setCodeSource(codeSource);
+                return spec;
+            } else {
+                throw new IOException("Resource is too large to be a valid class file");
+            }
+        } finally {
+            StreamUtil.safeClose(is);
+        }
+    }
+
+    // this MUST only be called after the input stream is fully read (see MODULES-201)
+    private CodeSource createCodeSource(final JarEntry entry) {
+        final CodeSigner[] entryCodeSigners = entry.getCodeSigners();
+        final CodeSigners codeSigners = entryCodeSigners == null || entryCodeSigners.length == 0 ? EMPTY_CODE_SIGNERS : new CodeSigners(entryCodeSigners);
+        CodeSource codeSource = codeSources.get(codeSigners);
+        if (codeSource == null) {
+            codeSources.put(codeSigners, codeSource = new CodeSource(rootUrl, entryCodeSigners));
+        }
+        return codeSource;
+    }
+
+    private JarEntry getJarEntry(final String fileName) {
+        return relativePath == null ? jarFile.getJarEntry(fileName) : jarFile.getJarEntry(relativePath + "/" + fileName);
+    }
+
+    public PackageSpec getPackageSpec(final String name) throws IOException {
+        final Manifest manifest;
+        if (relativePath == null) {
+            manifest = jarFile.getManifest();
+        } else {
+            JarEntry jarEntry = getJarEntry("META-INF/MANIFEST.MF");
+            if (jarEntry == null) {
+                manifest = null;
+            } else {
+                InputStream inputStream = jarFile.getInputStream(jarEntry);
+                try {
+                    manifest = new Manifest(inputStream);
+                } finally {
+                    StreamUtil.safeClose(inputStream);
+                }
+            }
+        }
+        return getPackageSpec(name, manifest, rootUrl);
+    }
+
+    public String getLibrary(final String name) {
+        // JARs cannot have libraries in them
+        return null;
+    }
+
+    public Resource getResource(String name) {
+        try {
+            final JarFile jarFile = this.jarFile;
+            name = PathUtils.canonicalize(PathUtils.relativize(name));
+            final JarEntry entry = getJarEntry(name);
+            if (entry == null) {
+                return null;
+            }
+            final URI uri;
+            try {
+                File absoluteFile = new File(jarFile.getName()).getAbsoluteFile();
+                String path = absoluteFile.getPath();
+                path = PathUtils.canonicalize(path);
+                if (File.separatorChar != '/') {
+                    // optimizes away on platforms with /
+                    path = path.replace(File.separatorChar, '/');
+                }
+                if (PathUtils.isRelative(path)) {
+                    // should not be possible, but the JDK thinks this might happen sometimes..?
+                    path = "/" + path;
+                }
+                if (path.startsWith("//")) {
+                    // UNC path URIs have loads of leading slashes
+                    path = "//" + path;
+                }
+                uri = new URI("file", null, path, null);
+            } catch (URISyntaxException x) {
+                throw new IllegalStateException(x);
+            }
+            return new JarEntryResource(jarFile, entry, new URL(null, getJarURI(uri, entry.getName()).toString(), (URLStreamHandler) null));
+        } catch (MalformedURLException e) {
+            // must be invalid...?  (todo: check this out)
+            return null;
+        } catch (URISyntaxException e) {
+            // must be invalid...?  (todo: check this out)
+            return null;
+        }
+    }
+
+    public Iterator<Resource> iterateResources(final String startPath, final boolean recursive) {
+        final JarFile jarFile = this.jarFile;
+        final String startName = PathUtils.canonicalize(PathUtils.relativize(startPath));
+        final Enumeration<JarEntry> entries = jarFile.entries();
+        return new Iterator<Resource>() {
+            private Resource next;
+
+            public boolean hasNext() {
+                while (next == null) {
+                    if (! entries.hasMoreElements()) {
+                        return false;
+                    }
+                    final JarEntry entry = entries.nextElement();
+                    final String name = entry.getName();
+                    if ((recursive ? PathUtils.isChild(startName, name) : PathUtils.isDirectChild(startName, name))) {
+                        if (!entry.isDirectory()) {
+                            try {
+                                next = new JarEntryResource(jarFile, entry, getJarURI(new File(jarFile.getName()).toURI(), entry.getName()).toURL());
+                            } catch (Exception ignored) {
+                            }
+                        }
+                    }
+                }
+                return true;
+            }
+
+            public Resource next() {
+                if (! hasNext()) {
+                    throw new NoSuchElementException();
+                }
+                try {
+                    return next;
+                } finally {
+                    next = null;
+                }
+            }
+
+            public void remove() {
+                throw new UnsupportedOperationException();
+            }
+        };
+    }
+
+    public Collection<String> getPaths() {
+        final Collection<String> index = new HashSet<String>();
+        index.add("");
+        String relativePath = this.relativePath;
+        // First check for an external index
+        final JarFile jarFile = this.jarFile;
+        final String jarFileName = jarFile.getName();
+        final long jarModified = fileOfJar.lastModified();
+        final File indexFile = new File(jarFileName + ".index");
+        if (ResourceLoaders.USE_INDEXES) {
+            if (indexFile.exists()) {
+                final long indexModified = indexFile.lastModified();
+                if (indexModified != 0L && jarModified != 0L && indexModified >= jarModified) try {
+                    return readIndex(new FileInputStream(indexFile), index, relativePath);
+                } catch (IOException e) {
+                    index.clear();
+                }
+            }
+        }
+        // Next check for an internal index
+        JarEntry listEntry = jarFile.getJarEntry(INDEX_FILE);
+        if (listEntry != null) {
+            try {
+                return readIndex(jarFile.getInputStream(listEntry), index, relativePath);
+            } catch (IOException e) {
+                index.clear();
+            }
+        }
+        // Next just read the JAR
+        extractJarPaths(jarFile, relativePath, index);
+
+        if (ResourceLoaders.WRITE_INDEXES && relativePath == null) {
+            writeExternalIndex(indexFile, index);
+        }
+        return index;
+    }
+
+    static void extractJarPaths(final JarFile jarFile, String relativePath,
+            final Collection<String> index) {
+        index.add("");
+        final Enumeration<JarEntry> entries = jarFile.entries();
+        while (entries.hasMoreElements()) {
+            final JarEntry jarEntry = entries.nextElement();
+            final String name = jarEntry.getName();
+            final int idx = name.lastIndexOf('/');
+            if (idx == -1) continue;
+            final String path = name.substring(0, idx);
+            if (path.length() == 0 || path.endsWith("/")) {
+                // invalid name, just skip...
+                continue;
+            }
+            if (relativePath == null) {
+                index.add(path);
+            } else {
+                if (path.startsWith(relativePath + "/")) {
+                    index.add(path.substring(relativePath.length() + 1));
+                }
+            }
+        }
+    }
+
+    static void writeExternalIndex(final File indexFile,
+            final Collection<String> index) {
+        // Now try to write it
+        boolean ok = false;
+        try {
+            final BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(indexFile)));
+            try {
+                for (String name : index) {
+                    writer.write(name);
+                    writer.write('\n');
+                }
+                writer.close();
+                ok = true;
+            } finally {
+                StreamUtil.safeClose(writer);
+            }
+        } catch (IOException e) {
+            // failed, ignore
+        } finally {
+            if (! ok) {
+                // well, we tried...
+                indexFile.delete();
+            }
+        }
+    }
+
+    static Collection<String> readIndex(final InputStream stream, final Collection<String> index, final String relativePath) throws IOException {
+        final BufferedReader r = new BufferedReader(new InputStreamReader(stream));
+        try {
+            String s;
+            while ((s = r.readLine()) != null) {
+                String name = s.trim();
+                if (relativePath == null) {
+                    index.add(name);
+                } else {
+                    if (name.startsWith(relativePath + "/")) {
+                        index.add(name.substring(relativePath.length() + 1));
+                    }
+                }
+            }
+            return index;
+        } finally {
+            // if exception is thrown, undo index creation
+            r.close();
+        }
+    }
+
+    static void addInternalIndex(File file, boolean modify) throws IOException {
+        final JarFile oldJarFile = new JarFile(file, false);
+        try {
+            final Collection<String> index = new TreeSet<String>();
+            final File outputFile;
+
+            outputFile = new File(file.getAbsolutePath().replace(".jar", "-indexed.jar"));
+
+            final ZipOutputStream zo = new ZipOutputStream(new FileOutputStream(outputFile));
+            try {
+                Enumeration<JarEntry> entries = oldJarFile.entries();
+                while (entries.hasMoreElements()) {
+                    final JarEntry entry = entries.nextElement();
+
+                    // copy data, unless we're replacing the index
+                    if (!entry.getName().equals(INDEX_FILE)) {
+                        final JarEntry clone = (JarEntry) entry.clone();
+                        // Compression level and format can vary across implementations
+                        if (clone.getMethod() != ZipEntry.STORED)
+                            clone.setCompressedSize(-1);
+                        zo.putNextEntry(clone);
+                        StreamUtil.copy(oldJarFile.getInputStream(entry), zo);
+                    }
+
+                    // add to the index
+                    final String name = entry.getName();
+                    final int idx = name.lastIndexOf('/');
+                    if (idx == -1) continue;
+                    final String path = name.substring(0, idx);
+                    if (path.length() == 0 || path.endsWith("/")) {
+                        // invalid name, just skip...
+                        continue;
+                    }
+                    index.add(path);
+                }
+
+                // write index
+                zo.putNextEntry(new ZipEntry(INDEX_FILE));
+                final BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(zo));
+                try {
+                    for (String name : index) {
+                        writer.write(name);
+                        writer.write('\n');
+                    }
+                    writer.close();
+                } finally {
+                    StreamUtil.safeClose(writer);
+                }
+                zo.close();
+                oldJarFile.close();
+
+                if (modify) {
+                    file.delete();
+                    if (!outputFile.renameTo(file)) {
+                        throw new IOException("failed to rename " + outputFile.getAbsolutePath() + " to " + file.getAbsolutePath());
+                    }
+                }
+            } finally {
+                StreamUtil.safeClose(zo);
+            }
+        } finally {
+            StreamUtil.safeClose(oldJarFile);
+        }
+    }
+
+    private static final CodeSigners EMPTY_CODE_SIGNERS = new CodeSigners(new CodeSigner[0]);
+
+    static final class CodeSigners {
+
+        private final CodeSigner[] codeSigners;
+        private final int hashCode;
+
+        public CodeSigners(final CodeSigner[] codeSigners) {
+            this.codeSigners = codeSigners;
+            hashCode = Arrays.hashCode(codeSigners);
+        }
+
+        public boolean equals(final Object obj) {
+            return obj instanceof CodeSigners && equals((CodeSigners) obj);
+        }
+
+        private boolean equals(final CodeSigners other) {
+            return Arrays.equals(codeSigners, other.codeSigners);
+        }
+
+        public int hashCode() {
+            return hashCode;
+        }
+    }
+}
diff --git a/src/main/java/org/jboss/modules/JarModuleFinder.java b/src/main/java/org/jboss/modules/JarModuleFinder.java
new file mode 100755
index 0000000..bd6641c
--- /dev/null
+++ b/src/main/java/org/jboss/modules/JarModuleFinder.java
@@ -0,0 +1,153 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.util.jar.Attributes;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+import org.jboss.modules.filter.MultiplePathFilterBuilder;
+import org.jboss.modules.filter.PathFilters;
+
+/**
+ * A module finder which uses a JAR file as a module repository.
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public final class JarModuleFinder implements ModuleFinder {
+    private final ModuleIdentifier myIdentifier;
+    private final JarFile jarFile;
+    private final AccessControlContext context;
+
+    /**
+     * Construct a new instance.
+     *
+     * @param myIdentifier the identifier to use for the JAR itself
+     * @param jarFile the JAR file to encapsulate
+     */
+    public JarModuleFinder(final ModuleIdentifier myIdentifier, final JarFile jarFile) {
+        this.myIdentifier = myIdentifier;
+        this.jarFile = jarFile;
+        context = AccessController.getContext();
+    }
+
+    public ModuleSpec findModule(final ModuleIdentifier identifier, final ModuleLoader delegateLoader) throws ModuleLoadException {
+        if (identifier.equals(myIdentifier)) {
+            // special root JAR module
+            Manifest manifest;
+            try {
+                manifest = jarFile.getManifest();
+            } catch (IOException e) {
+                throw new ModuleLoadException("Failed to load MANIFEST from JAR", e);
+            }
+            ModuleSpec.Builder builder = ModuleSpec.build(identifier);
+            Attributes mainAttributes = manifest.getMainAttributes();
+            String mainClass = mainAttributes.getValue(Attributes.Name.MAIN_CLASS);
+            if (mainClass != null) {
+                builder.setMainClass(mainClass);
+            }
+            String classPath = mainAttributes.getValue(Attributes.Name.CLASS_PATH);
+            String dependencies = mainAttributes.getValue("Dependencies");
+            MultiplePathFilterBuilder pathFilterBuilder = PathFilters.multiplePathFilterBuilder(true);
+            pathFilterBuilder.addFilter(PathFilters.is("modules"), false);
+            pathFilterBuilder.addFilter(PathFilters.isChildOf("modules"), false);
+            builder.addResourceRoot(ResourceLoaderSpec.createResourceLoaderSpec(new JarFileResourceLoader("", jarFile), pathFilterBuilder.create()));
+            String[] classPathEntries = classPath == null ? JarModuleLoader.NO_STRINGS : classPath.split("\\s+");
+            for (String entry : classPathEntries) {
+                if (! entry.isEmpty()) {
+                    if (entry.startsWith("../") || entry.startsWith("./") || entry.startsWith("/") || entry.contains("/../")) {
+                        // invalid
+                        continue;
+                    }
+                    if (entry.endsWith("/")) {
+                        // directory reference
+                        File root = new File(jarFile.getName(), entry);
+                        FileResourceLoader resourceLoader = new FileResourceLoader(entry, root, context);
+                        builder.addResourceRoot(ResourceLoaderSpec.createResourceLoaderSpec(resourceLoader));
+                    } else {
+                        // assume a JAR
+                        File root = new File(jarFile.getName(), entry);
+                        JarFile childJarFile;
+                        try {
+                            childJarFile = new JarFile(root, true);
+                        } catch (IOException e) {
+                            // ignore and continue
+                            continue;
+                        }
+                        builder.addResourceRoot(ResourceLoaderSpec.createResourceLoaderSpec(new JarFileResourceLoader(entry, childJarFile)));
+                    }
+                }
+            }
+            String[] dependencyEntries = dependencies == null ? JarModuleLoader.NO_STRINGS : dependencies.split("\\s*,\\s*");
+            for (String dependencyEntry : dependencyEntries) {
+                boolean optional = false;
+                boolean export = false;
+                dependencyEntry = dependencyEntry.trim();
+                if (! dependencyEntry.isEmpty()) {
+                    String[] fields = dependencyEntry.split("\\s+");
+                    if (fields.length < 1) {
+                        continue;
+                    }
+                    String moduleName = fields[0];
+                    for (int i = 1; i < fields.length; i++) {
+                        String field = fields[i];
+                        if (field.equals("optional")) {
+                            optional = true;
+                        } else if (field.equals("export")) {
+                            export = true;
+                        }
+                        // else ignored
+                    }
+                    builder.addDependency(DependencySpec.createModuleDependencySpec(ModuleIdentifier.fromString(moduleName), export, optional));
+                }
+            }
+            builder.addDependency(DependencySpec.createSystemDependencySpec(JDKPaths.JDK));
+            builder.addDependency(DependencySpec.createLocalDependencySpec());
+            return builder.create();
+        } else {
+            String namePath = identifier.getName().replace('.', '/');
+            String basePath = "modules/" + namePath + "/" + identifier.getSlot();
+            JarEntry moduleXmlEntry = jarFile.getJarEntry(basePath + "/module.xml");
+            if (moduleXmlEntry == null) {
+                return null;
+            }
+            ModuleSpec moduleSpec;
+            try {
+                InputStream inputStream = jarFile.getInputStream(moduleXmlEntry);
+                try {
+                    moduleSpec = ModuleXmlParser.parseModuleXml(new ModuleXmlParser.ResourceRootFactory() {
+                       public ResourceLoader createResourceLoader(final String rootPath, final String loaderPath, final String loaderName) throws IOException {
+                            return new JarFileResourceLoader(loaderName, jarFile, loaderPath);
+                        }
+                    }, basePath, inputStream, moduleXmlEntry.getName(), delegateLoader, identifier);
+                } finally {
+                    StreamUtil.safeClose(inputStream);
+                }
+            } catch (IOException e) {
+                throw new ModuleLoadException("Failed to read module.xml file", e);
+            }
+            return moduleSpec;
+        }
+    }
+}
diff --git a/src/main/java/org/jboss/modules/JarModuleLoader.java b/src/main/java/org/jboss/modules/JarModuleLoader.java
new file mode 100644
index 0000000..ef43078
--- /dev/null
+++ b/src/main/java/org/jboss/modules/JarModuleLoader.java
@@ -0,0 +1,67 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.io.File;
+import java.util.jar.JarFile;
+
+/**
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+final class JarModuleLoader extends ModuleLoader {
+
+    static final String[] NO_STRINGS = new String[0];
+    private final ModuleLoader delegate;
+    private final JarFile jarFile;
+    private final ModuleIdentifier myIdentifier;
+
+    JarModuleLoader(final ModuleLoader delegate, final JarFile jarFile) {
+        super(new ModuleFinder[] { new JarModuleFinder(simpleNameOf(jarFile), jarFile) });
+        this.delegate = delegate;
+        this.jarFile = jarFile;
+        myIdentifier = simpleNameOf(jarFile);
+    }
+
+    private static ModuleIdentifier simpleNameOf(JarFile jarFile) {
+        String jarName = jarFile.getName();
+        String simpleJarName = jarName.substring(jarName.lastIndexOf(File.separatorChar) + 1);
+        return ModuleIdentifier.create(simpleJarName);
+    }
+
+    protected Module preloadModule(final ModuleIdentifier identifier) throws ModuleLoadException {
+        if (identifier.equals(myIdentifier)) {
+            return loadModuleLocal(identifier);
+        } else {
+            Module module = loadModuleLocal(identifier);
+            if (module == null) {
+                return preloadModule(identifier, delegate);
+            } else {
+                return module;
+            }
+        }
+    }
+
+    ModuleIdentifier getMyIdentifier() {
+        return myIdentifier;
+    }
+
+    public String toString() {
+        return "JAR module loader";
+    }
+}
diff --git a/src/main/java/org/jboss/modules/LayeredModulePathFactory.java b/src/main/java/org/jboss/modules/LayeredModulePathFactory.java
new file mode 100644
index 0000000..bc32f4e
--- /dev/null
+++ b/src/main/java/org/jboss/modules/LayeredModulePathFactory.java
@@ -0,0 +1,263 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Properties;
+
+/**
+ * Provides a module path that includes entries for any "layer" and "add-on" directory structures found
+ * within the regular items in the provided module path.
+ *
+ * @author Brian Stansberry (c) 2012 Red Hat Inc.
+ */
+class LayeredModulePathFactory {
+
+    /**
+     * Inspects each element in the given {@code modulePath} to see if it includes a {@code layers.conf} file
+     * and/or a standard directory structure with child directories {@code system/layers} and, optionally,
+     * {@code system/add-ons}. If so, the layers identified in {@code layers.conf} are added to the module path
+     *
+     * @param modulePath the filesystem locations that make up the standard module path, each of which is to be
+     *                   checked for the presence of layers and add-ons
+     *
+     * @return a new module path, including any layers and add-ons, if found
+     */
+    static File[] resolveLayeredModulePath(File... modulePath) {
+
+        boolean foundLayers = false;
+        List<File> layeredPath = new ArrayList<File>();
+        for (File file : modulePath) {
+
+            // Always add the root, as the user may place modules directly in it
+            layeredPath.add(file);
+
+            LayersConfig layersConfig = getLayersConfig(file);
+
+            File layersDir = new File(file, layersConfig.getLayersPath());
+            if (!layersDir.exists())  {
+                if (layersConfig.isConfigured()) {
+                    // Bad config from user
+                    throw new IllegalStateException("No layers directory found at " + layersDir);
+                }
+                // else this isn't a root that has layers and add-ons
+                continue;
+            }
+
+            boolean validLayers = true;
+            List<File> layerFiles = new ArrayList<File>();
+            for (String layerName : layersConfig.getLayers()) {
+                File layer = new File(layersDir, layerName);
+                if (!layer.exists()) {
+                    if (layersConfig.isConfigured()) {
+                        // Bad config from user
+                        throw new IllegalStateException(String.format("Cannot find layer %s under directory %s", layerName, layersDir));
+                    }
+                    // else this isn't a standard layers and add-ons structure
+                    validLayers = false;
+                    break;
+                }
+                loadOverlays(layer, layerFiles);
+            }
+            if (validLayers) {
+                foundLayers = true;
+                layeredPath.addAll(layerFiles);
+                // Now add-ons
+                File[] addOns = new File(file, layersConfig.getAddOnsPath()).listFiles();
+                if (addOns != null) {
+                    for (File addOn : addOns) {
+                        if (addOn.isDirectory()) {
+                            loadOverlays(addOn, layeredPath);
+                        }
+                    }
+                }
+            }
+        }
+
+        return foundLayers ? layeredPath.toArray(new File[layeredPath.size()]) : modulePath;
+    }
+
+    private static LayersConfig getLayersConfig(File repoRoot) {
+        File layersList = new File(repoRoot, "layers.conf");
+        if (!layersList.exists()) {
+            return new LayersConfig();
+        }
+        Reader reader = null;
+        try {
+            reader = new InputStreamReader(new FileInputStream(layersList), "UTF-8");
+            Properties props = new Properties();
+            props.load(reader);
+
+            return new LayersConfig(props);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        } finally {
+            StreamUtil.safeClose(reader);
+        }
+    }
+
+    private static class LayersConfig {
+
+        private static final String DEFAULT_LAYERS_PATH = "system/layers";
+        private static final String DEFAULT_ADD_ONS_PATH = "system/add-ons";
+
+        private final boolean configured;
+        private final String layersPath;
+        private final String addOnsPath;
+        private final List<String> layers;
+
+        private LayersConfig() {
+            configured = false;
+            layersPath = DEFAULT_LAYERS_PATH;
+            addOnsPath = DEFAULT_ADD_ONS_PATH;
+            layers = Collections.singletonList("base");
+        }
+
+        private LayersConfig(Properties properties) {
+            configured = true;
+            // Possible future enhancement; probably better to use an xml file
+//            layersPath = properties.getProperty("layers.path", DEFAULT_LAYERS_PATH);
+//            addOnsPath = properties.getProperty("add-ons.path", DEFAULT_ADD_ONS_PATH);
+//            boolean excludeBase = Boolean.valueOf(properties.getProperty("exclude.base.layer", "false"));
+            layersPath = DEFAULT_LAYERS_PATH;
+            addOnsPath = DEFAULT_ADD_ONS_PATH;
+            boolean excludeBase = false;
+            String layersProp = (String) properties.get("layers");
+            if (layersProp == null || (layersProp = layersProp.trim()).length() == 0) {
+                if (excludeBase) {
+                    layers = Collections.emptyList();
+                } else {
+                    layers = Collections.singletonList("base");
+                }
+            } else {
+                String[] layerNames = layersProp.split(",");
+                layers = new ArrayList<String>();
+                boolean hasBase = false;
+                for (String layerName : layerNames) {
+                    if ("base".equals(layerName)) {
+                        hasBase = true;
+                    }
+                    layers.add(layerName);
+                }
+                if (!hasBase && !excludeBase) {
+                    layers.add("base");
+                }
+            }
+        }
+
+        boolean isConfigured() {
+            return configured;
+        }
+
+
+        String getLayersPath() {
+            return layersPath;
+        }
+
+        String getAddOnsPath() {
+            return addOnsPath;
+        }
+
+        List<String> getLayers() {
+            return layers;
+        }
+    }
+
+    private static final String OVERLAYS = ".overlays";
+
+    /**
+     * Load the overlays for each layer.
+     *
+     * @param layeringRoot the layer root
+     * @param path the module path
+     */
+    static void loadOverlays(final File layeringRoot, final List<File> path) {
+
+        final File overlays = new File(layeringRoot, OVERLAYS);
+        if (overlays.exists()) {
+            final File refs = new File(overlays, OVERLAYS);
+            if (refs.exists()) {
+                try {
+                    for (final String overlay : readRefs(refs)) {
+                        final File root = new File(overlays, overlay);
+                        path.add(root);
+                    }
+                } catch (IOException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        }
+        path.add(layeringRoot);
+    }
+
+    public static List<String> readRefs(final File file) throws IOException {
+        if(! file.exists()) {
+            return Collections.emptyList();
+        }
+        final InputStream is = new FileInputStream(file);
+        try {
+            return readRefs(is);
+        } finally {
+            if (is != null) try {
+                is.close();
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    static List<String> readRefs(final InputStream is) throws IOException {
+        final List<String> refs = new ArrayList<String>();
+        final StringBuffer buffer = new StringBuffer();
+        do {
+            if(buffer.length() > 0) {
+                final String ref = buffer.toString().trim();
+                if(ref.length() > 0) {
+                    refs.add(ref);
+                }
+            }
+        } while(readLine(is, buffer));
+        return refs;
+    }
+
+    static boolean readLine(InputStream is, StringBuffer buffer) throws IOException {
+        buffer.setLength(0);
+        int c;
+        for(;;) {
+            c = is.read();
+            switch(c) {
+                case '\t':
+                case '\r':
+                    break;
+                case -1: return false;
+                case '\n': return true;
+                default: buffer.append((char) c);
+            }
+        }
+    }
+
+}
diff --git a/src/main/java/org/jboss/modules/Linkage.java b/src/main/java/org/jboss/modules/Linkage.java
new file mode 100644
index 0000000..21e384d
--- /dev/null
+++ b/src/main/java/org/jboss/modules/Linkage.java
@@ -0,0 +1,81 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * The linkage state of a module.
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+final class Linkage {
+
+    private static final Dependency[] NO_DEPENDENCIES = new Dependency[0];
+    private static final DependencySpec[] NO_DEPENDENCY_SPECS = new DependencySpec[0];
+
+    enum State {
+        NEW,
+        UNLINKED,
+        LINKING,
+        LINKED,
+        ;
+    }
+
+    private final DependencySpec[] dependencySpecs;
+    private final Dependency[] dependencies;
+
+    private final State state;
+    private final Map<String, List<LocalLoader>> allPaths;
+
+    Linkage(final State state) {
+        this(NO_DEPENDENCY_SPECS, NO_DEPENDENCIES, state, Collections.<String, List<LocalLoader>>emptyMap());
+    }
+
+    Linkage(final DependencySpec[] dependencySpecs, final Dependency[] dependencies, final State state) {
+        this(dependencySpecs, dependencies, state, Collections.<String, List<LocalLoader>>emptyMap());
+    }
+
+    Linkage(final DependencySpec[] dependencySpecs, final Dependency[] dependencies, final State state, final Map<String, List<LocalLoader>> allPaths) {
+        this.dependencySpecs = dependencySpecs;
+        this.dependencies = dependencies;
+        this.state = state;
+        this.allPaths = allPaths;
+    }
+
+    Map<String, List<LocalLoader>> getPaths() {
+        return allPaths;
+    }
+
+    State getState() {
+        return state;
+    }
+
+    Dependency[] getDependencies() {
+        return dependencies;
+    }
+
+    DependencySpec[] getDependencySpecs() {
+        return dependencySpecs;
+    }
+
+    static final Linkage NONE = new Linkage(State.NEW);
+}
diff --git a/src/main/java/org/jboss/modules/LocalDependency.java b/src/main/java/org/jboss/modules/LocalDependency.java
new file mode 100644
index 0000000..7c14000
--- /dev/null
+++ b/src/main/java/org/jboss/modules/LocalDependency.java
@@ -0,0 +1,49 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.util.Set;
+import org.jboss.modules.filter.ClassFilter;
+import org.jboss.modules.filter.PathFilter;
+
+/**
+* @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+*/
+final class LocalDependency extends Dependency {
+    private final LocalLoader localLoader;
+    private final Set<String> paths;
+
+    LocalDependency(final PathFilter exportFilter, final PathFilter importFilter, final PathFilter resourceExportFilter, final PathFilter resourceImportFilter, final ClassFilter classExportFilter, final ClassFilter classImportFilter, final LocalLoader localLoader, final Set<String> paths) {
+        super(exportFilter, importFilter, resourceExportFilter, resourceImportFilter, classExportFilter, classImportFilter);
+        this.localLoader = localLoader;
+        this.paths = paths;
+    }
+
+    LocalLoader getLocalLoader() {
+        return localLoader;
+    }
+
+    Set<String> getPaths() {
+        return paths;
+    }
+
+    public String toString() {
+        return "dependency on " + localLoader;
+    }
+}
diff --git a/src/main/java/org/jboss/modules/LocalLoader.java b/src/main/java/org/jboss/modules/LocalLoader.java
new file mode 100644
index 0000000..508d651
--- /dev/null
+++ b/src/main/java/org/jboss/modules/LocalLoader.java
@@ -0,0 +1,59 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.util.List;
+
+/**
+ * A loader which implements the local part of a module.
+ * <p>
+ * <b>Thread safety warning!</b>  The loader must <b>never</b> call into a class loader (or any other object) which may
+ * take locks and subsequently delegate to a module class loader.  This will cause deadlocks and other hard-to-debug
+ * concurrency problems.
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public interface LocalLoader {
+
+    /**
+     * Load a class which is locally defined by this loader.
+     *
+     * @param name the class name
+     * @param resolve {@code true} to resolve the class
+     * @return the class, or {@code null} if there is no local class with this name
+     */
+    Class<?> loadClassLocal(String name, boolean resolve);
+
+    /**
+     * Load a package which is locally defined by this loader.
+     *
+     * @param name the package name
+     * @return the package, or {@code null} if there is no local package with this name
+     */
+    Package loadPackageLocal(String name);
+
+    /**
+     * Load a resource which is locally defined by this loader.  The given name is a path separated
+     * by "{@code /}" characters.
+     *
+     * @param name the resource path
+     * @return the resource or resources, or an empty list if there is no local resource with this name
+     */
+    List<Resource> loadResourceLocal(String name);
+}
diff --git a/src/main/java/org/jboss/modules/LocalLoaders.java b/src/main/java/org/jboss/modules/LocalLoaders.java
new file mode 100644
index 0000000..17367e4
--- /dev/null
+++ b/src/main/java/org/jboss/modules/LocalLoaders.java
@@ -0,0 +1,105 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import org.jboss.modules.filter.ClassFilter;
+import org.jboss.modules.filter.ClassFilters;
+import org.jboss.modules.filter.PathFilter;
+import org.jboss.modules.filter.PathFilters;
+
+/**
+ * Static factory methods for various types of local loaders.
+ *
+ * @apiviz.exclude
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public final class LocalLoaders {
+
+    private LocalLoaders() {
+    }
+
+    /**
+     * Create a filtered local loader.
+     *
+     * @param pathFilter the path filter to apply to resources
+     * @param originalLoader the original loader
+     * @return the filtered loader
+     */
+    public static LocalLoader createPathFilteredLocalLoader(final PathFilter pathFilter, final LocalLoader originalLoader) {
+        return new FilteredLocalLoader(ClassFilters.acceptAll(), pathFilter, originalLoader);
+    }
+
+    /**
+     * Create a filtered local loader.
+     *
+     * @param pathFilter the path filter to apply to resources
+     * @param originalLoader the original loader
+     * @return the filtered loader
+     */
+    public static IterableLocalLoader createIterablePathFilteredLocalLoader(final PathFilter pathFilter, final IterableLocalLoader originalLoader) {
+        return new FilteredIterableLocalLoader(ClassFilters.acceptAll(), pathFilter, originalLoader);
+    }
+
+    /**
+     * Create a filtered local loader.
+     *
+     * @param classFilter the class filter to apply to classes
+     * @param originalLoader the original loader
+     * @return the filtered loader
+     */
+    public static LocalLoader createClassFilteredLocalLoader(final ClassFilter classFilter, final LocalLoader originalLoader) {
+        return new FilteredLocalLoader(classFilter, PathFilters.acceptAll(), originalLoader);
+    }
+
+    /**
+     * Create a filtered local loader.
+     *
+     * @param classFilter the class filter to apply to classes
+     * @param originalLoader the original loader
+     * @return the filtered loader
+     */
+    public static IterableLocalLoader createIterableClassFilteredLocalLoader(final ClassFilter classFilter, final IterableLocalLoader originalLoader) {
+        return new FilteredIterableLocalLoader(classFilter, PathFilters.acceptAll(), originalLoader);
+    }
+
+    /**
+     * Create a filtered local loader.
+     *
+     * @param classFilter the class filter to apply to classes
+     * @param resourcePathFilter the path filter to apply to resources
+     * @param originalLoader the original loader
+     * @return the filtered loader
+     */
+    public static LocalLoader createFilteredLocalLoader(final ClassFilter classFilter, final PathFilter resourcePathFilter, final LocalLoader originalLoader) {
+        return new FilteredLocalLoader(classFilter, resourcePathFilter, originalLoader);
+    }
+
+    /**
+     * Create a filtered local loader.
+     *
+     * @param classFilter the class filter to apply to classes
+     * @param resourcePathFilter the path filter to apply to resources
+     * @param originalLoader the original loader
+     * @return the filtered loader
+     */
+    public static IterableLocalLoader createIterableFilteredLocalLoader(final ClassFilter classFilter, final PathFilter resourcePathFilter, final IterableLocalLoader originalLoader) {
+        return new FilteredIterableLocalLoader(classFilter, resourcePathFilter, originalLoader);
+    }
+}
diff --git a/src/main/java/org/jboss/modules/LocalModuleFinder.java b/src/main/java/org/jboss/modules/LocalModuleFinder.java
new file mode 100644
index 0000000..0e63ad1
--- /dev/null
+++ b/src/main/java/org/jboss/modules/LocalModuleFinder.java
@@ -0,0 +1,213 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.io.File;
+import java.io.FilePermission;
+import java.io.IOException;
+import java.lang.reflect.UndeclaredThrowableException;
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import org.jboss.modules.filter.PathFilter;
+import org.jboss.modules.filter.PathFilters;
+
+import static java.security.AccessController.doPrivileged;
+import static java.security.AccessController.getContext;
+
+/**
+ * A module finder which locates module specifications which are stored in a local module
+ * repository on the filesystem, which uses {@code module.xml} descriptors.
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public final class LocalModuleFinder implements ModuleFinder {
+
+    private static final File[] NO_FILES = new File[0];
+
+    private final File[] repoRoots;
+    private final PathFilter pathFilter;
+    private final AccessControlContext accessControlContext;
+
+    private LocalModuleFinder(final File[] repoRoots, final PathFilter pathFilter, final boolean cloneRoots) {
+        this.repoRoots = cloneRoots && repoRoots.length > 0 ? repoRoots.clone() : repoRoots;
+        final SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            for (File repoRoot : this.repoRoots) {
+                if (repoRoot == null) sm.checkPermission(new FilePermission(new File(repoRoot, "-").getPath(), "read"));
+            }
+        }
+        this.pathFilter = pathFilter;
+        this.accessControlContext = AccessController.getContext();
+    }
+
+    /**
+     * Construct a new instance.
+     *
+     * @param repoRoots the repository roots to use
+     * @param pathFilter the path filter to use
+     */
+    public LocalModuleFinder(final File[] repoRoots, final PathFilter pathFilter) {
+        this(repoRoots, pathFilter, true);
+    }
+
+    /**
+     * Construct a new instance.
+     *
+     * @param repoRoots the repository roots to use
+     */
+    public LocalModuleFinder(final File[] repoRoots) {
+        this(repoRoots, PathFilters.acceptAll());
+    }
+
+    /**
+     * Construct a new instance, using the {@code module.path} system property or the {@code JAVA_MODULEPATH} environment variable
+     * to get the list of module repository roots.
+     * <p>
+     * This is equivalent to a call to {@link LocalModuleFinder#LocalModuleFinder(boolean) LocalModuleFinder(true)}.
+     * </p>
+     */
+    public LocalModuleFinder() {
+        this(true);
+    }
+
+    /**
+     * Construct a new instance, using the {@code module.path} system property or the {@code JAVA_MODULEPATH} environment variable
+     * to get the list of module repository roots.
+     *
+     * @param supportLayersAndAddOns {@code true} if the identified module repository roots should be checked for
+     *                               an internal structure of child "layer" and "add-on" directories that may also
+     *                               be treated as module roots lower in precedence than the parent root. Any "layers"
+     *                               subdirectories whose names are specified in a {@code layers.conf} file found in
+     *                               the module repository root will be added in the precedence of order specified
+     *                               in the {@code layers.conf} file; all "add-on" subdirectories will be added at
+     *                               a lower precedence than all "layers" and with no guaranteed precedence order
+     *                               between them. If {@code false} no check for "layer" and "add-on" directories
+     *                               will be performed.
+     *
+     */
+    public LocalModuleFinder(boolean supportLayersAndAddOns) {
+        this(getRepoRoots(supportLayersAndAddOns), PathFilters.acceptAll(), false);
+    }
+
+    static File[] getRepoRoots(final boolean supportLayersAndAddOns) {
+        return supportLayersAndAddOns ? LayeredModulePathFactory.resolveLayeredModulePath(getModulePathFiles()) : getModulePathFiles();
+    }
+
+    private static File[] getModulePathFiles() {
+        return getFiles(System.getProperty("module.path", System.getenv("JAVA_MODULEPATH")), 0, 0);
+    }
+
+    private static File[] getFiles(final String modulePath, final int stringIdx, final int arrayIdx) {
+        if (modulePath == null) return NO_FILES;
+        final int i = modulePath.indexOf(File.pathSeparatorChar, stringIdx);
+        final File[] files;
+        if (i == -1) {
+            files = new File[arrayIdx + 1];
+            files[arrayIdx] = new File(modulePath.substring(stringIdx)).getAbsoluteFile();
+        } else {
+            files = getFiles(modulePath, i + 1, arrayIdx + 1);
+            files[arrayIdx] = new File(modulePath.substring(stringIdx, i)).getAbsoluteFile();
+        }
+        return files;
+    }
+
+    private static String toPathString(ModuleIdentifier moduleIdentifier) {
+        final StringBuilder builder = new StringBuilder(40);
+        builder.append(moduleIdentifier.getName().replace('.', File.separatorChar));
+        builder.append(File.separatorChar).append(moduleIdentifier.getSlot());
+        builder.append(File.separatorChar);
+        return builder.toString();
+    }
+
+    public ModuleSpec findModule(final ModuleIdentifier identifier, final ModuleLoader delegateLoader) throws ModuleLoadException {
+        final String child = toPathString(identifier);
+        if (pathFilter.accept(child)) {
+            try {
+                return doPrivileged(new PrivilegedExceptionAction<ModuleSpec>() {
+                    public ModuleSpec run() throws Exception {
+                        for (File root : repoRoots) {
+                            final File file = new File(root, child);
+                            final File moduleXml = new File(file, "module.xml");
+                            if (moduleXml.exists()) {
+                                final ModuleSpec spec = ModuleXmlParser.parseModuleXml(delegateLoader, identifier, file, moduleXml, accessControlContext);
+                                if (spec == null) break;
+                                return spec;
+                            }
+                        }
+                        return null;
+                    }
+                }, accessControlContext);
+            } catch (PrivilegedActionException e) {
+                try {
+                    throw e.getException();
+                } catch (RuntimeException e1) {
+                    throw e1;
+                } catch (ModuleLoadException e1) {
+                    throw e1;
+                } catch (Error e1) {
+                    throw e1;
+                } catch (Exception e1) {
+                    throw new UndeclaredThrowableException(e1);
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Parse a {@code module.xml} file and return the corresponding module specification.
+     *
+     * @param identifier the identifier to load
+     * @param delegateLoader the delegate module loader to use for module specifications
+     * @param roots the repository root paths to search
+     * @return the module specification
+     * @throws IOException if reading the module file failed
+     * @throws ModuleLoadException if creating the module specification failed (e.g. due to a parse error)
+     */
+    public static ModuleSpec parseModuleXmlFile(final ModuleIdentifier identifier, final ModuleLoader delegateLoader, final File... roots) throws IOException, ModuleLoadException {
+        final String child = toPathString(identifier);
+        for (File root : roots) {
+            final File file = new File(root, child);
+            final File moduleXml = new File(file, "module.xml");
+            if (moduleXml.exists()) {
+                final ModuleSpec spec = ModuleXmlParser.parseModuleXml(delegateLoader, identifier, file, moduleXml, getContext());
+                if (spec == null) break;
+                return spec;
+            }
+        }
+        return null;
+    }
+
+    public String toString() {
+        final StringBuilder b = new StringBuilder();
+        b.append("local module finder @").append(Integer.toHexString(hashCode())).append(" (roots: ");
+        final int repoRootsLength = repoRoots.length;
+        for (int i = 0; i < repoRootsLength; i++) {
+            final File root = repoRoots[i];
+            b.append(root);
+            if (i != repoRootsLength - 1) {
+                b.append(',');
+            }
+        }
+        b.append(')');
+        return b.toString();
+    }
+}
diff --git a/src/main/java/org/jboss/modules/LocalModuleLoader.java b/src/main/java/org/jboss/modules/LocalModuleLoader.java
new file mode 100644
index 0000000..d774600
--- /dev/null
+++ b/src/main/java/org/jboss/modules/LocalModuleLoader.java
@@ -0,0 +1,65 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.io.File;
+import org.jboss.modules.filter.PathFilter;
+import org.jboss.modules.filter.PathFilters;
+
+/**
+ * A local filesystem-backed module loader.
+ *
+ * @author <a href="mailto:jbailey at redhat.com">John Bailey</a>
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public final class LocalModuleLoader extends ModuleLoader {
+
+    /**
+     * Construct a new instance.
+     *
+     * @param repoRoots the array of repository roots to look for modules
+     */
+    public LocalModuleLoader(final File[] repoRoots) {
+        this(repoRoots, PathFilters.acceptAll());
+    }
+
+    /**
+     * Construct a new instance.
+     *
+     * @param repoRoots the array of repository roots to look for modules
+     * @param pathFilter the path filter to apply to roots
+     */
+    public LocalModuleLoader(final File[] repoRoots, final PathFilter pathFilter) {
+        super(new ModuleFinder[] { new LocalModuleFinder(repoRoots, pathFilter)});
+    }
+
+    /**
+     * Construct a new instance, using the {@code module.path} system property or the {@code JAVA_MODULEPATH} environment variable
+     * to get the list of module repository roots.
+     */
+    public LocalModuleLoader() {
+        super(new ModuleFinder[] { new LocalModuleFinder() });
+    }
+
+    public String toString() {
+        final StringBuilder b = new StringBuilder();
+        b.append("local module loader @").append(Integer.toHexString(hashCode())).append(" (finder: ").append(getFinders()[0]).append(')');
+        return b.toString();
+    }
+}
diff --git a/src/main/java/org/jboss/modules/Main.java b/src/main/java/org/jboss/modules/Main.java
new file mode 100644
index 0000000..86b4c63
--- /dev/null
+++ b/src/main/java/org/jboss/modules/Main.java
@@ -0,0 +1,561 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import __redirected.__JAXPRedirected;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.lang.management.ManagementFactory;
+import java.lang.reflect.InvocationTargetException;
+import java.net.URL;
+import java.security.AccessController;
+import java.security.Policy;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.ServiceLoader;
+import java.util.jar.Attributes;
+import java.util.jar.JarFile;
+import java.util.logging.LogManager;
+
+import java.util.jar.Manifest;
+import java.util.prefs.Preferences;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.jboss.modules.log.JDKModuleLogger;
+
+import static java.security.AccessController.doPrivileged;
+import static org.jboss.modules.SecurityActions.setContextClassLoader;
+
+/**
+ * The main entry point of JBoss Modules when run as a JAR on the command line.
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ * @author Jason T. Greene
+ * @apiviz.exclude
+ */
+public final class Main {
+
+    static {
+        // Force initialization at the earliest possible point
+        @SuppressWarnings("unused")
+        long start = StartTimeHolder.START_TIME;
+    }
+
+    private static final String[] NO_STRINGS = new String[0];
+
+    private Main() {
+    }
+
+    private static void usage() {
+        System.out.println("Usage: java [-jvmoptions...] -jar " + getJarName() + ".jar [-options...] <module-spec> [args...]");
+        System.out.println("       java [-jvmoptions...] -jar " + getJarName() + ".jar [-options...] -jar <jar-name> [args...]");
+        System.out.println("       java [-jvmoptions...] -jar " + getJarName() + ".jar [-options...] -cp <class-path> <class-name> [args...]");
+        System.out.println("       java [-jvmoptions...] -jar " + getJarName() + ".jar [-options...] -class <class-name> [args...]");
+        System.out.println("       java [-jvmoptions...] -jar " + getJarName() + ".jar -addindex [-modify] <jar-name> ");
+        System.out.println("where <module-spec> is a valid module specification string");
+        System.out.println("and options include:");
+        System.out.println("    -help         Display this message");
+        System.out.println("    -mp, -modulepath <search path of directories>");
+        System.out.println("                  A list of directories, separated by '" + File.pathSeparator + "', where modules may be located");
+        System.out.println("                  If not specified, the value of the \"module.path\" system property is used");
+        System.out.println("    -class        Specify that the final argument is a");
+        System.out.println("                  class to load from the class path; not compatible with -jar");
+        System.out.println("    -cp,-classpath <search path of archives or directories>");
+        System.out.println("                  A search path for class files; implies -class");
+        System.out.println("    -dep,-dependencies <module-spec>[,<module-spec>,...]");
+        System.out.println("                  A list of module dependencies to add to the class path;");
+        System.out.println("                  requires -class or -cp");
+        System.out.println("    -deptree      Print the dependency tree of the given module instead of running it");
+        System.out.println("    -jar          Specify that the final argument is the name of a");
+        System.out.println("                  JAR file to run as a module; not compatible with -class");
+        System.out.println("    -jaxpmodule <module-spec>");
+        System.out.println("                  The default JAXP implementation to use of the JDK");
+        System.out.println("    -secmgr       Run with a security manager installed; not compatible with -secmgrmodule");
+        System.out.println("    -secmgrmodule <module-spec>");
+        System.out.println("                  Run with a security manager module; not compatible with -secmgr");
+        System.out.println("    -addindex     Specify that the final argument is a");
+        System.out.println("                  jar to create an index for");
+        System.out.println("    -modify       Modify the indexes jar in-place");
+        System.out.println("    -version      Print version and exit\n");
+    }
+
+    /**
+     * Run JBoss Modules.
+     *
+     * @param args the command-line arguments
+     *
+     * @throws Throwable if an error occurs
+     */
+    public static void main(String[] args) throws Throwable {
+        final int argsLen = args.length;
+        String deps = null;
+        String[] moduleArgs = NO_STRINGS;
+        String modulePath = null;
+        String classpath = null;
+        boolean jar = false;
+        boolean classpathDefined = false;
+        boolean classDefined = false;
+        boolean depTree = false;
+        String nameArgument = null;
+        ModuleIdentifier jaxpModuleIdentifier = null;
+        boolean defaultSecMgr = false;
+        String secMgrModule = null;
+        boolean addIndex = false;
+        boolean modifyInPlace = false;
+        for (int i = 0, argsLength = argsLen; i < argsLength; i++) {
+            final String arg = args[i];
+            try {
+                if (arg.charAt(0) == '-') {
+                    // it's an option
+                    if ("-version".equals(arg)) {
+                        System.out.println("JBoss Modules version " + getVersionString());
+                        return;
+                    } else if ("-help".equals(arg)) {
+                        usage();
+                        return;
+                    } else if ("-addindex".equals(arg)) {
+                        addIndex = true;
+                    } else if ("-modify".equals(arg)) {
+                        modifyInPlace = true;
+                    } else if ("-modulepath".equals(arg) || "-mp".equals(arg)) {
+                        if (modulePath != null) {
+                            System.err.println("Module path may only be specified once");
+                            System.exit(1);
+                        }
+                        modulePath = args[++i];
+                        System.setProperty("module.path", modulePath);
+                    } else if ("-config".equals(arg)) {
+                        System.err.println("Config files are no longer supported.  Use the -mp option instead");
+                        System.exit(1);
+                    } else if ("-deptree".equals(arg)) {
+                        if (depTree) {
+                            System.err.println("-deptree may only be specified once");
+                            System.exit(1);
+                        }
+                        if (jar) {
+                            System.err.println("-deptree may not be specified with -jar");
+                            System.exit(1);
+                        }
+                        if (classDefined) {
+                            System.err.println("-deptree may not be specified with -class");
+                            System.exit(1);
+                        }
+                        if (classpathDefined) {
+                            System.err.println("-deptree may not be specified with -classpath");
+                            System.exit(1);
+                        }
+                        depTree = true;
+                    } else if ("-jaxpmodule".equals(arg)) {
+                        jaxpModuleIdentifier = ModuleIdentifier.fromString(args[++i]);
+                    } else if ("-jar".equals(arg)) {
+                        if (jar) {
+                            System.err.println("-jar flag may only be specified once");
+                            System.exit(1);
+                        }
+                        if (classpathDefined) {
+                            System.err.println("-cp/-classpath may not be specified with -jar");
+                            System.exit(1);
+                        }
+                        if (classDefined) {
+                            System.err.println("-class may not be specified with -jar");
+                            System.exit(1);
+                        }
+                        if (depTree) {
+                            System.err.println("-deptree may not be specified with -jar");
+                            System.exit(1);
+                        }
+                        jar = true;
+                    } else if ("-cp".equals(arg) || "-classpath".equals(arg)) {
+                        if (classpathDefined) {
+                            System.err.println("-cp or -classpath may only be specified once.");
+                            System.exit(1);
+                        }
+                        if (classDefined) {
+                            System.err.println("-class may not be specified with -cp/classpath");
+                            System.exit(1);
+                        }
+                        if (jar) {
+                            System.err.println("-cp/-classpath may not be specified with -jar");
+                            System.exit(1);
+                        }
+                        if (depTree) {
+                            System.err.println("-deptree may not be specified with -classpath");
+                            System.exit(1);
+                        }
+                        classpathDefined = true;
+                        classpath = args[++i];
+                        doPrivileged(new PropertyWriteAction("java.class.path", classpath));
+                    } else if ("-dep".equals(arg) || "-dependencies".equals(arg)) {
+                        if (deps != null) {
+                            System.err.println("-dep or -dependencies may only be specified once.");
+                            System.exit(1);
+                        }
+                        deps = args[++i];
+                    } else if ("-class".equals(arg)) {
+                        if (classDefined) {
+                            System.err.println("-class flag may only be specified once");
+                            System.exit(1);
+                        }
+                        if (classpathDefined) {
+                            System.err.println("-class may not be specified with -cp/classpath");
+                            System.exit(1);
+                        }
+                        if (jar) {
+                            System.err.println("-class may not be specified with -jar");
+                            System.exit(1);
+                        }
+                        if (depTree) {
+                            System.err.println("-deptree may not be specified with -class");
+                            System.exit(1);
+                        }
+                        classDefined = true;
+                    } else if ("-logmodule".equals(arg)) {
+                        System.err.println("WARNING: -logmodule is deprecated. Please use the system property 'java.util.logging.manager' or the 'java.util.logging.LogManager' service loader.");
+                        i++;
+                    } else if ("-secmgr".equals(arg)) {
+                        if (defaultSecMgr) {
+                            System.err.println("-secmgr may only be specified once");
+                            System.exit(1);
+                        }
+                        if (secMgrModule != null) {
+                            System.err.println("-secmgr may not be specified when -secmgrmodule is given");
+                            System.exit(1);
+                        }
+                        defaultSecMgr = true;
+                    } else if ("-secmgrmodule".equals(arg)) {
+                        if (secMgrModule != null) {
+                            System.err.println("-secmgrmodule may only be specified once");
+                            System.exit(1);
+                        }
+                        if (defaultSecMgr) {
+                            System.err.println("-secmgrmodule may not be specified when -secmgr is given");
+                            System.exit(1);
+                        }
+                        secMgrModule = args[++i];
+                    } else {
+                        System.err.printf("Invalid option '%s'\n", arg);
+                        usage();
+                        System.exit(1);
+                    }
+                } else {
+                    // it's the module specification
+                    nameArgument = arg;
+                    int cnt = argsLen - i - 1;
+                    moduleArgs = new String[cnt];
+                    System.arraycopy(args, i + 1, moduleArgs, 0, cnt);
+                    break;
+                }
+            } catch (IndexOutOfBoundsException e) {
+                System.err.printf("Argument expected for option %s\n", arg);
+                usage();
+                System.exit(1);
+            }
+        }
+        
+        if (modifyInPlace && ! addIndex) {
+            System.err.println("-modify requires -addindex");
+            usage();
+            System.exit(1);
+        }
+
+        if (addIndex) {
+            if (nameArgument == null) {
+                System.err.println("-addindex requires a target JAR name");
+                usage();
+                System.exit(1);
+            }
+            if (modulePath != null) {
+                System.err.println("-mp may not be used with -addindex");
+                usage();
+                System.exit(1);
+            }
+            if (jaxpModuleIdentifier != null) {
+                System.err.println("-jaxpModuleIdentifier may not be used with -addindex");
+                usage();
+                System.exit(1);
+            }
+            if (classpathDefined) {
+                System.err.println("-cp or -classpath may not be used with -addindex");
+                usage();
+                System.exit(1);
+            }
+            if (classDefined) {
+                System.err.println("-class may not be used with -addindex");
+                usage();
+                System.exit(1);
+            }
+            if (jar) {
+                System.err.println("-jar may not be used with -addindex");
+                usage();
+                System.exit(1);
+            }
+            if (deps != null) {
+                System.err.println("-deps may not be used with -addindex");
+                usage();
+                System.exit(1);
+            }
+            if (defaultSecMgr) {
+                System.err.println("-secmgr may not be used with -addindex");
+                usage();
+                System.exit(1);
+            }
+            if (secMgrModule != null) {
+                System.err.println("-secmgrmodule may not be used with -addindex");
+                usage();
+                System.exit(1);
+            }
+            if (depTree) {
+                System.err.println("-deptree may not be used with -addindex");
+                usage();
+                System.exit(1);
+            }
+
+            JarFileResourceLoader.addInternalIndex(new File(nameArgument), modifyInPlace);
+            return;
+        }
+
+        if (deps != null && ! classDefined && ! classpathDefined) {
+            System.err.println("-deps may only be specified when -cp/-classpath or -class is in use");
+            System.exit(1);
+        }
+
+        // run the module
+        if (nameArgument == null) {
+            if (classDefined || classpathDefined) {
+                System.err.println("No class name specified");
+            } else if (jar) {
+                System.err.println("No JAR specified");
+            } else {
+                System.err.println("No module specified");
+            }
+            usage();
+            System.exit(1);
+        }
+
+        if (depTree) {
+            DependencyTreeViewer.print(new PrintWriter(System.out), ModuleIdentifier.fromString(nameArgument), LocalModuleFinder.getRepoRoots(true));
+            System.exit(0);
+        }
+
+        final ModuleLoader loader;
+        final ModuleLoader environmentLoader;
+        environmentLoader = DefaultBootModuleLoaderHolder.INSTANCE;
+        final ModuleIdentifier moduleIdentifier;
+        if (jar) {
+            loader = new JarModuleLoader(environmentLoader, new JarFile(nameArgument));
+            moduleIdentifier = ((JarModuleLoader) loader).getMyIdentifier();
+        } else if (classpathDefined || classDefined) {
+            loader = new ClassPathModuleLoader(environmentLoader, nameArgument, classpath, deps);
+            moduleIdentifier = ModuleIdentifier.CLASSPATH;
+        } else {
+            loader = environmentLoader;
+            moduleIdentifier = ModuleIdentifier.fromString(nameArgument);
+        }
+        Module.initBootModuleLoader(loader);
+        if (jaxpModuleIdentifier != null) {
+            __JAXPRedirected.changeAll(jaxpModuleIdentifier, Module.getBootModuleLoader());
+        } else {
+            __JAXPRedirected.changeAll(moduleIdentifier, Module.getBootModuleLoader());
+        }
+
+        final Module module;
+        try {
+            module = loader.loadModule(moduleIdentifier);
+        } catch (ModuleNotFoundException e) {
+            e.printStackTrace(System.err);
+            System.exit(1);
+            return;
+        }
+        final String ourJavaVersion = doPrivileged(new PropertyReadAction("java.specification.version", "1.6"));
+        final String requireJavaVersion = module.getProperty("jboss.require-java-version", ourJavaVersion);
+        final Pattern versionPattern = Pattern.compile("1\\.(\\d+)");
+        final Matcher requireMatcher = versionPattern.matcher(requireJavaVersion);
+        final Matcher ourMatcher = versionPattern.matcher(ourJavaVersion);
+        if (requireMatcher.matches() && ourMatcher.matches() && Integer.valueOf(requireMatcher.group(1)) > Integer.valueOf(ourMatcher.group(1))) {
+            System.err.printf("This application requires Java specification version %s or later to run (this Java virtual machine implements specification version %s)%n", requireJavaVersion, ourJavaVersion);
+            System.exit(1);
+        }
+
+        ModularURLStreamHandlerFactory.addHandlerModule(module);
+        ModularContentHandlerFactory.addHandlerModule(module);
+
+        try {
+            final Iterator<Policy> iterator = module.loadService(Policy.class).iterator();
+            if (iterator.hasNext()) {
+                Policy.setPolicy(iterator.next());
+            }
+        } catch (Exception ignored) {}
+
+        // configure policy so that if SM is enabled, modules can still function
+        final ModulesPolicy policy = new ModulesPolicy(Policy.getPolicy());
+        Policy.setPolicy(policy);
+
+        // these two lines really needed for post EAP 6.x
+        ModuleClassLoader.POLICY_READY.set(true);
+        policy.refresh();
+
+        if (secMgrModule != null) {
+            final Module loadedModule;
+            try {
+                loadedModule = loader.loadModule(ModuleIdentifier.fromString(secMgrModule));
+            } catch (ModuleNotFoundException e) {
+                e.printStackTrace(System.err);
+                System.exit(1);
+                return;
+            }
+            final Iterator<SecurityManager> iterator = ServiceLoader.load(SecurityManager.class, loadedModule.getClassLoaderPrivate()).iterator();
+            if (iterator.hasNext()) {
+                System.setSecurityManager(iterator.next());
+            } else {
+                System.err.println("No security manager found in module " + secMgrModule);
+                System.exit(1);
+            }
+        }
+
+        if (defaultSecMgr) {
+            final Iterator<SecurityManager> iterator = module.loadService(SecurityManager.class).iterator();
+            if (iterator.hasNext()) {
+                System.setSecurityManager(iterator.next());
+            } else {
+                System.setSecurityManager(new SecurityManager());
+            }
+        }
+
+        final ModuleClassLoader bootClassLoader = module.getClassLoaderPrivate();
+        setContextClassLoader(bootClassLoader);
+
+        final String serviceName = getServiceName(bootClassLoader, "java.util.prefs.PreferencesFactory");
+        if (serviceName != null) {
+            final String old = System.setProperty("java.util.prefs.PreferencesFactory", serviceName);
+            try {
+                Preferences.systemRoot();
+            } finally {
+                if (old == null) {
+                    System.clearProperty("java.util.prefs.PreferencesFactory");
+                } else {
+                    System.setProperty("java.util.prefs.PreferencesFactory", old);
+                }
+            }
+        }
+
+        final String logManagerName = getServiceName(bootClassLoader, "java.util.logging.LogManager");
+        if (logManagerName != null) {
+            System.setProperty("java.util.logging.manager", logManagerName);
+            if (LogManager.getLogManager().getClass() == LogManager.class) {
+                System.err.println("WARNING: Failed to load the specified log manager class " + logManagerName);
+            } else {
+                Module.setModuleLogger(new JDKModuleLogger());
+            }
+        }
+
+        final String mbeanServerBuilderName = getServiceName(bootClassLoader, "javax.management.MBeanServerBuilder");
+        if (mbeanServerBuilderName != null) {
+            System.setProperty("javax.management.builder.initial", mbeanServerBuilderName);
+            // Initialize the platform mbean server
+            ManagementFactory.getPlatformMBeanServer();
+        }
+
+        ModuleLoader.installMBeanServer();
+
+        try {
+            module.run(moduleArgs);
+        } catch (InvocationTargetException e) {
+            throw e.getCause();
+        }
+        return;
+    }
+
+    private static String getServiceName(ClassLoader classLoader, String className) throws IOException {
+        final InputStream stream = classLoader.getResourceAsStream("META-INF/services/" + className);
+        if (stream == null) {
+            return null;
+        }
+        try {
+            final BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
+            String line;
+            while ((line = reader.readLine()) != null) {
+                final int i = line.indexOf('#');
+                if (i != -1) {
+                    line = line.substring(0, i);
+                }
+                line = line.trim();
+                if (line.length() == 0) continue;
+                return line;
+            }
+            return null;
+        } finally {
+            StreamUtil.safeClose(stream);
+        }
+    }
+
+    private static final String JAR_NAME;
+    private static final String VERSION_STRING;
+
+    static {
+        final Enumeration<URL> resources;
+        String jarName = "(unknown)";
+        String versionString = "(unknown)";
+        try {
+            final ClassLoader classLoader = Main.class.getClassLoader();
+            resources = classLoader == null ? ModuleClassLoader.getSystemResources("META-INF/MANIFEST.MF") : classLoader.getResources("META-INF/MANIFEST.MF");
+            while (resources.hasMoreElements()) {
+                final URL url = resources.nextElement();
+                try {
+                    final InputStream stream = url.openStream();
+                    if (stream != null) try {
+                        final Manifest manifest = new Manifest(stream);
+                        final Attributes mainAttributes = manifest.getMainAttributes();
+                        if (mainAttributes != null && "JBoss Modules".equals(mainAttributes.getValue("Specification-Title"))) {
+                            jarName = mainAttributes.getValue("Jar-Name");
+                            versionString = mainAttributes.getValue("Jar-Version");
+                        }
+                    } finally {
+                        StreamUtil.safeClose(stream);
+                    }
+                } catch (IOException ignored) {}
+            }
+        } catch (IOException ignored) {}
+        JAR_NAME = jarName;
+        VERSION_STRING = versionString;
+    }
+
+    /**
+     * Get the name of the JBoss Modules JAR.
+     *
+     * @return the name
+     */
+    public static String getJarName() {
+        return JAR_NAME;
+    }
+
+    /**
+     * Get the version string of JBoss Modules.
+     *
+     * @return the version string
+     */
+    public static String getVersionString() {
+        return VERSION_STRING;
+    }
+}
diff --git a/src/main/java/org/jboss/modules/MavenArtifactUtil.java b/src/main/java/org/jboss/modules/MavenArtifactUtil.java
new file mode 100755
index 0000000..a72186e
--- /dev/null
+++ b/src/main/java/org/jboss/modules/MavenArtifactUtil.java
@@ -0,0 +1,351 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import static org.jboss.modules.ModuleXmlParser.endOfDocument;
+import static org.jboss.modules.ModuleXmlParser.unexpectedContent;
+import static org.jboss.modules.xml.XmlPullParser.END_DOCUMENT;
+import static org.jboss.modules.xml.XmlPullParser.END_TAG;
+import static org.jboss.modules.xml.XmlPullParser.FEATURE_PROCESS_NAMESPACES;
+import static org.jboss.modules.xml.XmlPullParser.START_TAG;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.StandardOpenOption;
+import java.util.List;
+
+import org.jboss.modules.xml.MXParser;
+import org.jboss.modules.xml.XmlPullParser;
+import org.jboss.modules.xml.XmlPullParserException;
+
+/**
+ * Helper class to resolve a maven artifact
+ *
+ * @author <a href="mailto:bill at burkecentral.com">Bill Burke</a>
+ * @author <a href="mailto:tcerar at redhat.com">Tomaz Cerar</a>
+ * @version $Revision: 2 $
+ */
+class MavenArtifactUtil {
+
+    private static MavenSettings mavenSettings;
+    private static final Object settingLoaderMutex = new Object();
+
+    public static MavenSettings getSettings() throws IOException {
+        if (mavenSettings != null) {
+            return mavenSettings;
+        }
+        synchronized (settingLoaderMutex) {
+            MavenSettings settings = new MavenSettings();
+
+            Path m2 = java.nio.file.Paths.get(System.getProperty("user.home"), ".m2");
+            Path settingsPath = m2.resolve("settings.xml");
+
+            if (Files.notExists(settingsPath)) {
+                String mavenHome = System.getenv("M2_HOME");
+                if (mavenHome != null) {
+                    settingsPath = java.nio.file.Paths.get(mavenHome, "conf", "settings.xml");
+                }
+            }
+            if (Files.exists(settingsPath)) {
+                parseSettingsXml(settingsPath, settings);
+            }
+            if (settings.getLocalRepository() == null) {
+                Path repository = m2.resolve("repository");
+                settings.setLocalRepository(repository);
+            }
+            settings.resolveActiveSettings();
+            mavenSettings = settings;
+            return mavenSettings;
+        }
+    }
+
+    private static MavenSettings parseSettingsXml(Path settings, MavenSettings mavenSettings) throws IOException {
+        try {
+            final MXParser reader = new MXParser();
+            reader.setFeature(FEATURE_PROCESS_NAMESPACES, false);
+            InputStream source = Files.newInputStream(settings, StandardOpenOption.READ);
+            reader.setInput(source, null);
+            int eventType;
+            while ((eventType = reader.next()) != END_DOCUMENT) {
+                switch (eventType) {
+                    case START_TAG: {
+                        switch (reader.getName()) {
+                            case "settings": {
+                                parseSettings(reader, mavenSettings);
+                                break;
+                            }
+                        }
+                    }
+                    default: {
+                        break;
+                    }
+                }
+            }
+            return mavenSettings;
+        } catch (XmlPullParserException e) {
+            throw new IOException("Could not parse maven settings.xml");
+        }
+
+    }
+
+    private static void parseSettings(final XmlPullParser reader, MavenSettings mavenSettings) throws XmlPullParserException, IOException {
+        int eventType;
+        while ((eventType = reader.nextTag()) != END_DOCUMENT) {
+            switch (eventType) {
+                case END_TAG: {
+                    return;
+                }
+                case START_TAG: {
+
+                    switch (reader.getName()) {
+                        case "localRepository": {
+                            String localRepository = reader.nextText();
+                            mavenSettings.setLocalRepository(java.nio.file.Paths.get(localRepository));
+                            break;
+                        }
+                        case "profiles": {
+                            while ((eventType = reader.nextTag()) != END_DOCUMENT) {
+                                if (eventType == START_TAG) {
+                                    switch (reader.getName()) {
+                                        case "profile": {
+                                            parseProfile(reader, mavenSettings);
+                                            break;
+                                        }
+                                    }
+                                } else {
+                                    break;
+                                }
+                            }
+                            break;
+                        }
+                        case "activeProfiles": {
+                            while ((eventType = reader.nextTag()) != END_DOCUMENT) {
+                                if (eventType == START_TAG) {
+                                    switch (reader.getName()) {
+                                        case "activeProfile": {
+                                            mavenSettings.addActiveProfile(reader.nextText());
+                                            break;
+                                        }
+                                    }
+                                } else {
+                                    break;
+                                }
+
+                            }
+                            break;
+                        }
+                        default: {
+                            skip(reader);
+
+                        }
+                    }
+                    break;
+                }
+                default: {
+                    throw unexpectedContent(reader);
+                }
+            }
+        }
+        throw endOfDocument(reader);
+    }
+
+    private static void parseProfile(final XmlPullParser reader, MavenSettings mavenSettings) throws XmlPullParserException, IOException {
+        int eventType;
+        MavenSettings.Profile profile = new MavenSettings.Profile();
+        while ((eventType = reader.nextTag()) != END_DOCUMENT) {
+            if (eventType == START_TAG) {
+                switch (reader.getName()) {
+                    case "id": {
+                        profile.setId(reader.nextText());
+                        break;
+                    }
+                    case "repositories": {
+                        while ((eventType = reader.nextTag()) != END_DOCUMENT) {
+                            if (eventType == START_TAG) {
+                                switch (reader.getName()) {
+                                    case "repository": {
+                                        parseRepository(reader, profile);
+                                        break;
+                                    }
+                                }
+                            } else {
+                                break;
+                            }
+
+                        }
+                        break;
+                    }
+                    default: {
+                        skip(reader);
+                    }
+                }
+            } else {
+                break;
+            }
+        }
+        mavenSettings.addProfile(profile);
+    }
+
+    private static void parseRepository(final XmlPullParser reader, MavenSettings.Profile profile) throws XmlPullParserException, IOException {
+        int eventType;
+        while ((eventType = reader.nextTag()) != END_DOCUMENT) {
+            if (eventType == START_TAG) {
+                switch (reader.getName()) {
+                    case "url": {
+                        profile.addRepository(reader.nextText());
+                        break;
+                    }
+                    default: {
+                        skip(reader);
+                    }
+                }
+            } else {
+                break;
+            }
+
+        }
+    }
+
+    private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
+        if (parser.getEventType() != XmlPullParser.START_TAG) {
+            throw new IllegalStateException();
+        }
+        int depth = 1;
+        while (depth != 0) {
+            switch (parser.next()) {
+                case XmlPullParser.END_TAG:
+                    depth--;
+                    break;
+                case XmlPullParser.START_TAG:
+                    depth++;
+                    break;
+            }
+        }
+    }
+
+
+    private static final Object artifactLock = new Object();
+
+    /**
+     * Tries to find a maven jar artifact from the system property "local.maven.repo.path" This property is a list of
+     * platform separated directory names.  If not specified, then it looks in ${user.home}/.m2/repository by default.
+     * <p/>
+     * If it can't find it in local paths, then will try to download from a remote repository from the system property
+     * "remote.maven.repo".  There is no default remote repository.  It will download both the pom and jar and put it
+     * into the first directory listed in "local.maven.repo.path" (or the default dir).  This directory will be created
+     * if it doesn't exist.
+     * <p/>
+     * Finally, if you do not want a message to console, then set the system property "maven.download.message" to
+     * "false"
+     *
+     * @param qualifier group:artifact:version[:classifier]
+     * @return absolute path to artifact, null if none exists
+     * @throws IOException
+     */
+    public static File resolveJarArtifact(String qualifier) throws IOException {
+        String[] split = qualifier.split(":");
+        if (split.length < 3) {
+            throw new IllegalArgumentException("Illegal artifact " + qualifier);
+        }
+        String groupId = split[0];
+        String artifactId = split[1];
+        String version = split[2];
+        String classifier = "";
+        if (split.length >= 4) { classifier = "-" + split[3]; }
+
+        String artifactRelativePath = relativeArtifactPath(groupId, artifactId, version);
+        final MavenSettings settings = getSettings();
+        final Path localRepository = settings.getLocalRepository();
+
+        // serialize artifact lookup because we want to prevent parallel download
+        synchronized (artifactLock) {
+            String jarPath = artifactRelativePath + classifier + ".jar";
+            Path fp = java.nio.file.Paths.get(localRepository.toString(), jarPath);
+            if (Files.exists(fp)) {
+                return fp.toFile();
+            }
+
+            List<String> remoteRepos = mavenSettings.getRemoteRepositories();
+            if (remoteRepos.isEmpty()) {
+                return null;
+            }
+
+            final File jarFile = new File(localRepository.toFile(), jarPath);
+            final File pomFile = new File(localRepository.toFile(), artifactRelativePath + ".pom");
+            for (String remoteRepository : remoteRepos) {
+                try {
+                    String remotePomPath = remoteRepository + artifactRelativePath + ".pom";
+                    String remoteJarPath = remoteRepository + artifactRelativePath + classifier + ".jar";
+                    downloadFile(qualifier + ":pom", remotePomPath, pomFile);
+                    downloadFile(qualifier + ":jar", remoteJarPath, jarFile);
+                    if (jarFile.exists()) { //download successful
+                        return jarFile;
+                    }
+                } catch (IOException e) {
+                    Module.log.trace(e, "Could not download '%s' from '%s' repository", artifactRelativePath, remoteRepository);
+                    //
+                }
+            }
+            //could not find it in remote
+            Module.log.trace("Could not find in any remote repository");
+            return null;
+        }
+    }
+
+    public static String relativeArtifactPath(String groupId, String artifactId, String version) {
+        return relativeArtifactPath(File.separatorChar, groupId, artifactId, version);
+    }
+
+    public static String relativeArtifactHttpPath(String groupId, String artifactId, String version) {
+        return relativeArtifactPath('/', groupId, artifactId, version);
+    }
+
+    private static String relativeArtifactPath(char separator, String groupId, String artifactId, String version) {
+        StringBuilder builder = new StringBuilder(groupId.replace('.', separator));
+        builder.append(separator).append(artifactId).append(separator).append(version).append(separator).append(artifactId).append('-').append(version);
+        return builder.toString();
+    }
+
+    public static void downloadFile(String artifact, String src, File dest) throws IOException {
+        final URL url = new URL(src);
+        final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+        boolean message = Boolean.getBoolean("maven.download.message");
+
+        InputStream bis = connection.getInputStream();
+        try {
+            dest.getParentFile().mkdirs();
+            FileOutputStream fos = new FileOutputStream(dest);
+            try {
+                if (message) { System.out.println("Downloading " + artifact); }
+                Files.copy(bis,dest.toPath(), StandardCopyOption.REPLACE_EXISTING);
+            } finally {
+                StreamUtil.safeClose(fos);
+            }
+        } finally {
+            StreamUtil.safeClose(bis);
+        }
+    }
+}
diff --git a/src/main/java/org/jboss/modules/MavenSettings.java b/src/main/java/org/jboss/modules/MavenSettings.java
new file mode 100644
index 0000000..755bc8b
--- /dev/null
+++ b/src/main/java/org/jboss/modules/MavenSettings.java
@@ -0,0 +1,102 @@
+package org.jboss.modules;
+
+import java.io.File;
+import java.nio.file.*;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Tomaz Cerar (c) 2014 Red Hat Inc.
+ */
+final class MavenSettings {
+    private Path localRepository;
+    private final List<String> remoteRepositories = new LinkedList<>();
+    private final Map<String, Profile> profiles = new HashMap<>();
+    private final List<String> activeProfileNames = new LinkedList<>();
+
+    MavenSettings() {
+        configureDefaults();
+    }
+
+    void configureDefaults() {
+        //always add maven central
+        remoteRepositories.add("https://repo1.maven.org/maven2/");
+        String localRepositoryPath = System.getProperty("local.maven.repo.path");
+        if (localRepositoryPath != null) {
+            System.out.println("Please use 'maven.repo.local' instead of 'local.maven.repo.path'");
+            localRepository = java.nio.file.Paths.get(localRepositoryPath.split(File.pathSeparator)[0]);
+        }
+
+        localRepositoryPath = System.getProperty("maven.repo.local");
+        if (localRepositoryPath != null) {
+            localRepository = java.nio.file.Paths.get(localRepositoryPath);
+        }
+        String remoteRepository = System.getProperty("remote.maven.repo");
+        if (remoteRepository != null) {
+            if (!remoteRepository.endsWith("/")) {
+                remoteRepository += "/";
+            }
+            remoteRepositories.add(remoteRepository);
+        }
+    }
+
+    public void setLocalRepository(Path localRepository) {
+        this.localRepository = localRepository;
+    }
+
+    public Path getLocalRepository() {
+        return localRepository;
+    }
+
+    public List<String> getRemoteRepositories() {
+        return remoteRepositories;
+    }
+
+    public void addProfile(Profile profile) {
+        this.profiles.put(profile.getId(), profile);
+    }
+
+    public void addActiveProfile(String profileName) {
+        activeProfileNames.add(profileName);
+    }
+
+    void resolveActiveSettings() {
+        for (String name : activeProfileNames) {
+            Profile p = profiles.get(name);
+            if (p != null) {
+                remoteRepositories.addAll(p.getRepositories());
+            }
+        }
+    }
+
+
+    static final class Profile {
+        private String id;
+        final List<String> repositories = new LinkedList<>();
+
+        Profile() {
+
+        }
+
+        public void setId(String id) {
+            this.id = id;
+        }
+
+        public String getId() {
+            return id;
+        }
+
+        public void addRepository(String url) {
+            if (!url.endsWith("/")) {
+                url += "/";
+            }
+            repositories.add(url);
+        }
+
+        public List<String> getRepositories() {
+            return repositories;
+        }
+    }
+}
diff --git a/src/main/java/org/jboss/modules/Metrics.java b/src/main/java/org/jboss/modules/Metrics.java
new file mode 100644
index 0000000..e3d9c98
--- /dev/null
+++ b/src/main/java/org/jboss/modules/Metrics.java
@@ -0,0 +1,42 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.lang.management.ManagementFactory;
+import java.lang.management.ThreadMXBean;
+import java.security.AccessController;
+
+/**
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+final class Metrics {
+    static final boolean ENABLED;
+    static final ThreadMXBean THREAD_MX_BEAN = ManagementFactory.getThreadMXBean();
+
+    private Metrics() {
+    }
+
+    static long getCurrentCPUTime() {
+        return ENABLED ? false ? THREAD_MX_BEAN.getCurrentThreadCpuTime() : System.nanoTime() : 0L;
+    }
+
+    static {
+        ENABLED = Boolean.parseBoolean(AccessController.doPrivileged(new PropertyReadAction("jboss.modules.metrics", "false")));
+    }
+}
diff --git a/src/main/java/org/jboss/modules/ModularContentHandlerFactory.java b/src/main/java/org/jboss/modules/ModularContentHandlerFactory.java
new file mode 100644
index 0000000..c6b8287
--- /dev/null
+++ b/src/main/java/org/jboss/modules/ModularContentHandlerFactory.java
@@ -0,0 +1,92 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.net.ContentHandler;
+import java.net.ContentHandlerFactory;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ServiceLoader;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+final class ModularContentHandlerFactory implements ContentHandlerFactory {
+    private static final PrivilegedAction<String> CONTENT_MODULES_LIST_ACTION = new PropertyReadAction("jboss.content.handler.modules");
+
+    private static final List<Module> modules;
+
+    static {
+        CopyOnWriteArrayList<Module> list = new CopyOnWriteArrayList<Module>();
+        final SecurityManager sm = System.getSecurityManager();
+        final String urlModulesList;
+        if (sm != null) {
+            urlModulesList = AccessController.doPrivileged(CONTENT_MODULES_LIST_ACTION);
+        } else {
+            urlModulesList = CONTENT_MODULES_LIST_ACTION.run();
+        }
+        if (urlModulesList != null) {
+            final List<Module> moduleList = new ArrayList<Module>();
+            int f = 0;
+            int i;
+            do {
+                i = urlModulesList.indexOf('|', f);
+                final String moduleId = (i == -1 ? urlModulesList.substring(f) : urlModulesList.substring(f, i)).trim();
+                if (moduleId.length() > 0) {
+                    try {
+                        final ModuleIdentifier identifier = ModuleIdentifier.fromString(moduleId);
+                        Module module = Module.getBootModuleLoader().loadModule(identifier);
+                        moduleList.add(module);
+                    } catch (RuntimeException e) {
+                        // skip it
+                    } catch (ModuleLoadException e) {
+                        // skip it
+                    }
+                }
+                f = i + 1;
+            } while (i != -1);
+            list.addAll(moduleList);
+        }
+        modules = list;
+    }
+
+    static final ModularContentHandlerFactory INSTANCE = new ModularContentHandlerFactory();
+
+    static void addHandlerModule(Module module) {
+        modules.add(module);
+    }
+
+    public ContentHandler createContentHandler(final String mimeType) {
+        for (Module module : modules) {
+            ServiceLoader<ContentHandlerFactory> loader = module.loadService(ContentHandlerFactory.class);
+            for (ContentHandlerFactory factory : loader) try {
+                final ContentHandler handler = factory.createContentHandler(mimeType);
+                if (handler != null) {
+                    return handler;
+                }
+            } catch (RuntimeException e) {
+                // ignored
+            }
+        }
+        return null;
+    }
+}
diff --git a/src/main/java/org/jboss/modules/ModularURLStreamHandlerFactory.java b/src/main/java/org/jboss/modules/ModularURLStreamHandlerFactory.java
new file mode 100644
index 0000000..7757dbc
--- /dev/null
+++ b/src/main/java/org/jboss/modules/ModularURLStreamHandlerFactory.java
@@ -0,0 +1,126 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.net.URLStreamHandler;
+import java.net.URLStreamHandlerFactory;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ServiceLoader;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * The root URL stream handler factory for the module system.
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+final class ModularURLStreamHandlerFactory implements URLStreamHandlerFactory {
+    private static final PrivilegedAction<String> URL_MODULES_LIST_ACTION = new PropertyReadAction("jboss.protocol.handler.modules");
+
+    private static final List<Module> modules;
+
+    private static final ThreadLocal<Set<String>> reentered = new ThreadLocal<Set<String>>() {
+        protected Set<String> initialValue() {
+            return new FastCopyHashSet<String>();
+        }
+    };
+
+    static {
+        CopyOnWriteArrayList<Module> list = new CopyOnWriteArrayList<Module>();
+        final SecurityManager sm = System.getSecurityManager();
+        final String urlModulesList;
+        if (sm != null) {
+            urlModulesList = AccessController.doPrivileged(URL_MODULES_LIST_ACTION);
+        } else {
+            urlModulesList = URL_MODULES_LIST_ACTION.run();
+        }
+        if (urlModulesList != null) {
+            final List<Module> moduleList = new ArrayList<Module>();
+            int f = 0;
+            int i;
+            do {
+                i = urlModulesList.indexOf('|', f);
+                final String moduleId = (i == -1 ? urlModulesList.substring(f) : urlModulesList.substring(f, i)).trim();
+                if (moduleId.length() > 0) {
+                    try {
+                        final ModuleIdentifier identifier = ModuleIdentifier.fromString(moduleId);
+                        Module module = Module.getBootModuleLoader().loadModule(identifier);
+                        moduleList.add(module);
+                    } catch (RuntimeException e) {
+                        // skip it
+                    } catch (ModuleLoadException e) {
+                        // skip it
+                    }
+                }
+                f = i + 1;
+            } while (i != -1);
+            list.addAll(moduleList);
+        }
+        modules = list;
+    }
+
+    static final ModularURLStreamHandlerFactory INSTANCE = new ModularURLStreamHandlerFactory();
+
+    static void addHandlerModule(Module module) {
+        modules.add(module);
+    }
+
+    private ModularURLStreamHandlerFactory() {
+    }
+    
+    private URLStreamHandler locateHandler(final String protocol) {
+        for (Module module : modules) {
+            ServiceLoader<URLStreamHandlerFactory> loader = module.loadService(URLStreamHandlerFactory.class);
+            for (URLStreamHandlerFactory factory : loader) {
+                try {
+                    final URLStreamHandler handler = factory.createURLStreamHandler(protocol);
+                    if (handler != null) {
+                        return handler;
+                    }
+                } catch (RuntimeException e) {
+                    // ignored
+                }
+            }
+        }
+        
+        return null;
+    } 
+
+    public URLStreamHandler createURLStreamHandler(final String protocol) {
+        final Set<String> set = reentered.get();
+        if (set.add(protocol)) {
+            try {
+                if (System.getSecurityManager() == null) {
+                    return locateHandler(protocol);
+                }
+                return AccessController.doPrivileged(new PrivilegedAction<URLStreamHandler>() {
+                    public URLStreamHandler run() {
+                        return locateHandler(protocol);
+                    }
+                });
+            } finally {
+                set.remove(protocol);
+            }
+        }
+        return null;
+    }
+}
diff --git a/src/main/java/org/jboss/modules/Module.java b/src/main/java/org/jboss/modules/Module.java
new file mode 100644
index 0000000..f896388
--- /dev/null
+++ b/src/main/java/org/jboss/modules/Module.java
@@ -0,0 +1,1503 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.net.URL;
+import java.net.URLConnection;
+import java.security.AccessController;
+import java.security.PermissionCollection;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.ServiceLoader;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.jboss.modules._private.ModulesPrivateAccess;
+import org.jboss.modules.filter.ClassFilter;
+import org.jboss.modules.filter.ClassFilters;
+import org.jboss.modules.filter.PathFilter;
+import org.jboss.modules.filter.PathFilters;
+import org.jboss.modules.log.ModuleLogger;
+import org.jboss.modules.log.NoopModuleLogger;
+
+import __redirected.__JAXPRedirected;
+import org.jboss.modules.security.ModularPermissionFactory;
+
+/**
+ * A module is a unit of classes and other resources, along with the specification of what is imported and exported
+ * by this module from and to other modules.  Modules are created by {@link ModuleLoader}s which build modules from
+ * various configuration information and resource roots.
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ * @author <a href="mailto:jbailey at redhat.com">John Bailey</a>
+ * @author <a href="mailto:flavia.rainone at jboss.com">Flavia Rainone</a>
+ * @author Jason T. Greene
+ * @author thomas.diesler at jboss.com
+ *
+ * @apiviz.landmark
+*/
+public final class Module {
+
+    private static final AtomicReference<ModuleLoader> BOOT_MODULE_LOADER;
+
+    static {
+        log = NoopModuleLogger.getInstance();
+        BOOT_MODULE_LOADER = new AtomicReference<ModuleLoader>();
+        EMPTY_CLASS_FILTERS = new FastCopyHashSet<ClassFilter>(0);
+        EMPTY_PATH_FILTERS = new FastCopyHashSet<PathFilter>(0);
+        GET_DEPENDENCIES = new RuntimePermission("getDependencies");
+        GET_CLASS_LOADER = new RuntimePermission("getClassLoader");
+        GET_BOOT_MODULE_LOADER = new RuntimePermission("getBootModuleLoader");
+        ACCESS_MODULE_LOGGER = new RuntimePermission("accessModuleLogger");
+        ADD_CONTENT_HANDLER_FACTORY = new RuntimePermission("addContentHandlerFactory");
+        ADD_URL_STREAM_HANDLER_FACTORY = new RuntimePermission("addURLStreamHandlerFactory");
+
+        final String pkgsString = AccessController.doPrivileged(new PropertyReadAction("jboss.modules.system.pkgs"));
+        final List<String> list = new ArrayList<String>();
+        list.add("java.");
+        list.add("sun.reflect.");
+        list.add("__redirected.");
+        if (pkgsString != null) {
+            int i;
+            int nc = -1;
+            do {
+                i = nc + 1;
+                nc = pkgsString.indexOf(',', i);
+                String part;
+                if (nc == -1) {
+                    part = pkgsString.substring(i).trim();
+                } else {
+                    part = pkgsString.substring(i, nc).trim();
+                }
+                if (part.length() > 0) {
+                    list.add((part + ".").intern());
+                }
+            } while (nc != -1);
+        }
+        systemPackages = list.toArray(list.toArray(new String[list.size()]));
+        final ListIterator<String> iterator = list.listIterator();
+        // http://youtrack.jetbrains.net/issue/IDEA-72097
+        //noinspection WhileLoopReplaceableByForEach
+        while (iterator.hasNext()) {
+            iterator.set(iterator.next().replace('.', '/'));
+        }
+        systemPaths = list.toArray(list.toArray(new String[list.size()]));
+
+        AccessController.doPrivileged(new PrivilegedAction<Void>() {
+            public Void run() {
+                try {
+                    URL.setURLStreamHandlerFactory(ModularURLStreamHandlerFactory.INSTANCE);
+                } catch (Throwable t) {
+                    // todo log a warning or something
+                }
+                try {
+                    URLConnection.setContentHandlerFactory(ModularContentHandlerFactory.INSTANCE);
+                } catch (Throwable t) {
+                    // todo log a warning or something
+                }
+
+                __JAXPRedirected.initAll();
+
+                return null;
+            }
+        });
+    }
+
+    // static properties
+
+    static final String[] systemPackages;
+    static final String[] systemPaths;
+
+    static final ModulesPrivateAccess PRIVATE_ACCESS = new ModulesPrivateAccess() {
+        public ModuleClassLoader getClassLoaderOf(final Module module) {
+            return module.getClassLoaderPrivate();
+        }
+    };
+
+    /**
+     * Private access for module internal code.  Throws {@link SecurityException} for user code.
+     *
+     * @throws SecurityException always
+     */
+    public static ModulesPrivateAccess getPrivateAccess() {
+        if (CallerContext.getCallingClass() == ModularPermissionFactory.class) {
+            return PRIVATE_ACCESS;
+        }
+        throw new SecurityException();
+    }
+
+    /**
+     * The system-wide module logger, which may be changed via {@link #setModuleLogger(org.jboss.modules.log.ModuleLogger)}.
+     */
+    static volatile ModuleLogger log;
+
+    private static final FastCopyHashSet<ClassFilter> EMPTY_CLASS_FILTERS;
+    private static final FastCopyHashSet<PathFilter> EMPTY_PATH_FILTERS;
+
+    // immutable properties
+
+    /**
+     * The identifier of this module.
+     */
+    private final ModuleIdentifier identifier;
+    /**
+     * The name of the main class, if any (may be {@code null}).
+     */
+    private final String mainClassName;
+    /**
+     * The module class loader for this module.
+     */
+    private final ModuleClassLoader moduleClassLoader;
+    /**
+     * The module loader which created this module.
+     */
+    private final ModuleLoader moduleLoader;
+    /**
+     * The fallback local loader, if any is defined.
+     */
+    private final LocalLoader fallbackLoader;
+    /**
+     * The properties map specified when this module was defined.
+     */
+    private final Map<String, String> properties;
+    /**
+     * The assigned permission collection.
+     */
+    private final PermissionCollection permissionCollection;
+
+    // mutable properties
+
+    /**
+     * The linkage state.
+     */
+    private volatile Linkage linkage = Linkage.NONE;
+
+    // private constants
+
+    private static final RuntimePermission GET_DEPENDENCIES;
+    private static final RuntimePermission GET_CLASS_LOADER;
+    private static final RuntimePermission GET_BOOT_MODULE_LOADER;
+    private static final RuntimePermission ACCESS_MODULE_LOGGER;
+    private static final RuntimePermission ADD_CONTENT_HANDLER_FACTORY;
+    private static final RuntimePermission ADD_URL_STREAM_HANDLER_FACTORY;
+
+    /**
+     * Construct a new instance from a module specification.
+     *
+     * @param spec the module specification
+     * @param moduleLoader the module loader
+     */
+    Module(final ConcreteModuleSpec spec, final ModuleLoader moduleLoader) {
+        this.moduleLoader = moduleLoader;
+
+        // Initialize state from the spec.
+        identifier = spec.getModuleIdentifier();
+        mainClassName = spec.getMainClass();
+        fallbackLoader = spec.getFallbackLoader();
+        permissionCollection = spec.getPermissionCollection();
+        //noinspection ThisEscapedInObjectConstruction
+        final ModuleClassLoader.Configuration configuration = new ModuleClassLoader.Configuration(this, spec.getAssertionSetting(), spec.getResourceLoaders(), spec.getClassFileTransformer());
+        final ModuleClassLoaderFactory factory = spec.getModuleClassLoaderFactory();
+        ModuleClassLoader moduleClassLoader = null;
+        if (factory != null) moduleClassLoader = factory.create(configuration);
+        if (moduleClassLoader == null) moduleClassLoader = new ModuleClassLoader(configuration);
+        this.moduleClassLoader = moduleClassLoader;
+        final Map<String, String> properties = spec.getProperties();
+        this.properties = properties.isEmpty() ? Collections.<String, String>emptyMap() : new LinkedHashMap<String, String>(properties);
+    }
+
+    LocalLoader getFallbackLoader() {
+        return fallbackLoader;
+    }
+
+    Dependency[] getDependenciesInternal() {
+        return linkage.getDependencies();
+    }
+
+    DependencySpec[] getDependencySpecsInternal() {
+        return linkage.getDependencySpecs();
+    }
+
+    ModuleClassLoader getClassLoaderPrivate() {
+        return moduleClassLoader;
+    }
+
+    /**
+     * Get the current dependencies of this module.
+     *
+     * @return the current dependencies of this module
+     * @throws SecurityException if a security manager is enabled and the caller does not have the {@code getDependencies}
+     * {@link RuntimePermission}
+     */
+    public DependencySpec[] getDependencies() throws SecurityException {
+        final SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            sm.checkPermission(GET_DEPENDENCIES);
+        }
+        return getDependencySpecsInternal().clone();
+    }
+
+    /**
+     * Get an exported resource from a specific root in this module.
+     *
+     * @param rootPath the module root to search
+     * @param resourcePath the path of the resource
+     * @return the resource
+     */
+    public Resource getExportedResource(final String rootPath, final String resourcePath) {
+        return moduleClassLoader.loadResourceLocal(rootPath, resourcePath);
+    }
+
+    /**
+     * Run a module's main class, if any.
+     *
+     * @param args the arguments to pass
+     * @throws NoSuchMethodException if there is no main method
+     * @throws InvocationTargetException if the main method failed
+     * @throws ClassNotFoundException if the main class is not found
+     */
+    public void run(final String[] args) throws NoSuchMethodException, InvocationTargetException, ClassNotFoundException {
+        try {
+            if (mainClassName == null) {
+                throw new NoSuchMethodException("No main class defined for " + this);
+            }
+            final ClassLoader oldClassLoader = SecurityActions.setContextClassLoader(moduleClassLoader);
+            try {
+                final Class<?> mainClass = Class.forName(mainClassName, false, moduleClassLoader);
+                try {
+                    Class.forName(mainClassName, true, moduleClassLoader);
+                } catch (Throwable t) {
+                    throw new InvocationTargetException(t, "Failed to initialize main class '" + mainClassName + "'");
+                }
+                final Method mainMethod = mainClass.getMethod("main", String[].class);
+                final int modifiers = mainMethod.getModifiers();
+                if (! Modifier.isStatic(modifiers)) {
+                    throw new NoSuchMethodException("Main method is not static for " + this);
+                }
+                // ignore the return value
+                mainMethod.invoke(null, new Object[] {args});
+            } finally {
+                SecurityActions.setContextClassLoader(oldClassLoader);
+            }
+        } catch (IllegalAccessException e) {
+            // unexpected; should be public
+            throw new IllegalAccessError(e.getMessage());
+        }
+    }
+
+    /**
+     * Get this module's identifier.
+     *
+     * @return the identifier
+     */
+    public ModuleIdentifier getIdentifier() {
+        return identifier;
+    }
+
+    /**
+     * Get the module loader which created this module.
+     *
+     * @return the module loader of this module
+     */
+    public ModuleLoader getModuleLoader() {
+        return moduleLoader;
+    }
+
+    /**
+     * Load a service loader from this module.
+     *
+     * @param serviceType the service type class
+     * @param <S> the service type
+     * @return the service loader
+     */
+    public <S> ServiceLoader<S> loadService(Class<S> serviceType) {
+        return ServiceLoader.load(serviceType, moduleClassLoader);
+    }
+
+    /**
+     * Load a service loader from a module in the caller's module loader. The caller's
+     * module loader refers to the loader of the module of the class that calls this method.
+     * Note that {@link #loadService(Class)} is more efficient since it does not need to crawl
+     * the stack.
+     *
+     * @param <S> the the service type
+     * @param identifier the module identifier containing the service loader
+     * @param serviceType the service type class
+     * @return the loaded service from the caller's module
+     * @throws ModuleLoadException if the named module failed to load
+     */
+    public static <S> ServiceLoader<S> loadServiceFromCallerModuleLoader(ModuleIdentifier identifier, Class<S> serviceType) throws ModuleLoadException {
+        return getCallerModuleLoader().loadModule(identifier).loadService(serviceType);
+    }
+
+    /**
+     * Get the class loader for a module.  The class loader can be used to access non-exported classes and
+     * resources of the module.
+     * <p>
+     * If a security manager is present, then this method invokes the security manager's {@code checkPermission} method
+     * with a <code>RuntimePermission("getClassLoader")</code> permission to verify access to the class loader. If
+     * access is not granted, a {@code SecurityException} will be thrown.
+     *
+     * @return the module class loader
+     */
+    public ModuleClassLoader getClassLoader() {
+        final SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            sm.checkPermission(GET_CLASS_LOADER);
+        }
+        return moduleClassLoader;
+    }
+
+    /**
+     * Get all the paths exported by this module.
+     *
+     * @return the paths that are exported by this module
+     */
+    public Set<String> getExportedPaths() {
+        return Collections.unmodifiableSet(getPathsUnchecked().keySet());
+    }
+
+    /**
+     * Get the module for a loaded class, or {@code null} if the class did not come from any module.
+     *
+     * @param clazz the class
+     * @return the module it came from
+     */
+    public static Module forClass(Class<?> clazz) {
+        final ClassLoader cl = clazz.getClassLoader();
+        return forClassLoader(cl, false);
+    }
+
+    /**
+     * Get the module for a class loader, or {@code null} if the class loader is not associated with any module.  If
+     * the class loader is unknown, it is possible to check the parent class loader up the chain, and so on until a module is found.
+     *
+     * @param cl the class loader
+     * @param search {@code true} to search up the delegation chain
+     * @return the associated module
+     */
+    public static Module forClassLoader(ClassLoader cl, boolean search) {
+        if (cl instanceof ModuleClassLoader) {
+            return ((ModuleClassLoader) cl).getModule();
+        } else if (search && cl != null) {
+            return forClassLoader(cl.getParent(), true);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Gets the boot module loader. The boot module loader is the
+     * initial loader that is established by the module framework. It typically
+     * is based off of the environmental module path unless it is overridden by
+     * specifying a different class name for the {@code boot.module.loader} system
+     * property.
+     *
+     * @return the boot module loader
+     */
+    public static ModuleLoader getBootModuleLoader() {
+        final SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            sm.checkPermission(GET_BOOT_MODULE_LOADER);
+        }
+        ModuleLoader loader;
+        while ((loader = BOOT_MODULE_LOADER.get()) == null) {
+            loader = DefaultBootModuleLoaderHolder.INSTANCE;
+            if (BOOT_MODULE_LOADER.compareAndSet(null, loader)) {
+                break;
+            }
+            // get it again
+        }
+        return loader;
+    }
+
+    static void initBootModuleLoader(ModuleLoader loader) {
+        BOOT_MODULE_LOADER.set(loader);
+    }
+
+    /**
+     * Gets the current module loader. The current module loader is the
+     * loader of the module from the calling class. Note that this method
+     * must crawl the stack to determine this, so other mechanisms are more
+     * efficient.
+     *
+     * @return the current module loader, or {@code null} if this method is called outside of a module
+     */
+    public static ModuleLoader getCallerModuleLoader() {
+        Module callerModule = getCallerModule();
+        return callerModule == null ? null : callerModule.getModuleLoader();
+    }
+
+    /**
+     * Get the current thread's context module loader.  This loader is the one which defined the module
+     * whose class loader is, or is a parent of, the thread's current context class loader.  If there is none,
+     * then {@code null} is returned.
+     *
+     * @return the module loader, or {@code null} if none is set
+     */
+    public static ModuleLoader getContextModuleLoader() {
+        return Module.forClassLoader(Thread.currentThread().getContextClassLoader(), true).getModuleLoader();
+    }
+
+    /**
+     * Get a module from the current module loader. Note that this must crawl the
+     * stack to determine this, so other mechanisms are more efficient.
+     * @see #getCallerModuleLoader()
+     *
+     * @param identifier the module identifier
+     * @return the module
+     * @throws ModuleLoadException if the module could not be loaded
+     */
+    public static Module getModuleFromCallerModuleLoader(final ModuleIdentifier identifier) throws ModuleLoadException {
+        return getCallerModuleLoader().loadModule(identifier);
+    }
+
+    /**
+     * Get the caller's module. The caller's module is the module containing the method that calls this
+     * method. Note that this method crawls the stack so other ways of obtaining the
+     * module are more efficient.
+     *
+     * @return the current module
+     */
+    public static Module getCallerModule() {
+        return forClass(CallerContext.getCallingClass());
+    }
+
+    /**
+     * Get the module with the given identifier from the module loader used by this module.
+     *
+     * @param identifier the module identifier
+     * @return the module
+     * @throws ModuleLoadException if an error occurs
+     */
+    public Module getModule(final ModuleIdentifier identifier) throws ModuleLoadException {
+        return moduleLoader.loadModule(identifier);
+    }
+
+    /**
+     * Load a class from a module in the system module loader.
+     *
+     * @see #getBootModuleLoader()
+     *
+     * @param moduleIdentifier the identifier of the module from which the class
+     *        should be loaded
+     * @param className the class name to load
+     * @return the class
+     * @throws ModuleLoadException if the module could not be loaded
+     * @throws ClassNotFoundException if the class could not be loaded
+     */
+    public static Class<?> loadClassFromBootModuleLoader(final ModuleIdentifier moduleIdentifier, final String className)
+            throws ModuleLoadException, ClassNotFoundException {
+        return Class.forName(className, true, getBootModuleLoader().loadModule(moduleIdentifier).getClassLoader());
+    }
+
+    /**
+     * Load a class from a module in the caller's module loader.
+     *
+     * @see #getCallerModuleLoader()
+     *
+     * @param moduleIdentifier the identifier of the module from which the class
+     *        should be loaded
+     * @param className the class name to load
+     * @return the class
+     * @throws ModuleLoadException if the module could not be loaded
+     * @throws ClassNotFoundException if the class could not be loaded
+     */
+    public static Class<?> loadClassFromCallerModuleLoader(final ModuleIdentifier moduleIdentifier, final String className)
+            throws ModuleLoadException, ClassNotFoundException {
+        return Class.forName(className, true, getModuleFromCallerModuleLoader(moduleIdentifier).getClassLoader());
+    }
+
+    /**
+     * Load a class from a local loader.
+     *
+     * @param className the class name
+     * @param resolve {@code true} to resolve the class after definition
+     * @return the class
+     */
+    Class<?> loadModuleClass(final String className, final boolean resolve) throws ClassNotFoundException {
+        for (String s : systemPackages) {
+            if (className.startsWith(s)) {
+                return moduleClassLoader.loadClass(className, resolve);
+            }
+        }
+        final String path = pathOfClass(className);
+        final Map<String, List<LocalLoader>> paths = getPathsUnchecked();
+        final List<LocalLoader> loaders = paths.get(path);
+        if (loaders != null) {
+            Class<?> clazz;
+            for (LocalLoader loader : loaders) {
+                clazz = loader.loadClassLocal(className, resolve);
+                if (clazz != null) {
+                    return clazz;
+                }
+            }
+        }
+        final LocalLoader fallbackLoader = this.fallbackLoader;
+        if (fallbackLoader != null) {
+            return fallbackLoader.loadClassLocal(className, resolve);
+        }
+        return null;
+    }
+
+    /**
+     * Load a resource from a local loader.
+     *
+     * @param name the resource name
+     * @return the resource URL, or {@code null} if not found
+     */
+    URL getResource(final String name) {
+        final String canonPath = PathUtils.canonicalize(name);
+        for (String s : Module.systemPaths) {
+            if (canonPath.startsWith(s)) {
+                return moduleClassLoader.getResource(canonPath);
+            }
+        }
+        log.trace("Attempting to find resource %s in %s", canonPath, this);
+        final String path = pathOf(canonPath);
+        final Map<String, List<LocalLoader>> paths = getPathsUnchecked();
+        final List<LocalLoader> loaders = paths.get(path);
+        if (loaders != null) {
+            for (LocalLoader loader : loaders) {
+                final List<Resource> resourceList = loader.loadResourceLocal(canonPath);
+                for (Resource resource : resourceList) {
+                    return resource.getURL();
+                }
+            }
+        }
+        final LocalLoader fallbackLoader = this.fallbackLoader;
+        if (fallbackLoader != null) {
+            final List<Resource> resourceList = fallbackLoader.loadResourceLocal(canonPath);
+            for (Resource resource : resourceList) {
+                return resource.getURL();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Load a resource from a local loader.
+     *
+     * @param name the resource name
+     * @return the resource stream, or {@code null} if not found
+     */
+    InputStream getResourceAsStream(final String name) throws IOException {
+        final String canonPath = PathUtils.canonicalize(name);
+        for (String s : Module.systemPaths) {
+            if (canonPath.startsWith(s)) {
+                return moduleClassLoader.getResourceAsStream(canonPath);
+            }
+        }
+        log.trace("Attempting to find resource %s in %s", canonPath, this);
+        final String path = pathOf(canonPath);
+        final Map<String, List<LocalLoader>> paths = getPathsUnchecked();
+        final List<LocalLoader> loaders = paths.get(path);
+        if (loaders != null) {
+            for (LocalLoader loader : loaders) {
+                final List<Resource> resourceList = loader.loadResourceLocal(canonPath);
+                for (Resource resource : resourceList) {
+                    return resource.openStream();
+                }
+            }
+        }
+        final LocalLoader fallbackLoader = this.fallbackLoader;
+        if (fallbackLoader != null) {
+            final List<Resource> resourceList = fallbackLoader.loadResourceLocal(canonPath);
+            for (Resource resource : resourceList) {
+                return resource.openStream();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Load all resources of a given name from a local loader.
+     *
+     * @param name the resource name
+     * @return the enumeration of all the matching resource URLs (may be empty)
+     */
+    Enumeration<URL> getResources(final String name) {
+        final String canonPath = PathUtils.canonicalize(PathUtils.relativize(name));
+        for (String s : Module.systemPaths) {
+            if (canonPath.startsWith(s)) {
+                try {
+                    return moduleClassLoader.getResources(canonPath);
+                } catch (IOException e) {
+                    return ConcurrentClassLoader.EMPTY_ENUMERATION;
+                }
+            }
+        }
+        log.trace("Attempting to find all resources %s in %s", canonPath, this);
+        final String path = pathOf(canonPath);
+        final Map<String, List<LocalLoader>> paths = getPathsUnchecked();
+        final List<LocalLoader> loaders = paths.get(path);
+
+        final List<URL> list = new ArrayList<URL>();
+        if (loaders != null) {
+            for (LocalLoader loader : loaders) {
+                final List<Resource> resourceList = loader.loadResourceLocal(canonPath);
+                for (Resource resource : resourceList) {
+                    list.add(resource.getURL());
+                }
+            }
+        }
+        final LocalLoader fallbackLoader = this.fallbackLoader;
+        if (fallbackLoader != null) {
+            final List<Resource> resourceList = fallbackLoader.loadResourceLocal(canonPath);
+            for (Resource resource : resourceList) {
+                list.add(resource.getURL());
+            }
+        }
+
+        return list.size() == 0 ? ConcurrentClassLoader.EMPTY_ENUMERATION : Collections.enumeration(list);
+    }
+
+    /**
+     * Get an exported resource URL.
+     *
+     * @param name the resource name
+     * @return the resource, or {@code null} if it was not found
+     */
+    public URL getExportedResource(final String name) {
+        return getResource(name);
+    }
+
+    /**
+     * Get all exported resource URLs for a resource name.
+     *
+     * @param name the resource name
+     * @return the resource URLs
+     */
+    public Enumeration<URL> getExportedResources(final String name) {
+        return getResources(name);
+    }
+
+    /**
+     * Enumerate all the imported resources in this module, subject to a path filter.  The filter applies to
+     * the containing path of each resource.
+     *
+     * @param filter the filter to apply to the search
+     * @return the resource iterator (possibly empty)
+     * @throws ModuleLoadException if linking a dependency module fails for some reason
+     */
+    public Iterator<Resource> iterateResources(final PathFilter filter) throws ModuleLoadException {
+        final Map<String, List<LocalLoader>> paths = getPaths();
+        final Iterator<Map.Entry<String, List<LocalLoader>>> iterator = paths.entrySet().iterator();
+        return new Iterator<Resource>() {
+
+            private String path;
+            private Iterator<Resource> resourceIterator;
+            private Iterator<LocalLoader> loaderIterator;
+            private Resource next;
+
+            public boolean hasNext() {
+                while (next == null) {
+                    if (resourceIterator != null) {
+                        assert path != null;
+                        if (resourceIterator.hasNext()) {
+                            next = resourceIterator.next();
+                            return true;
+                        }
+                        resourceIterator = null;
+                    }
+                    if (loaderIterator != null) {
+                        assert path != null;
+                        if (loaderIterator.hasNext()) {
+                            final LocalLoader loader = loaderIterator.next();
+                            if (loader instanceof IterableLocalLoader) {
+                                resourceIterator = ((IterableLocalLoader)loader).iterateResources(path, false);
+                                continue;
+                            }
+                        }
+                        loaderIterator = null;
+                    }
+                    if (! iterator.hasNext()) {
+                        return false;
+                    }
+                    final Map.Entry<String, List<LocalLoader>> entry = iterator.next();
+                    path = entry.getKey();
+                    if (filter.accept(path)) {
+                        loaderIterator = entry.getValue().iterator();
+                    }
+                }
+                return true;
+            }
+
+            public Resource next() {
+                if (! hasNext()) throw new NoSuchElementException();
+                try {
+                    return next;
+                } finally {
+                    next = null;
+                }
+            }
+
+            public void remove() {
+                throw new UnsupportedOperationException();
+            }
+        };
+    }
+
+    /**
+     * Enumerate all imported resources in this module which match the given glob expression.  The glob applies to
+     * the whole resource name.
+     *
+     * @param glob the glob to apply
+     * @return the iterator
+     * @throws ModuleLoadException if linking a dependency module fails for some reason
+     */
+    public Iterator<Resource> globResources(final String glob) throws ModuleLoadException {
+        String safeGlob = PathUtils.canonicalize(PathUtils.relativize(glob));
+        final int i = safeGlob.lastIndexOf('/');
+        if (i == -1) {
+            return PathFilters.filtered(PathFilters.match(glob), iterateResources(PathFilters.acceptAll()));
+        } else {
+            return PathFilters.filtered(PathFilters.match(glob.substring(i + 1)), iterateResources(PathFilters.match(glob.substring(0, i))));
+        }
+    }
+
+    /**
+     * Get the (unmodifiable) set of paths which are imported into this module class loader, including local paths.  The
+     * set will include all paths defined by the module's resource loaders, minus any paths excluded by filters.  The
+     * set will generally always contain an empty entry ("").  The set is unordered and unsorted, and is iterable in
+     * O(n) time and accessible in O(1) time.
+     *
+     * @return the set of paths
+     * @throws ModuleLoadException if the module was previously unlinked, and there was an exception while linking
+     */
+    public final Set<String> getImportedPaths() throws ModuleLoadException {
+        return Collections.unmodifiableSet(getPaths().keySet());
+    }
+
+    /**
+     * Get the path name of a class.
+     *
+     * @param className the binary name of the class
+     * @return the parent path
+     */
+    static String pathOfClass(final String className) {
+        final String resourceName = className.replace('.', '/');
+        final String path;
+        final int idx = resourceName.lastIndexOf('/');
+        if (idx > -1) {
+            path = resourceName.substring(0, idx);
+        } else {
+            path = "";
+        }
+        return path;
+    }
+
+    /**
+     * Get the path name of a resource.
+     *
+     * @param resourceName the resource name
+     * @return the parent path
+     */
+    static String pathOf(final String resourceName) {
+        final String path;
+        if (resourceName.indexOf('/') == 0) {
+            return pathOf(resourceName.substring(1));
+        }
+        final int idx = resourceName.lastIndexOf('/');
+        if (idx > -1) {
+            path = resourceName.substring(0, idx);
+        } else {
+            path = "";
+        }
+        return path;
+    }
+
+    /**
+     * Get the file name of a class.
+     *
+     * @param className the class name
+     * @return the name of the corresponding class file
+     */
+    static String fileNameOfClass(final String className) {
+        return className.replace('.', '/') + ".class";
+    }
+
+    /**
+     * Get the property with the given name, or {@code null} if none was defined.
+     *
+     * @param name the property name
+     * @return the property value
+     */
+    public String getProperty(String name) {
+        return properties.get(name);
+    }
+
+    /**
+     * Get the property with the given name, or a default value if none was defined.
+     *
+     * @param name the property name
+     * @param defaultVal the default value
+     * @return the property value
+     */
+    public String getProperty(String name, String defaultVal) {
+        return properties.containsKey(name) ? properties.get(name) : defaultVal;
+    }
+
+    /**
+     * Get a copy of the list of property names.
+     *
+     * @return the property names list
+     */
+    public List<String> getPropertyNames() {
+        return new ArrayList<String>(properties.keySet());
+    }
+
+    /**
+     * Get the string representation of this module.
+     *
+     * @return the string representation
+     */
+    @Override
+    public String toString() {
+        return "Module \"" + identifier + "\"" + " from " + moduleLoader.toString();
+    }
+
+    /**
+     * Get the logger used by the module system.
+     * <p>
+     * If a security manager is present, then this method invokes the security manager's {@code checkPermission} method
+     * with a <code>RuntimePermission("accessModuleLogger")</code> permission to verify access to the module logger. If
+     * access is not granted, a {@code SecurityException} will be thrown.
+     *
+     * @return the module logger
+     */
+    public static ModuleLogger getModuleLogger() {
+        final SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            sm.checkPermission(ACCESS_MODULE_LOGGER);
+        }
+        return log;
+    }
+
+    /**
+     * Change the logger used by the module system.
+     * <p>
+     * If a security manager is present, then this method invokes the security manager's {@code checkPermission} method
+     * with a <code>RuntimePermission("accessModuleLogger")</code> permission to verify access to the module logger. If
+     * access is not granted, a {@code SecurityException} will be thrown.
+     *
+     * @param logger the new logger, must not be {@code null}
+     */
+    public static void setModuleLogger(final ModuleLogger logger) {
+        if (logger == null) {
+            throw new IllegalArgumentException("logger is null");
+        }
+        final SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            sm.checkPermission(ACCESS_MODULE_LOGGER);
+        }
+        logger.greeting();
+        log = logger;
+    }
+
+    /**
+     * Return the start time in millis when Module.class was loaded.
+     *
+     * @return start time of Module.class load
+     */
+    public static long getStartTime() {
+        return StartTimeHolder.START_TIME;
+    }
+
+    /**
+     * Register an additional module which contains content handlers.
+     * <p>
+     * If a security manager is present, then this method invokes the security manager's {@code checkPermission} method
+     * with a <code>RuntimePermission("addContentHandlerFactory")</code> permission to verify access. If
+     * access is not granted, a {@code SecurityException} will be thrown.
+     *
+     * @param module the module to add
+     */
+    public static void registerContentHandlerFactoryModule(Module module) {
+        final SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            sm.checkPermission(ADD_CONTENT_HANDLER_FACTORY);
+        }
+        ModularContentHandlerFactory.addHandlerModule(module);
+    }
+
+    /**
+     * Register an additional module which contains URL handlers.
+     * <p>
+     * If a security manager is present, then this method invokes the security manager's {@code checkPermission} method
+     * with a <code>RuntimePermission("addURLStreamHandlerFactory")</code> permission to verify access. If
+     * access is not granted, a {@code SecurityException} will be thrown.
+     *
+     * @param module the module to add
+     */
+    public static void registerURLStreamHandlerFactoryModule(Module module) {
+        final SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            sm.checkPermission(ADD_URL_STREAM_HANDLER_FACTORY);
+        }
+        ModularURLStreamHandlerFactory.addHandlerModule(module);
+    }
+
+    /**
+     * Get the platform identifier.  This is the string that uniquely identifies the hardware and OS combination for
+     * the current running system.
+     *
+     * @return the platform identifier
+     */
+    public static String getPlatformIdentifier() {
+        return NativeLibraryResourceLoader.getArchName();
+    }
+
+    PermissionCollection getPermissionCollection() {
+        return permissionCollection;
+    }
+
+    // Linking and resolution
+
+    static final class Visited {
+        private final Module module;
+        private final FastCopyHashSet<PathFilter> filters;
+        private final FastCopyHashSet<ClassFilter> classFilters;
+        private final FastCopyHashSet<PathFilter> resourceFilters;
+        private final int hashCode;
+
+        Visited(final Module module, final FastCopyHashSet<PathFilter> filters, final FastCopyHashSet<ClassFilter> classFilters, final FastCopyHashSet<PathFilter> resourceFilters) {
+            this.module = module;
+            this.filters = filters;
+            this.classFilters = classFilters;
+            this.resourceFilters = resourceFilters;
+            hashCode = ((resourceFilters.hashCode() * 13 + classFilters.hashCode()) * 13 + filters.hashCode()) * 13 + module.hashCode();
+        }
+
+        public int hashCode() {
+            return hashCode;
+        }
+
+        public boolean equals(Object other) {
+            return other instanceof Visited && equals((Visited)other);
+        }
+
+        public boolean equals(Visited other) {
+            return this == other || other != null && module == other.module && filters.equals(other.filters) && classFilters.equals(other.classFilters) && resourceFilters.equals(other.resourceFilters);
+        }
+    }
+
+    private long addPaths(Dependency[] dependencies, Map<String, List<LocalLoader>> map, FastCopyHashSet<PathFilter> filterStack, FastCopyHashSet<ClassFilter> classFilterStack, final FastCopyHashSet<PathFilter> resourceFilterStack, Set<Visited> visited) throws ModuleLoadException {
+        long subtract = 0L;
+        moduleLoader.incScanCount();
+        for (Dependency dependency : dependencies) {
+            if (dependency instanceof ModuleDependency) {
+                final ModuleDependency moduleDependency = (ModuleDependency) dependency;
+                final ModuleLoader moduleLoader = moduleDependency.getModuleLoader();
+                final ModuleIdentifier id = moduleDependency.getIdentifier();
+                final Module module;
+
+                try {
+                    long pauseStart = Metrics.getCurrentCPUTime();
+                    try {
+                        module = moduleLoader.preloadModule(id);
+                    } finally {
+                        subtract += Metrics.getCurrentCPUTime() - pauseStart;
+                    }
+                } catch (ModuleLoadException ex) {
+                    if (moduleDependency.isOptional()) {
+                        continue;
+                    } else {
+                        log.trace("Module %s, dependency %s preload failed: %s", getIdentifier(), moduleDependency.getIdentifier(), ex);
+                        throw ex;
+                    }
+                }
+                if (module == null) {
+                    if (!moduleDependency.isOptional()) {
+                        throw new ModuleNotFoundException(id.toString());
+                    }
+                    continue;
+                }
+
+                final PathFilter importFilter = dependency.getImportFilter();
+                final FastCopyHashSet<PathFilter> nestedFilters;
+                final FastCopyHashSet<ClassFilter> nestedClassFilters;
+                final FastCopyHashSet<PathFilter> nestedResourceFilters;
+                if (filterStack.contains(importFilter)) {
+                    nestedFilters = filterStack;
+                } else {
+                    nestedFilters = filterStack.clone();
+                    nestedFilters.add(importFilter);
+                }
+                final ClassFilter classImportFilter = dependency.getClassImportFilter();
+                if (classImportFilter == ClassFilters.acceptAll() || classFilterStack.contains(classImportFilter)) {
+                    nestedClassFilters = classFilterStack;
+                } else {
+                    nestedClassFilters = classFilterStack.clone();
+                    if (classImportFilter != ClassFilters.acceptAll()) nestedClassFilters.add(classImportFilter);
+                }
+                final PathFilter resourceImportFilter = dependency.getResourceImportFilter();
+                if (resourceImportFilter == PathFilters.acceptAll() || resourceFilterStack.contains(resourceImportFilter)) {
+                    nestedResourceFilters = resourceFilterStack;
+                } else {
+                    nestedResourceFilters = resourceFilterStack.clone();
+                    if (resourceImportFilter != PathFilters.acceptAll()) nestedResourceFilters.add(resourceImportFilter);
+                }
+                subtract += module.addExportedPaths(module.getDependenciesInternal(), map, nestedFilters, nestedClassFilters, nestedResourceFilters, visited);
+            } else if (dependency instanceof ModuleClassLoaderDependency) {
+                final ModuleClassLoaderDependency classLoaderDependency = (ModuleClassLoaderDependency) dependency;
+                LocalLoader localLoader = classLoaderDependency.getLocalLoader();
+                for (Object filter : classFilterStack.getRawArray()) {
+                    if (filter != null && filter != ClassFilters.acceptAll()) {
+                        localLoader = createClassFilteredLocalLoader((ClassFilter) filter, localLoader);
+                    }
+                }
+                for (Object filter : resourceFilterStack.getRawArray()) {
+                    if (filter != null && filter != PathFilters.acceptAll()) {
+                        localLoader = createPathFilteredLocalLoader((PathFilter) filter, localLoader);
+                    }
+                }
+                ClassFilter classFilter = classLoaderDependency.getClassImportFilter();
+                if (classFilter != ClassFilters.acceptAll()) {
+                    localLoader = createClassFilteredLocalLoader(classFilter, localLoader);
+                }
+                PathFilter resourceFilter = classLoaderDependency.getResourceImportFilter();
+                if (resourceFilter != PathFilters.acceptAll()) {
+                    localLoader = createPathFilteredLocalLoader(resourceFilter, localLoader);
+                }
+                final PathFilter importFilter = classLoaderDependency.getImportFilter();
+                final Set<String> paths = classLoaderDependency.getPaths();
+                for (String path : paths) {
+                    if (importFilter.accept(path)) {
+                        List<LocalLoader> list = map.get(path);
+                        if (list == null) {
+                            map.put(path, list = new ArrayList<LocalLoader>());
+                            list.add(localLoader);
+                        } else if (! list.contains(localLoader)) {
+                            list.add(localLoader);
+                        }
+                    }
+                }
+            } else if (dependency instanceof LocalDependency) {
+                final LocalDependency localDependency = (LocalDependency) dependency;
+                LocalLoader localLoader = localDependency.getLocalLoader();
+                for (Object filter : classFilterStack.getRawArray()) {
+                    if (filter != null && filter != ClassFilters.acceptAll()) {
+                        localLoader = createClassFilteredLocalLoader((ClassFilter) filter, localLoader);
+                    }
+                }
+                for (Object filter : resourceFilterStack.getRawArray()) {
+                    if (filter != null && filter != PathFilters.acceptAll()) {
+                        localLoader = createPathFilteredLocalLoader((PathFilter) filter, localLoader);
+                    }
+                }
+                final ClassFilter classFilter = localDependency.getClassImportFilter();
+                if (classFilter != ClassFilters.acceptAll()) {
+                    localLoader = createClassFilteredLocalLoader(classFilter, localLoader);
+                }
+                final PathFilter resourceFilter = localDependency.getResourceImportFilter();
+                if (resourceFilter != PathFilters.acceptAll()) {
+                    localLoader = createPathFilteredLocalLoader(resourceFilter, localLoader);
+                }
+                final PathFilter importFilter = localDependency.getImportFilter();
+                final Set<String> paths = localDependency.getPaths();
+                for (String path : paths) {
+                    if (importFilter.accept(path)) {
+                        List<LocalLoader> list = map.get(path);
+                        if (list == null) {
+                            map.put(path, list = new ArrayList<LocalLoader>());
+                            list.add(localLoader);
+                        } else if (! list.contains(localLoader)) {
+                            list.add(localLoader);
+                        }
+                    }
+                }
+            }
+            // else unknown dep type so just skip
+        }
+        return subtract;
+    }
+
+    private LocalLoader createPathFilteredLocalLoader(PathFilter filter, LocalLoader localLoader) {
+        if (localLoader instanceof IterableLocalLoader)
+            return LocalLoaders.createIterablePathFilteredLocalLoader(filter, (IterableLocalLoader) localLoader);
+        else
+            return LocalLoaders.createPathFilteredLocalLoader(filter, localLoader);
+    }
+
+    private LocalLoader createClassFilteredLocalLoader(ClassFilter filter, LocalLoader localLoader) {
+        if (localLoader instanceof IterableLocalLoader)
+            return LocalLoaders.createIterableClassFilteredLocalLoader(filter, (IterableLocalLoader) localLoader);
+        else
+            return LocalLoaders.createClassFilteredLocalLoader(filter, localLoader);
+    }
+
+    private long addExportedPaths(Dependency[] dependencies, Map<String, List<LocalLoader>> map, FastCopyHashSet<PathFilter> filterStack, FastCopyHashSet<ClassFilter> classFilterStack, final FastCopyHashSet<PathFilter> resourceFilterStack, Set<Visited> visited) throws ModuleLoadException {
+        if (!visited.add(new Visited(this, filterStack, classFilterStack, resourceFilterStack))) {
+            return 0L;
+        }
+        long subtract = 0L;
+        moduleLoader.incScanCount();
+        for (Dependency dependency : dependencies) {
+            final PathFilter exportFilter = dependency.getExportFilter();
+            // skip non-exported dependencies altogether
+            if (exportFilter != PathFilters.rejectAll()) {
+                if (dependency instanceof ModuleDependency) {
+                    final ModuleDependency moduleDependency = (ModuleDependency) dependency;
+                    final ModuleLoader moduleLoader = moduleDependency.getModuleLoader();
+                    final ModuleIdentifier id = moduleDependency.getIdentifier();
+                    final Module module;
+
+                    try {
+                        long pauseStart = Metrics.getCurrentCPUTime();
+                        try {
+                            module = moduleLoader.preloadModule(id);
+                        } finally {
+                            subtract += Metrics.getCurrentCPUTime() - pauseStart;
+                        }
+                    } catch (ModuleLoadException ex) {
+                        if (moduleDependency.isOptional()) {
+                            continue;
+                        } else {
+                            log.trace("Module %s, dependency %s preload failed: %s", getIdentifier(), moduleDependency.getIdentifier(), ex);
+                            throw ex;
+                        }
+                    }
+                    if (module == null) {
+                        if (!moduleDependency.isOptional()) {
+                            throw new ModuleNotFoundException(id.toString());
+                        }
+                        continue;
+                    }
+
+                    final PathFilter importFilter = dependency.getImportFilter();
+                    final FastCopyHashSet<PathFilter> nestedFilters;
+                    final FastCopyHashSet<ClassFilter> nestedClassFilters;
+                    final FastCopyHashSet<PathFilter> nestedResourceFilters;
+                    if (filterStack.contains(importFilter) && filterStack.contains(exportFilter)) {
+                        nestedFilters = filterStack;
+                    } else {
+                        nestedFilters = filterStack.clone();
+                        nestedFilters.add(importFilter);
+                        nestedFilters.add(exportFilter);
+                    }
+                    final ClassFilter classImportFilter = dependency.getClassImportFilter();
+                    final ClassFilter classExportFilter = dependency.getClassExportFilter();
+                    if ((classImportFilter == ClassFilters.acceptAll() || classFilterStack.contains(classImportFilter)) && (classExportFilter == ClassFilters.acceptAll() || classFilterStack.contains(classExportFilter))) {
+                        nestedClassFilters = classFilterStack;
+                    } else {
+                        nestedClassFilters = classFilterStack.clone();
+                        if (classImportFilter != ClassFilters.acceptAll()) nestedClassFilters.add(classImportFilter);
+                        if (classExportFilter != ClassFilters.acceptAll()) nestedClassFilters.add(classExportFilter);
+                    }
+                    final PathFilter resourceImportFilter = dependency.getResourceImportFilter();
+                    final PathFilter resourceExportFilter = dependency.getResourceExportFilter();
+                    if ((resourceImportFilter == PathFilters.acceptAll() || resourceFilterStack.contains(resourceImportFilter)) && (resourceExportFilter == PathFilters.acceptAll() || resourceFilterStack.contains(resourceExportFilter))) {
+                        nestedResourceFilters = resourceFilterStack;
+                    } else {
+                        nestedResourceFilters = resourceFilterStack.clone();
+                        if (resourceImportFilter != PathFilters.acceptAll()) nestedResourceFilters.add(resourceImportFilter);
+                        if (resourceExportFilter != PathFilters.acceptAll()) nestedResourceFilters.add(resourceExportFilter);
+                    }
+                    subtract += module.addExportedPaths(module.getDependenciesInternal(), map, nestedFilters, nestedClassFilters, nestedResourceFilters, visited);
+                } else if (dependency instanceof ModuleClassLoaderDependency) {
+                    final ModuleClassLoaderDependency classLoaderDependency = (ModuleClassLoaderDependency) dependency;
+                    LocalLoader localLoader = classLoaderDependency.getLocalLoader();
+                    for (Object filter : classFilterStack.getRawArray()) {
+                        if (filter != null && filter != ClassFilters.acceptAll()) {
+                            localLoader = createClassFilteredLocalLoader((ClassFilter) filter, localLoader);
+                        }
+                    }
+                    for (Object filter : resourceFilterStack.getRawArray()) {
+                        if (filter != null && filter != PathFilters.acceptAll()) {
+                            localLoader = createPathFilteredLocalLoader((PathFilter) filter, localLoader);
+                        }
+                    }
+                    ClassFilter classImportFilter = classLoaderDependency.getClassImportFilter();
+                    if (classImportFilter != ClassFilters.acceptAll()) {
+                        localLoader = createClassFilteredLocalLoader(classImportFilter, localLoader);
+                    }
+                    ClassFilter classExportFilter = classLoaderDependency.getClassExportFilter();
+                    if (classExportFilter != ClassFilters.acceptAll()) {
+                        localLoader = createClassFilteredLocalLoader(classExportFilter, localLoader);
+                    }
+                    PathFilter resourceImportFilter = classLoaderDependency.getResourceImportFilter();
+                    if (resourceImportFilter != PathFilters.acceptAll()) {
+                        localLoader = createPathFilteredLocalLoader(resourceImportFilter, localLoader);
+                    }
+                    PathFilter resourceExportFilter = classLoaderDependency.getResourceExportFilter();
+                    if (resourceExportFilter != PathFilters.acceptAll()) {
+                        localLoader = createPathFilteredLocalLoader(resourceExportFilter, localLoader);
+                    }
+                    final PathFilter importFilter = classLoaderDependency.getImportFilter();
+                    final Set<String> paths = classLoaderDependency.getPaths();
+                    for (String path : paths) {
+                        boolean accept = ! "_private".equals(path);
+                        if (accept) for (Object filter : filterStack.getRawArray()) {
+                            if (filter != null && ! ((PathFilter)filter).accept(path)) {
+                                accept = false; break;
+                            }
+                        }
+                        if (accept && importFilter.accept(path) && exportFilter.accept(path)) {
+                            List<LocalLoader> list = map.get(path);
+                            if (list == null) {
+                                map.put(path, list = new ArrayList<LocalLoader>(1));
+                                list.add(localLoader);
+                            } else if (! list.contains(localLoader)) {
+                                list.add(localLoader);
+                            }
+                        }
+                    }
+                } else if (dependency instanceof LocalDependency) {
+                    final LocalDependency localDependency = (LocalDependency) dependency;
+                    LocalLoader localLoader = localDependency.getLocalLoader();
+                    for (Object filter : classFilterStack.getRawArray()) {
+                        if (filter != null && filter != ClassFilters.acceptAll()) {
+                            localLoader = createClassFilteredLocalLoader((ClassFilter) filter, localLoader);
+                        }
+                    }
+                    for (Object filter : resourceFilterStack.getRawArray()) {
+                        if (filter != null && filter != PathFilters.acceptAll()) {
+                            localLoader = createPathFilteredLocalLoader((PathFilter) filter, localLoader);
+                        }
+                    }
+                    ClassFilter classFilter = localDependency.getClassExportFilter();
+                    if (classFilter != ClassFilters.acceptAll()) {
+                        localLoader = createClassFilteredLocalLoader(classFilter, localLoader);
+                    }
+                    classFilter = localDependency.getClassImportFilter();
+                    if (classFilter != ClassFilters.acceptAll()) {
+                        localLoader = createClassFilteredLocalLoader(classFilter, localLoader);
+                    }
+                    PathFilter resourceFilter = localDependency.getResourceExportFilter();
+                    if (resourceFilter != PathFilters.acceptAll()) {
+                        localLoader = createPathFilteredLocalLoader(resourceFilter, localLoader);
+                    }
+                    resourceFilter = localDependency.getResourceImportFilter();
+                    if (resourceFilter != PathFilters.acceptAll()) {
+                        localLoader = createPathFilteredLocalLoader(resourceFilter, localLoader);
+                    }
+                    final Set<String> paths = localDependency.getPaths();
+                    for (String path : paths) {
+                        boolean accept = true;
+                        for (Object filter : filterStack.getRawArray()) {
+                            if (filter != null && ! ((PathFilter)filter).accept(path)) {
+                                accept = false; break;
+                            }
+                        }
+                        if (accept && localDependency.getImportFilter().accept(path) && localDependency.getExportFilter().accept(path)) {
+                            List<LocalLoader> list = map.get(path);
+                            if (list == null) {
+                                map.put(path, list = new ArrayList<LocalLoader>(1));
+                                list.add(localLoader);
+                            } else if (! list.contains(localLoader)) {
+                                list.add(localLoader);
+                            }
+                        }
+                    }
+                }
+                // else unknown dep type so just skip
+            }
+        }
+        return subtract;
+    }
+
+    Map<String, List<LocalLoader>> getPaths() throws ModuleLoadException {
+        Linkage oldLinkage = this.linkage;
+        Linkage linkage;
+        Linkage.State state = oldLinkage.getState();
+        if (state == Linkage.State.LINKED) {
+            return oldLinkage.getPaths();
+        }
+        // slow path loop
+        boolean intr = false;
+        try {
+            for (;;) {
+                synchronized (this) {
+                    oldLinkage = this.linkage;
+                    state = oldLinkage.getState();
+                    while (state == Linkage.State.LINKING || state == Linkage.State.NEW) try {
+                        wait();
+                        oldLinkage = this.linkage;
+                        state = oldLinkage.getState();
+                    } catch (InterruptedException e) {
+                        intr = true;
+                    }
+                    if (state == Linkage.State.LINKED) {
+                        return oldLinkage.getPaths();
+                    }
+                    this.linkage = linkage = new Linkage(oldLinkage.getDependencySpecs(), oldLinkage.getDependencies(), Linkage.State.LINKING);
+                    // fall out and link
+                }
+                boolean ok = false;
+                try {
+                    link(linkage);
+                    ok = true;
+                } finally {
+                    if (! ok) {
+                        // restore original (lack of) linkage
+                        synchronized (this) {
+                            if (this.linkage == linkage) {
+                                this.linkage = oldLinkage;
+                                notifyAll();
+                            }
+                        }
+                    }
+                }
+            }
+        } finally {
+            if (intr) {
+                Thread.currentThread().interrupt();
+            }
+        }
+    }
+
+    Map<String, List<LocalLoader>> getPathsUnchecked() {
+        try {
+            return getPaths();
+        } catch (ModuleLoadException e) {
+            throw e.toError();
+        }
+    }
+
+    void link(final Linkage linkage) throws ModuleLoadException {
+        final HashMap<String, List<LocalLoader>> importsMap = new HashMap<String, List<LocalLoader>>();
+        final Dependency[] dependencies = linkage.getDependencies();
+        final long start = Metrics.getCurrentCPUTime();
+        long subtractTime = 0L;
+        try {
+            final Set<Visited> visited = new FastCopyHashSet<Visited>(16);
+            final FastCopyHashSet<PathFilter> filterStack = new FastCopyHashSet<PathFilter>(8);
+            final FastCopyHashSet<ClassFilter> classFilterStack = EMPTY_CLASS_FILTERS;
+            final FastCopyHashSet<PathFilter> resourceFilterStack = EMPTY_PATH_FILTERS;
+            subtractTime += addPaths(dependencies, importsMap, filterStack, classFilterStack, resourceFilterStack, visited);
+            synchronized (this) {
+                if (this.linkage == linkage) {
+                    this.linkage = new Linkage(linkage.getDependencySpecs(), linkage.getDependencies(), Linkage.State.LINKED, importsMap);
+                    notifyAll();
+                }
+                // else all our efforts were just wasted since someone changed the deps in the meantime
+            }
+        } finally {
+            moduleLoader.addLinkTime(Metrics.getCurrentCPUTime() - start - subtractTime);
+        }
+    }
+
+    void relinkIfNecessary() throws ModuleLoadException {
+        Linkage oldLinkage = this.linkage;
+        Linkage linkage;
+        if (oldLinkage.getState() != Linkage.State.UNLINKED) {
+            return;
+        }
+        synchronized (this) {
+            oldLinkage = this.linkage;
+            if (oldLinkage.getState() != Linkage.State.UNLINKED) {
+                return;
+            }
+            this.linkage = linkage = new Linkage(oldLinkage.getDependencySpecs(), oldLinkage.getDependencies(), Linkage.State.LINKING);
+        }
+        boolean ok = false;
+        try {
+            link(linkage);
+            ok = true;
+        } finally {
+            if (! ok) {
+                // restore original (lack of) linkage
+                synchronized (this) {
+                    if (this.linkage == linkage) {
+                        this.linkage = oldLinkage;
+                        notifyAll();
+                    }
+                }
+            }
+        }
+    }
+
+    void relink() throws ModuleLoadException {
+        link(linkage);
+    }
+
+    void setDependencies(final List<DependencySpec> dependencySpecs) {
+        if (dependencySpecs == null) {
+            throw new IllegalArgumentException("dependencySpecs is null");
+        }
+        final DependencySpec[] specs = dependencySpecs.toArray(new DependencySpec[dependencySpecs.size()]);
+        for (DependencySpec spec : specs) {
+            if (spec == null) {
+                throw new IllegalArgumentException("dependencySpecs contains a null dependency specification");
+            }
+        }
+        setDependencies(specs);
+    }
+
+    void setDependencies(final DependencySpec[] dependencySpecs) {
+        synchronized (this) {
+            linkage = new Linkage(dependencySpecs, calculateDependencies(dependencySpecs), Linkage.State.UNLINKED, null);
+            notifyAll();
+        }
+    }
+
+    private Dependency[] calculateDependencies(final DependencySpec[] dependencySpecs) {
+        final Dependency[] dependencies = new Dependency[dependencySpecs.length];
+        int i = 0;
+        for (DependencySpec spec : dependencySpecs) {
+            final Dependency dependency = spec.getDependency(this);
+            dependencies[i++] = dependency;
+        }
+        return dependencies;
+    }
+
+    String getMainClass() {
+        return mainClassName;
+    }
+
+    Package getPackage(final String name) {
+        List<LocalLoader> loaders = getPathsUnchecked().get(name.replace('.', '/'));
+        if (loaders != null) for (LocalLoader localLoader : loaders) {
+            Package pkg = localLoader.loadPackageLocal(name);
+            if (pkg != null) return pkg;
+        }
+        return null;
+    }
+
+    Package[] getPackages() {
+        final ArrayList<Package> packages = new ArrayList<Package>();
+        final Map<String, List<LocalLoader>> allPaths = getPathsUnchecked();
+        next: for (String path : allPaths.keySet()) {
+            String packageName = path.replace('/', '.');
+            for (LocalLoader loader : allPaths.get(path)) {
+                Package pkg = loader.loadPackageLocal(packageName);
+                if (pkg != null) {
+                    packages.add(pkg);
+                }
+                continue next;
+            }
+        }
+        return packages.toArray(new Package[packages.size()]);
+    }
+}
diff --git a/src/main/java/org/jboss/modules/ModuleClassLoader.java b/src/main/java/org/jboss/modules/ModuleClassLoader.java
new file mode 100644
index 0000000..0a81e3a
--- /dev/null
+++ b/src/main/java/org/jboss/modules/ModuleClassLoader.java
@@ -0,0 +1,823 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.security.AccessController;
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.security.Permissions;
+import java.security.Policy;
+import java.security.PrivilegedAction;
+import java.security.ProtectionDomain;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import org.jboss.modules.filter.PathFilter;
+import org.jboss.modules.filter.PathFilters;
+import org.jboss.modules.log.ModuleLogger;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.instrument.ClassFileTransformer;
+import java.net.URL;
+import java.security.CodeSource;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
+
+/**
+ * A module classloader.  Instances of this class implement the complete view of classes and resources available in a
+ * module.  Contrast with {@link Module}, which has API methods to access the exported view of classes and resources.
+ *
+ * @author <a href="mailto:jbailey at redhat.com">John Bailey</a>
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ * @author thomas.diesler at jboss.com
+ *
+ * @apiviz.landmark
+ */
+public class ModuleClassLoader extends ConcurrentClassLoader {
+
+    private static final boolean POLICY_PERMISSIONS;
+
+    static final AtomicBoolean POLICY_READY = new AtomicBoolean();
+
+    static {
+        boolean parallelOk = true;
+        try {
+            parallelOk = ClassLoader.registerAsParallelCapable();
+        } catch (Throwable ignored) {
+        }
+        if (! parallelOk) {
+            throw new Error("Failed to register " + ModuleClassLoader.class.getName() + " as parallel-capable");
+        }
+        POLICY_PERMISSIONS = Boolean.parseBoolean(System.getProperty("jboss.modules.policy-permissions", "false"));
+    }
+
+    static final ResourceLoaderSpec[] NO_RESOURCE_LOADERS = new ResourceLoaderSpec[0];
+
+    private final Module module;
+    private final ClassFileTransformer transformer;
+
+    private volatile Paths<ResourceLoader, ResourceLoaderSpec> paths;
+
+    private final LocalLoader localLoader = new IterableLocalLoader() {
+        public Class<?> loadClassLocal(final String name, final boolean resolve) {
+            try {
+                return ModuleClassLoader.this.loadClassLocal(name, resolve);
+            } catch (ClassNotFoundException e) {
+                final Throwable cause = e.getCause();
+                if (cause instanceof Error) {
+                    throw (Error) cause;
+                } else if (cause instanceof RuntimeException) {
+                    //unlikely
+                    throw (RuntimeException) cause;
+                }
+                return null;
+            }
+        }
+
+        public Package loadPackageLocal(final String name) {
+            return findLoadedPackage(name);
+        }
+
+        public List<Resource> loadResourceLocal(final String name) {
+            return ModuleClassLoader.this.loadResourceLocal(name);
+        }
+
+        public Iterator<Resource> iterateResources(final String startPath, final boolean recursive) {
+            return ModuleClassLoader.this.iterateResources(startPath, recursive);
+        }
+
+        public String toString() {
+            return "local loader for " + ModuleClassLoader.this.toString();
+        }
+    };
+
+    private static final AtomicReferenceFieldUpdater<ModuleClassLoader, Paths<ResourceLoader, ResourceLoaderSpec>> pathsUpdater
+            = unsafeCast(AtomicReferenceFieldUpdater.newUpdater(ModuleClassLoader.class, Paths.class, "paths"));
+
+    @SuppressWarnings({ "unchecked" })
+    private static <A, B> AtomicReferenceFieldUpdater<A, B> unsafeCast(AtomicReferenceFieldUpdater<?, ?> updater) {
+        return (AtomicReferenceFieldUpdater<A, B>) updater;
+    }
+
+    /**
+     * Construct a new instance.
+     *
+     * @param configuration the module class loader configuration to use
+     */
+    protected ModuleClassLoader(final Configuration configuration) {
+        module = configuration.getModule();
+        paths = new Paths<ResourceLoader, ResourceLoaderSpec>(configuration.getResourceLoaders(), Collections.<String, List<ResourceLoader>>emptyMap());
+        final AssertionSetting setting = configuration.getAssertionSetting();
+        if (setting != AssertionSetting.INHERIT) {
+            setDefaultAssertionStatus(setting == AssertionSetting.ENABLED);
+        }
+        transformer = configuration.getTransformer();
+    }
+
+    /**
+     * Recalculate the path maps for this module class loader.
+     *
+     * @return {@code true} if the paths were recalculated, or {@code false} if another thread finished recalculating
+     *  before the calling thread
+     */
+    boolean recalculate() {
+        final Paths<ResourceLoader, ResourceLoaderSpec> paths = this.paths;
+        return setResourceLoaders(paths, paths.getSourceList(NO_RESOURCE_LOADERS));
+    }
+
+    /**
+     * Change the set of resource loaders for this module class loader, and recalculate the path maps.
+     *
+     * @param resourceLoaders the new resource loaders
+     * @return {@code true} if the paths were recalculated, or {@code false} if another thread finished recalculating
+     *  before the calling thread
+     */
+    boolean setResourceLoaders(final ResourceLoaderSpec[] resourceLoaders) {
+        return setResourceLoaders(paths, resourceLoaders);
+    }
+
+    private boolean setResourceLoaders(final Paths<ResourceLoader, ResourceLoaderSpec> paths, final ResourceLoaderSpec[] resourceLoaders) {
+        final Map<String, List<ResourceLoader>> allPaths = new HashMap<String, List<ResourceLoader>>();
+        for (ResourceLoaderSpec loaderSpec : resourceLoaders) {
+            final ResourceLoader loader = loaderSpec.getResourceLoader();
+            final PathFilter filter = loaderSpec.getPathFilter();
+            for (String path : loader.getPaths()) {
+                if (filter.accept(path)) {
+                    final List<ResourceLoader> allLoaders = allPaths.get(path);
+                    if (allLoaders == null) {
+                        ArrayList<ResourceLoader> newList = new ArrayList<ResourceLoader>(16);
+                        newList.add(loader);
+                        allPaths.put(path, newList);
+                    } else {
+                        allLoaders.add(loader);
+                    }
+                }
+            }
+        }
+        return pathsUpdater.compareAndSet(this, paths, new Paths<ResourceLoader, ResourceLoaderSpec>(resourceLoaders, allPaths));
+    }
+
+    /**
+     * Get the local loader which refers to this module class loader.
+     *
+     * @return the local loader
+     */
+    LocalLoader getLocalLoader() {
+        return localLoader;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected final Class<?> findClass(String className, boolean exportsOnly, final boolean resolve) throws ClassNotFoundException {
+        // Check if we have already loaded it..
+        Class<?> loadedClass = findLoadedClass(className);
+        if (loadedClass != null) {
+            if (resolve) {
+                resolveClass(loadedClass);
+            }
+            return loadedClass;
+        }
+        final ModuleLogger log = Module.log;
+        final Module module = this.module;
+        log.trace("Finding class %s from %s", className, module);
+
+        final Class<?> clazz = module.loadModuleClass(className, resolve);
+
+        if (clazz != null) {
+            return clazz;
+        }
+
+        log.trace("Class %s not found from %s", className, module);
+
+        throw new ClassNotFoundException(className + " from [" + module + "]");
+    }
+
+    /**
+     * Load a class from this class loader.
+     *
+     * @param className the class name to load
+     * @return the loaded class or {@code null} if it was not found
+     * @throws ClassNotFoundException if an exception occurs while loading the class or its dependencies
+     */
+    public Class<?> loadClassLocal(String className) throws ClassNotFoundException {
+        return loadClassLocal(className, false);
+    }
+
+    /**
+     * Load a local class from this class loader.
+     *
+     * @param className the class name
+     * @param resolve {@code true} to resolve the loaded class
+     * @return the loaded class or {@code null} if it was not found
+     * @throws ClassNotFoundException if an error occurs while loading the class
+     */
+    public Class<?> loadClassLocal(final String className, final boolean resolve) throws ClassNotFoundException {
+        final ModuleLogger log = Module.log;
+        final Module module = this.module;
+        log.trace("Finding local class %s from %s", className, module);
+
+        // Check if we have already loaded it..
+        Class<?> loadedClass = findLoadedClass(className);
+        if (loadedClass != null) {
+            log.trace("Found previously loaded %s from %s", loadedClass, module);
+            if (resolve) {
+                resolveClass(loadedClass);
+            }
+            return loadedClass;
+        }
+
+        final Map<String, List<ResourceLoader>> paths = this.paths.getAllPaths();
+
+        log.trace("Loading class %s locally from %s", className, module);
+
+        String pathOfClass = Module.pathOfClass(className);
+        final List<ResourceLoader> loaders = paths.get(pathOfClass);
+        if (loaders == null) {
+            // no loaders for this path
+            return null;
+        }
+
+        // Check to see if we can define it locally it
+        ClassSpec classSpec;
+        ResourceLoader resourceLoader;
+        try {
+            if (loaders.size() > 0) {
+                String fileName = Module.fileNameOfClass(className);
+                for (ResourceLoader loader : loaders) {
+                    classSpec = loader.getClassSpec(fileName);
+                    if (classSpec != null) {
+                        resourceLoader = loader;
+                        try {
+                            preDefine(classSpec, className);
+                        }
+                        catch (Throwable th) {
+                            throw new ClassNotFoundException("Failed to preDefine class: " + className, th);
+                        }
+                        final Class<?> clazz = defineClass(className, classSpec, resourceLoader);
+                        try {
+                            postDefine(classSpec, clazz);
+                        }
+                        catch (Throwable th) {
+                            throw new ClassNotFoundException("Failed to postDefine class: " + className, th);
+                        }
+                        if (resolve) {
+                            resolveClass(clazz);
+                        }
+                        return clazz;
+                    }
+                }
+            }
+        } catch (IOException e) {
+            throw new ClassNotFoundException(className, e);
+        } catch (RuntimeException e) {
+            log.trace(e, "Unexpected runtime exception in module loader");
+            throw new ClassNotFoundException(className, e);
+        } catch (Error e) {
+            log.trace(e, "Unexpected error in module loader");
+            throw e;
+        }
+        log.trace("No local specification found for class %s in %s", className, module);
+        return null;
+    }
+
+    /**
+     * Load a local resource from a specific root from this module class loader.
+     *
+     * @param root the root name
+     * @param name the resource name
+     * @return the resource, or {@code null} if it was not found
+     */
+    Resource loadResourceLocal(final String root, final String name) {
+
+        final Map<String, List<ResourceLoader>> paths = this.paths.getAllPaths();
+
+        final String path = Module.pathOf(name);
+
+        final List<ResourceLoader> loaders = paths.get(path);
+        if (loaders == null) {
+            // no loaders for this path
+            return null;
+        }
+
+        for (ResourceLoader loader : loaders) {
+            if (root.equals(loader.getRootName())) {
+                return loader.getResource(name);
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Load a local resource from this class loader.
+     *
+     * @param name the resource name
+     * @return the list of resources
+     */
+    public List<Resource> loadResourceLocal(final String name) {
+        final Map<String, List<ResourceLoader>> paths = this.paths.getAllPaths();
+
+        final String path = Module.pathOf(name);
+
+        final List<ResourceLoader> loaders = paths.get(path);
+        if (loaders == null) {
+            // no loaders for this path
+            return Collections.emptyList();
+        }
+
+        final List<Resource> list = new ArrayList<Resource>(loaders.size());
+        for (ResourceLoader loader : loaders) {
+            final Resource resource = loader.getResource(name);
+            if (resource != null) {
+                list.add(resource);
+            }
+        }
+        return list.isEmpty() ? Collections.<Resource>emptyList() : list;
+    }
+
+    private Class<?> doDefineOrLoadClass(final String className, final byte[] bytes, int off, int len, ProtectionDomain protectionDomain) {
+        try {
+            final Class<?> definedClass = defineClass(className, bytes, off, len, protectionDomain);
+            module.getModuleLoader().incClassCount();
+            return definedClass;
+        } catch (LinkageError e) {
+            final Class<?> loadedClass = findLoadedClass(className);
+            if (loadedClass != null) {
+                module.getModuleLoader().incRaceCount();
+                return loadedClass;
+            }
+            throw e;
+        }
+    }
+
+    private final IdentityHashMap<CodeSource, ProtectionDomain> protectionDomains = new IdentityHashMap<CodeSource, ProtectionDomain>();
+
+    private static final PrivilegedAction<Policy> GET_POLICY_ACTION = new PrivilegedAction<Policy>() {
+        public Policy run() {
+            return Policy.getPolicy();
+        }
+    };
+
+    private ProtectionDomain getProtectionDomain(CodeSource codeSource) {
+        final IdentityHashMap<CodeSource, ProtectionDomain> map = protectionDomains;
+        synchronized (map) {
+            ProtectionDomain protectionDomain = map.get(codeSource);
+            if (protectionDomain == null) {
+                final PermissionCollection permissions = module.getPermissionCollection();
+                if (POLICY_PERMISSIONS && POLICY_READY.get()) {
+                    final Policy policy = AccessController.doPrivileged(GET_POLICY_ACTION);
+                    if (policy != null) {
+                        final PermissionCollection policyPermissions = policy.getPermissions(codeSource);
+                        if (policyPermissions != null && policyPermissions != Policy.UNSUPPORTED_EMPTY_COLLECTION) {
+                            if (permissions == null) {
+                                protectionDomain = new ProtectionDomain(codeSource, policyPermissions);
+                            } else {
+                                final Enumeration<Permission> e2 = policyPermissions.elements();
+                                final Enumeration<Permission> e1 = permissions.elements();
+                                if (e2.hasMoreElements()) {
+                                    if (e1.hasMoreElements()) {
+                                        final Permissions combined = new Permissions();
+                                        do {
+                                            combined.add(e1.nextElement());
+                                        } while (e1.hasMoreElements());
+                                        while (e2.hasMoreElements()) {
+                                            combined.add(e2.nextElement());
+                                        }
+                                        combined.setReadOnly();
+                                        protectionDomain = new ProtectionDomain(codeSource, combined);
+                                    } else {
+                                        protectionDomain = new ProtectionDomain(codeSource, policyPermissions);
+                                    }
+                                } else {
+                                    protectionDomain = new ProtectionDomain(codeSource, permissions);
+                                }
+                            }
+                        } else {
+                            protectionDomain = new ProtectionDomain(codeSource, permissions);
+                        }
+                    } else {
+                        protectionDomain = new ProtectionDomain(codeSource, permissions);
+                    }
+                } else {
+                    protectionDomain = new ProtectionDomain(codeSource, permissions);
+                }
+                map.put(codeSource, protectionDomain);
+            }
+            return protectionDomain;
+        }
+    }
+
+    /**
+     * Define a class from a class name and class spec.  Also defines any enclosing {@link Package} instances,
+     * and performs any sealed-package checks.
+     *
+     * @param name the class name
+     * @param classSpec the class spec
+     * @param resourceLoader the resource loader of the class spec
+     * @return the new class
+     */
+    private Class<?> defineClass(final String name, final ClassSpec classSpec, final ResourceLoader resourceLoader) {
+        final ModuleLogger log = Module.log;
+        final Module module = this.module;
+        log.trace("Attempting to define class %s in %s", name, module);
+
+        // Ensure that the package is loaded
+        final int lastIdx = name.lastIndexOf('.');
+        if (lastIdx != -1) {
+            // there's a package name; get the Package for it
+            final String packageName = name.substring(0, lastIdx);
+            synchronized (this) {
+                Package pkg = findLoadedPackage(packageName);
+                if (pkg == null) {
+                    try {
+                        pkg = definePackage(packageName, resourceLoader.getPackageSpec(packageName));
+                    } catch (IOException e) {
+                        pkg = definePackage(packageName, null);
+                    }
+                }
+                // Check sealing
+                if (pkg.isSealed() && ! pkg.isSealed(classSpec.getCodeSource().getLocation())) {
+                    log.trace("Detected a sealing violation (attempt to define class %s in sealed package %s in %s)", name, packageName, module);
+                    // use the same message as the JDK
+                    throw new SecurityException("sealing violation: package " + packageName + " is sealed");
+                }
+            }
+        }
+        final Class<?> newClass;
+        try {
+            byte[] bytes = classSpec.getBytes();
+            try {
+                final ProtectionDomain protectionDomain = getProtectionDomain(classSpec.getCodeSource());
+                if (transformer != null) {
+                    try {
+                        bytes = transformer.transform(this, name.replace('.', '/'), null, protectionDomain, bytes);
+                    } catch (Exception e) {
+                        ClassFormatError error = new ClassFormatError(e.getMessage());
+                        error.initCause(e);
+                        throw error;
+                    }
+                }
+                final long start = Metrics.getCurrentCPUTime();
+                newClass = doDefineOrLoadClass(name, bytes, 0, bytes.length, protectionDomain);
+                module.getModuleLoader().addClassLoadTime(Metrics.getCurrentCPUTime() - start);
+                log.classDefined(name, module);
+            } catch (NoClassDefFoundError e) {
+                // Prepend the current class name, so that transitive class definition issues are clearly expressed
+                final LinkageError ne = new LinkageError("Failed to link " + name.replace('.', '/') + " (" + module + ")");
+                ne.initCause(e);
+                throw ne;
+            }
+        } catch (Error e) {
+            log.classDefineFailed(e, name, module);
+            throw e;
+        } catch (RuntimeException e) {
+            log.classDefineFailed(e, name, module);
+            throw e;
+        }
+        final AssertionSetting setting = classSpec.getAssertionSetting();
+        if (setting != AssertionSetting.INHERIT) {
+            setClassAssertionStatus(name, setting == AssertionSetting.ENABLED);
+        }
+        return newClass;
+    }
+
+    /**
+     * A hook which is invoked before a class is defined.
+     *
+     * @param classSpec the class spec of the defined class
+     * @param className the class to be defined
+     */
+    @SuppressWarnings("unused")
+    protected void preDefine(ClassSpec classSpec, String className) {
+    }
+
+    /**
+     * A hook which is invoked after a class is defined.
+     *
+     * @param classSpec the class spec of the defined class
+     * @param definedClass the class that was defined
+     */
+    @SuppressWarnings("unused")
+    protected void postDefine(ClassSpec classSpec, Class<?> definedClass) {
+    }
+
+    /**
+     * Define a package from a package spec.
+     *
+     * @param name the package name
+     * @param spec the package specification
+     * @return the new package
+     */
+    private Package definePackage(final String name, final PackageSpec spec) {
+        final Module module = this.module;
+        final ModuleLogger log = Module.log;
+        log.trace("Attempting to define package %s in %s", name, module);
+
+        final Package pkg;
+        if (spec == null) {
+            pkg = definePackage(name, null, null, null, null, null, null, null);
+        } else {
+            pkg = definePackage(name, spec.getSpecTitle(), spec.getSpecVersion(), spec.getSpecVendor(), spec.getImplTitle(), spec.getImplVersion(), spec.getImplVendor(), spec.getSealBase());
+            final AssertionSetting setting = spec.getAssertionSetting();
+            if (setting != AssertionSetting.INHERIT) {
+                setPackageAssertionStatus(name, setting == AssertionSetting.ENABLED);
+            }
+        }
+        log.trace("Defined package %s in %s", name, module);
+        return pkg;
+    }
+
+    /**
+     * Find a library from one of the resource loaders.
+     *
+     * @param libname the library name
+     * @return the full absolute path to the library
+     */
+    @Override
+    protected final String findLibrary(final String libname) {
+        final ModuleLogger log = Module.log;
+        log.trace("Attempting to load native library %s from %s", libname, module);
+
+        for (ResourceLoaderSpec loader : paths.getSourceList(NO_RESOURCE_LOADERS)) {
+            final String library = loader.getResourceLoader().getLibrary(libname);
+            if (library != null) {
+                return library;
+            }
+        }
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final URL findResource(final String name, final boolean exportsOnly) {
+        return module.getResource(name);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final Enumeration<URL> findResources(final String name, final boolean exportsOnly) throws IOException {
+        return module.getResources(name);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final InputStream findResourceAsStream(final String name, boolean exportsOnly) {
+        try {
+            return module.getResourceAsStream(name);
+        } catch (IOException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Get the module for this class loader.
+     *
+     * @return the module
+     */
+    public final Module getModule() {
+        return module;
+    }
+
+    /**
+     * Get a string representation of this class loader.
+     *
+     * @return the string
+     */
+    @Override
+    public final String toString() {
+        return getClass().getSimpleName() + " for " + module;
+    }
+
+    Set<String> getPaths() {
+        return paths.getAllPaths().keySet();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected final Package definePackage(final String name, final String specTitle, final String specVersion, final String specVendor, final String implTitle, final String implVersion, final String implVendor, final URL sealBase) throws IllegalArgumentException {
+        return super.definePackage(name, specTitle, specVersion, specVendor, implTitle, implVersion, implVendor, sealBase);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected final Package getPackageByName(final String name) {
+        Package loaded = findLoadedPackage(name);
+        if (loaded != null) {
+            return loaded;
+        }
+        return module.getPackage(name);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected final Package[] getPackages() {
+        return module.getPackages();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final void setDefaultAssertionStatus(final boolean enabled) {
+        super.setDefaultAssertionStatus(enabled);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final void setPackageAssertionStatus(final String packageName, final boolean enabled) {
+        super.setPackageAssertionStatus(packageName, enabled);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final void setClassAssertionStatus(final String className, final boolean enabled) {
+        super.setClassAssertionStatus(className, enabled);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final void clearAssertionStatus() {
+        super.clearAssertionStatus();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final int hashCode() {
+        return super.hashCode();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final boolean equals(final Object obj) {
+        return super.equals(obj);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected final Object clone() throws CloneNotSupportedException {
+        return super.clone();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected final void finalize() throws Throwable {
+        super.finalize();
+    }
+
+    ResourceLoader[] getResourceLoaders() {
+        final ResourceLoaderSpec[] specs = paths.getSourceList(NO_RESOURCE_LOADERS);
+        final int length = specs.length;
+        final ResourceLoader[] loaders = new ResourceLoader[length];
+        for (int i = 0; i < length; i++) {
+            loaders[i] = specs[i].getResourceLoader();
+        }
+        return loaders;
+    }
+
+    /**
+     * Iterate the resources within this module class loader.  Only resource roots which are inherently iterable will
+     * be checked, thus the result of this method may only be a subset of the actual loadable resources.  The returned
+     * resources are not sorted or grouped in any particular way.
+     *
+     * @param startName the directory name to search
+     * @param recurse {@code true} to recurse into subdirectories, {@code false} otherwise
+     * @return the resource iterator
+     */
+    public final Iterator<Resource> iterateResources(final String startName, final boolean recurse) {
+        final String realStartName = PathUtils.canonicalize(PathUtils.relativize(startName));
+        final PathFilter filter;
+        if (recurse) {
+            if (realStartName.isEmpty()) {
+                filter = PathFilters.acceptAll();
+            } else {
+                filter = PathFilters.any(PathFilters.is(realStartName), PathFilters.isChildOf(realStartName));
+            }
+        } else {
+            filter = PathFilters.is(realStartName);
+        }
+        final Map<String, List<ResourceLoader>> paths = this.paths.getAllPaths();
+        final Iterator<Map.Entry<String, List<ResourceLoader>>> iterator = paths.entrySet().iterator();
+        return new Iterator<Resource>() {
+
+            private String path;
+            private Iterator<Resource> resourceIterator;
+            private Iterator<ResourceLoader> loaderIterator;
+            private Resource next;
+
+            public boolean hasNext() {
+                while (next == null) {
+                    if (resourceIterator != null) {
+                        assert path != null;
+                        if (resourceIterator.hasNext()) {
+                            next = resourceIterator.next();
+                            return true;
+                        }
+                        resourceIterator = null;
+                    }
+                    if (loaderIterator != null) {
+                        assert path != null;
+                        if (loaderIterator.hasNext()) {
+                            final ResourceLoader loader = loaderIterator.next();
+                            if (loader instanceof IterableResourceLoader) {
+                                resourceIterator = ((IterableResourceLoader)loader).iterateResources(path, false);
+                                continue;
+                            }
+                        }
+                        loaderIterator = null;
+                    }
+                    if (! iterator.hasNext()) {
+                        return false;
+                    }
+                    final Map.Entry<String, List<ResourceLoader>> entry = iterator.next();
+                    path = entry.getKey();
+                    if (filter.accept(path)) {
+                        loaderIterator = entry.getValue().iterator();
+                    }
+                }
+                return true;
+            }
+
+            public Resource next() {
+                if (! hasNext()) throw new NoSuchElementException();
+                try {
+                    return next;
+                } finally {
+                    next = null;
+                }
+            }
+
+            public void remove() {
+                throw new UnsupportedOperationException();
+            }
+        };
+    }
+
+    /**
+     * Get the (unmodifiable) set of paths which are locally available in this module class loader.  The set will
+     * include all paths defined by the module's resource loaders, minus any paths excluded by filters.  The set will
+     * generally always contain an empty entry ("").  The set is unordered and unsorted, and is iterable in O(n) time
+     * and accessible in O(1) time.
+     *
+     * @return the set of local paths
+     */
+    public final Set<String> getLocalPaths() {
+        return Collections.unmodifiableSet(paths.getAllPaths().keySet());
+    }
+
+    /**
+     * An opaque configuration used internally to create a module class loader.
+     *
+     * @apiviz.exclude
+     */
+    protected static final class Configuration {
+        private final Module module;
+        private final AssertionSetting assertionSetting;
+        private final ResourceLoaderSpec[] resourceLoaders;
+        private final ClassFileTransformer transformer;
+
+        Configuration(final Module module, final AssertionSetting assertionSetting, final ResourceLoaderSpec[] resourceLoaders, final ClassFileTransformer transformer) {
+            this.module = module;
+            this.assertionSetting = assertionSetting;
+            this.resourceLoaders = resourceLoaders;
+            this.transformer = transformer;
+        }
+
+        Module getModule() {
+            return module;
+        }
+
+        AssertionSetting getAssertionSetting() {
+            return assertionSetting;
+        }
+
+        ResourceLoaderSpec[] getResourceLoaders() {
+            return resourceLoaders;
+        }
+
+        ClassFileTransformer getTransformer() {
+            return transformer;
+        }
+    }
+}
diff --git a/src/main/java/org/jboss/modules/ModuleClassLoaderDependency.java b/src/main/java/org/jboss/modules/ModuleClassLoaderDependency.java
new file mode 100644
index 0000000..1f35ad3
--- /dev/null
+++ b/src/main/java/org/jboss/modules/ModuleClassLoaderDependency.java
@@ -0,0 +1,52 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.util.Set;
+import org.jboss.modules.filter.ClassFilter;
+import org.jboss.modules.filter.PathFilter;
+
+/**
+* @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+*/
+final class ModuleClassLoaderDependency extends Dependency {
+    private final ModuleClassLoader moduleClassLoader;
+
+    ModuleClassLoaderDependency(final PathFilter exportFilter, final PathFilter importFilter, final ModuleClassLoader moduleClassLoader) {
+        super(exportFilter, importFilter);
+        this.moduleClassLoader = moduleClassLoader;
+    }
+
+    ModuleClassLoaderDependency(final PathFilter exportFilter, final PathFilter importFilter, final PathFilter resourceExportFilter, final PathFilter resourceImportFilter, final ClassFilter classExportFilter, final ClassFilter classImportFilter, final ModuleClassLoader moduleClassLoader) {
+        super(exportFilter, importFilter, resourceExportFilter, resourceImportFilter, classExportFilter, classImportFilter);
+        this.moduleClassLoader = moduleClassLoader;
+    }
+
+    LocalLoader getLocalLoader() {
+        return moduleClassLoader.getLocalLoader();
+    }
+
+    Set<String> getPaths() {
+        return moduleClassLoader.getPaths();
+    }
+
+    public String toString() {
+        return "dependency on " + moduleClassLoader;
+    }
+}
diff --git a/src/main/java/org/jboss/modules/ModuleClassLoaderFactory.java b/src/main/java/org/jboss/modules/ModuleClassLoaderFactory.java
new file mode 100644
index 0000000..fcf8841
--- /dev/null
+++ b/src/main/java/org/jboss/modules/ModuleClassLoaderFactory.java
@@ -0,0 +1,35 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+/**
+ * A producer for Module class loaders.
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public interface ModuleClassLoaderFactory {
+
+    /**
+     * Create the module class loader.  If this method returns {@code null}, then a default implementation is used.
+     *
+     * @param configuration the configuration to use
+     * @return the module class loader, or {@code null} to use the default
+     */
+    ModuleClassLoader create(ModuleClassLoader.Configuration configuration);
+}
diff --git a/src/main/java/org/jboss/modules/ModuleDependency.java b/src/main/java/org/jboss/modules/ModuleDependency.java
new file mode 100644
index 0000000..af2ec1f
--- /dev/null
+++ b/src/main/java/org/jboss/modules/ModuleDependency.java
@@ -0,0 +1,61 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import org.jboss.modules.filter.ClassFilter;
+import org.jboss.modules.filter.PathFilter;
+
+/**
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+final class ModuleDependency extends Dependency {
+    private final ModuleLoader moduleLoader;
+    private final ModuleIdentifier identifier;
+    private final boolean optional;
+
+    ModuleDependency(final PathFilter exportFilter, final PathFilter importFilter, final ModuleLoader moduleLoader, final ModuleIdentifier identifier, final boolean optional) {
+        super(exportFilter, importFilter);
+        this.moduleLoader = moduleLoader;
+        this.identifier = identifier;
+        this.optional = optional;
+    }
+
+    ModuleDependency(final PathFilter exportFilter, final PathFilter importFilter, final PathFilter resourceExportFilter, final PathFilter resourceImportFilter, final ClassFilter classExportFilter, final ClassFilter classImportFilter, final ModuleLoader moduleLoader, final ModuleIdentifier identifier, final boolean optional) {
+        super(exportFilter, importFilter, resourceExportFilter, resourceImportFilter, classExportFilter, classImportFilter);
+        this.moduleLoader = moduleLoader;
+        this.identifier = identifier;
+        this.optional = optional;
+    }
+
+    ModuleIdentifier getIdentifier() {
+        return identifier;
+    }
+
+    boolean isOptional() {
+        return optional;
+    }
+
+    ModuleLoader getModuleLoader() {
+        return moduleLoader;
+    }
+
+    public String toString() {
+        return (optional ? "optional " : "" ) + "dependency on " + identifier + " (" + moduleLoader + ")";
+    }
+}
diff --git a/src/main/java/org/jboss/modules/ModuleDependencySpec.java b/src/main/java/org/jboss/modules/ModuleDependencySpec.java
new file mode 100644
index 0000000..3ca6ff8
--- /dev/null
+++ b/src/main/java/org/jboss/modules/ModuleDependencySpec.java
@@ -0,0 +1,75 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import org.jboss.modules.filter.ClassFilter;
+import org.jboss.modules.filter.PathFilter;
+
+/**
+ * A dependency specification on a module.
+ */
+public final class ModuleDependencySpec extends DependencySpec {
+
+    private final ModuleLoader moduleLoader;
+    private final ModuleIdentifier identifier;
+    private final boolean optional;
+
+    ModuleDependencySpec(final PathFilter importFilter, final PathFilter exportFilter, final PathFilter resourceImportFilter, final PathFilter resourceExportFilter, final ClassFilter classImportFilter, final ClassFilter classExportFilter, final ModuleLoader moduleLoader, final ModuleIdentifier identifier, final boolean optional) {
+        super(importFilter, exportFilter, resourceImportFilter, resourceExportFilter, classImportFilter, classExportFilter);
+        this.moduleLoader = moduleLoader;
+        this.identifier = identifier;
+        this.optional = optional;
+    }
+
+    Dependency getDependency(final Module module) {
+        final ModuleLoader loader = moduleLoader;
+        return new ModuleDependency(exportFilter, importFilter, resourceExportFilter, resourceImportFilter, classExportFilter, classImportFilter, loader == null ? module.getModuleLoader() : loader, identifier, optional);
+    }
+
+    /**
+     * Get the module loader of this dependency, or {@code null} if the defined module's loader is to be used.
+     *
+     * @return the module loader
+     */
+    public ModuleLoader getModuleLoader() {
+        return moduleLoader;
+    }
+
+    /**
+     * Get the module identifier of the dependency.
+     *
+     * @return the module identifier
+     */
+    public ModuleIdentifier getIdentifier() {
+        return identifier;
+    }
+
+    /**
+     * Determine whether this dependency is optional.
+     *
+     * @return {@code true} if the dependency is optional, {@code false} if it is required
+     */
+    public boolean isOptional() {
+        return optional;
+    }
+
+    public String toString() {
+        return "dependency on " + identifier;
+    }
+}
diff --git a/src/main/java/org/jboss/modules/ModuleFinder.java b/src/main/java/org/jboss/modules/ModuleFinder.java
new file mode 100644
index 0000000..63c4ab1
--- /dev/null
+++ b/src/main/java/org/jboss/modules/ModuleFinder.java
@@ -0,0 +1,36 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+/**
+ * A locator for module definitions.
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public interface ModuleFinder {
+
+    /**
+     * Find a module specification for the given identifier.
+     *
+     * @param identifier the module identifier
+     * @param delegateLoader the module loader from which dependencies should be resolved
+     * @return the module specification, or {@code null} if no specification is found for this identifier
+     */
+    ModuleSpec findModule(ModuleIdentifier identifier, ModuleLoader delegateLoader) throws ModuleLoadException;
+}
diff --git a/src/main/java/org/jboss/modules/ModuleIdentifier.java b/src/main/java/org/jboss/modules/ModuleIdentifier.java
new file mode 100644
index 0000000..c42176e
--- /dev/null
+++ b/src/main/java/org/jboss/modules/ModuleIdentifier.java
@@ -0,0 +1,276 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.Serializable;
+import java.lang.reflect.Field;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+/**
+ * A unique identifier for a module within a module loader.
+ *
+ * @author <a href="mailto:jbailey at redhat.com">John Bailey</a>
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ * @author Jason T. Greene
+ *
+ * @apiviz.landmark
+ */
+public final class ModuleIdentifier implements Serializable {
+
+    private static final long serialVersionUID = 118533026624827995L;
+
+    private static final String DEFAULT_SLOT = "main";
+
+    private final String name;
+    private final String slot;
+
+    private final transient int hashCode;
+
+    private static final Field hashField;
+
+    static {
+        hashField = AccessController.doPrivileged(new PrivilegedAction<Field>() {
+            public Field run() {
+                final Field field;
+                try {
+                    field = ModuleIdentifier.class.getDeclaredField("hashCode");
+                    field.setAccessible(true);
+                } catch (NoSuchFieldException e) {
+                    throw new NoSuchFieldError(e.getMessage());
+                }
+                return field;
+            }
+        });
+    }
+
+    /**
+     * The class path module (only present if booted from a class path).
+     */
+    public static final ModuleIdentifier CLASSPATH = new ModuleIdentifier("Classpath", DEFAULT_SLOT);
+
+    private ModuleIdentifier(final String name, final String slot) {
+        this.name = name;
+        this.slot = slot;
+        hashCode = calculateHashCode(name, slot);
+    }
+
+    private static int calculateHashCode(final String name, final String slot) {
+        int h = 17;
+        h = 37 * h + name.hashCode();
+        h = 37 * h + slot.hashCode();
+        return h;
+    }
+
+    /**
+     * Get the module name.
+     *
+     * @return the module name
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Get the module version slot.
+     *
+     * @return the version slot
+     */
+    public String getSlot() {
+        return slot;
+    }
+
+    /**
+     * Determine whether this object is equal to another.
+     *
+     * @param other the other object
+     * @return {@code true} if they are equal, {@code false} otherwise
+     */
+    public boolean equals(Object other) {
+        return other instanceof ModuleIdentifier && equals((ModuleIdentifier)other);
+    }
+
+    /**
+     * Determine whether this object is equal to another.
+     *
+     * @param other the other object
+     * @return {@code true} if they are equal, {@code false} otherwise
+     */
+    public boolean equals(ModuleIdentifier other) {
+        return this == other || other != null && name.equals(other.name) && slot.equals(other.slot);
+    }
+
+    /**
+     * Determine the hash code of this module identifier.
+     *
+     * @return the hash code
+     */
+    @Override
+    public int hashCode() {
+        return hashCode;
+    }
+
+    /**
+     * Get the string representation of this module identifier.
+     *
+     * @return the string representation
+     */
+    @Override
+    public String toString() {
+        return escapeName(name) + ":" + escapeSlot(slot);
+    }
+
+    private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
+        ois.defaultReadObject();
+        try {
+            hashField.setInt(this, calculateHashCode(name, slot));
+        } catch (IllegalAccessException e) {
+            throw new IllegalAccessError(e.getMessage());
+        }
+    }
+
+    private static String escapeName(String name) {
+        final StringBuilder b = new StringBuilder();
+        boolean escaped = false;
+        int c;
+        for (int i = 0; i < name.length(); i = name.offsetByCodePoints(i, 1)) {
+            c = name.codePointAt(i);
+            switch (c) {
+                case '\\':
+                case ':':
+                    escaped = true;
+                    b.append('\\');
+                    // fall thru
+                default:
+                    b.append(c);
+            }
+        }
+        return escaped ? b.toString() : name;
+    }
+
+    private static String escapeSlot(String slot) {
+        final StringBuilder b = new StringBuilder();
+        boolean escaped = false;
+        int c;
+        for (int i = 0; i < slot.length(); i = slot.offsetByCodePoints(i, 1)) {
+            c = slot.codePointAt(i);
+            switch (c) {
+                case '\\':
+                    escaped = true;
+                    b.append('\\');
+                    // fall thru
+                default:
+                    b.append(c);
+            }
+        }
+        return escaped ? b.toString() : slot;
+    }
+
+    /**
+     * Parse a module specification from a string.
+     *
+     * @param moduleSpec the specification string
+     * @return the module identifier
+     * @throws IllegalArgumentException if the format of the module specification is invalid or it is {@code null}
+     */
+    public static ModuleIdentifier fromString(String moduleSpec) throws IllegalArgumentException {
+        if (moduleSpec == null) {
+            throw new IllegalArgumentException("Module specification is null");
+        }
+        if (moduleSpec.length() == 0) {
+            throw new IllegalArgumentException("Empty module specification");
+        }
+
+        int c;
+        final StringBuilder b = new StringBuilder();
+        int i = 0;
+        while (i < moduleSpec.length()) {
+            c = moduleSpec.codePointAt(i);
+            if (c == '\\') {
+                b.appendCodePoint(c);
+                i = moduleSpec.offsetByCodePoints(i, 1);
+                if (i < moduleSpec.length()) {
+                    c = moduleSpec.codePointAt(i);
+                    b.appendCodePoint(c);
+                } else {
+                    throw new IllegalArgumentException("Name has an unterminated escape");
+                }
+            } else if (c == ':') {
+                i = moduleSpec.offsetByCodePoints(i, 1);
+                if (i == moduleSpec.length()) {
+                    throw new IllegalArgumentException("Slot is empty");
+                }
+                // end of name, now comes the slot
+                break;
+            } else {
+                b.appendCodePoint(c);
+            }
+            i = moduleSpec.offsetByCodePoints(i, 1);
+        }
+        final String name = b.toString();
+        b.setLength(0);
+        if (i < moduleSpec.length()) do {
+            c = moduleSpec.codePointAt(i);
+            b.appendCodePoint(c);
+            i = moduleSpec.offsetByCodePoints(i, 1);
+        } while (i < moduleSpec.length()); else {
+            return new ModuleIdentifier(name, DEFAULT_SLOT);
+        }
+        return new ModuleIdentifier(name, b.toString());
+    }
+
+    /**
+     * Creates a new module identifier using the specified name and slot.
+     * A slot allows for multiple modules to exist with the same name.
+     * The main usage pattern for this is to differentiate between
+     * two incompatible release streams of a module.
+     *
+     * Normally all module definitions wind up in the "main" slot.
+     * An unspecified or null slot will result in placement in the "main"
+     * slot.
+     *
+     * Unless you have a true need for a slot, it should not be specified.
+     * When in doubt use the {{@link #create(String)} method instead.
+     *
+     * @param name the name of the module
+     * @param slot the slot this module belongs in
+     * @return the identifier
+     */
+    public static ModuleIdentifier create(final String name, String slot) {
+        if (name == null)
+            throw new IllegalArgumentException("Name can not be null");
+
+        if (slot == null)
+            slot = DEFAULT_SLOT;
+
+        return new ModuleIdentifier(name, slot);
+    }
+
+    /**
+     * Creates a new module identifier using the specified name.
+     *
+     * @param name the name of the module
+     * @return the identifier
+     */
+    public static ModuleIdentifier create(String name) {
+        return create(name, null);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/jboss/modules/ModuleLoadError.java b/src/main/java/org/jboss/modules/ModuleLoadError.java
new file mode 100644
index 0000000..f825368
--- /dev/null
+++ b/src/main/java/org/jboss/modules/ModuleLoadError.java
@@ -0,0 +1,76 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+/**
+ * Module load error, thrown when there is some problem loading a module during runtime.
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public class ModuleLoadError extends Error {
+
+    private static final long serialVersionUID = 3286005346300416890L;
+
+    /**
+     * Constructs a {@code ModuleLoadException} with no detail message. The cause is not initialized, and may subsequently
+     * be initialized by a call to {@link #initCause(Throwable) initCause}.
+     */
+    public ModuleLoadError() {
+    }
+
+    /**
+     * Constructs a {@code ModuleLoadException} with the specified detail message. The cause is not initialized, and may
+     * subsequently be initialized by a call to {@link #initCause(Throwable) initCause}.
+     *
+     * @param msg the detail message
+     */
+    public ModuleLoadError(final String msg) {
+        super(msg);
+    }
+
+    /**
+     * Constructs a {@code ModuleLoadException} with the specified cause. The detail message is set to:
+     * <pre>(cause == null ? null : cause.toString())</pre>
+     * (which typically contains the class and detail message of {@code cause}).
+     *
+     * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method)
+     */
+    public ModuleLoadError(final Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Constructs a {@code ModuleLoadException} with the specified detail message and cause.
+     *
+     * @param msg the detail message
+     * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method)
+     */
+    public ModuleLoadError(final String msg, final Throwable cause) {
+        super(msg, cause);
+    }
+
+    /**
+     * Convert to a checked exception type.
+     *
+     * @return the checked exception
+     */
+    public ModuleLoadException toException() {
+        return new ModuleLoadException(getMessage(), getCause());
+    }
+}
diff --git a/src/main/java/org/jboss/modules/ModuleLoadException.java b/src/main/java/org/jboss/modules/ModuleLoadException.java
new file mode 100644
index 0000000..32b1672
--- /dev/null
+++ b/src/main/java/org/jboss/modules/ModuleLoadException.java
@@ -0,0 +1,76 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+/**
+ * Module load exception, thrown when there is some problem loading a module.
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public class ModuleLoadException extends Exception {
+
+    private static final long serialVersionUID = 3286005346300416890L;
+
+    /**
+     * Constructs a {@code ModuleLoadException} with no detail message. The cause is not initialized, and may subsequently
+     * be initialized by a call to {@link #initCause(Throwable) initCause}.
+     */
+    public ModuleLoadException() {
+    }
+
+    /**
+     * Constructs a {@code ModuleLoadException} with the specified detail message. The cause is not initialized, and may
+     * subsequently be initialized by a call to {@link #initCause(Throwable) initCause}.
+     *
+     * @param msg the detail message
+     */
+    public ModuleLoadException(final String msg) {
+        super(msg);
+    }
+
+    /**
+     * Constructs a {@code ModuleLoadException} with the specified cause. The detail message is set to:
+     * <pre>(cause == null ? null : cause.toString())</pre>
+     * (which typically contains the class and detail message of {@code cause}).
+     *
+     * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method)
+     */
+    public ModuleLoadException(final Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Constructs a {@code ModuleLoadException} with the specified detail message and cause.
+     *
+     * @param msg the detail message
+     * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method)
+     */
+    public ModuleLoadException(final String msg, final Throwable cause) {
+        super(msg, cause);
+    }
+
+    /**
+     * Convert to an unchecked error type.
+     *
+     * @return the unchecked error
+     */
+    public ModuleLoadError toError() {
+        return new ModuleLoadError(getMessage(), getCause());
+    }
+}
diff --git a/src/main/java/org/jboss/modules/ModuleLoader.java b/src/main/java/org/jboss/modules/ModuleLoader.java
new file mode 100644
index 0000000..0a08951
--- /dev/null
+++ b/src/main/java/org/jboss/modules/ModuleLoader.java
@@ -0,0 +1,1013 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import static java.security.AccessController.doPrivileged;
+import static org.jboss.modules.management.ObjectProperties.property;
+
+import java.lang.management.ManagementFactory;
+import java.lang.reflect.UndeclaredThrowableException;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
+import java.util.concurrent.atomic.AtomicLongFieldUpdater;
+import org.jboss.modules.log.ModuleLogger;
+import org.jboss.modules.management.DependencyInfo;
+import org.jboss.modules.management.ModuleInfo;
+import org.jboss.modules.management.ModuleLoaderMXBean;
+import org.jboss.modules.management.ObjectProperties;
+import org.jboss.modules.management.ResourceLoaderInfo;
+import org.jboss.modules.ref.Reaper;
+import org.jboss.modules.ref.Reference;
+import org.jboss.modules.ref.WeakReference;
+
+import javax.management.MBeanServer;
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+
+/**
+ * A repository for modules, from which a module may be loaded by identifier.  Module loaders may additionally
+ * delegate to one or more other module loaders.
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ * @author <a href="mailto:jbailey at redhat.com">John Bailey</a>
+ * @author Jason T. Greene
+ *
+ * @apiviz.landmark
+ */
+public class ModuleLoader {
+
+    private static final RuntimePermission ML_PERM = new RuntimePermission("canCreateModuleLoader");
+    private static final RuntimePermission MODULE_REDEFINE_PERM = new RuntimePermission("canRedefineModule");
+    private static final RuntimePermission MODULE_REDEFINE_ANY_PERM = new RuntimePermission("canRedefineAnyModule");
+    private static final RuntimePermission MODULE_UNLOAD_ANY_PERM = new RuntimePermission("canUnloadAnyModule");
+    private static final RuntimePermission MODULE_ITERATE_PERM = new RuntimePermission("canIterateModules");
+
+    private static final AtomicInteger SEQ = new AtomicInteger(1);
+
+    private static volatile MBeanReg REG_REF = new TempMBeanReg();
+
+    /**
+     * A constant representing zero module finders.
+     */
+    public static final ModuleFinder[] NO_FINDERS = new ModuleFinder[0];
+
+    private final ConcurrentMap<ModuleIdentifier, FutureModule> moduleMap = new UnlockedReadHashMap<ModuleIdentifier, FutureModule>(256);
+    private final ModuleFinder[] finders;
+
+    private final boolean canRedefine;
+    private final ModuleLoaderMXBean mxBean;
+
+    @SuppressWarnings({"unused", "VolatileLongOrDoubleField"})
+    private volatile long linkTime;
+    @SuppressWarnings({"unused", "VolatileLongOrDoubleField"})
+    private volatile long loadTime;
+    @SuppressWarnings({"unused", "VolatileLongOrDoubleField"})
+    private volatile long classLoadTime;
+    @SuppressWarnings("unused")
+    private volatile int scanCount;
+    @SuppressWarnings("unused")
+    private volatile int raceCount;
+    @SuppressWarnings("unused")
+    private volatile int classCount;
+
+    private static final AtomicLongFieldUpdater<ModuleLoader> linkTimeUpdater = AtomicLongFieldUpdater.newUpdater(ModuleLoader.class, "linkTime");
+    private static final AtomicLongFieldUpdater<ModuleLoader> loadTimeUpdater = AtomicLongFieldUpdater.newUpdater(ModuleLoader.class, "loadTime");
+    private static final AtomicLongFieldUpdater<ModuleLoader> classLoadTimeUpdater = AtomicLongFieldUpdater.newUpdater(ModuleLoader.class, "classLoadTime");
+    private static final AtomicIntegerFieldUpdater<ModuleLoader> scanCountUpdater = AtomicIntegerFieldUpdater.newUpdater(ModuleLoader.class, "scanCount");
+    private static final AtomicIntegerFieldUpdater<ModuleLoader> raceCountUpdater = AtomicIntegerFieldUpdater.newUpdater(ModuleLoader.class, "raceCount");
+    private static final AtomicIntegerFieldUpdater<ModuleLoader> classCountUpdater = AtomicIntegerFieldUpdater.newUpdater(ModuleLoader.class, "classCount");
+
+    ModuleLoader(boolean canRedefine, boolean skipRegister) {
+        this(canRedefine, skipRegister, NO_FINDERS);
+    }
+
+    // Bypass security check for classes in this package
+    ModuleLoader(boolean canRedefine, boolean skipRegister, ModuleFinder[] finders) {
+        this.canRedefine = canRedefine;
+        this.finders = finders;
+        mxBean = skipRegister ? null : doPrivileged(new PrivilegedAction<ModuleLoaderMXBean>() {
+            public ModuleLoaderMXBean run() {
+                ObjectName objectName;
+                try {
+                    objectName = new ObjectName("jboss.modules", ObjectProperties.properties(property("type", "ModuleLoader"), property("name", ModuleLoader.this.getClass().getSimpleName() + "-" + Integer.toString(SEQ.incrementAndGet()))));
+                } catch (MalformedObjectNameException e) {
+                    return null;
+                }
+                try {
+                    MXBeanImpl mxBean = new MXBeanImpl(ModuleLoader.this, objectName);
+                    REG_REF.addMBean(objectName, mxBean);
+                    return mxBean;
+                } catch (Throwable ignored) {
+                }
+                return null;
+            }
+        });
+    }
+
+    /**
+     * Construct a new instance.
+     */
+    protected ModuleLoader() {
+        this(NO_FINDERS);
+    }
+
+    /**
+     * Construct a new instance.
+     *
+     * @param finders the module finders to search, in order
+     */
+    public ModuleLoader(final ModuleFinder[] finders) {
+        this(checkPermissions(), false, safeClone(finders));
+    }
+
+    private static ModuleFinder[] safeClone(ModuleFinder[] finders) {
+        if (finders == null || finders.length == 0) {
+            return NO_FINDERS;
+        }
+        finders = finders.clone();
+        for (ModuleFinder finder : finders) {
+            if (finder == null) {
+                throw new IllegalArgumentException("Module finder cannot be null");
+            }
+        }
+        return finders;
+    }
+
+    private static boolean checkPermissions() {
+        SecurityManager manager = System.getSecurityManager();
+        if (manager == null) {
+            return true;
+        }
+        manager.checkPermission(ML_PERM);
+        try {
+            manager.checkPermission(MODULE_REDEFINE_PERM);
+            return true;
+        } catch (SecurityException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Get the module loader for a class.
+     *
+     * @param clazz the class
+     *
+     * @return the module loader or {@code null} if the class's class loader does not belong to a module loader.
+     */
+    public static ModuleLoader forClass(Class<?> clazz) {
+        final Module module = Module.forClass(clazz);
+        if (module == null) {
+            return null;
+        }
+        return module.getModuleLoader();
+    }
+
+    /**
+     * Get the module loader for a class loader.
+     *
+     * @param classLoader the class loader
+     *
+     * @return the module loader or {@code null} if the class loader does not belong to a module loader.
+     */
+    public static ModuleLoader forClassLoader(ClassLoader classLoader) {
+        final Module module = Module.forClassLoader(classLoader, true);
+        if (module == null) {
+            return null;
+        }
+        return module.getModuleLoader();
+    }
+
+    /**
+     * Get the string representation of this module loader.
+     *
+     * @return the string representation
+     */
+    public String toString() {
+        return String.format("%s@%x for finders %s", getClass().getSimpleName(), hashCode(), Arrays.toString(finders));
+    }
+
+    static void installMBeanServer() {
+        REG_REF.installReal();
+    }
+
+    /**
+     * Load a module based on an identifier.  This method delegates to {@link #preloadModule(ModuleIdentifier)} and then
+     * links the returned module if necessary.
+     *
+     * @param identifier The module identifier
+     * @return The loaded Module
+     * @throws ModuleLoadException if the Module can not be loaded
+     */
+    public final Module loadModule(ModuleIdentifier identifier) throws ModuleLoadException {
+        final Module module = preloadModule(identifier);
+        if (module == null) {
+            throw new ModuleNotFoundException(identifier.toString());
+        }
+        module.relinkIfNecessary();
+        return module;
+    }
+
+    /**
+     * Iterate the modules which can be located via this module loader.
+     *
+     * @param baseIdentifier the identifier to start with, or {@code null} to iterate all modules
+     * @param recursive {@code true} to find recursively nested modules, {@code false} to only find immediately nested modules
+     * @return an iterator for the modules in this module finder
+     * @throws SecurityException if the caller does not have permission to iterate module loaders
+     */
+    public final Iterator<ModuleIdentifier> iterateModules(final ModuleIdentifier baseIdentifier, final boolean recursive) {
+        final SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            sm.checkPermission(MODULE_ITERATE_PERM);
+        }
+        return new Iterator<ModuleIdentifier>() {
+            int idx;
+            Iterator<ModuleIdentifier> nested;
+
+            public boolean hasNext() {
+                for (;;) {
+                    while (nested == null) {
+                        if (idx == finders.length) {
+                            return false;
+                        }
+                        final ModuleFinder finder = finders[idx++];
+                        if (finder instanceof IterableModuleFinder) {
+                            nested = ((IterableModuleFinder) finder).iterateModules(baseIdentifier, recursive);
+                        }
+                    }
+
+                    if (! nested.hasNext()) {
+                        nested = null;
+                    } else {
+                        return true;
+                    }
+                }
+            }
+
+            public ModuleIdentifier next() {
+                if (! hasNext()) {
+                    throw new NoSuchElementException();
+                }
+                return nested.next();
+            }
+
+            public void remove() {
+                throw new UnsupportedOperationException();
+            }
+        };
+    }
+
+    /**
+     * Preload a module based on an identifier.  By default, no delegation is done and this method simply invokes
+     * {@link #loadModuleLocal(ModuleIdentifier)}.  A delegating module loader may delegate to the appropriate module
+     * loader based on loader-specific criteria (via the {@link #preloadModule(ModuleIdentifier, ModuleLoader)} method).
+     *
+     * @param identifier the module identifier
+     * @return the load result, or {@code null} if the module is not found
+     * @throws ModuleLoadException if an error occurs
+     */
+    protected Module preloadModule(ModuleIdentifier identifier) throws ModuleLoadException {
+        return loadModuleLocal(identifier);
+    }
+
+    /**
+     * Preload an "exported" module based on an identifier.  By default this simply delegates to {@link #preloadModule(ModuleIdentifier)}.
+     *
+     * @param identifier the module identifier
+     * @return the load result, or {@code null} if the module is not found
+     * @throws ModuleLoadException if an error occurs
+     */
+    protected Module preloadExportedModule(ModuleIdentifier identifier) throws ModuleLoadException {
+        return preloadModule(identifier);
+    }
+
+    /**
+     * Utility method to delegate to another module loader, accessible from subclasses.  The delegate module loader
+     * will be queried for exported modules only.
+     *
+     * @param identifier the module identifier
+     * @param moduleLoader the module loader to delegate to
+     * @return the delegation result
+     * @throws ModuleLoadException if an error occurs
+     */
+    protected static Module preloadModule(ModuleIdentifier identifier, ModuleLoader moduleLoader) throws ModuleLoadException {
+        return moduleLoader.preloadExportedModule(identifier);
+    }
+
+    /**
+     * Try to load a module from this module loader.  Returns {@code null} if the module is not found.  The returned
+     * module may not yet be resolved.  The returned module may have a different name than the given identifier if
+     * the identifier is an alias for another module.
+     *
+     * @param identifier the module identifier
+     * @return the module
+     * @throws ModuleLoadException if an error occurs while loading the module
+     */
+    protected final Module loadModuleLocal(ModuleIdentifier identifier) throws ModuleLoadException {
+        FutureModule futureModule = moduleMap.get(identifier);
+        if (futureModule != null) {
+            return futureModule.getModule();
+        }
+
+        FutureModule newFuture = new FutureModule(identifier);
+        futureModule = moduleMap.putIfAbsent(identifier, newFuture);
+        if (futureModule != null) {
+            return futureModule.getModule();
+        }
+
+        boolean ok = false;
+        try {
+            final ModuleLogger log = Module.log;
+            log.trace("Locally loading module %s from %s", identifier, this);
+            final long startTime = Metrics.getCurrentCPUTime();
+            final ModuleSpec moduleSpec = findModule(identifier);
+            loadTimeUpdater.addAndGet(this, Metrics.getCurrentCPUTime() - startTime);
+            if (moduleSpec == null) {
+                log.trace("Module %s not found from %s", identifier, this);
+                return null;
+            }
+            if (! moduleSpec.getModuleIdentifier().equals(identifier)) {
+                throw new ModuleLoadException("Module loader found a module with the wrong name");
+            }
+            final Module module;
+            if ( moduleSpec instanceof AliasModuleSpec) {
+                final ModuleIdentifier aliasTarget = ((AliasModuleSpec) moduleSpec).getAliasTarget();
+                try {
+                    newFuture.setModule(module = loadModuleLocal(aliasTarget));
+                } catch (RuntimeException e) {
+                    log.trace(e, "Failed to load module %s (alias for %s)", identifier, aliasTarget);
+                    throw e;
+                } catch (Error e) {
+                    log.trace(e, "Failed to load module %s (alias for %s)", identifier, aliasTarget);
+                    throw e;
+                }
+            } else {
+                module = defineModule((ConcreteModuleSpec) moduleSpec, newFuture);
+            }
+            log.trace("Loaded module %s from %s", identifier, this);
+            ok = true;
+            return module;
+        } finally {
+            if (! ok) {
+                newFuture.setModule(null);
+                moduleMap.remove(identifier, newFuture);
+            }
+        }
+    }
+
+    /**
+     * Find an already-loaded module, returning {@code null} if the module isn't currently loaded.  May block
+     * while the loaded state of the module is in question (if the module is being concurrently loaded from another
+     * thread, for example).
+     *
+     * @param identifier the module identifier
+     * @return the module, or {@code null} if it wasn't found
+     */
+    protected final Module findLoadedModuleLocal(ModuleIdentifier identifier) {
+        FutureModule futureModule = moduleMap.get(identifier);
+        if (futureModule != null) {
+            try {
+                return futureModule.getModule();
+            } catch (ModuleNotFoundException e) {
+                return null;
+            }
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Unload a module from this module loader.  Note that this has no effect on existing modules which refer to the
+     * module being unloaded.  Also, only modules from the current module loader can be unloaded.  Unloading the same
+     * module more than once has no additional effect.  This method only removes the mapping for the module; any running
+     * threads which are currently accessing or linked to the module will continue to function, however attempts to load
+     * this module will fail until a new module is loaded with the same name.  Once this happens, if all references to
+     * the previous module are not cleared, the same module may be loaded more than once, causing possible class duplication
+     * and class cast exceptions if proper care is not taken.
+     *
+     * @param module the module to unload
+     * @throws SecurityException if an attempt is made to unload a module which does not belong to this module loader
+     * @throws SecurityException if the module was not defined by this module loader
+     */
+    protected final void unloadModuleLocal(Module module) throws SecurityException {
+        final ModuleLoader moduleLoader = module.getModuleLoader();
+        if (moduleLoader != this) {
+            throw new SecurityException("Attempted to unload " + module + " from a different module loader");
+        }
+        final ModuleIdentifier id = module.getIdentifier();
+        final FutureModule futureModule = moduleMap.get(id);
+        if (futureModule.module == module) {
+            moduleMap.remove(id, futureModule);
+        }
+    }
+
+    /**
+     * Find a Module's specification in this ModuleLoader by its identifier.  This can be overriden by sub-classes to
+     * implement the Module loading strategy for this loader.  The default implementation iterates the module finders
+     * provided during construction.
+     * <p/>
+     * If no module is found in this module loader with the given identifier, then this method should return {@code
+     * null}. If the module is found but some problem occurred (for example, a transitive dependency failed to load)
+     * then this method should throw a {@link ModuleLoadException} of the relevant type.
+     *
+     * @param moduleIdentifier the module identifier
+     * @return the module specification, or {@code null} if no module is found with the given identifier
+     * @throws ModuleLoadException if any problems occur finding the module
+     */
+    protected ModuleSpec findModule(final ModuleIdentifier moduleIdentifier) throws ModuleLoadException {
+        for (ModuleFinder finder : finders) {
+            if (finder != null) {
+                final ModuleSpec spec = finder.findModule(moduleIdentifier, this);
+                if (spec != null) {
+                    return spec;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Get the module finders configured for this module loader.
+     *
+     * @return the module finders
+     */
+    protected final ModuleFinder[] getFinders() {
+        return finders.length > 0 ? finders.clone() : NO_FINDERS;
+    }
+
+    /**
+     * Defines a Module based on a specification.  May only be called from {@link #loadModuleLocal(ModuleIdentifier)}.
+     *
+     * @param moduleSpec The module specification to create the Module from
+     * @param futureModule the future module to populate
+     * @return The defined Module
+     * @throws ModuleLoadException If any dependent modules can not be loaded
+     */
+    private Module defineModule(final ConcreteModuleSpec moduleSpec, final FutureModule futureModule) throws ModuleLoadException {
+        try {
+            return doPrivileged(new PrivilegedExceptionAction<Module>() {
+                public Module run() throws Exception {
+                    final ModuleLogger log = Module.log;
+                    final ModuleIdentifier moduleIdentifier = moduleSpec.getModuleIdentifier();
+
+                    final Module module = new Module(moduleSpec, ModuleLoader.this);
+                    module.getClassLoaderPrivate().recalculate();
+                    module.setDependencies(moduleSpec.getDependenciesInternal());
+                    log.moduleDefined(moduleIdentifier, ModuleLoader.this);
+                    try {
+                        futureModule.setModule(module);
+                        return module;
+                    } catch (RuntimeException e) {
+                        log.trace(e, "Failed to load module %s", moduleIdentifier);
+                        throw e;
+                    } catch (Error e) {
+                        log.trace(e, "Failed to load module %s", moduleIdentifier);
+                        throw e;
+                    }
+                }
+            });
+        } catch (PrivilegedActionException pe) {
+            try {
+                throw pe.getException();
+            } catch (RuntimeException e) {
+                throw e;
+            } catch (ModuleLoadException e) {
+                throw e;
+            } catch (Exception e) {
+                throw new UndeclaredThrowableException(e);
+            }
+        }
+    }
+
+    /**
+     * Refreshes the paths provided by resource loaders associated with the
+     * specified Module. This is an advanced method that is intended to be
+     * called on modules that have a resource loader implementation that has
+     * changed and is returning different paths.
+     *
+     * @param module the module to refresh
+     * @throws SecurityException if the module was not defined by this module loader, or if the module loader does not
+     *      have the required permissions associated with it
+     */
+    protected void refreshResourceLoaders(Module module) {
+        if (! canRedefine) {
+            throw new SecurityException("Module redefinition requires canRedefineModule permission");
+        }
+        if (module.getModuleLoader() != this) {
+            throw new SecurityException("Module is not defined by this module loader");
+        }
+
+        module.getClassLoaderPrivate().recalculate();
+    }
+
+    /**
+     * Replaces the resources loaders for the specified module and refreshes the
+     * internal path list that is derived from the loaders. This is an advanced
+     * method that should be used carefully, since it alters a live module.
+     * Modules that import resources from the specified module will not
+     * automatically be updated to reflect the change. For this to occur
+     * {@link #relink(Module)} must be called on all of them.
+     *
+     * @param module the module to update and refresh
+     * @param loaders the new collection of loaders the module should use
+     * @throws SecurityException if the module was not defined by this module loader, or if the module loader does not
+     *      have the required permissions associated with it
+     */
+    protected void setAndRefreshResourceLoaders(Module module, Collection<ResourceLoaderSpec> loaders) {
+        if (! canRedefine) {
+            throw new SecurityException("Module redefinition requires canRedefineModule permission");
+        }
+        if (module.getModuleLoader() != this) {
+            throw new SecurityException("Module is not defined by this module loader");
+        }
+
+        module.getClassLoaderPrivate().setResourceLoaders(loaders.toArray(new ResourceLoaderSpec[loaders.size()]));
+    }
+
+    /**
+     * Relinks the dependencies associated with the specified Module. This is an
+     * advanced method that is intended to be called on all modules that
+     * directly or indirectly import dependencies that are re-exported by a module
+     * that has recently been updated and relinked via
+     * {@link #setAndRelinkDependencies(Module, java.util.List)}.
+     *
+     * @param module the module to relink
+     * @throws ModuleLoadException if relinking failed
+     * @throws SecurityException if the module was not defined by this module loader, or if the module loader does not
+     *      have the required permissions associated with it
+     */
+    protected void relink(Module module) throws ModuleLoadException {
+        if (! canRedefine) {
+            throw new SecurityException("Module redefinition requires canRedefineModule permission");
+        }
+        if (module.getModuleLoader() != this) {
+            throw new SecurityException("Module is not defined by this module loader");
+        }
+
+        module.relink();
+    }
+
+    /**
+     * Replaces the dependencies for the specified module and relinks against
+     * the new modules This is an advanced method that should be used carefully,
+     * since it alters a live module. Modules that import dependencies that are
+     * re-exported from the specified module will not automatically be updated
+     * to reflect the change. For this to occur {@link #relink(Module)} must be
+     * called on all of them.
+     *
+     * @param module the module to update and relink
+     * @param dependencies the new dependency list
+     * @throws ModuleLoadException if relinking failed
+     * @throws SecurityException if the module was not defined by this module loader, or if the module loader does not
+     *      have the required permissions associated with it
+     */
+    protected void setAndRelinkDependencies(Module module, List<DependencySpec> dependencies) throws ModuleLoadException {
+        if (! canRedefine) {
+            throw new SecurityException("Module redefinition requires canRedefineModule permission");
+        }
+        if (module.getModuleLoader() != this) {
+            throw new SecurityException("Module is not defined by this module loader");
+        }
+
+        module.setDependencies(dependencies);
+        module.relinkIfNecessary();
+    }
+
+    /**
+     * Get the current dependency list for a module which was defined by this module loader, without any access checks.
+     *
+     * @return the current dependency list for the module
+     * @throws SecurityException if the module was not defined by this module loader
+     */
+    protected DependencySpec[] getDependencies(Module module) {
+        if (module.getModuleLoader() != this) {
+            throw new SecurityException("Module is not defined by this module loader");
+        }
+        return module.getDependencySpecsInternal().clone();
+    }
+
+    void addLinkTime(long amount) {
+        if (amount != 0L) linkTimeUpdater.addAndGet(this, amount);
+    }
+
+    void addClassLoadTime(final long time) {
+        if (time != 0L) classLoadTimeUpdater.addAndGet(this, time);
+    }
+
+    void incScanCount() {
+        if (Metrics.ENABLED) scanCountUpdater.getAndIncrement(this);
+    }
+
+    void incRaceCount() {
+        if (Metrics.ENABLED) raceCountUpdater.getAndIncrement(this);
+    }
+
+    void incClassCount() {
+        if (Metrics.ENABLED) classCountUpdater.getAndIncrement(this);
+    }
+
+    private static final class FutureModule {
+        private static final Object NOT_FOUND = new Object();
+
+        private final ModuleIdentifier identifier;
+        private volatile Object module;
+
+        FutureModule(final ModuleIdentifier identifier) {
+            this.identifier = identifier;
+        }
+
+        Module getModule() throws ModuleNotFoundException {
+            boolean intr = false;
+            try {
+                Object module = this.module;
+                if (module == null) synchronized (this) {
+                    while ((module = this.module) == null) {
+                        try {
+                            wait();
+                        } catch (InterruptedException e) {
+                            intr = true;
+                        }
+                    }
+                }
+                if (module == NOT_FOUND) throw new ModuleNotFoundException(identifier.toString());
+                return (Module) module;
+            } finally {
+                if (intr) Thread.currentThread().interrupt();
+            }
+        }
+
+        void setModule(Module m) {
+            synchronized (this) {
+                module = m == null ? NOT_FOUND : m;
+                notifyAll();
+            }
+        }
+    }
+
+    private static final Reaper<ModuleLoader, ObjectName> reaper = new Reaper<ModuleLoader, ObjectName>() {
+        public void reap(final Reference<ModuleLoader, ObjectName> reference) {
+            REG_REF.removeMBean(reference.getAttachment());
+        }
+    };
+
+    static final class MXBeanImpl implements ModuleLoaderMXBean {
+        private final Reference<ModuleLoader, ObjectName> reference;
+
+        MXBeanImpl(final ModuleLoader moduleLoader, final ObjectName objectName) {
+            reference = new WeakReference<ModuleLoader, ObjectName>(moduleLoader, objectName, reaper);
+        }
+
+        public String getDescription() {
+            return getModuleLoader().toString();
+        }
+
+        public long getLinkTime() {
+            return getModuleLoader().linkTime;
+        }
+
+        public long getLoadTime() {
+            return getModuleLoader().loadTime;
+        }
+
+        public long getClassDefineTime() {
+            return getModuleLoader().classLoadTime;
+        }
+
+        public int getScanCount() {
+            return getModuleLoader().scanCount;
+        }
+
+        public int getLoadedModuleCount() {
+            return getModuleLoader().moduleMap.size();
+        }
+
+        public int getRaceCount() {
+            return getModuleLoader().raceCount;
+        }
+
+        public int getClassCount() {
+            return getModuleLoader().classCount;
+        }
+
+        public List<String> queryLoadedModuleNames() {
+            ModuleLoader loader = getModuleLoader();
+            final Set<ModuleIdentifier> identifiers = loader.moduleMap.keySet();
+            final ArrayList<String> list = new ArrayList<String>(identifiers.size());
+            for (ModuleIdentifier identifier : identifiers) {
+                list.add(identifier.toString());
+            }
+            Collections.sort(list);
+            return list;
+        }
+
+        public String dumpAllModuleInformation() {
+            final StringBuilder b = new StringBuilder();
+            for (String name : queryLoadedModuleNames()) {
+                doDumpModuleInformation(name, b);
+            }
+            return b.toString();
+        }
+
+        public String dumpModuleInformation(final String name) {
+            final StringBuilder b = new StringBuilder();
+            doDumpModuleInformation(name, b);
+            return b.toString();
+        }
+
+        private void doDumpModuleInformation(final String name, final StringBuilder b) {
+            ModuleInfo description = getModuleDescription(name);
+            b.append("Module ").append(name).append('\n');
+            b.append("    Class loader: ").append(description.getClassLoader()).append('\n');
+            String fallbackLoader = description.getFallbackLoader();
+            if (fallbackLoader != null) b.append("    Fallback loader: ").append(fallbackLoader).append('\n');
+            String mainClass = description.getMainClass();
+            if (mainClass != null) b.append("    Main Class: ").append(mainClass).append('\n');
+            List<ResourceLoaderInfo> loaders = description.getResourceLoaders();
+            b.append("    Resource Loaders:\n");
+            for (ResourceLoaderInfo loader : loaders) {
+                b.append("        Loader Type: ").append(loader.getType()).append('\n');
+                b.append("        Paths:\n");
+                for (String path : loader.getPaths()) {
+                    b.append("            ").append(path).append('\n');
+                }
+            }
+            b.append("    Dependencies:\n");
+            for (DependencyInfo dependencyInfo : description.getDependencies()) {
+                b.append("        Type: ").append(dependencyInfo.getDependencyType()).append('\n');
+                String moduleName = dependencyInfo.getModuleName();
+                if (moduleName != null) {
+                    b.append("        Module Name: ").append(moduleName).append('\n');
+                }
+                if (dependencyInfo.isOptional()) b.append("        (optional)\n");
+                b.append("        Export Filter: ").append(dependencyInfo.getExportFilter()).append('\n');
+                b.append("        Import Filter: ").append(dependencyInfo.getImportFilter()).append('\n');
+                String localLoader = dependencyInfo.getLocalLoader();
+                if (localLoader != null) {
+                    b.append("        Local Loader: ").append(localLoader).append('\n');
+                    b.append("        Paths:\n");
+                    for (String path : dependencyInfo.getLocalLoaderPaths()) {
+                        b.append("            ").append(path).append('\n');
+                    }
+                }
+            }
+        }
+
+        public boolean unloadModule(final String name) {
+            final SecurityManager sm = System.getSecurityManager();
+            if (sm != null) {
+                sm.checkPermission(MODULE_UNLOAD_ANY_PERM);
+            }
+            final ModuleLoader loader = getModuleLoader();
+            final Module module = loader.findLoadedModuleLocal(ModuleIdentifier.fromString(name));
+            if (module == null) {
+                return false;
+            } else {
+                loader.unloadModuleLocal(module);
+                return true;
+            }
+        }
+
+        public void refreshResourceLoaders(final String name) {
+            final SecurityManager sm = System.getSecurityManager();
+            if (sm != null) {
+                sm.checkPermission(MODULE_REDEFINE_ANY_PERM);
+            }
+            final ModuleLoader loader = getModuleLoader();
+            final Module module = loadModule(name, loader);
+            loader.refreshResourceLoaders(module);
+        }
+
+        public void relink(final String name) {
+            final SecurityManager sm = System.getSecurityManager();
+            if (sm != null) {
+                sm.checkPermission(MODULE_REDEFINE_ANY_PERM);
+            }
+            final ModuleLoader loader = getModuleLoader();
+            final Module module = loadModule(name, loader);
+            try {
+                loader.relink(module);
+            } catch (ModuleLoadException e) {
+                throw new IllegalStateException("Module load failure for module " + name + ": " + e.toString());
+            }
+        }
+
+        public List<DependencyInfo> getDependencies(final String name) {
+            final ModuleLoader loader = getModuleLoader();
+            final Module module = loadModule(name, loader);
+            return doGetDependencies(module);
+        }
+
+        private List<DependencyInfo> doGetDependencies(final Module module) {
+            Dependency[] dependencies = module.getDependenciesInternal();
+            if (dependencies == null) {
+                return Collections.emptyList();
+            }
+            ArrayList<DependencyInfo> list = new ArrayList<DependencyInfo>(dependencies.length);
+            for (Dependency dependency : dependencies) {
+                final String dependencyType = dependency.getClass().getSimpleName();
+                final String exportFilter = dependency.getExportFilter().toString();
+                final String importFilter = dependency.getImportFilter().toString();
+                final DependencyInfo info;
+                if (dependency instanceof LocalDependency) {
+                    final LocalDependency localDependency = (LocalDependency) dependency;
+                    ArrayList<String> pathList = new ArrayList<String>(localDependency.getPaths());
+                    Collections.sort(pathList);
+                    info = new DependencyInfo(dependencyType, exportFilter, importFilter, null, null, false, localDependency.getLocalLoader().toString(), pathList);
+                } else if (dependency instanceof ModuleDependency) {
+                    final ModuleDependency moduleDependency = (ModuleDependency) dependency;
+                    info = new DependencyInfo(dependencyType, exportFilter, importFilter, moduleDependency.getModuleLoader().mxBean, moduleDependency.getIdentifier().toString(), moduleDependency.isOptional(), null, null);
+                } else {
+                    info = new DependencyInfo(dependencyType, exportFilter, importFilter, null, null, false, null, null);
+                }
+                list.add(info);
+            }
+            return list;
+        }
+
+        public List<ResourceLoaderInfo> getResourceLoaders(final String name) {
+            ModuleLoader loader = getModuleLoader();
+            final Module module = loadModule(name, loader);
+            return doGetResourceLoaders(module);
+        }
+
+        private List<ResourceLoaderInfo> doGetResourceLoaders(final Module module) {
+            final ModuleClassLoader classLoader = module.getClassLoaderPrivate();
+            final ResourceLoader[] loaders = classLoader.getResourceLoaders();
+            final ArrayList<ResourceLoaderInfo> list = new ArrayList<ResourceLoaderInfo>(loaders.length);
+            for (ResourceLoader resourceLoader : loaders) {
+                list.add(new ResourceLoaderInfo(resourceLoader.getClass().getName(), new ArrayList<String>(resourceLoader.getPaths())));
+            }
+            return list;
+        }
+
+        public ModuleInfo getModuleDescription(final String name) {
+            ModuleLoader loader = getModuleLoader();
+            final Module module = loadModule(name, loader);
+            final List<DependencyInfo> dependencies = doGetDependencies(module);
+            final List<ResourceLoaderInfo> resourceLoaders = doGetResourceLoaders(module);
+            final LocalLoader fallbackLoader = module.getFallbackLoader();
+            final String fallbackLoaderString = fallbackLoader == null ? null : fallbackLoader.toString();
+            return new ModuleInfo(module.getIdentifier().toString(), module.getModuleLoader().mxBean, dependencies, resourceLoaders, module.getMainClass(), module.getClassLoaderPrivate().toString(), fallbackLoaderString);
+        }
+
+        public SortedMap<String, List<String>> getModulePathsInfo(final String name, final boolean exports) {
+            ModuleLoader loader = getModuleLoader();
+            final Module module = loadModule(name, loader);
+            final Map<String, List<LocalLoader>> paths;
+            try {
+                paths = module.getPathsUnchecked();
+            } catch (ModuleLoadError e) {
+                throw new IllegalArgumentException("Error loading module " + name + ": " + e.toString());
+            }
+            final TreeMap<String, List<String>> result = new TreeMap<String, List<String>>();
+            for (Map.Entry<String, List<LocalLoader>> entry : paths.entrySet()) {
+                final String path = entry.getKey();
+                final List<LocalLoader> loaders = entry.getValue();
+                if (loaders.isEmpty()) {
+                    result.put(path, Collections.<String>emptyList());
+                } else if (loaders.size() == 1) {
+                    result.put(path, Collections.<String>singletonList(loaders.get(0).toString()));
+                } else {
+                    final ArrayList<String> list = new ArrayList<String>();
+                    for (LocalLoader localLoader : loaders) {
+                        list.add(localLoader.toString());
+                    }
+                    result.put(path, list);
+                }
+            }
+            return result;
+        }
+
+        private Module loadModule(final String name, final ModuleLoader loader) {
+            try {
+                final Module module = loader.findLoadedModuleLocal(ModuleIdentifier.fromString(name));
+                if (module == null) {
+                    throw new IllegalArgumentException("Module " + name + " not found");
+                }
+                return module;
+            } catch (ModuleLoadError e) {
+                throw new IllegalArgumentException("Error loading module " + name + ": " + e.toString());
+            }
+        }
+
+        private ModuleLoader getModuleLoader() {
+            final ModuleLoader loader = reference.get();
+            if (loader == null) {
+                throw new IllegalStateException("Module Loader is gone");
+            }
+            return loader;
+        }
+    }
+
+    private interface MBeanReg {
+        boolean addMBean(ObjectName name, Object bean);
+
+        void removeMBean(ObjectName name);
+
+        void installReal();
+    }
+
+    private static final class TempMBeanReg implements MBeanReg {
+        private final Map<ObjectName, Object> mappings = new LinkedHashMap<ObjectName, Object>();
+
+        public boolean addMBean(final ObjectName name, final Object bean) {
+            if (bean == null) {
+                throw new IllegalArgumentException("bean is null");
+            }
+            synchronized (ModuleLoader.class) {
+                if (REG_REF == this) {
+                    return mappings.put(name, bean) == null;
+                } else {
+                    return REG_REF.addMBean(name, bean);
+                }
+            }
+        }
+
+        public void removeMBean(final ObjectName name) {
+            synchronized (ModuleLoader.class) {
+                if (REG_REF == this) {
+                    mappings.remove(name);
+                } else {
+                    REG_REF.removeMBean(name);
+                }
+            }
+        }
+
+        public void installReal() {
+            synchronized (ModuleLoader.class) {
+                RealMBeanReg real = new RealMBeanReg();
+                if (REG_REF == this) {
+                    REG_REF = real;
+                    for (Map.Entry<ObjectName, Object> entry : mappings.entrySet()) {
+                        real.addMBean(entry.getKey(), entry.getValue());
+                    }
+                    mappings.clear();
+                }
+            }
+        }
+    }
+
+    private static final class RealMBeanReg implements MBeanReg {
+        private final MBeanServer server;
+
+        RealMBeanReg() {
+            server = doPrivileged(new PrivilegedAction<MBeanServer>() {
+                public MBeanServer run() {
+                    return ManagementFactory.getPlatformMBeanServer();
+                }
+            });
+        }
+
+        public boolean addMBean(final ObjectName name, final Object bean) {
+            try {
+                server.registerMBean(bean, name);
+                return true;
+            } catch (Throwable e) {
+            }
+            return false;
+        }
+
+        public void removeMBean(final ObjectName name) {
+            try {
+                server.unregisterMBean(name);
+            } catch (Throwable e) {
+            }
+        }
+
+        public void installReal() {
+            // ignore
+        }
+    }
+}
diff --git a/src/main/java/org/jboss/modules/ModuleNotFoundException.java b/src/main/java/org/jboss/modules/ModuleNotFoundException.java
new file mode 100644
index 0000000..b7ac861
--- /dev/null
+++ b/src/main/java/org/jboss/modules/ModuleNotFoundException.java
@@ -0,0 +1,68 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+/**
+ * Module not found exceptions are thrown when no module loaders can locate a module which fulfills a given module
+ * identifier.
+ *
+ * @author <a href="mailto:jbailey at redhat.com">John Bailey</a>
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public class ModuleNotFoundException extends ModuleLoadException {
+    private static final long serialVersionUID = -1225396191255481860L;
+
+    /**
+     * Constructs a {@code ModuleNotFoundException} with no detail message. The cause is not initialized, and may
+     * subsequently be initialized by a call to {@link #initCause(Throwable) initCause}.
+     */
+    public ModuleNotFoundException() {
+    }
+
+    /**
+     * Constructs a {@code ModuleNotFoundException} with the specified detail message. The cause is not initialized, and may
+     * subsequently be initialized by a call to {@link #initCause(Throwable) initCause}.
+     *
+     * @param msg the detail message
+     */
+    public ModuleNotFoundException(final String msg) {
+        super(msg);
+    }
+
+    /**
+     * Constructs a {@code ModuleNotFoundException} with the specified cause. The detail message is set to:
+     * <pre>(cause == null ? null : cause.toString())</pre>
+     * (which typically contains the class and detail message of {@code cause}).
+     *
+     * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method)
+     */
+    public ModuleNotFoundException(final Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Constructs a {@code ModuleNotFoundException} with the specified detail message and cause.
+     *
+     * @param msg   the detail message
+     * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method)
+     */
+    public ModuleNotFoundException(final String msg, final Throwable cause) {
+        super(msg, cause);
+    }
+}
diff --git a/src/main/java/org/jboss/modules/ModuleSpec.java b/src/main/java/org/jboss/modules/ModuleSpec.java
new file mode 100644
index 0000000..a7607bd
--- /dev/null
+++ b/src/main/java/org/jboss/modules/ModuleSpec.java
@@ -0,0 +1,292 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.lang.instrument.ClassFileTransformer;
+import java.security.AllPermission;
+import java.security.PermissionCollection;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A {@code Module} specification which is used by a {@code ModuleLoader} to define new modules.
+ *
+ * @apiviz.exclude
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public abstract class ModuleSpec {
+
+    private final ModuleIdentifier moduleIdentifier;
+
+    ModuleSpec(final ModuleIdentifier moduleIdentifier) {
+        this.moduleIdentifier = moduleIdentifier;
+    }
+
+    /**
+     * Get a builder for a new module specification.
+     *
+     * @param moduleIdentifier the module identifier
+     * @return the builder
+     */
+    public static Builder build(final ModuleIdentifier moduleIdentifier) {
+        if (moduleIdentifier == null) {
+            throw new IllegalArgumentException("moduleIdentifier is null");
+        }
+        return new Builder() {
+            private String mainClass;
+            private AssertionSetting assertionSetting = AssertionSetting.INHERIT;
+            private final List<ResourceLoaderSpec> resourceLoaders = new ArrayList<ResourceLoaderSpec>(0);
+            private final List<DependencySpec> dependencies = new ArrayList<DependencySpec>();
+            private final Map<String, String> properties = new LinkedHashMap<String, String>(0);
+            private LocalLoader fallbackLoader;
+            private ModuleClassLoaderFactory moduleClassLoaderFactory;
+            private ClassFileTransformer classFileTransformer;
+            private PermissionCollection permissionCollection;
+
+            @Override
+            public Builder setFallbackLoader(final LocalLoader fallbackLoader) {
+                this.fallbackLoader = fallbackLoader;
+                return this;
+            }
+
+            @Override
+            public Builder setMainClass(final String mainClass) {
+                this.mainClass = mainClass;
+                return this;
+            }
+
+            @Override
+            public Builder setAssertionSetting(final AssertionSetting assertionSetting) {
+                this.assertionSetting = assertionSetting == null ? AssertionSetting.INHERIT : assertionSetting;
+                return this;
+            }
+
+            @Override
+            public Builder addDependency(final DependencySpec dependencySpec) {
+                dependencies.add(dependencySpec);
+                return this;
+            }
+
+            @Override
+            public Builder addResourceRoot(final ResourceLoaderSpec resourceLoader) {
+                resourceLoaders.add(resourceLoader);
+                return this;
+            }
+
+            @Override
+            public Builder setModuleClassLoaderFactory(final ModuleClassLoaderFactory moduleClassLoaderFactory) {
+                this.moduleClassLoaderFactory = moduleClassLoaderFactory;
+                return this;
+            }
+
+            @Override
+            public Builder setClassFileTransformer(final ClassFileTransformer classFileTransformer) {
+                this.classFileTransformer = classFileTransformer;
+                return this;
+            }
+
+            @Override
+            public Builder addProperty(final String name, final String value) {
+                properties.put(name, value);
+                return this;
+            }
+
+            @Override
+            public Builder setPermissionCollection(PermissionCollection permissionCollection) {
+                this.permissionCollection = permissionCollection;
+                return this;
+            }
+
+            @Override
+            public ModuleSpec create() {
+                return new ConcreteModuleSpec(moduleIdentifier, mainClass, assertionSetting, resourceLoaders.toArray(new ResourceLoaderSpec[resourceLoaders.size()]), dependencies.toArray(new DependencySpec[dependencies.size()]), fallbackLoader, moduleClassLoaderFactory, classFileTransformer, properties, permissionCollection);
+            }
+
+            @Override
+            public ModuleIdentifier getIdentifier() {
+                return moduleIdentifier;
+            }
+        };
+    }
+
+    /**
+     * Get a builder for a new module alias specification.
+     *
+     * @param moduleIdentifier the module identifier
+     * @param aliasTarget the alias target identifier
+     * @return the builder
+     */
+    public static AliasBuilder buildAlias(final ModuleIdentifier moduleIdentifier, final ModuleIdentifier aliasTarget) {
+        if (moduleIdentifier == null) {
+            throw new IllegalArgumentException("moduleIdentifier is null");
+        }
+        if (aliasTarget == null) {
+            throw new IllegalArgumentException("aliasTarget is null");
+        }
+        return new AliasBuilder() {
+            public ModuleSpec create() {
+                return new AliasModuleSpec(moduleIdentifier, aliasTarget);
+            }
+
+            public ModuleIdentifier getIdentifier() {
+                return moduleIdentifier;
+            }
+
+            public ModuleIdentifier getAliasTarget() {
+                return aliasTarget;
+            }
+        };
+    }
+
+    /**
+     * Get the module identifier for the module which is specified by this object.
+     *
+     * @return the module identifier
+     */
+    public ModuleIdentifier getModuleIdentifier() {
+        return moduleIdentifier;
+    }
+
+    /**
+     * A builder for new concrete module specifications.
+     *
+     * @apiviz.exclude
+     */
+    public interface Builder {
+
+        /**
+         * Set the main class for this module, or {@code null} for none.
+         *
+         * @param mainClass the main class name
+         * @return this builder
+         */
+        ModuleSpec.Builder setMainClass(String mainClass);
+
+        /**
+         * Set the default assertion setting for this module.
+         *
+         * @param assertionSetting the assertion setting
+         * @return this builder
+         */
+        ModuleSpec.Builder setAssertionSetting(AssertionSetting assertionSetting);
+
+        /**
+         * Add a dependency specification.
+         *
+         * @param dependencySpec the dependency specification
+         * @return this builder
+         */
+        ModuleSpec.Builder addDependency(DependencySpec dependencySpec);
+
+        /**
+         * Add a local resource root, from which this module will load class definitions and resources.
+         *
+         * @param resourceLoader the resource loader for the root
+         * @return this builder
+         */
+        ModuleSpec.Builder addResourceRoot(ResourceLoaderSpec resourceLoader);
+
+        /**
+         * Create the module specification from this builder.
+         *
+         * @return the module specification
+         */
+        ModuleSpec create();
+
+        /**
+         * Get the identifier of the module being defined by this builder.
+         *
+         * @return the module identifier
+         */
+        ModuleIdentifier getIdentifier();
+
+        /**
+         * Sets a "fall-back" loader that will attempt to load a class if all other mechanisms
+         * are unsuccessful.
+         *
+         * @param fallbackLoader the fall-back loader
+         * @return this builder
+         */
+        ModuleSpec.Builder setFallbackLoader(final LocalLoader fallbackLoader);
+
+        /**
+         * Set the module class loader factory to use to create the module class loader for this module.
+         *
+         * @param moduleClassLoaderFactory the factory
+         * @return this builder
+         */
+        ModuleSpec.Builder setModuleClassLoaderFactory(ModuleClassLoaderFactory moduleClassLoaderFactory);
+
+        /**
+         * Set the class file transformer to use for this module.
+         *
+         * @param classFileTransformer the class file transformer
+         * @return this builder
+         */
+        ModuleSpec.Builder setClassFileTransformer(ClassFileTransformer classFileTransformer);
+
+        /**
+         * Add a property to this module specification.
+         *
+         * @param name the property name
+         * @param value the property value
+         * @return this builder
+         */
+        ModuleSpec.Builder addProperty(String name, String value);
+
+        /**
+         * Set the permission collection for this module specification.  If none is given, a collection implying
+         * {@link AllPermission} is assumed.
+         *
+         * @param permissionCollection the permission collection
+         * @return this builder
+         */
+        ModuleSpec.Builder setPermissionCollection(PermissionCollection permissionCollection);
+    }
+
+    /**
+     * A builder for new alias module specifications.
+     */
+    public interface AliasBuilder {
+
+        /**
+         * Create the module specification from this builder.
+         *
+         * @return the module specification
+         */
+        ModuleSpec create();
+
+        /**
+         * Get the identifier of the module being defined by this builder.
+         *
+         * @return the module identifier
+         */
+        ModuleIdentifier getIdentifier();
+
+        /**
+         * Get the identifier of the module being referenced by this builder.
+         *
+         * @return the module identifier
+         */
+        ModuleIdentifier getAliasTarget();
+    }
+}
diff --git a/src/main/java/org/jboss/modules/ModuleXmlParser.java b/src/main/java/org/jboss/modules/ModuleXmlParser.java
new file mode 100755
index 0000000..81fc9f7
--- /dev/null
+++ b/src/main/java/org/jboss/modules/ModuleXmlParser.java
@@ -0,0 +1,1076 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.AccessControlContext;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
+import java.util.jar.JarFile;
+
+import org.jboss.modules.filter.MultiplePathFilterBuilder;
+import org.jboss.modules.filter.PathFilter;
+import org.jboss.modules.filter.PathFilters;
+import org.jboss.modules.security.FactoryPermissionCollection;
+import org.jboss.modules.security.ModularPermissionFactory;
+import org.jboss.modules.security.PermissionFactory;
+import org.jboss.modules.xml.MXParser;
+import org.jboss.modules.xml.XmlPullParser;
+import org.jboss.modules.xml.XmlPullParserException;
+
+import static org.jboss.modules.xml.XmlPullParser.CDSECT;
+import static org.jboss.modules.xml.XmlPullParser.COMMENT;
+import static org.jboss.modules.xml.XmlPullParser.DOCDECL;
+import static org.jboss.modules.xml.XmlPullParser.END_DOCUMENT;
+import static org.jboss.modules.xml.XmlPullParser.END_TAG;
+import static org.jboss.modules.xml.XmlPullParser.ENTITY_REF;
+import static org.jboss.modules.xml.XmlPullParser.FEATURE_PROCESS_NAMESPACES;
+import static org.jboss.modules.xml.XmlPullParser.IGNORABLE_WHITESPACE;
+import static org.jboss.modules.xml.XmlPullParser.PROCESSING_INSTRUCTION;
+import static org.jboss.modules.xml.XmlPullParser.START_DOCUMENT;
+import static org.jboss.modules.xml.XmlPullParser.START_TAG;
+import static org.jboss.modules.xml.XmlPullParser.TEXT;
+
+/**
+ * A fast, validating module.xml parser.
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ * @author thomas.diesler at jboss.com
+ */
+final class ModuleXmlParser {
+
+    interface ResourceRootFactory {
+        ResourceLoader createResourceLoader(final String rootPath, final String loaderPath, final String loaderName) throws IOException;
+    }
+
+    private ModuleXmlParser() {
+    }
+
+    private static final String MODULE_1_0 = "urn:jboss:module:1.0";
+    private static final String MODULE_1_1 = "urn:jboss:module:1.1";
+    private static final String MODULE_1_2 = "urn:jboss:module:1.2";
+    private static final String MODULE_1_3 = "urn:jboss:module:1.3";
+
+    private static final String E_MODULE = "module";
+    private static final String E_ARTIFACT = "artifact";
+    private static final String E_NATIVE_ARTIFACT = "native-artifact";
+    private static final String E_DEPENDENCIES = "dependencies";
+    private static final String E_RESOURCES = "resources";
+    private static final String E_MAIN_CLASS = "main-class";
+    private static final String E_RESOURCE_ROOT = "resource-root";
+    private static final String E_PATH = "path";
+    private static final String E_EXPORTS = "exports";
+    private static final String E_IMPORTS = "imports";
+    private static final String E_INCLUDE = "include";
+    private static final String E_EXCLUDE = "exclude";
+    private static final String E_INCLUDE_SET = "include-set";
+    private static final String E_EXCLUDE_SET = "exclude-set";
+    private static final String E_FILTER = "filter";
+    private static final String E_SYSTEM = "system";
+    private static final String E_PATHS = "paths";
+    private static final String E_MODULE_ALIAS = "module-alias";
+    private static final String E_MODULE_ABSENT = "module-absent";
+    private static final String E_PROPERTIES = "properties";
+    private static final String E_PROPERTY = "property";
+    private static final String E_PERMISSIONS = "permissions";
+    private static final String E_GRANT = "grant";
+
+    private static final String A_NAME = "name";
+    private static final String A_SLOT = "slot";
+    private static final String A_EXPORT = "export";
+    private static final String A_SERVICES = "services";
+    private static final String A_PATH = "path";
+    private static final String A_OPTIONAL = "optional";
+    private static final String A_TARGET_NAME = "target-name";
+    private static final String A_TARGET_SLOT = "target-slot";
+    private static final String A_VALUE = "value";
+    private static final String A_PERMISSION = "permission";
+    private static final String A_ACTIONS = "actions";
+
+    private static final String D_NONE = "none";
+    private static final String D_IMPORT = "import";
+    private static final String D_EXPORT = "export";
+
+    static ModuleSpec parseModuleXml(final ModuleLoader moduleLoader, final ModuleIdentifier moduleIdentifier, final File root, final File moduleInfoFile, final AccessControlContext context) throws ModuleLoadException, IOException {
+        final FileInputStream fis;
+        try {
+            fis = new FileInputStream(moduleInfoFile);
+        } catch (FileNotFoundException e) {
+            throw new ModuleLoadException("No module.xml file found at " + moduleInfoFile);
+        }
+        try {
+            return parseModuleXml(new ResourceRootFactory() {
+                public ResourceLoader createResourceLoader(final String rootPath, final String loaderPath, final String loaderName) throws IOException {
+                    File file = new File(rootPath, loaderPath);
+                    if (file.isDirectory()) {
+                        return new FileResourceLoader(loaderName, file, context);
+                    } else {
+                        final JarFile jarFile = new JarFile(file, true);
+                        return new JarFileResourceLoader(loaderName, jarFile);
+                    }
+                }
+            }, root.getPath(), new BufferedInputStream(fis), moduleInfoFile.getPath(), moduleLoader, moduleIdentifier);
+        } finally {
+            StreamUtil.safeClose(fis);
+        }
+    }
+
+    static ModuleSpec parseModuleXml(final ResourceRootFactory factory, final String rootPath, InputStream source, final String moduleInfoFile, final ModuleLoader moduleLoader, final ModuleIdentifier moduleIdentifier) throws ModuleLoadException, IOException {
+        try {
+            final MXParser parser = new MXParser();
+            parser.setFeature(FEATURE_PROCESS_NAMESPACES, true);
+            parser.setInput(source, null);
+            return parseDocument(factory, rootPath, parser, moduleLoader, moduleIdentifier);
+        } catch (XmlPullParserException e) {
+            throw new ModuleLoadException("Error loading module from " + moduleInfoFile, e);
+        } finally {
+            StreamUtil.safeClose(source);
+        }
+    }
+
+    protected static XmlPullParserException unexpectedContent(final XmlPullParser reader) {
+        final String kind;
+        switch (reader.getEventType()) {
+            case CDSECT: kind = "cdata"; break;
+            case COMMENT: kind = "comment"; break;
+            case DOCDECL: kind = "document decl"; break;
+            case END_DOCUMENT: kind = "document end"; break;
+            case END_TAG: kind = "element end"; break;
+            case ENTITY_REF: kind = "entity ref"; break;
+            case PROCESSING_INSTRUCTION: kind = "processing instruction"; break;
+            case IGNORABLE_WHITESPACE: kind = "whitespace"; break;
+            case START_DOCUMENT: kind = "document start"; break;
+            case START_TAG: kind = "element start"; break;
+            case TEXT: kind = "text"; break;
+            default: kind = "unknown"; break;
+        }
+        final StringBuilder b = new StringBuilder("Unexpected content of type '").append(kind).append('\'');
+        if (reader.getName() != null) {
+            b.append(" named '").append(reader.getName()).append('\'');
+        }
+        if (reader.getText() != null) {
+            b.append(", text is: '").append(reader.getText()).append('\'');
+        }
+        return new XmlPullParserException(b.toString(), reader, null);
+    }
+
+    protected static XmlPullParserException endOfDocument(final XmlPullParser reader) {
+        return new XmlPullParserException("Unexpected end of document", reader, null);
+    }
+
+    private static XmlPullParserException invalidModuleName(final XmlPullParser reader, final ModuleIdentifier expected) {
+        return new XmlPullParserException("Invalid/mismatched module name (expected " + expected + ")", reader, null);
+    }
+
+    private static XmlPullParserException missingAttributes(final XmlPullParser reader, final Set<String> required) {
+        final StringBuilder b = new StringBuilder("Missing one or more required attributes:");
+        for (String attribute : required) {
+            b.append(' ').append(attribute);
+        }
+        return new XmlPullParserException(b.toString(), reader, null);
+    }
+
+    private static XmlPullParserException unknownAttribute(final XmlPullParser parser, final int index) {
+        final String namespace = parser.getAttributeNamespace(index);
+        final String prefix = parser.getAttributePrefix(index);
+        final String name = parser.getAttributeName(index);
+        final StringBuilder eb = new StringBuilder("Unknown attribute \"");
+        if (prefix != null) eb.append(prefix).append(':');
+        eb.append(name);
+        if (namespace != null) eb.append("\" from namespace \"").append(namespace);
+        eb.append('"');
+        return new XmlPullParserException(eb.toString(), parser, null);
+    }
+
+    private static XmlPullParserException unknownAttributeValue(final XmlPullParser parser, final int index) {
+        final String namespace = parser.getAttributeNamespace(index);
+        final String prefix = parser.getAttributePrefix(index);
+        final String name = parser.getAttributeName(index);
+        final StringBuilder eb = new StringBuilder("Unknown value \"");
+        eb.append(parser.getAttributeValue(index));
+        eb.append("\" for attribute \"");
+        if (prefix != null && ! prefix.isEmpty()) eb.append(prefix).append(':');
+        eb.append(name);
+        if (namespace != null && ! namespace.isEmpty()) eb.append("\" from namespace \"").append(namespace);
+        eb.append('"');
+        return new XmlPullParserException(eb.toString(), parser, null);
+    }
+
+    private static void validateNamespace(final XmlPullParser reader) throws XmlPullParserException {
+        switch (reader.getNamespace()) {
+            case MODULE_1_0:
+            case MODULE_1_1:
+            case MODULE_1_2:
+            case MODULE_1_3:
+                break;
+            default: throw unexpectedContent(reader);
+        }
+    }
+
+    private static void assertNoAttributes(final XmlPullParser reader) throws XmlPullParserException {
+        final int attributeCount = reader.getAttributeCount();
+        if (attributeCount > 0) {
+            throw unknownAttribute(reader, 0);
+        }
+    }
+
+    private static void validateAttributeNamespace(final XmlPullParser reader, final int index) throws XmlPullParserException {
+        if (! reader.getAttributeNamespace(index).isEmpty()) {
+            throw unknownAttribute(reader, index);
+        }
+    }
+
+    private static ModuleSpec parseDocument(final ResourceRootFactory factory, final String rootPath, XmlPullParser reader, final ModuleLoader moduleLoader, final ModuleIdentifier moduleIdentifier) throws XmlPullParserException, IOException {
+        int eventType;
+        while ((eventType = reader.nextTag()) != END_DOCUMENT) {
+            switch (eventType) {
+                case START_DOCUMENT: {
+                    return parseRootElement(factory, rootPath, reader, moduleLoader, moduleIdentifier);
+                }
+                case START_TAG: {
+                    final String element = reader.getName();
+                    switch (element) {
+                        case E_MODULE: {
+                            final ModuleSpec.Builder specBuilder = ModuleSpec.build(moduleIdentifier);
+                            parseModuleContents(reader, factory, moduleLoader, moduleIdentifier, specBuilder, rootPath);
+                            parseEndDocument(reader);
+                            return specBuilder.create();
+                        }
+                        case E_MODULE_ALIAS: {
+                            final ModuleSpec moduleSpec = parseModuleAliasContents(reader, moduleIdentifier);
+                            parseEndDocument(reader);
+                            return moduleSpec;
+                        }
+                        case E_MODULE_ABSENT: {
+                            parseModuleAbsentContents(reader, moduleIdentifier);
+                            return null;
+                        }
+                        default: {
+                            throw unexpectedContent(reader);
+                        }
+                    }
+                }
+                default: {
+                    throw unexpectedContent(reader);
+                }
+            }
+        }
+        throw endOfDocument(reader);
+    }
+
+    private static ModuleSpec parseRootElement(final ResourceRootFactory factory, final String rootPath, final XmlPullParser reader, final ModuleLoader moduleLoader, final ModuleIdentifier moduleIdentifier) throws XmlPullParserException, IOException {
+        assertNoAttributes(reader);
+        int eventType;
+        while ((eventType = reader.nextTag()) != END_DOCUMENT) {
+            switch (eventType) {
+                case START_TAG: {
+                    validateNamespace(reader);
+                    final String element = reader.getName();
+                    switch (element) {
+                        case E_MODULE: {
+                            final ModuleSpec.Builder specBuilder = ModuleSpec.build(moduleIdentifier);
+                            parseModuleContents(reader, factory, moduleLoader, moduleIdentifier, specBuilder, rootPath);
+                            parseEndDocument(reader);
+                            return specBuilder.create();
+                        }
+                        case E_MODULE_ALIAS: {
+                            final ModuleSpec moduleSpec = parseModuleAliasContents(reader, moduleIdentifier);
+                            parseEndDocument(reader);
+                            return moduleSpec;
+                        }
+                        case E_MODULE_ABSENT: {
+                            parseModuleAbsentContents(reader, moduleIdentifier);
+                            return null;
+                        }
+                        default: {
+                            throw unexpectedContent(reader);
+                        }
+                    }
+                }
+                default: {
+                    throw unexpectedContent(reader);
+                }
+            }
+        }
+        throw endOfDocument(reader);
+    }
+
+    private static ModuleSpec parseModuleAliasContents(final XmlPullParser reader, final ModuleIdentifier moduleIdentifier) throws XmlPullParserException, IOException {
+        final int count = reader.getAttributeCount();
+        String name = null;
+        String slot = null;
+        String targetName = null;
+        String targetSlot = null;
+        final Set<String> required = new HashSet<>(Arrays.asList(A_NAME, A_TARGET_NAME));
+        for (int i = 0; i < count; i ++) {
+            validateAttributeNamespace(reader, i);
+            final String attribute = reader.getAttributeName(i);
+            required.remove(attribute);
+            switch (attribute) {
+                case A_NAME:    name = reader.getAttributeValue(i); break;
+                case A_SLOT:    slot = reader.getAttributeValue(i); break;
+                case A_TARGET_NAME: targetName = reader.getAttributeValue(i); break;
+                case A_TARGET_SLOT: targetSlot = reader.getAttributeValue(i); break;
+                default: throw unknownAttribute(reader, i);
+            }
+        }
+        if (! required.isEmpty()) {
+            throw missingAttributes(reader, required);
+        }
+        if (! moduleIdentifier.equals(ModuleIdentifier.create(name, slot))) {
+            throw invalidModuleName(reader, moduleIdentifier);
+        }
+        int eventType;
+        while ((eventType = reader.nextTag()) != END_DOCUMENT) {
+            switch (eventType) {
+                case END_TAG: {
+                    return ModuleSpec.buildAlias(moduleIdentifier, ModuleIdentifier.create(targetName, targetSlot)).create();
+                }
+                default: {
+                    throw unexpectedContent(reader);
+                }
+            }
+        }
+        throw endOfDocument(reader);
+    }
+
+    private static void parseModuleAbsentContents(final XmlPullParser reader, final ModuleIdentifier moduleIdentifier) throws XmlPullParserException, IOException {
+        final int count = reader.getAttributeCount();
+        String name = null;
+        String slot = null;
+        final Set<String> required = new HashSet<>(Arrays.asList(A_NAME, A_TARGET_NAME));
+        for (int i = 0; i < count; i ++) {
+            validateAttributeNamespace(reader, i);
+            final String attribute = reader.getAttributeName(i);
+            required.remove(attribute);
+            switch (attribute) {
+                case A_NAME:    name = reader.getAttributeValue(i); break;
+                case A_SLOT:    slot = reader.getAttributeValue(i); break;
+                default: throw unknownAttribute(reader, i);
+            }
+        }
+        if (! required.isEmpty()) {
+            throw missingAttributes(reader, required);
+        }
+        if (! moduleIdentifier.equals(ModuleIdentifier.create(name, slot))) {
+            throw invalidModuleName(reader, moduleIdentifier);
+        }
+        int eventType;
+        while ((eventType = reader.nextTag()) != END_DOCUMENT) {
+            switch (eventType) {
+                case END_TAG: {
+                    return;
+                }
+                default: {
+                    throw unexpectedContent(reader);
+                }
+            }
+        }
+        throw endOfDocument(reader);
+    }
+
+    private static void parseModuleContents(final XmlPullParser reader, final ResourceRootFactory factory, final ModuleLoader moduleLoader, final ModuleIdentifier moduleIdentifier, final ModuleSpec.Builder specBuilder, final String rootPath) throws XmlPullParserException, IOException {
+        final int count = reader.getAttributeCount();
+        String name = null;
+        String slot = null;
+        final Set<String> required = new HashSet<>(Arrays.asList(A_NAME));
+        for (int i = 0; i < count; i ++) {
+            validateAttributeNamespace(reader, i);
+            final String attribute = reader.getAttributeName(i);
+            required.remove(attribute);
+            switch (attribute) {
+                case A_NAME:    name = reader.getAttributeValue(i); break;
+                case A_SLOT:    slot = reader.getAttributeValue(i); break;
+                default: throw unknownAttribute(reader, i);
+            }
+        }
+        if (! required.isEmpty()) {
+            throw missingAttributes(reader, required);
+        }
+        if (! specBuilder.getIdentifier().equals(ModuleIdentifier.create(name, slot))) {
+            throw invalidModuleName(reader, specBuilder.getIdentifier());
+        }
+        // xsd:all
+        MultiplePathFilterBuilder exportsBuilder = PathFilters.multiplePathFilterBuilder(true);
+        Set<String> visited = new HashSet<>();
+        int eventType;
+        boolean gotPerms = false;
+        specBuilder.addDependency(DependencySpec.createLocalDependencySpec(PathFilters.acceptAll(), exportsBuilder.create()));
+        while ((eventType = reader.nextTag()) != END_DOCUMENT) {
+            switch (eventType) {
+                case END_TAG: {
+                    if (! gotPerms) specBuilder.setPermissionCollection(ModulesPolicy.DEFAULT_PERMISSION_COLLECTION);
+                    return;
+                }
+                case START_TAG: {
+                    validateNamespace(reader);
+                    final String element = reader.getName();
+                    if (visited.contains(element)) {
+                        throw unexpectedContent(reader);
+                    }
+                    visited.add(element);
+                    switch (element) {
+                        case E_EXPORTS:      parseFilterList(reader, exportsBuilder); break;
+                        case E_DEPENDENCIES: parseDependencies(reader, specBuilder); break;
+                        case E_MAIN_CLASS:   parseMainClass(reader, specBuilder); break;
+                        case E_RESOURCES:    parseResources(factory, rootPath, reader, specBuilder); break;
+                        case E_PROPERTIES:   parseProperties(reader, specBuilder); break;
+                        case E_PERMISSIONS:  parsePermissions(reader, moduleLoader, moduleIdentifier, specBuilder); gotPerms = true; break;
+                        default: throw unexpectedContent(reader);
+                    }
+                    break;
+                }
+                default: {
+                    throw unexpectedContent(reader);
+                }
+            }
+        }
+        throw endOfDocument(reader);
+    }
+
+    private static void parseDependencies(final XmlPullParser reader, final ModuleSpec.Builder specBuilder) throws XmlPullParserException, IOException {
+        assertNoAttributes(reader);
+        // xsd:choice
+        int eventType;
+        while ((eventType = reader.nextTag()) != END_DOCUMENT) {
+            switch (eventType) {
+                case END_TAG: {
+                    return;
+                }
+                case START_TAG: {
+                    validateNamespace(reader);
+                    switch (reader.getName()) {
+                        case E_MODULE: parseModuleDependency(reader, specBuilder); break;
+                        case E_SYSTEM: parseSystemDependency(reader, specBuilder); break;
+                        default: throw unexpectedContent(reader);
+                    }
+                    break;
+                }
+                default: {
+                    throw unexpectedContent(reader);
+                }
+            }
+        }
+        throw endOfDocument(reader);
+    }
+
+    private static void parseModuleDependency(final XmlPullParser reader, final ModuleSpec.Builder specBuilder) throws XmlPullParserException, IOException {
+        String name = null;
+        String slot = null;
+        boolean export = false;
+        boolean optional = false;
+        String services = D_NONE;
+        final Set<String> required = new HashSet<>(Arrays.asList(A_NAME));
+        final int count = reader.getAttributeCount();
+        for (int i = 0; i < count; i ++) {
+            validateAttributeNamespace(reader, i);
+            final String attribute = reader.getAttributeName(i);
+            required.remove(attribute);
+            switch (attribute) {
+                case A_NAME:     name = reader.getAttributeValue(i); break;
+                case A_SLOT:     slot = reader.getAttributeValue(i); break;
+                case A_EXPORT:   export = Boolean.parseBoolean(reader.getAttributeValue(i)); break;
+                case A_OPTIONAL: optional = Boolean.parseBoolean(reader.getAttributeValue(i)); break;
+                case A_SERVICES: {
+                    services = reader.getAttributeValue(i);
+                    switch (services) {
+                        case D_NONE:
+                        case D_IMPORT:
+                        case D_EXPORT:
+                            break;
+                        default: throw unknownAttributeValue(reader, i);
+                    }
+                    break;
+                }
+                default: throw unknownAttribute(reader, i);
+            }
+        }
+        if (! required.isEmpty()) {
+            throw missingAttributes(reader, required);
+        }
+        final MultiplePathFilterBuilder importBuilder = PathFilters.multiplePathFilterBuilder(true);
+        final MultiplePathFilterBuilder exportBuilder = PathFilters.multiplePathFilterBuilder(export);
+        int eventType;
+        while ((eventType = reader.nextTag()) != END_DOCUMENT) {
+            switch (eventType) {
+                case END_TAG: {
+                    assert services.equals(D_NONE) || services.equals(D_EXPORT) || services.equals(D_IMPORT);
+                    if (services.equals(D_EXPORT)) {
+                        // If services are to be re-exported, add META-INF/services -> true near the end of the list
+                        exportBuilder.addFilter(PathFilters.getMetaInfServicesFilter(), true);
+                    }
+                    if (export) {
+                        // If re-exported, add META-INF/** -> false at the end of the list (require explicit override)
+                        exportBuilder.addFilter(PathFilters.getMetaInfSubdirectoriesFilter(), false);
+                        exportBuilder.addFilter(PathFilters.getMetaInfFilter(), false);
+                    }
+                    final PathFilter exportFilter = exportBuilder.create();
+                    final PathFilter importFilter;
+                    if (importBuilder.isEmpty()) {
+                        importFilter = services.equals(D_NONE) ? PathFilters.getDefaultImportFilter() : PathFilters.getDefaultImportFilterWithServices();
+                    } else {
+                        if (! services.equals(D_NONE)) {
+                            importBuilder.addFilter(PathFilters.getMetaInfServicesFilter(), true);
+                        }
+                        importBuilder.addFilter(PathFilters.getMetaInfSubdirectoriesFilter(), false);
+                        importBuilder.addFilter(PathFilters.getMetaInfFilter(), false);
+                        importFilter = importBuilder.create();
+                    }
+                    specBuilder.addDependency(DependencySpec.createModuleDependencySpec(importFilter, exportFilter, null, ModuleIdentifier.create(name, slot), optional));
+                    return;
+                }
+                case START_TAG: {
+                    validateNamespace(reader);
+                    switch (reader.getName()) {
+                        case E_EXPORTS: parseFilterList(reader, exportBuilder); break;
+                        case E_IMPORTS: parseFilterList(reader, importBuilder); break;
+                        default: throw unexpectedContent(reader);
+                    }
+                    break;
+                }
+                default: {
+                    throw unexpectedContent(reader);
+                }
+            }
+        }
+    }
+
+    private static void parseSystemDependency(final XmlPullParser reader, final ModuleSpec.Builder specBuilder) throws XmlPullParserException, IOException {
+        boolean export = false;
+        final int count = reader.getAttributeCount();
+        for (int i = 0; i < count; i ++) {
+            validateAttributeNamespace(reader, i);
+            final String attribute = reader.getAttributeName(i);
+            switch (attribute) {
+                case A_EXPORT:  export = Boolean.parseBoolean(reader.getAttributeValue(i)); break;
+                default: throw unexpectedContent(reader);
+            }
+        }
+        Set<String> paths = Collections.emptySet();
+        final MultiplePathFilterBuilder exportBuilder = PathFilters.multiplePathFilterBuilder(export);
+        int eventType;
+        while ((eventType = reader.nextTag()) != END_DOCUMENT) {
+            switch (eventType) {
+                case END_TAG: {
+                    final PathFilter exportFilter = exportBuilder.create();
+                    specBuilder.addDependency(DependencySpec.createSystemDependencySpec(PathFilters.acceptAll(), exportFilter, paths));
+                    return;
+                }
+                case START_TAG: {
+                    validateNamespace(reader);
+                    switch (reader.getName()) {
+                        case E_PATHS: {
+                            paths = parseSet(reader);
+                            break;
+                        }
+                        case E_EXPORTS: {
+                            parseFilterList(reader, exportBuilder);
+                            break;
+                        }
+                        default: {
+                            throw unexpectedContent(reader);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private static void parseMainClass(final XmlPullParser reader, final ModuleSpec.Builder specBuilder) throws XmlPullParserException, IOException {
+        String name = null;
+        final Set<String> required = new HashSet<>(Arrays.asList(A_NAME));
+        final int count = reader.getAttributeCount();
+        for (int i = 0; i < count; i ++) {
+            validateAttributeNamespace(reader, i);
+            final String attribute = reader.getAttributeName(i);
+            required.remove(attribute);
+            switch (attribute) {
+                case A_NAME: name = reader.getAttributeValue(i); break;
+                default: throw unexpectedContent(reader);
+            }
+        }
+        if (! required.isEmpty()) {
+            throw missingAttributes(reader, required);
+        }
+        specBuilder.setMainClass(name);
+        // consume remainder of element
+        parseNoContent(reader);
+    }
+
+    private static void parseResources(final ResourceRootFactory factory, final String rootPath, final XmlPullParser reader, final ModuleSpec.Builder specBuilder) throws XmlPullParserException, IOException {
+        assertNoAttributes(reader);
+        // xsd:choice
+        int eventType;
+        while ((eventType = reader.nextTag()) != END_DOCUMENT) {
+            switch (eventType) {
+                case END_TAG: {
+                    specBuilder.addResourceRoot(new ResourceLoaderSpec(new NativeLibraryResourceLoader(new File(rootPath, "lib")), PathFilters.rejectAll()));
+                    return;
+                }
+                case START_TAG: {
+                    validateNamespace(reader);
+                    switch (reader.getName()) {
+                        case E_RESOURCE_ROOT: {
+                            parseResourceRoot(factory, rootPath, reader, specBuilder);
+                            break;
+                        }
+                        case E_ARTIFACT: {
+                            parseArtifact(reader, specBuilder);
+                            break;
+                        }
+                        case E_NATIVE_ARTIFACT: {
+                            parseNativeArtifact(reader, specBuilder);
+                            break;
+                        }
+                        default: throw unexpectedContent(reader);
+                    }
+                    break;
+                }
+                default: {
+                    throw unexpectedContent(reader);
+                }
+            }
+        }
+        throw endOfDocument(reader);
+    }
+
+    static ResourceLoader createMavenArtifactLoader(final String name) throws IOException
+    {
+        File fp = MavenArtifactUtil.resolveJarArtifact(name);
+        if (fp == null) return null;
+        JarFile jarFile = new JarFile(fp, true);
+        return new JarFileResourceLoader(name, jarFile);
+    }
+
+    static void createMavenNativeArtifactLoader(final String name, final XmlPullParser reader, final ModuleSpec.Builder specBuilder) throws IOException, XmlPullParserException
+    {
+        File fp = MavenArtifactUtil.resolveJarArtifact(name);
+        if (fp == null) throw new XmlPullParserException(String.format("Failed to resolve native artifact '%s'", name), reader, null);
+        File lib = new File(fp.getParentFile(), "lib");
+        if (!lib.exists()) {
+            if (!fp.getParentFile().canWrite()) throw new XmlPullParserException(String.format("Native artifact '%s' cannot be unpacked", name), reader, null);
+            StreamUtil.unzip(fp, fp.getParentFile());
+        }
+        specBuilder.addResourceRoot(new ResourceLoaderSpec(new NativeLibraryResourceLoader(lib), PathFilters.rejectAll()));
+    }
+
+
+    private static void parseNativeArtifact(final XmlPullParser reader, final ModuleSpec.Builder specBuilder) throws XmlPullParserException, IOException {
+        String name = null;
+        final Set<String> required = new HashSet<>(Arrays.asList(A_NAME));
+        final int count = reader.getAttributeCount();
+        for (int i = 0; i < count; i ++) {
+            validateAttributeNamespace(reader, i);
+            final String attribute = reader.getAttributeName(i);
+            required.remove(attribute);
+            switch (attribute) {
+                case A_NAME: name = reader.getAttributeValue(i); break;
+                default: throw unknownAttribute(reader, i);
+            }
+        }
+        if (! required.isEmpty()) {
+            throw missingAttributes(reader, required);
+        }
+
+        int eventType;
+        while ((eventType = reader.nextTag()) != END_DOCUMENT) {
+            switch (eventType) {
+                case END_TAG: {
+                    try {
+                        createMavenNativeArtifactLoader(name, reader, specBuilder);
+                    } catch (IOException e) {
+                        throw new XmlPullParserException(String.format("Failed to add artifact '%s'", name), reader, e);
+                    }
+                    return;
+                }
+                case START_TAG: {
+                    throw unexpectedContent(reader);
+                }
+                default: {
+                    throw unexpectedContent(reader);
+                }
+            }
+        }
+    }
+
+    private static void parseArtifact(final XmlPullParser reader, final ModuleSpec.Builder specBuilder) throws XmlPullParserException, IOException {
+        String name = null;
+        final Set<String> required = new HashSet<>(Arrays.asList(A_NAME));
+        final int count = reader.getAttributeCount();
+        for (int i = 0; i < count; i ++) {
+            validateAttributeNamespace(reader, i);
+            final String attribute = reader.getAttributeName(i);
+            required.remove(attribute);
+            switch (attribute) {
+                case A_NAME: name = reader.getAttributeValue(i); break;
+                default: throw unknownAttribute(reader, i);
+            }
+        }
+        if (! required.isEmpty()) {
+            throw missingAttributes(reader, required);
+        }
+
+        ResourceLoader resourceLoader;
+        int eventType;
+        while ((eventType = reader.nextTag()) != END_DOCUMENT) {
+            switch (eventType) {
+                case END_TAG: {
+                    try {
+                        resourceLoader = createMavenArtifactLoader(name);
+                    } catch (IOException e) {
+                        throw new XmlPullParserException(String.format("Failed to add artifact '%s'", name), reader, e);
+                    }
+                    if (resourceLoader == null) throw new XmlPullParserException(String.format("Failed to resolve artifact '%s'", name), reader, null);
+                    specBuilder.addResourceRoot(ResourceLoaderSpec.createResourceLoaderSpec(resourceLoader));
+                    return;
+                }
+                case START_TAG: {
+                    throw unexpectedContent(reader);
+                }
+                default: {
+                    throw unexpectedContent(reader);
+                }
+            }
+        }
+    }
+
+    private static void parseResourceRoot(final ResourceRootFactory factory, final String rootPath, final XmlPullParser reader, final ModuleSpec.Builder specBuilder) throws XmlPullParserException, IOException {
+        String name = null;
+        String path = null;
+        final Set<String> required = new HashSet<>(Arrays.asList(A_PATH));
+        final int count = reader.getAttributeCount();
+        for (int i = 0; i < count; i ++) {
+            validateAttributeNamespace(reader, i);
+            final String attribute = reader.getAttributeName(i);
+            required.remove(attribute);
+            switch (attribute) {
+                case A_NAME: name = reader.getAttributeValue(i); break;
+                case A_PATH: path = reader.getAttributeValue(i); break;
+                default: throw unknownAttribute(reader, i);
+            }
+        }
+        if (! required.isEmpty()) {
+            throw missingAttributes(reader, required);
+        }
+        if (name == null) name = path;
+
+        final MultiplePathFilterBuilder filterBuilder = PathFilters.multiplePathFilterBuilder(true);
+        final ResourceLoader resourceLoader;
+
+        final Set<String> encountered = new HashSet<>();
+        int eventType;
+        while ((eventType = reader.nextTag()) != END_DOCUMENT) {
+            switch (eventType) {
+                case END_TAG: {
+                    try {
+                        resourceLoader = factory.createResourceLoader(rootPath, path, name);
+                    } catch (IOException e) {
+                        throw new XmlPullParserException(String.format("Failed to add resource root '%s' at path '%s'", name, path), reader, e);
+                    }
+                    specBuilder.addResourceRoot(new ResourceLoaderSpec(resourceLoader, filterBuilder.create()));
+                    return;
+                }
+                case START_TAG: {
+                    validateNamespace(reader);
+                    final String element = reader.getName();
+                    if (! encountered.add(element)) throw unexpectedContent(reader);
+                    switch (element) {
+                        case E_FILTER: parseFilterList(reader, filterBuilder); break;
+                        default: throw unexpectedContent(reader);
+                    }
+                    break;
+                }
+                default: {
+                    throw unexpectedContent(reader);
+                }
+            }
+        }
+    }
+
+    private static void parseFilterList(final XmlPullParser reader, final MultiplePathFilterBuilder builder) throws XmlPullParserException, IOException {
+        assertNoAttributes(reader);
+        // xsd:choice
+        int eventType;
+        while ((eventType = reader.nextTag()) != END_DOCUMENT) {
+            switch (eventType) {
+                case END_TAG: {
+                    return;
+                }
+                case START_TAG: {
+                    validateNamespace(reader);
+                    switch (reader.getName()) {
+                        case E_INCLUDE: parsePath(reader, true, builder); break;
+                        case E_EXCLUDE: parsePath(reader, false, builder); break;
+                        case E_INCLUDE_SET: parseSet(reader, true, builder); break;
+                        case E_EXCLUDE_SET: parseSet(reader, false, builder); break;
+                        default: throw unexpectedContent(reader);
+                    }
+                    break;
+                }
+                default: {
+                    throw unexpectedContent(reader);
+                }
+            }
+        }
+        throw endOfDocument(reader);
+    }
+
+    private static void parsePath(final XmlPullParser reader, final boolean include, final MultiplePathFilterBuilder builder) throws XmlPullParserException, IOException {
+        String path = null;
+        final Set<String> required = new HashSet<>(Arrays.asList(A_PATH));
+        final int count = reader.getAttributeCount();
+        for (int i = 0; i < count; i ++) {
+            validateAttributeNamespace(reader, i);
+            final String attribute = reader.getAttributeName(i);
+            required.remove(attribute);
+            switch (attribute) {
+                case A_PATH: path = reader.getAttributeValue(i); break;
+                default: throw unknownAttribute(reader, i);
+            }
+        }
+        if (! required.isEmpty()) {
+            throw missingAttributes(reader, required);
+        }
+
+        final boolean literal = path.indexOf('*') == -1 && path.indexOf('?') == -1;
+        if (literal) {
+            if (path.charAt(path.length() - 1) == '/') {
+                builder.addFilter(PathFilters.isChildOf(path), include);
+            } else {
+                builder.addFilter(PathFilters.is(path), include);
+            }
+        } else {
+            builder.addFilter(PathFilters.match(path), include);
+        }
+
+        // consume remainder of element
+        parseNoContent(reader);
+    }
+
+    private static Set<String> parseSet(final XmlPullParser reader) throws XmlPullParserException, IOException {
+        assertNoAttributes(reader);
+        final Set<String> set = new FastCopyHashSet<>();
+        // xsd:choice
+        int eventType;
+        while ((eventType = reader.nextTag()) != END_DOCUMENT) {
+            switch (eventType) {
+                case END_TAG: {
+                    return set;
+                }
+                case START_TAG: {
+                    validateNamespace(reader);
+                    switch (reader.getName()) {
+                        case E_PATH: parsePathName(reader, set); break;
+                        default: throw unexpectedContent(reader);
+                    }
+                }
+            }
+        }
+        return set;
+    }
+
+    private static void parseSet(final XmlPullParser reader, final boolean include, final MultiplePathFilterBuilder builder) throws XmlPullParserException, IOException {
+        builder.addFilter(PathFilters.in(parseSet(reader)), include);
+    }
+
+    private static void parsePathName(final XmlPullParser reader, final Set<String> set) throws XmlPullParserException, IOException {
+        String name = null;
+        final Set<String> required = new HashSet<>(Arrays.asList(A_NAME));
+        final int count = reader.getAttributeCount();
+        for (int i = 0; i < count; i ++) {
+            validateAttributeNamespace(reader, i);
+            final String attribute = reader.getAttributeName(i);
+            required.remove(attribute);
+            switch (attribute) {
+                case A_NAME: name = reader.getAttributeValue(i); break;
+                default: throw unknownAttribute(reader, i);
+            }
+        }
+        if (! required.isEmpty()) {
+            throw missingAttributes(reader, required);
+        }
+        set.add(name);
+
+        // consume remainder of element
+        parseNoContent(reader);
+    }
+
+    private static void parseProperties(final XmlPullParser reader, final ModuleSpec.Builder specBuilder) throws XmlPullParserException, IOException {
+        assertNoAttributes(reader);
+        // xsd:choice
+        int eventType;
+        while ((eventType = reader.nextTag()) != END_DOCUMENT) {
+            switch (eventType) {
+                case END_TAG: {
+                    return;
+                }
+                case START_TAG: {
+                    validateNamespace(reader);
+                    switch (reader.getName()) {
+                        case E_PROPERTY: {
+                            parseProperty(reader, specBuilder);
+                            break;
+                        }
+                        default: throw unexpectedContent(reader);
+                    }
+                    break;
+                }
+                default: {
+                    throw unexpectedContent(reader);
+                }
+            }
+        }
+        throw endOfDocument(reader);
+    }
+
+    private static void parseProperty(final XmlPullParser reader, final ModuleSpec.Builder specBuilder) throws XmlPullParserException, IOException {
+        String name = null;
+        String value = null;
+        final Set<String> required = new HashSet<>(Arrays.asList(A_NAME));
+        final int count = reader.getAttributeCount();
+        for (int i = 0; i < count; i ++) {
+            validateAttributeNamespace(reader, i);
+            final String attribute = reader.getAttributeName(i);
+            required.remove(attribute);
+            switch (attribute) {
+                case A_NAME: name = reader.getAttributeValue(i); break;
+                case A_VALUE: value = reader.getAttributeValue(i); break;
+                default: throw unknownAttribute(reader, i);
+            }
+        }
+        if (! required.isEmpty()) {
+            throw missingAttributes(reader, required);
+        }
+        specBuilder.addProperty(name, value == null ? "true" : value);
+        if ("jboss.assertions".equals(name)) try {
+            specBuilder.setAssertionSetting(AssertionSetting.valueOf(value.toUpperCase(Locale.US)));
+        } catch (IllegalArgumentException ignored) {}
+
+        // consume remainder of element
+        parseNoContent(reader);
+    }
+
+    private static void parsePermissions(final XmlPullParser reader, final ModuleLoader moduleLoader, final ModuleIdentifier moduleIdentifier, final ModuleSpec.Builder specBuilder) throws XmlPullParserException, IOException {
+        assertNoAttributes(reader);
+        // xsd:choice
+        ArrayList<PermissionFactory> list = new ArrayList<>();
+        int eventType;
+        while ((eventType = reader.nextTag()) != END_DOCUMENT) {
+            switch (eventType) {
+                case END_TAG: {
+                    specBuilder.setPermissionCollection(new FactoryPermissionCollection(list.toArray(new PermissionFactory[list.size()])));
+                    return;
+                }
+                case START_TAG: {
+                    validateNamespace(reader);
+                    switch (reader.getName()) {
+                        case E_GRANT: {
+                            parseGrant(reader, moduleLoader, moduleIdentifier, list);
+                            break;
+                        }
+                        default: throw unexpectedContent(reader);
+                    }
+                    break;
+                }
+                default: {
+                    throw unexpectedContent(reader);
+                }
+            }
+        }
+        throw endOfDocument(reader);
+    }
+
+    private static void parseGrant(final XmlPullParser reader, final ModuleLoader moduleLoader, final ModuleIdentifier moduleIdentifier, final ArrayList<PermissionFactory> list) throws XmlPullParserException, IOException {
+        String permission = null;
+        String name = null;
+        String actions = null;
+        final Set<String> required = new HashSet<>(Arrays.asList(A_PERMISSION, A_NAME));
+        final int count = reader.getAttributeCount();
+        for (int i = 0; i < count; i ++) {
+            validateAttributeNamespace(reader, i);
+            final String attribute = reader.getAttributeName(i);
+            required.remove(attribute);
+            switch (attribute) {
+                case A_PERMISSION: permission = reader.getAttributeValue(i); break;
+                case A_NAME: name = reader.getAttributeValue(i); break;
+                case A_ACTIONS: actions = reader.getAttributeValue(i); break;
+                default: throw unknownAttribute(reader, i);
+            }
+        }
+        if (! required.isEmpty()) {
+            throw missingAttributes(reader, required);
+        }
+        list.add(new ModularPermissionFactory(moduleLoader, moduleIdentifier, permission, name, actions));
+
+        // consume remainder of element
+        parseNoContent(reader);
+    }
+
+    private static void parseNoContent(final XmlPullParser reader) throws XmlPullParserException, IOException {
+        int eventType;
+        while ((eventType = reader.nextTag()) != END_DOCUMENT) {
+            switch (eventType) {
+                case END_TAG: {
+                    return;
+                }
+                default: {
+                    throw unexpectedContent(reader);
+                }
+            }
+        }
+        throw endOfDocument(reader);
+    }
+
+    static void parseEndDocument(final XmlPullParser reader) throws XmlPullParserException, IOException {
+        int eventType;
+        while ((eventType = reader.nextToken()) != END_DOCUMENT) {
+            switch (eventType) {
+                case END_DOCUMENT: {
+                    return;
+                }
+                case TEXT:
+                case CDSECT: {
+                    if (! reader.isWhitespace()) {
+                        throw unexpectedContent(reader);
+                    }
+                    // ignore
+                    break;
+                }
+                case IGNORABLE_WHITESPACE:
+                case COMMENT: {
+                    // ignore
+                    break;
+                }
+                default: {
+                    throw unexpectedContent(reader);
+                }
+            }
+        }
+        return;
+    }
+}
diff --git a/src/main/java/org/jboss/modules/ModuleXmlUtil.java b/src/main/java/org/jboss/modules/ModuleXmlUtil.java
new file mode 100644
index 0000000..e1b012f
--- /dev/null
+++ b/src/main/java/org/jboss/modules/ModuleXmlUtil.java
@@ -0,0 +1,155 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.io.File;
+
+/**
+ * Utility class for default module file names.
+ * <p/>
+ * Date: 10.05.2011
+ *
+ * @author <a href="mailto:jperkins at redhat.com">James R. Perkins</a>
+ */
+class ModuleXmlUtil {
+    public static final String DEFAULT_FILENAME = "module.xml";
+
+    /**
+     * Private constructor.
+     */
+    private ModuleXmlUtil() {
+    }
+
+    /**
+     * Creates a file based on the directory and the module identifier.
+     * <p/>
+     * The {@code dir} parameter must be a directory and the {@code identifier} cannot be {@code null}.
+     *
+     * @param dir        the base directory where the file should be located.
+     * @param identifier the module identifier.
+     *
+     * @return the module XML file.
+     * @throws IllegalArgumentException if the {@code dir} parameter is not a directory or an argument is {@code null}.
+     */
+    public static File toFile(final File dir, final ModuleIdentifier identifier) {
+        return toFile(dir, DEFAULT_FILENAME, identifier);
+    }
+
+    /**
+     * Creates a file based on the directory and the module identifier.
+     * <p/>
+     * The {@code dir} parameter must be a directory and the {@code identifier} cannot be {@code null}.
+     *
+     * @param dir        the base directory where the file should be located.
+     * @param name       the name of the XML file.
+     * @param identifier the module identifier.
+     *
+     * @return the module XML file.
+     * @throws IllegalArgumentException if the {@code dir} parameter is not a directory or an argument is {@code null}.
+     */
+    public static File toFile(final File dir, final String name, final ModuleIdentifier identifier) {
+        if (dir == null || !dir.isDirectory()) {
+            throw new IllegalArgumentException(String.format("Must be a directory. File %s is not a directory.", dir));
+        }
+        if (name == null || name.isEmpty()) {
+            throw new IllegalArgumentException("The name cannot be empty.");
+        }
+        if (identifier == null) {
+            throw new IllegalArgumentException("The module identifier cannot be null.");
+        }
+        return new File(dir, baseFilename(name, identifier));
+    }
+
+    /**
+     * Creates a path name from the module identifier. The name always ends with the separator character.
+     * </p>
+     * A {@code null} identifier will result in no separator being used.
+     *
+     * @param identifier the module identifier.
+     * @param separator  the directory separator.
+     *
+     * @return a path name of the module identifier.
+     * @throws IllegalArgumentException if the module identifier is {@code null}.
+     */
+    public static String baseDirectory(final ModuleIdentifier identifier, final String separator) {
+        if (identifier == null) {
+            throw new IllegalArgumentException("The module identifier cannot be null.");
+        }
+        final String namePath = identifier.getName().replace('.', File.separatorChar);
+        final StringBuilder baseName = new StringBuilder();
+        baseName.append(namePath).
+                append((separator == null ? "" : separator)).
+                append(identifier.getSlot()).
+                append((separator == null ? "" : separator));
+        return baseName.toString();
+    }
+
+    /**
+     * Creates a path name from the module identifier with the default {@link java.io.File#separator} character.
+     *
+     * @param identifier the module identifier.
+     *
+     * @return a path name of the module identifier.
+     * @throws IllegalArgumentException if the module identifier is {@code null}.
+     * @see #baseDirectory(ModuleIdentifier, String)
+     */
+    public static String baseDirectory(final ModuleIdentifier identifier) {
+        return baseDirectory(identifier, File.separator);
+    }
+
+    /**
+     * Creates a path name to the module XML file from the module identifier. Uses the {@link #DEFAULT_FILENAME} for
+     * the XML file name.
+     *
+     * @param identifier the module identifier.
+     *
+     * @return a path name to the module XML file.
+     * @throws IllegalArgumentException if the module identifier is {@code null}.
+     */
+    public static String baseFilename(final ModuleIdentifier identifier) {
+        return baseFilename(DEFAULT_FILENAME, identifier);
+    }
+
+    /**
+     * Creates a path name to the module XML file from the module identifier.
+     *
+     * @param name       the XML file name.
+     * @param identifier the module identifier.
+     *
+     * @return a path name to the module XML file.
+     * @throws IllegalArgumentException if the module identifier is {@code null}.
+     */
+    public static String baseFilename(final String name, final ModuleIdentifier identifier) {
+        return baseDirectory(identifier) + name;
+    }
+
+    /**
+     * Creates a path name to the module XML
+     *
+     * @param name       the XML file name. file from the module identifier.
+     * @param separator  the directory separator.
+     * @param identifier the module identifier.
+     *
+     * @return a path name to the module XML file.
+     * @throws IllegalArgumentException if the module identifier is {@code null}.
+     */
+    public static String baseFilename(final String name, final String separator, final ModuleIdentifier identifier) {
+        return baseDirectory(identifier, separator) + name;
+    }
+}
diff --git a/src/main/java/org/jboss/modules/ModulesPolicy.java b/src/main/java/org/jboss/modules/ModulesPolicy.java
new file mode 100644
index 0000000..1b8da50
--- /dev/null
+++ b/src/main/java/org/jboss/modules/ModulesPolicy.java
@@ -0,0 +1,79 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.security.AllPermission;
+import java.security.CodeSource;
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.security.Permissions;
+import java.security.Policy;
+import java.security.ProtectionDomain;
+import java.security.Provider;
+
+final class ModulesPolicy extends Policy {
+
+    private static final AllPermission ALL_PERMISSION = new AllPermission();
+
+    static final Permissions DEFAULT_PERMISSION_COLLECTION = getAllPermission();
+
+    private static final CodeSource ourCodeSource = ModulesPolicy.class.getProtectionDomain().getCodeSource();
+
+    private final Policy policy;
+
+    private static Permissions getAllPermission() {
+        final Permissions permissions = new Permissions();
+        permissions.add(ALL_PERMISSION);
+        return permissions;
+    }
+
+    public ModulesPolicy(final Policy policy) {
+        this.policy = policy;
+    }
+
+    public Provider getProvider() {
+        return policy.getProvider();
+    }
+
+    public String getType() {
+        return policy.getType();
+    }
+
+    public Parameters getParameters() {
+        return policy.getParameters();
+    }
+
+    public PermissionCollection getPermissions(final CodeSource codesource) {
+        return codesource.equals(ourCodeSource) ? getAllPermission() : policy.getPermissions(codesource);
+    }
+
+    public PermissionCollection getPermissions(final ProtectionDomain domain) {
+        final CodeSource codeSource = domain.getCodeSource();
+        return codeSource != null && codeSource.equals(ourCodeSource) ? getAllPermission() : policy.getPermissions(domain);
+    }
+
+    public boolean implies(final ProtectionDomain domain, final Permission permission) {
+        final CodeSource codeSource = domain.getCodeSource();
+        return codeSource != null && codeSource.equals(ourCodeSource) || policy.implies(domain, permission);
+    }
+
+    public void refresh() {
+        policy.refresh();
+    }
+}
diff --git a/src/main/java/org/jboss/modules/NativeLibraryResourceLoader.java b/src/main/java/org/jboss/modules/NativeLibraryResourceLoader.java
new file mode 100644
index 0000000..71c090d
--- /dev/null
+++ b/src/main/java/org/jboss/modules/NativeLibraryResourceLoader.java
@@ -0,0 +1,320 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.io.File;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.Locale;
+
+/**
+ * A base class for resource loaders which can load native libraries.
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public class NativeLibraryResourceLoader extends AbstractResourceLoader {
+
+    /**
+     * Separate class for native platform ID which is only loaded when native libs are loaded.
+     */
+    static class Identification {
+        static final String OS_ID;
+        static final String CPU_ID;
+        static final String ARCH_NAME;
+        static final String[] NATIVE_SEARCH_PATHS;
+
+        static {
+            final Object[] strings = AccessController.doPrivileged(new PrivilegedAction<Object[]>() {
+                public Object[] run() {
+                    // First, identify the operating system.
+                    boolean knownOs = true;
+                    String osName;
+                    // let the user override it.
+                    osName = System.getProperty("jboss.modules.os-name");
+                    if (osName == null) {
+                        String sysOs = System.getProperty("os.name");
+                        if (sysOs == null) {
+                            osName = "unknown";
+                            knownOs = false;
+                        } else {
+                            sysOs = sysOs.toUpperCase(Locale.US);
+                            if (sysOs.startsWith("LINUX")) {
+                                osName = "linux";
+                            } else if (sysOs.startsWith("MAC OS")) {
+                                osName = "macosx";
+                            } else if (sysOs.startsWith("WINDOWS")) {
+                                osName = "win";
+                            } else if (sysOs.startsWith("OS/2")) {
+                                osName = "os2";
+                            } else if (sysOs.startsWith("SOLARIS") || sysOs.startsWith("SUNOS")) {
+                                osName = "solaris";
+                            } else if (sysOs.startsWith("MPE/IX")) {
+                                osName = "mpeix";
+                            } else if (sysOs.startsWith("HP-UX")) {
+                                osName = "hpux";
+                            } else if (sysOs.startsWith("AIX")) {
+                                osName = "aix";
+                            } else if (sysOs.startsWith("OS/390")) {
+                                osName = "os390";
+                            } else if (sysOs.startsWith("OS/400")) {
+                                osName = "os400";
+                            } else if (sysOs.startsWith("FREEBSD")) {
+                                osName = "freebsd";
+                            } else if (sysOs.startsWith("OPENBSD")) {
+                                osName = "openbsd";
+                            } else if (sysOs.startsWith("NETBSD")) {
+                                osName = "netbsd";
+                            } else if (sysOs.startsWith("IRIX")) {
+                                osName = "irix";
+                            } else if (sysOs.startsWith("DIGITAL UNIX")) {
+                                osName = "digitalunix";
+                            } else if (sysOs.startsWith("OSF1")) {
+                                osName = "osf1";
+                            } else if (sysOs.startsWith("OPENVMS")) {
+                                osName = "openvms";
+                            } else if (sysOs.startsWith("IOS")) {
+                                osName = "iOS";
+                            } else {
+                                osName = "unknown";
+                                knownOs = false;
+                            }
+                        }
+                    }
+                    // Next, our CPU ID and its compatible variants.
+                    boolean knownCpu = true;
+                    ArrayList<String> cpuNames = new ArrayList<>();
+
+                    String cpuName = System.getProperty("jboss.modules.cpu-name");
+                    if (cpuName == null) {
+                        String sysArch = System.getProperty("os.arch");
+                        if (sysArch == null) {
+                            cpuName = "unknown";
+                            knownCpu = false;
+                        } else {
+                            boolean hasEndian = false;
+                            boolean hasHardFloatABI = false;
+                            sysArch = sysArch.toUpperCase(Locale.US);
+                            if (sysArch.startsWith("SPARCV9") || sysArch.startsWith("SPARC64")) {
+                                cpuName = "sparcv9";
+                            } else if (sysArch.startsWith("SPARC")) {
+                                cpuName = "sparc";
+                            } else if (sysArch.startsWith("X86_64") || sysArch.startsWith("AMD64")) {
+                                cpuName = "x86_64";
+                            } else if (sysArch.startsWith("I386")) {
+                                cpuName = "i386";
+                            } else if (sysArch.startsWith("I486")) {
+                                cpuName = "i486";
+                            } else if (sysArch.startsWith("I586")) {
+                                cpuName = "i586";
+                            } else if (sysArch.startsWith("I686") || sysArch.startsWith("X86") || sysArch.contains("IA32")) {
+                                cpuName = "i686";
+                            } else if (sysArch.startsWith("X32")) {
+                                cpuName = "x32";
+                            } else if (sysArch.startsWith("PPC64")) {
+                                cpuName = "ppc64";
+                            } else if (sysArch.startsWith("PPC") || sysArch.startsWith("POWER")) {
+                                cpuName = "ppc";
+                            } else if (sysArch.startsWith("ARMV7A") || sysArch.contains("AARCH32")) {
+                                hasEndian = true;
+                                hasHardFloatABI = true;
+                                cpuName = "armv7a";
+                            } else if (sysArch.startsWith("AARCH64") || sysArch.startsWith("ARM64") || sysArch.startsWith("ARMV8") || sysArch.startsWith("PXA9") || sysArch.startsWith("PXA10")) {
+                                hasEndian = true;
+                                cpuName = "aarch64";
+                            } else if (sysArch.startsWith("PXA27")) {
+                                hasEndian = true;
+                                cpuName = "armv5t-iwmmx";
+                            } else if (sysArch.startsWith("PXA3")) {
+                                hasEndian = true;
+                                cpuName = "armv5t-iwmmx2";
+                            } else if (sysArch.startsWith("ARMV4T") || sysArch.startsWith("EP93")) {
+                                hasEndian = true;
+                                cpuName = "armv4t";
+                            } else if (sysArch.startsWith("ARMV4") || sysArch.startsWith("EP73")) {
+                                hasEndian = true;
+                                cpuName = "armv4";
+                            } else if (sysArch.startsWith("ARMV5T") || sysArch.startsWith("PXA") || sysArch.startsWith("IXC") || sysArch.startsWith("IOP") || sysArch.startsWith("IXP") || sysArch.startsWith("CE")) {
+                                hasEndian = true;
+                                String isaList = System.getProperty("sun.arch.isalist");
+                                if (isaList != null) {
+                                    if (isaList.toUpperCase(Locale.US).contains("MMX2")) {
+                                        cpuName = "armv5t-iwmmx2";
+                                    } else if (isaList.toUpperCase(Locale.US).contains("MMX")) {
+                                        cpuName = "armv5t-iwmmx";
+                                    } else {
+                                        cpuName = "armv5t";
+                                    }
+                                } else {
+                                    cpuName = "armv5t";
+                                }
+                            } else if (sysArch.startsWith("ARMV5")) {
+                                hasEndian = true;
+                                cpuName = "armv5";
+                            } else if (sysArch.startsWith("ARMV6")) {
+                                hasEndian = true;
+                                hasHardFloatABI = true;
+                                cpuName = "armv6";
+                            } else if (sysArch.startsWith("PA_RISC2.0W")) {
+                                cpuName = "parisc64";
+                            } else if (sysArch.startsWith("PA_RISC") || sysArch.startsWith("PA-RISC")) {
+                                cpuName = "parisc";
+                            } else if (sysArch.startsWith("IA64")) {
+                                // HP-UX reports IA64W for 64-bit Itanium and IA64N when running
+                                // in 32-bit mode.
+                                cpuName = sysArch.toLowerCase(Locale.US);
+                            } else if (sysArch.startsWith("ALPHA")) {
+                                cpuName = "alpha";
+                            } else if (sysArch.startsWith("MIPS")) {
+                                cpuName = "mips";
+                            } else {
+                                knownCpu = false;
+                                cpuName = "unknown";
+                            }
+
+                            boolean be = false;
+                            boolean hf = false;
+
+                            if (knownCpu && hasEndian && "big".equals(System.getProperty("sun.cpu.endian", "little"))) {
+                                be = true;
+                            }
+
+                            if (knownCpu && hasHardFloatABI) {
+                                String archAbi = System.getProperty("sun.arch.abi");
+                                if (archAbi != null) {
+                                    if (archAbi.toUpperCase(Locale.US).contains("HF")) {
+                                        hf = true;
+                                    }
+                                } else {
+                                    String libPath = System.getProperty("java.library.path");
+                                    if (libPath != null && libPath.toUpperCase(Locale.US).contains("GNUEABIHF")) {
+                                        hf = true;
+                                    }
+                                }
+                                if (hf) cpuName += "-hf";
+                            }
+
+                            if (knownCpu) {
+                                switch (cpuName) {
+                                    case "i686": cpuNames.add("i686");
+                                    case "i586": cpuNames.add("i586");
+                                    case "i486": cpuNames.add("i486");
+                                    case "i386": cpuNames.add("i386");
+                                        break;
+                                    case "armv7a": cpuNames.add("armv7a"); if (hf) break;
+                                    case "armv6":  cpuNames.add("armv6"); if (hf) break;
+                                    case "armv5t": cpuNames.add("armv5t");
+                                    case "armv5":  cpuNames.add("armv5");
+                                    case "armv4t": cpuNames.add("armv4t");
+                                    case "armv4":  cpuNames.add("armv4");
+                                        break;
+                                    case "armv5t-iwmmx2": cpuNames.add("armv5t-iwmmx2");
+                                    case "armv5t-iwmmx":  cpuNames.add("armv5t-iwmmx");
+                                                          cpuNames.add("armv5t");
+                                                          cpuNames.add("armv5");
+                                                          cpuNames.add("armv4t");
+                                                          cpuNames.add("armv4");
+                                        break;
+                                    default: cpuNames.add(cpuName);
+                                        break;
+                                }
+                                if (hf || be) for (int i = 0; i < cpuNames.size(); i++) {
+                                    String name = cpuNames.get(i);
+                                    if (be) name += "-be";
+                                    if (hf) name += "-hf";
+                                    cpuNames.set(i, name);
+                                }
+                                cpuName = cpuNames.get(0);
+                            }
+                        }
+                    }
+
+                    // Finally, search paths.
+                    final int cpuCount = cpuNames.size();
+                    String[] searchPaths = new String[cpuCount];
+                    if (knownOs && knownCpu) {
+                        for (int i = 0; i < cpuCount; i++) {
+                            final String name = cpuNames.get(i);
+                            searchPaths[i] = osName + "-" + name;
+                        }
+                    } else {
+                        searchPaths = new String[0];
+                    }
+
+                    return new Object[] {
+                        osName,
+                        cpuName,
+                        osName + "-" + cpuName,
+                        searchPaths
+                    };
+                }
+            });
+            OS_ID = strings[0].toString();
+            CPU_ID = strings[1].toString();
+            ARCH_NAME = strings[2].toString();
+            NATIVE_SEARCH_PATHS = (String[]) strings[3];
+        }
+    }
+
+    /**
+     * The filesystem root of the resource loader.
+     */
+    private final File root;
+
+    /**
+     * Construct a new instance.
+     *
+     * @param root the filesystem root of the resource loader
+     */
+    public NativeLibraryResourceLoader(final File root) {
+        this.root = root;
+    }
+
+    /** {@inheritDoc} */
+    public String getLibrary(final String name) {
+        final String mappedName = System.mapLibraryName(name);
+        final File root = this.root;
+        File testFile;
+        for (String path : Identification.NATIVE_SEARCH_PATHS) {
+            testFile = new File(new File(root, path), mappedName);
+            if (testFile.exists()) {
+                return testFile.getAbsolutePath();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Get the filesystem root of the resource loader.
+     *
+     * @return the filesystem root of the resource loader
+     */
+    public File getRoot() {
+        return root;
+    }
+
+    /**
+     * Get the detected architecture name for this platform.
+     *
+     * @return the architecture name
+     */
+    public static String getArchName() {
+        return Identification.ARCH_NAME;
+    }
+}
diff --git a/src/main/java/org/jboss/modules/PackageSpec.java b/src/main/java/org/jboss/modules/PackageSpec.java
new file mode 100644
index 0000000..3593cea
--- /dev/null
+++ b/src/main/java/org/jboss/modules/PackageSpec.java
@@ -0,0 +1,200 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.net.URL;
+
+/**
+ * A specification for a package to define.
+ *
+ * @apiviz.exclude
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public final class PackageSpec {
+
+    private String specTitle;
+    private String specVersion;
+    private String specVendor;
+    private String implTitle;
+    private String implVersion;
+    private String implVendor;
+    private URL sealBase;
+    private AssertionSetting assertionSetting = AssertionSetting.INHERIT;
+
+    /**
+     * Get the package specification title.
+     *
+     * @return the specification title
+     * @see java.util.jar.Attributes.Name#SPECIFICATION_TITLE
+     */
+    public String getSpecTitle() {
+        return specTitle;
+    }
+
+    /**
+     * Set the package specification title.
+     *
+     * @param specTitle the specification title
+     * @see java.util.jar.Attributes.Name#SPECIFICATION_TITLE
+     */
+    public void setSpecTitle(final String specTitle) {
+        this.specTitle = specTitle;
+    }
+
+    /**
+     * Get the package specification version.
+     *
+     * @return the specification version
+     * @see java.util.jar.Attributes.Name#SPECIFICATION_VERSION
+     */
+    public String getSpecVersion() {
+        return specVersion;
+    }
+
+    /**
+     * Set the package specification version.
+     *
+     * @param specVersion the specification version
+     * @see java.util.jar.Attributes.Name#SPECIFICATION_VERSION
+     */
+    public void setSpecVersion(final String specVersion) {
+        this.specVersion = specVersion;
+    }
+
+    /**
+     * Set the package specification vendor.
+     *
+     * @return the specification vendor
+     * @see java.util.jar.Attributes.Name#SPECIFICATION_VENDOR
+     */
+    public String getSpecVendor() {
+        return specVendor;
+    }
+
+    /**
+     * Set the package specification vendor.
+     *
+     * @param specVendor the specification vendor
+     * @see java.util.jar.Attributes.Name#SPECIFICATION_VENDOR
+     */
+    public void setSpecVendor(final String specVendor) {
+        this.specVendor = specVendor;
+    }
+
+    /**
+     * Get the implementation title.
+     *
+     * @return the implementation title
+     * @see java.util.jar.Attributes.Name#IMPLEMENTATION_TITLE
+     */
+    public String getImplTitle() {
+        return implTitle;
+    }
+
+    /**
+     * Set the implementation title.
+     *
+     * @param implTitle the implementation title
+     * @see java.util.jar.Attributes.Name#IMPLEMENTATION_TITLE
+     */
+    public void setImplTitle(final String implTitle) {
+        this.implTitle = implTitle;
+    }
+
+    /**
+     * Get the implementation version.
+     *
+     * @return the implementation version
+     * @see java.util.jar.Attributes.Name#IMPLEMENTATION_VERSION
+     */
+    public String getImplVersion() {
+        return implVersion;
+    }
+
+    /**
+     * Set the implementation version.
+     *
+     * @param implVersion the implementation version
+     * @see java.util.jar.Attributes.Name#IMPLEMENTATION_VENDOR
+     */
+    public void setImplVersion(final String implVersion) {
+        this.implVersion = implVersion;
+    }
+
+    /**
+     * Get the implementation vendor.
+     *
+     * @return the implementation vendor
+     * @see java.util.jar.Attributes.Name#IMPLEMENTATION_VENDOR
+     */
+    public String getImplVendor() {
+        return implVendor;
+    }
+
+    /**
+     * Set the implementation vendor.
+     *
+     * @param implVendor the implementation vendor
+     * @see java.util.jar.Attributes.Name#IMPLEMENTATION_VENDOR
+     */
+    public void setImplVendor(final String implVendor) {
+        this.implVendor = implVendor;
+    }
+
+    /**
+     * Get the URL against which this package is sealed.
+     *
+     * @return the seal base URL
+     * @see java.util.jar.Attributes.Name#SEALED
+     */
+    public URL getSealBase() {
+        return sealBase;
+    }
+
+    /**
+     * Set the URL against which this package is sealed.
+     *
+     * @param sealBase the seal base URL
+     * @see java.util.jar.Attributes.Name#SEALED
+     */
+    public void setSealBase(URL sealBase) {
+        this.sealBase = sealBase;
+    }
+
+    /**
+     * Get the package assertion setting.
+     *
+     * @return the package assertion setting
+     */
+    public AssertionSetting getAssertionSetting() {
+        return assertionSetting;
+    }
+
+    /**
+     * Set the package assertion setting.
+     *
+     * @param assertionSetting the package assertion setting
+     */
+    public void setAssertionSetting(final AssertionSetting assertionSetting) {
+        if (assertionSetting == null) {
+            throw new IllegalArgumentException("assertionSetting is null");
+        }
+        this.assertionSetting = assertionSetting;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/jboss/modules/PathUtils.java b/src/main/java/org/jboss/modules/PathUtils.java
new file mode 100644
index 0000000..5d4b2aa
--- /dev/null
+++ b/src/main/java/org/jboss/modules/PathUtils.java
@@ -0,0 +1,269 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import org.jboss.modules.filter.PathFilter;
+
+/**
+ * General helpful path utility methods.
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public final class PathUtils {
+
+    private PathUtils() {
+    }
+
+    /**
+     * Filter the paths from {@code source} into {@code target} using {@code filter}.
+     *
+     * @param source the source paths
+     * @param filter the filter to apply
+     * @param target the destination for filtered paths
+     * @param <T> the collection type
+     * @return the {@code target} set
+     */
+    public static <T extends Collection<? super String>> T filterPaths(Iterable<String> source, PathFilter filter, T target) {
+        for (String path : source) {
+            if (filter.accept(path)) {
+                target.add(path);
+            }
+        }
+        return target;
+    }
+
+    /**
+     * Attempt to get a set of all paths defined directly by the given class loader.  If the path set cannot be
+     * ascertained, {@code null} is returned.
+     *
+     * @param classLoader the class loader to inspect
+     * @return the set, or {@code null} if the paths could not be determined
+     */
+    public static Set<String> getPathSet(ClassLoader classLoader) {
+        if (classLoader == null) {
+            return JDKPaths.JDK;
+        } else if (classLoader instanceof ModuleClassLoader) {
+            final ModuleClassLoader moduleClassLoader = (ModuleClassLoader) classLoader;
+            return Collections.unmodifiableSet(moduleClassLoader.getPaths());
+        } else if (classLoader instanceof URLClassLoader) {
+            // here's where it starts to get ugly...
+            final URLClassLoader urlClassLoader = (URLClassLoader) classLoader;
+            final URL[] urls = urlClassLoader.getURLs();
+            final Set<String> paths = new HashSet<String>();
+            for (URL url : urls) {
+                final URI uri;
+                try {
+                    uri = url.toURI();
+                } catch (URISyntaxException e) {
+                    return null;
+                }
+                final String scheme = uri.getScheme();
+                if ("file".equals(scheme)) {
+                    final File file;
+                    try {
+                        file = new File(uri);
+                    } catch (Exception e) {
+                        return null;
+                    }
+                    if (file.exists()) {
+                        if (file.isDirectory()) {
+                            JDKPaths.processDirectory0(paths, file);
+                        } else {
+                            try {
+                                JDKPaths.processJar(paths, file);
+                            } catch (IOException e) {
+                                return null;
+                            }
+                        }
+                    }
+                }
+            }
+            return Collections.unmodifiableSet(paths);
+        } else {
+            // ???
+            return null;
+        }
+    }
+
+    /**
+     * Relativize the given path.  Removes any leading {@code /} segments from the path.
+     *
+     * @param path the path to relativize
+     * @return the relative path
+     */
+    public static String relativize(String path) {
+        for (int i = 0; i < path.length(); i ++) {
+            if (path.charAt(i) != '/' && path.charAt(i) != File.separatorChar) {
+                return i == 0 ? path : path.substring(i);
+            }
+        }
+        return "";
+    }
+
+    /**
+     * Canonicalize the given path.  Removes all {@code .} and {@code ..} segments from the path.
+     *
+     * @param path the relative or absolute possibly non-canonical path
+     * @return the canonical path
+     */
+    public static String canonicalize(String path) {
+        final int length = path.length();
+        // 0 - start
+        // 1 - got one .
+        // 2 - got two .
+        // 3 - got /
+        int state = 0;
+        if (length == 0) {
+            return path;
+        }
+        final char[] targetBuf = new char[length];
+        // string segment end exclusive
+        int e = length;
+        // string cursor position
+        int i = length;
+        // buffer cursor position
+        int a = length - 1;
+        // number of segments to skip
+        int skip = 0;
+        loop: while (--i >= 0) {
+            char c = path.charAt(i);
+            outer: switch (c) {
+                case '/': {
+                    inner: switch (state) {
+                        case 0: state = 3; e = i; break outer;
+                        case 1: state = 3; e = i; break outer;
+                        case 2: state = 3; e = i; skip ++; break outer;
+                        case 3: e = i; break outer;
+                        default: throw new IllegalStateException();
+                    }
+                    // not reached!
+                }
+                case '.': {
+                    inner: switch (state) {
+                        case 0: state = 1; break outer;
+                        case 1: state = 2; break outer;
+                        case 2: break inner; // emit!
+                        case 3: state = 1; break outer;
+                        default: throw new IllegalStateException();
+                    }
+                    // fall thru
+                }
+                default: {
+                    if (File.separatorChar != '/' && c == File.separatorChar) {
+                        switch (state) {
+                            case 0: state = 3; e = i; break outer;
+                            case 1: state = 3; e = i; break outer;
+                            case 2: state = 3; e = i; skip ++; break outer;
+                            case 3: e = i; break outer;
+                            default: throw new IllegalStateException();
+                        }
+                        // not reached!
+                    }
+                    final int newE = e > 0 ? path.lastIndexOf('/', e - 1) : -1;
+                    final int segmentLength = e - newE - 1;
+                    if (skip > 0) {
+                        skip--;
+                    } else {
+                        if (state == 3) {
+                            targetBuf[a--] = '/';
+                        }
+                        path.getChars(newE + 1, e, targetBuf, (a -= segmentLength) + 1);
+                    }
+                    state = 0;
+                    i = newE + 1;
+                    e = newE;
+                    break;
+                }
+            }
+        }
+        if (state == 3) {
+            targetBuf[a--] = '/';
+        }
+        return new String(targetBuf, a + 1, length - a - 1);
+    }
+
+    /**
+     * Determine whether one path is a child of another.
+     *
+     * @param parent the parent path
+     * @param child the child path
+     * @return {@code true} if the child is truly a child of parent
+     */
+    public static boolean isChild(final String parent, final String child) {
+        String cp = canonicalize(parent);
+        String cc = canonicalize(child);
+        if (isRelative(cp) != isRelative(cc)) {
+            throw new IllegalArgumentException("Cannot compare relative and absolute paths");
+        }
+        final int cpl = cp.length();
+        return cpl == 0 || cc.length() > cpl + 1 && cc.startsWith(cp) && cc.charAt(cpl) == '/';
+    }
+
+    /**
+     * Determine whether one path is a direct (or immediate) child of another.
+     *
+     * @param parent the parent path
+     * @param child the child path
+     * @return {@code true} if the child is truly a direct child of parent
+     */
+    public static boolean isDirectChild(final String parent, final String child) {
+        String cp = canonicalize(parent);
+        String cc = canonicalize(child);
+        if (isRelative(cp) != isRelative(cc)) {
+            throw new IllegalArgumentException("Cannot compare relative and absolute paths");
+        }
+        final int cpl = cp.length();
+        if (cpl == 0) {
+            return cc.indexOf('/') < 0;
+        } else {
+            return cc.length() > cpl + 1 && cc.startsWith(cp) && cc.charAt(cpl) == '/' && cc.indexOf('/', cpl + 1) == -1;
+        }
+    }
+
+    /**
+     * Determine whether a path name is relative.
+     *
+     * @param path the path name
+     * @return {@code true} if it is relative
+     */
+    public static boolean isRelative(final String path) {
+        return path.isEmpty() || !isSeparator(path.charAt(0));
+    }
+
+    /**
+     * Determine whether the given character is a {@code /} or a platform-specific separator.
+     *
+     * @param ch the character to test
+     * @return {@code true} if it is a separator
+     */
+    public static boolean isSeparator(final char ch) {
+        // the second half of this compare will optimize away on / OSes
+        return ch == '/' || File.separatorChar != '/' && ch == File.separatorChar;
+    }
+}
diff --git a/src/main/java/org/jboss/modules/Paths.java b/src/main/java/org/jboss/modules/Paths.java
new file mode 100644
index 0000000..187b772
--- /dev/null
+++ b/src/main/java/org/jboss/modules/Paths.java
@@ -0,0 +1,56 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A pair of path maps.
+ *
+ * @param <T> the type of object that each path refers to
+ * @param <A> the type of the source object used to calculate the path maps
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+final class Paths<T, A> {
+    private final A[] sourceList;
+    private final Map<String, List<T>> allPaths;
+
+    Paths(final A[] sourceList, final Map<String, List<T>> allPaths) {
+        this.sourceList = sourceList;
+        this.allPaths = allPaths;
+    }
+
+    Map<String, List<T>> getAllPaths() {
+        return allPaths;
+    }
+
+    A[] getSourceList(A[] defVal) {
+        final A[] sourceList = this.sourceList;
+        return sourceList == null ? defVal : sourceList;
+    }
+
+    static final Paths<?, ?> NONE = new Paths<Object, Object>(null, null);
+
+    @SuppressWarnings({ "unchecked" })
+    static <T, A> Paths<T, A> none() {
+        return (Paths<T, A>) NONE;
+    }
+}
diff --git a/src/main/java/org/jboss/modules/PropertyReadAction.java b/src/main/java/org/jboss/modules/PropertyReadAction.java
new file mode 100644
index 0000000..1229600
--- /dev/null
+++ b/src/main/java/org/jboss/modules/PropertyReadAction.java
@@ -0,0 +1,43 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.security.PrivilegedAction;
+
+/**
+* @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+*/
+class PropertyReadAction implements PrivilegedAction<String> {
+
+    private final String key;
+    private final String defVal;
+
+    PropertyReadAction(final String key) {
+        this(key, null);
+    }
+
+    PropertyReadAction(final String key, final String defVal) {
+        this.key = key;
+        this.defVal = defVal;
+    }
+
+    public String run() {
+        return System.getProperty(key, defVal);
+    }
+}
diff --git a/src/main/java/org/jboss/modules/PropertyWriteAction.java b/src/main/java/org/jboss/modules/PropertyWriteAction.java
new file mode 100644
index 0000000..f502754
--- /dev/null
+++ b/src/main/java/org/jboss/modules/PropertyWriteAction.java
@@ -0,0 +1,42 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.security.PrivilegedAction;
+
+/**
+ * Date: 11.05.2011
+ *
+ * @author <a href="mailto:jperkins at redhat.com">James R. Perkins</a>
+ */
+class PropertyWriteAction implements PrivilegedAction<String> {
+
+    private final String key;
+    private final String value;
+
+    PropertyWriteAction(final String key, final String value) {
+        this.key = key;
+        this.value = value;
+    }
+
+    @Override
+    public String run() {
+        return System.setProperty(key, value);
+    }
+}
diff --git a/src/main/java/org/jboss/modules/Resource.java b/src/main/java/org/jboss/modules/Resource.java
new file mode 100644
index 0000000..e8de9ae
--- /dev/null
+++ b/src/main/java/org/jboss/modules/Resource.java
@@ -0,0 +1,59 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+/**
+ * A single resource from a {@link ResourceLoader}.
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public interface Resource {
+    /**
+     * Get the relative resource name.
+     *
+     * @return the name
+     */
+    String getName();
+
+    /**
+     * Get the complete URL of this resource.
+     *
+     * @return the URL
+     */
+    URL getURL();
+
+    /**
+     * Open an input stream to this resource.
+     *
+     * @return the stream
+     * @throws java.io.IOException if an I/O error occurs
+     */
+    InputStream openStream() throws IOException;
+
+    /**
+     * Get the size of the resource, if known.
+     *
+     * @return the size, or 0L if unknown
+     */
+    long getSize();
+}
diff --git a/src/main/java/org/jboss/modules/ResourceLoader.java b/src/main/java/org/jboss/modules/ResourceLoader.java
new file mode 100644
index 0000000..dc3e8ab
--- /dev/null
+++ b/src/main/java/org/jboss/modules/ResourceLoader.java
@@ -0,0 +1,85 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.io.IOException;
+import java.util.Collection;
+
+/**
+ * A loader for resources from a specific resource root within a module.
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public interface ResourceLoader {
+
+    /**
+     * Get the name of the root represented by this resource loader.
+     *
+     * @return the name of the root
+     */
+    String getRootName();
+
+    /**
+     * Get the class specification for the given class name.  If no matching class is found, {@code null} is returned.
+     *
+     * @param fileName the fileName of the class, e.g. for the class <code>org.jboss.modules.ResourceLoader</code>
+     * the fileName will be <code>org/jboss/modules/ResourceLoader.class</code>
+     * @return the class specification, or {@code null} if the named class is not found
+     * @throws IOException if an I/O error occurs
+     */
+    ClassSpec getClassSpec(String fileName) throws IOException;
+
+    /**
+     * Get the package specification for the given directory name.  Always returns a package specification; this
+     * method cannot be used to test for the existence of a package.  A package spec should always be acquired from
+     * the same resource loader which provided the class specification.  The directory name will always be specified
+     * using "{@code /}" separators.
+     *
+     * @param name the directory name
+     * @return the package specification
+     * @throws IOException if an I/O error occurs
+     */
+    PackageSpec getPackageSpec(String name) throws IOException;
+
+    /**
+     * Get a resource with the given name.  If no such resource is available, {@code null} is returned.
+     * The resource name will always be specified using "{@code /}" separators for the directory segments.
+     *
+     * @param name the resource name
+     * @return the resource, or {@code null} if it is not available
+     */
+    Resource getResource(String name);
+
+    /**
+     * Get the absolute physical filesystem path for a library with the given name.  The resultant path should be
+     * path-separated using "{@code /}" characters.
+     *
+     * @param name the name
+     * @return the path or {@code null} if the library is not present
+     */
+    String getLibrary(String name);
+
+    /**
+     * Get the collection of resource paths.  Called one time only when the resource loader is initialized.  The
+     * paths should use "{@code /}" characters to separate the path segments.
+     *
+     * @return the resource paths
+     */
+    Collection<String> getPaths();
+}
\ No newline at end of file
diff --git a/src/main/java/org/jboss/modules/ResourceLoaderSpec.java b/src/main/java/org/jboss/modules/ResourceLoaderSpec.java
new file mode 100644
index 0000000..58b84fc
--- /dev/null
+++ b/src/main/java/org/jboss/modules/ResourceLoaderSpec.java
@@ -0,0 +1,69 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import org.jboss.modules.filter.PathFilter;
+import org.jboss.modules.filter.PathFilters;
+
+/**
+ * A specification of a resource loader within a module.  A resource loader may optionally be associated with a
+ * path filter which can be used to decide which paths of a resource loader to include.
+ *
+ * @apiviz.exclude
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public final class ResourceLoaderSpec {
+    private final ResourceLoader resourceLoader;
+    private final PathFilter pathFilter;
+
+    ResourceLoaderSpec(final ResourceLoader resourceLoader, final PathFilter pathFilter) {
+        this.resourceLoader = resourceLoader;
+        this.pathFilter = pathFilter;
+    }
+
+    /**
+     * Construct a new instance.
+     *
+     * @param resourceLoader the resource loader to include
+     * @param pathFilter the path filter to apply to the resource loader's paths
+     * @return the specification
+     */
+    public static ResourceLoaderSpec createResourceLoaderSpec(final ResourceLoader resourceLoader, final PathFilter pathFilter) {
+        return new ResourceLoaderSpec(resourceLoader, pathFilter);
+    }
+
+    /**
+     * Construct a new instance which accepts all paths in the resource loader.
+     *
+     * @param resourceLoader the resource loader to include
+     * @return the specification
+     */
+    public static ResourceLoaderSpec createResourceLoaderSpec(final ResourceLoader resourceLoader) {
+        return new ResourceLoaderSpec(resourceLoader, PathFilters.acceptAll());
+    }
+
+    ResourceLoader getResourceLoader() {
+        return resourceLoader;
+    }
+
+    PathFilter getPathFilter() {
+        return pathFilter;
+    }
+}
diff --git a/src/main/java/org/jboss/modules/ResourceLoaders.java b/src/main/java/org/jboss/modules/ResourceLoaders.java
new file mode 100644
index 0000000..ddc4428
--- /dev/null
+++ b/src/main/java/org/jboss/modules/ResourceLoaders.java
@@ -0,0 +1,116 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.io.File;
+import java.security.AccessController;
+import java.util.jar.JarFile;
+import org.jboss.modules.filter.PathFilter;
+
+/**
+ * Static factory methods for various types of resource loaders.
+ *
+ * @apiviz.exclude
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public final class ResourceLoaders {
+    static final boolean USE_INDEXES;
+    static final boolean WRITE_INDEXES;
+
+    static {
+        USE_INDEXES = Boolean.parseBoolean(AccessController.doPrivileged(new PropertyReadAction("jboss.modules.use-indexes", "false")));
+        WRITE_INDEXES = USE_INDEXES && Boolean.parseBoolean(AccessController.doPrivileged(new PropertyReadAction("jboss.modules.write-indexes", "false")));
+    }
+
+    private ResourceLoaders() {
+    }
+
+    /**
+     * Create a filesystem-backed resource loader with support for native libraries.  Created classes
+     * have a code source with a {@code file:} URL.
+     *
+     * @param name the name of the resource root
+     * @param root the root file of the resource loader
+     * @return the resource loader
+     */
+    public static ResourceLoader createFileResourceLoader(final String name, final File root) {
+        return new FileResourceLoader(name, root, AccessController.getContext());
+    }
+
+    /**
+     * Create a filesystem-backed iterable resource loader with support for native libraries.  Created classes
+     * have a code source with a {@code file:} URL.
+     *
+     * @param name the name of the resource root
+     * @param root the root file of the resource loader
+     * @return the resource loader
+     */
+    public static IterableResourceLoader createIterableFileResourceLoader(final String name, final File root) {
+        return new FileResourceLoader(name, root, AccessController.getContext());
+    }
+
+    /**
+     * Create a JAR-backed resource loader.  JAR resource loaders do not have native library support.
+     * Created classes have a code source with a {@code jar:} URL; nested JARs are not supported.
+     *
+     * @param name the name of the resource root
+     * @param jarFile the backing JAR file
+     * @return the resource loader
+     */
+    public static ResourceLoader createJarResourceLoader(final String name, final JarFile jarFile) {
+        return new JarFileResourceLoader(name, jarFile);
+    }
+
+    /**
+     * Create a JAR-backed iterable resource loader.  JAR resource loaders do not have native library support.
+     * Created classes have a code source with a {@code jar:} URL; nested JARs are not supported.
+     *
+     * @param name the name of the resource root
+     * @param jarFile the backing JAR file
+     * @return the resource loader
+     */
+    public static IterableResourceLoader createIterableJarResourceLoader(final String name, final JarFile jarFile) {
+        return new JarFileResourceLoader(name, jarFile);
+    }
+
+    /**
+     * Create a filtered view of a resource loader, which allows classes to be included or excluded on a name basis.
+     * The given filter is matched against the actual class or resource name, not the directory name.
+     *
+     * @param pathFilter the path filter to apply
+     * @param originalLoader the original loader to apply to
+     * @return the filtered resource loader
+     */
+    public static ResourceLoader createFilteredResourceLoader(final PathFilter pathFilter, final ResourceLoader originalLoader) {
+        return new FilteredResourceLoader(pathFilter, originalLoader);
+    }
+
+    /**
+     * Create a filtered view of an iterable resource loader, which allows classes to be included or excluded on a name basis.
+     * The given filter is matched against the actual class or resource name, not the directory name.
+     *
+     * @param pathFilter the path filter to apply
+     * @param originalLoader the original loader to apply to
+     * @return the filtered resource loader
+     */
+    public static IterableResourceLoader createIterableFilteredResourceLoader(final PathFilter pathFilter, final IterableResourceLoader originalLoader) {
+        return new FilteredIterableResourceLoader(pathFilter, originalLoader);
+    }
+}
diff --git a/src/main/java/org/jboss/modules/SecurityActions.java b/src/main/java/org/jboss/modules/SecurityActions.java
new file mode 100644
index 0000000..1b82c97
--- /dev/null
+++ b/src/main/java/org/jboss/modules/SecurityActions.java
@@ -0,0 +1,69 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.security.PrivilegedAction;
+
+import static java.lang.System.getSecurityManager;
+import static java.lang.Thread.currentThread;
+import static java.security.AccessController.doPrivileged;
+
+/**
+ * This class must not be public.
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+final class SecurityActions {
+
+    private static final PrivilegedAction<ClassLoader> GET_LOADER_ACTION = new PrivilegedAction<ClassLoader>() {
+        public ClassLoader run() {
+            return currentThread().getContextClassLoader();
+        }
+    };
+
+    static ClassLoader setContextClassLoader(final ClassLoader classLoader) {
+        final SecurityManager sm = getSecurityManager();
+        if (sm != null) {
+            return doPrivileged(new PrivilegedAction<ClassLoader>() {
+                public ClassLoader run() {
+                    try {
+                        return currentThread().getContextClassLoader();
+                    } finally {
+                        currentThread().setContextClassLoader(classLoader);
+                    }
+                }
+            });
+        } else {
+            try {
+                return currentThread().getContextClassLoader();
+            } finally {
+                currentThread().setContextClassLoader(classLoader);
+            }
+        }
+    }
+
+    static ClassLoader getContextClassLoader() {
+        final SecurityManager sm = getSecurityManager();
+        if (sm != null) {
+            return doPrivileged(GET_LOADER_ACTION);
+        } else {
+            return currentThread().getContextClassLoader();
+        }
+    }
+}
diff --git a/src/main/java/org/jboss/modules/StartTimeHolder.java b/src/main/java/org/jboss/modules/StartTimeHolder.java
new file mode 100644
index 0000000..6109772
--- /dev/null
+++ b/src/main/java/org/jboss/modules/StartTimeHolder.java
@@ -0,0 +1,22 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jboss.modules;
+
+final class StartTimeHolder {
+   static final long START_TIME = System.currentTimeMillis();
+}
diff --git a/src/main/java/org/jboss/modules/StreamUtil.java b/src/main/java/org/jboss/modules/StreamUtil.java
new file mode 100644
index 0000000..402a394
--- /dev/null
+++ b/src/main/java/org/jboss/modules/StreamUtil.java
@@ -0,0 +1,92 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Enumeration;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+final class StreamUtil {
+
+    static void copy(InputStream in, OutputStream out) throws IOException {
+        byte[] buf = new byte[16384];
+        int len;
+        while ((len = in.read(buf)) > 0) {
+            out.write(buf, 0, len);
+        }
+        out.flush();
+    }
+
+    static void safeClose(Closeable closeable) {
+        if (closeable != null) try {
+            closeable.close();
+        } catch (Throwable ignored) {}
+    }
+
+    // ZipFile didn't implement Closeable in 1.6
+    static void safeClose(ZipFile closeable) {
+        if (closeable != null) try {
+            closeable.close();
+        } catch (Throwable ignored) {}
+    }
+
+    static final void unzip(File src, File destDir) throws IOException {
+        final String absolutePath = destDir.getAbsolutePath();
+        final ZipFile zip = new ZipFile(src);
+
+        try {
+            final Enumeration<? extends ZipEntry> entries = zip.entries();
+
+            while (entries.hasMoreElements()) {
+                final ZipEntry entry = entries.nextElement();
+                if (entry.isDirectory()) {
+                    continue;
+                }
+
+                final File fp = new File(absolutePath, PathUtils.canonicalize(PathUtils.relativize(entry.getName())));
+                final File parent = fp.getParentFile();
+                if (! parent.exists()) {
+                    parent.mkdirs();
+                }
+                final InputStream is = zip.getInputStream(entry);
+                try {
+                    final FileOutputStream os = new FileOutputStream(fp);
+                    try {
+                        copy(is, os);
+                    } finally {
+                        safeClose(os);
+                    }
+                } finally {
+                    safeClose(is);
+                }
+            }
+        } finally {
+            safeClose(zip);
+        }
+    }
+}
diff --git a/src/main/java/org/jboss/modules/URLResource.java b/src/main/java/org/jboss/modules/URLResource.java
new file mode 100644
index 0000000..7a4ca15
--- /dev/null
+++ b/src/main/java/org/jboss/modules/URLResource.java
@@ -0,0 +1,50 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+/**
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+final class URLResource implements Resource {
+    private final URL url;
+
+    public URLResource(final URL url) {
+        this.url = url;
+    }
+
+    public String getName() {
+        return url.getPath();
+    }
+
+    public URL getURL() {
+        return url;
+    }
+
+    public InputStream openStream() throws IOException {
+        return url.openStream();
+    }
+
+    public long getSize() {
+        return 0L;
+    }
+}
diff --git a/src/main/java/org/jboss/modules/UnlockedReadHashMap.java b/src/main/java/org/jboss/modules/UnlockedReadHashMap.java
new file mode 100644
index 0000000..c6889fb
--- /dev/null
+++ b/src/main/java/org/jboss/modules/UnlockedReadHashMap.java
@@ -0,0 +1,413 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.util.AbstractMap;
+import java.util.AbstractSet;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicReferenceArray;
+
+/**
+ * A hash map that supports non-blocking, lockless read access.
+ *
+ * @param <K> the key type
+ * @param <V> the value type
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+final class UnlockedReadHashMap<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V> {
+
+    private static final int DEFAULT_INITIAL_CAPACITY = 128;
+    private static final int MAXIMUM_CAPACITY = 1 << 30;
+    private static final float DEFAULT_LOAD_FACTOR = 0.60f;
+
+    // Final fields (thread-safe)
+    private final Object writeLock = new Object();
+    private final Set<Entry<K, V>> entrySet = new EntrySet();
+    private final float loadFactor;
+
+    // Volatile fields (writes protected by {@link #writeLock})
+    private volatile int size;
+    private volatile AtomicReferenceArray<Item<K,V>[]> table;
+
+    // Raw fields (reads and writes protected by {@link #writeLock}
+    private int threshold;
+
+    public UnlockedReadHashMap(int initialCapacity, final float loadFactor) {
+        if (initialCapacity < 0) {
+            throw new IllegalArgumentException("Initial capacity must be > 0");
+        }
+        if (initialCapacity > MAXIMUM_CAPACITY) {
+            initialCapacity = MAXIMUM_CAPACITY;
+        }
+        if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
+            throw new IllegalArgumentException("Load factor must be > 0.0f");
+        }
+
+        int capacity = 1;
+
+        while (capacity < initialCapacity) {
+            capacity <<= 1;
+        }
+
+        this.loadFactor = loadFactor;
+        synchronized (writeLock) {
+            threshold = (int)(capacity * loadFactor);
+            table = new AtomicReferenceArray<Item<K, V>[]>(capacity);
+        }
+    }
+
+    public UnlockedReadHashMap(final float loadFactor) {
+        this(DEFAULT_INITIAL_CAPACITY, loadFactor);
+    }
+
+    public UnlockedReadHashMap(final int initialCapacity) {
+        this(initialCapacity, DEFAULT_LOAD_FACTOR);
+    }
+
+    public UnlockedReadHashMap() {
+        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
+    }
+
+    @SuppressWarnings( { "unchecked" })
+    private void resize() {
+        assert Thread.holdsLock(writeLock);
+        final AtomicReferenceArray<Item<K, V>[]> oldTable = table;
+        final int oldCapacity = oldTable.length();
+        if (oldCapacity == MAXIMUM_CAPACITY) {
+            return;
+        }
+        final int newCapacity = oldCapacity << 1;
+        final AtomicReferenceArray<Item<K, V>[]> newTable = new AtomicReferenceArray<Item<K, V>[]>(newCapacity);
+        final int newThreshold = (int)(newCapacity * loadFactor);
+        for (int i = 0; i < oldCapacity; i ++) {
+            final Item<K, V>[] items = oldTable.get(i);
+            if (items != null) {
+                final int length = items.length;
+                for (int j = 0; j < length; j++) {
+                    Item<K, V> item = items[j];
+                    final int hc = item.hashCode() & (newCapacity - 1);
+                    final Item<K, V>[] old = newTable.get(hc);
+                    if (old == null) {
+                        newTable.lazySet(hc, new Item[] { item });
+                    } else {
+                        final int oldLen = old.length;
+                        final Item<K, V>[] copy = Arrays.copyOf(old, oldLen + 1);
+                        copy[oldLen] = item;
+                        newTable.lazySet(hc, copy);
+                    }
+                }
+            }
+        }
+        table = newTable;
+        threshold = newThreshold;
+    }
+
+    private static <K, V> Item<K, V> doGet(final AtomicReferenceArray<Item<K, V>[]> table, final Object key) {
+        Item<K, V>[] row = doGetRow(table, key);
+        return row == null ? null : doGet(row, key);
+    }
+
+    private static <K, V> Item<K, V>[] doGetRow(final AtomicReferenceArray<Item<K, V>[]> table, final Object key) {
+        final int hc = getIndex(table, key);
+        return doGetRow(table, hc);
+    }
+
+    private static <K, V> int getIndex(final AtomicReferenceArray<Item<K, V>[]> table, final Object key) {
+        return key.hashCode() & (table.length() - 1);
+    }
+
+    private static <K, V> Item<K, V>[] doGetRow(final AtomicReferenceArray<Item<K, V>[]> table, final int hc) {
+        return table.get(hc);
+    }
+
+    private static <K, V> Item<K, V> doGet(Item<K, V>[] row, Object key) {
+        for (Item<K, V> item : row) {
+            if (item.key.equals(key)) {
+                return item;
+            }
+        }
+        return null;
+    }
+
+    private V doPut(AtomicReferenceArray<Item<K, V>[]> table, K key, V value, boolean ifAbsent) {
+        final int hc = getIndex(table, key);
+        final Item<K, V>[] old = doGetRow(table, hc);
+        if (old == null) {
+            @SuppressWarnings( { "unchecked" })
+            final Item<K, V>[] newRow = new Item[] { new Item<K, V>(key, value) };
+            table.set(hc, newRow);
+            if (size++ == threshold) {
+                resize();
+            }
+            return null;
+        } else {
+            final Item<K, V> item = doGet(old, key);
+            if (item != null) {
+                try {
+                    return item.value;
+                } finally {
+                    if (! ifAbsent) item.value = value;
+                }
+            }
+            final int oldLen = old.length;
+            final Item<K, V>[] newRow = Arrays.copyOf(old, oldLen + 1);
+            newRow[oldLen] = new Item<K, V>(key, value);
+            table.set(hc, newRow);
+            if (size++ == threshold) {
+                resize();
+            }
+            return null;
+        }
+    }
+
+    private static <K, V> Item<K, V>[] remove(Item<K, V>[] row, int idx) {
+        final int len = row.length;
+        assert idx < len;
+        if (len == 1) {
+            return null;
+        }
+        @SuppressWarnings("unchecked")
+        Item<K, V>[] newRow = new Item[len - 1];
+        if (idx > 0) {
+            System.arraycopy(row, 0, newRow, 0, idx);
+        }
+        if (idx < len - 1) {
+            System.arraycopy(row, idx + 1, newRow, idx, len - 1 - idx);
+        }
+        return newRow;
+    }
+
+    public Set<Entry<K, V>> entrySet() {
+        return entrySet;
+    }
+
+    public int size() {
+        return size;
+    }
+
+    public boolean containsKey(final Object key) {
+        if (key == null) {
+            return false;
+        }
+        final Item<K, V> item = doGet(table, key);
+        return item != null;
+    }
+
+    public V get(final Object key) {
+        if (key == null) {
+            return null;
+        }
+        final Item<K, V> item = doGet(table, key);
+        return item == null ? null : item.value;
+    }
+
+    public V put(final K key, final V value) {
+        if (key == null) {
+            throw new IllegalArgumentException("key is null");
+        }
+        synchronized (writeLock) {
+            return doPut(table, key, value, false);
+        }
+    }
+
+    public V remove(final Object key) {
+        if (key == null) {
+            return null;
+        }
+        synchronized (writeLock) {
+            final int hc = getIndex(table, key);
+            final Item<K, V>[] row = doGetRow(table, hc);
+            if (row == null) {
+                return null;
+            }
+            final int rowLen = row.length;
+            for (int i = 0; i < rowLen; i++) {
+                final Item<K, V> item = row[i];
+                if (item.key.equals(key)) {
+                    table.set(hc, remove(row, i));
+                    size --;
+                    return item.value;
+                }
+            }
+            return null;
+        }
+    }
+
+    public void clear() {
+        synchronized (writeLock) {
+            table = new AtomicReferenceArray<Item<K, V>[]>(table.length());
+            size = 0;
+        }
+    }
+
+    public V putIfAbsent(final K key, final V value) {
+        if (key == null) {
+            throw new IllegalArgumentException("key is null");
+        }
+        synchronized (writeLock) {
+            return doPut(table, key, value, true);
+        }
+    }
+
+    public boolean remove(final Object key, final Object value) {
+        if (key == null) {
+            return false;
+        }
+        synchronized (writeLock) {
+            final int hc = getIndex(table, key);
+            final Item<K, V>[] row = doGetRow(table, hc);
+            if (row == null) {
+                return false;
+            }
+            final int rowLen = row.length;
+            for (int i = 0; i < rowLen; i++) {
+                final Item<K, V> item = row[i];
+                if (item.key.equals(key) && (value == null ? item.value == null : value.equals(item.value))) {
+                    table.set(hc, remove(row, i));
+                    size --;
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    public boolean replace(final K key, final V oldValue, final V newValue) {
+        if (key == null) {
+            return false;
+        }
+        synchronized (writeLock) {
+            final Item<K, V> item = doGet(table, key);
+            if (item != null) {
+                if (oldValue == null ? item.value == null : oldValue.equals(item.value)) {
+                    item.value = newValue;
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    public V replace(final K key, final V value) {
+        if (key == null) {
+            return null;
+        }
+        synchronized (writeLock) {
+            final Item<K, V> item = doGet(table, key);
+            if (item != null) try {
+                return item.value;
+            } finally {
+                item.value = value;
+            }
+            return null;
+        }
+    }
+
+    private final class EntrySet extends AbstractSet<Entry<K, V>> implements Set<Entry<K, V>> {
+
+        public Iterator<Entry<K, V>> iterator() {
+            return new EntryIterator();
+        }
+
+        public int size() {
+            return UnlockedReadHashMap.this.size();
+        }
+    }
+
+    private final class EntryIterator implements Iterator<Entry<K, V>> {
+        private final AtomicReferenceArray<Item<K,V>[]> table = UnlockedReadHashMap.this.table;
+        private int tableIdx;
+        private int itemIdx;
+        private Item<K, V> next;
+
+        public boolean hasNext() {
+            while (next == null) {
+                if (table.length() == tableIdx) {
+                    return false;
+                }
+                final Item<K, V>[] items = table.get(tableIdx);
+                if (items != null) {
+                    final int len = items.length;
+                    if (itemIdx < len) {
+                        next = items[itemIdx++];
+                        return true;
+                    }
+                }
+                itemIdx = 0;
+                tableIdx++;
+            }
+            return true;
+        }
+
+        public Entry<K, V> next() {
+            if (hasNext()) try {
+                return next;
+            } finally {
+                next = null;
+            }
+            throw new NoSuchElementException();
+        }
+
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    private static final class Item<K, V> implements Entry<K, V> {
+        private final K key;
+        private volatile V value;
+
+        private Item(final K key, final V value) {
+            this.key = key;
+            this.value = value;
+        }
+
+        public K getKey() {
+            return key;
+        }
+
+        public V getValue() {
+            return value;
+        }
+
+        public V setValue(final V value) {
+            try {
+                return this.value;
+            } finally {
+                this.value = value;
+            }
+        }
+
+        public int hashCode() {
+            return key.hashCode();
+        }
+
+        public boolean equals(final Object obj) {
+            return obj instanceof Item && equals((Item<?,?>) obj);
+        }
+
+        public boolean equals(final Item<?, ?> obj) {
+            return obj != null && obj.key.equals(key);
+        }
+    }
+}
diff --git a/src/main/java/org/jboss/modules/_private/ModulesPrivateAccess.java b/src/main/java/org/jboss/modules/_private/ModulesPrivateAccess.java
new file mode 100644
index 0000000..c6f27f4
--- /dev/null
+++ b/src/main/java/org/jboss/modules/_private/ModulesPrivateAccess.java
@@ -0,0 +1,32 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules._private;
+
+import org.jboss.modules.Module;
+import org.jboss.modules.ModuleClassLoader;
+
+/**
+ * Private-access methods for modules.  User classes cannot acquire an instance
+ * of this class.
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public interface ModulesPrivateAccess {
+    ModuleClassLoader getClassLoaderOf(Module module);
+}
diff --git a/src/main/java/org/jboss/modules/filter/AggregatePathFilter.java b/src/main/java/org/jboss/modules/filter/AggregatePathFilter.java
new file mode 100644
index 0000000..e87d576
--- /dev/null
+++ b/src/main/java/org/jboss/modules/filter/AggregatePathFilter.java
@@ -0,0 +1,83 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.filter;
+
+import java.util.Arrays;
+
+/**
+ * PathFilter implementation that aggregates multiple other filters.
+ *
+ * @author John E. Bailey
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+final class AggregatePathFilter implements PathFilter {
+    private final PathFilter[] delegates;
+    private final boolean any;
+    private final int hashCode;
+
+    /**
+     * Construct a new instance.
+     *
+     * @param any {@code true} if this is an "any" filter, {@code false} if this an "all" filter
+     * @param delegates the delegate filter list
+     */
+    AggregatePathFilter(final boolean any, final PathFilter... delegates) {
+        this.any = any;
+        this.delegates = delegates;
+        hashCode = Boolean.valueOf(any).hashCode() ^ Arrays.hashCode(delegates);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean accept(String path) {
+        for (PathFilter filter : delegates) {
+            if (filter.accept(path) == any) {
+                return any;
+            }
+        }
+        return ! any;
+    }
+
+
+    public int hashCode() {
+        return hashCode;
+    }
+
+    public boolean equals(final Object obj) {
+        return obj instanceof AggregatePathFilter && equals((AggregatePathFilter) obj);
+    }
+
+    public boolean equals(final AggregatePathFilter obj) {
+        return obj != null && obj.any == any && Arrays.equals(obj.delegates, delegates);
+    }
+
+    public String toString() {
+        final StringBuilder b = new StringBuilder();
+        b.append(any ? "Any " : "All ").append("of (");
+        for (int idx = 0; idx < delegates.length; idx++) {
+            final PathFilter delegate = delegates[idx];
+            b.append(delegate);
+            if (idx < delegates.length - 1) {
+                b.append(',');
+            }
+        }
+        b.append(')');
+        return b.toString();
+    }
+}
diff --git a/src/main/java/org/jboss/modules/filter/BooleanClassFilter.java b/src/main/java/org/jboss/modules/filter/BooleanClassFilter.java
new file mode 100644
index 0000000..5d99fd2
--- /dev/null
+++ b/src/main/java/org/jboss/modules/filter/BooleanClassFilter.java
@@ -0,0 +1,50 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.filter;
+
+/**
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+final class BooleanClassFilter implements ClassFilter {
+
+    private final boolean result;
+
+    private BooleanClassFilter(final boolean result) {
+        this.result = result;
+    }
+
+    public boolean accept(final String path) {
+        return result;
+    }
+
+    static final BooleanClassFilter TRUE = new BooleanClassFilter(true);
+    static final BooleanClassFilter FALSE = new BooleanClassFilter(false);
+
+    public int hashCode() {
+        return Boolean.valueOf(result).hashCode();
+    }
+
+    public boolean equals(final Object obj) {
+        return obj == this;
+    }
+
+    public String toString() {
+        return result ? "Accept" : "Reject";
+    }
+}
diff --git a/src/main/java/org/jboss/modules/filter/BooleanPathFilter.java b/src/main/java/org/jboss/modules/filter/BooleanPathFilter.java
new file mode 100644
index 0000000..de04218
--- /dev/null
+++ b/src/main/java/org/jboss/modules/filter/BooleanPathFilter.java
@@ -0,0 +1,54 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.filter;
+
+/**
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+final class BooleanPathFilter implements PathFilter {
+
+    private final boolean result;
+
+    private BooleanPathFilter(final boolean result) {
+        this.result = result;
+    }
+
+    public boolean accept(final String path) {
+        return result;
+    }
+
+    static final BooleanPathFilter TRUE = new BooleanPathFilter(true);
+    static final BooleanPathFilter FALSE = new BooleanPathFilter(false);
+
+    public int hashCode() {
+        return Boolean.valueOf(result).hashCode();
+    }
+
+    public boolean equals(final Object obj) {
+        return obj == this;
+    }
+
+    public String toString() {
+        return result ? "Accept" : "Reject";
+    }
+
+    boolean getResult() {
+        return result;
+    }
+}
diff --git a/src/main/java/org/jboss/modules/filter/ChildPathFilter.java b/src/main/java/org/jboss/modules/filter/ChildPathFilter.java
new file mode 100644
index 0000000..be80e01
--- /dev/null
+++ b/src/main/java/org/jboss/modules/filter/ChildPathFilter.java
@@ -0,0 +1,51 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.filter;
+
+/**
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+final class ChildPathFilter implements PathFilter {
+
+    private final String prefix;
+
+    ChildPathFilter(final String path) {
+        prefix = path.charAt(path.length() - 1) == '/' ? path : path + "/";
+    }
+
+    public boolean accept(final String path) {
+        return path.startsWith(prefix);
+    }
+
+    public boolean equals(final Object obj) {
+        return obj instanceof ChildPathFilter && equals((ChildPathFilter) obj);
+    }
+
+    public boolean equals(final ChildPathFilter obj) {
+        return obj != null && obj.prefix.equals(prefix);
+    }
+
+    public String toString() {
+        return "children of \"" + prefix + "\"";
+    }
+
+    public int hashCode() {
+        return prefix.hashCode();
+    }
+}
diff --git a/src/main/java/org/jboss/modules/filter/ClassFilter.java b/src/main/java/org/jboss/modules/filter/ClassFilter.java
new file mode 100644
index 0000000..dc6e65a
--- /dev/null
+++ b/src/main/java/org/jboss/modules/filter/ClassFilter.java
@@ -0,0 +1,36 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.filter;
+
+/**
+ * Filter used to determine whether a class name should be included or excluded from imports and exports.
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public interface ClassFilter {
+
+    /**
+     * Determine whether a class name should be accepted by this filter.  The class name is qualified with a dot-separated
+     * package name.
+     *
+     * @param className the class name
+     * @return {@code true} to accept the class, {@code false} otherwise
+     */
+    boolean accept(String className);
+}
diff --git a/src/main/java/org/jboss/modules/filter/ClassFilters.java b/src/main/java/org/jboss/modules/filter/ClassFilters.java
new file mode 100644
index 0000000..ad34929
--- /dev/null
+++ b/src/main/java/org/jboss/modules/filter/ClassFilters.java
@@ -0,0 +1,58 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.filter;
+
+/**
+ * Static factory methods for class filter types.
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public final class ClassFilters {
+
+    private ClassFilters() {
+    }
+
+    /**
+     * Get a filter which always returns {@code true}.
+     *
+     * @return the accept-all filter
+     */
+    public static ClassFilter acceptAll() {
+        return BooleanClassFilter.TRUE;
+    }
+
+    /**
+     * Get a filter which always returns {@code false}.
+     *
+     * @return the reject-all filter
+     */
+    public static ClassFilter rejectAll() {
+        return BooleanClassFilter.FALSE;
+    }
+
+    /**
+     * Get a class filter which uses a resource path filter to filter classes.
+     *
+     * @param resourcePathFilter the resource path filter
+     * @return the class filter
+     */
+    public static ClassFilter fromResourcePathFilter(final PathFilter resourcePathFilter) {
+        return resourcePathFilter == PathFilters.acceptAll() ? acceptAll() : new PathClassFilter(resourcePathFilter);
+    }
+}
diff --git a/src/main/java/org/jboss/modules/filter/EqualsPathFilter.java b/src/main/java/org/jboss/modules/filter/EqualsPathFilter.java
new file mode 100644
index 0000000..7f33ee3
--- /dev/null
+++ b/src/main/java/org/jboss/modules/filter/EqualsPathFilter.java
@@ -0,0 +1,54 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.filter;
+
+/**
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+final class EqualsPathFilter implements PathFilter {
+
+    private final String path;
+
+    EqualsPathFilter(final String path) {
+        if (path == null) {
+            throw new IllegalArgumentException("path is null");
+        }
+        this.path = path;
+    }
+
+    public boolean accept(final String path) {
+        return path.equals(this.path);
+    }
+
+    public boolean equals(final Object obj) {
+        return obj instanceof EqualsPathFilter && equals((EqualsPathFilter) obj);
+    }
+
+    public boolean equals(final EqualsPathFilter obj) {
+        return obj != null && obj.path.equals(path);
+    }
+
+    public String toString() {
+        return "equals \"" + path + "\"";
+    }
+
+    public int hashCode() {
+        return path.hashCode() + 7;
+    }
+}
diff --git a/src/main/java/org/jboss/modules/filter/GlobPathFilter.java b/src/main/java/org/jboss/modules/filter/GlobPathFilter.java
new file mode 100644
index 0000000..645ae71
--- /dev/null
+++ b/src/main/java/org/jboss/modules/filter/GlobPathFilter.java
@@ -0,0 +1,137 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.filter;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Default implementation of PathFilter.  Uses glob based includes and excludes to determine whether to export.  
+ *
+ * @author John E. Bailey
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+final class GlobPathFilter implements PathFilter {
+    private static final Pattern GLOB_PATTERN = Pattern.compile("(\\*\\*?)|(\\?)|(\\\\.)|(/+)|([^*?]+)");
+
+    private final String glob;
+    private final Pattern pattern;
+
+    /**
+     * Construct a new instance.
+     *
+     * @param glob the path glob to match
+     */
+    GlobPathFilter(final String glob) {
+        pattern = getGlobPattern(glob);
+        this.glob = glob;
+    }
+
+    /**
+     * Determine whether a path should be accepted.
+     *
+     * @param path the path to check
+     * @return true if the path should be accepted, false if not
+     */
+    public boolean accept(final String path) {
+        return pattern.matcher(path).matches();
+    }
+
+    /**
+     * Get a regular expression pattern which accept any path names which match the given glob.  The glob patterns
+     * function similarly to {@code ant} file patterns.  Valid metacharacters in the glob pattern include:
+     * <ul>
+     * <li><code>"\"</code> - escape the next character (treat it literally, even if it is itself a recognized metacharacter)</li>
+     * <li><code>"?"</code> - match any non-slash character</li>
+     * <li><code>"*"</code> - match zero or more non-slash characters</li>
+     * <li><code>"**"</code> - match zero or more characters, including slashes</li>
+     * <li><code>"/"</code> - match one or more slash characters.  Consecutive {@code /} characters are collapsed down into one.</li>
+     * </ul>
+     * In addition, any glob pattern matches all subdirectories thereof.  A glob pattern ending in {@code /} is equivalent
+     * to a glob pattern ending in <code>/**</code> in that the named directory is not itself included in the glob.
+     * <p/>
+     * <b>See also:</b> <a href="http://ant.apache.org/manual/dirtasks.html#patterns">"Patterns" in the Ant Manual</a>
+     *
+     * @param glob the glob to match
+     *
+     * @return the pattern
+     */
+    private static Pattern getGlobPattern(final String glob) {
+        StringBuilder patternBuilder = new StringBuilder();
+        final Matcher m = GLOB_PATTERN.matcher(glob);
+        boolean lastWasSlash = false;
+        while (m.find()) {
+            lastWasSlash = false;
+            String grp;
+            if ((grp = m.group(1)) != null) {
+                // match a * or **
+                if (grp.length() == 2) {
+                    // it's a **
+                    patternBuilder.append(".*");
+                } else {
+                    // it's a *
+                    patternBuilder.append("[^/]*");
+                }
+            } else if ((grp = m.group(2)) != null) {
+                // match a '?' glob pattern; any non-slash character
+                patternBuilder.append("[^/]");
+            } else if ((grp = m.group(3)) != null) {
+                // backslash-escaped value
+                patternBuilder.append(Pattern.quote(m.group().substring(1)));
+            } else if ((grp = m.group(4)) != null) {
+                // match any number of / chars
+                patternBuilder.append("/+");
+                lastWasSlash = true;
+            } else {
+                // some other string
+                patternBuilder.append(Pattern.quote(m.group()));
+            }
+        }
+        if (lastWasSlash) {
+            // ends in /, append **
+            patternBuilder.append(".*");
+        } else {
+            patternBuilder.append("(?:/.*)?");
+        }
+        return Pattern.compile(patternBuilder.toString());
+    }
+
+    public int hashCode() {
+        return glob.hashCode() + 13;
+    }
+
+    public boolean equals(final Object obj) {
+        return obj instanceof GlobPathFilter && equals((GlobPathFilter) obj);
+    }
+
+    public boolean equals(final GlobPathFilter obj) {
+        return obj != null && obj.pattern.equals(pattern);
+    }
+
+    public String toString() {
+        final StringBuilder b = new StringBuilder();
+        b.append("match ");
+        if (glob != null) {
+            b.append('"').append(glob).append('"');
+        } else {
+            b.append('/').append(pattern).append('/');
+        }
+        return b.toString();
+    }
+}
diff --git a/src/main/java/org/jboss/modules/filter/InvertingPathFilter.java b/src/main/java/org/jboss/modules/filter/InvertingPathFilter.java
new file mode 100644
index 0000000..6ef297b
--- /dev/null
+++ b/src/main/java/org/jboss/modules/filter/InvertingPathFilter.java
@@ -0,0 +1,65 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.filter;
+
+/**
+ * A path filter which simply inverts the result of another path filter.
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+final class InvertingPathFilter implements PathFilter {
+    private final PathFilter delegate;
+
+    /**
+     * Construct a new instance.
+     *
+     * @param delegate the filter to delegate to
+     */
+    InvertingPathFilter(final PathFilter delegate) {
+        if (delegate == null) {
+            throw new IllegalArgumentException("delegate is null");
+        }
+        this.delegate = delegate;
+    }
+
+    /** {@inheritDoc} */
+    public boolean accept(final String path) {
+        return ! delegate.accept(path);
+    }
+
+    PathFilter getDelegate() {
+        return delegate;
+    }
+
+    public int hashCode() {
+        return 47 + delegate.hashCode();
+    }
+
+    public boolean equals(final Object obj) {
+        return obj instanceof InvertingPathFilter && equals((InvertingPathFilter) obj);
+    }
+
+    public boolean equals(final InvertingPathFilter obj) {
+        return obj != null && obj.delegate.equals(delegate);
+    }
+
+    public String toString() {
+        return "not " + delegate.toString();
+    }
+}
diff --git a/src/main/java/org/jboss/modules/filter/MultiplePathFilter.java b/src/main/java/org/jboss/modules/filter/MultiplePathFilter.java
new file mode 100644
index 0000000..6fafc38
--- /dev/null
+++ b/src/main/java/org/jboss/modules/filter/MultiplePathFilter.java
@@ -0,0 +1,74 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.filter;
+
+import java.util.Arrays;
+
+/**
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+final class MultiplePathFilter implements PathFilter {
+    private final PathFilter[] filters;
+    private final boolean[] includeFlag;
+    private final boolean defaultVal;
+    private final int hashCode;
+
+    MultiplePathFilter(final PathFilter[] filters, final boolean[] includeFlag, final boolean defaultVal) {
+        this.filters = filters;
+        this.includeFlag = includeFlag;
+        this.defaultVal = defaultVal;
+        hashCode = Boolean.valueOf(defaultVal).hashCode() * 13 + (Arrays.hashCode(includeFlag) * 13 + (Arrays.hashCode(filters)));
+    }
+
+    public boolean accept(final String path) {
+        final int len = filters.length;
+        for (int i = 0; i < len; i++) {
+            if (filters[i].accept(path)) return includeFlag[i];
+        }
+        return defaultVal;
+    }
+
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("multi-path filter {");
+        int len = filters.length;
+        for (int i = 0; i < len; i++) {
+            final PathFilter filter = filters[i];
+            final boolean include = includeFlag[i];
+            builder.append(include ? "include " : "exclude ");
+            builder.append(filter);
+            builder.append(", ");
+        }
+        builder.append("default ").append(defaultVal ? "accept" : "reject");
+        builder.append('}');
+        return builder.toString();
+    }
+
+    public int hashCode() {
+        return hashCode;
+    }
+
+    public boolean equals(Object other) {
+        return other instanceof MultiplePathFilter && equals((MultiplePathFilter)other);
+    }
+
+    public boolean equals(MultiplePathFilter other) {
+        return this == other || other != null && Arrays.equals(filters, other.filters) && Arrays.equals(includeFlag, other.includeFlag) && defaultVal == other.defaultVal;
+    }
+}
diff --git a/src/main/java/org/jboss/modules/filter/MultiplePathFilterBuilder.java b/src/main/java/org/jboss/modules/filter/MultiplePathFilterBuilder.java
new file mode 100644
index 0000000..dd2ecfb
--- /dev/null
+++ b/src/main/java/org/jboss/modules/filter/MultiplePathFilterBuilder.java
@@ -0,0 +1,79 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.filter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A builder for a multiple-path filter.
+ *
+ * @apiviz.exclude
+ * @see PathFilters#multiplePathFilterBuilder(boolean)
+ */
+public class MultiplePathFilterBuilder {
+    private final List<PathFilter> filters = new ArrayList<PathFilter>();
+    private final List<Boolean> includeFlags = new ArrayList<Boolean>();
+    private final boolean defaultVal;
+
+    MultiplePathFilterBuilder(final boolean defaultVal) {
+        this.defaultVal = defaultVal;
+    }
+
+    /**
+     * Add a filter to this builder.
+     *
+     * @param filter the filter to add
+     * @param include {@code true} if matching paths should be included, {@code false} for excluded
+     */
+    public void addFilter(final PathFilter filter, final boolean include) {
+        if (filter == null) {
+            throw new IllegalArgumentException("filter is null");
+        }
+        filters.add(filter);
+        includeFlags.add(Boolean.valueOf(include));
+    }
+
+    /**
+     * Create the path filter from this builder's current state.
+     *
+     * @return the path filter
+     */
+    public PathFilter create() {
+        final PathFilter[] filters = this.filters.toArray(new PathFilter[this.filters.size()]);
+        final boolean[] includeFlags = new boolean[this.includeFlags.size()];
+        for (int i = 0, includeFlagsSize = this.includeFlags.size(); i < includeFlagsSize; i++) {
+            includeFlags[i] = this.includeFlags.get(i).booleanValue();
+        }
+        if (filters.length == 0) {
+            return defaultVal ? PathFilters.acceptAll() : PathFilters.rejectAll();
+        } else {
+            return new MultiplePathFilter(filters, includeFlags, defaultVal);
+        }
+    }
+
+    /**
+     * Determine if this filter builder is empty (i.e. has no path filters set on it).
+     *
+     * @return {@code true} if this builder is empty, {@code false} otherwise
+     */
+    public boolean isEmpty() {
+        return filters.isEmpty();
+    }
+}
diff --git a/src/main/java/org/jboss/modules/filter/PathClassFilter.java b/src/main/java/org/jboss/modules/filter/PathClassFilter.java
new file mode 100644
index 0000000..cf434c5
--- /dev/null
+++ b/src/main/java/org/jboss/modules/filter/PathClassFilter.java
@@ -0,0 +1,35 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.filter;
+
+/**
+* @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+*/
+class PathClassFilter implements ClassFilter {
+
+    private final PathFilter resourcePathFilter;
+
+    public PathClassFilter(final PathFilter resourcePathFilter) {
+        this.resourcePathFilter = resourcePathFilter;
+    }
+
+    public boolean accept(final String className) {
+        return resourcePathFilter.accept(className.replace('.', '/').concat(".class"));
+    }
+}
diff --git a/src/main/java/org/jboss/modules/filter/PathFilter.java b/src/main/java/org/jboss/modules/filter/PathFilter.java
new file mode 100644
index 0000000..e7f0e36
--- /dev/null
+++ b/src/main/java/org/jboss/modules/filter/PathFilter.java
@@ -0,0 +1,52 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.filter;
+
+/**
+ * Filter used to determine whether a path should be included or excluded from imports and exports.
+ *
+ * @author John Bailey
+ */
+public interface PathFilter {
+
+    /**
+     * Determine whether a path should be accepted.  The given name is a path separated
+     * by "{@code /}" characters.
+     *
+     * @param path the path to check
+     * @return true if the path should be accepted, false if not
+     */
+    boolean accept(String path);
+
+    /**
+     * Calculate a unique hash code for this path filter.  Equal path filters must yield identical hash codes.
+     *
+     * @return the hash code
+     */
+    int hashCode();
+
+    /**
+     * Determine whether this filter is equal to another.  Filters must implement meaningful (non-identity) equality
+     * semantics.
+     *
+     * @param other the other object
+     * @return {@code true} if this filter is the same
+     */
+    boolean equals(Object other);
+}
\ No newline at end of file
diff --git a/src/main/java/org/jboss/modules/filter/PathFilters.java b/src/main/java/org/jboss/modules/filter/PathFilters.java
new file mode 100644
index 0000000..289155b
--- /dev/null
+++ b/src/main/java/org/jboss/modules/filter/PathFilters.java
@@ -0,0 +1,338 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.filter;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import org.jboss.modules.Resource;
+
+/**
+ * Static factory methods for path filter types.
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public final class PathFilters {
+    private PathFilters() {}
+
+    /**
+     * Get a path filter which returns {@code true} if all of the given filters return {@code true}.
+     *
+     * @param filters the filters
+     * @return the "all" filter
+     */
+    public static PathFilter all(PathFilter... filters) {
+        return new AggregatePathFilter(false, filters);
+    }
+
+    /**
+     * Get a path filter which returns {@code true} if all of the given filters return {@code true}.
+     *
+     * @param filters the filters
+     * @return the "all" filter
+     */
+    public static PathFilter all(Collection<PathFilter> filters) {
+        return all(filters.toArray(new PathFilter[filters.size()]));
+    }
+
+    /**
+     * Get a path filter which returns {@code true} if any of the given filters return {@code true}.
+     *
+     * @param filters the filters
+     * @return the "any" filter
+     */
+    public static PathFilter any(PathFilter... filters) {
+        return new AggregatePathFilter(true, filters);
+    }
+
+    /**
+     * Get a path filter which returns {@code true} if any of the given filters return {@code true}.
+     *
+     * @param filters the filters
+     * @return the "any" filter
+     */
+    public static PathFilter any(Collection<PathFilter> filters) {
+        return any(filters.toArray(new PathFilter[filters.size()]));
+    }
+
+    /**
+     * Get a path filter which returns {@code true} if none of the given filters return {@code true}.
+     *
+     * @param filters the filters
+     * @return the "none" filter
+     */
+    public static PathFilter none(PathFilter... filters) {
+        return not(any(filters));
+    }
+
+    /**
+     * Get a path filter which returns {@code true} if none of the given filters return {@code true}.
+     *
+     * @param filters the filters
+     * @return the "none" filter
+     */
+    public static PathFilter none(Collection<PathFilter> filters) {
+        return not(any(filters));
+    }
+
+    /**
+     * Get a path filter which is {@code true} when the given filter is {@code false}, and vice-versa.
+     *
+     * @param filter the filter
+     * @return the inverting filter
+     */
+    public static PathFilter not(PathFilter filter) {
+        if (filter instanceof BooleanPathFilter) {
+            return booleanFilter(!((BooleanPathFilter) filter).getResult());
+        } else if (filter instanceof InvertingPathFilter) {
+            return ((InvertingPathFilter) filter).getDelegate();
+        } else {
+            return new InvertingPathFilter(filter);
+        }
+    }
+
+    /**
+     * Get a path filter which matches a glob.  The given glob is a path separated
+     * by "{@code /}" characters, which may include the special "{@code *}" and "{@code **}" segment strings
+     * which match any directory and any number of nested directories, respectively.
+     *
+     * @param glob the glob
+     * @return a filter which returns {@code true} if the glob matches
+     */
+    public static PathFilter match(String glob) {
+        return new GlobPathFilter(glob);
+    }
+
+    /**
+     * Get a path filter which matches an exact path name.
+     *
+     * @param path the path name
+     * @return a filter which returns {@code true} if the path name is an exact match
+     */
+    public static PathFilter is(String path) {
+        return new EqualsPathFilter(path);
+    }
+
+    /**
+     * Get a path filter which matches any path which is a child of the given path name (not including the
+     * path name itself).
+     *
+     * @param path the path name
+     * @return a filter which returns {@code true} if the path name is a child of the given path
+     */
+    public static PathFilter isChildOf(String path) {
+        return new ChildPathFilter(path);
+    }
+
+    /**
+     * Get a path filter which matches any path which is equal to, or a child of, the given path name.
+     *
+     * @param path the path name
+     * @return a filter which returns {@code true} if the path name is equal to, or a child of, the given path
+     */
+    public static PathFilter isOrIsChildOf(String path) {
+        return any(is(path), isChildOf(path));
+    }
+
+    /**
+     * Get a builder for a multiple-path filter.  Such a filter contains multiple filters, each associated
+     * with a flag which indicates that matching paths should be included or excluded.
+     *
+     * @param defaultValue the value to return if none of the nested filters match
+     * @return the builder
+     */
+    public static MultiplePathFilterBuilder multiplePathFilterBuilder(boolean defaultValue) {
+        return new MultiplePathFilterBuilder(defaultValue);
+    }
+
+    private static BooleanPathFilter booleanFilter(boolean val) {
+        return val ? BooleanPathFilter.TRUE : BooleanPathFilter.FALSE;
+    }
+
+    /**
+     * Get a filter which always returns {@code true}.
+     *
+     * @return the accept-all filter
+     */
+    public static PathFilter acceptAll() {
+        return BooleanPathFilter.TRUE;
+    }
+
+    /**
+     * Get a filter which always returns {@code false}.
+     *
+     * @return the reject-all filter
+     */
+    public static PathFilter rejectAll() {
+        return BooleanPathFilter.FALSE;
+    }
+
+    /**
+     * Get a filter which returns {@code true} if the tested path is contained within the given set.
+     * Each member of the set is a path separated by "{@code /}" characters; {@code null}s are disallowed.
+     *
+     * @param paths the path set
+     * @return the filter
+     */
+    public static PathFilter in(Set<String> paths) {
+        return new SetPathFilter(new HashSet<String>(paths));
+    }
+
+    /**
+     * Get a filter which returns {@code true} if the tested path is contained within the given collection.
+     * Each member of the collection is a path separated by "{@code /}" characters; {@code null}s are disallowed.
+     *
+     * @param paths the path collection
+     * @return the filter
+     */
+    public static PathFilter in(Collection<String> paths) {
+        return new SetPathFilter(new HashSet<String>(paths));
+    }
+
+    /**
+     * Get a filtered view of a resource iteration.  Only resources which pass the given filter are accepted.
+     *
+     * @param filter the filter to check
+     * @param original the original iterator
+     * @return the filtered iterator
+     */
+    public static Iterator<Resource> filtered(final PathFilter filter, final Iterator<Resource> original) {
+        return new Iterator<Resource>() {
+            private Resource next;
+
+            public boolean hasNext() {
+                Resource next;
+                while (this.next == null && original.hasNext()) {
+                    next = original.next();
+                    if (filter.accept(next.getName())) {
+                        this.next = next;
+                    }
+                }
+                return this.next != null;
+            }
+
+            public Resource next() {
+                if (! hasNext()) {
+                    throw new NoSuchElementException();
+                }
+                try {
+                    return next;
+                } finally {
+                    next = null;
+                }
+            }
+
+            public void remove() {
+                original.remove();
+            }
+        };
+    }
+
+    private static final PathFilter defaultImportFilter;
+    private static final PathFilter defaultImportFilterWithServices;
+    private static final PathFilter metaInfFilter;
+    private static final PathFilter metaInfSubdirectoriesFilter;
+    private static final PathFilter metaInfServicesFilter;
+    private static final PathFilter metaInfSubdirectoriesWithoutMetaInfFilter;
+
+    static {
+        final PathFilter metaInfChildren = isChildOf("META-INF");
+        final PathFilter metaInf = is("META-INF");
+        final PathFilter metaInfServicesChildren = isChildOf("META-INF/services");
+        final PathFilter metaInfServices = is("META-INF/services");
+
+        metaInfFilter = metaInf;
+        metaInfSubdirectoriesFilter = metaInfChildren;
+        metaInfServicesFilter = any(metaInfServices, metaInfServicesChildren);
+
+        final MultiplePathFilterBuilder builder = multiplePathFilterBuilder(true);
+        builder.addFilter(metaInfChildren, false);
+        builder.addFilter(metaInf, false);
+        defaultImportFilter = builder.create();
+
+        final MultiplePathFilterBuilder builder2 = multiplePathFilterBuilder(true);
+        builder2.addFilter(metaInfServices, true);
+        builder2.addFilter(metaInfServicesChildren, true);
+        builder2.addFilter(metaInfChildren, false);
+        builder2.addFilter(metaInf, false);
+        defaultImportFilterWithServices = builder2.create();
+
+        final MultiplePathFilterBuilder builder3 = multiplePathFilterBuilder(true);
+        builder2.addFilter(metaInfChildren, true);
+        builder3.addFilter(metaInf, false);
+        metaInfSubdirectoriesWithoutMetaInfFilter = builder3.create();
+    }
+
+    /**
+     * Get the default import path filter, which excludes all of {@code META-INF} and its subdirectories.
+     *
+     * @return the default import path filter
+     */
+    public static PathFilter getDefaultImportFilter() {
+        return defaultImportFilter;
+    }
+
+    /**
+     * Get the default import-with-services path filter which excludes all of {@code META-INF} and its subdirectories,
+     * with the exception of {@code META-INF/services}.
+     *
+     * @return the default import-with-services path filter
+     */
+    public static PathFilter getDefaultImportFilterWithServices() {
+        return defaultImportFilterWithServices;
+    }
+
+    /**
+     * Get a filter which matches the path {@code "META-INF"}.
+     *
+     * @return the filter
+     */
+    public static PathFilter getMetaInfFilter() {
+        return metaInfFilter;
+    }
+
+    /**
+     * Get a filter which matches any subdirectory of the path {@code "META-INF"}.
+     *
+     * @return the filter
+     */
+    public static PathFilter getMetaInfSubdirectoriesFilter() {
+        return metaInfSubdirectoriesFilter;
+    }
+
+    /**
+     * Get a filter which matches the path {@code "META-INF/services"}.
+     *
+     * @return the filter
+     */
+    public static PathFilter getMetaInfServicesFilter() {
+        return metaInfServicesFilter;
+    }
+
+    /**
+     * Get a filter which matches all of {@code META-INF}'s subdirectories, but not {@code META-INF} itself.
+     *
+     * @return the filter
+     */
+    public static PathFilter getMetaInfSubdirectoriesWithoutMetaInfFilter() {
+        return metaInfSubdirectoriesWithoutMetaInfFilter;
+    }
+}
diff --git a/src/main/java/org/jboss/modules/filter/SetPathFilter.java b/src/main/java/org/jboss/modules/filter/SetPathFilter.java
new file mode 100644
index 0000000..5cfaa31
--- /dev/null
+++ b/src/main/java/org/jboss/modules/filter/SetPathFilter.java
@@ -0,0 +1,67 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.filter;
+
+import java.util.Iterator;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+final class SetPathFilter implements PathFilter {
+
+    private final Set<String> paths;
+    private final int hashCode;
+
+    SetPathFilter(final Set<String> paths) {
+        this.paths = paths;
+        hashCode = paths.hashCode();
+    }
+
+    public boolean accept(final String path) {
+        return paths.contains(path);
+    }
+
+    public String toString() {
+        final StringBuilder b = new StringBuilder();
+        b.append("in {");
+        Iterator<String> iterator = paths.iterator();
+        while (iterator.hasNext()) {
+            final String path = iterator.next();
+            b.append(path);
+            if (iterator.hasNext()) {
+                b.append(", ");
+            }
+        }
+        b.append('}');
+        return b.toString();
+    }
+
+    public boolean equals(final Object obj) {
+        return obj instanceof SetPathFilter && equals((SetPathFilter) obj);
+    }
+
+    public boolean equals(final SetPathFilter obj) {
+        return obj != null && obj.paths.equals(paths);
+    }
+
+    public int hashCode() {
+        return hashCode;
+    }
+}
diff --git a/src/main/java/org/jboss/modules/filter/package-info.java b/src/main/java/org/jboss/modules/filter/package-info.java
new file mode 100644
index 0000000..0a7abd5
--- /dev/null
+++ b/src/main/java/org/jboss/modules/filter/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Classes related to filtering.  See the {@link PathFilter} interface for the contract of path filters.  See
+ * {@link PathFilters} for factory methods for creating simple and complex filters.
+ */
+package org.jboss.modules.filter;
\ No newline at end of file
diff --git a/src/main/java/org/jboss/modules/log/JDKModuleLogger.java b/src/main/java/org/jboss/modules/log/JDKModuleLogger.java
new file mode 100644
index 0000000..b9617fa
--- /dev/null
+++ b/src/main/java/org/jboss/modules/log/JDKModuleLogger.java
@@ -0,0 +1,205 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.log;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.jboss.modules.Main;
+import org.jboss.modules.Module;
+import org.jboss.modules.ModuleIdentifier;
+import org.jboss.modules.ModuleLoader;
+
+/**
+ * A {@link ModuleLogger} which logs to a JDK logging category.
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public final class JDKModuleLogger implements ModuleLogger {
+
+    private static final Level TRACE;
+    private static final Level DEBUG;
+    private static final Level WARN;
+
+    static {
+        Level trace;
+        Level debug;
+        Level warn;
+        try {
+            trace = Level.parse("TRACE");
+        } catch (IllegalArgumentException ignored) {
+            trace = Level.FINEST;
+        }
+        try {
+            debug = Level.parse("DEBUG");
+        } catch (IllegalArgumentException ignored) {
+            debug = Level.FINE;
+        }
+        try {
+            warn = Level.parse("WARN");
+        } catch (IllegalArgumentException ignored) {
+            warn = Level.WARNING;
+        }
+        TRACE = trace;
+        DEBUG = debug;
+        WARN = warn;
+    }
+
+    @SuppressWarnings({ "NonConstantLogger" })
+    private final Logger logger;
+    @SuppressWarnings({ "NonConstantLogger" })
+    private final Logger defineLogger;
+
+    /**
+     * Construct a new instance.
+     *
+     * @param logger the main logger to write to
+     * @param defineLogger the main logger to write class-define-related trace messages to
+     */
+    public JDKModuleLogger(final Logger logger, final Logger defineLogger) {
+        this.logger = logger;
+        this.defineLogger = defineLogger;
+    }
+
+    /**
+     * Construct a new instance.
+     *
+     * @param category the name of the logger category to write to
+     */
+    public JDKModuleLogger(final String category) {
+        this(Logger.getLogger(category), Logger.getLogger(category + ".define"));
+    }
+
+    /**
+     * Construct a new instance using the category {@code org.jboss.modules}.
+     */
+    public JDKModuleLogger() {
+        this("org.jboss.modules");
+    }
+
+    private void doLog(final Level level, final String str) {
+        doLog(level, str, null);
+    }
+
+    private void doLog(final Level level, final String str, final Throwable t) {
+        try {
+            final ModuleLogRecord rec = new ModuleLogRecord(level, str);
+            rec.setLoggerName(logger.getName());
+            if (t != null) rec.setThrown(t);
+            logger.log(rec);
+        } catch (Throwable ignored) {
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void trace(final String message) {
+        doLog(TRACE, message, null);
+    }
+
+    /** {@inheritDoc} */
+    public void trace(final String format, final Object arg1) {
+        if (logger.isLoggable(TRACE)) {
+            doLog(TRACE, String.format(format, arg1), null);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void trace(final String format, final Object arg1, final Object arg2) {
+        if (logger.isLoggable(TRACE)) {
+            doLog(TRACE, String.format(format, arg1, arg2));
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void trace(final String format, final Object arg1, final Object arg2, final Object arg3) {
+        if (logger.isLoggable(TRACE)) {
+            doLog(TRACE, String.format(format, arg1, arg2, arg3));
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void trace(final String format, final Object... args) {
+        if (logger.isLoggable(TRACE)) {
+            doLog(TRACE, String.format(format, (Object[]) args));
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void trace(final Throwable t, final String message) {
+        doLog(TRACE, message, t);
+    }
+
+    /** {@inheritDoc} */
+    public void trace(final Throwable t, final String format, final Object arg1) {
+        if (logger.isLoggable(TRACE)) {
+            doLog(TRACE, String.format(format, arg1), t);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void trace(final Throwable t, final String format, final Object arg1, final Object arg2) {
+        if (logger.isLoggable(TRACE)) {
+            doLog(TRACE, String.format(format, arg1, arg2), t);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void trace(final Throwable t, final String format, final Object arg1, final Object arg2, final Object arg3) {
+        if (logger.isLoggable(TRACE)) {
+            doLog(TRACE, String.format(format, arg1, arg2, arg3), t);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void trace(final Throwable t, final String format, final Object... args) {
+        if (logger.isLoggable(TRACE)) {
+            doLog(TRACE, String.format(format, (Object[]) args), t);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void greeting() {
+        doLog(Level.INFO, String.format("JBoss Modules version %s", Main.getVersionString()));
+    }
+
+    /** {@inheritDoc} */
+    public void moduleDefined(final ModuleIdentifier identifier, final ModuleLoader moduleLoader) {
+        if (logger.isLoggable(DEBUG)) {
+            doLog(DEBUG, String.format("Module %s defined by %s", identifier, moduleLoader));
+        }
+    }
+
+    public void classDefineFailed(final Throwable throwable, final String className, final Module module) {
+        if (defineLogger.isLoggable(WARN)) {
+            doLog(WARN, String.format("Failed to define class %s in %s", className, module), throwable);
+        }
+    }
+
+    public void classDefined(final String name, final Module module) {
+        if (defineLogger.isLoggable(TRACE)) {
+            doLog(TRACE, String.format("Defined class %s in %s", name, module));
+        }
+    }
+
+    public void providerUnloadable(String name, ClassLoader loader) {
+        if (defineLogger.isLoggable(TRACE)) {
+            doLog(TRACE, String.format("Could not load provider %s in %s", name, loader));
+        }
+    }
+}
diff --git a/src/main/java/org/jboss/modules/log/ModuleLogRecord.java b/src/main/java/org/jboss/modules/log/ModuleLogRecord.java
new file mode 100644
index 0000000..b3b3f10
--- /dev/null
+++ b/src/main/java/org/jboss/modules/log/ModuleLogRecord.java
@@ -0,0 +1,95 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.log;
+
+import java.util.logging.LogRecord;
+import java.util.logging.Level;
+
+/**
+ * A log record class which knows how to divine the proper class and method name.
+ */
+class ModuleLogRecord extends LogRecord {
+
+    private static final long serialVersionUID = 542119905844866161L;
+    private boolean resolved;
+    private static final String LOGGER_CLASS_NAME = JDKModuleLogger.class.getName();
+
+    ModuleLogRecord(final Level level, final String msg) {
+        super(level, msg);
+    }
+
+    public String getSourceClassName() {
+        if (! resolved) {
+            resolve();
+        }
+        return super.getSourceClassName();
+    }
+
+    public void setSourceClassName(final String sourceClassName) {
+        resolved = true;
+        super.setSourceClassName(sourceClassName);
+    }
+
+    public String getSourceMethodName() {
+        if (! resolved) {
+            resolve();
+        }
+        return super.getSourceMethodName();
+    }
+
+    public void setSourceMethodName(final String sourceMethodName) {
+        resolved = true;
+        super.setSourceMethodName(sourceMethodName);
+    }
+
+    private void resolve() {
+        resolved = true;
+        final StackTraceElement[] stack = new Throwable().getStackTrace();
+        boolean found = false;
+        for (StackTraceElement element : stack) {
+            final String className = element.getClassName();
+            if (found) {
+                if (! LOGGER_CLASS_NAME.equals(className)) {
+                    setSourceClassName(className);
+                    setSourceMethodName(element.getMethodName());
+                    return;
+                }
+            } else {
+                found = LOGGER_CLASS_NAME.equals(className);
+            }
+        }
+        setSourceClassName("<unknown>");
+        setSourceMethodName("<unknown>");
+    }
+
+    protected Object writeReplace() {
+        final LogRecord replacement = new LogRecord(getLevel(), getMessage());
+        replacement.setResourceBundle(getResourceBundle());
+        replacement.setLoggerName(getLoggerName());
+        replacement.setMillis(getMillis());
+        replacement.setParameters(getParameters());
+        replacement.setResourceBundleName(getResourceBundleName());
+        replacement.setSequenceNumber(getSequenceNumber());
+        replacement.setSourceClassName(getSourceClassName());
+        replacement.setSourceMethodName(getSourceMethodName());
+        replacement.setThreadID(getThreadID());
+        replacement.setThrown(getThrown());
+        return replacement;
+    }
+}
diff --git a/src/main/java/org/jboss/modules/log/ModuleLogger.java b/src/main/java/org/jboss/modules/log/ModuleLogger.java
new file mode 100644
index 0000000..8c29256
--- /dev/null
+++ b/src/main/java/org/jboss/modules/log/ModuleLogger.java
@@ -0,0 +1,62 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.log;
+
+import org.jboss.modules.Module;
+import org.jboss.modules.ModuleIdentifier;
+import org.jboss.modules.ModuleLoader;
+
+/**
+ * A simple Logger abstraction.
+ *
+ * @author thomas.diesler at jboss.com
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public interface ModuleLogger {
+
+    void trace(String message);
+
+    void trace(String format, Object arg1);
+
+    void trace(String format, Object arg1, Object arg2);
+
+    void trace(String format, Object arg1, Object arg2, Object arg3);
+
+    void trace(String format, Object... args);
+
+    void trace(Throwable t, String message);
+
+    void trace(Throwable t, String format, Object arg1);
+
+    void trace(Throwable t, String format, Object arg1, Object arg2);
+
+    void trace(Throwable t, String format, Object arg1, Object arg2, Object arg3);
+
+    void trace(Throwable t, String format, Object... args);
+
+    void greeting();
+
+    void moduleDefined(ModuleIdentifier identifier, final ModuleLoader moduleLoader);
+
+    void classDefineFailed(Throwable throwable, String className, Module module);
+
+    void classDefined(String name, Module module);
+
+    void providerUnloadable(String name, ClassLoader loader);
+}
diff --git a/src/main/java/org/jboss/modules/log/NoopModuleLogger.java b/src/main/java/org/jboss/modules/log/NoopModuleLogger.java
new file mode 100644
index 0000000..9a18ae2
--- /dev/null
+++ b/src/main/java/org/jboss/modules/log/NoopModuleLogger.java
@@ -0,0 +1,98 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.log;
+
+import org.jboss.modules.Module;
+import org.jboss.modules.ModuleIdentifier;
+import org.jboss.modules.ModuleLoader;
+
+/**
+ * A {@link ModuleLogger} implementation that does not log.
+ *
+ * @author thomas.diesler at jboss.com
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public final class NoopModuleLogger implements ModuleLogger {
+
+    private static ModuleLogger instance = new NoopModuleLogger();
+
+    public static ModuleLogger getInstance() {
+        return instance;
+    }
+
+    @Override
+    public void trace(String message) {
+    }
+
+    @Override
+    public void trace(final String format, final Object arg1) {
+    }
+
+    @Override
+    public void trace(final String format, final Object arg1, final Object arg2) {
+    }
+
+    @Override
+    public void trace(final String format, final Object arg1, final Object arg2, final Object arg3) {
+    }
+
+    @Override
+    public void trace(final String format, final Object... args) {
+    }
+
+    @Override
+    public void trace(final Throwable t, final String message) {
+    }
+
+    @Override
+    public void trace(final Throwable t, final String format, final Object arg1) {
+    }
+
+    @Override
+    public void trace(final Throwable t, final String format, final Object arg1, final Object arg2) {
+    }
+
+    @Override
+    public void trace(final Throwable t, final String format, final Object arg1, final Object arg2, final Object arg3) {
+    }
+
+    @Override
+    public void trace(final Throwable t, final String format, final Object... args) {
+    }
+
+    @Override
+    public void greeting() {
+    }
+
+    @Override
+    public void moduleDefined(final ModuleIdentifier identifier, final ModuleLoader moduleLoader) {
+    }
+
+    @Override
+    public void classDefineFailed(final Throwable throwable, final String className, final Module module) {
+    }
+
+    @Override
+    public void classDefined(final String name, final Module module) {
+    }
+
+    @Override
+    public void providerUnloadable(String name, ClassLoader loader) {
+    }
+}
diff --git a/src/main/java/org/jboss/modules/log/StreamModuleLogger.java b/src/main/java/org/jboss/modules/log/StreamModuleLogger.java
new file mode 100644
index 0000000..bb08d81
--- /dev/null
+++ b/src/main/java/org/jboss/modules/log/StreamModuleLogger.java
@@ -0,0 +1,160 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.log;
+
+import java.io.OutputStream;
+import java.io.PrintStream;
+
+import org.jboss.modules.Module;
+import org.jboss.modules.ModuleIdentifier;
+import org.jboss.modules.ModuleLoader;
+
+/**
+ * A {@link ModuleLogger} implementation that logs all output (including trace) to an output or print stream.
+ *
+ * @author thomas.diesler at jboss.com
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public final class StreamModuleLogger implements ModuleLogger {
+
+    private PrintStream print;
+
+    /**
+     * Construct a new instance.
+     *
+     * @param stream the print stream to write to
+     */
+    public StreamModuleLogger(PrintStream stream) {
+        if (stream == null) {
+            throw new IllegalArgumentException("stream is null");
+        }
+        print = new PrintStream(stream);
+    }
+
+    /**
+     * Construct a new instance.
+     *
+     * @param stream the output stream to write to
+     */
+    public StreamModuleLogger(OutputStream stream) {
+        this(new PrintStream(stream));
+    }
+
+    /** {@inheritDoc} */
+    public void trace(final String message) {
+        print.print("modules TRACE: ");
+        print.println(message);
+        print.flush();
+    }
+
+    /** {@inheritDoc} */
+    public void trace(final String format, final Object arg1) {
+        print.print("modules TRACE: ");
+        print.printf(format, arg1);
+        print.println();
+        print.flush();
+    }
+
+    /** {@inheritDoc} */
+    public void trace(final String format, final Object arg1, final Object arg2) {
+        print.print("modules TRACE: ");
+        print.printf(format, arg1, arg2);
+        print.println();
+        print.flush();
+    }
+
+    /** {@inheritDoc} */
+    public void trace(final String format, final Object arg1, final Object arg2, final Object arg3) {
+        print.print("modules TRACE: ");
+        print.printf(format, arg1, arg2, arg3);
+        print.println();
+        print.flush();
+    }
+
+    /** {@inheritDoc} */
+    public void trace(final String format, final Object... args) {
+        print.print("modules TRACE: ");
+        print.printf(format, (Object[]) args);
+        print.println();
+        print.flush();
+    }
+
+    /** {@inheritDoc} */
+    public void trace(final Throwable t, final String message) {
+        print.print("modules TRACE: ");
+        print.print(message);
+        print.print(": ");
+        t.printStackTrace(print);
+        print.flush();
+    }
+
+    /** {@inheritDoc} */
+    public void trace(final Throwable t, final String format, final Object arg1) {
+        print.print("modules TRACE: ");
+        print.printf(format, arg1);
+        print.print(": ");
+        t.printStackTrace(print);
+        print.flush();
+    }
+
+    /** {@inheritDoc} */
+    public void trace(final Throwable t, final String format, final Object arg1, final Object arg2) {
+        print.print("modules TRACE: ");
+        print.printf(format, arg1, arg2);
+        print.print(": ");
+        t.printStackTrace(print);
+        print.flush();
+    }
+
+    /** {@inheritDoc} */
+    public void trace(final Throwable t, final String format, final Object arg1, final Object arg2, final Object arg3) {
+        print.print("modules TRACE: ");
+        print.printf(format, arg1, arg2, arg3);
+        print.print(": ");
+        t.printStackTrace(print);
+        print.flush();
+    }
+
+    /** {@inheritDoc} */
+    public void trace(final Throwable t, final String format, final Object... args) {
+        print.print("modules TRACE: ");
+        print.printf(format, args);
+        print.print(": ");
+        t.printStackTrace(print);
+        print.flush();
+    }
+
+    /** {@inheritDoc} */
+    public void greeting() {
+    }
+
+    /** {@inheritDoc} */
+    public void moduleDefined(final ModuleIdentifier identifier, final ModuleLoader moduleLoader) {
+    }
+
+    /** {@inheritDoc} */
+    public void classDefineFailed(final Throwable throwable, final String className, final Module module) {
+    }
+
+    public void classDefined(final String name, final Module module) {
+    }
+
+    public void providerUnloadable(String name, ClassLoader loader) {
+    }
+}
diff --git a/src/main/java/org/jboss/modules/log/package-info.java b/src/main/java/org/jboss/modules/log/package-info.java
new file mode 100644
index 0000000..c10c65f
--- /dev/null
+++ b/src/main/java/org/jboss/modules/log/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * The logging interface for JBoss Modules.  Logging is abstracted in order to support logging services being provided
+ * by a module.  To change the logger in use, use the {@link org.jboss.modules.Module#setModuleLogger(ModuleLogger)} method.
+ * See {@link ModuleLogger} for the logging contract.
+ */
+package org.jboss.modules.log;
\ No newline at end of file
diff --git a/src/main/java/org/jboss/modules/management/DependencyInfo.java b/src/main/java/org/jboss/modules/management/DependencyInfo.java
new file mode 100644
index 0000000..b56e2f8
--- /dev/null
+++ b/src/main/java/org/jboss/modules/management/DependencyInfo.java
@@ -0,0 +1,134 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.management;
+
+import java.beans.ConstructorProperties;
+import java.util.List;
+
+/**
+ * Information describing a dependency.
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public final class DependencyInfo {
+    private final String dependencyType;
+    private final String exportFilter;
+    private final String importFilter;
+    private final ModuleLoaderMXBean moduleLoader;
+    private final String moduleName;
+    private final boolean optional;
+    private final String localLoader;
+    private final List<String> localLoaderPaths;
+
+    /**
+     * Construct a new instance.
+     *
+     * @param dependencyType the dependency type class name
+     * @param exportFilter the export filter, as a string
+     * @param importFilter the import filter, as a string
+     * @param moduleLoader the module loader MXBean of this dependency
+     * @param moduleName the module name, as a string
+     * @param optional {@code true} if this is an optional dependency
+     * @param localLoader the local loader type class name
+     * @param localLoaderPaths the list of paths made available by the local loader
+     */
+    @ConstructorProperties({"dependencyType", "exportFilter", "importFilter", "moduleLoader", "moduleName", "optional", "localLoader", "localLoaderPaths"})
+    public DependencyInfo(final String dependencyType, final String exportFilter, final String importFilter, final ModuleLoaderMXBean moduleLoader, final String moduleName, final boolean optional, final String localLoader, final List<String> localLoaderPaths) {
+        this.dependencyType = dependencyType;
+        this.exportFilter = exportFilter;
+        this.importFilter = importFilter;
+        this.moduleLoader = moduleLoader;
+        this.moduleName = moduleName;
+        this.optional = optional;
+        this.localLoader = localLoader;
+        this.localLoaderPaths = localLoaderPaths;
+    }
+
+    /**
+     * Get the dependency type class name.
+     *
+     * @return the dependency type class name
+     */
+    public String getDependencyType() {
+        return dependencyType;
+    }
+
+    /**
+     * Get the export filter, as a string.
+     *
+     * @return the export filter, as a string
+     */
+    public String getExportFilter() {
+        return exportFilter;
+    }
+
+    /**
+     * Get the import filter, as a string.
+     *
+     * @return the import filter, as a string
+     */
+    public String getImportFilter() {
+        return importFilter;
+    }
+
+    /**
+     * Get the module loader MXBean of this dependency.
+     *
+     * @return the module loader MXBean of this dependency
+     */
+    public ModuleLoaderMXBean getModuleLoader() {
+        return moduleLoader;
+    }
+
+    /**
+     * Get the module name, as a string.
+     *
+     * @return the module name, as a string
+     */
+    public String getModuleName() {
+        return moduleName;
+    }
+
+    /**
+     * Determine whether this is an optional dependency.
+     *
+     * @return {@code true} if this is an optional dependency
+     */
+    public boolean isOptional() {
+        return optional;
+    }
+
+    /**
+     * Get the local loader type class name.
+     *
+     * @return the local loader type class name
+     */
+    public String getLocalLoader() {
+        return localLoader;
+    }
+
+    /**
+     * Get the list of paths made available by the local loader.
+     *
+     * @return the list of paths made available by the local loader
+     */
+    public List<String> getLocalLoaderPaths() {
+        return localLoaderPaths;
+    }
+}
diff --git a/src/main/java/org/jboss/modules/management/ModuleInfo.java b/src/main/java/org/jboss/modules/management/ModuleInfo.java
new file mode 100644
index 0000000..4d1cbad
--- /dev/null
+++ b/src/main/java/org/jboss/modules/management/ModuleInfo.java
@@ -0,0 +1,122 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.management;
+
+import java.beans.ConstructorProperties;
+import java.util.List;
+
+/**
+ * Management information about a module instance.
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public final class ModuleInfo {
+    private final String name;
+    private final ModuleLoaderMXBean moduleLoader;
+    private final List<DependencyInfo> dependencies;
+    private final List<ResourceLoaderInfo> resourceLoaders;
+    private final String mainClass;
+    private final String classLoader;
+    private final String fallbackLoader;
+
+    /**
+     * Construct a new instance.
+     *
+     * @param name the module name
+     * @param moduleLoader the module loader
+     * @param dependencies the dependencies list
+     * @param resourceLoaders the resource loaders list
+     * @param mainClass the main class name
+     * @param classLoader the class loader
+     * @param fallbackLoader the fallback loader
+     */
+    @ConstructorProperties({"name", "moduleLoader", "dependencies", "resourceLoaders", "mainClass", "classLoader", "fallbackLoader"})
+    public ModuleInfo(final String name, final ModuleLoaderMXBean moduleLoader, final List<DependencyInfo> dependencies, final List<ResourceLoaderInfo> resourceLoaders, final String mainClass, final String classLoader, final String fallbackLoader) {
+        this.name = name;
+        this.moduleLoader = moduleLoader;
+        this.dependencies = dependencies;
+        this.resourceLoaders = resourceLoaders;
+        this.mainClass = mainClass;
+        this.classLoader = classLoader;
+        this.fallbackLoader = fallbackLoader;
+    }
+
+    /**
+     * Get the name of the corresponding module.
+     *
+     * @return the name of the corresponding module
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Get the associated module loader MXBean.
+     *
+     * @return the associated module loader MXBean
+     */
+    public ModuleLoaderMXBean getModuleLoader() {
+        return moduleLoader;
+    }
+
+    /**
+     * Get the dependency information list.
+     *
+     * @return the dependency information list
+     */
+    public List<DependencyInfo> getDependencies() {
+        return dependencies;
+    }
+
+    /**
+     * Get the resource loader information list.
+     *
+     * @return the resource loader information list
+     */
+    public List<ResourceLoaderInfo> getResourceLoaders() {
+        return resourceLoaders;
+    }
+
+    /**
+     * Get the main class name.
+     *
+     * @return the main class name
+     */
+    public String getMainClass() {
+        return mainClass;
+    }
+
+    /**
+     * Get the class loader (as a string).
+     *
+     * @return the class loader (as a string)
+     */
+    public String getClassLoader() {
+        return classLoader;
+    }
+
+    /**
+     * Get the fallback loader (as a string).
+     *
+     * @return the fallback loader (as a string)
+     */
+    public String getFallbackLoader() {
+        return fallbackLoader;
+    }
+}
diff --git a/src/main/java/org/jboss/modules/management/ModuleLoaderMXBean.java b/src/main/java/org/jboss/modules/management/ModuleLoaderMXBean.java
new file mode 100644
index 0000000..9b0abd6
--- /dev/null
+++ b/src/main/java/org/jboss/modules/management/ModuleLoaderMXBean.java
@@ -0,0 +1,163 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.management;
+
+import java.util.List;
+import java.util.SortedMap;
+
+/**
+ * An MXBean for getting runtime information about a module loader.
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public interface ModuleLoaderMXBean {
+
+    /**
+     * Get a description of this module loader.
+     *
+     * @return a description of this module loader
+     */
+    String getDescription();
+
+    /**
+     * Get the estimated CPU time (in nanoseconds) spent linking in the life of this module loader.
+     *
+     * @return the estimated time in nanoseconds
+     */
+    long getLinkTime();
+
+    /**
+     * Get the estimated CPU time (in nanoseconds) spent loading modules into this loader.
+     *
+     * @return the estimated time in nanoseconds
+     */
+    long getLoadTime();
+
+    /**
+     * Get the estimated CPU time (in nanoseconds) spent defining classes for this loader.
+     *
+     * @return the estimated time in nanoseconds
+     */
+    long getClassDefineTime();
+
+    /**
+     * Get the number of times that dependencies of a module from this loader have been scanned.
+     *
+     * @return the count
+     */
+    int getScanCount();
+
+    /**
+     * Get the number of modules currently loaded.
+     *
+     * @return the loaded module count
+     */
+    int getLoadedModuleCount();
+
+    /**
+     * Get the number of times a class was defined by two threads at once.
+     *
+     * @return the race count
+     */
+    int getRaceCount();
+
+    /**
+     * Get the number of classes defined in this module loader.
+     *
+     * @return the number of classes defined in this module loader
+     */
+    int getClassCount();
+
+    /**
+     * Obtain a list of the current module names.
+     *
+     * @return the module names
+     */
+    List<String> queryLoadedModuleNames();
+
+    /**
+     * Dump all information for a single module as a string.
+     *
+     * @param name the module name
+     * @return the string of module information
+     */
+    String dumpModuleInformation(String name);
+
+    /**
+     * Dump all information for all modules as a string.
+     *
+     * @return the string of module information
+     */
+    String dumpAllModuleInformation();
+
+    /**
+     * Attempt to unload a module from this module loader.
+     *
+     * @param name the string form of the module identifier to unload
+     * @return {@code true} if the module was unloaded
+     */
+    boolean unloadModule(String name);
+
+    /**
+     * Attempt to refresh the resource loaders of the given module.
+     *
+     * @param name the name of the module to refresh
+     */
+    void refreshResourceLoaders(String name);
+
+    /**
+     * Attempt to relink the given module.
+     *
+     * @param name the name of the module to relink
+     */
+    void relink(String name);
+
+    /**
+     * Get the dependencies of the named module.
+     *
+     * @param name the module name
+     * @return the module's dependencies
+     */
+    List<DependencyInfo> getDependencies(String name);
+
+    /**
+     * Get the resource loaders of the named module.
+     *
+     * @param name the module name
+     * @return the module's resource loaders
+     */
+    List<ResourceLoaderInfo> getResourceLoaders(String name);
+
+    /**
+     * Get the complete description of this module.
+     *
+     * @param name the module name
+     * @return the module description
+     */
+    ModuleInfo getModuleDescription(String name);
+
+    /**
+     * Get a paths map for a given module.
+     *
+     * @param name the module name
+     * @param exports {@code true} for the exported paths, {@code false} for all paths
+     * @return the paths map information
+     */
+    SortedMap<String, List<String>> getModulePathsInfo(String name, boolean exports);
+}
diff --git a/src/main/java/org/jboss/modules/management/ObjectProperties.java b/src/main/java/org/jboss/modules/management/ObjectProperties.java
new file mode 100644
index 0000000..5cef3cc
--- /dev/null
+++ b/src/main/java/org/jboss/modules/management/ObjectProperties.java
@@ -0,0 +1,168 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.management;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A Hashtable variant which keeps property names in order, for use by MBean {@code ObjectName}s.
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public final class ObjectProperties extends Hashtable<String, String> {
+
+    private static final long serialVersionUID = -4691081844415343670L;
+
+    private final Map<String, String> realMap;
+
+    public static Property property(String key, String value) {
+        return new Property(key, value);
+    }
+
+    public static ObjectProperties properties(Property... properties) {
+        return new ObjectProperties(properties);
+    }
+
+    public ObjectProperties(final int initialCapacity, final float loadFactor) {
+        realMap = new LinkedHashMap<String, String>(initialCapacity, loadFactor);
+    }
+
+    public ObjectProperties(final int initialCapacity) {
+        realMap = new LinkedHashMap<String, String>(initialCapacity);
+    }
+
+    public ObjectProperties() {
+        realMap = new LinkedHashMap<String, String>();
+    }
+
+    public ObjectProperties(final Map<? extends String, ? extends String> t) {
+        realMap = new LinkedHashMap<String, String>(t);
+    }
+
+    public ObjectProperties(Property... properties) {
+        realMap = new LinkedHashMap<String, String>(properties.length);
+        for (Property property : properties) {
+            realMap.put(property.getKey(), property.getValue());
+        }
+    }
+
+    public int size() {
+        return realMap.size();
+    }
+
+    public boolean isEmpty() {
+        return realMap.isEmpty();
+    }
+
+    public Enumeration<String> keys() {
+        return Collections.enumeration(realMap.keySet());
+    }
+
+    public Enumeration<String> elements() {
+        return Collections.enumeration(realMap.values());
+    }
+
+    public boolean contains(final Object value) {
+        return realMap.containsValue(value);
+    }
+
+    public boolean containsValue(final Object value) {
+        return realMap.containsValue(value);
+    }
+
+    public boolean containsKey(final Object key) {
+        return realMap.containsKey(key);
+    }
+
+    public String get(final Object key) {
+        return realMap.get(key);
+    }
+
+    protected void rehash() {
+    }
+
+    public String put(final String key, final String value) {
+        return realMap.put(key, value);
+    }
+
+    public String remove(final Object key) {
+        return realMap.remove(key);
+    }
+
+    public void putAll(final Map<? extends String, ? extends String> t) {
+        realMap.putAll(t);
+    }
+
+    public void clear() {
+        realMap.clear();
+    }
+
+    public Object clone() {
+        return super.clone();
+    }
+
+    public String toString() {
+        return realMap.toString();
+    }
+
+    public Set<String> keySet() {
+        return realMap.keySet();
+    }
+
+    public Set<Map.Entry<String, String>> entrySet() {
+        return realMap.entrySet();
+    }
+
+    public Collection<String> values() {
+        return realMap.values();
+    }
+
+    /**
+     * A single property in a properties list.
+     */
+    public static final class Property {
+        private final String key;
+        private final String value;
+
+        public Property(final String key, final String value) {
+            if (key == null) {
+                throw new IllegalArgumentException("key is null");
+            }
+            if (value == null) {
+                throw new IllegalArgumentException("value is null");
+            }
+            this.key = key;
+            this.value = value;
+        }
+
+        public String getKey() {
+            return key;
+        }
+
+        public String getValue() {
+            return value;
+        }
+    }
+}
diff --git a/src/main/java/org/jboss/modules/management/ResourceLoaderInfo.java b/src/main/java/org/jboss/modules/management/ResourceLoaderInfo.java
new file mode 100644
index 0000000..1b63b50
--- /dev/null
+++ b/src/main/java/org/jboss/modules/management/ResourceLoaderInfo.java
@@ -0,0 +1,62 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.management;
+
+import java.beans.ConstructorProperties;
+import java.util.List;
+
+/**
+ * Information about a resource loader.
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public final class ResourceLoaderInfo {
+    private final String type;
+    private final List<String> paths;
+
+    /**
+     * Construct a new instance.
+     *
+     * @param type the type name
+     * @param paths the paths list
+     */
+    @ConstructorProperties({"type", "paths"})
+    public ResourceLoaderInfo(final String type, final List<String> paths) {
+        this.type = type;
+        this.paths = paths;
+    }
+
+    /**
+     * Get the type name.
+     *
+     * @return the type name
+     */
+    public String getType() {
+        return type;
+    }
+
+    /**
+     * Get the paths list.
+     *
+     * @return the paths list
+     */
+    public List<String> getPaths() {
+        return paths;
+    }
+}
diff --git a/src/main/java/org/jboss/modules/management/package-info.java b/src/main/java/org/jboss/modules/management/package-info.java
new file mode 100644
index 0000000..fe20a19
--- /dev/null
+++ b/src/main/java/org/jboss/modules/management/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Management interfaces for utilizing JBoss Modules via an MBean container.
+ */
+package org.jboss.modules.management;
\ No newline at end of file
diff --git a/src/main/java/org/jboss/modules/package-info.java b/src/main/java/org/jboss/modules/package-info.java
new file mode 100644
index 0000000..8128eec
--- /dev/null
+++ b/src/main/java/org/jboss/modules/package-info.java
@@ -0,0 +1,25 @@
+
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * The primary JBoss Modules API.  The key classes in this package are {@link Module}, {@link ModuleLoader},
+ * {@link ConcurrentClassLoader}, and {@link ModuleClassLoader}.  These classes make up the backbone of the module
+ * system.
+ */
+package org.jboss.modules;
\ No newline at end of file
diff --git a/src/main/java/org/jboss/modules/ref/PhantomReference.java b/src/main/java/org/jboss/modules/ref/PhantomReference.java
new file mode 100644
index 0000000..3236e9e
--- /dev/null
+++ b/src/main/java/org/jboss/modules/ref/PhantomReference.java
@@ -0,0 +1,65 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.ref;
+
+import java.lang.ref.ReferenceQueue;
+
+/**
+ * A reapable phantom reference with an attachment.  If a {@link Reaper} is given, then it will be used to asynchronously
+ * clean up the referent.
+ *
+ * @param <T> the reference value type
+ * @param <A> the attachment type
+ *
+ * @see java.lang.ref.PhantomReference
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public class PhantomReference<T, A> extends java.lang.ref.PhantomReference<T> implements Reference<T, A>, Reapable<T, A> {
+    private final A attachment;
+    private final Reaper<T, A> reaper;
+
+    public PhantomReference(final T referent, final A attachment, final ReferenceQueue<? super T> q) {
+        super(referent, q);
+        this.attachment = attachment;
+        reaper = null;
+    }
+
+    public PhantomReference(final T referent, final A attachment, final Reaper<T, A> reaper) {
+        super(referent, References.ReaperThread.REAPER_QUEUE);
+        this.reaper = reaper;
+        this.attachment = attachment;
+    }
+
+    public A getAttachment() {
+        return attachment;
+    }
+
+    public Type getType() {
+        return Type.PHANTOM;
+    }
+
+    public Reaper<T, A> getReaper() {
+        return reaper;
+    }
+
+    public String toString() {
+        return "phantom reference to " + String.valueOf(get());
+    }
+}
diff --git a/src/main/java/org/jboss/modules/ref/Reapable.java b/src/main/java/org/jboss/modules/ref/Reapable.java
new file mode 100644
index 0000000..094adeb
--- /dev/null
+++ b/src/main/java/org/jboss/modules/ref/Reapable.java
@@ -0,0 +1,39 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.ref;
+
+/**
+ * A reference which is reapable (can be automatically collected).
+ *
+ * @param <T> the reference type
+ * @param <A> the reference attachment type
+ *
+ * @apiviz.exclude
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+interface Reapable<T, A> {
+
+    /**
+     * Get the associated reaper.
+     *
+     * @return the reaper
+     */
+    Reaper<T, A> getReaper();
+}
diff --git a/src/main/java/org/jboss/modules/ref/Reaper.java b/src/main/java/org/jboss/modules/ref/Reaper.java
new file mode 100644
index 0000000..c667835
--- /dev/null
+++ b/src/main/java/org/jboss/modules/ref/Reaper.java
@@ -0,0 +1,37 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.ref;
+
+/**
+ * A cleaner for a dead object.
+ *
+ * @param <T> the reference type
+ * @param <A> the reference attachment type
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public interface Reaper<T, A> {
+
+    /**
+     * Perform the cleanup action for a reference.
+     *
+     * @param reference the reference
+     */
+    void reap(Reference<T, A> reference);
+}
diff --git a/src/main/java/org/jboss/modules/ref/Reference.java b/src/main/java/org/jboss/modules/ref/Reference.java
new file mode 100644
index 0000000..67c0d22
--- /dev/null
+++ b/src/main/java/org/jboss/modules/ref/Reference.java
@@ -0,0 +1,87 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.ref;
+
+/**
+ * An enhanced reference type with a type-safe attachment.
+ *
+ * @param <T> the reference value type
+ * @param <A> the attachment type
+ *
+ * @see java.lang.ref.Reference
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public interface Reference<T, A> {
+
+    /**
+     * Get the value, or {@code null} if the reference has been cleared.
+     *
+     * @return the value
+     */
+    T get();
+
+    /**
+     * Get the attachment, if any.
+     *
+     * @return the attachment
+     */
+    A getAttachment();
+
+    /**
+     * Clear the reference.
+     */
+    void clear();
+
+    /**
+     * Get the type of the reference.
+     *
+     * @return the type
+     */
+    Type getType();
+
+    /**
+     * A reference type.
+     *
+     * @apiviz.exclude
+     */
+    enum Type {
+
+        /**
+         * A strong reference.
+         */
+        STRONG,
+        /**
+         * A weak reference.
+         */
+        WEAK,
+        /**
+         * A phantom reference.
+         */
+        PHANTOM,
+        /**
+         * A soft reference.
+         */
+        SOFT,
+        /**
+         * A {@code null} reference.
+         */
+        NULL,
+    }
+}
diff --git a/src/main/java/org/jboss/modules/ref/References.java b/src/main/java/org/jboss/modules/ref/References.java
new file mode 100644
index 0000000..126d6be
--- /dev/null
+++ b/src/main/java/org/jboss/modules/ref/References.java
@@ -0,0 +1,182 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.ref;
+
+import java.lang.ref.ReferenceQueue;
+
+/**
+ * A set of utility methods for reference types.
+ */
+public final class References {
+    @SuppressWarnings({ "RawUseOfParameterizedType" })
+    private static final Reference NULL = new Reference() {
+        public Object get() {
+            return null;
+        }
+
+        public Object getAttachment() {
+            return null;
+        }
+
+        public void clear() {
+        }
+
+        public Type getType() {
+            return Type.NULL;
+        }
+
+        public String toString() {
+            return "NULL reference";
+        }
+    };
+
+    private References() {
+    }
+
+    static final class ReaperThread extends Thread {
+        static final ReferenceQueue<Object> REAPER_QUEUE = new ReferenceQueue<Object>();
+
+        static {
+            final ReaperThread thr = new ReaperThread();
+            thr.setName("Reference Reaper");
+            thr.setDaemon(true);
+            thr.start();
+        }
+
+        public void run() {
+            for (;;) try {
+                final java.lang.ref.Reference<? extends Object> ref = REAPER_QUEUE.remove();
+                if (ref instanceof Reapable) {
+                    reap((Reapable<?, ?>) ref);
+                }
+            } catch (InterruptedException ignored) {
+            } catch (Throwable ignored) {
+            }
+        }
+
+        @SuppressWarnings({ "unchecked" })
+        private static <T, A> void reap(final Reapable<T, A> reapable) {
+            reapable.getReaper().reap((Reference<T, A>) reapable);
+        }
+    }
+
+    /**
+     * Create a reference of a given type with the provided value and attachment.  If the reference type is
+     * {@link Reference.Type#STRONG} or {@link Reference.Type#NULL} then the reaper argument is ignored.  If
+     * the reference type is {@link Reference.Type#NULL} then the value and attachment arguments are ignored.
+     *
+     * @param type the reference type
+     * @param value the reference value
+     * @param attachment the attachment value
+     * @param reaper the reaper to use, if any
+     * @param <T> the reference value type
+     * @param <A> the reference attachment type
+     * @return the reference
+     */
+    public static <T, A> Reference<T, A> create(Reference.Type type, T value, A attachment, Reaper<T, A> reaper) {
+        switch (type) {
+            case STRONG:
+                return new StrongReference<T, A>(value, attachment);
+            case WEAK:
+                return new WeakReference<T, A>(value, attachment, reaper);
+            case PHANTOM:
+                return new PhantomReference<T, A>(value, attachment, reaper);
+            case SOFT:
+                return new SoftReference<T, A>(value, attachment, reaper);
+            case NULL:
+                return getNullReference();
+            default:
+                throw new IllegalStateException();
+        }
+    }
+
+    /**
+     * Create a reference of a given type with the provided value and attachment.  If the reference type is
+     * {@link Reference.Type#STRONG} or {@link Reference.Type#NULL} then the reference queue argument is ignored.  If
+     * the reference type is {@link Reference.Type#NULL} then the value and attachment arguments are ignored.
+     *
+     * @param type the reference type
+     * @param value the reference value
+     * @param attachment the attachment value
+     * @param referenceQueue the reference queue to use, if any
+     * @param <T> the reference value type
+     * @param <A> the reference attachment type
+     * @return the reference
+     */
+    public static <T, A> Reference<T, A> create(Reference.Type type, T value, A attachment, ReferenceQueue<? super T> referenceQueue) {
+        switch (type) {
+            case STRONG:
+                return new StrongReference<T, A>(value, attachment);
+            case WEAK:
+                return new WeakReference<T, A>(value, attachment, referenceQueue);
+            case PHANTOM:
+                return new PhantomReference<T, A>(value, attachment, referenceQueue);
+            case SOFT:
+                return new SoftReference<T, A>(value, attachment, referenceQueue);
+            case NULL:
+                return getNullReference();
+            default:
+                throw new IllegalStateException();
+        }
+    }
+
+    /**
+     * Create a reference of a given type with the provided value and attachment.  If the reference type is
+     * {@link Reference.Type#PHANTOM} then this method will throw an {@code IllegalArgumentException} because
+     * such references are not constructable without a queue or reaper.  If the reference type is
+     * {@link Reference.Type#NULL} then the value and attachment arguments are ignored.
+     *
+     * @param type the reference type
+     * @param value the reference value
+     * @param attachment the attachment value
+     * @param <T> the reference value type
+     * @param <A> the reference attachment type
+     * @return the reference
+     * @throws IllegalArgumentException if the reference type is {@link Reference.Type#PHANTOM}
+     */
+    public static <T, A> Reference<T, A> create(Reference.Type type, T value, A attachment) throws IllegalArgumentException {
+        switch (type) {
+            case STRONG:
+                return new StrongReference<T, A>(value, attachment);
+            case WEAK:
+                return new WeakReference<T, A>(value, attachment);
+            case PHANTOM:
+                throw new IllegalArgumentException("Phantom reference may not be created without a queue or reaper");
+            case SOFT:
+                return new SoftReference<T, A>(value, attachment);
+            case NULL:
+                return getNullReference();
+            default:
+                throw new IllegalStateException();
+        }
+    }
+
+    /**
+     * Get a null reference.  This reference type is always cleared and does not retain an attachment; as such
+     * there is only one single instance of it.
+     *
+     * @param <T> the reference value type
+     * @param <A> the attachment value type
+     * @return the null reference
+     */
+    @SuppressWarnings({ "unchecked" })
+    public static <T, A> Reference<T, A> getNullReference() {
+        return (Reference<T, A>) NULL;
+    }
+}
diff --git a/src/main/java/org/jboss/modules/ref/SoftReference.java b/src/main/java/org/jboss/modules/ref/SoftReference.java
new file mode 100644
index 0000000..a15d289
--- /dev/null
+++ b/src/main/java/org/jboss/modules/ref/SoftReference.java
@@ -0,0 +1,73 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.ref;
+
+import java.lang.ref.ReferenceQueue;
+
+/**
+ * A reapable soft reference with an attachment.  If a {@link Reaper} is given, then it will be used to asynchronously
+ * clean up the referent.
+ *
+ * @param <T> the reference value type
+ * @param <A> the attachment type
+ *
+ * @see java.lang.ref.SoftReference
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public class SoftReference<T, A> extends java.lang.ref.SoftReference<T> implements Reference<T, A>, Reapable<T, A> {
+    private final A attachment;
+    private final Reaper<T, A> reaper;
+
+    public SoftReference(final T referent) {
+        this(referent, null, (ReferenceQueue<T>) null);
+    }
+
+    public SoftReference(final T referent, final A attachment) {
+        this(referent, attachment, (ReferenceQueue<T>) null);
+    }
+
+    public SoftReference(final T referent, final A attachment, final ReferenceQueue<? super T> q) {
+        super(referent, q);
+        reaper = null;
+        this.attachment = attachment;
+    }
+
+    public SoftReference(final T referent, final A attachment, final Reaper<T, A> reaper) {
+        super(referent, References.ReaperThread.REAPER_QUEUE);
+        this.reaper = reaper;
+        this.attachment = attachment;
+    }
+
+    public Reaper<T, A> getReaper() {
+        return reaper;
+    }
+
+    public A getAttachment() {
+        return attachment;
+    }
+
+    public Type getType() {
+        return Type.SOFT;
+    }
+
+    public String toString() {
+        return "soft reference to " + String.valueOf(get());
+    }
+}
diff --git a/src/main/java/org/jboss/modules/ref/StrongReference.java b/src/main/java/org/jboss/modules/ref/StrongReference.java
new file mode 100644
index 0000000..d4846f8
--- /dev/null
+++ b/src/main/java/org/jboss/modules/ref/StrongReference.java
@@ -0,0 +1,62 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.ref;
+
+/**
+ * A strong reference with an attachment.  Since strong references are always reachable, a reaper may not be used.
+ *
+ * @param <T> the reference value type
+ * @param <A> the attachment type
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public class StrongReference<T, A> implements Reference<T, A> {
+
+    private volatile T value;
+    private final A attachment;
+
+    public StrongReference(final T value, final A attachment) {
+        this.value = value;
+        this.attachment = attachment;
+    }
+
+    public StrongReference(final T value) {
+        this(value, null);
+    }
+
+    public T get() {
+        return value;
+    }
+
+    public void clear() {
+        value = null;
+    }
+
+    public A getAttachment() {
+        return attachment;
+    }
+
+    public Type getType() {
+        return Type.STRONG;
+    }
+
+    public String toString() {
+        return "strong reference to " + String.valueOf(get());
+    }
+}
diff --git a/src/main/java/org/jboss/modules/ref/WeakReference.java b/src/main/java/org/jboss/modules/ref/WeakReference.java
new file mode 100644
index 0000000..647de80
--- /dev/null
+++ b/src/main/java/org/jboss/modules/ref/WeakReference.java
@@ -0,0 +1,73 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.ref;
+
+import java.lang.ref.ReferenceQueue;
+
+/**
+ * A reapable weak reference with an attachment.  If a {@link Reaper} is given, then it will be used to asynchronously
+ * clean up the referent.
+ *
+ * @param <T> the reference value type
+ * @param <A> the attachment type
+ *
+ * @see java.lang.ref.WeakReference
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public class WeakReference<T, A> extends java.lang.ref.WeakReference<T> implements Reference<T, A>, Reapable<T, A> {
+    private final A attachment;
+    private final Reaper<T, A> reaper;
+
+    public WeakReference(final T referent) {
+        this(referent, null, (Reaper<T, A>) null);
+    }
+
+    public WeakReference(final T referent, final A attachment) {
+        this(referent, attachment, (Reaper<T, A>) null);
+    }
+
+    public WeakReference(final T referent, final A attachment, final ReferenceQueue<? super T> q) {
+        super(referent, q);
+        this.attachment = attachment;
+        reaper = null;
+    }
+
+    public WeakReference(final T referent, final A attachment, final Reaper<T, A> reaper) {
+        super(referent, References.ReaperThread.REAPER_QUEUE);
+        this.attachment = attachment;
+        this.reaper = reaper;
+    }
+
+    public A getAttachment() {
+        return attachment;
+    }
+
+    public Type getType() {
+        return Type.WEAK;
+    }
+
+    public Reaper<T, A> getReaper() {
+        return reaper;
+    }
+
+    public String toString() {
+        return "weak reference to " + String.valueOf(get());
+    }
+}
diff --git a/src/main/java/org/jboss/modules/ref/package-info.java b/src/main/java/org/jboss/modules/ref/package-info.java
new file mode 100644
index 0000000..5855d7f
--- /dev/null
+++ b/src/main/java/org/jboss/modules/ref/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Classes which implement reference types which can be cleaned up automatically by a background thread.  See
+ * {@link Reference} and its subtypes, and {@link Reaper} for more information.
+ */
+package org.jboss.modules.ref;
\ No newline at end of file
diff --git a/src/main/java/org/jboss/modules/security/FactoryPermissionCollection.java b/src/main/java/org/jboss/modules/security/FactoryPermissionCollection.java
new file mode 100644
index 0000000..f6bd528
--- /dev/null
+++ b/src/main/java/org/jboss/modules/security/FactoryPermissionCollection.java
@@ -0,0 +1,84 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.security;
+
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.security.Permissions;
+import java.util.Enumeration;
+
+/**
+ * A permission collection which lazily instantiates the permission objects on first use.  Any
+ * unavailable permission objects will not be granted to the resultant collection.
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public final class FactoryPermissionCollection extends PermissionCollection {
+
+    private static final long serialVersionUID = -2524371701490830970L;
+
+    private final PermissionFactory[] factories;
+    private volatile Permissions assembled;
+
+    /**
+     * Construct a new instance.
+     *
+     * @param factories the factories to use to construct the permission collection
+     */
+    public FactoryPermissionCollection(final PermissionFactory... factories) {
+        this.factories = factories.clone();
+    }
+
+    public void add(final Permission permission) {
+        throw new SecurityException("Read-only permission collection");
+    }
+
+    Permissions getAssembled() {
+        if (assembled == null) {
+            synchronized (this) {
+                if (assembled == null) {
+                    final Permissions assembled = new Permissions();
+                    for (PermissionFactory factory : factories) {
+                        if (factory != null) {
+                            final Permission permission = factory.construct();
+                            if (permission != null) {
+                                assembled.add(permission);
+                            }
+                        }
+                    }
+                    assembled.setReadOnly();
+                    this.assembled = assembled;
+                }
+            }
+        }
+        return assembled;
+    }
+
+    public boolean implies(final Permission permission) {
+        return getAssembled().implies(permission);
+    }
+
+    public Enumeration<Permission> elements() {
+        return getAssembled().elements();
+    }
+
+    Object writeReplace() {
+        return getAssembled();
+    }
+}
diff --git a/src/main/java/org/jboss/modules/security/ImmediatePermissionFactory.java b/src/main/java/org/jboss/modules/security/ImmediatePermissionFactory.java
new file mode 100644
index 0000000..53707f9
--- /dev/null
+++ b/src/main/java/org/jboss/modules/security/ImmediatePermissionFactory.java
@@ -0,0 +1,46 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.security;
+
+import java.security.Permission;
+
+/**
+ * A permission factory which simply holds a permission object.
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public final class ImmediatePermissionFactory implements PermissionFactory {
+    private final Permission permission;
+
+    /**
+     * Construct a new instance.
+     *
+     * @param permission the permission to return
+     */
+    public ImmediatePermissionFactory(final Permission permission) {
+        if (permission == null) {
+            throw new IllegalArgumentException("permission is null");
+        }
+        this.permission = permission;
+    }
+
+    public Permission construct() {
+        return permission;
+    }
+}
diff --git a/src/main/java/org/jboss/modules/security/LoadedPermissionFactory.java b/src/main/java/org/jboss/modules/security/LoadedPermissionFactory.java
new file mode 100644
index 0000000..8e27ca0
--- /dev/null
+++ b/src/main/java/org/jboss/modules/security/LoadedPermissionFactory.java
@@ -0,0 +1,73 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.security;
+
+import java.lang.reflect.Constructor;
+import java.security.Permission;
+
+/**
+ * A permission factory which instantiates a permission with the given class name.
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public final class LoadedPermissionFactory implements PermissionFactory {
+    private final ClassLoader classLoader;
+    private final String className;
+    private final String targetName;
+    private final String permissionActions;
+
+    private volatile Permission instance = UninitializedPermission.INSTANCE;
+
+    /**
+     * Construct a new instance.
+     *
+     * @param classLoader the class loader from which the permission should be loaded
+     * @param className the name of the permission class
+     * @param targetName the name to pass to the permission class constructor or {@code null} for none
+     * @param permissionActions the action list to pass to the permission class constructor or {@code null} for none
+     */
+    public LoadedPermissionFactory(final ClassLoader classLoader, final String className, final String targetName, final String permissionActions) {
+        if (className == null) {
+            throw new IllegalArgumentException("className is null");
+        }
+        this.classLoader = classLoader;
+        this.className = className;
+        this.targetName = targetName;
+        this.permissionActions = permissionActions;
+    }
+
+    public Permission construct() {
+        if (instance != UninitializedPermission.INSTANCE) {
+            return instance;
+        }
+        synchronized (this) {
+            if (instance != UninitializedPermission.INSTANCE) {
+                return instance;
+            }
+            try {
+                final Class<? extends Permission> permissionClass = classLoader.loadClass(className).asSubclass(Permission.class);
+                final Constructor<? extends Permission> constructor = permissionClass.getConstructor(String.class, String.class);
+                return instance = constructor.newInstance(targetName, permissionActions);
+            } catch (Throwable t) {
+                instance = null;
+                return null;
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/jboss/modules/security/ModularPermissionFactory.java b/src/main/java/org/jboss/modules/security/ModularPermissionFactory.java
new file mode 100644
index 0000000..7b184b9
--- /dev/null
+++ b/src/main/java/org/jboss/modules/security/ModularPermissionFactory.java
@@ -0,0 +1,89 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.security;
+
+import java.lang.reflect.Constructor;
+import java.security.Permission;
+import org.jboss.modules.Module;
+import org.jboss.modules.ModuleIdentifier;
+import org.jboss.modules.ModuleLoader;
+import org.jboss.modules._private.ModulesPrivateAccess;
+
+/**
+ * A permission factory which instantiates a permission from a module.
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public final class ModularPermissionFactory implements PermissionFactory {
+    private final ModuleLoader moduleLoader;
+    private final ModuleIdentifier moduleIdentifier;
+    private final String className;
+    private final String targetName;
+    private final String permissionActions;
+
+    private volatile Permission instance = UninitializedPermission.INSTANCE;
+
+    private static final ModulesPrivateAccess access = Module.getPrivateAccess();
+
+    /**
+     * Construct a new instance.
+     *
+     * @param moduleLoader the module loader from which the module is to be loaded
+     * @param moduleIdentifier the module identifier from which the permission is to be loaded
+     * @param className the name of the permission class
+     * @param targetName the name to pass to the permission class constructor or {@code null} for none
+     * @param permissionActions the action list to pass to the permission class constructor or {@code null} for none
+     */
+    public ModularPermissionFactory(final ModuleLoader moduleLoader, final ModuleIdentifier moduleIdentifier, final String className, final String targetName, final String permissionActions) {
+        if (moduleLoader == null) {
+            throw new IllegalArgumentException("moduleLoader is null");
+        }
+        if (moduleIdentifier == null) {
+            throw new IllegalArgumentException("moduleIdentifier is null");
+        }
+        if (className == null) {
+            throw new IllegalArgumentException("className is null");
+        }
+        this.moduleLoader = moduleLoader;
+        this.moduleIdentifier = moduleIdentifier;
+        this.className = className;
+        this.targetName = targetName;
+        this.permissionActions = permissionActions;
+    }
+
+    public Permission construct() {
+        if (instance != UninitializedPermission.INSTANCE) {
+            return instance;
+        }
+        synchronized (this) {
+            if (instance != UninitializedPermission.INSTANCE) {
+                return instance;
+            }
+            try {
+                final Module module = moduleLoader.loadModule(moduleIdentifier);
+                final Class<? extends Permission> permissionClass = access.getClassLoaderOf(module).loadClass(className, true).asSubclass(Permission.class);
+                final Constructor<? extends Permission> constructor = permissionClass.getConstructor(String.class, String.class);
+                return instance = constructor.newInstance(targetName, permissionActions);
+            } catch (Throwable t) {
+                instance = null;
+                return null;
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/jboss/modules/security/PermissionFactory.java b/src/main/java/org/jboss/modules/security/PermissionFactory.java
new file mode 100644
index 0000000..df98289
--- /dev/null
+++ b/src/main/java/org/jboss/modules/security/PermissionFactory.java
@@ -0,0 +1,36 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.security;
+
+import java.security.Permission;
+
+/**
+ * A factory for {@link Permission} objects.
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public interface PermissionFactory {
+
+    /**
+     * Construct a new instance of the permission.  The instance may be cached.
+     *
+     * @return the permission
+     */
+    Permission construct();
+}
diff --git a/src/main/java/org/jboss/modules/security/UninitializedPermission.java b/src/main/java/org/jboss/modules/security/UninitializedPermission.java
new file mode 100644
index 0000000..e8c12ba
--- /dev/null
+++ b/src/main/java/org/jboss/modules/security/UninitializedPermission.java
@@ -0,0 +1,48 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.security;
+
+import java.security.Permission;
+
+final class UninitializedPermission extends Permission {
+
+    private static final long serialVersionUID = 468335190774708422L;
+
+    static final UninitializedPermission INSTANCE = new UninitializedPermission();
+
+    private UninitializedPermission() {
+        super(null);
+    }
+
+    public boolean implies(final Permission permission) {
+        return false;
+    }
+
+    public boolean equals(final Object obj) {
+        return false;
+    }
+
+    public int hashCode() {
+        return 0;
+    }
+
+    public String getActions() {
+        return null;
+    }
+}
diff --git a/src/main/java/org/jboss/modules/xml/MXParser.java b/src/main/java/org/jboss/modules/xml/MXParser.java
new file mode 100644
index 0000000..419b328
--- /dev/null
+++ b/src/main/java/org/jboss/modules/xml/MXParser.java
@@ -0,0 +1,3305 @@
+/* -*-             c-basic-offset: 4; indent-tabs-mode: nil; -*-  //------100-columns-wide------>|*/
+/*
+ * Copyright (c) 2003 Extreme! Lab, Indiana University. All rights reserved.
+ *
+ * This software is open source. See the bottom of this file for the license.
+ *
+ * $Id: MXParser.java,v 1.52 2006/11/09 18:29:37 aslom Exp $
+ */
+
+package org.jboss.modules.xml;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.UnsupportedEncodingException;
+
+//TODO best handling of interning issues
+//   have isAllNewStringInterned ???
+
+//TODO handling surrogate pairs: http://www.unicode.org/unicode/faq/utf_bom.html#6
+
+//TODO review code for use of bufAbsoluteStart when keeping pos between next()/fillBuf()
+
+/**
+ * Absolutely minimal implementation of XMLPULL V1 API
+ *
+ * @author <a href="http://www.extreme.indiana.edu/~aslom/">Aleksander Slominski</a>
+ */
+
+public class MXParser
+    implements XmlPullParser
+{
+    //NOTE: no interning of those strings --> by Java lang spec they MUST be already interned
+    protected final static String XML_URI = "http://www.w3.org/XML/1998/namespace";
+    protected final static String XMLNS_URI = "http://www.w3.org/2000/xmlns/";
+    protected final static String FEATURE_XML_ROUNDTRIP=
+        //"http://xmlpull.org/v1/doc/features.html#xml-roundtrip";
+        "http://xmlpull.org/v1/doc/features.html#xml-roundtrip";
+    protected final static String FEATURE_NAMES_INTERNED =
+        "http://xmlpull.org/v1/doc/features.html#names-interned";
+    protected final static String PROPERTY_XMLDECL_VERSION =
+        "http://xmlpull.org/v1/doc/properties.html#xmldecl-version";
+    protected final static String PROPERTY_XMLDECL_STANDALONE =
+        "http://xmlpull.org/v1/doc/properties.html#xmldecl-standalone";
+    protected final static String PROPERTY_XMLDECL_CONTENT =
+        "http://xmlpull.org/v1/doc/properties.html#xmldecl-content";
+    protected final static String PROPERTY_LOCATION =
+        "http://xmlpull.org/v1/doc/properties.html#location";
+
+    /**
+     * Implementation notice:
+     * the is instance variable that controls if newString() is interning.
+     * <p><b>NOTE:</b> newStringIntern <b>always</b> returns interned strings
+     * and newString MAY return interned String depending on this variable.
+     * <p><b>NOTE:</b> by default in this minimal implementation it is false!
+     */
+    protected boolean allStringsInterned;
+
+    protected void resetStringCache() {
+        //System.out.println("resetStringCache() minimum called");
+    }
+
+    protected String newString(char[] cbuf, int off, int len) {
+        return new String(cbuf, off, len);
+    }
+
+    protected String newStringIntern(char[] cbuf, int off, int len) {
+        return (new String(cbuf, off, len)).intern();
+    }
+
+    private static final boolean TRACE_SIZING = false;
+
+    // NOTE: features are not resettable and typically defaults to false ...
+    protected boolean processNamespaces;
+    protected boolean roundtripSupported;
+
+    // global parser state
+    protected String location;
+    protected int lineNumber;
+    protected int columnNumber;
+    protected boolean seenRoot;
+    protected boolean reachedEnd;
+    protected int eventType;
+    protected boolean emptyElementTag;
+    // element stack
+    protected int depth;
+    protected char[] elRawName[];
+    protected int elRawNameEnd[];
+    protected int elRawNameLine[];
+
+    protected String elName[];
+    protected String elPrefix[];
+    protected String elUri[];
+    //protected String elValue[];
+    protected int elNamespaceCount[];
+
+
+
+    /**
+     * Make sure that we have enough space to keep element stack if passed size.
+     * It will always create one additional slot then current depth
+     */
+    protected void ensureElementsCapacity() {
+        final int elStackSize = elName != null ? elName.length : 0;
+        if( (depth + 1) >= elStackSize) {
+            // we add at least one extra slot ...
+            final int newSize = (depth >= 7 ? 2 * depth : 8) + 2; // = lucky 7 + 1 //25
+            if(TRACE_SIZING) {
+                System.err.println("TRACE_SIZING elStackSize "+elStackSize+" ==> "+newSize);
+            }
+            final boolean needsCopying = elStackSize > 0;
+            String[] arr = null;
+            // reuse arr local variable slot
+            arr = new String[newSize];
+            if(needsCopying) System.arraycopy(elName, 0, arr, 0, elStackSize);
+            elName = arr;
+            arr = new String[newSize];
+            if(needsCopying) System.arraycopy(elPrefix, 0, arr, 0, elStackSize);
+            elPrefix = arr;
+            arr = new String[newSize];
+            if(needsCopying) System.arraycopy(elUri, 0, arr, 0, elStackSize);
+            elUri = arr;
+
+            int[] iarr = new int[newSize];
+            if(needsCopying) {
+                System.arraycopy(elNamespaceCount, 0, iarr, 0, elStackSize);
+            } else {
+                // special initialization
+                iarr[0] = 0;
+            }
+            elNamespaceCount = iarr;
+
+            //TODO: avoid using element raw name ...
+            iarr = new int[newSize];
+            if(needsCopying) {
+                System.arraycopy(elRawNameEnd, 0, iarr, 0, elStackSize);
+            }
+            elRawNameEnd = iarr;
+
+            iarr = new int[newSize];
+            if(needsCopying) {
+                System.arraycopy(elRawNameLine, 0, iarr, 0, elStackSize);
+            }
+            elRawNameLine = iarr;
+
+            final char[][] carr = new char[newSize][];
+            if(needsCopying) {
+                System.arraycopy(elRawName, 0, carr, 0, elStackSize);
+            }
+            elRawName = carr;
+            //            arr = new String[newSize];
+            //            if(needsCopying) System.arraycopy(elLocalName, 0, arr, 0, elStackSize);
+            //            elLocalName = arr;
+            //            arr = new String[newSize];
+            //            if(needsCopying) System.arraycopy(elDefaultNs, 0, arr, 0, elStackSize);
+            //            elDefaultNs = arr;
+            //            int[] iarr = new int[newSize];
+            //            if(needsCopying) System.arraycopy(elNsStackPos, 0, iarr, 0, elStackSize);
+            //            for (int i = elStackSize; i < iarr.length; i++)
+            //            {
+            //                iarr[i] = (i > 0) ? -1 : 0;
+            //            }
+            //            elNsStackPos = iarr;
+            //assert depth < elName.length;
+        }
+    }
+
+
+
+    // attribute stack
+    protected int attributeCount;
+    protected String attributeName[];
+    protected int attributeNameHash[];
+    //protected int attributeNameStart[];
+    //protected int attributeNameEnd[];
+    protected String attributePrefix[];
+    protected String attributeUri[];
+    protected String attributeValue[];
+    //protected int attributeValueStart[];
+    //protected int attributeValueEnd[];
+
+
+    /**
+     * Make sure that in attributes temporary array is enough space.
+     */
+    protected  void ensureAttributesCapacity(int size) {
+        final int attrPosSize = attributeName != null ? attributeName.length : 0;
+        if(size >= attrPosSize) {
+            final int newSize = size > 7 ? 2 * size : 8; // = lucky 7 + 1 //25
+            if(TRACE_SIZING) {
+                System.err.println("TRACE_SIZING attrPosSize "+attrPosSize+" ==> "+newSize);
+            }
+            final boolean needsCopying = attrPosSize > 0;
+            String[] arr = null;
+
+            arr = new String[newSize];
+            if(needsCopying) System.arraycopy(attributeName, 0, arr, 0, attrPosSize);
+            attributeName = arr;
+
+            arr = new String[newSize];
+            if(needsCopying) System.arraycopy(attributePrefix, 0, arr, 0, attrPosSize);
+            attributePrefix = arr;
+
+            arr = new String[newSize];
+            if(needsCopying) System.arraycopy(attributeUri, 0, arr, 0, attrPosSize);
+            attributeUri = arr;
+
+            arr = new String[newSize];
+            if(needsCopying) System.arraycopy(attributeValue, 0, arr, 0, attrPosSize);
+            attributeValue = arr;
+
+            if( ! allStringsInterned ) {
+                final int[] iarr = new int[newSize];
+                if(needsCopying) System.arraycopy(attributeNameHash, 0, iarr, 0, attrPosSize);
+                attributeNameHash = iarr;
+            }
+
+            arr = null;
+            // //assert attrUri.length > size
+        }
+    }
+
+    // namespace stack
+    protected int namespaceEnd;
+    protected String namespacePrefix[];
+    protected int namespacePrefixHash[];
+    protected String namespaceUri[];
+
+    protected void ensureNamespacesCapacity(int size) {
+        final int namespaceSize = namespacePrefix != null ? namespacePrefix.length : 0;
+        if(size >= namespaceSize) {
+            final int newSize = size > 7 ? 2 * size : 8; // = lucky 7 + 1 //25
+            if(TRACE_SIZING) {
+                System.err.println("TRACE_SIZING namespaceSize "+namespaceSize+" ==> "+newSize);
+            }
+            final String[] newNamespacePrefix = new String[newSize];
+            final String[] newNamespaceUri = new String[newSize];
+            if(namespacePrefix != null) {
+                System.arraycopy(
+                    namespacePrefix, 0, newNamespacePrefix, 0, namespaceEnd);
+                System.arraycopy(
+                    namespaceUri, 0, newNamespaceUri, 0, namespaceEnd);
+            }
+            namespacePrefix = newNamespacePrefix;
+            namespaceUri = newNamespaceUri;
+
+
+            if( ! allStringsInterned ) {
+                final int[] newNamespacePrefixHash = new int[newSize];
+                if(namespacePrefixHash != null) {
+                    System.arraycopy(
+                        namespacePrefixHash, 0, newNamespacePrefixHash, 0, namespaceEnd);
+                }
+                namespacePrefixHash = newNamespacePrefixHash;
+            }
+            //prefixesSize = newSize;
+            // //assert nsPrefixes.length > size && nsPrefixes.length == newSize
+        }
+    }
+
+    /**
+     * simplistic implementation of hash function that has <b>constant</b>
+     * time to compute - so it also means diminishing hash quality for long strings
+     * but for XML parsing it should be good enough ...
+     */
+    protected static final int fastHash( char ch[], int off, int len ) {
+        if(len == 0) return 0;
+        //assert len >0
+        int hash = ch[off]; // hash at beginning
+        //try {
+        hash = (hash << 7) + ch[ off +  len - 1 ]; // hash at the end
+        //} catch(ArrayIndexOutOfBoundsException aie) {
+        //    aie.printStackTrace(); //should never happen ...
+        //    throw new RuntimeException("this is violation of pre-condition");
+        //}
+        if(len > 16) hash = (hash << 7) + ch[ off + (len / 4)];  // 1/4 from beginning
+        if(len > 8)  hash = (hash << 7) + ch[ off + (len / 2)];  // 1/2 of string size ...
+        // notice that hash is at most done 3 times <<7 so shifted by 21 bits 8 bit value
+        // so max result == 29 bits so it is quite just below 31 bits for long (2^32) ...
+        //assert hash >= 0;
+        return  hash;
+    }
+
+    // entity replacement stack
+    protected int entityEnd;
+
+    protected String entityName[];
+    protected char[] entityNameBuf[];
+    protected String entityReplacement[];
+    protected char[] entityReplacementBuf[];
+
+    protected int entityNameHash[];
+
+    protected void ensureEntityCapacity() {
+        final int entitySize = entityReplacementBuf != null ? entityReplacementBuf.length : 0;
+        if(entityEnd >= entitySize) {
+            final int newSize = entityEnd > 7 ? 2 * entityEnd : 8; // = lucky 7 + 1 //25
+            if(TRACE_SIZING) {
+                System.err.println("TRACE_SIZING entitySize "+entitySize+" ==> "+newSize);
+            }
+            final String[] newEntityName = new String[newSize];
+            final char[] newEntityNameBuf[] = new char[newSize][];
+            final String[] newEntityReplacement = new String[newSize];
+            final char[] newEntityReplacementBuf[] = new char[newSize][];
+            if(entityName != null) {
+                System.arraycopy(entityName, 0, newEntityName, 0, entityEnd);
+                System.arraycopy(entityNameBuf, 0, newEntityNameBuf, 0, entityEnd);
+                System.arraycopy(entityReplacement, 0, newEntityReplacement, 0, entityEnd);
+                System.arraycopy(entityReplacementBuf, 0, newEntityReplacementBuf, 0, entityEnd);
+            }
+            entityName = newEntityName;
+            entityNameBuf = newEntityNameBuf;
+            entityReplacement = newEntityReplacement;
+            entityReplacementBuf = newEntityReplacementBuf;
+
+            if( ! allStringsInterned ) {
+                final int[] newEntityNameHash = new int[newSize];
+                if(entityNameHash != null) {
+                    System.arraycopy(entityNameHash, 0, newEntityNameHash, 0, entityEnd);
+                }
+                entityNameHash = newEntityNameHash;
+            }
+        }
+    }
+
+    // input buffer management
+    protected static final int READ_CHUNK_SIZE = 8*1024; //max data chars in one read() call
+    protected Reader reader;
+    protected String inputEncoding;
+    protected InputStream inputStream;
+
+
+    protected int bufLoadFactor = 95;  // 99%
+    //protected int bufHardLimit;  // only matters when expanding
+
+    protected char buf[] = new char[
+        Runtime.getRuntime().freeMemory() > 1000000L ? READ_CHUNK_SIZE : 256 ];
+    protected int bufSoftLimit = ( bufLoadFactor * buf.length ) /100; // desirable size of buffer
+    protected boolean preventBufferCompaction;
+
+    protected int bufAbsoluteStart; // this is buf
+    protected int bufStart;
+    protected int bufEnd;
+    protected int pos;
+    protected int posStart;
+    protected int posEnd;
+
+    protected char pc[] = new char[
+        Runtime.getRuntime().freeMemory() > 1000000L ? READ_CHUNK_SIZE : 64 ];
+    protected int pcStart;
+    protected int pcEnd;
+
+
+    // parsing state
+    //protected boolean needsMore;
+    //protected boolean seenMarkup;
+    protected boolean usePC;
+
+
+    protected boolean seenStartTag;
+    protected boolean seenEndTag;
+    protected boolean pastEndTag;
+    protected boolean seenAmpersand;
+    protected boolean seenMarkup;
+    protected boolean seenDocdecl;
+
+    // transient variable set during each call to next/Token()
+    protected boolean tokenize;
+    protected String text;
+    protected String entityRefName;
+
+    protected String xmlDeclVersion;
+    protected Boolean xmlDeclStandalone;
+    protected String xmlDeclContent;
+
+    protected void reset() {
+        //System.out.println("reset() called");
+        location = null;
+        lineNumber = 1;
+        columnNumber = 0;
+        seenRoot = false;
+        reachedEnd = false;
+        eventType = START_DOCUMENT;
+        emptyElementTag = false;
+
+        depth = 0;
+
+        attributeCount = 0;
+
+        namespaceEnd = 0;
+
+        entityEnd = 0;
+
+        reader = null;
+        inputEncoding = null;
+
+        preventBufferCompaction = false;
+        bufAbsoluteStart = 0;
+        bufEnd = bufStart = 0;
+        pos = posStart = posEnd = 0;
+
+        pcEnd = pcStart = 0;
+
+        usePC = false;
+
+        seenStartTag = false;
+        seenEndTag = false;
+        pastEndTag = false;
+        seenAmpersand = false;
+        seenMarkup = false;
+        seenDocdecl = false;
+
+        xmlDeclVersion = null;
+        xmlDeclStandalone = null;
+        xmlDeclContent = null;
+
+        resetStringCache();
+    }
+
+    public MXParser() {
+    }
+
+
+    /**
+     * Method setFeature
+     *
+     * @param    name                a  String
+     * @param    state               a  boolean
+     *
+     * @throws   XmlPullParserException
+     *
+     */
+    public void setFeature(String name,
+                           boolean state) throws XmlPullParserException
+    {
+        if(name == null) throw new IllegalArgumentException("feature name should not be null");
+        if(FEATURE_PROCESS_NAMESPACES.equals(name)) {
+            if(eventType != START_DOCUMENT) throw new XmlPullParserException(
+                    "namespace processing feature can only be changed before parsing", this, null);
+            processNamespaces = state;
+            //        } else if(FEATURE_REPORT_NAMESPACE_ATTRIBUTES.equals(name)) {
+            //      if(type != START_DOCUMENT) throw new XmlPullParserException(
+            //              "namespace reporting feature can only be changed before parsing", this, null);
+            //            reportNsAttribs = state;
+        } else if(FEATURE_NAMES_INTERNED.equals(name)) {
+            if(state != false) {
+                throw new XmlPullParserException(
+                    "interning names in this implementation is not supported");
+            }
+        } else if(FEATURE_PROCESS_DOCDECL.equals(name)) {
+            if(state != false) {
+                throw new XmlPullParserException(
+                    "processing DOCDECL is not supported");
+            }
+            //} else if(REPORT_DOCDECL.equals(name)) {
+            //    paramNotifyDoctype = state;
+        } else if(FEATURE_XML_ROUNDTRIP.equals(name)) {
+            //if(state == false) {
+            //    throw new XmlPullParserException(
+            //        "roundtrip feature can not be switched off");
+            //}
+            roundtripSupported = state;
+        } else {
+            throw new XmlPullParserException("unsupported feature "+name);
+        }
+    }
+
+    /** Unknown properties are <strong>always</strong> returned as false */
+    public boolean getFeature(String name)
+    {
+        if(name == null) throw new IllegalArgumentException("feature name should not be null");
+        if(FEATURE_PROCESS_NAMESPACES.equals(name)) {
+            return processNamespaces;
+            //        } else if(FEATURE_REPORT_NAMESPACE_ATTRIBUTES.equals(name)) {
+            //            return reportNsAttribs;
+        } else if(FEATURE_NAMES_INTERNED.equals(name)) {
+            return false;
+        } else if(FEATURE_PROCESS_DOCDECL.equals(name)) {
+            return false;
+            //} else if(REPORT_DOCDECL.equals(name)) {
+            //    return paramNotifyDoctype;
+        } else if(FEATURE_XML_ROUNDTRIP.equals(name)) {
+            //return true;
+            return roundtripSupported;
+        }
+        return false;
+    }
+
+    public void setProperty(String name,
+                            Object value)
+        throws XmlPullParserException
+    {
+        if(PROPERTY_LOCATION.equals(name)) {
+            location = (String) value;
+        } else {
+            throw new XmlPullParserException("unsupported property: '"+name+"'");
+        }
+    }
+
+
+    public Object getProperty(String name)
+    {
+        if(name == null) throw new IllegalArgumentException("property name should not be null");
+        if(PROPERTY_XMLDECL_VERSION.equals(name)) {
+            return xmlDeclVersion;
+        } else if(PROPERTY_XMLDECL_STANDALONE.equals(name)) {
+            return xmlDeclStandalone;
+        } else if(PROPERTY_XMLDECL_CONTENT.equals(name)) {
+            return xmlDeclContent;
+        } else if(PROPERTY_LOCATION.equals(name)) {
+            return location;
+        }
+        return null;
+    }
+
+
+    public void setInput(Reader in) throws XmlPullParserException
+    {
+        reset();
+        reader = in;
+    }
+
+    public void setInput(InputStream inputStream, String inputEncoding)
+        throws XmlPullParserException
+    {
+        if(inputStream == null) {
+            throw new IllegalArgumentException("input stream can not be null");
+        }
+        this.inputStream = inputStream;
+        Reader reader;
+        //if(inputEncoding != null) {
+        try {
+            if(inputEncoding != null) {
+                reader = new InputStreamReader(inputStream, inputEncoding);
+            } else {
+                //by default use UTF-8 (InputStreamReader(inputStream)) would use OS default ...
+                reader = new InputStreamReader(inputStream, "UTF-8");
+            }
+        } catch (UnsupportedEncodingException une) {
+            throw new XmlPullParserException(
+                "could not create reader for encoding "+inputEncoding+" : "+une, this, une);
+        }
+        //} else {
+        //    reader = new InputStreamReader(inputStream);
+        //}
+        setInput(reader);
+        //must be here as reest() was called in setInput() and has set this.inputEncoding to null ...
+        this.inputEncoding = inputEncoding;
+    }
+
+    public String getInputEncoding() {
+        return inputEncoding;
+    }
+
+    public void defineEntityReplacementText(String entityName,
+                                            String replacementText)
+        throws XmlPullParserException
+    {
+        //      throw new XmlPullParserException("not allowed");
+
+        //protected char[] entityReplacement[];
+        ensureEntityCapacity();
+
+        // this is to make sure that if interning works we will take advantage of it ...
+        this.entityName[entityEnd] = newString(entityName.toCharArray(), 0, entityName.length());
+        entityNameBuf[entityEnd] = entityName.toCharArray();
+
+        entityReplacement[entityEnd] = replacementText;
+        entityReplacementBuf[entityEnd] = replacementText.toCharArray();
+        if(!allStringsInterned) {
+            entityNameHash[ entityEnd ] =
+                fastHash(entityNameBuf[entityEnd], 0, entityNameBuf[entityEnd].length);
+        }
+        ++entityEnd;
+        //TODO disallow < or & in entity replacement text (or ]]>???)
+        // TOOD keepEntityNormalizedForAttributeValue cached as well ...
+    }
+
+    public int getNamespaceCount(int depth)
+        throws XmlPullParserException
+    {
+        if(processNamespaces == false || depth == 0) {
+            return 0;
+        }
+        //int maxDepth = eventType == END_TAG ? this.depth + 1 : this.depth;
+        //if(depth < 0 || depth > maxDepth) throw new IllegalArgumentException(
+        if(depth < 0 || depth > this.depth) throw new IllegalArgumentException(
+                "allowed namespace depth 0.."+this.depth+" not "+depth);
+        return elNamespaceCount[ depth ];
+    }
+
+    public String getNamespacePrefix(int pos)
+        throws XmlPullParserException
+    {
+
+        //int end = eventType == END_TAG ? elNamespaceCount[ depth + 1 ] : namespaceEnd;
+        //if(pos < end) {
+        if(pos < namespaceEnd) {
+            return namespacePrefix[ pos ];
+        } else {
+            throw new XmlPullParserException(
+                "position "+pos+" exceeded number of available namespaces "+namespaceEnd);
+        }
+    }
+
+    public String getNamespaceUri(int pos) throws XmlPullParserException
+    {
+        //int end = eventType == END_TAG ? elNamespaceCount[ depth + 1 ] : namespaceEnd;
+        //if(pos < end) {
+        if(pos < namespaceEnd) {
+            return namespaceUri[ pos ];
+        } else {
+            throw new XmlPullParserException(
+                "position "+pos+" exceeded number of available namespaces "+namespaceEnd);
+        }
+    }
+
+    public String getNamespace( String prefix )
+        //throws XmlPullParserException
+    {
+        //int count = namespaceCount[ depth ];
+        if(prefix != null) {
+            for( int i = namespaceEnd -1; i >= 0; i--) {
+                if( prefix.equals( namespacePrefix[ i ] ) ) {
+                    return namespaceUri[ i ];
+                }
+            }
+            if("xml".equals( prefix )) {
+                return XML_URI;
+            } else if("xmlns".equals( prefix )) {
+                return XMLNS_URI;
+            }
+        } else {
+            for( int i = namespaceEnd -1; i >= 0; i--) {
+                if( namespacePrefix[ i ]  == null) { //"") { //null ) { //TODO check FIXME Alek
+                    return namespaceUri[ i ];
+                }
+            }
+
+        }
+        return null;
+    }
+
+
+    public int getDepth()
+    {
+        return depth;
+    }
+
+
+    private static int findFragment(int bufMinPos, char[] b, int start, int end) {
+        //System.err.println("bufStart="+bufStart+" b="+printable(new String(b, start, end - start))+" start="+start+" end="+end);
+        if(start < bufMinPos) {
+            start = bufMinPos;
+            if(start > end) start = end;
+            return start;
+        }
+        if(end - start > 65) {
+            start = end - 10; // try to find good location
+        }
+        int i = start + 1;
+        while(--i > bufMinPos) {
+            if((end - i) > 65) break;
+            final char c = b[i];
+            if(c == '<' && (start - i) > 10) break;
+        }
+        return i;
+    }
+
+
+    /**
+     * Return string describing current position of parsers as
+     * text 'STATE [seen %s...] @line:column'.
+     */
+    public String getPositionDescription ()
+    {
+        String fragment = null;
+        if(posStart <= pos) {
+            final int start = findFragment(0, buf, posStart, pos);
+            //System.err.println("start="+start);
+            if(start < pos) {
+                fragment = new String(buf, start, pos - start);
+            }
+            if(bufAbsoluteStart > 0 || start > 0) fragment = "..." + fragment;
+        }
+        //        return " at line "+tokenizerPosRow
+        //            +" and column "+(tokenizerPosCol-1)
+        //            +(fragment != null ? " seen "+printable(fragment)+"..." : "");
+        return " "+TYPES[ eventType ] +
+            (fragment != null ? " seen "+printable(fragment)+"..." : "")
+            +" "+(location != null ? location : "")
+            +"@"+getLineNumber()+":"+getColumnNumber();
+    }
+
+    public int getLineNumber()
+    {
+        return lineNumber;
+    }
+
+    public int getColumnNumber()
+    {
+        return columnNumber;
+    }
+
+
+    public boolean isWhitespace() throws XmlPullParserException
+    {
+        if(eventType == TEXT || eventType == CDSECT) {
+            if(usePC) {
+                for (int i = pcStart; i <pcEnd; i++)
+                {
+                    if(!isS(pc[ i ])) return false;
+                }
+                return true;
+            } else {
+                for (int i = posStart; i <posEnd; i++)
+                {
+                    if(!isS(buf[ i ])) return false;
+                }
+                return true;
+            }
+        } else if(eventType == IGNORABLE_WHITESPACE) {
+            return true;
+        }
+        throw new XmlPullParserException("no content available to check for white spaces");
+    }
+
+    public String getText()
+    {
+        if(eventType == START_DOCUMENT || eventType == END_DOCUMENT) {
+            //throw new XmlPullParserException("no content available to read");
+            //      if(roundtripSupported) {
+            //          text = new String(buf, posStart, posEnd - posStart);
+            //      } else {
+            return null;
+            //      }
+        } else if(eventType == ENTITY_REF) {
+            return text;
+        }
+        if(text == null) {
+            if(!usePC || eventType == START_TAG || eventType == END_TAG) {
+                text = new String(buf, posStart, posEnd - posStart);
+            } else {
+                text = new String(pc, pcStart, pcEnd - pcStart);
+            }
+        }
+        return text;
+    }
+
+    public char[] getTextCharacters(int [] holderForStartAndLength)
+    {
+        if( eventType == TEXT ) {
+            if(usePC) {
+                holderForStartAndLength[0] = pcStart;
+                holderForStartAndLength[1] = pcEnd - pcStart;
+                return pc;
+            } else {
+                holderForStartAndLength[0] = posStart;
+                holderForStartAndLength[1] = posEnd - posStart;
+                return buf;
+
+            }
+        } else if( eventType == START_TAG
+                      || eventType == END_TAG
+                      || eventType == CDSECT
+                      || eventType == COMMENT
+                      || eventType == ENTITY_REF
+                      || eventType == PROCESSING_INSTRUCTION
+                      || eventType == IGNORABLE_WHITESPACE
+                      || eventType == DOCDECL)
+        {
+            holderForStartAndLength[0] = posStart;
+            holderForStartAndLength[1] = posEnd - posStart;
+            return buf;
+        } else if(eventType == START_DOCUMENT
+                      || eventType == END_DOCUMENT) {
+            //throw new XmlPullParserException("no content available to read");
+            holderForStartAndLength[0] = holderForStartAndLength[1] = -1;
+            return null;
+        } else {
+            throw new IllegalArgumentException("unknown text eventType: "+eventType);
+        }
+        //      String s = getText();
+        //      char[] cb = null;
+        //      if(s!= null) {
+        //          cb = s.toCharArray();
+        //          holderForStartAndLength[0] = 0;
+        //          holderForStartAndLength[1] = s.length();
+        //      } else {
+        //      }
+        //      return cb;
+    }
+
+    public String getNamespace()
+    {
+        if(eventType == START_TAG) {
+            //return processNamespaces ? elUri[ depth - 1 ] : NO_NAMESPACE;
+            return processNamespaces ? elUri[ depth  ] : NO_NAMESPACE;
+        } else if(eventType == END_TAG) {
+            return processNamespaces ? elUri[ depth ] : NO_NAMESPACE;
+        }
+        return null;
+        //        String prefix = elPrefix[ maxDepth ];
+        //        if(prefix != null) {
+        //            for( int i = namespaceEnd -1; i >= 0; i--) {
+        //                if( prefix.equals( namespacePrefix[ i ] ) ) {
+        //                    return namespaceUri[ i ];
+        //                }
+        //            }
+        //        } else {
+        //            for( int i = namespaceEnd -1; i >= 0; i--) {
+        //                if( namespacePrefix[ i ]  == null ) {
+        //                    return namespaceUri[ i ];
+        //                }
+        //            }
+        //
+        //        }
+        //        return "";
+    }
+
+    public String getName()
+    {
+        if(eventType == START_TAG) {
+            //return elName[ depth - 1 ] ;
+            return elName[ depth ] ;
+        } else if(eventType == END_TAG) {
+            return elName[ depth ] ;
+        } else if(eventType == ENTITY_REF) {
+            if(entityRefName == null) {
+                entityRefName = newString(buf, posStart, posEnd - posStart);
+            }
+            return entityRefName;
+        } else {
+            return null;
+        }
+    }
+
+    public String getPrefix()
+    {
+        if(eventType == START_TAG) {
+            //return elPrefix[ depth - 1 ] ;
+            return elPrefix[ depth ] ;
+        } else if(eventType == END_TAG) {
+            return elPrefix[ depth ] ;
+        }
+        return null;
+        //        if(eventType != START_TAG && eventType != END_TAG) return null;
+        //        int maxDepth = eventType == END_TAG ? depth : depth - 1;
+        //        return elPrefix[ maxDepth ];
+    }
+
+
+    public boolean isEmptyElementTag() throws XmlPullParserException
+    {
+        if(eventType != START_TAG) throw new XmlPullParserException(
+                "parser must be on START_TAG to check for empty element", this, null);
+        return emptyElementTag;
+    }
+
+    public int getAttributeCount()
+    {
+        if(eventType != START_TAG) return -1;
+        return attributeCount;
+    }
+
+    public String getAttributeNamespace(int index)
+    {
+        if(eventType != START_TAG) throw new IndexOutOfBoundsException(
+                "only START_TAG can have attributes");
+        if(processNamespaces == false) return NO_NAMESPACE;
+        if(index < 0 || index >= attributeCount) throw new IndexOutOfBoundsException(
+                "attribute position must be 0.."+(attributeCount-1)+" and not "+index);
+        return attributeUri[ index ];
+    }
+
+    public String getAttributeName(int index)
+    {
+        if(eventType != START_TAG) throw new IndexOutOfBoundsException(
+                "only START_TAG can have attributes");
+        if(index < 0 || index >= attributeCount) throw new IndexOutOfBoundsException(
+                "attribute position must be 0.."+(attributeCount-1)+" and not "+index);
+        return attributeName[ index ];
+    }
+
+    public String getAttributePrefix(int index)
+    {
+        if(eventType != START_TAG) throw new IndexOutOfBoundsException(
+                "only START_TAG can have attributes");
+        if(processNamespaces == false) return null;
+        if(index < 0 || index >= attributeCount) throw new IndexOutOfBoundsException(
+                "attribute position must be 0.."+(attributeCount-1)+" and not "+index);
+        return attributePrefix[ index ];
+    }
+
+    public String getAttributeType(int index) {
+        if(eventType != START_TAG) throw new IndexOutOfBoundsException(
+                "only START_TAG can have attributes");
+        if(index < 0 || index >= attributeCount) throw new IndexOutOfBoundsException(
+                "attribute position must be 0.."+(attributeCount-1)+" and not "+index);
+        return "CDATA";
+    }
+
+    public boolean isAttributeDefault(int index) {
+        if(eventType != START_TAG) throw new IndexOutOfBoundsException(
+                "only START_TAG can have attributes");
+        if(index < 0 || index >= attributeCount) throw new IndexOutOfBoundsException(
+                "attribute position must be 0.."+(attributeCount-1)+" and not "+index);
+        return false;
+    }
+
+    public String getAttributeValue(int index)
+    {
+        if(eventType != START_TAG) throw new IndexOutOfBoundsException(
+                "only START_TAG can have attributes");
+        if(index < 0 || index >= attributeCount) throw new IndexOutOfBoundsException(
+                "attribute position must be 0.."+(attributeCount-1)+" and not "+index);
+        return attributeValue[ index ];
+    }
+
+    public String getAttributeValue(String namespace,
+                                    String name)
+    {
+        if(eventType != START_TAG) throw new IndexOutOfBoundsException(
+                "only START_TAG can have attributes"+getPositionDescription());
+        if(name == null) {
+            throw new IllegalArgumentException("attribute name can not be null");
+        }
+        // TODO make check if namespace is interned!!! etc. for names!!!
+        if(processNamespaces) {
+            if(namespace == null) {
+                namespace = "";
+            }
+
+            for(int i = 0; i < attributeCount; ++i) {
+                if((namespace == attributeUri[ i ] ||
+                        namespace.equals(attributeUri[ i ]) )
+                       //(namespace != null && namespace.equals(attributeUri[ i ]))
+                       // taking advantage of String.intern()
+                       && name.equals(attributeName[ i ]) )
+                {
+                    return attributeValue[i];
+                }
+            }
+        } else {
+            if(namespace != null && namespace.length() == 0) {
+                namespace = null;
+            }
+            if(namespace != null) throw new IllegalArgumentException(
+                    "when namespaces processing is disabled attribute namespace must be null");
+            for(int i = 0; i < attributeCount; ++i) {
+                if(name.equals(attributeName[i]))
+                {
+                    return attributeValue[i];
+                }
+            }
+        }
+        return null;
+    }
+
+
+    public int getEventType()
+    {
+        return eventType;
+    }
+
+    public void require(int type, String namespace, String name)
+        throws XmlPullParserException, IOException
+    {
+        if(processNamespaces == false && namespace != null) {
+            throw new XmlPullParserException(
+                "processing namespaces must be enabled on parser (or factory)"+
+                    " to have possible namespaces declared on elements"
+                    +(" (position:"+ getPositionDescription())+")");
+        }
+        if (type != getEventType()
+                || (namespace != null && !namespace.equals (getNamespace()))
+                || (name != null && !name.equals (getName ())) )
+        {
+            throw new XmlPullParserException (
+                "expected event "+TYPES[ type ]
+                    +(name != null ? " with name '"+name+"'" : "")
+                    +(namespace != null && name != null ? " and" : "")
+                    +(namespace != null ? " with namespace '"+namespace+"'" : "")
+                    +" but got"
+                    +(type != getEventType() ? " "+TYPES[ getEventType() ] : "")
+                    +(name != null && getName() != null && !name.equals (getName ())
+                          ? " name '"+getName()+"'" : "")
+                    +(namespace != null && name != null
+                          && getName() != null && !name.equals (getName ())
+                          && getNamespace() != null && !namespace.equals (getNamespace())
+                          ? " and" : "")
+                    +(namespace != null && getNamespace() != null && !namespace.equals (getNamespace())
+                          ? " namespace '"+getNamespace()+"'" : "")
+                    +(" (position:"+ getPositionDescription())+")");
+        }
+    }
+
+
+    /**
+     * Skip sub tree that is currently parser positioned on.
+     * <br>NOTE: parser must be on START_TAG and when function returns
+     * parser will be positioned on corresponding END_TAG
+     */
+    public void skipSubTree()
+        throws XmlPullParserException, IOException
+    {
+        require(START_TAG, null, null);
+        int level = 1;
+        while(level > 0) {
+            int eventType = next();
+            if(eventType == END_TAG) {
+                --level;
+            } else if(eventType == START_TAG) {
+                ++level;
+            }
+        }
+    }
+
+    //    public String readText() throws XmlPullParserException, IOException
+    //    {
+    //        if (getEventType() != TEXT) return "";
+    //        String result = getText();
+    //        next();
+    //        return result;
+    //    }
+
+    public String nextText() throws XmlPullParserException, IOException
+    {
+        //        String result = null;
+        //        boolean onStartTag = false;
+        //        if(eventType == START_TAG) {
+        //            onStartTag = true;
+        //            next();
+        //        }
+        //        if(eventType == TEXT) {
+        //            result = getText();
+        //            next();
+        //        } else if(onStartTag && eventType == END_TAG) {
+        //            result = "";
+        //        } else {
+        //            throw new XmlPullParserException(
+        //                "parser must be on START_TAG or TEXT to read text", this, null);
+        //        }
+        //        if(eventType != END_TAG) {
+        //            throw new XmlPullParserException(
+        //                "event TEXT it must be immediately followed by END_TAG", this, null);
+        //        }
+        //        return result;
+        if(getEventType() != START_TAG) {
+            throw new XmlPullParserException(
+                "parser must be on START_TAG to read next text", this, null);
+        }
+        int eventType = next();
+        if(eventType == TEXT) {
+            final String result = getText();
+            eventType = next();
+            if(eventType != END_TAG) {
+                throw new XmlPullParserException(
+                    "TEXT must be immediately followed by END_TAG and not "
+                        +TYPES[ getEventType() ], this, null);
+            }
+            return result;
+        } else if(eventType == END_TAG) {
+            return "";
+        } else {
+            throw new XmlPullParserException(
+                "parser must be on START_TAG or TEXT to read text", this, null);
+        }
+    }
+
+    public int nextTag() throws XmlPullParserException, IOException
+    {
+        next();
+        if(eventType == TEXT && isWhitespace()) {  // skip whitespace
+            next();
+        }
+        if (eventType != START_TAG && eventType != END_TAG) {
+            throw new XmlPullParserException("expected START_TAG or END_TAG not "
+                                                 +TYPES[ getEventType() ], this, null);
+        }
+        return eventType;
+    }
+
+    public int next()
+        throws XmlPullParserException, IOException
+    {
+        tokenize = false;
+        return nextImpl();
+    }
+
+    public int nextToken()
+        throws XmlPullParserException, IOException
+    {
+        tokenize = true;
+        return nextImpl();
+    }
+
+
+    protected int nextImpl()
+        throws XmlPullParserException, IOException
+    {
+        text = null;
+        pcEnd = pcStart = 0;
+        usePC = false;
+        bufStart = posEnd;
+        if(pastEndTag) {
+            pastEndTag = false;
+            --depth;
+            namespaceEnd = elNamespaceCount[ depth ]; // less namespaces available
+        }
+        if(emptyElementTag) {
+            emptyElementTag = false;
+            pastEndTag = true;
+            return eventType = END_TAG;
+        }
+
+        // [1] document ::= prolog element Misc*
+        if(depth > 0) {
+
+            if(seenStartTag) {
+                seenStartTag = false;
+                return eventType = parseStartTag();
+            }
+            if(seenEndTag) {
+                seenEndTag = false;
+                return eventType = parseEndTag();
+            }
+
+            // ASSUMPTION: we are _on_ first character of content or markup!!!!
+            // [43] content ::= CharData? ((element | Reference | CDSect | PI | Comment) CharData?)*
+            char ch;
+            if(seenMarkup) {  // we have read ahead ...
+                seenMarkup = false;
+                ch = '<';
+            } else if(seenAmpersand) {
+                seenAmpersand = false;
+                ch = '&';
+            } else {
+                ch = more();
+            }
+            posStart = pos - 1; // VERY IMPORTANT: this is correct start of event!!!
+
+            // when true there is some potential event TEXT to return - keep gathering
+            boolean hadCharData = false;
+
+            // when true TEXT data is not continual (like <![CDATA[text]]>) and requires PC merging
+            boolean needsMerging = false;
+
+            MAIN_LOOP:
+            while(true) {
+                // work on MARKUP
+                if(ch == '<') {
+                    if(hadCharData) {
+                        //posEnd = pos - 1;
+                        if(tokenize) {
+                            seenMarkup = true;
+                            return eventType = TEXT;
+                        }
+                    }
+                    ch = more();
+                    if(ch == '/') {
+                        if(!tokenize && hadCharData) {
+                            seenEndTag = true;
+                            //posEnd = pos - 2;
+                            return eventType = TEXT;
+                        }
+                        return eventType = parseEndTag();
+                    } else if(ch == '!') {
+                        ch = more();
+                        if(ch == '-') {
+                            // note: if(tokenize == false) posStart/End is NOT changed!!!!
+                            parseComment();
+                            if(tokenize) return eventType = COMMENT;
+                            if( !usePC && hadCharData ) {
+                                needsMerging = true;
+                            } else {
+                                posStart = pos;  //completely ignore comment
+                            }
+                        } else if(ch == '[') {
+                            //posEnd = pos - 3;
+                            // must remember previous posStart/End as it merges with content of CDATA
+                            //int oldStart = posStart + bufAbsoluteStart;
+                            //int oldEnd = posEnd + bufAbsoluteStart;
+                            parseCDSect(hadCharData);
+                            if(tokenize) return eventType = CDSECT;
+                            final int cdStart = posStart;
+                            final int cdEnd = posEnd;
+                            final int cdLen = cdEnd - cdStart;
+
+
+                            if(cdLen > 0) { // was there anything inside CDATA section?
+                                hadCharData = true;
+                                if(!usePC) {
+                                    needsMerging = true;
+                                }
+                            }
+
+                            //                          posStart = oldStart;
+                            //                          posEnd = oldEnd;
+                            //                          if(cdLen > 0) { // was there anything inside CDATA section?
+                            //                              if(hadCharData) {
+                            //                                  // do merging if there was anything in CDSect!!!!
+                            //                                  //                                    if(!usePC) {
+                            //                                  //                                        // posEnd is correct already!!!
+                            //                                  //                                        if(posEnd > posStart) {
+                            //                                  //                                            joinPC();
+                            //                                  //                                        } else {
+                            //                                  //                                            usePC = true;
+                            //                                  //                                            pcStart = pcEnd = 0;
+                            //                                  //                                        }
+                            //                                  //                                    }
+                            //                                  //                                    if(pcEnd + cdLen >= pc.length) ensurePC(pcEnd + cdLen);
+                            //                                  //                                    // copy [cdStart..cdEnd) into PC
+                            //                                  //                                    System.arraycopy(buf, cdStart, pc, pcEnd, cdLen);
+                            //                                  //                                    pcEnd += cdLen;
+                            //                                  if(!usePC) {
+                            //                                      needsMerging = true;
+                            //                                      posStart = cdStart;
+                            //                                      posEnd = cdEnd;
+                            //                                  }
+                            //                              } else {
+                            //                                  if(!usePC) {
+                            //                                      needsMerging = true;
+                            //                                      posStart = cdStart;
+                            //                                      posEnd = cdEnd;
+                            //                                      hadCharData = true;
+                            //                                  }
+                            //                              }
+                            //                              //hadCharData = true;
+                            //                          } else {
+                            //                              if( !usePC && hadCharData ) {
+                            //                                  needsMerging = true;
+                            //                              }
+                            //                          }
+                        } else {
+                            throw new XmlPullParserException(
+                                "unexpected character in markup "+printable(ch), this, null);
+                        }
+                    } else if(ch == '?') {
+                        parsePI();
+                        if(tokenize) return eventType = PROCESSING_INSTRUCTION;
+                        if( !usePC && hadCharData ) {
+                            needsMerging = true;
+                        } else {
+                            posStart = pos;  //completely ignore PI
+                        }
+
+                    } else if( isNameStartChar(ch) ) {
+                        if(!tokenize && hadCharData) {
+                            seenStartTag = true;
+                            //posEnd = pos - 2;
+                            return eventType = TEXT;
+                        }
+                        return eventType = parseStartTag();
+                    } else {
+                        throw new XmlPullParserException(
+                            "unexpected character in markup "+printable(ch), this, null);
+                    }
+                    // do content compaction if it makes sense!!!!
+
+                } else if(ch == '&') {
+                    // work on ENTITTY
+                    //posEnd = pos - 1;
+                    if(tokenize && hadCharData) {
+                        seenAmpersand = true;
+                        return eventType = TEXT;
+                    }
+                    final int oldStart = posStart + bufAbsoluteStart;
+                    final int oldEnd = posEnd + bufAbsoluteStart;
+                    final char[] resolvedEntity = parseEntityRef();
+                    if(tokenize) return eventType = ENTITY_REF;
+                    // check if replacement text can be resolved !!!
+                    if(resolvedEntity == null) {
+                        if(entityRefName == null) {
+                            entityRefName = newString(buf, posStart, posEnd - posStart);
+                        }
+                        throw new XmlPullParserException(
+                            "could not resolve entity named '"+printable(entityRefName)+"'",
+                            this, null);
+                    }
+                    //int entStart = posStart;
+                    //int entEnd = posEnd;
+                    posStart = oldStart - bufAbsoluteStart;
+                    posEnd = oldEnd - bufAbsoluteStart;
+                    if(!usePC) {
+                        if(hadCharData) {
+                            joinPC(); // posEnd is already set correctly!!!
+                            needsMerging = false;
+                        } else {
+                            usePC = true;
+                            pcStart = pcEnd = 0;
+                        }
+                    }
+                    //assert usePC == true;
+                    // write into PC replacement text - do merge for replacement text!!!!
+                    for (int i = 0; i < resolvedEntity.length; i++)
+                    {
+                        if(pcEnd >= pc.length) ensurePC(pcEnd);
+                        pc[pcEnd++] = resolvedEntity[ i ];
+
+                    }
+                    hadCharData = true;
+                    //assert needsMerging == false;
+                } else {
+
+                    if(needsMerging) {
+                        //assert usePC == false;
+                        joinPC();  // posEnd is already set correctly!!!
+                        //posStart = pos  -  1;
+                        needsMerging = false;
+                    }
+
+
+                    //no MARKUP not ENTITIES so work on character data ...
+
+
+
+                    // [14] CharData ::=   [^<&]* - ([^<&]* ']]>' [^<&]*)
+
+
+                    hadCharData = true;
+
+                    boolean normalizedCR = false;
+                    final boolean normalizeInput = tokenize == false || roundtripSupported == false;
+                    // use loop locality here!!!!
+                    boolean seenBracket = false;
+                    boolean seenBracketBracket = false;
+                    do {
+
+                        // check that ]]> does not show in
+                        if(ch == ']') {
+                            if(seenBracket) {
+                                seenBracketBracket = true;
+                            } else {
+                                seenBracket = true;
+                            }
+                        } else if(seenBracketBracket && ch == '>') {
+                            throw new XmlPullParserException(
+                                "characters ]]> are not allowed in content", this, null);
+                        } else {
+                            if(seenBracket) {
+                                seenBracketBracket = seenBracket = false;
+                            }
+                            // assert seenTwoBrackets == seenBracket == false;
+                        }
+                        if(normalizeInput) {
+                            // deal with normalization issues ...
+                            if(ch == '\r') {
+                                normalizedCR = true;
+                                posEnd = pos -1;
+                                // posEnd is already is set
+                                if(!usePC) {
+                                    if(posEnd > posStart) {
+                                        joinPC();
+                                    } else {
+                                        usePC = true;
+                                        pcStart = pcEnd = 0;
+                                    }
+                                }
+                                //assert usePC == true;
+                                if(pcEnd >= pc.length) ensurePC(pcEnd);
+                                pc[pcEnd++] = '\n';
+                            } else if(ch == '\n') {
+                                //   if(!usePC) {  joinPC(); } else { if(pcEnd >= pc.length) ensurePC(); }
+                                if(!normalizedCR && usePC) {
+                                    if(pcEnd >= pc.length) ensurePC(pcEnd);
+                                    pc[pcEnd++] = '\n';
+                                }
+                                normalizedCR = false;
+                            } else {
+                                if(usePC) {
+                                    if(pcEnd >= pc.length) ensurePC(pcEnd);
+                                    pc[pcEnd++] = ch;
+                                }
+                                normalizedCR = false;
+                            }
+                        }
+
+                        ch = more();
+                    } while(ch != '<' && ch != '&');
+                    posEnd = pos - 1;
+                    continue MAIN_LOOP;  // skip ch = more() from below - we are alreayd ahead ...
+                }
+                ch = more();
+            } // endless while(true)
+        } else {
+            if(seenRoot) {
+                return parseEpilog();
+            } else {
+                return parseProlog();
+            }
+        }
+    }
+
+
+    protected int parseProlog()
+        throws XmlPullParserException, IOException
+    {
+        // [2] prolog: ::= XMLDecl? Misc* (doctypedecl Misc*)? and look for [39] element
+
+        char ch;
+        if(seenMarkup) {
+            ch = buf[ pos - 1 ];
+        } else {
+            ch = more();
+        }
+
+        if(eventType == START_DOCUMENT) {
+            // bootstrap parsing with getting first character input!
+            // deal with BOM
+            // detect BOM and drop it (Unicode int Order Mark)
+            if(ch == '\uFFFE') {
+                throw new XmlPullParserException(
+                    "first character in input was UNICODE noncharacter (0xFFFE)"+
+                        "- input requires int swapping", this, null);
+            }
+            if(ch == '\uFEFF') {
+                // skipping UNICODE int Order Mark (so called BOM)
+                ch = more();
+            }
+        }
+        seenMarkup = false;
+        boolean gotS = false;
+        posStart = pos - 1;
+        final boolean normalizeIgnorableWS = tokenize == true && roundtripSupported == false;
+        boolean normalizedCR = false;
+        while(true) {
+            // deal with Misc
+            // [27] Misc ::= Comment | PI | S
+            // deal with docdecl --> mark it!
+            // else parseStartTag seen <[^/]
+            if(ch == '<') {
+                if(gotS && tokenize) {
+                    posEnd = pos - 1;
+                    seenMarkup = true;
+                    return eventType = IGNORABLE_WHITESPACE;
+                }
+                ch = more();
+                if(ch == '?') {
+                    // check if it is 'xml'
+                    // deal with XMLDecl
+                    if(parsePI()) {  // make sure to skip XMLDecl
+                        if(tokenize) {
+                            return eventType = PROCESSING_INSTRUCTION;
+                        }
+                    } else {
+                        // skip over - continue tokenizing
+                        posStart = pos;
+                        gotS = false;
+                    }
+
+                } else if(ch == '!') {
+                    ch = more();
+                    if(ch == 'D') {
+                        if(seenDocdecl) {
+                            throw new XmlPullParserException(
+                                "only one docdecl allowed in XML document", this, null);
+                        }
+                        seenDocdecl = true;
+                        parseDocdecl();
+                        if(tokenize) return eventType = DOCDECL;
+                    } else if(ch == '-') {
+                        parseComment();
+                        if(tokenize) return eventType = COMMENT;
+                    } else {
+                        throw new XmlPullParserException(
+                            "unexpected markup <!"+printable(ch), this, null);
+                    }
+                } else if(ch == '/') {
+                    throw new XmlPullParserException(
+                        "expected start tag name and not "+printable(ch), this, null);
+                } else if(isNameStartChar(ch)) {
+                    seenRoot = true;
+                    return parseStartTag();
+                } else {
+                    throw new XmlPullParserException(
+                        "expected start tag name and not "+printable(ch), this, null);
+                }
+            } else if(isS(ch)) {
+                gotS = true;
+                if(normalizeIgnorableWS) {
+                    if(ch == '\r') {
+                        normalizedCR = true;
+                        //posEnd = pos -1;
+                        //joinPC();
+                        // posEnd is already is set
+                        if(!usePC) {
+                            posEnd = pos -1;
+                            if(posEnd > posStart) {
+                                joinPC();
+                            } else {
+                                usePC = true;
+                                pcStart = pcEnd = 0;
+                            }
+                        }
+                        //assert usePC == true;
+                        if(pcEnd >= pc.length) ensurePC(pcEnd);
+                        pc[pcEnd++] = '\n';
+                    } else if(ch == '\n') {
+                        if(!normalizedCR && usePC) {
+                            if(pcEnd >= pc.length) ensurePC(pcEnd);
+                            pc[pcEnd++] = '\n';
+                        }
+                        normalizedCR = false;
+                    } else {
+                        if(usePC) {
+                            if(pcEnd >= pc.length) ensurePC(pcEnd);
+                            pc[pcEnd++] = ch;
+                        }
+                        normalizedCR = false;
+                    }
+                }
+            } else {
+                throw new XmlPullParserException(
+                    "only whitespace content allowed before start tag and not "+printable(ch),
+                    this, null);
+            }
+            ch = more();
+        }
+    }
+
+    protected int parseEpilog()
+        throws XmlPullParserException, IOException
+    {
+        if(eventType == END_DOCUMENT) {
+            throw new XmlPullParserException("already reached end of XML input", this, null);
+        }
+        if(reachedEnd) {
+            return eventType = END_DOCUMENT;
+        }
+        boolean gotS = false;
+        final boolean normalizeIgnorableWS = tokenize == true && roundtripSupported == false;
+        boolean normalizedCR = false;
+        try {
+            // epilog: Misc*
+            char ch;
+            if(seenMarkup) {
+                ch = buf[ pos - 1 ];
+            } else {
+                ch = more();
+            }
+            seenMarkup = false;
+            posStart = pos - 1;
+            if(!reachedEnd) {
+                while(true) {
+                    // deal with Misc
+                    // [27] Misc ::= Comment | PI | S
+                    if(ch == '<') {
+                        if(gotS && tokenize) {
+                            posEnd = pos - 1;
+                            seenMarkup = true;
+                            return eventType = IGNORABLE_WHITESPACE;
+                        }
+                        ch = more();
+                        if(reachedEnd) {
+                            break;
+                        }
+                        if(ch == '?') {
+                            // check if it is 'xml'
+                            // deal with XMLDecl
+                            parsePI();
+                            if(tokenize) return eventType = PROCESSING_INSTRUCTION;
+
+                        } else if(ch == '!') {
+                            ch = more();
+                            if(reachedEnd) {
+                                break;
+                            }
+                            if(ch == 'D') {
+                                parseDocdecl(); //FIXME
+                                if(tokenize) return eventType = DOCDECL;
+                            } else if(ch == '-') {
+                                parseComment();
+                                if(tokenize) return eventType = COMMENT;
+                            } else {
+                                throw new XmlPullParserException(
+                                    "unexpected markup <!"+printable(ch), this, null);
+                            }
+                        } else if(ch == '/') {
+                            throw new XmlPullParserException(
+                                "end tag not allowed in epilog but got "+printable(ch), this, null);
+                        } else if(isNameStartChar(ch)) {
+                            throw new XmlPullParserException(
+                                "start tag not allowed in epilog but got "+printable(ch), this, null);
+                        } else {
+                            throw new XmlPullParserException(
+                                "in epilog expected ignorable content and not "+printable(ch),
+                                this, null);
+                        }
+                    } else if(isS(ch)) {
+                        gotS = true;
+                        if(normalizeIgnorableWS) {
+                            if(ch == '\r') {
+                                normalizedCR = true;
+                                //posEnd = pos -1;
+                                //joinPC();
+                                // posEnd is alreadys set
+                                if(!usePC) {
+                                    posEnd = pos -1;
+                                    if(posEnd > posStart) {
+                                        joinPC();
+                                    } else {
+                                        usePC = true;
+                                        pcStart = pcEnd = 0;
+                                    }
+                                }
+                                //assert usePC == true;
+                                if(pcEnd >= pc.length) ensurePC(pcEnd);
+                                pc[pcEnd++] = '\n';
+                            } else if(ch == '\n') {
+                                if(!normalizedCR && usePC) {
+                                    if(pcEnd >= pc.length) ensurePC(pcEnd);
+                                    pc[pcEnd++] = '\n';
+                                }
+                                normalizedCR = false;
+                            } else {
+                                if(usePC) {
+                                    if(pcEnd >= pc.length) ensurePC(pcEnd);
+                                    pc[pcEnd++] = ch;
+                                }
+                                normalizedCR = false;
+                            }
+                        }
+                    } else {
+                        throw new XmlPullParserException(
+                            "in epilog non whitespace content is not allowed but got "+printable(ch),
+                            this, null);
+                    }
+                    ch = more();
+                    if(reachedEnd) {
+                        break;
+                    }
+
+                }
+            }
+
+            // throw Exception("unexpected content in epilog
+            // catch EOFException return END_DOCUEMENT
+            //try {
+        } catch(EOFException ex) {
+            reachedEnd = true;
+        }
+        if(reachedEnd) {
+            if(tokenize && gotS) {
+                posEnd = pos; // well - this is LAST available character pos
+                return eventType = IGNORABLE_WHITESPACE;
+            }
+            return eventType = END_DOCUMENT;
+        } else {
+            throw new XmlPullParserException("internal error in parseEpilog");
+        }
+    }
+
+
+    public int parseEndTag() throws XmlPullParserException, IOException {
+        //ASSUMPTION ch is past "</"
+        // [42] ETag ::=  '</' Name S? '>'
+        char ch = more();
+        if(!isNameStartChar(ch)) {
+            throw new XmlPullParserException(
+                "expected name start and not "+printable(ch), this, null);
+        }
+        posStart = pos - 3;
+        final int nameStart = pos - 1 + bufAbsoluteStart;
+        do {
+            ch = more();
+        } while(isNameChar(ch));
+
+        // now we go one level down -- do checks
+        //--depth;  //FIXME
+
+        // check that end tag name is the same as start tag
+        //String name = new String(buf, nameStart - bufAbsoluteStart,
+        //                           (pos - 1) - (nameStart - bufAbsoluteStart));
+        //int last = pos - 1;
+        int off = nameStart - bufAbsoluteStart;
+        //final int len = last - off;
+        final int len = (pos - 1) - off;
+        final char[] cbuf = elRawName[depth];
+        if(elRawNameEnd[depth] != len) {
+            // construct strings for exception
+            final String startname = new String(cbuf, 0, elRawNameEnd[depth]);
+            final String endname = new String(buf, off, len);
+            throw new XmlPullParserException(
+                "end tag name </"+endname+"> must match start tag name <"+startname+">"
+                    +" from line "+elRawNameLine[depth], this, null);
+        }
+        for (int i = 0; i < len; i++)
+        {
+            if(buf[off++] != cbuf[i]) {
+                // construct strings for exception
+                final String startname = new String(cbuf, 0, len);
+                final String endname = new String(buf, off - i - 1, len);
+                throw new XmlPullParserException(
+                    "end tag name </"+endname+"> must be the same as start tag <"+startname+">"
+                        +" from line "+elRawNameLine[depth], this, null);
+            }
+        }
+
+        while(isS(ch)) { ch = more(); } // skip additional white spaces
+        if(ch != '>') {
+            throw new XmlPullParserException(
+                "expected > to finish end tag not "+printable(ch)
+                    +" from line "+elRawNameLine[depth], this, null);
+        }
+
+
+        //namespaceEnd = elNamespaceCount[ depth ]; //FIXME
+
+        posEnd = pos;
+        pastEndTag = true;
+        return eventType = END_TAG;
+    }
+
+    public int parseStartTag() throws XmlPullParserException, IOException {
+        //ASSUMPTION ch is past <T
+        // [40] STag ::=  '<' Name (S Attribute)* S? '>'
+        // [44] EmptyElemTag ::= '<' Name (S Attribute)* S? '/>'
+        ++depth; //FIXME
+
+        posStart = pos - 2;
+
+        emptyElementTag = false;
+        attributeCount = 0;
+        // retrieve name
+        final int nameStart = pos - 1 + bufAbsoluteStart;
+        int colonPos = -1;
+        char ch = buf[ pos - 1];
+        if(ch == ':' && processNamespaces) throw new XmlPullParserException(
+                "when namespaces processing enabled colon can not be at element name start",
+                this, null);
+        while(true) {
+            ch = more();
+            if(!isNameChar(ch)) break;
+            if(ch == ':' && processNamespaces) {
+                if(colonPos != -1) throw new XmlPullParserException(
+                        "only one colon is allowed in name of element when namespaces are enabled",
+                        this, null);
+                colonPos = pos - 1 + bufAbsoluteStart;
+            }
+        }
+
+        // retrieve name
+        ensureElementsCapacity();
+
+
+        //TODO check for efficient interning and then use elRawNameInterned!!!!
+
+        int elLen = (pos - 1) - (nameStart - bufAbsoluteStart);
+        if(elRawName[ depth ] == null || elRawName[ depth ].length < elLen) {
+            elRawName[ depth ] = new char[ 2 * elLen ];
+        }
+        System.arraycopy(buf, nameStart - bufAbsoluteStart, elRawName[ depth ], 0, elLen);
+        elRawNameEnd[ depth ] = elLen;
+        elRawNameLine[ depth ] = lineNumber;
+
+        String name = null;
+
+        // work on prefixes and namespace URI
+        String prefix = null;
+        if(processNamespaces) {
+            if(colonPos != -1) {
+                prefix = elPrefix[ depth ] = newString(buf, nameStart - bufAbsoluteStart,
+                                                       colonPos - nameStart);
+                name = elName[ depth ] = newString(buf, colonPos + 1 - bufAbsoluteStart,
+                                                   //(pos -1) - (colonPos + 1));
+                                                   pos - 2 - (colonPos - bufAbsoluteStart));
+            } else {
+                prefix = elPrefix[ depth ] = null;
+                name = elName[ depth ] = newString(buf, nameStart - bufAbsoluteStart, elLen);
+            }
+        } else {
+
+            name = elName[ depth ] = newString(buf, nameStart - bufAbsoluteStart, elLen);
+
+        }
+
+
+        while(true) {
+
+            while(isS(ch)) { ch = more(); } // skip additional white spaces
+
+            if(ch == '>') {
+                break;
+            } else if(ch == '/') {
+                if(emptyElementTag) throw new XmlPullParserException(
+                        "repeated / in tag declaration", this, null);
+                emptyElementTag = true;
+                ch = more();
+                if(ch != '>') throw new XmlPullParserException(
+                        "expected > to end empty tag not "+printable(ch), this, null);
+                break;
+            } else if(isNameStartChar(ch)) {
+                ch = parseAttribute();
+                ch = more();
+                continue;
+            } else {
+                throw new XmlPullParserException(
+                    "start tag unexpected character "+printable(ch), this, null);
+            }
+            //ch = more(); // skip space
+        }
+
+        // now when namespaces were declared we can resolve them
+        if(processNamespaces) {
+            String uri = getNamespace(prefix);
+            if(uri == null) {
+                if(prefix == null) { // no prefix and no uri => use default namespace
+                    uri = NO_NAMESPACE;
+                } else {
+                    throw new XmlPullParserException(
+                        "could not determine namespace bound to element prefix "+prefix,
+                        this, null);
+                }
+
+            }
+            elUri[ depth ] = uri;
+
+
+            //String uri = getNamespace(prefix);
+            //if(uri == null && prefix == null) { // no prefix and no uri => use default namespace
+            //  uri = "";
+            //}
+            // resolve attribute namespaces
+            for (int i = 0; i < attributeCount; i++)
+            {
+                final String attrPrefix = attributePrefix[ i ];
+                if(attrPrefix != null) {
+                    final String attrUri = getNamespace(attrPrefix);
+                    if(attrUri == null) {
+                        throw new XmlPullParserException(
+                            "could not determine namespace bound to attribute prefix "+attrPrefix,
+                            this, null);
+
+                    }
+                    attributeUri[ i ] = attrUri;
+                } else {
+                    attributeUri[ i ] = NO_NAMESPACE;
+                }
+            }
+
+            //TODO
+            //[ WFC: Unique Att Spec ]
+            // check attribute uniqueness constraint for attributes that has namespace!!!
+
+            for (int i = 1; i < attributeCount; i++)
+            {
+                for (int j = 0; j < i; j++)
+                {
+                    if( attributeUri[j] == attributeUri[i]
+                           && (allStringsInterned && attributeName[j].equals(attributeName[i])
+                                   || (!allStringsInterned
+                                           && attributeNameHash[ j ] == attributeNameHash[ i ]
+                                           && attributeName[j].equals(attributeName[i])) )
+
+                      ) {
+                        // prepare data for nice error message?
+                        String attr1 = attributeName[j];
+                        if(attributeUri[j] != null) attr1 = attributeUri[j]+":"+attr1;
+                        String attr2 = attributeName[i];
+                        if(attributeUri[i] != null) attr2 = attributeUri[i]+":"+attr2;
+                        throw new XmlPullParserException(
+                            "duplicated attributes "+attr1+" and "+attr2, this, null);
+                    }
+                }
+            }
+
+
+        } else { // ! processNamespaces
+
+            //[ WFC: Unique Att Spec ]
+            // check raw attribute uniqueness constraint!!!
+            for (int i = 1; i < attributeCount; i++)
+            {
+                for (int j = 0; j < i; j++)
+                {
+                    if((allStringsInterned && attributeName[j].equals(attributeName[i])
+                            || (!allStringsInterned
+                                    && attributeNameHash[ j ] == attributeNameHash[ i ]
+                                    && attributeName[j].equals(attributeName[i])) )
+
+                      ) {
+                        // prepare data for nice error message?
+                        final String attr1 = attributeName[j];
+                        final String attr2 = attributeName[i];
+                        throw new XmlPullParserException(
+                            "duplicated attributes "+attr1+" and "+attr2, this, null);
+                    }
+                }
+            }
+        }
+
+        elNamespaceCount[ depth ] = namespaceEnd;
+        posEnd = pos;
+        return eventType = START_TAG;
+    }
+
+    protected char parseAttribute() throws XmlPullParserException, IOException
+    {
+        // parse attribute
+        // [41] Attribute ::= Name Eq AttValue
+        // [WFC: No External Entity References]
+        // [WFC: No < in Attribute Values]
+        final int prevPosStart = posStart + bufAbsoluteStart;
+        final int nameStart = pos - 1 + bufAbsoluteStart;
+        int colonPos = -1;
+        char ch = buf[ pos - 1 ];
+        if(ch == ':' && processNamespaces) throw new XmlPullParserException(
+                "when namespaces processing enabled colon can not be at attribute name start",
+                this, null);
+
+
+        boolean startsWithXmlns = processNamespaces && ch == 'x';
+        int xmlnsPos = 0;
+
+        ch = more();
+        while(isNameChar(ch)) {
+            if(processNamespaces) {
+                if(startsWithXmlns && xmlnsPos < 5) {
+                    ++xmlnsPos;
+                    if(xmlnsPos == 1) { if(ch != 'm') startsWithXmlns = false; }
+                    else if(xmlnsPos == 2) { if(ch != 'l') startsWithXmlns = false; }
+                    else if(xmlnsPos == 3) { if(ch != 'n') startsWithXmlns = false; }
+                    else if(xmlnsPos == 4) { if(ch != 's') startsWithXmlns = false; }
+                    else if(xmlnsPos == 5) {
+                        if(ch != ':') throw new XmlPullParserException(
+                                "after xmlns in attribute name must be colon"
+                                    +"when namespaces are enabled", this, null);
+                        //colonPos = pos - 1 + bufAbsoluteStart;
+                    }
+                }
+                if(ch == ':') {
+                    if(colonPos != -1) throw new XmlPullParserException(
+                            "only one colon is allowed in attribute name"
+                                +" when namespaces are enabled", this, null);
+                    colonPos = pos - 1 + bufAbsoluteStart;
+                }
+            }
+            ch = more();
+        }
+
+        ensureAttributesCapacity(attributeCount);
+
+        // --- start processing attributes
+        String name = null;
+        String prefix = null;
+        // work on prefixes and namespace URI
+        if(processNamespaces) {
+            if(xmlnsPos < 4) startsWithXmlns = false;
+            if(startsWithXmlns) {
+                if(colonPos != -1) {
+                    //prefix = attributePrefix[ attributeCount ] = null;
+                    final int nameLen = pos - 2 - (colonPos - bufAbsoluteStart);
+                    if(nameLen == 0) {
+                        throw new XmlPullParserException(
+                            "namespace prefix is required after xmlns: "
+                                +" when namespaces are enabled", this, null);
+                    }
+                    name = //attributeName[ attributeCount ] =
+                        newString(buf, colonPos - bufAbsoluteStart + 1, nameLen);
+                    //pos - 1 - (colonPos + 1 - bufAbsoluteStart)
+                }
+            } else {
+                if(colonPos != -1) {
+                    int prefixLen = colonPos - nameStart;
+                    prefix = attributePrefix[ attributeCount ] =
+                        newString(buf, nameStart - bufAbsoluteStart,prefixLen);
+                    //colonPos - (nameStart - bufAbsoluteStart));
+                    int nameLen = pos - 2 - (colonPos - bufAbsoluteStart);
+                    name = attributeName[ attributeCount ] =
+                        newString(buf, colonPos - bufAbsoluteStart + 1, nameLen);
+                    //pos - 1 - (colonPos + 1 - bufAbsoluteStart));
+
+                    //name.substring(0, colonPos-nameStart);
+                } else {
+                    prefix = attributePrefix[ attributeCount ]  = null;
+                    name = attributeName[ attributeCount ] =
+                        newString(buf, nameStart - bufAbsoluteStart,
+                                  pos - 1 - (nameStart - bufAbsoluteStart));
+                }
+                if(!allStringsInterned) {
+                    attributeNameHash[ attributeCount ] = name.hashCode();
+                }
+            }
+
+        } else {
+            // retrieve name
+            name = attributeName[ attributeCount ] =
+                newString(buf, nameStart - bufAbsoluteStart,
+                          pos - 1 - (nameStart - bufAbsoluteStart));
+            ////assert name != null;
+            if(!allStringsInterned) {
+                attributeNameHash[ attributeCount ] = name.hashCode();
+            }
+        }
+
+        // [25] Eq ::=  S? '=' S?
+        while(isS(ch)) { ch = more(); } // skip additional spaces
+        if(ch != '=') throw new XmlPullParserException(
+                "expected = after attribute name", this, null);
+        ch = more();
+        while(isS(ch)) { ch = more(); } // skip additional spaces
+
+        // [10] AttValue ::=   '"' ([^<&"] | Reference)* '"'
+        //                  |  "'" ([^<&'] | Reference)* "'"
+        final char delimit = ch;
+        if(delimit != '"' && delimit != '\'') throw new XmlPullParserException(
+                "attribute value must start with quotation or apostrophe not "
+                    +printable(delimit), this, null);
+        // parse until delimit or < and resolve Reference
+        //[67] Reference ::= EntityRef | CharRef
+        //int valueStart = pos + bufAbsoluteStart;
+
+
+        boolean normalizedCR = false;
+        usePC = false;
+        pcStart = pcEnd;
+        posStart = pos;
+
+        while(true) {
+            ch = more();
+            if(ch == delimit) {
+                break;
+            } if(ch == '<') {
+                throw new XmlPullParserException(
+                    "markup not allowed inside attribute value - illegal < ", this, null);
+            } if(ch == '&') {
+                // extractEntityRef
+                posEnd = pos - 1;
+                if(!usePC) {
+                    final boolean hadCharData = posEnd > posStart;
+                    if(hadCharData) {
+                        // posEnd is already set correctly!!!
+                        joinPC();
+                    } else {
+                        usePC = true;
+                        pcStart = pcEnd = 0;
+                    }
+                }
+                //assert usePC == true;
+
+                final char[] resolvedEntity = parseEntityRef();
+                // check if replacement text can be resolved !!!
+                if(resolvedEntity == null) {
+                    if(entityRefName == null) {
+                        entityRefName = newString(buf, posStart, posEnd - posStart);
+                    }
+                    throw new XmlPullParserException(
+                        "could not resolve entity named '"+printable(entityRefName)+"'",
+                        this, null);
+                }
+                // write into PC replacement text - do merge for replacement text!!!!
+                for (int i = 0; i < resolvedEntity.length; i++)
+                {
+                    if(pcEnd >= pc.length) ensurePC(pcEnd);
+                    pc[pcEnd++] = resolvedEntity[ i ];
+                }
+            } else if(ch == '\t' || ch == '\n' || ch == '\r') {
+                // do attribute value normalization
+                // as described in http://www.w3.org/TR/REC-xml#AVNormalize
+                // TODO add test for it form spec ...
+                // handle EOL normalization ...
+                if(!usePC) {
+                    posEnd = pos - 1;
+                    if(posEnd > posStart) {
+                        joinPC();
+                    } else {
+                        usePC = true;
+                        pcEnd = pcStart = 0;
+                    }
+                }
+                //assert usePC == true;
+                if(pcEnd >= pc.length) ensurePC(pcEnd);
+                if(ch != '\n' || !normalizedCR) {
+                    pc[pcEnd++] = ' '; //'\n';
+                }
+
+            } else {
+                if(usePC) {
+                    if(pcEnd >= pc.length) ensurePC(pcEnd);
+                    pc[pcEnd++] = ch;
+                }
+            }
+            normalizedCR = ch == '\r';
+        }
+
+
+        if(processNamespaces && startsWithXmlns) {
+            String ns = null;
+            if(!usePC) {
+                ns = newStringIntern(buf, posStart, pos - 1 - posStart);
+            } else {
+                ns = newStringIntern(pc, pcStart, pcEnd - pcStart);
+            }
+            ensureNamespacesCapacity(namespaceEnd);
+            int prefixHash = -1;
+            if(colonPos != -1) {
+                if(ns.length() == 0) {
+                    throw new XmlPullParserException(
+                        "non-default namespace can not be declared to be empty string", this, null);
+                }
+                // declare new namespace
+                namespacePrefix[ namespaceEnd ] = name;
+                if(!allStringsInterned) {
+                    prefixHash = namespacePrefixHash[ namespaceEnd ] = name.hashCode();
+                }
+            } else {
+                // declare  new default namespace ...
+                namespacePrefix[ namespaceEnd ] = null; //""; //null; //TODO check FIXME Alek
+                if(!allStringsInterned) {
+                    prefixHash = namespacePrefixHash[ namespaceEnd ] = -1;
+                }
+            }
+            namespaceUri[ namespaceEnd ] = ns;
+
+            // detect duplicate namespace declarations!!!
+            final int startNs = elNamespaceCount[ depth - 1 ];
+            for (int i = namespaceEnd - 1; i >= startNs; --i)
+            {
+                if(((allStringsInterned || name == null) && namespacePrefix[ i ] == name)
+                       || (!allStringsInterned && name != null &&
+                               namespacePrefixHash[ i ] == prefixHash
+                               && name.equals(namespacePrefix[ i ])
+                          ))
+                {
+                    final String s = name == null ? "default" : "'"+name+"'";
+                    throw new XmlPullParserException(
+                        "duplicated namespace declaration for "+s+" prefix", this, null);
+                }
+            }
+
+            ++namespaceEnd;
+
+        } else {
+            if(!usePC) {
+                attributeValue[ attributeCount ] =
+                    new String(buf, posStart, pos - 1 - posStart);
+            } else {
+                attributeValue[ attributeCount ] =
+                    new String(pc, pcStart, pcEnd - pcStart);
+            }
+            ++attributeCount;
+        }
+        posStart = prevPosStart - bufAbsoluteStart;
+        return ch;
+    }
+
+    protected char[] charRefOneCharBuf = new char[1];
+
+    protected char[] parseEntityRef()
+        throws XmlPullParserException, IOException
+    {
+        // entity reference http://www.w3.org/TR/2000/REC-xml-20001006#NT-Reference
+        // [67] Reference          ::=          EntityRef | CharRef
+
+        // ASSUMPTION just after &
+        entityRefName = null;
+        posStart = pos;
+        char ch = more();
+        if(ch == '#') {
+            // parse character reference
+            char charRef = 0;
+            ch = more();
+            if(ch == 'x') {
+                //encoded in hex
+                while(true) {
+                    ch = more();
+                    if(ch >= '0' && ch <= '9') {
+                        charRef = (char)(charRef * 16 + (ch - '0'));
+                    } else if(ch >= 'a' && ch <= 'f') {
+                        charRef = (char)(charRef * 16 + (ch - ('a' - 10)));
+                    } else if(ch >= 'A' && ch <= 'F') {
+                        charRef = (char)(charRef * 16 + (ch - ('A' - 10)));
+                    } else if(ch == ';') {
+                        break;
+                    } else {
+                        throw new XmlPullParserException(
+                            "character reference (with hex value) may not contain "
+                                +printable(ch), this, null);
+                    }
+                }
+            } else {
+                // encoded in decimal
+                while(true) {
+                    if(ch >= '0' && ch <= '9') {
+                        charRef = (char)(charRef * 10 + (ch - '0'));
+                    } else if(ch == ';') {
+                        break;
+                    } else {
+                        throw new XmlPullParserException(
+                            "character reference (with decimal value) may not contain "
+                                +printable(ch), this, null);
+                    }
+                    ch = more();
+                }
+            }
+            posEnd = pos - 1;
+            charRefOneCharBuf[0] = charRef;
+            if(tokenize) {
+                text = newString(charRefOneCharBuf, 0, 1);
+            }
+            return charRefOneCharBuf;
+        } else {
+            // [68]     EntityRef          ::=          '&' Name ';'
+            // scan name until ;
+            if(!isNameStartChar(ch)) {
+                throw new XmlPullParserException(
+                    "entity reference names can not start with character '"
+                        +printable(ch)+"'", this, null);
+            }
+            while(true) {
+                ch = more();
+                if(ch == ';') {
+                    break;
+                }
+                if(!isNameChar(ch)) {
+                    throw new XmlPullParserException(
+                        "entity reference name can not contain character "
+                            +printable(ch)+"'", this, null);
+                }
+            }
+            posEnd = pos - 1;
+            // determine what name maps to
+            final int len = posEnd - posStart;
+            if(len == 2 && buf[posStart] == 'l' && buf[posStart+1] == 't') {
+                if(tokenize) {
+                    text = "<";
+                }
+                charRefOneCharBuf[0] = '<';
+                return charRefOneCharBuf;
+                //if(paramPC || isParserTokenizing) {
+                //    if(pcEnd >= pc.length) ensurePC();
+                //   pc[pcEnd++] = '<';
+                //}
+            } else if(len == 3 && buf[posStart] == 'a'
+                          && buf[posStart+1] == 'm' && buf[posStart+2] == 'p') {
+                if(tokenize) {
+                    text = "&";
+                }
+                charRefOneCharBuf[0] = '&';
+                return charRefOneCharBuf;
+            } else if(len == 2 && buf[posStart] == 'g' && buf[posStart+1] == 't') {
+                if(tokenize) {
+                    text = ">";
+                }
+                charRefOneCharBuf[0] = '>';
+                return charRefOneCharBuf;
+            } else if(len == 4 && buf[posStart] == 'a' && buf[posStart+1] == 'p'
+                          && buf[posStart+2] == 'o' && buf[posStart+3] == 's')
+            {
+                if(tokenize) {
+                    text = "'";
+                }
+                charRefOneCharBuf[0] = '\'';
+                return charRefOneCharBuf;
+            } else if(len == 4 && buf[posStart] == 'q' && buf[posStart+1] == 'u'
+                          && buf[posStart+2] == 'o' && buf[posStart+3] == 't')
+            {
+                if(tokenize) {
+                    text = "\"";
+                }
+                charRefOneCharBuf[0] = '"';
+                return charRefOneCharBuf;
+            } else {
+                final char[] result = lookuEntityReplacement(len);
+                if(result != null) {
+                    return result;
+                }
+            }
+            if(tokenize) text = null;
+            return null;
+        }
+    }
+
+    protected char[] lookuEntityReplacement(int entitNameLen)
+        throws XmlPullParserException, IOException
+
+    {
+        if(!allStringsInterned) {
+            final int hash = fastHash(buf, posStart, posEnd - posStart);
+            LOOP:
+            for (int i = entityEnd - 1; i >= 0; --i)
+            {
+                if(hash == entityNameHash[ i ] && entitNameLen == entityNameBuf[ i ].length) {
+                    final char[] entityBuf = entityNameBuf[ i ];
+                    for (int j = 0; j < entitNameLen; j++)
+                    {
+                        if(buf[posStart + j] != entityBuf[j]) continue LOOP;
+                    }
+                    if(tokenize) text = entityReplacement[ i ];
+                    return entityReplacementBuf[ i ];
+                }
+            }
+        } else {
+            entityRefName = newString(buf, posStart, posEnd - posStart);
+            for (int i = entityEnd - 1; i >= 0; --i)
+            {
+                // take advantage that interning for newStirng is enforced
+                if(entityRefName == entityName[ i ]) {
+                    if(tokenize) text = entityReplacement[ i ];
+                    return entityReplacementBuf[ i ];
+                }
+            }
+        }
+        return null;
+    }
+
+
+    protected void parseComment()
+        throws XmlPullParserException, IOException
+    {
+        // implements XML 1.0 Section 2.5 Comments
+
+        //ASSUMPTION: seen <!-
+        char ch = more();
+        if(ch != '-') throw new XmlPullParserException(
+                "expected <!-- for comment start", this, null);
+        if(tokenize) posStart = pos;
+
+        final int curLine = lineNumber;
+        final int curColumn = columnNumber;
+        try {
+            final boolean normalizeIgnorableWS = tokenize == true && roundtripSupported == false;
+            boolean normalizedCR = false;
+
+            boolean seenDash = false;
+            boolean seenDashDash = false;
+            while(true) {
+                // scan until it hits -->
+                ch = more();
+                if(seenDashDash && ch != '>') {
+                    throw new XmlPullParserException(
+                        "in comment after two dashes (--) next character must be >"
+                            +" not "+printable(ch), this, null);
+                }
+                if(ch == '-') {
+                    if(!seenDash) {
+                        seenDash = true;
+                    } else {
+                        seenDashDash = true;
+                        seenDash = false;
+                    }
+                } else if(ch == '>') {
+                    if(seenDashDash) {
+                        break;  // found end sequence!!!!
+                    } else {
+                        seenDashDash = false;
+                    }
+                    seenDash = false;
+                } else {
+                    seenDash = false;
+                }
+                if(normalizeIgnorableWS) {
+                    if(ch == '\r') {
+                        normalizedCR = true;
+                        //posEnd = pos -1;
+                        //joinPC();
+                        // posEnd is already set
+                        if(!usePC) {
+                            posEnd = pos -1;
+                            if(posEnd > posStart) {
+                                joinPC();
+                            } else {
+                                usePC = true;
+                                pcStart = pcEnd = 0;
+                            }
+                        }
+                        //assert usePC == true;
+                        if(pcEnd >= pc.length) ensurePC(pcEnd);
+                        pc[pcEnd++] = '\n';
+                    } else if(ch == '\n') {
+                        if(!normalizedCR && usePC) {
+                            if(pcEnd >= pc.length) ensurePC(pcEnd);
+                            pc[pcEnd++] = '\n';
+                        }
+                        normalizedCR = false;
+                    } else {
+                        if(usePC) {
+                            if(pcEnd >= pc.length) ensurePC(pcEnd);
+                            pc[pcEnd++] = ch;
+                        }
+                        normalizedCR = false;
+                    }
+                }
+            }
+
+        } catch(EOFException ex) {
+            // detect EOF and create meaningful error ...
+            throw new XmlPullParserException(
+                "comment started on line "+curLine+" and column "+curColumn+" was not closed",
+                this, ex);
+        }
+        if(tokenize) {
+            posEnd = pos - 3;
+            if(usePC) {
+                pcEnd -= 2;
+            }
+        }
+    }
+
+    protected boolean parsePI()
+        throws XmlPullParserException, IOException
+    {
+        // implements XML 1.0 Section 2.6 Processing Instructions
+
+        // [16] PI ::= '<?' PITarget (S (Char* - (Char* '?>' Char*)))? '?>'
+        // [17] PITarget         ::=    Name - (('X' | 'x') ('M' | 'm') ('L' | 'l'))
+        //ASSUMPTION: seen <?
+        if(tokenize) posStart = pos;
+        final int curLine = lineNumber;
+        final int curColumn = columnNumber;
+        int piTargetStart = pos + bufAbsoluteStart;
+        int piTargetEnd = -1;
+        final boolean normalizeIgnorableWS = tokenize == true && roundtripSupported == false;
+        boolean normalizedCR = false;
+
+        try {
+            boolean seenQ = false;
+            char ch = more();
+            if(isS(ch)) {
+                throw new XmlPullParserException(
+                    "processing instruction PITarget must be exactly after <? and not white space character",
+                    this, null);
+            }
+            while(true) {
+                // scan until it hits ?>
+                //ch = more();
+
+                if(ch == '?') {
+                    seenQ = true;
+                } else if(ch == '>') {
+                    if(seenQ) {
+                        break;  // found end sequence!!!!
+                    }
+                    seenQ = false;
+                } else {
+                    if(piTargetEnd == -1 && isS(ch)) {
+                        piTargetEnd = pos - 1 + bufAbsoluteStart;
+
+                        // [17] PITarget ::= Name - (('X' | 'x') ('M' | 'm') ('L' | 'l'))
+                        if((piTargetEnd - piTargetStart) == 3) {
+                            if((buf[piTargetStart] == 'x' || buf[piTargetStart] == 'X')
+                                   && (buf[piTargetStart+1] == 'm' || buf[piTargetStart+1] == 'M')
+                                   && (buf[piTargetStart+2] == 'l' || buf[piTargetStart+2] == 'L')
+                              )
+                            {
+                                if(piTargetStart > 3) {  //<?xml is allowed as first characters in input ...
+                                    throw new XmlPullParserException(
+                                        "processing instruction can not have PITarget with reserveld xml name",
+                                        this, null);
+                                } else {
+                                    if(buf[piTargetStart] != 'x'
+                                           && buf[piTargetStart+1] != 'm'
+                                           && buf[piTargetStart+2] != 'l')
+                                    {
+                                        throw new XmlPullParserException(
+                                            "XMLDecl must have xml name in lowercase",
+                                            this, null);
+                                    }
+                                }
+                                parseXmlDecl(ch);
+                                if(tokenize) posEnd = pos - 2;
+                                final int off = piTargetStart - bufAbsoluteStart + 3;
+                                final int len = pos - 2 - off;
+                                xmlDeclContent = newString(buf, off, len);
+                                return false;
+                            }
+                        }
+                    }
+                    seenQ = false;
+                }
+                if(normalizeIgnorableWS) {
+                    if(ch == '\r') {
+                        normalizedCR = true;
+                        //posEnd = pos -1;
+                        //joinPC();
+                        // posEnd is already set
+                        if(!usePC) {
+                            posEnd = pos -1;
+                            if(posEnd > posStart) {
+                                joinPC();
+                            } else {
+                                usePC = true;
+                                pcStart = pcEnd = 0;
+                            }
+                        }
+                        //assert usePC == true;
+                        if(pcEnd >= pc.length) ensurePC(pcEnd);
+                        pc[pcEnd++] = '\n';
+                    } else if(ch == '\n') {
+                        if(!normalizedCR && usePC) {
+                            if(pcEnd >= pc.length) ensurePC(pcEnd);
+                            pc[pcEnd++] = '\n';
+                        }
+                        normalizedCR = false;
+                    } else {
+                        if(usePC) {
+                            if(pcEnd >= pc.length) ensurePC(pcEnd);
+                            pc[pcEnd++] = ch;
+                        }
+                        normalizedCR = false;
+                    }
+                }
+                ch = more();
+            }
+        } catch(EOFException ex) {
+            // detect EOF and create meaningful error ...
+            throw new XmlPullParserException(
+                "processing instruction started on line "+curLine+" and column "+curColumn
+                    +" was not closed",
+                this, ex);
+        }
+        if(piTargetEnd == -1) {
+            piTargetEnd = pos - 2 + bufAbsoluteStart;
+            //throw new XmlPullParserException(
+            //    "processing instruction must have PITarget name", this, null);
+        }
+        piTargetStart -= bufAbsoluteStart;
+        piTargetEnd -= bufAbsoluteStart;
+        if(tokenize) {
+            posEnd = pos - 2;
+            if(normalizeIgnorableWS) {
+                --pcEnd;
+            }
+        }
+        return true;
+    }
+
+    //    protected final static char[] VERSION = {'v','e','r','s','i','o','n'};
+    //    protected final static char[] NCODING = {'n','c','o','d','i','n','g'};
+    //    protected final static char[] TANDALONE = {'t','a','n','d','a','l','o','n','e'};
+    //    protected final static char[] YES = {'y','e','s'};
+    //    protected final static char[] NO = {'n','o'};
+
+    protected final static char[] VERSION = "version".toCharArray();
+    protected final static char[] NCODING = "ncoding".toCharArray();
+    protected final static char[] TANDALONE = "tandalone".toCharArray();
+    protected final static char[] YES = "yes".toCharArray();
+    protected final static char[] NO = "no".toCharArray();
+
+
+
+    protected void parseXmlDecl(char ch)
+        throws XmlPullParserException, IOException
+    {
+        // [23] XMLDecl ::= '<?xml' VersionInfo EncodingDecl? SDDecl? S? '?>'
+
+        // first make sure that relative positions will stay OK
+        preventBufferCompaction = true;
+        bufStart = 0; // necessary to keep pos unchanged during expansion!
+
+        // --- parse VersionInfo
+
+        // [24] VersionInfo ::= S 'version' Eq ("'" VersionNum "'" | '"' VersionNum '"')
+        // parse is positioned just on first S past <?xml
+        ch = skipS(ch);
+        ch = requireInput(ch, VERSION);
+        // [25] Eq ::= S? '=' S?
+        ch = skipS(ch);
+        if(ch != '=') {
+            throw new XmlPullParserException(
+                "expected equals sign (=) after version and not "+printable(ch), this, null);
+        }
+        ch = more();
+        ch = skipS(ch);
+        if(ch != '\'' && ch != '"') {
+            throw new XmlPullParserException(
+                "expected apostrophe (') or quotation mark (\") after version and not "
+                    +printable(ch), this, null);
+        }
+        final char quotChar = ch;
+        //int versionStart = pos + bufAbsoluteStart;  // required if preventBufferCompaction==false
+        final int versionStart = pos;
+        ch = more();
+        // [26] VersionNum ::= ([a-zA-Z0-9_.:] | '-')+
+        while(ch != quotChar) {
+            if((ch  < 'a' || ch > 'z') && (ch  < 'A' || ch > 'Z') && (ch  < '0' || ch > '9')
+                   && ch != '_' && ch != '.' && ch != ':' && ch != '-')
+            {
+                throw new XmlPullParserException(
+                    "<?xml version value expected to be in ([a-zA-Z0-9_.:] | '-')"
+                        +" not "+printable(ch), this, null);
+            }
+            ch = more();
+        }
+        final int versionEnd = pos - 1;
+        parseXmlDeclWithVersion(versionStart, versionEnd);
+        preventBufferCompaction = false; // alow again buffer commpaction - pos MAY chnage
+    }
+    //protected String xmlDeclVersion;
+
+    protected void parseXmlDeclWithVersion(int versionStart, int versionEnd)
+        throws XmlPullParserException, IOException
+    {
+        String oldEncoding = this.inputEncoding;
+
+        // check version is "1.0"
+        if((versionEnd - versionStart != 3)
+               || buf[versionStart] != '1'
+               || buf[versionStart+1] != '.'
+               || buf[versionStart+2] != '0')
+        {
+            throw new XmlPullParserException(
+                "only 1.0 is supported as <?xml version not '"
+                    +printable(new String(buf, versionStart, versionEnd - versionStart))+"'", this, null);
+        }
+        xmlDeclVersion = newString(buf, versionStart, versionEnd - versionStart);
+
+        // [80] EncodingDecl ::= S 'encoding' Eq ('"' EncName '"' | "'" EncName "'" )
+        char ch = more();
+        ch = skipS(ch);
+        if(ch == 'e') {
+            ch = more();
+            ch = requireInput(ch, NCODING);
+            ch = skipS(ch);
+            if(ch != '=') {
+                throw new XmlPullParserException(
+                    "expected equals sign (=) after encoding and not "+printable(ch), this, null);
+            }
+            ch = more();
+            ch = skipS(ch);
+            if(ch != '\'' && ch != '"') {
+                throw new XmlPullParserException(
+                    "expected apostrophe (') or quotation mark (\") after encoding and not "
+                        +printable(ch), this, null);
+            }
+            final char quotChar = ch;
+            final int encodingStart = pos;
+            ch = more();
+            // [81] EncName ::= [A-Za-z] ([A-Za-z0-9._] | '-')*
+            if((ch  < 'a' || ch > 'z') && (ch  < 'A' || ch > 'Z'))
+            {
+                throw new XmlPullParserException(
+                    "<?xml encoding name expected to start with [A-Za-z]"
+                        +" not "+printable(ch), this, null);
+            }
+            ch = more();
+            while(ch != quotChar) {
+                if((ch  < 'a' || ch > 'z') && (ch  < 'A' || ch > 'Z') && (ch  < '0' || ch > '9')
+                       && ch != '.' && ch != '_' && ch != '-')
+                {
+                    throw new XmlPullParserException(
+                        "<?xml encoding value expected to be in ([A-Za-z0-9._] | '-')"
+                            +" not "+printable(ch), this, null);
+                }
+                ch = more();
+            }
+            final int encodingEnd = pos - 1;
+
+
+            // TODO reconcile with setInput encodingName
+            inputEncoding = newString(buf, encodingStart, encodingEnd - encodingStart);
+            ch = more();
+        }
+
+        ch = skipS(ch);
+        // [32] SDDecl ::= S 'standalone' Eq (("'" ('yes' | 'no') "'") | ('"' ('yes' | 'no') '"'))
+        if(ch == 's') {
+            ch = more();
+            ch = requireInput(ch, TANDALONE);
+            ch = skipS(ch);
+            if(ch != '=') {
+                throw new XmlPullParserException(
+                    "expected equals sign (=) after standalone and not "+printable(ch),
+                    this, null);
+            }
+            ch = more();
+            ch = skipS(ch);
+            if(ch != '\'' && ch != '"') {
+                throw new XmlPullParserException(
+                    "expected apostrophe (') or quotation mark (\") after encoding and not "
+                        +printable(ch), this, null);
+            }
+            char quotChar = ch;
+            int standaloneStart = pos;
+            ch = more();
+            if(ch == 'y') {
+                ch = requireInput(ch, YES);
+                //Boolean standalone = new Boolean(true);
+                xmlDeclStandalone = new Boolean(true);
+            } else if(ch == 'n') {
+                ch = requireInput(ch, NO);
+                //Boolean standalone = new Boolean(false);
+                xmlDeclStandalone = new Boolean(false);
+            } else {
+                throw new XmlPullParserException(
+                    "expected 'yes' or 'no' after standalone and not "
+                        +printable(ch), this, null);
+            }
+            if(ch != quotChar) {
+                throw new XmlPullParserException(
+                    "expected "+quotChar+" after standalone value not "
+                        +printable(ch), this, null);
+            }
+            ch = more();
+        }
+
+
+        ch = skipS(ch);
+        if(ch != '?') {
+            throw new XmlPullParserException(
+                "expected ?> as last part of <?xml not "
+                    +printable(ch), this, null);
+        }
+        ch = more();
+        if(ch != '>') {
+            throw new XmlPullParserException(
+                "expected ?> as last part of <?xml not "
+                    +printable(ch), this, null);
+        }
+
+// NOTE: this code is broken as for some types of input streams (URLConnection ...)
+// it is not possible to do more than once new InputStreamReader(inputStream)
+// as it somehow detects it and closes undelrying inout stram (b.....d!)
+// In future one will need better low level byte-by-byte reading of prolog and then doing InputStream ...
+// for more details see http://www.extreme.indiana.edu/bugzilla/show_bug.cgi?id=135
+        //        //reset input stream
+//        if ((this.inputEncoding != oldEncoding) && (this.inputStream != null)) {
+//            if ((this.inputEncoding != null) && (!this.inputEncoding.equalsIgnoreCase(oldEncoding))) {
+//                //              //there is need to reparse input to set location OK
+//                //              reset();
+//                this.reader = new InputStreamReader(this.inputStream, this.inputEncoding);
+//                //              //skip <?xml
+//                //              for (int i = 0; i < 5; i++){
+//                //                  ch=more();
+//                //              }
+//                //              parseXmlDecl(ch);
+//            }
+//        }
+    }
+    protected void parseDocdecl()
+        throws XmlPullParserException, IOException
+    {
+        //ASSUMPTION: seen <!D
+        char ch = more();
+        if(ch != 'O') throw new XmlPullParserException(
+                "expected <!DOCTYPE", this, null);
+        ch = more();
+        if(ch != 'C') throw new XmlPullParserException(
+                "expected <!DOCTYPE", this, null);
+        ch = more();
+        if(ch != 'T') throw new XmlPullParserException(
+                "expected <!DOCTYPE", this, null);
+        ch = more();
+        if(ch != 'Y') throw new XmlPullParserException(
+                "expected <!DOCTYPE", this, null);
+        ch = more();
+        if(ch != 'P') throw new XmlPullParserException(
+                "expected <!DOCTYPE", this, null);
+        ch = more();
+        if(ch != 'E') throw new XmlPullParserException(
+                "expected <!DOCTYPE", this, null);
+        posStart = pos;
+        // do simple and crude scanning for end of doctype
+
+        // [28]  doctypedecl ::= '<!DOCTYPE' S Name (S ExternalID)? S? ('['
+        //                      (markupdecl | DeclSep)* ']' S?)? '>'
+        int bracketLevel = 0;
+        final boolean normalizeIgnorableWS = tokenize == true && roundtripSupported == false;
+        boolean normalizedCR = false;
+        while(true) {
+            ch = more();
+            if(ch == '[') ++bracketLevel;
+            if(ch == ']') --bracketLevel;
+            if(ch == '>' && bracketLevel == 0) break;
+            if(normalizeIgnorableWS) {
+                if(ch == '\r') {
+                    normalizedCR = true;
+                    //posEnd = pos -1;
+                    //joinPC();
+                    // posEnd is alreadys set
+                    if(!usePC) {
+                        posEnd = pos -1;
+                        if(posEnd > posStart) {
+                            joinPC();
+                        } else {
+                            usePC = true;
+                            pcStart = pcEnd = 0;
+                        }
+                    }
+                    //assert usePC == true;
+                    if(pcEnd >= pc.length) ensurePC(pcEnd);
+                    pc[pcEnd++] = '\n';
+                } else if(ch == '\n') {
+                    if(!normalizedCR && usePC) {
+                        if(pcEnd >= pc.length) ensurePC(pcEnd);
+                        pc[pcEnd++] = '\n';
+                    }
+                    normalizedCR = false;
+                } else {
+                    if(usePC) {
+                        if(pcEnd >= pc.length) ensurePC(pcEnd);
+                        pc[pcEnd++] = ch;
+                    }
+                    normalizedCR = false;
+                }
+            }
+
+        }
+        posEnd = pos - 1;
+    }
+
+    protected void parseCDSect(boolean hadCharData)
+        throws XmlPullParserException, IOException
+    {
+        // implements XML 1.0 Section 2.7 CDATA Sections
+
+        // [18] CDSect ::= CDStart CData CDEnd
+        // [19] CDStart ::=  '<![CDATA['
+        // [20] CData ::= (Char* - (Char* ']]>' Char*))
+        // [21] CDEnd ::= ']]>'
+
+        //ASSUMPTION: seen <![
+        char ch = more();
+        if(ch != 'C') throw new XmlPullParserException(
+                "expected <[CDATA[ for comment start", this, null);
+        ch = more();
+        if(ch != 'D') throw new XmlPullParserException(
+                "expected <[CDATA[ for comment start", this, null);
+        ch = more();
+        if(ch != 'A') throw new XmlPullParserException(
+                "expected <[CDATA[ for comment start", this, null);
+        ch = more();
+        if(ch != 'T') throw new XmlPullParserException(
+                "expected <[CDATA[ for comment start", this, null);
+        ch = more();
+        if(ch != 'A') throw new XmlPullParserException(
+                "expected <[CDATA[ for comment start", this, null);
+        ch = more();
+        if(ch != '[') throw new XmlPullParserException(
+                "expected <![CDATA[ for comment start", this, null);
+
+        //if(tokenize) {
+        final int cdStart = pos + bufAbsoluteStart;
+        final int curLine = lineNumber;
+        final int curColumn = columnNumber;
+        final boolean normalizeInput = tokenize == false || roundtripSupported == false;
+        try {
+            if(normalizeInput) {
+                if(hadCharData) {
+                    if(!usePC) {
+                        // posEnd is correct already!!!
+                        if(posEnd > posStart) {
+                            joinPC();
+                        } else {
+                            usePC = true;
+                            pcStart = pcEnd = 0;
+                        }
+                    }
+                }
+            }
+            boolean seenBracket = false;
+            boolean seenBracketBracket = false;
+            boolean normalizedCR = false;
+            while(true) {
+                // scan until it hits "]]>"
+                ch = more();
+                if(ch == ']') {
+                    if(!seenBracket) {
+                        seenBracket = true;
+                    } else {
+                        seenBracketBracket = true;
+                        //seenBracket = false;
+                    }
+                } else if(ch == '>') {
+                    if(seenBracket && seenBracketBracket) {
+                        break;  // found end sequence!!!!
+                    } else {
+                        seenBracketBracket = false;
+                    }
+                    seenBracket = false;
+                } else {
+                    if(seenBracket) {
+                        seenBracket = false;
+                    }
+                }
+                if(normalizeInput) {
+                    // deal with normalization issues ...
+                    if(ch == '\r') {
+                        normalizedCR = true;
+                        posStart = cdStart - bufAbsoluteStart;
+                        posEnd = pos - 1; // posEnd is alreadys set
+                        if(!usePC) {
+                            if(posEnd > posStart) {
+                                joinPC();
+                            } else {
+                                usePC = true;
+                                pcStart = pcEnd = 0;
+                            }
+                        }
+                        //assert usePC == true;
+                        if(pcEnd >= pc.length) ensurePC(pcEnd);
+                        pc[pcEnd++] = '\n';
+                    } else if(ch == '\n') {
+                        if(!normalizedCR && usePC) {
+                            if(pcEnd >= pc.length) ensurePC(pcEnd);
+                            pc[pcEnd++] = '\n';
+                        }
+                        normalizedCR = false;
+                    } else {
+                        if(usePC) {
+                            if(pcEnd >= pc.length) ensurePC(pcEnd);
+                            pc[pcEnd++] = ch;
+                        }
+                        normalizedCR = false;
+                    }
+                }
+            }
+        } catch(EOFException ex) {
+            // detect EOF and create meaningful error ...
+            throw new XmlPullParserException(
+                "CDATA section started on line "+curLine+" and column "+curColumn+" was not closed",
+                this, ex);
+        }
+        if(normalizeInput) {
+            if(usePC) {
+                pcEnd = pcEnd - 2;
+            }
+        }
+        posStart = cdStart - bufAbsoluteStart;
+        posEnd = pos - 3;
+    }
+
+    protected void fillBuf() throws IOException, XmlPullParserException {
+        if(reader == null) throw new XmlPullParserException(
+                "reader must be set before parsing is started");
+
+        // see if we are in compaction area
+        if(bufEnd > bufSoftLimit) {
+
+            // expand buffer it makes sense!!!!
+            boolean compact = bufStart > bufSoftLimit;
+            boolean expand = false;
+            if(preventBufferCompaction) {
+                compact = false;
+                expand = true;
+            } else if(!compact) {
+                //freeSpace
+                if(bufStart < buf.length / 2) {
+                    // less then half buffer available forcompactin --> expand instead!!!
+                    expand = true;
+                } else {
+                    // at least half of buffer can be reclaimed --> worthwhile effort!!!
+                    compact = true;
+                }
+            }
+
+            // if buffer almost full then compact it
+            if(compact) {
+                //TODO: look on trashing
+                // //assert bufStart > 0
+                System.arraycopy(buf, bufStart, buf, 0, bufEnd - bufStart);
+                if(TRACE_SIZING) System.out.println(
+                        "TRACE_SIZING fillBuf() compacting "+bufStart
+                            +" bufEnd="+bufEnd
+                            +" pos="+pos+" posStart="+posStart+" posEnd="+posEnd
+                            +" buf first 100 chars:"+new String(buf, bufStart,
+                                                                bufEnd - bufStart < 100 ? bufEnd - bufStart : 100 ));
+
+            } else if(expand) {
+                final int newSize = 2 * buf.length;
+                final char newBuf[] = new char[ newSize ];
+                if(TRACE_SIZING) System.out.println("TRACE_SIZING fillBuf() "+buf.length+" => "+newSize);
+                System.arraycopy(buf, bufStart, newBuf, 0, bufEnd - bufStart);
+                buf = newBuf;
+                if(bufLoadFactor > 0) {
+                    //bufSoftLimit = ( bufLoadFactor * buf.length ) /100;
+                    bufSoftLimit = (int) (( ((long) bufLoadFactor) * buf.length ) /100);
+                }
+
+            } else {
+                throw new XmlPullParserException("internal error in fillBuffer()");
+            }
+            bufEnd -= bufStart;
+            pos -= bufStart;
+            posStart -= bufStart;
+            posEnd -= bufStart;
+            bufAbsoluteStart += bufStart;
+            bufStart = 0;
+            if(TRACE_SIZING) System.out.println(
+                    "TRACE_SIZING fillBuf() after bufEnd="+bufEnd
+                        +" pos="+pos+" posStart="+posStart+" posEnd="+posEnd
+                        +" buf first 100 chars:"+new String(buf, 0, bufEnd < 100 ? bufEnd : 100));
+        }
+        // at least one character must be read or error
+        final int len = buf.length - bufEnd > READ_CHUNK_SIZE ? READ_CHUNK_SIZE : buf.length - bufEnd;
+        final int ret = reader.read(buf, bufEnd, len);
+        if(ret > 0) {
+            bufEnd += ret;
+            if(TRACE_SIZING) System.out.println(
+                    "TRACE_SIZING fillBuf() after filling in buffer"
+                        +" buf first 100 chars:"+new String(buf, 0, bufEnd < 100 ? bufEnd : 100));
+
+            return;
+        }
+        if(ret == -1) {
+            if(bufAbsoluteStart == 0 && pos == 0) {
+                throw new EOFException("input contained no data");
+            } else {
+                if(seenRoot && depth == 0) { // inside parsing epilog!!!
+                    reachedEnd = true;
+                    return;
+                } else {
+                    StringBuffer expectedTagStack = new StringBuffer();
+                    if(depth > 0) {
+                        //final char[] cbuf = elRawName[depth];
+                        //final String startname = new String(cbuf, 0, elRawNameEnd[depth]);
+                        expectedTagStack.append(" - expected end tag");
+                        if(depth > 1) {
+                            expectedTagStack.append("s"); //more than one end tag
+                        }
+                        expectedTagStack.append(" ");
+                        for (int i = depth; i > 0; i--)
+                        {
+                            String tagName = new String(elRawName[i], 0, elRawNameEnd[i]);
+                            expectedTagStack.append("</").append(tagName).append('>');
+                        }
+                        expectedTagStack.append(" to close");
+                        for (int i = depth; i > 0; i--)
+                        {
+                            if(i != depth) {
+                                expectedTagStack.append(" and"); //more than one end tag
+                            }
+                            String tagName = new String(elRawName[i], 0, elRawNameEnd[i]);
+                            expectedTagStack.append(" start tag <"+tagName+">");
+                            expectedTagStack.append(" from line "+elRawNameLine[i]);
+                        }
+                        expectedTagStack.append(", parser stopped on");
+                    }
+                    throw new EOFException("no more data available"
+                                               +expectedTagStack.toString()+getPositionDescription());
+                }
+            }
+        } else {
+            throw new IOException("error reading input, returned "+ret);
+        }
+    }
+
+    protected char more() throws IOException, XmlPullParserException {
+        if(pos >= bufEnd) {
+            fillBuf();
+            // this return value should be ignonored as it is used in epilog parsing ...
+            if(reachedEnd) return (char)-1;
+        }
+        final char ch = buf[pos++];
+        //line/columnNumber
+        if(ch == '\n') { ++lineNumber; columnNumber = 1; }
+        else { ++columnNumber; }
+        //System.out.print(ch);
+        return ch;
+    }
+
+    //    /**
+    //     * This function returns position of parser in XML input stream
+    //     * (how many <b>characters</b> were processed.
+    //     * <p><b>NOTE:</b> this logical position and not byte offset as encodings
+    //     * such as UTF8 may use more than one byte to encode one character.
+    //     */
+    //    public int getCurrentInputPosition() {
+    //        return pos + bufAbsoluteStart;
+    //    }
+
+    protected void ensurePC(int end) {
+        //assert end >= pc.length;
+        final int newSize = end > READ_CHUNK_SIZE ? 2 * end : 2 * READ_CHUNK_SIZE;
+        final char[] newPC = new char[ newSize ];
+        if(TRACE_SIZING) System.out.println("TRACE_SIZING ensurePC() "+pc.length+" ==> "+newSize+" end="+end);
+        System.arraycopy(pc, 0, newPC, 0, pcEnd);
+        pc = newPC;
+        //assert end < pc.length;
+    }
+
+    protected void joinPC() {
+        //assert usePC == false;
+        //assert posEnd > posStart;
+        final int len = posEnd - posStart;
+        final int newEnd = pcEnd + len + 1;
+        if(newEnd >= pc.length) ensurePC(newEnd); // add 1 for extra space for one char
+        //assert newEnd < pc.length;
+        System.arraycopy(buf, posStart, pc, pcEnd, len);
+        pcEnd += len;
+        usePC = true;
+
+    }
+
+    protected char requireInput(char ch, char[] input)
+        throws XmlPullParserException, IOException
+    {
+        for (int i = 0; i < input.length; i++)
+        {
+            if(ch != input[i]) {
+                throw new XmlPullParserException(
+                    "expected "+printable(input[i])+" in "+new String(input)
+                        +" and not "+printable(ch), this, null);
+            }
+            ch = more();
+        }
+        return ch;
+    }
+
+    protected char requireNextS()
+        throws XmlPullParserException, IOException
+    {
+        final char ch = more();
+        if(!isS(ch)) {
+            throw new XmlPullParserException(
+                "white space is required and not "+printable(ch), this, null);
+        }
+        return skipS(ch);
+    }
+
+    protected char skipS(char ch)
+        throws XmlPullParserException, IOException
+    {
+        while(isS(ch)) { ch = more(); } // skip additional spaces
+        return ch;
+    }
+
+    // nameStart / name lookup tables based on XML 1.1 http://www.w3.org/TR/2001/WD-xml11-20011213/
+    protected static final int LOOKUP_MAX = 0x400;
+    protected static final char LOOKUP_MAX_CHAR = (char)LOOKUP_MAX;
+    //    protected static int lookupNameStartChar[] = new int[ LOOKUP_MAX_CHAR / 32 ];
+    //    protected static int lookupNameChar[] = new int[ LOOKUP_MAX_CHAR / 32 ];
+    protected static boolean lookupNameStartChar[] = new boolean[ LOOKUP_MAX ];
+    protected static boolean lookupNameChar[] = new boolean[ LOOKUP_MAX ];
+
+    private static final void setName(char ch)
+        //{ lookupNameChar[ (int)ch / 32 ] |= (1 << (ch % 32)); }
+    { lookupNameChar[ ch ] = true; }
+    private static final void setNameStart(char ch)
+        //{ lookupNameStartChar[ (int)ch / 32 ] |= (1 << (ch % 32)); setName(ch); }
+    { lookupNameStartChar[ ch ] = true; setName(ch); }
+
+    static {
+        setNameStart(':');
+        for (char ch = 'A'; ch <= 'Z'; ++ch) setNameStart(ch);
+        setNameStart('_');
+        for (char ch = 'a'; ch <= 'z'; ++ch) setNameStart(ch);
+        for (char ch = '\u00c0'; ch <= '\u02FF'; ++ch) setNameStart(ch);
+        for (char ch = '\u0370'; ch <= '\u037d'; ++ch) setNameStart(ch);
+        for (char ch = '\u037f'; ch < '\u0400'; ++ch) setNameStart(ch);
+
+        setName('-');
+        setName('.');
+        for (char ch = '0'; ch <= '9'; ++ch) setName(ch);
+        setName('\u00b7');
+        for (char ch = '\u0300'; ch <= '\u036f'; ++ch) setName(ch);
+    }
+
+    //private final static boolean isNameStartChar(char ch) {
+    protected boolean isNameStartChar(char ch) {
+        return (ch < LOOKUP_MAX_CHAR && lookupNameStartChar[ ch ])
+            || (ch >= LOOKUP_MAX_CHAR && ch <= '\u2027')
+            || (ch >= '\u202A' &&  ch <= '\u218F')
+            || (ch >= '\u2800' &&  ch <= '\uFFEF')
+            ;
+
+        //      if(ch < LOOKUP_MAX_CHAR) return lookupNameStartChar[ ch ];
+        //      else return ch <= '\u2027'
+        //              || (ch >= '\u202A' &&  ch <= '\u218F')
+        //              || (ch >= '\u2800' &&  ch <= '\uFFEF')
+        //              ;
+        //return false;
+        //        return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == ':'
+        //          || (ch >= '0' && ch <= '9');
+        //        if(ch < LOOKUP_MAX_CHAR) return (lookupNameStartChar[ (int)ch / 32 ] & (1 << (ch % 32))) != 0;
+        //        if(ch <= '\u2027') return true;
+        //        //[#x202A-#x218F]
+        //        if(ch < '\u202A') return false;
+        //        if(ch <= '\u218F') return true;
+        //        // added pairts [#x2800-#xD7FF] | [#xE000-#xFDCF] | [#xFDE0-#xFFEF] | [#x10000-#x10FFFF]
+        //        if(ch < '\u2800') return false;
+        //        if(ch <= '\uFFEF') return true;
+        //        return false;
+
+
+        // else return (supportXml11 && ( (ch < '\u2027') || (ch > '\u2029' && ch < '\u2200') ...
+    }
+
+    //private final static boolean isNameChar(char ch) {
+    protected boolean isNameChar(char ch) {
+        //return isNameStartChar(ch);
+
+        //        if(ch < LOOKUP_MAX_CHAR) return (lookupNameChar[ (int)ch / 32 ] & (1 << (ch % 32))) != 0;
+
+        return (ch < LOOKUP_MAX_CHAR && lookupNameChar[ ch ])
+            || (ch >= LOOKUP_MAX_CHAR && ch <= '\u2027')
+            || (ch >= '\u202A' &&  ch <= '\u218F')
+            || (ch >= '\u2800' &&  ch <= '\uFFEF')
+            ;
+        //return false;
+        //        return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == ':'
+        //          || (ch >= '0' && ch <= '9');
+        //        if(ch < LOOKUP_MAX_CHAR) return (lookupNameStartChar[ (int)ch / 32 ] & (1 << (ch % 32))) != 0;
+
+        //else return
+        //  else if(ch <= '\u2027') return true;
+        //        //[#x202A-#x218F]
+        //        else if(ch < '\u202A') return false;
+        //        else if(ch <= '\u218F') return true;
+        //        // added pairts [#x2800-#xD7FF] | [#xE000-#xFDCF] | [#xFDE0-#xFFEF] | [#x10000-#x10FFFF]
+        //        else if(ch < '\u2800') return false;
+        //        else if(ch <= '\uFFEF') return true;
+        //else return false;
+    }
+
+    protected boolean isS(char ch) {
+        return (ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t');
+        // || (supportXml11 && (ch == '\u0085' || ch == '\u2028');
+    }
+
+    //protected boolean isChar(char ch) { return (ch < '\uD800' || ch > '\uDFFF')
+    //  ch != '\u0000' ch < '\uFFFE'
+
+
+    //protected char printable(char ch) { return ch; }
+    protected String printable(char ch) {
+        if(ch == '\n') {
+            return "\\n";
+        } else if(ch == '\r') {
+            return "\\r";
+        } else if(ch == '\t') {
+            return "\\t";
+        } else if(ch == '\'') {
+            return "\\'";
+        } if(ch > 127 || ch < 32) {
+            return "\\u"+Integer.toHexString((int)ch);
+        }
+        return ""+ch;
+    }
+
+    protected String printable(String s) {
+        if(s == null) return null;
+        final int sLen = s.length();
+        StringBuffer buf = new StringBuffer(sLen + 10);
+        for(int i = 0; i < sLen; ++i) {
+            buf.append(printable(s.charAt(i)));
+        }
+        s = buf.toString();
+        return s;
+    }
+}
+
+
+/*
+ * Indiana University Extreme! Lab Software License, Version 1.2
+ *
+ * Copyright (C) 2003 The Trustees of Indiana University.
+ * 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) All redistributions of source code must retain the above
+ *    copyright notice, the list of authors in the original source
+ *    code, this list of conditions and the disclaimer listed in this
+ *    license;
+ *
+ * 2) All redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the disclaimer
+ *    listed in this license in the documentation and/or other
+ *    materials provided with the distribution;
+ *
+ * 3) Any documentation included with all redistributions must include
+ *    the following acknowledgement:
+ *
+ *      "This product includes software developed by the Indiana
+ *      University Extreme! Lab.  For further information please visit
+ *      http://www.extreme.indiana.edu/"
+ *
+ *    Alternatively, this acknowledgment may appear in the software
+ *    itself, and wherever such third-party acknowledgments normally
+ *    appear.
+ *
+ * 4) The name "Indiana University" or "Indiana University
+ *    Extreme! Lab" shall not be used to endorse or promote
+ *    products derived from this software without prior written
+ *    permission from Indiana University.  For written permission,
+ *    please contact http://www.extreme.indiana.edu/.
+ *
+ * 5) Products derived from this software may not use "Indiana
+ *    University" name nor may "Indiana University" appear in their name,
+ *    without prior written permission of the Indiana University.
+ *
+ * Indiana University provides no reassurances that the source code
+ * provided does not infringe the patent or any other intellectual
+ * property rights of any other entity.  Indiana University disclaims any
+ * liability to any recipient for claims brought by any other entity
+ * based on infringement of intellectual property rights or otherwise.
+ *
+ * LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH
+ * NO WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA
+ * UNIVERSITY GIVES NO WARRANTIES AND MAKES NO REPRESENTATION THAT
+ * SOFTWARE IS FREE OF INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR
+ * OTHER PROPRIETARY RIGHTS.  INDIANA UNIVERSITY MAKES NO WARRANTIES THAT
+ * SOFTWARE IS FREE FROM "BUGS", "VIRUSES", "TROJAN HORSES", "TRAP
+ * DOORS", "WORMS", OR OTHER HARMFUL CODE.  LICENSEE ASSUMES THE ENTIRE
+ * RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR ASSOCIATED MATERIALS,
+ * AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION GENERATED USING
+ * SOFTWARE.
+ */
+
diff --git a/src/main/java/org/jboss/modules/xml/XmlPullParser.java b/src/main/java/org/jboss/modules/xml/XmlPullParser.java
new file mode 100644
index 0000000..d5e7524
--- /dev/null
+++ b/src/main/java/org/jboss/modules/xml/XmlPullParser.java
@@ -0,0 +1,1133 @@
+/* -*-             c-basic-offset: 4; indent-tabs-mode: nil; -*-  //------100-columns-wide------>|*/
+// for license please see accompanying LICENSE.txt file (available also at http://www.xmlpull.org/)
+
+package org.jboss.modules.xml;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+
+/**
+ * XML Pull Parser is an interface that defines parsing functionlity provided
+ * in <a href="http://www.xmlpull.org/">XMLPULL V1 API</a> (visit this website to
+ * learn more about API and its implementations).
+ *
+ * <p>There are following different
+ * kinds of parser depending on which features are set:<ul>
+ * <li><b>non-validating</b> parser as defined in XML 1.0 spec when
+ *   FEATURE_PROCESS_DOCDECL is set to true
+ * <li><b>validating parser</b> as defined in XML 1.0 spec when
+ *   FEATURE_VALIDATION is true (and that implies that FEATURE_PROCESS_DOCDECL is true)
+ * <li>when FEATURE_PROCESS_DOCDECL is false (this is default and
+ *   if different value is required necessary must be changed before parsing is started)
+ *   then parser behaves like XML 1.0 compliant non-validating parser under condition that
+ *  <em>no DOCDECL is present</em> in XML documents
+ *   (internal entites can still be defined with defineEntityReplacementText()).
+ *   This mode of operation is intened <b>for operation in constrained environments</b> such as J2ME.
+ * </ul>
+ *
+ *
+ * <p>There are two key methods: next() and nextToken(). While next() provides
+ * access to high level parsing events, nextToken() allows access to lower
+ * level tokens.
+ *
+ * <p>The current event state of the parser
+ * can be determined by calling the
+ * <a href="#getEventType()">getEventType()</a> method.
+ * Initially, the parser is in the <a href="#START_DOCUMENT">START_DOCUMENT</a>
+ * state.
+ *
+ * <p>The method <a href="#next()">next()</a> advances the parser to the
+ * next event. The int value returned from next determines the current parser
+ * state and is identical to the value returned from following calls to
+ * getEventType ().
+ *
+ * <p>Th following event types are seen by next()<dl>
+ * <dt><a href="#START_TAG">START_TAG</a><dd> An XML start tag was read.
+ * <dt><a href="#TEXT">TEXT</a><dd> Text content was read;
+ * the text content can be retreived using the getText() method.
+ *  (when in validating mode next() will not report ignorable whitespaces, use nextToken() instead)
+ * <dt><a href="#END_TAG">END_TAG</a><dd> An end tag was read
+ * <dt><a href="#END_DOCUMENT">END_DOCUMENT</a><dd> No more events are available
+ * </dl>
+ *
+ * <p>after first next() or nextToken() (or any other next*() method)
+ * is called user application can obtain
+ * XML version, standalone and encoding from XML declaration
+ * in following ways:<ul>
+ * <li><b>version</b>:
+ *  getProperty("<a href="http://xmlpull.org/v1/doc/properties.html#xmldecl-version">http://xmlpull.org/v1/doc/properties.html#xmldecl-version</a>")
+ *       returns String ("1.0") or null if XMLDecl was not read or if property is not supported
+ * <li><b>standalone</b>:
+ *  getProperty("<a href="http://xmlpull.org/v1/doc/features.html#xmldecl-standalone">http://xmlpull.org/v1/doc/features.html#xmldecl-standalone</a>")
+ *       returns Boolean: null if there was no standalone declaration
+ *  or if property is not supported
+ *         otherwise returns Boolean(true) if standalon="yes" and Boolean(false) when standalone="no"
+ * <li><b>encoding</b>: obtained from getInputEncoding()
+ *       null if stream had unknown encoding (not set in setInputStream)
+ *           and it was not declared in XMLDecl
+ * </ul>
+ *
+ * A minimal example for using this API may look as follows:
+ * <pre>
+ * import java.io.IOException;
+ * import java.io.StringReader;
+ *
+ * import org.xmlpull.v1.XmlPullParser;
+ * import org.xmlpull.v1.<a href="XmlPullParserException.html">XmlPullParserException.html</a>;
+ * import org.xmlpull.v1.<a href="XmlPullParserFactory.html">XmlPullParserFactory</a>;
+ *
+ * public class SimpleXmlPullApp
+ * {
+ *
+ *     public static void main (String args[])
+ *         throws XmlPullParserException, IOException
+ *     {
+ *         XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
+ *         factory.setNamespaceAware(true);
+ *         XmlPullParser xpp = factory.newPullParser();
+ *
+ *         xpp.<a href="#setInput">setInput</a>( new StringReader ( "<foo>Hello World!</foo>" ) );
+ *         int eventType = xpp.getEventType();
+ *         while (eventType != XmlPullParser.END_DOCUMENT) {
+ *          if(eventType == XmlPullParser.START_DOCUMENT) {
+ *              System.out.println("Start document");
+ *          } else if(eventType == XmlPullParser.END_DOCUMENT) {
+ *              System.out.println("End document");
+ *          } else if(eventType == XmlPullParser.START_TAG) {
+ *              System.out.println("Start tag "+xpp.<a href="#getName()">getName()</a>);
+ *          } else if(eventType == XmlPullParser.END_TAG) {
+ *              System.out.println("End tag "+xpp.getName());
+ *          } else if(eventType == XmlPullParser.TEXT) {
+ *              System.out.println("Text "+xpp.<a href="#getText()">getText()</a>);
+ *          }
+ *          eventType = xpp.next();
+ *         }
+ *     }
+ * }
+ * </pre>
+ *
+ * <p>The above example will generate the following output:
+ * <pre>
+ * Start document
+ * Start tag foo
+ * Text Hello World!
+ * End tag foo
+ * </pre>
+ *
+ * <p>For more details on API usage, please refer to the
+ * quick Introduction available at <a href="http://www.xmlpull.org">http://www.xmlpull.org</a>
+ *
+ * @see XmlPullParserFactory
+ * @see #defineEntityReplacementText
+ * @see #getName
+ * @see #getNamespace
+ * @see #getText
+ * @see #next
+ * @see #nextToken
+ * @see #setInput
+ * @see #FEATURE_PROCESS_DOCDECL
+ * @see #FEATURE_VALIDATION
+ * @see #START_DOCUMENT
+ * @see #START_TAG
+ * @see #TEXT
+ * @see #END_TAG
+ * @see #END_DOCUMENT
+ *
+ * @author <a href="http://www-ai.cs.uni-dortmund.de/PERSONAL/haustein.html">Stefan Haustein</a>
+ * @author <a href="http://www.extreme.indiana.edu/~aslom/">Aleksander Slominski</a>
+ */
+
+public interface XmlPullParser {
+
+    /** This constant represents the default namespace (empty string "") */
+    String NO_NAMESPACE = "";
+
+    // ----------------------------------------------------------------------------
+    // EVENT TYPES as reported by next()
+
+    /**
+     * Signalize that parser is at the very beginning of the document
+     * and nothing was read yet.
+     * This event type can only be observed by calling getEvent()
+     * before the first call to next(), nextToken, or nextTag()</a>).
+     *
+     * @see #next
+     * @see #nextToken
+     */
+    int START_DOCUMENT = 0;
+
+    /**
+     * Logical end of the xml document. Returned from getEventType, next()
+     * and nextToken()
+     * when the end of the input document has been reached.
+     * <p><strong>NOTE:</strong> calling again
+     * <a href="#next()">next()</a> or <a href="#nextToken()">nextToken()</a>
+     * will result in exception being thrown.
+     *
+     * @see #next
+     * @see #nextToken
+     */
+    int END_DOCUMENT = 1;
+
+    /**
+     * Returned from getEventType(),
+     * <a href="#next()">next()</a>, <a href="#nextToken()">nextToken()</a> when
+     * a start tag was read.
+     * The name of start tag is available from getName(), its namespace and prefix are
+     * available from getNamespace() and getPrefix()
+     * if <a href='#FEATURE_PROCESS_NAMESPACES'>namespaces are enabled</a>.
+     * See getAttribute* methods to retrieve element attributes.
+     * See getNamespace* methods to retrieve newly declared namespaces.
+     *
+     * @see #next
+     * @see #nextToken
+     * @see #getName
+     * @see #getPrefix
+     * @see #getNamespace
+     * @see #getAttributeCount
+     * @see #getDepth
+     * @see #getNamespaceCount
+     * @see #getNamespace
+     * @see #FEATURE_PROCESS_NAMESPACES
+     */
+    int START_TAG = 2;
+
+    /**
+     * Returned from getEventType(), <a href="#next()">next()</a>, or
+     * <a href="#nextToken()">nextToken()</a> when an end tag was read.
+     * The name of start tag is available from getName(), its
+     * namespace and prefix are
+     * available from getNamespace() and getPrefix().
+     *
+     * @see #next
+     * @see #nextToken
+     * @see #getName
+     * @see #getPrefix
+     * @see #getNamespace
+     * @see #FEATURE_PROCESS_NAMESPACES
+     */
+    int END_TAG = 3;
+
+
+    /**
+     * Character data was read and will is available by calling getText().
+     * <p><strong>Please note:</strong> <a href="#next()">next()</a> will
+     * accumulate multiple
+     * events into one TEXT event, skipping IGNORABLE_WHITESPACE,
+     * PROCESSING_INSTRUCTION and COMMENT events,
+     * In contrast, <a href="#nextToken()">nextToken()</a> will stop reading
+     * text when any other event is observed.
+     * Also, when the state was reached by calling next(), the text value will
+     * be normalized, whereas getText() will
+     * return unnormalized content in the case of nextToken(). This allows
+     * an exact roundtrip without chnanging line ends when examining low
+     * level events, whereas for high level applications the text is
+     * normalized apropriately.
+     *
+     * @see #next
+     * @see #nextToken
+     * @see #getText
+     */
+    int TEXT = 4;
+
+    // ----------------------------------------------------------------------------
+    // additional events exposed by lower level nextToken()
+
+    /**
+     * A CDATA sections was just read;
+     * this token is available only from calls to <a href="#nextToken()">nextToken()</a>.
+     * A call to next() will accumulate various text events into a single event
+     * of type TEXT. The text contained in the CDATA section is available
+     * by callling getText().
+     *
+     * @see #nextToken
+     * @see #getText
+     */
+    int CDSECT = 5;
+
+    /**
+     * An entity reference was just read;
+     * this token is available from <a href="#nextToken()">nextToken()</a>
+     * only. The entity name is available by calling getName(). If available,
+     * the replacement text can be obtained by calling getTextt(); otherwise,
+     * the user is responsibile for resolving the entity reference.
+     * This event type is never returned from next(); next() will
+     * accumulate the replacement text and other text
+     * events to a single TEXT event.
+     *
+     * @see #nextToken
+     * @see #getText
+     */
+    int ENTITY_REF = 6;
+
+    /**
+     * Ignorable whitespace was just read.
+     * This token is available only from <a href="#nextToken()">nextToken()</a>).
+     * For non-validating
+     * parsers, this event is only reported by nextToken() when outside
+     * the root element.
+     * Validating parsers may be able to detect ignorable whitespace at
+     * other locations.
+     * The ignorable whitespace string is available by calling getText()
+     *
+     * <p><strong>NOTE:</strong> this is different from calling the
+     *  isWhitespace() method, since text content
+     *  may be whitespace but not ignorable.
+     *
+     * Ignorable whitespace is skipped by next() automatically; this event
+     * type is never returned from next().
+     *
+     * @see #nextToken
+     * @see #getText
+     */
+    int IGNORABLE_WHITESPACE = 7;
+
+    /**
+     * An XML processing instruction declaration was just read. This
+     * event type is available only via <a href="#nextToken()">nextToken()</a>.
+     * getText() will return text that is inside the processing instruction.
+     * Calls to next() will skip processing instructions automatically.
+     * @see #nextToken
+     * @see #getText
+     */
+    int PROCESSING_INSTRUCTION = 8;
+
+    /**
+     * An XML comment was just read. This event type is this token is
+     * available via <a href="#nextToken()">nextToken()</a> only;
+     * calls to next() will skip comments automatically.
+     * The content of the comment can be accessed using the getText()
+     * method.
+     *
+     * @see #nextToken
+     * @see #getText
+     */
+    int COMMENT = 9;
+
+    /**
+     * An XML document type declaration was just read. This token is
+     * available from <a href="#nextToken()">nextToken()</a> only.
+     * The unparsed text inside the doctype is available via
+     * the getText() method.
+     *
+     * @see #nextToken
+     * @see #getText
+     */
+    int DOCDECL = 10;
+
+    /**
+     * This array can be used to convert the event type integer constants
+     * such as START_TAG or TEXT to
+     * to a string. For example, the value of TYPES[START_TAG] is
+     * the string "START_TAG".
+     *
+     * This array is intended for diagnostic output only. Relying
+     * on the contents of the array may be dangerous since malicous
+     * applications may alter the array, although it is final, due
+     * to limitations of the Java language.
+     */
+    String [] TYPES = {
+        "START_DOCUMENT",
+            "END_DOCUMENT",
+            "START_TAG",
+            "END_TAG",
+            "TEXT",
+            "CDSECT",
+            "ENTITY_REF",
+            "IGNORABLE_WHITESPACE",
+            "PROCESSING_INSTRUCTION",
+            "COMMENT",
+            "DOCDECL"
+    };
+
+
+    // ----------------------------------------------------------------------------
+    // namespace related features
+
+    /**
+     * This feature determines whether the parser processes
+     * namespaces. As for all features, the default value is false.
+     * <p><strong>NOTE:</strong> The value can not be changed during
+     * parsing an must be set before parsing.
+     *
+     * @see #getFeature
+     * @see #setFeature
+     */
+    String FEATURE_PROCESS_NAMESPACES =
+        "http://xmlpull.org/v1/doc/features.html#process-namespaces";
+
+    /**
+     * This feature determines whether namespace attributes are
+     * exposed via the attribute access methods. Like all features,
+     * the default value is false. This feature cannot be changed
+     * during parsing.
+     *
+     * @see #getFeature
+     * @see #setFeature
+     */
+    String FEATURE_REPORT_NAMESPACE_ATTRIBUTES =
+        "http://xmlpull.org/v1/doc/features.html#report-namespace-prefixes";
+
+    /**
+     * This feature determines whether the document declaration
+     * is processed. If set to false,
+     * the DOCDECL event type is reported by nextToken()
+     * and ignored by next().
+     *
+     * If this featue is activated, then the document declaration
+     * must be processed by the parser.
+     *
+     * <p><strong>Please note:</strong> If the document type declaration
+     * was ignored, entity references may cause exceptions
+     * later in the parsing process.
+     * The default value of this feature is false. It cannot be changed
+     * during parsing.
+     *
+     * @see #getFeature
+     * @see #setFeature
+     */
+    String FEATURE_PROCESS_DOCDECL =
+        "http://xmlpull.org/v1/doc/features.html#process-docdecl";
+
+    /**
+     * If this feature is activated, all validation errors as
+     * defined in the XML 1.0 sepcification are reported.
+     * This implies that FEATURE_PROCESS_DOCDECL is true and both, the
+     * internal and external document type declaration will be processed.
+     * <p><strong>Please Note:</strong> This feature can not be changed
+     * during parsing. The default value is false.
+     *
+     * @see #getFeature
+     * @see #setFeature
+     */
+    String FEATURE_VALIDATION =
+        "http://xmlpull.org/v1/doc/features.html#validation";
+
+    /**
+     * Use this call to change the general behaviour of the parser,
+     * such as namespace processing or doctype declaration handling.
+     * This method must be called before the first call to next or
+     * nextToken. Otherwise, an exception is thrown.
+     * <p>Example: call setFeature(FEATURE_PROCESS_NAMESPACES, true) in order
+     * to switch on namespace processing. The initial settings correspond
+     * to the properties requested from the XML Pull Parser factory.
+     * If none were requested, all feautures are deactivated by default.
+     *
+     * @exception XmlPullParserException If the feature is not supported or can not be set
+     * @exception IllegalArgumentException If string with the feature name is null
+     */
+    void setFeature(String name, boolean state) throws XmlPullParserException;
+
+    /**
+     * Returns the current value of the given feature.
+     * <p><strong>Please note:</strong> unknown features are
+     * <strong>always</strong> returned as false.
+     *
+     * @param name The name of feature to be retrieved.
+     * @return The value of the feature.
+     * @exception IllegalArgumentException if string the feature name is null
+     */
+
+    boolean getFeature(String name);
+
+    /**
+     * Set the value of a property.
+     *
+     * The property name is any fully-qualified URI.
+     *
+     * @exception XmlPullParserException If the property is not supported or can not be set
+     * @exception IllegalArgumentException If string with the property name is null
+     */
+    void setProperty(String name, Object value) throws XmlPullParserException;
+
+    /**
+     * Look up the value of a property.
+     *
+     * The property name is any fully-qualified URI.
+     * <p><strong>NOTE:</strong> unknown properties are <strong>always</strong>
+     * returned as null.
+     *
+     * @param name The name of property to be retrieved.
+     * @return The value of named property.
+     */
+    Object getProperty(String name);
+
+
+    /**
+     * Set the input source for parser to the given reader and
+     * resets the parser. The event type is set to the initial value
+     * START_DOCUMENT.
+     * Setting the reader to null will just stop parsing and
+     * reset parser state,
+     * allowing the parser to free internal resources
+     * such as parsing buffers.
+     */
+    void setInput(Reader in) throws XmlPullParserException;
+
+
+    /**
+     * Sets the input stream the parser is going to process.
+     * This call resets the parser state and sets the event type
+     * to the initial value START_DOCUMENT.
+     *
+     * <p><strong>NOTE:</strong> If an input encoding string is passed,
+     *  it MUST be used. Otherwise,
+     *  if inputEncoding is null, the parser SHOULD try to determine
+     *  input encoding following XML 1.0 specification (see below).
+     *  If encoding detection is supported then following feature
+     *  <a href="http://xmlpull.org/v1/doc/features.html#detect-encoding">http://xmlpull.org/v1/doc/features.html#detect-encoding</a>
+     *  MUST be true amd otherwise it must be false
+     *
+     * @param inputStream contains a raw byte input stream of possibly
+     *     unknown encoding (when inputEncoding is null).
+     *
+     * @param inputEncoding if not null it MUST be used as encoding for inputStream
+     */
+    void setInput(InputStream inputStream, String inputEncoding)
+        throws XmlPullParserException;
+
+    /**
+     * Returns the input encoding if known, null otherwise.
+     * If setInput(InputStream, inputEncoding) was called with an inputEncoding
+     * value other than null, this value must be returned
+     * from this method. Otherwise, if inputEncoding is null and
+     * the parser suppports the encoding detection feature
+     * (http://xmlpull.org/v1/doc/features.html#detect-encoding),
+     * it must return the detected encoding.
+     * If setInput(Reader) was called, null is returned.
+     * After first call to next if XML declaration was present this method
+     * will return encoding declared.
+     */
+    String getInputEncoding();
+
+    /**
+     * Set new value for entity replacement text as defined in
+     * <a href="http://www.w3.org/TR/REC-xml#intern-replacement">XML 1.0 Section 4.5
+     * Construction of Internal Entity Replacement Text</a>.
+     * If FEATURE_PROCESS_DOCDECL or FEATURE_VALIDATION are set, calling this
+     * function will result in an exception -- when processing of DOCDECL is
+     * enabled, there is no need to the entity replacement text manually.
+     *
+     * <p>The motivation for this function is to allow very small
+     * implementations of XMLPULL that will work in J2ME environments.
+     * Though these implementations may not be able to process the document type
+     * declaration, they still can work with known DTDs by using this function.
+     *
+     * <p><b>Please notes:</b> The given value is used literally as replacement text
+     * and it corresponds to declaring entity in DTD that has all special characters
+     * escaped: left angle bracket is replaced with &lt;, ampersnad with &amp;
+     * and so on.
+     *
+     * <p><b>Note:</b> The given value is the literal replacement text and must not
+     * contain any other entity reference (if it contains any entity reference
+     * there will be no further replacement).
+     *
+     * <p><b>Note:</b> The list of pre-defined entity names will
+     * always contain standard XML entities such as
+     * amp (&amp;), lt (&lt;), gt (&gt;), quot (&quot;), and apos (&apos;).
+     * Those cannot be redefined by this method!
+     *
+     * @see #setInput
+     * @see #FEATURE_PROCESS_DOCDECL
+     * @see #FEATURE_VALIDATION
+     */
+    void defineEntityReplacementText(String entityName, String replacementText) throws XmlPullParserException;
+
+    /**
+     * Returns the numbers of elements in the namespace stack for the given
+     * depth.
+     * If namespaces are not enabled, 0 is returned.
+     *
+     * <p><b>NOTE:</b> when parser is on END_TAG then it is allowed to call
+     *  this function with getDepth()+1 argument to retrieve position of namespace
+     *  prefixes and URIs that were declared on corresponding START_TAG.
+     * <p><b>NOTE:</b> to retrieve lsit of namespaces declared in current element:<pre>
+     *       XmlPullParser pp = ...
+     *       int nsStart = pp.getNamespaceCount(pp.getDepth()-1);
+     *       int nsEnd = pp.getNamespaceCount(pp.getDepth());
+     *       for (int i = nsStart; i < nsEnd; i++) {
+     *          String prefix = pp.getNamespacePrefix(i);
+     *          String ns = pp.getNamespaceUri(i);
+     *           // ...
+     *      }
+     * </pre>
+     *
+     * @see #getNamespacePrefix
+     * @see #getNamespaceUri
+     * @see #getNamespace()
+     * @see #getNamespace(String)
+     */
+    int getNamespaceCount(int depth) throws XmlPullParserException;
+
+    /**
+     * Returns the namespace prefixe for the given position
+     * in the namespace stack.
+     * Default namespace declaration (xmlns='...') will have null as prefix.
+     * If the given index is out of range, an exception is thrown.
+     * <p><b>Please note:</b> when the parser is on an END_TAG,
+     * namespace prefixes that were declared
+     * in the corresponding START_TAG are still accessible
+     * although they are no longer in scope.
+     */
+    String getNamespacePrefix(int pos) throws XmlPullParserException;
+
+    /**
+     * Returns the namespace URI for the given position in the
+     * namespace stack
+     * If the position is out of range, an exception is thrown.
+     * <p><b>NOTE:</b> when parser is on END_TAG then namespace prefixes that were declared
+     *  in corresponding START_TAG are still accessible even though they are not in scope
+     */
+    String getNamespaceUri(int pos) throws XmlPullParserException;
+
+    /**
+     * Returns the URI corresponding to the given prefix,
+     * depending on current state of the parser.
+     *
+     * <p>If the prefix was not declared in the current scope,
+     * null is returned. The default namespace is included
+     * in the namespace table and is available via
+     * getNamespace (null).
+     *
+     * <p>This method is a convenience method for
+     *
+     * <pre>
+     *  for (int i = getNamespaceCount(getDepth ())-1; i >= 0; i--) {
+     *   if (getNamespacePrefix(i).equals( prefix )) {
+     *     return getNamespaceUri(i);
+     *   }
+     *  }
+     *  return null;
+     * </pre>
+     *
+     * <p><strong>Please note:</strong> parser implementations
+     * may provide more efifcient lookup, e.g. using a Hashtable.
+     * The 'xml' prefix is bound to "http://www.w3.org/XML/1998/namespace", as
+     * defined in the
+     * <a href="http://www.w3.org/TR/REC-xml-names/#ns-using">Namespaces in XML</a>
+     * specification. Analogous, the 'xmlns' prefix is resolved to
+     * <a href="http://www.w3.org/2000/xmlns/">http://www.w3.org/2000/xmlns/</a>
+     *
+     * @see #getNamespaceCount
+     * @see #getNamespacePrefix
+     * @see #getNamespaceUri
+     */
+    String getNamespace(String prefix);
+
+
+    // --------------------------------------------------------------------------
+    // miscellaneous reporting methods
+
+    /**
+     * Returns the current depth of the element.
+     * Outside the root element, the depth is 0. The
+     * depth is incremented by 1 when a start tag is reached.
+     * The depth is decremented AFTER the end tag
+     * event was observed.
+     *
+     * <pre>
+     * <!-- outside -->     0
+     * <root>                  1
+     *   sometext                 1
+     *     <foobar>         2
+     *     </foobar>        2
+     * </root>              1
+     * <!-- outside -->     0
+     * </pre>
+     */
+    int getDepth();
+
+    /**
+     * Returns a short text describing the current parser state, including
+     * the position, a
+     * description of the current event and the data source if known.
+     * This method is especially useful to provide meaningful
+     * error messages and for debugging purposes.
+     */
+    String getPositionDescription();
+
+
+    /**
+     * Returns the current line number, starting from 1.
+     * When the parser does not know the current line number
+     * or can not determine it,  -1 is returned (e.g. for WBXML).
+     *
+     * @return current line number or -1 if unknown.
+     */
+    int getLineNumber();
+
+    /**
+     * Returns the current column number, starting from 0.
+     * When the parser does not know the current column number
+     * or can not determine it,  -1 is returned (e.g. for WBXML).
+     *
+     * @return current column number or -1 if unknown.
+     */
+    int getColumnNumber();
+
+
+    // --------------------------------------------------------------------------
+    // TEXT related methods
+
+    /**
+     * Checks whether the current TEXT event contains only whitespace
+     * characters.
+     * For IGNORABLE_WHITESPACE, this is always true.
+     * For TEXT and CDSECT, false is returned when the current event text
+     * contains at least one non-white space character. For any other
+     * event type an exception is thrown.
+     *
+     * <p><b>Please note:</b> non-validating parsers are not
+     * able to distinguish whitespace and ignorable whitespace,
+     * except from whitespace outside the root element. Ignorable
+     * whitespace is reported as separate event, which is exposed
+     * via nextToken only.
+     *
+     */
+    boolean isWhitespace() throws XmlPullParserException;
+
+    /**
+     * Returns the text content of the current event as String.
+     * The value returned depends on current event type,
+     * for example for TEXT event it is element content
+     * (this is typical case when next() is used).
+     *
+     * See description of nextToken() for detailed description of
+     * possible returned values for different types of events.
+     *
+     * <p><strong>NOTE:</strong> in case of ENTITY_REF, this method returns
+     * the entity replacement text (or null if not available). This is
+     * the only case where
+     * getText() and getTextCharacters() return different values.
+     *
+     * @see #getEventType
+     * @see #next
+     * @see #nextToken
+     */
+    String getText();
+
+
+    /**
+     * Returns the buffer that contains the text of the current event,
+     * as well as the start offset and length relevant for the current
+     * event. See getText(), next() and nextToken() for description of possible returned values.
+     *
+     * <p><strong>Please note:</strong> this buffer must not
+     * be modified and its content MAY change after a call to
+     * next() or nextToken(). This method will always return the
+     * same value as getText(), except for ENTITY_REF. In the case
+     * of ENTITY ref, getText() returns the replacement text and
+     * this method returns the actual input buffer containing the
+     * entity name.
+     * If getText() returns null, this method returns null as well and
+     * the values returned in the holder array MUST be -1 (both start
+     * and length).
+     *
+     * @see #getText
+     * @see #next
+     * @see #nextToken
+     *
+     * @param holderForStartAndLength Must hold an 2-element int array
+     * into which the start offset and length values will be written.
+     * @return char buffer that contains the text of the current event
+     *  (null if the current event has no text associated).
+     */
+    char[] getTextCharacters(int[] holderForStartAndLength);
+
+    // --------------------------------------------------------------------------
+    // START_TAG / END_TAG shared methods
+
+    /**
+     * Returns the namespace URI of the current element.
+     * The default namespace is represented
+     * as empty string.
+     * If namespaces are not enabled, an empty String ("") is always returned.
+     * The current event must be START_TAG or END_TAG; otherwise,
+     * null is returned.
+     */
+    String getNamespace();
+
+    /**
+     * For START_TAG or END_TAG events, the (local) name of the current
+     * element is returned when namespaces are enabled. When namespace
+     * processing is disabled, the raw name is returned.
+     * For ENTITY_REF events, the entity name is returned.
+     * If the current event is not START_TAG, END_TAG, or ENTITY_REF,
+     * null is returned.
+     * <p><b>Please note:</b> To reconstruct the raw element name
+     *  when namespaces are enabled and the prefix is not null,
+     * you will need to  add the prefix and a colon to localName..
+     *
+     */
+    String getName();
+
+    /**
+     * Returns the prefix of the current element.
+     * If the element is in the default namespace (has no prefix),
+     * null is returned.
+     * If namespaces are not enabled, or the current event
+     * is not  START_TAG or END_TAG, null is returned.
+     */
+    String getPrefix();
+
+    /**
+     * Returns true if the current event is START_TAG and the tag
+     * is degenerated
+     * (e.g. <foobar/>).
+     * <p><b>NOTE:</b> if the parser is not on START_TAG, an exception
+     * will be thrown.
+     */
+    boolean isEmptyElementTag() throws XmlPullParserException;
+
+    // --------------------------------------------------------------------------
+    // START_TAG Attributes retrieval methods
+
+    /**
+     * Returns the number of attributes of the current start tag, or
+     * -1 if the current event type is not START_TAG
+     *
+     * @see #getAttributeNamespace
+     * @see #getAttributeName
+     * @see #getAttributePrefix
+     * @see #getAttributeValue
+     */
+    int getAttributeCount();
+
+    /**
+     * Returns the namespace URI of the attribute
+     * with the given index (starts from 0).
+     * Returns an empty string ("") if namespaces are not enabled
+     * or the attribute has no namespace.
+     * Throws an IndexOutOfBoundsException if the index is out of range
+     * or the current event type is not START_TAG.
+     *
+     * <p><strong>NOTE:</strong> if FEATURE_REPORT_NAMESPACE_ATTRIBUTES is set
+     * then namespace attributes (xmlns:ns='...') must be reported
+     * with namespace
+     * <a href="http://www.w3.org/2000/xmlns/">http://www.w3.org/2000/xmlns/</a>
+     * (visit this URL for description!).
+     * The default namespace attribute (xmlns="...") will be reported with empty namespace.
+     * <p><strong>NOTE:</strong>The xml prefix is bound as defined in
+     * <a href="http://www.w3.org/TR/REC-xml-names/#ns-using">Namespaces in XML</a>
+     * specification to "http://www.w3.org/XML/1998/namespace".
+     *
+     * @param zero based index of attribute
+     * @return attribute namespace,
+     *   empty string ("") is returned  if namesapces processing is not enabled or
+     *   namespaces processing is enabled but attribute has no namespace (it has no prefix).
+     */
+    String getAttributeNamespace(int index);
+
+    /**
+     * Returns the local name of the specified attribute
+     * if namespaces are enabled or just attribute name if namespaces are disabled.
+     * Throws an IndexOutOfBoundsException if the index is out of range
+     * or current event type is not START_TAG.
+     *
+     * @param zero based index of attribute
+     * @return attribute name (null is never returned)
+     */
+    String getAttributeName(int index);
+
+    /**
+     * Returns the prefix of the specified attribute
+     * Returns null if the element has no prefix.
+     * If namespaces are disabled it will always return null.
+     * Throws an IndexOutOfBoundsException if the index is out of range
+     * or current event type is not START_TAG.
+     *
+     * @param zero based index of attribute
+     * @return attribute prefix or null if namespaces processing is not enabled.
+     */
+    String getAttributePrefix(int index);
+
+    /**
+     * Returns the type of the specified attribute
+     * If parser is non-validating it MUST return CDATA.
+     *
+     * @param zero based index of attribute
+     * @return attribute type (null is never returned)
+     */
+    String getAttributeType(int index);
+
+    /**
+     * Returns if the specified attribute was not in input was declared in XML.
+     * If parser is non-validating it MUST always return false.
+     * This information is part of XML infoset:
+     *
+     * @param zero based index of attribute
+     * @return false if attribute was in input
+     */
+    boolean isAttributeDefault(int index);
+
+    /**
+     * Returns the given attributes value.
+     * Throws an IndexOutOfBoundsException if the index is out of range
+     * or current event type is not START_TAG.
+     *
+     * <p><strong>NOTE:</strong> attribute value must be normalized
+     * (including entity replacement text if PROCESS_DOCDECL is false) as described in
+     * <a href="http://www.w3.org/TR/REC-xml#AVNormalize">XML 1.0 section
+     * 3.3.3 Attribute-Value Normalization</a>
+     *
+     * @see #defineEntityReplacementText
+     *
+     * @param zero based index of attribute
+     * @return value of attribute (null is never returned)
+     */
+    String getAttributeValue(int index);
+
+    /**
+     * Returns the attributes value identified by namespace URI and namespace localName.
+     * If namespaces are disabled namespace must be null.
+     * If current event type is not START_TAG then IndexOutOfBoundsException will be thrown.
+     *
+     * <p><strong>NOTE:</strong> attribute value must be normalized
+     * (including entity replacement text if PROCESS_DOCDECL is false) as described in
+     * <a href="http://www.w3.org/TR/REC-xml#AVNormalize">XML 1.0 section
+     * 3.3.3 Attribute-Value Normalization</a>
+     *
+     * @see #defineEntityReplacementText
+     *
+     * @param namespace Namespace of the attribute if namespaces are enabled otherwise must be null
+     * @param name If namespaces enabled local name of attribute otherwise just attribute name
+     * @return value of attribute or null if attribute with given name does not exist
+     */
+    String getAttributeValue(String namespace, String name);
+
+    // --------------------------------------------------------------------------
+    // actual parsing methods
+
+    /**
+     * Returns the type of the current event (START_TAG, END_TAG, TEXT, etc.)
+     *
+     * @see #next()
+     * @see #nextToken()
+     */
+    int getEventType();
+
+    /**
+     * Get next parsing event - element content wil be coalesced and only one
+     * TEXT event must be returned for whole element content
+     * (comments and processing instructions will be ignored and emtity references
+     * must be expanded or exception mus be thrown if entity reerence can not be exapnded).
+     * If element content is empty (content is "") then no TEXT event will be reported.
+     *
+     * <p><b>NOTE:</b> empty element (such as <tag/>) will be reported
+     *  with  two separate events: START_TAG, END_TAG - it must be so to preserve
+     *   parsing equivalency of empty element to <tag></tag>.
+     *  (see isEmptyElementTag ())
+     *
+     * @see #isEmptyElementTag
+     * @see #START_TAG
+     * @see #TEXT
+     * @see #END_TAG
+     * @see #END_DOCUMENT
+     */
+
+    int next()
+        throws XmlPullParserException, IOException;
+
+
+    /**
+     * This method works similarly to next() but will expose
+     * additional event types (COMMENT, CDSECT, DOCDECL, ENTITY_REF, PROCESSING_INSTRUCTION, or
+     * IGNORABLE_WHITESPACE) if they are available in input.
+     *
+     * <p>If special feature
+     * <a href="http://xmlpull.org/v1/doc/features.html#xml-roundtrip">FEATURE_XML_ROUNDTRIP</a>
+     * (identified by URI: http://xmlpull.org/v1/doc/features.html#xml-roundtrip)
+     * is enabled it is possible to do XML document round trip ie. reproduce
+     * exectly on output the XML input using getText():
+     * returned content is always unnormalized (exactly as in input).
+     * Otherwise returned content is end-of-line normalized as described
+     * <a href="http://www.w3.org/TR/REC-xml#sec-line-ends">XML 1.0 End-of-Line Handling</a>
+     * and. Also when this feature is enabled exact content of START_TAG, END_TAG,
+     * DOCDECL and PROCESSING_INSTRUCTION is available.
+     *
+     * <p>Here is the list of tokens that can be  returned from nextToken()
+     * and what getText() and getTextCharacters() returns:<dl>
+     * <dt>START_DOCUMENT<dd>null
+     * <dt>END_DOCUMENT<dd>null
+     * <dt>START_TAG<dd>null unless FEATURE_XML_ROUNDTRIP
+     *   enabled and then returns XML tag, ex: <tag attr='val'>
+     * <dt>END_TAG<dd>null unless FEATURE_XML_ROUNDTRIP
+     *  id enabled and then returns XML tag, ex: </tag>
+     * <dt>TEXT<dd>return element content.
+     *  <br>Note: that element content may be delivered in multiple consecutive TEXT events.
+     * <dt>IGNORABLE_WHITESPACE<dd>return characters that are determined to be ignorable white
+     * space. If the FEATURE_XML_ROUNDTRIP is enabled all whitespace content outside root
+     * element will always reported as IGNORABLE_WHITESPACE otherise rteporting is optional.
+     *  <br>Note: that element content may be delevered in multiple consecutive IGNORABLE_WHITESPACE events.
+     * <dt>CDSECT<dd>
+     * return text <em>inside</em> CDATA
+     *  (ex. 'fo<o' from <!CDATA[fo<o]]>)
+     * <dt>PROCESSING_INSTRUCTION<dd>
+     *  if FEATURE_XML_ROUNDTRIP is true
+     *  return exact PI content ex: 'pi foo' from <?pi foo?>
+     *  otherwise it may be exact PI content or concatenation of PI target,
+     * space and data so for example for
+     *   <?target    data?> string "target data" may
+     *       be returned if FEATURE_XML_ROUNDTRIP is false.
+     * <dt>COMMENT<dd>return comment content ex. 'foo bar' from <!--foo bar-->
+     * <dt>ENTITY_REF<dd>getText() MUST return entity replacement text if PROCESS_DOCDECL is false
+     * otherwise getText() MAY return null,
+     * additionally getTextCharacters() MUST return entity name
+     * (for example 'entity_name' for &entity_name;).
+     * <br><b>NOTE:</b> this is the only place where value returned from getText() and
+     *   getTextCharacters() <b>are different</b>
+     * <br><b>NOTE:</b> it is user responsibility to resolve entity reference
+     *    if PROCESS_DOCDECL is false and there is no entity replacement text set in
+     *    defineEntityReplacementText() method (getText() will be null)
+     * <br><b>NOTE:</b> character entities (ex. &#32;) and standard entities such as
+     *  &amp; &lt; &gt; &quot; &apos; are reported as well
+     *  and are <b>not</b> reported as TEXT tokens but as ENTITY_REF tokens!
+     *  This requirement is added to allow to do roundtrip of XML documents!
+     * <dt>DOCDECL<dd>
+     * if FEATURE_XML_ROUNDTRIP is true or PROCESS_DOCDECL is false
+     * then return what is inside of DOCDECL for example it returns:<pre>
+     * " titlepage SYSTEM "http://www.foo.bar/dtds/typo.dtd"
+     * [<!ENTITY % active.links "INCLUDE">]"</pre>
+     * <p>for input document that contained:<pre>
+     * <!DOCTYPE titlepage SYSTEM "http://www.foo.bar/dtds/typo.dtd"
+     * [<!ENTITY % active.links "INCLUDE">]></pre>
+     * otherwise if FEATURE_XML_ROUNDTRIP is false and PROCESS_DOCDECL is true
+     *    then what is returned is undefined (it may be even null)
+     * </dd>
+     * </dl>
+     *
+     * <p><strong>NOTE:</strong> there is no gurantee that there will only one TEXT or
+     * IGNORABLE_WHITESPACE event from nextToken() as parser may chose to deliver element content in
+     * multiple tokens (dividing element content into chunks)
+     *
+     * <p><strong>NOTE:</strong> whether returned text of token is end-of-line normalized
+     *  is depending on FEATURE_XML_ROUNDTRIP.
+     *
+     * <p><strong>NOTE:</strong> XMLDecl (<?xml ...?>) is not reported but its content
+     * is available through optional properties (see class description above).
+     *
+     * @see #next
+     * @see #START_TAG
+     * @see #TEXT
+     * @see #END_TAG
+     * @see #END_DOCUMENT
+     * @see #COMMENT
+     * @see #DOCDECL
+     * @see #PROCESSING_INSTRUCTION
+     * @see #ENTITY_REF
+     * @see #IGNORABLE_WHITESPACE
+     */
+    int nextToken()
+        throws XmlPullParserException, IOException;
+
+    //-----------------------------------------------------------------------------
+    // utility methods to mak XML parsing easier ...
+
+    /**
+     * Test if the current event is of the given type and if the
+     * namespace and name do match. null will match any namespace
+     * and any name. If the test is not passed, an exception is
+     * thrown. The exception text indicates the parser position,
+     * the expected event and the current event that is not meeting the
+     * requirement.
+     *
+     * <p>Essentially it does this
+     * <pre>
+     *  if (type != getEventType()
+     *  || (namespace != null &&  !namespace.equals( getNamespace () ) )
+     *  || (name != null &&  !name.equals( getName() ) ) )
+     *     throw new XmlPullParserException( "expected "+ TYPES[ type ]+getPositionDescription());
+     * </pre>
+     */
+    void require(int type, String namespace, String name)
+        throws XmlPullParserException, IOException;
+
+    /**
+     * If current event is START_TAG then if next element is TEXT then element content is returned
+     * or if next event is END_TAG then empty string is returned, otherwise exception is thrown.
+     * After calling this function successfully parser will be positioned on END_TAG.
+     *
+     * <p>The motivation for this function is to allow to parse consistently both
+     * empty elements and elements that has non empty content, for example for input: <ol>
+     * <li><tag>foo</tag>
+     * <li><tag></tag> (which is equivalent to <tag/>
+     * both input can be parsed with the same code:
+     * <pre>
+     *   p.nextTag()
+     *   p.requireEvent(p.START_TAG, "", "tag");
+     *   String content = p.nextText();
+     *   p.requireEvent(p.END_TAG, "", "tag");
+     * </pre>
+     * This function together with nextTag make it very easy to parse XML that has
+     * no mixed content.
+     *
+     *
+     * <p>Essentially it does this
+     * <pre>
+     *  if(getEventType() != START_TAG) {
+     *     throw new XmlPullParserException(
+     *       "parser must be on START_TAG to read next text", this, null);
+     *  }
+     *  int eventType = next();
+     *  if(eventType == TEXT) {
+     *     String result = getText();
+     *     eventType = next();
+     *     if(eventType != END_TAG) {
+     *       throw new XmlPullParserException(
+     *          "event TEXT it must be immediately followed by END_TAG", this, null);
+     *      }
+     *      return result;
+     *  } else if(eventType == END_TAG) {
+     *     return "";
+     *  } else {
+     *     throw new XmlPullParserException(
+     *       "parser must be on START_TAG or TEXT to read text", this, null);
+     *  }
+     * </pre>
+     */
+    String nextText() throws XmlPullParserException, IOException;
+
+    /**
+     * Call next() and return event if it is START_TAG or END_TAG
+     * otherwise throw an exception.
+     * It will skip whitespace TEXT before actual tag if any.
+     *
+     * <p>essentially it does this
+     * <pre>
+     *   int eventType = next();
+     *   if(eventType == TEXT &&  isWhitespace()) {   // skip whitespace
+     *      eventType = next();
+     *   }
+     *   if (eventType != START_TAG &&  eventType != END_TAG) {
+     *      throw new XmlPullParserException("expected start or end tag", this, null);
+     *   }
+     *   return eventType;
+     * </pre>
+     */
+    int nextTag() throws XmlPullParserException, IOException;
+
+    
+//    /**
+//     * Skip sub tree on which the parser is currently positioned on.
+//     * <br>NOTE: parser must be on START_TAG and when function returns
+//     * parser will be positioned on matching END_TAG
+//     *
+//     * This method is typically optimized by parser but the its logic should follow this:
+//     * <code>
+//     * require(XmlPullParser.START_TAG, null, null);
+//     * int level = 1;
+//     * while(level > 0) {
+//     *   int eventType = next();
+//     *   if(eventType == XmlPullParser.END_TAG) {
+//     *     --level;
+//     *   } else if(eventType == XmlPullParser.START_TAG) {
+//     *     ++level;
+//     *   }
+//     * }
+//     * </code>
+//     */
+//    public void skipSubTree() throws XmlPullParserException, IOException;
+    
+}
+
diff --git a/src/main/java/org/jboss/modules/xml/XmlPullParserException.java b/src/main/java/org/jboss/modules/xml/XmlPullParserException.java
new file mode 100644
index 0000000..89afd40
--- /dev/null
+++ b/src/main/java/org/jboss/modules/xml/XmlPullParserException.java
@@ -0,0 +1,76 @@
+/* -*-             c-basic-offset: 4; indent-tabs-mode: nil; -*-  //------100-columns-wide------>|*/
+// for license please see accompanying LICENSE.txt file (available also at http://www.xmlpull.org/)
+
+package org.jboss.modules.xml;
+
+/**
+ * This exception is thrown to signal XML Pull Parser related faults.
+ *
+ * @author <a href="http://www.extreme.indiana.edu/~aslom/">Aleksander Slominski</a>
+ */
+public class XmlPullParserException extends Exception {
+    protected Throwable detail;
+    protected int row = -1;
+    protected int column = -1;
+
+    /*    public XmlPullParserException() {
+          }*/
+
+    public XmlPullParserException(String s) {
+        super(s);
+    }
+
+    /*
+    public XmlPullParserException(String s, Throwable thrwble) {
+        super(s);
+        this.detail = thrwble;
+        }
+
+    public XmlPullParserException(String s, int row, int column) {
+        super(s);
+        this.row = row;
+        this.column = column;
+    }
+    */
+
+    public XmlPullParserException(String msg, XmlPullParser parser, Throwable chain) {
+        super ((msg == null ? "" : msg+" ")
+               + (parser == null ? "" : "(position:"+parser.getPositionDescription()+") ")
+               + (chain == null ? "" : "caused by: "+chain));
+
+        if (parser != null) {
+            this.row = parser.getLineNumber();
+            this.column = parser.getColumnNumber();
+        }
+        this.detail = chain;
+    }
+
+    public Throwable getDetail() { return detail; }
+    //    public void setDetail(Throwable cause) { this.detail = cause; }
+    public int getLineNumber() { return row; }
+    public int getColumnNumber() { return column; }
+
+    /*
+    public String getMessage() {
+        if(detail == null)
+            return super.getMessage();
+        else
+            return super.getMessage() + "; nested exception is: \n\t"
+                + detail.getMessage();
+    }
+    */
+
+    //NOTE: code that prints this and detail is difficult in J2ME
+    public void printStackTrace() {
+        if (detail == null) {
+            super.printStackTrace();
+        } else {
+            synchronized(System.err) {
+                System.err.println(super.getMessage() + "; nested exception is:");
+                detail.printStackTrace();
+            }
+        }
+    }
+
+}
+
diff --git a/src/main/resources/XPP3-LICENSE.txt b/src/main/resources/XPP3-LICENSE.txt
new file mode 100644
index 0000000..4cc7224
--- /dev/null
+++ b/src/main/resources/XPP3-LICENSE.txt
@@ -0,0 +1,46 @@
+Indiana University Extreme! Lab Software License
+
+Version 1.1.1
+
+Copyright (c) 2002 Extreme! Lab, Indiana University. 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 end-user documentation included with the redistribution, if any, 
+   must include the following acknowledgment:
+
+  "This product includes software developed by the Indiana University 
+  Extreme! Lab (http://www.extreme.indiana.edu/)."
+
+Alternately, this acknowledgment may appear in the software itself, 
+if and wherever such third-party acknowledgments normally appear.
+
+4. The names "Indiana Univeristy" and "Indiana Univeristy Extreme! Lab" 
+must not be used to endorse or promote products derived from this 
+software without prior written permission. For written permission, 
+please contact http://www.extreme.indiana.edu/.
+
+5. Products derived from this software may not use "Indiana Univeristy" 
+name nor may "Indiana Univeristy" appear in their name, without prior 
+written permission of the Indiana University.
+
+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 THE AUTHORS, COPYRIGHT HOLDERS OR ITS CONTRIBUTORS
+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.
diff --git a/src/main/resources/schema/module-1_0.xsd b/src/main/resources/schema/module-1_0.xsd
new file mode 100644
index 0000000..72ec782
--- /dev/null
+++ b/src/main/resources/schema/module-1_0.xsd
@@ -0,0 +1,462 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  ~ JBoss, Home of Professional Open Source.
+  ~ Copyright 2014 Red Hat, Inc., and individual contributors
+  ~ as indicated by the @author tags.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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"
+            targetNamespace="urn:jboss:module:1.0"
+            xmlns="urn:jboss:module:1.0"
+            elementFormDefault="qualified"
+            attributeFormDefault="unqualified"
+            version="1.0">
+
+    <!-- Root element -->
+    <xsd:element name="module" type="moduleType">
+        <xsd:annotation>
+            <xsd:documentation>
+                Root element for a module declaration.
+            </xsd:documentation>
+        </xsd:annotation>
+    </xsd:element>
+
+    <!-- Root element -->
+    <xsd:element name="configuration" type="configurationType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                Root element for a filesystem module loader configuration.
+            </documentation>
+        </annotation>
+    </xsd:element>
+
+    <xsd:complexType name="moduleType">
+        <xsd:annotation>
+            <xsd:documentation>
+                The module declaration type; contains dependencies, resources, and the main class
+                specification.
+            </xsd:documentation>
+        </xsd:annotation>
+        <xsd:all>
+            <xsd:element name="exports" type="filterType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        Lists filter expressions to apply to the export filter of the local resources of this module
+                        (optional). By default, everything is exported. If filter expressions are provided, the default
+                        action is to accept all paths if no filters match.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+            <xsd:element name="dependencies" type="dependenciesType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        Lists the dependencies of this module (optional).
+                    </documentation>
+                </annotation>
+            </xsd:element>
+            <xsd:element name="resources" type="resourcesType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        Lists the resource roots of this module (optional).
+                    </documentation>
+                </annotation>
+            </xsd:element>
+            <xsd:element name="main-class" type="classNameType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        Specifies the main class of this module; used to run the module from the command-line
+                        (optional).
+                    </documentation>
+                </annotation>
+            </xsd:element>
+        </xsd:all>
+        <xsd:attribute name="name" type="moduleNameType" use="required">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The name of this module (required).
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+        <xsd:attribute name="slot" type="moduleSlotType" use="optional">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The version slot of this module (optional).
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+    </xsd:complexType>
+
+    <xsd:simpleType name="moduleNameType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A module name, which consists of one or more dot (.)-separated segments. Each segment must begin and end
+                with an alphanumeric or underscore (_), and may otherwise contain alphanumerics, underscores, and hyphens
+                (-).
+            </documentation>
+        </annotation>
+        <xsd:restriction base="xsd:string">
+            <xsd:pattern value="[a-zA-Z0-9_]([-a-zA-Z0-9_]*[a-zA-Z0-9_])?(\.[a-zA-Z0-9_]([-a-zA-Z0-9_]*[a-zA-Z0-9_])?)*"/>
+        </xsd:restriction>
+    </xsd:simpleType>
+
+    <xsd:simpleType name="moduleSlotType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A module version slot. A slot may consist of one or more alphanumerics, hyphens (-), underscores (_),
+                plus signs (+), asterisks (*), or dots (.).
+            </documentation>
+        </annotation>
+        <xsd:restriction base="xsd:string">
+            <xsd:pattern value="[-a-zA-Z0-9_+*.]+"/>
+        </xsd:restriction>
+    </xsd:simpleType>
+
+    <xsd:complexType name="dependenciesType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A list of zero or more module dependencies.
+            </documentation>
+        </annotation>
+        <xsd:choice minOccurs="0" maxOccurs="unbounded">
+            <xsd:element name="module" type="moduleDependencyType">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A specified module dependency.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+        </xsd:choice>
+    </xsd:complexType>
+
+    <xsd:complexType name="moduleDependencyType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A single module dependency expression.
+            </documentation>
+        </annotation>
+        <xsd:all minOccurs="0">
+            <xsd:element name="exports" type="filterType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A filter used to restrict what packages or directories from this dependency are re-exported by
+                        this module. See also the "export" and "services" attributes. The default action of this filter
+                        list is controlled by the value of the "export" attribute. Regardless of the setting of these
+                        attributes, this filter always behaves as if it has a final entry which rejects META-INF and
+                        all of its subdirectories.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+            <xsd:element name="imports" type="filterType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A filter used to restrict what packages or directories from this dependency are visible to this
+                        module. See also the "services" attribute. The default action of this filter list is to reject
+                        a path if not matched.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+        </xsd:all>
+        <xsd:attribute name="name" type="moduleNameType" use="required">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The dependency module name (required).
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+        <xsd:attribute name="slot" type="moduleSlotType" use="optional">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The dependency module version slot (optional).
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+        <xsd:attribute name="export" type="xsd:boolean" use="optional" default="false">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    Specifies whether this module dependency is re-exported by default (default is "false"). Setting
+                    this attribute to true sets the default action for the export filter list to "accept"; leaving it
+                    as false sets the default action to "reject".  Thus you can still export dependency resources even
+                    if this attribute is false by listing explicit paths for the export list.
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+        <xsd:attribute name="services" type="serviceDispositionType" use="optional" default="none">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    Specifies whether and how services found in this dependency are used (default is "none"). Specifying
+                    a value of "import" for this attribute is equivalent to adding a filter at the end of the import
+                    filter list which includes the META-INF/services path from the dependency module.  Setting a value
+                    of "export" for this attribute is equivalent to the same action on the export filter list.
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+        <xsd:attribute name="optional" type="xsd:boolean" use="optional" default="false">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    Specifies whether this dependency is optional (defaults to false). An optional dependency will not
+                    cause the module to fail to load if not found; however if the module is added later, it will not be
+                    retroactively linked into this module's dependency list.
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+    </xsd:complexType>
+
+    <xsd:simpleType name="serviceDispositionType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                The requested behavior for service handling on a dependency.
+            </documentation>
+        </annotation>
+        <xsd:restriction base="xsd:string">
+            <xsd:enumeration value="none">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        Do not import or export services from this dependency.
+                    </documentation>
+                </annotation>
+            </xsd:enumeration>
+            <xsd:enumeration value="import">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        Import, but do not re-export, services from this dependency.
+                    </documentation>
+                </annotation>
+            </xsd:enumeration>
+            <xsd:enumeration value="export">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        Import and re-export services found in this dependency.
+                    </documentation>
+                </annotation>
+            </xsd:enumeration>
+        </xsd:restriction>
+    </xsd:simpleType>
+
+    <xsd:complexType name="classNameType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A class name.
+            </documentation>
+        </annotation>
+        <xsd:attribute name="name" type="xsd:string" use="required">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The class name.
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+    </xsd:complexType>
+
+    <xsd:complexType name="pathType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A filesystem path name.
+            </documentation>
+        </annotation>
+        <xsd:attribute name="name" type="xsd:string" use="required">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The path name.
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+    </xsd:complexType>
+
+    <xsd:complexType name="resourcesType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A list of zero or more resource roots for this deployment.
+            </documentation>
+        </annotation>
+        <xsd:choice minOccurs="0" maxOccurs="unbounded">
+            <xsd:element name="resource-root" type="resourceType">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A resource root within this deployment.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+        </xsd:choice>
+    </xsd:complexType>
+
+    <xsd:complexType name="resourceType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A resource root within a deployment.
+            </documentation>
+        </annotation>
+        <xsd:all>
+            <xsd:element name="filter" type="filterType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A path filter specification for this resource root (optional). By default all paths are accepted.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+        </xsd:all>
+        <xsd:attribute name="name" type="xsd:string" use="optional">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The name of this resource root (optional). If not specified, defaults to the value of the path
+                    attribute.
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+        <xsd:attribute name="path" type="xsd:string" use="required">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The path of this resource root, relative to the path in which the module.xml file is found.
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+    </xsd:complexType>
+
+    <xsd:complexType name="filterType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A filter specification, consisting of zero or more filter items.
+            </documentation>
+        </annotation>
+        <xsd:choice minOccurs="0" maxOccurs="unbounded">
+            <xsd:element name="include" type="pathSpecType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A path to include. The path value can be a path name or a "glob" which may include the special
+                        wildcards "*", "**", and "?".
+                    </documentation>
+                </annotation>
+            </xsd:element>
+            <xsd:element name="exclude" type="pathSpecType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A path to exclude. The path value can be a path name or a "glob" which may include the special
+                        wildcards "*", "**", and "?".
+                    </documentation>
+                </annotation>
+            </xsd:element>
+            <xsd:element name="include-set" type="pathSetType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A set of literal path names to include. Wildcards are not supported.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+            <xsd:element name="exclude-set" type="pathSetType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A set of literal path names to exclude. Wildcards are not supported.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+        </xsd:choice>
+    </xsd:complexType>
+
+    <xsd:complexType name="pathSpecType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A path specification type, which may include wildcards.
+            </documentation>
+        </annotation>
+        <xsd:attribute name="path" type="xsd:string" use="required">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The path name, which can be a literal path name or it may include the special wildcards "*", "**",
+                    and "?".
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+    </xsd:complexType>
+
+    <xsd:complexType name="pathSetType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A set of literal path names which can be used for efficient matching against multiple possible values.
+            </documentation>
+        </annotation>
+        <xsd:choice minOccurs="0" maxOccurs="unbounded">
+            <xsd:element name="path" type="pathType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        The path name to include in the set.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+        </xsd:choice>
+    </xsd:complexType>
+
+    <xsd:complexType name="configurationType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A configuration for the default module loader.
+            </documentation>
+        </annotation>
+        <xsd:choice minOccurs="1" maxOccurs="unbounded">
+            <xsd:element name="loader" type="loaderType">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A defined loader. More than one loader may be defined.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+        </xsd:choice>
+        <xsd:attribute name="default-loader" type="loaderNameType" use="required">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The loader to use. The name matches the value of the "name" attribute of one of the defined loaders.
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+    </xsd:complexType>
+
+    <xsd:complexType name="loaderType">
+        <xsd:choice minOccurs="0" maxOccurs="unbounded">
+            <xsd:element name="module-path" type="pathType">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A module root path for this loader. Paths are relative to ${user.dir} by default.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+            <xsd:element name="import" type="xsd:string">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        Specifies another loader whose content should be imported. Such loaders are visible to modules
+                        defined by this loader, but not to modules defined in other loaders.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+        </xsd:choice>
+        <xsd:attribute name="name" type="loaderNameType" use="required">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The name of this particular module loader.
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+    </xsd:complexType>
+
+    <xsd:simpleType name="loaderNameType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A loader type name, which may consist of letters, numbers, hyphens, and underscores.
+            </documentation>
+        </annotation>
+        <xsd:restriction base="xsd:token">
+            <xsd:pattern value="[-0-9a-zA-Z_]+"/>
+        </xsd:restriction>
+    </xsd:simpleType>
+</xsd:schema>
+
diff --git a/src/main/resources/schema/module-1_1.xsd b/src/main/resources/schema/module-1_1.xsd
new file mode 100644
index 0000000..3da051b
--- /dev/null
+++ b/src/main/resources/schema/module-1_1.xsd
@@ -0,0 +1,582 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  ~ JBoss, Home of Professional Open Source.
+  ~ Copyright 2014 Red Hat, Inc., and individual contributors
+  ~ as indicated by the @author tags.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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"
+            targetNamespace="urn:jboss:module:1.1"
+            xmlns="urn:jboss:module:1.1"
+            elementFormDefault="qualified"
+            attributeFormDefault="unqualified"
+            version="1.0">
+
+    <!-- Root element -->
+    <xsd:element name="module" type="moduleType">
+        <xsd:annotation>
+            <xsd:documentation>
+                Root element for a module declaration.
+            </xsd:documentation>
+        </xsd:annotation>
+    </xsd:element>
+
+    <!-- Root element -->
+    <xsd:element name="configuration" type="configurationType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                Root element for a filesystem module loader configuration.
+            </documentation>
+        </annotation>
+    </xsd:element>
+
+    <!-- Root element -->
+    <xsd:element name="module-alias" type="moduleAliasType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                Root element for a module alias declaration.
+            </documentation>
+        </annotation>
+    </xsd:element>
+
+    <xsd:complexType name="moduleType">
+        <xsd:annotation>
+            <xsd:documentation>
+                The module declaration type; contains dependencies, resources, and the main class
+                specification.
+            </xsd:documentation>
+        </xsd:annotation>
+        <xsd:all>
+            <xsd:element name="exports" type="filterType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        Lists filter expressions to apply to the export filter of the local resources of this module
+                        (optional). By default, everything is exported. If filter expressions are provided, the default
+                        action is to accept all paths if no filters match.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+            <xsd:element name="dependencies" type="dependenciesType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        Lists the dependencies of this module (optional).
+                    </documentation>
+                </annotation>
+            </xsd:element>
+            <xsd:element name="resources" type="resourcesType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        Lists the resource roots of this module (optional).
+                    </documentation>
+                </annotation>
+            </xsd:element>
+            <xsd:element name="main-class" type="classNameType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        Specifies the main class of this module; used to run the module from the command-line
+                        (optional).
+                    </documentation>
+                </annotation>
+            </xsd:element>
+            <xsd:element name="properties" type="propertyListType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        Lists the user-defined properties to be associated with this module (optional).
+                    </documentation>
+                </annotation>
+            </xsd:element>
+        </xsd:all>
+        <xsd:attribute name="name" type="moduleNameType" use="required">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The name of this module (required).
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+        <xsd:attribute name="slot" type="moduleSlotType" use="optional">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The version slot of this module (optional).
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+    </xsd:complexType>
+
+    <xsd:simpleType name="moduleNameType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A module name, which consists of one or more dot (.)-separated segments. Each segment must begin and end
+                with an alphanumeric or underscore (_), and may otherwise contain alphanumerics, underscores, and hyphens
+                (-).
+            </documentation>
+        </annotation>
+        <xsd:restriction base="xsd:string">
+            <xsd:pattern value="[a-zA-Z0-9_]([-a-zA-Z0-9_]*[a-zA-Z0-9_])?(\.[a-zA-Z0-9_]([-a-zA-Z0-9_]*[a-zA-Z0-9_])?)*"/>
+        </xsd:restriction>
+    </xsd:simpleType>
+
+    <xsd:simpleType name="moduleSlotType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A module version slot. A slot may consist of one or more alphanumerics, hyphens (-), underscores (_),
+                plus signs (+), asterisks (*), or dots (.).
+            </documentation>
+        </annotation>
+        <xsd:restriction base="xsd:string">
+            <xsd:pattern value="[-a-zA-Z0-9_+*.]+"/>
+        </xsd:restriction>
+    </xsd:simpleType>
+
+    <xsd:complexType name="dependenciesType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A list of zero or more module dependencies.
+            </documentation>
+        </annotation>
+        <xsd:choice minOccurs="0" maxOccurs="unbounded">
+            <xsd:element name="module" type="moduleDependencyType">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A specified module dependency.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+            <xsd:element name="system" type="systemDependencyType">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A dependency on the system (or embedding) class loader.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+        </xsd:choice>
+    </xsd:complexType>
+
+    <xsd:complexType name="moduleDependencyType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A single module dependency expression.
+            </documentation>
+        </annotation>
+        <xsd:all minOccurs="0">
+            <xsd:element name="exports" type="filterType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A filter used to restrict what packages or directories from this dependency are re-exported by
+                        this module. See also the "export" and "services" attributes. The default action of this filter
+                        list is controlled by the value of the "export" attribute. Regardless of the setting of these
+                        attributes, this filter always behaves as if it has a final entry which rejects META-INF and
+                        all of its subdirectories.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+            <xsd:element name="imports" type="filterType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A filter used to restrict what packages or directories from this dependency are visible to this
+                        module. See also the "services" attribute. The default action of this filter list is to reject
+                        a path if not matched.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+        </xsd:all>
+        <xsd:attribute name="name" type="moduleNameType" use="required">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The dependency module name (required).
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+        <xsd:attribute name="slot" type="moduleSlotType" use="optional">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The dependency module version slot (optional).
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+        <xsd:attribute name="export" type="xsd:boolean" use="optional" default="false">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    Specifies whether this module dependency is re-exported by default (default is "false"). Setting
+                    this attribute to true sets the default action for the export filter list to "accept"; leaving it
+                    as false sets the default action to "reject".  Thus you can still export dependency resources even
+                    if this attribute is false by listing explicit paths for the export list.
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+        <xsd:attribute name="services" type="serviceDispositionType" use="optional" default="none">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    Specifies whether and how services found in this dependency are used (default is "none"). Specifying
+                    a value of "import" for this attribute is equivalent to adding a filter at the end of the import
+                    filter list which includes the META-INF/services path from the dependency module.  Setting a value
+                    of "export" for this attribute is equivalent to the same action on the export filter list.
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+        <xsd:attribute name="optional" type="xsd:boolean" use="optional" default="false">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    Specifies whether this dependency is optional (defaults to false). An optional dependency will not
+                    cause the module to fail to load if not found; however if the module is added later, it will not be
+                    retroactively linked into this module's dependency list.
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+    </xsd:complexType>
+
+    <xsd:complexType name="systemDependencyType">
+        <xsd:all>
+            <xsd:element name="paths" type="pathSetType">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        The list of paths which are applicable for this system dependency.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+            <xsd:element name="exports" type="filterType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A filter used to restrict what packages or directories from this dependency are re-exported by
+                        this module. See also the "export" and "services" attributes. The default action of this filter
+                        list is controlled by the value of the "export" attribute. Regardless of the setting of these
+                        attributes, this filter always behaves as if it has a final entry which rejects META-INF and
+                        all of its subdirectories.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+        </xsd:all>
+        <xsd:attribute name="export" type="xsd:boolean" use="optional" default="false">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    Specifies whether this module dependency is re-exported by default (default is "false"). Setting
+                    this attribute to true sets the default action for the export filter list to "accept"; leaving it
+                    as false sets the default action to "reject".  Thus you can still export dependency resources even
+                    if this attribute is false by listing explicit paths for the export list.
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+    </xsd:complexType>
+
+    <xsd:simpleType name="serviceDispositionType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                The requested behavior for service handling on a dependency.
+            </documentation>
+        </annotation>
+        <xsd:restriction base="xsd:string">
+            <xsd:enumeration value="none">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        Do not import or export services from this dependency.
+                    </documentation>
+                </annotation>
+            </xsd:enumeration>
+            <xsd:enumeration value="import">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        Import, but do not re-export, services from this dependency.
+                    </documentation>
+                </annotation>
+            </xsd:enumeration>
+            <xsd:enumeration value="export">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        Import and re-export services found in this dependency.
+                    </documentation>
+                </annotation>
+            </xsd:enumeration>
+        </xsd:restriction>
+    </xsd:simpleType>
+
+    <xsd:complexType name="classNameType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A class name.
+            </documentation>
+        </annotation>
+        <xsd:attribute name="name" type="xsd:string" use="required">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The class name.
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+    </xsd:complexType>
+
+    <xsd:complexType name="pathType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A filesystem path name.
+            </documentation>
+        </annotation>
+        <xsd:attribute name="name" type="xsd:string" use="required">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The path name.
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+    </xsd:complexType>
+
+    <xsd:complexType name="resourcesType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A list of zero or more resource roots for this deployment.
+            </documentation>
+        </annotation>
+        <xsd:choice minOccurs="0" maxOccurs="unbounded">
+            <xsd:element name="resource-root" type="resourceType">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A resource root within this deployment.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+        </xsd:choice>
+    </xsd:complexType>
+
+    <xsd:complexType name="resourceType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A resource root within a deployment.
+            </documentation>
+        </annotation>
+        <xsd:all>
+            <xsd:element name="filter" type="filterType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A path filter specification for this resource root (optional). By default all paths are accepted.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+        </xsd:all>
+        <xsd:attribute name="name" type="xsd:string" use="optional">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The name of this resource root (optional). If not specified, defaults to the value of the path
+                    attribute.
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+        <xsd:attribute name="path" type="xsd:string" use="required">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The path of this resource root, relative to the path in which the module.xml file is found.
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+    </xsd:complexType>
+
+    <xsd:complexType name="filterType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A filter specification, consisting of zero or more filter items.
+            </documentation>
+        </annotation>
+        <xsd:choice minOccurs="0" maxOccurs="unbounded">
+            <xsd:element name="include" type="pathSpecType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A path to include. The path value can be a path name or a "glob" which may include the special
+                        wildcards "*", "**", and "?".
+                    </documentation>
+                </annotation>
+            </xsd:element>
+            <xsd:element name="exclude" type="pathSpecType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A path to exclude. The path value can be a path name or a "glob" which may include the special
+                        wildcards "*", "**", and "?".
+                    </documentation>
+                </annotation>
+            </xsd:element>
+            <xsd:element name="include-set" type="pathSetType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A set of literal path names to include. Wildcards are not supported.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+            <xsd:element name="exclude-set" type="pathSetType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A set of literal path names to exclude. Wildcards are not supported.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+        </xsd:choice>
+    </xsd:complexType>
+
+    <xsd:complexType name="pathSpecType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A path specification type, which may include wildcards.
+            </documentation>
+        </annotation>
+        <xsd:attribute name="path" type="xsd:string" use="required">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The path name, which can be a literal path name or it may include the special wildcards "*", "**",
+                    and "?".
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+    </xsd:complexType>
+
+    <xsd:complexType name="pathSetType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A set of literal path names which can be used for efficient matching against multiple possible values.
+            </documentation>
+        </annotation>
+        <xsd:choice minOccurs="0" maxOccurs="unbounded">
+            <xsd:element name="path" type="pathType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        The path name to include in the set.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+        </xsd:choice>
+    </xsd:complexType>
+
+    <xsd:complexType name="configurationType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A configuration for the default module loader.
+            </documentation>
+        </annotation>
+        <xsd:choice minOccurs="1" maxOccurs="unbounded">
+            <xsd:element name="loader" type="loaderType">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A defined loader. More than one loader may be defined.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+        </xsd:choice>
+        <xsd:attribute name="default-loader" type="loaderNameType" use="required">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The loader to use. The name matches the value of the "name" attribute of one of the defined loaders.
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+    </xsd:complexType>
+
+    <xsd:complexType name="loaderType">
+        <xsd:choice minOccurs="0" maxOccurs="unbounded">
+            <xsd:element name="module-path" type="pathType">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A module root path for this loader. Paths are relative to ${user.dir} by default.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+            <xsd:element name="import" type="xsd:string">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        Specifies another loader whose content should be imported. Such loaders are visible to modules
+                        defined by this loader, but not to modules defined in other loaders.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+        </xsd:choice>
+        <xsd:attribute name="name" type="loaderNameType" use="required">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The name of this particular module loader.
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+    </xsd:complexType>
+
+    <xsd:simpleType name="loaderNameType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A loader type name, which may consist of letters, numbers, hyphens, and underscores.
+            </documentation>
+        </annotation>
+        <xsd:restriction base="xsd:token">
+            <xsd:pattern value="[-0-9a-zA-Z_]+"/>
+        </xsd:restriction>
+    </xsd:simpleType>
+
+    <xsd:complexType name="moduleAliasType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A module alias type, which defines the target for a module alias.
+            </documentation>
+        </annotation>
+        <xsd:attribute name="name" type="moduleNameType" use="required">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The name of this module alias (required).
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+        <xsd:attribute name="slot" type="moduleSlotType" use="optional">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The version slot of this module alias (optional).
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+        <xsd:attribute name="target-name" type="moduleNameType" use="required">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The name of the module to which this alias refers (required).
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+        <xsd:attribute name="target-slot" type="moduleSlotType" use="optional">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The version slot of the module to which this alias refers (optional).
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+    </xsd:complexType>
+
+    <xsd:complexType name="propertyListType">
+        <xsd:choice minOccurs="0" maxOccurs="unbounded">
+            <xsd:element name="property" type="propertyType">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A property in this property list.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+        </xsd:choice>
+    </xsd:complexType>
+
+    <xsd:complexType name="propertyType">
+        <xsd:attribute name="name" type="xsd:string" use="required">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The property name as a string (required).
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+        <xsd:attribute name="value" type="xsd:string" use="optional" default="true">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The property value (optional, defaults to "true").
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+    </xsd:complexType>
+</xsd:schema>
diff --git a/src/main/resources/schema/module-1_2.xsd b/src/main/resources/schema/module-1_2.xsd
new file mode 100755
index 0000000..326e577
--- /dev/null
+++ b/src/main/resources/schema/module-1_2.xsd
@@ -0,0 +1,591 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  ~ JBoss, Home of Professional Open Source.
+  ~ Copyright 2014 Red Hat, Inc., and individual contributors
+  ~ as indicated by the @author tags.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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"
+            targetNamespace="urn:jboss:module:1.2"
+            xmlns="urn:jboss:module:1.2"
+            elementFormDefault="qualified"
+            attributeFormDefault="unqualified"
+            version="1.0">
+
+    <!-- Root element -->
+    <xsd:element name="module" type="moduleType">
+        <xsd:annotation>
+            <xsd:documentation>
+                Root element for a module declaration.
+            </xsd:documentation>
+        </xsd:annotation>
+    </xsd:element>
+
+    <!-- Root element -->
+    <xsd:element name="module-alias" type="moduleAliasType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                Root element for a module alias declaration.
+            </documentation>
+        </annotation>
+    </xsd:element>
+
+    <!-- Root element -->
+    <xsd:element name="module-absent" type="moduleAbsentType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                Root element for an absent module.
+            </documentation>
+        </annotation>
+    </xsd:element>
+
+    <xsd:complexType name="moduleType">
+        <xsd:annotation>
+            <xsd:documentation>
+                The module declaration type; contains dependencies, resources, and the main class
+                specification.
+            </xsd:documentation>
+        </xsd:annotation>
+        <xsd:all>
+            <xsd:element name="exports" type="filterType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        Lists filter expressions to apply to the export filter of the local resources of this module
+                        (optional). By default, everything is exported. If filter expressions are provided, the default
+                        action is to accept all paths if no filters match.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+            <xsd:element name="dependencies" type="dependenciesType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        Lists the dependencies of this module (optional).
+                    </documentation>
+                </annotation>
+            </xsd:element>
+            <xsd:element name="resources" type="resourcesType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        Lists the resource roots of this module (optional).
+                    </documentation>
+                </annotation>
+            </xsd:element>
+            <xsd:element name="main-class" type="classNameType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        Specifies the main class of this module; used to run the module from the command-line
+                        (optional).
+                    </documentation>
+                </annotation>
+            </xsd:element>
+            <xsd:element name="properties" type="propertyListType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        Lists the user-defined properties to be associated with this module (optional).
+                    </documentation>
+                </annotation>
+            </xsd:element>
+            <xsd:element name="permissions" type="permissionsType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        Lists the requested permission set for this module. If the requested permissions cannot
+                        be assigned, the module cannot be loaded.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+        </xsd:all>
+        <xsd:attribute name="name" type="moduleNameType" use="required">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The name of this module (required).
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+        <xsd:attribute name="slot" type="moduleSlotType" use="optional">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The version slot of this module (optional).
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+    </xsd:complexType>
+
+    <xsd:simpleType name="moduleNameType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A module name, which consists of one or more dot (.)-separated segments. Each segment must begin and end
+                with an alphanumeric or underscore (_), and may otherwise contain alphanumerics, underscores, and hyphens
+                (-).
+            </documentation>
+        </annotation>
+        <xsd:restriction base="xsd:string">
+            <xsd:pattern value="[a-zA-Z0-9_]([-a-zA-Z0-9_]*[a-zA-Z0-9_])?(\.[a-zA-Z0-9_]([-a-zA-Z0-9_]*[a-zA-Z0-9_])?)*"/>
+        </xsd:restriction>
+    </xsd:simpleType>
+
+    <xsd:simpleType name="moduleSlotType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A module version slot. A slot may consist of one or more alphanumerics, hyphens (-), underscores (_),
+                plus signs (+), asterisks (*), or dots (.).
+            </documentation>
+        </annotation>
+        <xsd:restriction base="xsd:string">
+            <xsd:pattern value="[-a-zA-Z0-9_+*.]+"/>
+        </xsd:restriction>
+    </xsd:simpleType>
+
+    <xsd:complexType name="dependenciesType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A list of zero or more module dependencies.
+            </documentation>
+        </annotation>
+        <xsd:choice minOccurs="0" maxOccurs="unbounded">
+            <xsd:element name="module" type="moduleDependencyType">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A specified module dependency.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+            <xsd:element name="system" type="systemDependencyType">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A dependency on the system (or embedding) class loader.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+        </xsd:choice>
+    </xsd:complexType>
+
+    <xsd:complexType name="moduleDependencyType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A single module dependency expression.
+            </documentation>
+        </annotation>
+        <xsd:all minOccurs="0">
+            <xsd:element name="exports" type="filterType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A filter used to restrict what packages or directories from this dependency are re-exported by
+                        this module. See also the "export" and "services" attributes. The default action of this filter
+                        list is controlled by the value of the "export" attribute. Regardless of the setting of these
+                        attributes, this filter always behaves as if it has a final entry which rejects META-INF and
+                        all of its subdirectories.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+            <xsd:element name="imports" type="filterType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A filter used to restrict what packages or directories from this dependency are visible to this
+                        module. See also the "services" attribute. The default action of this filter list is to reject
+                        a path if not matched.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+        </xsd:all>
+        <xsd:attribute name="name" type="moduleNameType" use="required">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The dependency module name (required).
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+        <xsd:attribute name="slot" type="moduleSlotType" use="optional">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The dependency module version slot (optional).
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+        <xsd:attribute name="export" type="xsd:boolean" use="optional" default="false">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    Specifies whether this module dependency is re-exported by default (default is "false"). Setting
+                    this attribute to true sets the default action for the export filter list to "accept"; leaving it
+                    as false sets the default action to "reject".  Thus you can still export dependency resources even
+                    if this attribute is false by listing explicit paths for the export list.
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+        <xsd:attribute name="services" type="serviceDispositionType" use="optional" default="none">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    Specifies whether and how services found in this dependency are used (default is "none"). Specifying
+                    a value of "import" for this attribute is equivalent to adding a filter at the end of the import
+                    filter list which includes the META-INF/services path from the dependency module.  Setting a value
+                    of "export" for this attribute is equivalent to the same action on the export filter list.
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+        <xsd:attribute name="optional" type="xsd:boolean" use="optional" default="false">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    Specifies whether this dependency is optional (defaults to false). An optional dependency will not
+                    cause the module to fail to load if not found; however if the module is added later, it will not be
+                    retroactively linked into this module's dependency list.
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+    </xsd:complexType>
+
+    <xsd:complexType name="systemDependencyType">
+        <xsd:all>
+            <xsd:element name="paths" type="pathSetType">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        The list of paths which are applicable for this system dependency.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+            <xsd:element name="exports" type="filterType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A filter used to restrict what packages or directories from this dependency are re-exported by
+                        this module. See also the "export" and "services" attributes. The default action of this filter
+                        list is controlled by the value of the "export" attribute. Regardless of the setting of these
+                        attributes, this filter always behaves as if it has a final entry which rejects META-INF and
+                        all of its subdirectories.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+        </xsd:all>
+        <xsd:attribute name="export" type="xsd:boolean" use="optional" default="false">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    Specifies whether this module dependency is re-exported by default (default is "false"). Setting
+                    this attribute to true sets the default action for the export filter list to "accept"; leaving it
+                    as false sets the default action to "reject".  Thus you can still export dependency resources even
+                    if this attribute is false by listing explicit paths for the export list.
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+    </xsd:complexType>
+
+    <xsd:simpleType name="serviceDispositionType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                The requested behavior for service handling on a dependency.
+            </documentation>
+        </annotation>
+        <xsd:restriction base="xsd:string">
+            <xsd:enumeration value="none">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        Do not import or export services from this dependency.
+                    </documentation>
+                </annotation>
+            </xsd:enumeration>
+            <xsd:enumeration value="import">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        Import, but do not re-export, services from this dependency.
+                    </documentation>
+                </annotation>
+            </xsd:enumeration>
+            <xsd:enumeration value="export">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        Import and re-export services found in this dependency.
+                    </documentation>
+                </annotation>
+            </xsd:enumeration>
+        </xsd:restriction>
+    </xsd:simpleType>
+
+    <xsd:complexType name="classNameType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A class name.
+            </documentation>
+        </annotation>
+        <xsd:attribute name="name" type="xsd:string" use="required">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The class name.
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+    </xsd:complexType>
+
+    <xsd:complexType name="pathType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A filesystem path name.
+            </documentation>
+        </annotation>
+        <xsd:attribute name="name" type="xsd:string" use="required">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The path name.
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+    </xsd:complexType>
+
+    <xsd:complexType name="resourcesType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A list of zero or more resource roots for this deployment.
+            </documentation>
+        </annotation>
+        <xsd:choice minOccurs="0" maxOccurs="unbounded">
+            <xsd:element name="resource-root" type="resourceType">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A resource root within this deployment.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+        </xsd:choice>
+    </xsd:complexType>
+
+    <xsd:complexType name="resourceType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A resource root within a deployment.
+            </documentation>
+        </annotation>
+        <xsd:all>
+            <xsd:element name="filter" type="filterType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A path filter specification for this resource root (optional). By default all paths are accepted.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+        </xsd:all>
+        <xsd:attribute name="name" type="xsd:string" use="optional">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The name of this resource root (optional). If not specified, defaults to the value of the path
+                    attribute.
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+        <xsd:attribute name="path" type="xsd:string" use="required">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The path of this resource root, relative to the path in which the module.xml file is found.
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+    </xsd:complexType>
+
+    <xsd:complexType name="filterType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A filter specification, consisting of zero or more filter items.
+            </documentation>
+        </annotation>
+        <xsd:choice minOccurs="0" maxOccurs="unbounded">
+            <xsd:element name="include" type="pathSpecType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A path to include. The path value can be a path name or a "glob" which may include the special
+                        wildcards "*", "**", and "?".
+                    </documentation>
+                </annotation>
+            </xsd:element>
+            <xsd:element name="exclude" type="pathSpecType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A path to exclude. The path value can be a path name or a "glob" which may include the special
+                        wildcards "*", "**", and "?".
+                    </documentation>
+                </annotation>
+            </xsd:element>
+            <xsd:element name="include-set" type="pathSetType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A set of literal path names to include. Wildcards are not supported.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+            <xsd:element name="exclude-set" type="pathSetType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A set of literal path names to exclude. Wildcards are not supported.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+        </xsd:choice>
+    </xsd:complexType>
+
+    <xsd:complexType name="pathSpecType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A path specification type, which may include wildcards.
+            </documentation>
+        </annotation>
+        <xsd:attribute name="path" type="xsd:string" use="required">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The path name, which can be a literal path name or it may include the special wildcards "*", "**",
+                    and "?".
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+    </xsd:complexType>
+
+    <xsd:complexType name="pathSetType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A set of literal path names which can be used for efficient matching against multiple possible values.
+            </documentation>
+        </annotation>
+        <xsd:choice minOccurs="0" maxOccurs="unbounded">
+            <xsd:element name="path" type="pathType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        The path name to include in the set.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+        </xsd:choice>
+    </xsd:complexType>
+
+    <xsd:complexType name="moduleAliasType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A module alias type, which defines the target for a module alias.
+            </documentation>
+        </annotation>
+        <xsd:attribute name="name" type="moduleNameType" use="required">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The name of this module alias (required).
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+        <xsd:attribute name="slot" type="moduleSlotType" use="optional">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The version slot of this module alias (optional).
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+        <xsd:attribute name="target-name" type="moduleNameType" use="required">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The name of the module to which this alias refers (required).
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+        <xsd:attribute name="target-slot" type="moduleSlotType" use="optional">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The version slot of the module to which this alias refers (optional).
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+    </xsd:complexType>
+
+    <xsd:complexType name="moduleAbsentType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                An explicitly absent module.
+            </documentation>
+        </annotation>
+        <xsd:attribute name="name" type="moduleNameType" use="required">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The name of the absent module (required).
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+        <xsd:attribute name="slot" type="moduleSlotType" use="optional">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The version slot of the absent module (optional).
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+    </xsd:complexType>
+
+    <xsd:complexType name="propertyListType">
+        <xsd:choice minOccurs="0" maxOccurs="unbounded">
+            <xsd:element name="property" type="propertyType">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A property in this property list.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+        </xsd:choice>
+    </xsd:complexType>
+
+    <xsd:complexType name="propertyType">
+        <xsd:attribute name="name" type="xsd:string" use="required">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The property name as a string (required).
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+        <xsd:attribute name="value" type="xsd:string" use="optional" default="true">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The property value (optional, defaults to "true").
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+    </xsd:complexType>
+
+    <xsd:complexType name="permissionsType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A list of permissions that this module requires.
+            </documentation>
+        </annotation>
+        <xsd:choice minOccurs="0" maxOccurs="unbounded">
+            <xsd:element name="grant" type="permissionType">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        The permission to grant.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+        </xsd:choice>
+    </xsd:complexType>
+    
+    <xsd:complexType name="permissionType">
+        <xsd:attribute name="permission" type="xsd:string" use="required">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The qualified class name of the permission to grant.
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+        <xsd:attribute name="name" type="xsd:string" use="optional">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The permission name to provide to the permission class constructor.
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+        <xsd:attribute name="actions" type="xsd:string" use="optional">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The (optional) list of actions, required by some permission types.
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+    </xsd:complexType>
+</xsd:schema>
diff --git a/src/main/resources/schema/module-1_3.xsd b/src/main/resources/schema/module-1_3.xsd
new file mode 100755
index 0000000..455bce2
--- /dev/null
+++ b/src/main/resources/schema/module-1_3.xsd
@@ -0,0 +1,623 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  ~ JBoss, Home of Professional Open Source.
+  ~ Copyright 2014 Red Hat, Inc., and individual contributors
+  ~ as indicated by the @author tags.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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"
+            targetNamespace="urn:jboss:module:1.3"
+            xmlns="urn:jboss:module:1.3"
+            elementFormDefault="qualified"
+            attributeFormDefault="unqualified"
+            version="1.0">
+
+    <!-- Root element -->
+    <xsd:element name="module" type="moduleType">
+        <xsd:annotation>
+            <xsd:documentation>
+                Root element for a module declaration.
+            </xsd:documentation>
+        </xsd:annotation>
+    </xsd:element>
+
+    <!-- Root element -->
+    <xsd:element name="module-alias" type="moduleAliasType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                Root element for a module alias declaration.
+            </documentation>
+        </annotation>
+    </xsd:element>
+
+    <!-- Root element -->
+    <xsd:element name="module-absent" type="moduleAbsentType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                Root element for an absent module.
+            </documentation>
+        </annotation>
+    </xsd:element>
+
+    <xsd:complexType name="moduleType">
+        <xsd:annotation>
+            <xsd:documentation>
+                The module declaration type; contains dependencies, resources, and the main class
+                specification.
+            </xsd:documentation>
+        </xsd:annotation>
+        <xsd:all>
+            <xsd:element name="exports" type="filterType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        Lists filter expressions to apply to the export filter of the local resources of this module
+                        (optional). By default, everything is exported. If filter expressions are provided, the default
+                        action is to accept all paths if no filters match.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+            <xsd:element name="dependencies" type="dependenciesType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        Lists the dependencies of this module (optional).
+                    </documentation>
+                </annotation>
+            </xsd:element>
+            <xsd:element name="resources" type="resourcesType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        Lists the resource roots of this module (optional).
+                    </documentation>
+                </annotation>
+            </xsd:element>
+            <xsd:element name="main-class" type="classNameType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        Specifies the main class of this module; used to run the module from the command-line
+                        (optional).
+                    </documentation>
+                </annotation>
+            </xsd:element>
+            <xsd:element name="properties" type="propertyListType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        Lists the user-defined properties to be associated with this module (optional).
+                    </documentation>
+                </annotation>
+            </xsd:element>
+            <xsd:element name="permissions" type="permissionsType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        Lists the requested permission set for this module. If the requested permissions cannot
+                        be assigned, the module cannot be loaded.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+        </xsd:all>
+        <xsd:attribute name="name" type="moduleNameType" use="required">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The name of this module (required).
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+        <xsd:attribute name="slot" type="moduleSlotType" use="optional">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The version slot of this module (optional).
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+    </xsd:complexType>
+
+    <xsd:simpleType name="moduleNameType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A module name, which consists of one or more dot (.)-separated segments. Each segment must begin and end
+                with an alphanumeric or underscore (_), and may otherwise contain alphanumerics, underscores, and hyphens
+                (-).
+            </documentation>
+        </annotation>
+        <xsd:restriction base="xsd:string">
+            <xsd:pattern value="[a-zA-Z0-9_]([-a-zA-Z0-9_]*[a-zA-Z0-9_])?(\.[a-zA-Z0-9_]([-a-zA-Z0-9_]*[a-zA-Z0-9_])?)*"/>
+        </xsd:restriction>
+    </xsd:simpleType>
+
+    <xsd:simpleType name="moduleSlotType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A module version slot. A slot may consist of one or more alphanumerics, hyphens (-), underscores (_),
+                plus signs (+), asterisks (*), or dots (.).
+            </documentation>
+        </annotation>
+        <xsd:restriction base="xsd:string">
+            <xsd:pattern value="[-a-zA-Z0-9_+*.]+"/>
+        </xsd:restriction>
+    </xsd:simpleType>
+
+    <xsd:complexType name="dependenciesType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A list of zero or more module dependencies.
+            </documentation>
+        </annotation>
+        <xsd:choice minOccurs="0" maxOccurs="unbounded">
+            <xsd:element name="module" type="moduleDependencyType">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A specified module dependency.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+            <xsd:element name="system" type="systemDependencyType">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A dependency on the system (or embedding) class loader.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+        </xsd:choice>
+    </xsd:complexType>
+
+    <xsd:complexType name="moduleDependencyType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A single module dependency expression.
+            </documentation>
+        </annotation>
+        <xsd:all minOccurs="0">
+            <xsd:element name="exports" type="filterType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A filter used to restrict what packages or directories from this dependency are re-exported by
+                        this module. See also the "export" and "services" attributes. The default action of this filter
+                        list is controlled by the value of the "export" attribute. Regardless of the setting of these
+                        attributes, this filter always behaves as if it has a final entry which rejects META-INF and
+                        all of its subdirectories.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+            <xsd:element name="imports" type="filterType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A filter used to restrict what packages or directories from this dependency are visible to this
+                        module. See also the "services" attribute. The default action of this filter list is to reject
+                        a path if not matched.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+        </xsd:all>
+        <xsd:attribute name="name" type="moduleNameType" use="required">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The dependency module name (required).
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+        <xsd:attribute name="slot" type="moduleSlotType" use="optional">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The dependency module version slot (optional).
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+        <xsd:attribute name="export" type="xsd:boolean" use="optional" default="false">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    Specifies whether this module dependency is re-exported by default (default is "false"). Setting
+                    this attribute to true sets the default action for the export filter list to "accept"; leaving it
+                    as false sets the default action to "reject".  Thus you can still export dependency resources even
+                    if this attribute is false by listing explicit paths for the export list.
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+        <xsd:attribute name="services" type="serviceDispositionType" use="optional" default="none">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    Specifies whether and how services found in this dependency are used (default is "none"). Specifying
+                    a value of "import" for this attribute is equivalent to adding a filter at the end of the import
+                    filter list which includes the META-INF/services path from the dependency module.  Setting a value
+                    of "export" for this attribute is equivalent to the same action on the export filter list.
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+        <xsd:attribute name="optional" type="xsd:boolean" use="optional" default="false">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    Specifies whether this dependency is optional (defaults to false). An optional dependency will not
+                    cause the module to fail to load if not found; however if the module is added later, it will not be
+                    retroactively linked into this module's dependency list.
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+    </xsd:complexType>
+
+    <xsd:complexType name="systemDependencyType">
+        <xsd:all>
+            <xsd:element name="paths" type="pathSetType">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        The list of paths which are applicable for this system dependency.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+            <xsd:element name="exports" type="filterType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A filter used to restrict what packages or directories from this dependency are re-exported by
+                        this module. See also the "export" and "services" attributes. The default action of this filter
+                        list is controlled by the value of the "export" attribute. Regardless of the setting of these
+                        attributes, this filter always behaves as if it has a final entry which rejects META-INF and
+                        all of its subdirectories.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+        </xsd:all>
+        <xsd:attribute name="export" type="xsd:boolean" use="optional" default="false">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    Specifies whether this module dependency is re-exported by default (default is "false"). Setting
+                    this attribute to true sets the default action for the export filter list to "accept"; leaving it
+                    as false sets the default action to "reject".  Thus you can still export dependency resources even
+                    if this attribute is false by listing explicit paths for the export list.
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+    </xsd:complexType>
+
+    <xsd:simpleType name="serviceDispositionType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                The requested behavior for service handling on a dependency.
+            </documentation>
+        </annotation>
+        <xsd:restriction base="xsd:string">
+            <xsd:enumeration value="none">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        Do not import or export services from this dependency.
+                    </documentation>
+                </annotation>
+            </xsd:enumeration>
+            <xsd:enumeration value="import">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        Import, but do not re-export, services from this dependency.
+                    </documentation>
+                </annotation>
+            </xsd:enumeration>
+            <xsd:enumeration value="export">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        Import and re-export services found in this dependency.
+                    </documentation>
+                </annotation>
+            </xsd:enumeration>
+        </xsd:restriction>
+    </xsd:simpleType>
+
+    <xsd:complexType name="classNameType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A class name.
+            </documentation>
+        </annotation>
+        <xsd:attribute name="name" type="xsd:string" use="required">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The class name.
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+    </xsd:complexType>
+
+    <xsd:complexType name="pathType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A filesystem path name.
+            </documentation>
+        </annotation>
+        <xsd:attribute name="name" type="xsd:string" use="required">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The path name.
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+    </xsd:complexType>
+
+    <xsd:complexType name="resourcesType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A list of zero or more resource roots for this deployment.
+            </documentation>
+        </annotation>
+        <xsd:choice minOccurs="0" maxOccurs="unbounded">
+            <xsd:element name="resource-root" type="resourceType">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A resource root within this deployment.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+            <xsd:element name="artifact" type="artifactType">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A maven artifact within this deployment.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+            <xsd:element name="native-artifact" type="artifactType">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A maven native artifact within this deployment.  This is a jar that contains a lib/ directory
+                        with corresponding platform directories and binaries.  This element will cause the jar to
+                        be unzipped within the artifact's local repository directory.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+        </xsd:choice>
+    </xsd:complexType>
+
+    <xsd:complexType name="artifactType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A maven artifact within a deployment.
+            </documentation>
+        </annotation>
+        <xsd:attribute name="name" type="xsd:string" use="required">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    URI that points to the maven artifact "group:artifact:version[:classifier]"
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+    </xsd:complexType>
+
+
+    <xsd:complexType name="resourceType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A resource root within a deployment.
+            </documentation>
+        </annotation>
+        <xsd:all>
+            <xsd:element name="filter" type="filterType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A path filter specification for this resource root (optional). By default all paths are accepted.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+        </xsd:all>
+        <xsd:attribute name="name" type="xsd:string" use="optional">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The name of this resource root (optional). If not specified, defaults to the value of the path
+                    attribute.
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+        <xsd:attribute name="path" type="xsd:string" use="required">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The path of this resource root, relative to the path in which the module.xml file is found.
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+    </xsd:complexType>
+
+    <xsd:complexType name="filterType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A filter specification, consisting of zero or more filter items.
+            </documentation>
+        </annotation>
+        <xsd:choice minOccurs="0" maxOccurs="unbounded">
+            <xsd:element name="include" type="pathSpecType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A path to include. The path value can be a path name or a "glob" which may include the special
+                        wildcards "*", "**", and "?".
+                    </documentation>
+                </annotation>
+            </xsd:element>
+            <xsd:element name="exclude" type="pathSpecType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A path to exclude. The path value can be a path name or a "glob" which may include the special
+                        wildcards "*", "**", and "?".
+                    </documentation>
+                </annotation>
+            </xsd:element>
+            <xsd:element name="include-set" type="pathSetType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A set of literal path names to include. Wildcards are not supported.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+            <xsd:element name="exclude-set" type="pathSetType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A set of literal path names to exclude. Wildcards are not supported.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+        </xsd:choice>
+    </xsd:complexType>
+
+    <xsd:complexType name="pathSpecType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A path specification type, which may include wildcards.
+            </documentation>
+        </annotation>
+        <xsd:attribute name="path" type="xsd:string" use="required">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The path name, which can be a literal path name or it may include the special wildcards "*", "**",
+                    and "?".
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+    </xsd:complexType>
+
+    <xsd:complexType name="pathSetType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A set of literal path names which can be used for efficient matching against multiple possible values.
+            </documentation>
+        </annotation>
+        <xsd:choice minOccurs="0" maxOccurs="unbounded">
+            <xsd:element name="path" type="pathType" minOccurs="0">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        The path name to include in the set.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+        </xsd:choice>
+    </xsd:complexType>
+
+    <xsd:complexType name="moduleAliasType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A module alias type, which defines the target for a module alias.
+            </documentation>
+        </annotation>
+        <xsd:attribute name="name" type="moduleNameType" use="required">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The name of this module alias (required).
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+        <xsd:attribute name="slot" type="moduleSlotType" use="optional">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The version slot of this module alias (optional).
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+        <xsd:attribute name="target-name" type="moduleNameType" use="required">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The name of the module to which this alias refers (required).
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+        <xsd:attribute name="target-slot" type="moduleSlotType" use="optional">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The version slot of the module to which this alias refers (optional).
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+    </xsd:complexType>
+
+    <xsd:complexType name="moduleAbsentType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                An explicitly absent module.
+            </documentation>
+        </annotation>
+        <xsd:attribute name="name" type="moduleNameType" use="required">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The name of the absent module (required).
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+        <xsd:attribute name="slot" type="moduleSlotType" use="optional">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The version slot of the absent module (optional).
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+    </xsd:complexType>
+
+    <xsd:complexType name="propertyListType">
+        <xsd:choice minOccurs="0" maxOccurs="unbounded">
+            <xsd:element name="property" type="propertyType">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        A property in this property list.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+        </xsd:choice>
+    </xsd:complexType>
+
+    <xsd:complexType name="propertyType">
+        <xsd:attribute name="name" type="xsd:string" use="required">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The property name as a string (required).
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+        <xsd:attribute name="value" type="xsd:string" use="optional" default="true">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The property value (optional, defaults to "true").
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+    </xsd:complexType>
+
+    <xsd:complexType name="permissionsType">
+        <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+            <documentation>
+                A list of permissions that this module requires.
+            </documentation>
+        </annotation>
+        <xsd:choice minOccurs="0" maxOccurs="unbounded">
+            <xsd:element name="grant" type="permissionType">
+                <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                    <documentation>
+                        The permission to grant.
+                    </documentation>
+                </annotation>
+            </xsd:element>
+        </xsd:choice>
+    </xsd:complexType>
+    
+    <xsd:complexType name="permissionType">
+        <xsd:attribute name="permission" type="xsd:string" use="required">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The qualified class name of the permission to grant.
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+        <xsd:attribute name="name" type="xsd:string" use="optional">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The permission name to provide to the permission class constructor.
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+        <xsd:attribute name="actions" type="xsd:string" use="optional">
+            <annotation xmlns="http://www.w3.org/2001/XMLSchema">
+                <documentation>
+                    The (optional) list of actions, required by some permission types.
+                </documentation>
+            </annotation>
+        </xsd:attribute>
+    </xsd:complexType>
+</xsd:schema>
diff --git a/src/test/java/org/jboss/modules/AbstractModuleTestCase.java b/src/test/java/org/jboss/modules/AbstractModuleTestCase.java
new file mode 100644
index 0000000..940a76e
--- /dev/null
+++ b/src/test/java/org/jboss/modules/AbstractModuleTestCase.java
@@ -0,0 +1,90 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import org.jboss.modules.log.StreamModuleLogger;
+import org.jboss.modules.util.Util;
+import org.junit.BeforeClass;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Abstract Test Case used as a base for all module tests.
+ *
+ * @author John Bailey
+ */
+public class AbstractModuleTestCase {
+    protected static final ModuleIdentifier MODULE_ID = ModuleIdentifier.fromString("test.test");
+
+    @BeforeClass
+    public static void initUrlHandler() {
+        // this also kicks off Module's static init
+        Module.setModuleLogger(new StreamModuleLogger(System.err));
+    }
+
+    protected File getResource(final String path) throws Exception {
+        return Util.getResourceFile(getClass(), path);
+    }
+
+    protected void copyResource(final String inputResource, final String outputBase, final String outputPath) throws Exception {
+        final File resource = getResource(inputResource);
+        final File outputDirectory = new File(getResource(outputBase), outputPath);
+
+        if(!resource.exists())
+            throw new IllegalArgumentException("Resource does not exist");
+        if (outputDirectory.exists() && outputDirectory.isFile())
+            throw new IllegalArgumentException("OutputDirectory must be a directory");
+        if (!outputDirectory.exists()) {
+            if (!outputDirectory.mkdirs())
+                throw new RuntimeException("Failed to create output directory");
+        }
+        final File outputFile = new File(outputDirectory, resource.getName());
+        final InputStream in = new FileInputStream(resource);
+        try {
+            final OutputStream out = new FileOutputStream(outputFile);
+            try {
+                final byte[] b = new byte[8192];
+                int c;
+                while ((c = in.read(b)) != -1) {
+                    out.write(b, 0, c);
+                }
+                out.close();
+                in.close();
+            } finally {
+                safeClose(out);
+            }
+        } finally {
+            safeClose(in);
+        }
+    }
+
+    private static void safeClose(final Closeable closeable) {
+        if (closeable != null) try {
+            closeable.close();
+        } catch (IOException e) {
+            // meh
+        }
+    }
+}
diff --git a/src/test/java/org/jboss/modules/AbstractResourceLoaderTestCase.java b/src/test/java/org/jboss/modules/AbstractResourceLoaderTestCase.java
new file mode 100644
index 0000000..12d3ddf
--- /dev/null
+++ b/src/test/java/org/jboss/modules/AbstractResourceLoaderTestCase.java
@@ -0,0 +1,181 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import static org.jboss.modules.util.Util.readBytes;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.net.URL;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.jboss.modules.filter.PathFilter;
+import org.jboss.modules.filter.PathFilters;
+import org.junit.Before;
+import org.junit.Test;
+
+
+/**
+ * Abstract Test Case used as the base for all resource loader tests.
+ *
+ * @author John Bailey
+ */
+public abstract class AbstractResourceLoaderTestCase extends AbstractModuleTestCase {
+
+    protected ResourceLoader loader;
+
+    @Before
+    public void setupLoader() throws Exception {
+        loader = createLoader(PathFilters.acceptAll());
+    }
+
+    protected abstract ResourceLoader createLoader(final PathFilter exportFilter) throws Exception;
+    protected abstract void assertResource(final Resource resource, final String fileName);
+
+    @Test
+    public void testBasicResource() throws Exception {
+        Resource resource = loader.getResource("/test.txt");
+        assertNotNull(resource);
+        assertResource(resource, "test.txt");
+        resource = loader.getResource("/nested/nested.txt");
+        assertNotNull(resource);
+        assertResource(resource, "nested/nested.txt");
+    }
+
+    @Test
+    public void testMissingResource() throws Exception {
+        Resource resource = loader.getResource("/test-bogus.txt");
+        assertNull(resource);
+    }
+
+    @Test
+    public void testIndexPaths() throws Exception {
+        final Collection<String> paths = loader.getPaths();
+        assertFalse(paths.isEmpty());
+
+        assertTrue(paths.contains(""));
+        assertTrue(paths.contains("META-INF"));
+        assertTrue(paths.contains("nested"));
+        assertTrue(paths.contains("org"));
+        assertTrue(paths.contains("org/jboss"));
+        assertTrue(paths.contains("org/jboss/modules"));
+        assertTrue(paths.contains("org/jboss/modules/test"));
+    }
+
+    @Test
+    public void testGetClassSpec() throws Exception {
+        ClassSpec spec = loader.getClassSpec(Module.fileNameOfClass("org.jboss.modules.test.TestClass"));
+        assertNotNull(spec);
+        byte[] bytes = spec.getBytes();
+
+        final URL classResource = getClass().getClassLoader().getResource("org/jboss/modules/test/TestClass.class");
+        final byte[] expectedBytes = readBytes(classResource.openStream());
+        assertArrayEquals(expectedBytes, bytes);
+    }
+
+    @Test
+    public void testMissingClassSpec() throws Exception {
+        ClassSpec spec = loader.getClassSpec(Module.fileNameOfClass("org.jboss.modules.test.BogusClass"));
+        assertNull(spec);
+    }
+
+    @Test
+    public void testGetPackageSpec() throws Exception {
+        PackageSpec spec = loader.getPackageSpec("org.jboss.modules.test");
+        assertNotNull(spec);
+
+        assertEquals("JBoss Modules Test Classes", spec.getSpecTitle());
+        assertEquals("0.1", spec.getSpecVersion());
+        assertEquals("JBoss", spec.getSpecVendor());
+        assertEquals("org.jboss.modules.test", spec.getImplTitle());
+        assertEquals("1.0", spec.getImplVersion());
+        assertEquals("JBoss", spec.getImplVendor());
+    }
+
+    @Test
+    public void testMissingPackageSpec() throws Exception {
+        PackageSpec spec = loader.getPackageSpec("org.jboss.modules.bogus");
+        assertNotNull(spec);
+
+        assertEquals("MODULES-89", spec.getSpecTitle());
+        assertNull(spec.getSpecVersion());
+        assertNull(spec.getSpecVendor());
+        assertNull(spec.getImplTitle());
+        assertNull(spec.getImplVersion());
+        assertNull(spec.getImplVendor());
+    }
+
+    @Test
+    public void testIterateResourcesRootRecursive() throws Exception {
+        Set<String> expected = new HashSet<String>();
+        expected.add("test.txt");
+        expected.add("nested/nested.txt");
+        expected.add("org/jboss/modules/test/TestClass.class");
+        expected.add("META-INF/MANIFEST.MF");
+        assertEquals(expected, getResourceNames("/", true));
+        assertEquals(expected, getResourceNames("", true));
+    }
+
+    @Test
+    public void testIterateResourcesRoot() throws Exception {
+        Set<String> expected = new HashSet<String>();
+        expected.add("test.txt");
+        assertEquals(expected, getResourceNames("/", false));
+        assertEquals(expected, getResourceNames("", false));
+    }
+
+    @Test
+    public void testIterateResourcesNested() throws Exception {
+        Set<String> expected = new HashSet<String>();
+        expected.add("nested/nested.txt");
+        assertEquals(expected, getResourceNames("/nested", true));
+        assertEquals(expected, getResourceNames("/nested", false));
+        assertEquals(expected, getResourceNames("nested", true));
+        assertEquals(expected, getResourceNames("nested", false));
+    }
+
+    @Test
+    public void testIterateResourcesClasses() throws Exception {
+        Set<String> expected = new HashSet<String>();
+        expected.add("org/jboss/modules/test/TestClass.class");
+        assertEquals(expected, getResourceNames("/org/jboss/modules", true));
+        assertEquals(expected, getResourceNames("org/jboss/modules", true));
+        expected = Collections.<String>emptySet();
+        assertEquals(expected, getResourceNames("/org/jboss/modules", false));
+        assertEquals(expected, getResourceNames("org/jboss/modules", false));
+    }
+
+    private Set<String> getResourceNames(String startPath, boolean recursive) {
+        Set<String> result = new HashSet<String>();
+        IterableResourceLoader itloader = (IterableResourceLoader) loader;
+        Iterator<Resource> itres = itloader.iterateResources(startPath, recursive);
+        while(itres.hasNext()) {
+            result.add(itres.next().getName());
+        }
+        return result;
+    }
+}
diff --git a/src/test/java/org/jboss/modules/ClassFilteringTest.java b/src/test/java/org/jboss/modules/ClassFilteringTest.java
new file mode 100644
index 0000000..1560355
--- /dev/null
+++ b/src/test/java/org/jboss/modules/ClassFilteringTest.java
@@ -0,0 +1,98 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jboss.modules;
+
+import org.jboss.modules.filter.ClassFilter;
+import org.jboss.modules.filter.ClassFilters;
+import org.jboss.modules.filter.PathFilter;
+import org.jboss.modules.filter.PathFilters;
+import org.jboss.modules.test.BarImpl;
+import org.jboss.modules.test.QuxBar;
+import org.jboss.modules.test.QuxFoo;
+import org.jboss.modules.test.QuxImpl;
+import org.jboss.modules.util.ModulesTestBase;
+import org.jboss.modules.util.TestResourceLoader;
+import org.junit.Test;
+
+import static org.jboss.modules.DependencySpec.createLocalDependencySpec;
+import static org.jboss.modules.DependencySpec.createModuleDependencySpec;
+import static org.jboss.modules.ResourceLoaderSpec.createResourceLoaderSpec;
+import static org.jboss.modules.util.TestResourceLoader.TestResourceLoaderBuilder;
+
+/**
+ * [MODULES-69] Allow for OSGi style Class Filtering
+ * 
+ * @author Thomas.Diesler at jboss.com
+ * @since 28-Apr-2011
+ */
+public class ClassFilteringTest extends ModulesTestBase {
+
+    @Test
+    public void testClassFilter() throws Exception {
+        final ModuleIdentifier identifierA = ModuleIdentifier.create(getClass().getSimpleName());
+
+        ModuleSpec.Builder specBuilderA = ModuleSpec.build(identifierA);
+
+        // Export-Package: com.acme.foo; include:="Qux*,BarImpl";exclude:=QuxImpl
+
+        String packagePath = QuxBar.class.getPackage().getName().replace('.', '/');
+        PathFilter inA = PathFilters.match(packagePath + "/Qux*.class");
+        PathFilter inB = PathFilters.match(packagePath + "/BarImpl.class");
+        PathFilter exA = PathFilters.match(packagePath + "/QuxImpl.class");
+
+        //A class is only visible if it is:
+        //    Matched with an entry in the included list, and
+        //    Not matched with an entry in the excluded list.
+
+        PathFilter in = PathFilters.any(inA, inB);
+        PathFilter ex = PathFilters.not(PathFilters.any(exA));
+        final PathFilter filter = PathFilters.all(in, ex);
+
+        ClassFilter classImportFilter = ClassFilters.acceptAll();
+        ClassFilter classExportFilter = ClassFilters.fromResourcePathFilter(filter);
+
+        specBuilderA.addResourceRoot(createResourceLoaderSpec(getTestResourceLoader()));
+        PathFilter importFilter = PathFilters.acceptAll();
+        PathFilter exportFilter = PathFilters.acceptAll();
+        PathFilter resourceImportFilter = PathFilters.acceptAll();
+        PathFilter resourceExportFilter = PathFilters.acceptAll();
+        specBuilderA.addDependency(createLocalDependencySpec(importFilter, exportFilter, resourceImportFilter, resourceExportFilter, classImportFilter, classExportFilter));
+        addModuleSpec(specBuilderA.create());
+
+        ModuleIdentifier identifierB = ModuleIdentifier.create("moduleB");
+        ModuleSpec.Builder specBuilderB = ModuleSpec.build(identifierB);
+        specBuilderB.addDependency(createModuleDependencySpec(identifierA));
+        addModuleSpec(specBuilderB.create());
+
+        assertLoadClass(identifierA, QuxFoo.class.getName());
+        assertLoadClass(identifierA, QuxBar.class.getName());
+        assertLoadClass(identifierA, QuxImpl.class.getName());
+        assertLoadClass(identifierA, BarImpl.class.getName());
+
+        assertLoadClass(identifierB, QuxFoo.class.getName());
+        assertLoadClass(identifierB, QuxBar.class.getName());
+        assertLoadClassFail(identifierB, QuxImpl.class.getName());
+        assertLoadClass(identifierB, BarImpl.class.getName());
+    }
+
+    private TestResourceLoader getTestResourceLoader() throws Exception {
+        TestResourceLoaderBuilder builder = new TestResourceLoaderBuilder();
+        builder.addClasses(QuxBar.class, QuxFoo.class, QuxImpl.class, BarImpl.class);
+        return builder.create();
+    }
+}
diff --git a/src/test/java/org/jboss/modules/ClassPathModuleLoaderTest.java b/src/test/java/org/jboss/modules/ClassPathModuleLoaderTest.java
new file mode 100644
index 0000000..eeeea52
--- /dev/null
+++ b/src/test/java/org/jboss/modules/ClassPathModuleLoaderTest.java
@@ -0,0 +1,99 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.lang.reflect.Method;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.io.File;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Enumeration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+/**
+ * Test to verify the functionality of the ClassPathModuleLoader.
+ *
+ * @author @author <a href="mailto:jperkins at redhat.com">James R. Perkins</a>
+ * @author Scott Stark (sstark at redhat.com)
+ */
+public class ClassPathModuleLoaderTest extends AbstractModuleTestCase {
+
+    @BeforeClass
+    public static void beforeClass() throws Exception {
+        final Method method = ModuleLoader.class.getDeclaredMethod("installMBeanServer");
+        method.setAccessible(true);
+        method.invoke(null);
+    }
+
+    @Test
+    public void testLoader() throws Exception {
+        final File repoRoot = getResource("test/repo");
+        final String classPath = "./target/test-classes/test/repo";
+        final String deps = "test.test,test.with-deps";
+        final String mainClass = "org.jboss.modules.test.TestClass";
+        final ModuleLoader moduleLoader = new ClassPathModuleLoader(new LocalModuleLoader(new File[] { repoRoot }), mainClass, classPath, deps);
+        Module module = moduleLoader.loadModule(ModuleIdentifier.CLASSPATH);
+        module.getClassLoader();
+        assertNotNull(module);
+    }
+
+    /**
+     * I need to be able to load EJBContainerProvider from a dependency.
+     *
+     * @author <a href="mailto:cdewolf at redhat.com">Carlo de Wolf</a>
+     */
+    @Test
+    public void testService() throws Exception {
+        final File repoRoot = getResource("test/repo");
+        final String classPath = "./target/test-classes/test/repo";
+        final String deps = "test.service";
+        final String mainClass = null;
+        final ModuleLoader moduleLoader = new ClassPathModuleLoader(new LocalModuleLoader(new File[] { repoRoot }), mainClass, classPath, deps);
+        final Module module = moduleLoader.loadModule(ModuleIdentifier.CLASSPATH);
+        final ClassLoader classLoader = module.getClassLoader();
+        final URL url = classLoader.getResource("META-INF/services/dummy");
+        assertNotNull(url);
+    }
+
+    /**
+     * Validate that dependent module META-INF/services/* content is seen
+     * @throws Exception
+     */
+    @Test
+    public void testMultipleServices() throws Exception {
+        final File repoRoot = getResource("test/repo");
+        final String classPath = "./target/test-classes/test/repo";
+        final String deps = "test.jaxrs";
+        final String mainClass = null;
+        final ModuleLoader moduleLoader = new ClassPathModuleLoader(new LocalModuleLoader(new File[] { repoRoot }), mainClass, classPath, deps);
+        final Module module = moduleLoader.loadModule(ModuleIdentifier.CLASSPATH);
+        final ClassLoader classLoader = module.getClassLoader();
+        final Enumeration<URL> services = classLoader.getResources("META-INF/services/javax.ws.rs.ext.Providers");
+        assertNotNull(services);
+        ArrayList<URL> found = new ArrayList<URL>();
+        while(services.hasMoreElements()) {
+            found.add(services.nextElement());
+        }
+        assertEquals("Found 2 services of type javax.ws.rs.ext.Providers", 2, found.size());
+    }
+}
diff --git a/src/test/java/org/jboss/modules/ConcurrentClassLoaderTest.java b/src/test/java/org/jboss/modules/ConcurrentClassLoaderTest.java
new file mode 100644
index 0000000..d1dd4bd
--- /dev/null
+++ b/src/test/java/org/jboss/modules/ConcurrentClassLoaderTest.java
@@ -0,0 +1,128 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import org.jboss.modules.test.ClassA;
+import org.jboss.modules.test.ClassB;
+import org.jboss.modules.test.ClassC;
+import org.jboss.modules.test.ClassD;
+import org.jboss.modules.util.Util;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Test case to verify the concurrent classloader base correctly handles common concurrency issues with classloading..
+ *
+ * @author John E. Bailey
+ */
+public class ConcurrentClassLoaderTest {
+    volatile Throwable threadOneProblem;
+    volatile Throwable threadTwoProblem;
+
+    @Test
+    public void testClassLoadingDeadlockAvoidance() throws Throwable {
+        /*
+            Uncomment the following lines to demonstrate a deadlock that occurs with normal classloader delegation
+         */
+        //final DeadLockingLoader classLoaderOne = new DeadLockingLoader(ConcurrentClassLoaderTest.class.getClassLoader(), Arrays.asList(ClassA.class.getName(), ClassD.class.getName()));
+        //final DeadLockingLoader classLoaderTwo = new DeadLockingLoader(ConcurrentClassLoaderTest.class.getClassLoader(), Arrays.asList(ClassB.class.getName(), ClassC.class.getName()));
+        final TestConcurrentClassLoader classLoaderOne = new TestConcurrentClassLoader(ConcurrentClassLoaderTest.class.getClassLoader(), Arrays.asList(ClassA.class.getName(), ClassD.class.getName()));
+        final TestConcurrentClassLoader classLoaderTwo = new TestConcurrentClassLoader(ConcurrentClassLoaderTest.class.getClassLoader(), Arrays.asList(ClassB.class.getName(), ClassC.class.getName()));        classLoaderOne.delegate = classLoaderTwo;
+        classLoaderTwo.delegate = classLoaderOne;
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        final Thread threadOne = new Thread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    latch.await();
+                    classLoaderOne.loadClass(ClassA.class.getName());
+                } catch (Throwable t) {
+                    threadOneProblem = t;
+                }
+            }
+        });
+        final Thread threadTwo = new Thread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    latch.await();
+                    classLoaderTwo.loadClass(ClassC.class.getName());
+                } catch (Throwable t) {
+                    threadTwoProblem = t;
+                }
+            }
+        });
+        threadOne.start();
+        threadTwo.start();
+
+        latch.countDown();
+
+        threadOne.join();
+        threadTwo.join();
+
+        if (threadOneProblem != null) throw threadOneProblem;
+        if (threadTwoProblem != null) throw threadTwoProblem;
+    }
+
+    private static final class TestConcurrentClassLoader extends ConcurrentClassLoader {
+        static {
+            boolean parallelOk = true;
+            try {
+                parallelOk = ClassLoader.registerAsParallelCapable();
+            } catch (Throwable ignored) {
+            }
+            if (! parallelOk) {
+                throw new Error("Failed to register " + TestConcurrentClassLoader.class.getName() + " as parallel-capable");
+            }
+        }
+
+        private final ClassLoader realLoader;
+        private final Set<String> allowedClasses = new HashSet<String>();
+        private ClassLoader delegate;
+
+        private TestConcurrentClassLoader(final ClassLoader realLoader, final Collection<String> allowedClasses) {
+            this.realLoader = realLoader;
+            this.allowedClasses.addAll(allowedClasses);
+        }
+
+        @Override
+        protected Class<?> findClass(String className, boolean exportsOnly, final boolean resolve) throws ClassNotFoundException {
+            Class<?> c = findLoadedClass(className);
+            if(c == null && className.startsWith("java"))
+                c = findSystemClass(className);
+            if(c == null && allowedClasses.contains(className)) {
+                try {
+                    final byte[] classBytes = Util.getClassBytes(realLoader.loadClass(className));
+                    c = defineClass(className, classBytes, 0, classBytes.length);
+                } catch(Throwable t) {
+                    throw new ClassNotFoundException("Failed to load class " + className, t);
+                }
+            }
+            if(c == null)
+                c =  delegate.loadClass(className);
+            return c;
+        }
+    };
+}
diff --git a/src/test/java/org/jboss/modules/ErrorHandlingTest.java b/src/test/java/org/jboss/modules/ErrorHandlingTest.java
new file mode 100644
index 0000000..99fb0c8
--- /dev/null
+++ b/src/test/java/org/jboss/modules/ErrorHandlingTest.java
@@ -0,0 +1,61 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jboss.modules;
+
+import org.jboss.modules.test.ClassA;
+import org.jboss.modules.util.TestModuleLoader;
+import org.jboss.modules.util.TestResourceLoader;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.fail;
+
+/**
+ * @author <a href="mailto:cdewolf at redhat.com">Carlo de Wolf</a>
+ */
+public class ErrorHandlingTest extends AbstractModuleTestCase {
+    private static final ModuleIdentifier MODULE_A = ModuleIdentifier.fromString("test-module-a");
+
+    private TestModuleLoader moduleLoader;
+
+    @Before
+    public void before() throws Exception {
+        moduleLoader = new TestModuleLoader();
+
+        final ModuleSpec.Builder moduleABuilder = ModuleSpec.build(MODULE_A);
+        moduleABuilder.addResourceRoot(ResourceLoaderSpec.createResourceLoaderSpec(
+                TestResourceLoader.build()
+                        .addClass(ClassA.class)
+                        .create())
+        );
+        moduleABuilder.addDependency(DependencySpec.createLocalDependencySpec());
+        moduleLoader.addModuleSpec(moduleABuilder.create());
+    }
+
+    @Test
+    public void testNonLinkingClass() throws ModuleLoadException, ClassNotFoundException {
+        final Module module = moduleLoader.loadModule(MODULE_A);
+        final ClassLoader classLoader = module.getClassLoader();
+        try {
+            classLoader.loadClass(ClassA.class.getName());
+            fail("Should have thrown a LinkageError");
+        } catch(LinkageError e) {
+            // good
+        }
+    }
+}
diff --git a/src/test/java/org/jboss/modules/FileResourceLoaderTest.java b/src/test/java/org/jboss/modules/FileResourceLoaderTest.java
new file mode 100644
index 0000000..0b5d980
--- /dev/null
+++ b/src/test/java/org/jboss/modules/FileResourceLoaderTest.java
@@ -0,0 +1,57 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import org.jboss.modules.filter.PathFilter;
+import org.junit.Assert;
+
+import java.io.File;
+import java.security.AccessController;
+
+/**
+ * Test the functionality of the FileResourceLoader
+ *
+ * @author John Bailey
+ */
+public class FileResourceLoaderTest extends AbstractResourceLoaderTestCase {
+
+    private File resourceRoot;
+
+    protected ResourceLoader createLoader(final PathFilter exportFilter) throws Exception {
+        resourceRoot = getResource("test/fileresourceloader");
+        // Copy the classfile over
+        copyResource("org/jboss/modules/test/TestClass.class", "test/fileresourceloader", "org/jboss/modules/test");
+        return new FileResourceLoader("test-root", resourceRoot, AccessController.getContext());
+    }
+
+    @Override
+    protected void assertResource(Resource resource, String fileName) {
+        final File resourceFile = getExpectedFile(fileName);
+
+        Assert.assertEquals(resourceFile.length(), resource.getSize());
+    }
+
+    public void testGetClassSpec() throws Exception {
+        super.testGetClassSpec();
+    }
+
+    protected File getExpectedFile(String fileName) {
+        return new File(resourceRoot, fileName);
+    }
+}
diff --git a/src/test/java/org/jboss/modules/InstantiatePrivateAccessTest.java b/src/test/java/org/jboss/modules/InstantiatePrivateAccessTest.java
new file mode 100644
index 0000000..080b790
--- /dev/null
+++ b/src/test/java/org/jboss/modules/InstantiatePrivateAccessTest.java
@@ -0,0 +1,62 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.security.AllPermission;
+import java.security.Permissions;
+
+import org.jboss.modules.security.ModularPermissionFactory;
+import org.junit.Test;
+
+/**
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public class InstantiatePrivateAccessTest {
+
+    @Test
+    public void ensureFailure() {
+        try {
+            Module.getPrivateAccess();
+            fail("Expected security exception");
+        } catch (SecurityException ok) {}
+    }
+
+    @Test
+    public void ensureModularPermissionFactory() {
+        final ModuleIdentifier test = ModuleIdentifier.create("test");
+        final ModuleLoader moduleLoader = new ModuleLoader(new ModuleFinder[]{
+            new ModuleFinder() {
+                public ModuleSpec findModule(final ModuleIdentifier identifier, final ModuleLoader delegateLoader) throws ModuleLoadException {
+                    if (identifier.equals(test)) {
+                        final Permissions perms = new Permissions();
+                        perms.add(new AllPermission());
+                        return ModuleSpec.build(test).setPermissionCollection(perms).create();
+                    } else {
+                        return null;
+                    }
+                }
+            }
+        });
+        final ModularPermissionFactory factory = new ModularPermissionFactory(moduleLoader, test, RuntimePermission.class.getName(), "foo", "*");
+        assertEquals(new RuntimePermission("foo", "*"), factory.construct());
+    }
+}
diff --git a/src/test/java/org/jboss/modules/JAXPModuleTest.java b/src/test/java/org/jboss/modules/JAXPModuleTest.java
new file mode 100644
index 0000000..6d4422d
--- /dev/null
+++ b/src/test/java/org/jboss/modules/JAXPModuleTest.java
@@ -0,0 +1,1325 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.Writer;
+import java.lang.reflect.Modifier;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Properties;
+
+import javax.xml.XMLConstants;
+import javax.xml.datatype.DatatypeConstants.Field;
+import javax.xml.datatype.DatatypeFactory;
+import javax.xml.datatype.Duration;
+import javax.xml.datatype.XMLGregorianCalendar;
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.namespace.QName;
+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 javax.xml.stream.EventFilter;
+import javax.xml.stream.Location;
+import javax.xml.stream.StreamFilter;
+import javax.xml.stream.XMLEventFactory;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLEventWriter;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLReporter;
+import javax.xml.stream.XMLResolver;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+import javax.xml.stream.XMLStreamWriter;
+import javax.xml.stream.events.Attribute;
+import javax.xml.stream.events.Characters;
+import javax.xml.stream.events.Comment;
+import javax.xml.stream.events.DTD;
+import javax.xml.stream.events.EndDocument;
+import javax.xml.stream.events.EndElement;
+import javax.xml.stream.events.EntityDeclaration;
+import javax.xml.stream.events.EntityReference;
+import javax.xml.stream.events.Namespace;
+import javax.xml.stream.events.ProcessingInstruction;
+import javax.xml.stream.events.StartDocument;
+import javax.xml.stream.events.StartElement;
+import javax.xml.stream.util.XMLEventAllocator;
+import javax.xml.transform.ErrorListener;
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+import javax.xml.transform.Templates;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.URIResolver;
+import javax.xml.transform.sax.SAXTransformerFactory;
+import javax.xml.transform.sax.TemplatesHandler;
+import javax.xml.transform.sax.TransformerHandler;
+import javax.xml.validation.Schema;
+import javax.xml.validation.SchemaFactory;
+import javax.xml.validation.Validator;
+import javax.xml.validation.ValidatorHandler;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathExpression;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+import javax.xml.xpath.XPathFactoryConfigurationException;
+import javax.xml.xpath.XPathFunctionResolver;
+import javax.xml.xpath.XPathVariableResolver;
+
+import __redirected.__SchemaFactory;
+import __redirected.__XMLReaderFactory;
+import __redirected.__XPathFactory;
+import __redirected.__DatatypeFactory;
+import __redirected.__DocumentBuilderFactory;
+import __redirected.__JAXPRedirected;
+import __redirected.__SAXParserFactory;
+import __redirected.__TransformerFactory;
+import __redirected.__XMLEventFactory;
+import __redirected.__XMLInputFactory;
+import __redirected.__XMLOutputFactory;
+
+import org.jboss.modules.filter.PathFilter;
+import org.jboss.modules.filter.PathFilters;
+import org.jboss.modules.test.JAXPCaller;
+import org.jboss.modules.util.TestModuleLoader;
+import org.jboss.modules.util.TestResourceLoader;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.w3c.dom.DOMImplementation;
+import org.w3c.dom.Document;
+import org.w3c.dom.ls.LSResourceResolver;
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.DTDHandler;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXNotRecognizedException;
+import org.xml.sax.SAXNotSupportedException;
+import org.xml.sax.XMLFilter;
+import org.xml.sax.XMLReader;
+
+/**
+ * Tests JAXP, including all of the possible ways to trigger redirection
+ *
+ * @author Jason T. Greene
+ */
+ at SuppressWarnings("deprecation")
+public class JAXPModuleTest extends AbstractModuleTestCase {
+
+    private static final ModuleIdentifier FAKE_JAXP = ModuleIdentifier.fromString("fake-jaxp");
+
+    private TestModuleLoader moduleLoader;
+    private PathFilter jdkApiFilter;
+
+
+    @Before
+    public void setupModuleLoader() throws Exception {
+        jdkApiFilter = PathFilters.any(PathFilters.match("javax/**"),
+                       PathFilters.match("org/w3c/**"),
+                       PathFilters.match("org/xml/**"));
+        moduleLoader = new TestModuleLoader();
+
+        ModuleSpec.Builder moduleWithContentBuilder = ModuleSpec.build(ModuleIdentifier.fromString("test-jaxp"));
+        moduleWithContentBuilder.addResourceRoot(ResourceLoaderSpec.createResourceLoaderSpec(
+                TestResourceLoader.build()
+                .addClass(JAXPCaller.class)
+                .create()
+        ));
+        moduleWithContentBuilder.addDependency(DependencySpec.createSystemDependencySpec(JDKPaths.JDK));
+        moduleWithContentBuilder.addDependency(DependencySpec.createLocalDependencySpec());
+        moduleWithContentBuilder.setMainClass(JAXPCaller.class.getName());
+        moduleLoader.addModuleSpec(moduleWithContentBuilder.create());
+
+        moduleWithContentBuilder = ModuleSpec.build(FAKE_JAXP);
+        moduleWithContentBuilder.addResourceRoot(ResourceLoaderSpec.createResourceLoaderSpec(
+                TestResourceLoader.build()
+                        .addClass(FakeSAXParserFactory.class)
+                        .addClass(FakeSAXParser.class)
+                        .addClass(FakeDocumentBuilderFactory.class)
+                        .addClass(FakeDocumentBuilder.class)
+                        .addClass(FakeTransformerFactory.class)
+                        .addClass(FakeTransformer.class)
+                        .addClass(FakeTransformerHandler.class)
+                        .addClass(FakeXMLEventFactory.class)
+                        .addClass(FakeDTD.class)
+                        .addClass(FakeXMLInputFactory.class)
+                        .addClass(FakeXMLOutputFactory.class)
+                        .addClass(FakeDatatypeFactory.class)
+                        .addClass(FakeDuration.class)
+                        .addClass(FakeXPathFactory.class)
+                        .addClass(FakeXPath.class)
+                        .addClass(FakeSchemaFactory.class)
+                        .addClass(FakeSchema.class)
+                        .addClass(FakeXMLReader.class)
+                        .addResources(getResource("test/modulecontentloader/jaxp"))
+                        .create()
+        ));
+        moduleWithContentBuilder.addDependency(DependencySpec.createSystemDependencySpec(jdkApiFilter, PathFilters.rejectAll(), JDKPaths.JDK));
+        moduleWithContentBuilder.addDependency(DependencySpec.createLocalDependencySpec());
+        moduleLoader.addModuleSpec(moduleWithContentBuilder.create());
+
+        moduleWithContentBuilder = ModuleSpec.build(ModuleIdentifier.fromString("test-jaxp-import"));
+        moduleWithContentBuilder.addResourceRoot(ResourceLoaderSpec.createResourceLoaderSpec(
+                TestResourceLoader.build()
+                .addClass(JAXPCaller.class)
+                .create()
+        ));
+        moduleWithContentBuilder.addDependency(DependencySpec.createSystemDependencySpec(jdkApiFilter, PathFilters.rejectAll(), JDKPaths.JDK));
+        moduleWithContentBuilder.addDependency(DependencySpec.createModuleDependencySpec(PathFilters.acceptAll(), PathFilters.rejectAll(), moduleLoader, FAKE_JAXP, false));
+        moduleWithContentBuilder.addDependency(DependencySpec.createLocalDependencySpec());
+        moduleLoader.addModuleSpec(moduleWithContentBuilder.create());
+    }
+
+    @SuppressWarnings("unchecked")
+    private static <T> T invokeMethod(Object obj, String method) {
+        try {
+            return (T) obj.getClass().getMethod(method).invoke(obj);
+        } catch (Exception e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    @Test
+    public void testJVMDefault() throws Exception {
+        ModuleClassLoader cl = moduleLoader.loadModule(ModuleIdentifier.fromString("test-jaxp")).getClassLoader();
+        ClassLoader old = Thread.currentThread().getContextClassLoader();
+        try {
+            Thread.currentThread().setContextClassLoader(cl);
+            Class<?> clazz = cl.loadClass("org.jboss.modules.test.JAXPCaller");
+            checkDom(clazz, false);
+            checkSax(clazz, false);
+            checkTransformer(clazz, false);
+            checkSAXTransformer(clazz, false);
+            checkXPath(clazz, false);
+            checkXmlEvent(clazz, false);
+            checkXmlInput(clazz, false);
+            checkXmlOutput(clazz, false);
+            checkDatatype(clazz, false);
+            checkSchema(clazz, false);
+            checkXMLReader(clazz, false);
+        } finally {
+            Thread.currentThread().setContextClassLoader(old);
+        }
+    }
+
+    @Test
+    public void testReplaceDefault() throws Exception {
+        __JAXPRedirected.changeAll(FAKE_JAXP, moduleLoader);
+
+        ModuleClassLoader cl = moduleLoader.loadModule(ModuleIdentifier.fromString("test-jaxp")).getClassLoader();
+        Class<?> clazz = cl.loadClass("org.jboss.modules.test.JAXPCaller");
+        ClassLoader old = Thread.currentThread().getContextClassLoader();
+        try {
+            Thread.currentThread().setContextClassLoader(cl);
+            checkDom(clazz, true);
+            checkSax(clazz, true);
+            checkTransformer(clazz, true);
+            checkSAXTransformer(clazz, true);
+            checkXPath(clazz, true);
+            checkXmlEvent(clazz, true);
+            checkXmlInput(clazz, true);
+            checkXmlOutput(clazz, true);
+            checkDatatype(clazz, true);
+            checkSchema(clazz, true);
+            checkXMLReader(clazz, true);
+        } finally {
+            Thread.currentThread().setContextClassLoader(old);
+            __JAXPRedirected.restorePlatformFactory();
+        }
+    }
+
+    @Test
+    public void testImport() throws Exception {
+        ModuleClassLoader cl = moduleLoader.loadModule(ModuleIdentifier.fromString("test-jaxp-import")).getClassLoader();
+        Class<?> clazz = cl.loadClass("org.jboss.modules.test.JAXPCaller");
+        ClassLoader old = Thread.currentThread().getContextClassLoader();
+        try {
+            Thread.currentThread().setContextClassLoader(cl);
+            checkDom(clazz, true);
+            checkSax(clazz, true);
+            checkTransformer(clazz, true);
+            checkSAXTransformer(clazz, true);
+            checkXPath(clazz, true);
+            checkXmlEvent(clazz, true);
+            checkXmlInput(clazz, true);
+            checkXmlOutput(clazz, true);
+            checkDatatype(clazz, true);
+            checkSchema(clazz, true);
+            checkXMLReader(clazz, true);
+        } finally {
+            Thread.currentThread().setContextClassLoader(old);
+        }
+    }
+
+    /*
+     * This test is slightly dangerous. If it causes problems, just add @Ignore
+     * and/or let me know.
+     *   -Jason
+     */
+    @Test
+    public void testMain() throws Throwable {
+        java.lang.reflect.Field field = DefaultBootModuleLoaderHolder.class.getDeclaredField("INSTANCE");
+        java.lang.reflect.Field modifiersField = java.lang.reflect.Field.class.getDeclaredField("modifiers");
+        modifiersField.setAccessible(true);
+        modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
+        field.setAccessible(true);
+        ModuleLoader oldMl = (ModuleLoader) field.get(null);
+        field.set(null, moduleLoader);
+
+        Main.main(new String[] {"-jaxpmodule", "fake-jaxp", "test-jaxp"});
+        ModuleClassLoader cl = moduleLoader.loadModule(ModuleIdentifier.fromString("test-jaxp")).getClassLoader();
+        Class<?> clazz = cl.loadClass("org.jboss.modules.test.JAXPCaller");
+        ClassLoader old = Thread.currentThread().getContextClassLoader();
+        try {
+            Thread.currentThread().setContextClassLoader(cl);
+            checkDom(clazz, true);
+            checkSax(clazz, true);
+            checkTransformer(clazz, true);
+            checkSAXTransformer(clazz, true);
+            checkXmlEvent(clazz, true);
+            checkXPath(clazz, true);
+            checkXmlInput(clazz, true);
+            checkXmlOutput(clazz, true);
+            checkDatatype(clazz, true);
+            checkSchema(clazz, true);
+            checkXMLReader(clazz, true);
+        } finally {
+            field.set(null, oldMl);
+            Thread.currentThread().setContextClassLoader(old);
+            __JAXPRedirected.restorePlatformFactory();
+        }
+    }
+
+    public void checkDom(Class<?> clazz, boolean fake) throws Exception {
+        DocumentBuilder builder = invokeMethod(clazz.newInstance(), "documentBuilder");
+        DocumentBuilderFactory factory = invokeMethod(clazz.newInstance(), "documentFactory");
+
+        Assert.assertEquals(__DocumentBuilderFactory.class.getName(), factory.getClass().getName());
+
+        if (fake) {
+            Assert.assertEquals(FakeDocumentBuilder.class.getName(), builder.getClass().getName());
+        } else {
+            // Double check that it works
+            Document document = invokeMethod(clazz.newInstance(), "document");
+            document.createElement("test");
+            Assert.assertSame(DocumentBuilderFactory.newInstance().newDocumentBuilder().getClass(), builder.getClass());
+        }
+    }
+
+    public void checkSax(Class<?> clazz, boolean fake) throws Exception {
+        SAXParser parser = invokeMethod(clazz.newInstance(), "saxParser");
+        SAXParserFactory factory = invokeMethod(clazz.newInstance(), "saxParserFactory");
+
+        Assert.assertEquals(__SAXParserFactory.class.getName(), factory.getClass().getName());
+
+        if (fake) {
+            Assert.assertEquals(FakeSAXParser.class.getName(), parser.getClass().getName());
+        } else {
+            Assert.assertSame(SAXParserFactory.newInstance().newSAXParser().getClass(), parser.getClass());
+        }
+    }
+
+    public void checkTransformer(Class<?> clazz, boolean fake) throws Exception {
+        Transformer parser = invokeMethod(clazz.newInstance(), "transformer");
+        TransformerFactory factory = invokeMethod(clazz.newInstance(), "transformerFactory");
+
+        Assert.assertEquals(__TransformerFactory.class.getName(), factory.getClass().getName());
+
+        if (fake) {
+            Assert.assertEquals(FakeTransformer.class.getName(), parser.getClass().getName());
+        } else {
+            Assert.assertSame(TransformerFactory.newInstance().newTransformer().getClass(), parser.getClass());
+        }
+    }
+
+    public void checkXPath(Class<?> clazz, boolean fake) throws Exception {
+        XPath parser = invokeMethod(clazz.newInstance(), "xpath");
+        XPathFactory factory = invokeMethod(clazz.newInstance(), "xpathFactory");
+
+        Assert.assertEquals(__XPathFactory.class.getName(), factory.getClass().getName());
+
+        if (fake) {
+            Assert.assertEquals(FakeXPath.class.getName(), parser.getClass().getName());
+        } else {
+            Assert.assertSame(XPathFactory.newInstance().newXPath().getClass(), parser.getClass());
+        }
+    }
+
+    public void checkSchema(Class<?> clazz, boolean fake) throws Exception {
+        Schema parser = invokeMethod(clazz.newInstance(), "schema");
+        SchemaFactory factory = invokeMethod(clazz.newInstance(), "schemaFactory");
+
+        Assert.assertEquals(__SchemaFactory.class.getName(), factory.getClass().getName());
+
+        if (fake) {
+            Assert.assertEquals(FakeSchema.class.getName(), parser.getClass().getName());
+        } else {
+            Assert.assertSame(SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI).newSchema().getClass(), parser.getClass());
+        }
+    }
+
+    public void checkXMLReader(Class<?> clazz, boolean fake) throws Exception {
+        XMLReader parser = invokeMethod(clazz.newInstance(), "xmlReader");
+
+        Assert.assertEquals(__XMLReaderFactory.class.getName(), parser.getClass().getName());
+
+
+        Object test = null;
+        try {
+            test = parser.getProperty("test");
+        } catch (Exception ignore) {
+        }
+
+        if (fake) {
+            Assert.assertEquals("fake-fake-fake", test);
+        } else {
+            Assert.assertFalse("fake-fake-fake".equals(test));
+        }
+    }
+
+    public void checkSAXTransformer(Class<?> clazz, boolean fake) throws Exception {
+        TransformerHandler transformerHandler = invokeMethod(clazz.newInstance(), "transformerHandler");
+        TransformerFactory factory = invokeMethod(clazz.newInstance(), "transformerFactory");
+
+        Assert.assertEquals(__TransformerFactory.class.getName(), factory.getClass().getName());
+
+        if (fake) {
+            Assert.assertEquals(FakeTransformerHandler.class.getName(), transformerHandler.getClass().getName());
+        } else {
+            Assert.assertSame(((SAXTransformerFactory) TransformerFactory.newInstance()).newTransformerHandler().getClass(), transformerHandler.getClass());
+        }
+    }
+
+    public void checkXmlEvent(Class<?> clazz, boolean fake) throws Exception {
+        DTD dtd = invokeMethod(clazz.newInstance(), "eventDTD");
+        XMLEventFactory factory = invokeMethod(clazz.newInstance(), "eventFactory");
+
+        Assert.assertEquals(__XMLEventFactory.class.getName(), factory.getClass().getName());
+
+        if (fake) {
+            Assert.assertEquals(FakeDTD.class.getName(), dtd.getClass().getName());
+        } else {
+            Assert.assertSame(XMLEventFactory.newInstance().createDTD("blah").getClass(), dtd.getClass());
+        }
+    }
+
+    public void checkXmlInput(Class<?> clazz, boolean fake) throws Exception {
+        String property = invokeMethod(clazz.newInstance(), "inputProperty");
+        XMLInputFactory factory = invokeMethod(clazz.newInstance(), "inputFactory");
+
+        Assert.assertEquals(__XMLInputFactory.class.getName(), factory.getClass().getName());
+
+        if (fake) {
+            Assert.assertEquals(new FakeXMLInputFactory().getProperty("blah"), property);
+        } else {
+            Assert.assertFalse(new FakeXMLInputFactory().getProperty("blah").equals(property));
+        }
+    }
+
+    public void checkXmlOutput(Class<?> clazz, boolean fake) throws Exception {
+        String property = invokeMethod(clazz.newInstance(), "outputProperty");
+        XMLOutputFactory factory = invokeMethod(clazz.newInstance(), "outputFactory");
+
+        Assert.assertEquals(__XMLOutputFactory.class.getName(), factory.getClass().getName());
+
+        if (fake) {
+            Assert.assertEquals(new FakeXMLOutputFactory().getProperty("blah"), property);
+        } else {
+            Assert.assertFalse(new FakeXMLInputFactory().getProperty("blah").equals(property));
+        }
+    }
+
+    public void checkDatatype(Class<?> clazz, boolean fake) throws Exception {
+        Duration duration = invokeMethod(clazz.newInstance(), "duration");
+        DatatypeFactory factory = invokeMethod(clazz.newInstance(), "datatypeFactory");
+
+        Assert.assertEquals(__DatatypeFactory.class.getName(), factory.getClass().getName());
+
+        if (fake) {
+            Assert.assertEquals(new FakeDuration().getSign(), duration.getSign());
+        } else {
+            Assert.assertFalse(new FakeDuration().getSign() == duration.getSign());
+        }
+    }
+
+    public static class FakeSAXParserFactory extends SAXParserFactory {
+        public SAXParser newSAXParser() throws ParserConfigurationException, SAXException {
+            return new FakeSAXParser();
+        }
+
+        public void setFeature(String name, boolean value) throws ParserConfigurationException, SAXNotRecognizedException,
+                SAXNotSupportedException {
+        }
+
+        public boolean getFeature(String name) throws ParserConfigurationException, SAXNotRecognizedException,
+                SAXNotSupportedException {
+            return false;
+        }
+    }
+
+    public static class FakeSAXParser extends SAXParser {
+        @SuppressWarnings("deprecation")
+        public org.xml.sax.Parser getParser() throws SAXException {
+            return null;
+        }
+
+        public XMLReader getXMLReader() throws SAXException {
+            return null;
+        }
+
+        public boolean isNamespaceAware() {
+            return false;
+        }
+
+        public boolean isValidating() {
+            return false;
+        }
+
+        public void setProperty(String name, Object value) throws SAXNotRecognizedException, SAXNotSupportedException {
+        }
+
+        public Object getProperty(String name) throws SAXNotRecognizedException, SAXNotSupportedException {
+            return null;
+        }
+    }
+
+    public static class FakeDocumentBuilderFactory extends DocumentBuilderFactory {
+        public DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
+            return new FakeDocumentBuilder();
+        }
+
+        public void setAttribute(String name, Object value) throws IllegalArgumentException {
+        }
+
+        public Object getAttribute(String name) throws IllegalArgumentException {
+            return null;
+        }
+
+        public void setFeature(String name, boolean value) throws ParserConfigurationException {
+        }
+
+        public boolean getFeature(String name) throws ParserConfigurationException {
+            return false;
+        }
+    }
+
+    public static class FakeDocumentBuilder extends DocumentBuilder {
+        public Document parse(InputSource is) throws SAXException, IOException {
+            return null;
+        }
+        public boolean isNamespaceAware() {
+            return false;
+        }
+
+        public boolean isValidating() {
+            return false;
+        }
+
+        public void setEntityResolver(EntityResolver er) {
+        }
+
+        public void setErrorHandler(ErrorHandler eh) {
+        }
+
+        public Document newDocument() {
+            return null;
+        }
+
+        public DOMImplementation getDOMImplementation() {
+            return null;
+        }
+    }
+
+    public static class FakeTransformerFactory extends SAXTransformerFactory {
+        public Transformer newTransformer(Source source) throws TransformerConfigurationException {
+            return new FakeTransformer();
+        }
+
+        public Transformer newTransformer() throws TransformerConfigurationException {
+            return new FakeTransformer();
+        }
+
+        public Templates newTemplates(Source source) throws TransformerConfigurationException {
+            return null;
+        }
+
+        public Source getAssociatedStylesheet(Source source, String media, String title, String charset)
+                throws TransformerConfigurationException {
+            return null;
+        }
+
+        public void setURIResolver(URIResolver resolver) {
+        }
+
+        public URIResolver getURIResolver() {
+            return null;
+        }
+
+        public void setFeature(String name, boolean value) throws TransformerConfigurationException {
+        }
+
+        public boolean getFeature(String name) {
+            return false;
+        }
+
+        public void setAttribute(String name, Object value) {
+        }
+
+        public Object getAttribute(String name) {
+            return null;
+        }
+
+        public void setErrorListener(ErrorListener listener) {
+        }
+
+        public ErrorListener getErrorListener() {
+            return null;
+        }
+
+        public TransformerHandler newTransformerHandler(Source src) throws TransformerConfigurationException {
+            return null;
+        }
+
+        public TransformerHandler newTransformerHandler(Templates templates) throws TransformerConfigurationException {
+            return null;
+        }
+
+        public TransformerHandler newTransformerHandler() throws TransformerConfigurationException {
+            return new FakeTransformerHandler();
+        }
+
+        public TemplatesHandler newTemplatesHandler() throws TransformerConfigurationException {
+            return null;
+        }
+
+        public XMLFilter newXMLFilter(Source src) throws TransformerConfigurationException {
+            return null;
+        }
+
+        public XMLFilter newXMLFilter(Templates templates) throws TransformerConfigurationException {
+            return null;
+        }
+
+
+    }
+
+    private static class FakeTransformerHandler implements TransformerHandler {
+        public void setResult(Result result) throws IllegalArgumentException {
+        }
+
+        public void setSystemId(String systemID) {
+        }
+
+        public String getSystemId() {
+            return null;
+        }
+
+        public Transformer getTransformer() {
+            return null;
+        }
+
+        public void setDocumentLocator(Locator locator) {
+        }
+
+        public void startDocument() throws SAXException {
+        }
+
+        public void endDocument() throws SAXException {
+        }
+
+        public void startPrefixMapping(String prefix, String uri) throws SAXException {
+        }
+
+        public void endPrefixMapping(String prefix) throws SAXException {
+        }
+
+        public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
+        }
+
+        public void endElement(String uri, String localName, String qName) throws SAXException {
+        }
+
+        public void characters(char[] ch, int start, int length) throws SAXException {
+        }
+
+        public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
+        }
+
+        public void processingInstruction(String target, String data) throws SAXException {
+        }
+
+        public void skippedEntity(String name) throws SAXException {
+        }
+
+        public void notationDecl(String name, String publicId, String systemId) throws SAXException {
+        }
+
+        public void unparsedEntityDecl(String name, String publicId, String systemId, String notationName) throws SAXException {
+        }
+
+        public void startDTD(String name, String publicId, String systemId) throws SAXException {
+        }
+
+        public void endDTD() throws SAXException {
+        }
+
+        public void startEntity(String name) throws SAXException {
+        }
+
+        public void endEntity(String name) throws SAXException {
+        }
+
+        public void startCDATA() throws SAXException {
+        }
+
+        public void endCDATA() throws SAXException {
+        }
+
+        public void comment(char[] ch, int start, int length) throws SAXException {
+        }
+    }
+
+    public static class FakeTransformer extends Transformer {
+
+        public void transform(Source xmlSource, Result outputTarget) throws TransformerException {
+        }
+
+        public void setParameter(String name, Object value) {
+        }
+
+        public Object getParameter(String name) {
+            return null;
+        }
+
+        public void clearParameters() {
+        }
+
+        public void setURIResolver(URIResolver resolver) {
+        }
+
+        public URIResolver getURIResolver() {
+            return null;
+        }
+
+        public void setOutputProperties(Properties format) {
+        }
+
+        public Properties getOutputProperties() {
+            return null;
+        }
+
+        public void setOutputProperty(String name, String value) throws IllegalArgumentException {
+        }
+
+        public String getOutputProperty(String name) throws IllegalArgumentException {
+            return null;
+        }
+
+        public void setErrorListener(ErrorListener listener) throws IllegalArgumentException {
+        }
+
+        public ErrorListener getErrorListener() {
+            return null;
+        }
+
+    }
+
+    public static class FakeXMLEventFactory extends XMLEventFactory {
+
+        public void setLocation(Location location) {
+        }
+
+        public Attribute createAttribute(String prefix, String namespaceURI, String localName, String value) {
+            return null;
+        }
+
+        public Attribute createAttribute(String localName, String value) {
+            return null;
+        }
+
+        public Attribute createAttribute(QName name, String value) {
+            return null;
+        }
+
+        public Namespace createNamespace(String namespaceURI) {
+            return null;
+        }
+
+        public Namespace createNamespace(String prefix, String namespaceUri) {
+            return null;
+        }
+
+        public StartElement createStartElement(QName name, Iterator attributes, Iterator namespaces) {
+            return null;
+        }
+
+        public StartElement createStartElement(String prefix, String namespaceUri, String localName) {
+            return null;
+        }
+
+        public StartElement createStartElement(String prefix, String namespaceUri, String localName, Iterator attributes,
+                Iterator namespaces) {
+            return null;
+        }
+
+        public StartElement createStartElement(String prefix, String namespaceUri, String localName, Iterator attributes,
+                Iterator namespaces, NamespaceContext context) {
+            return null;
+        }
+
+        public EndElement createEndElement(QName name, Iterator namespaces) {
+            return null;
+        }
+
+        public EndElement createEndElement(String prefix, String namespaceUri, String localName) {
+            return null;
+        }
+
+        public EndElement createEndElement(String prefix, String namespaceUri, String localName, Iterator namespaces) {
+            return null;
+        }
+
+        public Characters createCharacters(String content) {
+            return null;
+        }
+
+        public Characters createCData(String content) {
+            return null;
+        }
+
+        public Characters createSpace(String content) {
+            return null;
+        }
+
+        public Characters createIgnorableSpace(String content) {
+            return null;
+        }
+
+        public StartDocument createStartDocument() {
+            return null;
+        }
+
+        public StartDocument createStartDocument(String encoding, String version, boolean standalone) {
+            return null;
+        }
+
+        public StartDocument createStartDocument(String encoding, String version) {
+            return null;
+        }
+
+        public StartDocument createStartDocument(String encoding) {
+            return null;
+        }
+
+        public EndDocument createEndDocument() {
+            return null;
+        }
+
+        public EntityReference createEntityReference(String name, EntityDeclaration declaration) {
+            return null;
+        }
+
+        public Comment createComment(String text) {
+            return null;
+        }
+
+        public ProcessingInstruction createProcessingInstruction(String target, String data) {
+            return null;
+        }
+
+        public DTD createDTD(String dtd) {
+            return new FakeDTD();
+        }
+
+    }
+
+    public static class FakeDTD implements DTD {
+
+        public int getEventType() {
+            return 0;
+        }
+
+        public Location getLocation() {
+            return null;
+        }
+
+        public boolean isStartElement() {
+            return false;
+        }
+
+        public boolean isAttribute() {
+            return false;
+        }
+
+        public boolean isNamespace() {
+            return false;
+        }
+
+        public boolean isEndElement() {
+            return false;
+        }
+
+        public boolean isEntityReference() {
+            return false;
+        }
+
+        public boolean isProcessingInstruction() {
+            return false;
+        }
+
+        public boolean isCharacters() {
+            return false;
+        }
+
+        public boolean isStartDocument() {
+            return false;
+        }
+
+        public boolean isEndDocument() {
+            return false;
+        }
+
+        public StartElement asStartElement() {
+            return null;
+        }
+
+        public EndElement asEndElement() {
+            return null;
+        }
+
+        public Characters asCharacters() {
+            return null;
+        }
+
+        public QName getSchemaType() {
+            return null;
+        }
+
+        public void writeAsEncodedUnicode(Writer writer) throws XMLStreamException {
+        }
+
+        public String getDocumentTypeDeclaration() {
+            return null;
+        }
+
+        public Object getProcessedDTD() {
+            return null;
+        }
+
+        public List getNotations() {
+            return null;
+        }
+
+        public List getEntities() {
+            return null;
+        }
+
+    }
+
+    public static class FakeXMLInputFactory extends XMLInputFactory {
+
+        public XMLStreamReader createXMLStreamReader(Reader reader) throws XMLStreamException {
+            return null;
+        }
+
+        public XMLStreamReader createXMLStreamReader(Source source) throws XMLStreamException {
+            return null;
+        }
+
+        public XMLStreamReader createXMLStreamReader(InputStream stream) throws XMLStreamException {
+            return null;
+        }
+
+        public XMLStreamReader createXMLStreamReader(InputStream stream, String encoding) throws XMLStreamException {
+            return null;
+        }
+
+        public XMLStreamReader createXMLStreamReader(String systemId, InputStream stream) throws XMLStreamException {
+            return null;
+        }
+
+        public XMLStreamReader createXMLStreamReader(String systemId, Reader reader) throws XMLStreamException {
+            return null;
+        }
+
+        public XMLEventReader createXMLEventReader(Reader reader) throws XMLStreamException {
+            return null;
+        }
+
+        public XMLEventReader createXMLEventReader(String systemId, Reader reader) throws XMLStreamException {
+            return null;
+        }
+
+        public XMLEventReader createXMLEventReader(XMLStreamReader reader) throws XMLStreamException {
+            return null;
+        }
+
+        public XMLEventReader createXMLEventReader(Source source) throws XMLStreamException {
+            return null;
+        }
+
+        public XMLEventReader createXMLEventReader(InputStream stream) throws XMLStreamException {
+            return null;
+        }
+
+        public XMLEventReader createXMLEventReader(InputStream stream, String encoding) throws XMLStreamException {
+            return null;
+        }
+
+        public XMLEventReader createXMLEventReader(String systemId, InputStream stream) throws XMLStreamException {
+            return null;
+        }
+
+        public XMLStreamReader createFilteredReader(XMLStreamReader reader, StreamFilter filter) throws XMLStreamException {
+            return null;
+        }
+
+        public XMLEventReader createFilteredReader(XMLEventReader reader, EventFilter filter) throws XMLStreamException {
+            return null;
+        }
+
+        public XMLResolver getXMLResolver() {
+            return null;
+        }
+
+        public void setXMLResolver(XMLResolver resolver) {
+        }
+
+        public XMLReporter getXMLReporter() {
+            return null;
+        }
+
+        public void setXMLReporter(XMLReporter reporter) {
+        }
+
+        public void setProperty(String name, Object value) throws IllegalArgumentException {
+        }
+
+        public Object getProperty(String name) throws IllegalArgumentException {
+            return "magic-fake-thing";
+        }
+
+        public boolean isPropertySupported(String name) {
+            return false;
+        }
+
+        public void setEventAllocator(XMLEventAllocator allocator) {
+        }
+
+        public XMLEventAllocator getEventAllocator() {
+            return null;
+        }
+
+    }
+
+    public static class FakeXMLOutputFactory extends XMLOutputFactory {
+
+        public XMLStreamWriter createXMLStreamWriter(Writer stream) throws XMLStreamException {
+            return null;
+        }
+
+        public XMLStreamWriter createXMLStreamWriter(OutputStream stream) throws XMLStreamException {
+            return null;
+        }
+
+        public XMLStreamWriter createXMLStreamWriter(OutputStream stream, String encoding) throws XMLStreamException {
+            return null;
+        }
+
+        public XMLStreamWriter createXMLStreamWriter(Result result) throws XMLStreamException {
+            return null;
+        }
+
+        public XMLEventWriter createXMLEventWriter(Result result) throws XMLStreamException {
+            return null;
+        }
+
+        public XMLEventWriter createXMLEventWriter(OutputStream stream) throws XMLStreamException {
+            return null;
+        }
+
+        public XMLEventWriter createXMLEventWriter(OutputStream stream, String encoding) throws XMLStreamException {
+            return null;
+        }
+
+        public XMLEventWriter createXMLEventWriter(Writer stream) throws XMLStreamException {
+            return null;
+        }
+
+        public void setProperty(String name, Object value) throws IllegalArgumentException {
+        }
+
+        public Object getProperty(String name) throws IllegalArgumentException {
+            return "magic-fake-thing";
+        }
+
+        public boolean isPropertySupported(String name) {
+            return false;
+        }
+
+    }
+
+    public static class FakeDatatypeFactory extends DatatypeFactory {
+        public Duration newDuration(String lexicalRepresentation) {
+            return null;
+        }
+
+        public Duration newDuration(long durationInMilliSeconds) {
+            return new FakeDuration();
+        }
+
+        public Duration newDuration(boolean isPositive, BigInteger years, BigInteger months, BigInteger days, BigInteger hours,
+                BigInteger minutes, BigDecimal seconds) {
+            return null;
+        }
+
+        public XMLGregorianCalendar newXMLGregorianCalendar() {
+            return null;
+        }
+
+        public XMLGregorianCalendar newXMLGregorianCalendar(String lexicalRepresentation) {
+            return null;
+        }
+
+        public XMLGregorianCalendar newXMLGregorianCalendar(GregorianCalendar cal) {
+            return null;
+        }
+
+        public XMLGregorianCalendar newXMLGregorianCalendar(BigInteger year, int month, int day, int hour, int minute,
+                int second, BigDecimal fractionalSecond, int timezone) {
+            return null;
+        }
+    }
+
+    public static class FakeDuration extends Duration {
+        public int getSign() {
+            return 123456789;
+        }
+
+        public Number getField(Field field) {
+            return null;
+        }
+
+        public boolean isSet(Field field) {
+            return false;
+        }
+
+        public Duration add(Duration rhs) {
+            return null;
+        }
+
+        public void addTo(Calendar calendar) {
+        }
+
+        public Duration multiply(BigDecimal factor) {
+            return null;
+        }
+
+        public Duration negate() {
+            return null;
+        }
+
+        public Duration normalizeWith(Calendar startTimeInstant) {
+            return null;
+        }
+
+        public int compare(Duration duration) {
+            return 0;
+        }
+
+        public int hashCode() {
+            return 0;
+        }
+    }
+
+    public static class FakeXPathFactory extends XPathFactory {
+
+        public boolean isObjectModelSupported(String objectModel) {
+            return XPathFactory.DEFAULT_OBJECT_MODEL_URI.equals(objectModel);
+        }
+
+        public void setFeature(String name, boolean value) throws XPathFactoryConfigurationException {
+        }
+
+        public boolean getFeature(String name) throws XPathFactoryConfigurationException {
+            return false;
+        }
+
+        public void setXPathVariableResolver(XPathVariableResolver resolver) {
+        }
+
+        public void setXPathFunctionResolver(XPathFunctionResolver resolver) {
+        }
+
+        public XPath newXPath() {
+            return new FakeXPath();
+        }
+    }
+
+    public static class FakeXPath implements XPath {
+
+        public void reset() {
+        }
+
+        public void setXPathVariableResolver(XPathVariableResolver resolver) {
+        }
+
+        public XPathVariableResolver getXPathVariableResolver() {
+            return null;
+        }
+
+        public void setXPathFunctionResolver(XPathFunctionResolver resolver) {
+        }
+
+        public XPathFunctionResolver getXPathFunctionResolver() {
+            return null;
+        }
+
+        public void setNamespaceContext(NamespaceContext nsContext) {
+        }
+
+        public NamespaceContext getNamespaceContext() {
+            return null;
+        }
+
+        public XPathExpression compile(String expression) throws XPathExpressionException {
+            return null;
+        }
+
+        public Object evaluate(String expression, Object item, QName returnType) throws XPathExpressionException {
+            return null;
+        }
+
+        public String evaluate(String expression, Object item) throws XPathExpressionException {
+            return null;
+        }
+
+        public Object evaluate(String expression, InputSource source, QName returnType) throws XPathExpressionException {
+            return null;
+        }
+
+        public String evaluate(String expression, InputSource source) throws XPathExpressionException {
+            return null;
+        }
+    }
+
+    public static class FakeSchemaFactory extends SchemaFactory {
+        public boolean isSchemaLanguageSupported(String schemaLanguage) {
+            return XMLConstants.W3C_XML_SCHEMA_NS_URI.equals(schemaLanguage);
+        }
+
+        public void setErrorHandler(ErrorHandler errorHandler) {
+        }
+
+        public ErrorHandler getErrorHandler() {
+            return null;
+        }
+
+        public void setResourceResolver(LSResourceResolver resourceResolver) {
+        }
+
+        public LSResourceResolver getResourceResolver() {
+            return null;
+        }
+
+        public Schema newSchema(Source[] schemas) throws SAXException {
+            return null;
+        }
+
+        public Schema newSchema() throws SAXException {
+            return new FakeSchema();
+        }
+    }
+
+    public static class FakeSchema extends Schema {
+        public Validator newValidator() {
+            return null;
+        }
+
+        public ValidatorHandler newValidatorHandler() {
+            return null;
+        }
+    }
+
+    public static class FakeXMLReader implements XMLReader {
+        public boolean getFeature(String name) throws SAXNotRecognizedException, SAXNotSupportedException {
+            return false;
+        }
+
+        public void setFeature(String name, boolean value) throws SAXNotRecognizedException, SAXNotSupportedException {
+        }
+
+        public Object getProperty(String name) throws SAXNotRecognizedException, SAXNotSupportedException {
+            return "fake-fake-fake";
+        }
+
+        public void setProperty(String name, Object value) throws SAXNotRecognizedException, SAXNotSupportedException {
+        }
+
+        public void setEntityResolver(EntityResolver resolver) {
+        }
+
+        public EntityResolver getEntityResolver() {
+            return null;
+        }
+
+        public void setDTDHandler(DTDHandler handler) {
+        }
+
+        public DTDHandler getDTDHandler() {
+            return null;
+        }
+
+        public void setContentHandler(ContentHandler handler) {
+        }
+
+        public ContentHandler getContentHandler() {
+            return null;
+        }
+
+        public void setErrorHandler(ErrorHandler handler) {
+        }
+
+        public ErrorHandler getErrorHandler() {
+            return null;
+        }
+
+        public void parse(InputSource input) throws IOException, SAXException {
+        }
+
+        public void parse(String systemId) throws IOException, SAXException {
+        }
+    }
+}
diff --git a/src/test/java/org/jboss/modules/JarResourceLoaderTest.java b/src/test/java/org/jboss/modules/JarResourceLoaderTest.java
new file mode 100644
index 0000000..03376ed
--- /dev/null
+++ b/src/test/java/org/jboss/modules/JarResourceLoaderTest.java
@@ -0,0 +1,103 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import org.jboss.modules.filter.PathFilter;
+import org.junit.Assert;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.JarOutputStream;
+
+/**
+ * Test the functionality of the JarResourceLoader.
+ *
+ * @author John Bailey
+ */
+public class JarResourceLoaderTest extends AbstractResourceLoaderTestCase {
+
+    private JarFile jarFile;
+
+    protected ResourceLoader createLoader(final PathFilter exportFilter) throws Exception {
+        File fileResourceRoot = getResource("test/fileresourceloader");
+        // Copy the classfile over
+        copyResource("org/jboss/modules/test/TestClass.class", "test/fileresourceloader", "org/jboss/modules/test");
+        
+        // Build a jar to match the fileresource loader
+        final File outputFile = new File(getResource("test"), "jarresourceloader/test.jar");
+        outputFile.getParentFile().mkdirs();
+        buildJar(fileResourceRoot, outputFile);
+        // Create the jar file and resource loader
+        jarFile = new JarFile(outputFile, true);
+        return new JarFileResourceLoader("test-root", jarFile);
+    }
+
+    @Override
+    protected void assertResource(Resource resource, String fileName) {
+        final JarEntry entry = jarFile.getJarEntry(fileName);
+        Assert.assertEquals(entry.getSize(), resource.getSize());
+    }
+
+    private void buildJar(final File source, final File targetFile) throws IOException {
+        final JarOutputStream target = new JarOutputStream(new FileOutputStream(targetFile));
+        final String sourceBase = source.getPath();
+        add(sourceBase, source, target);
+        target.close();
+    }
+
+    private void add(final String sourceBase, final File source, final JarOutputStream target) throws IOException {
+        BufferedInputStream in = null;
+        String entryName = source.getPath().replace(sourceBase, "").replace("\\", "/");
+        if(entryName.startsWith("/"))
+            entryName = entryName.substring(1);
+        try {
+            if (source.isDirectory()) {
+                if (!entryName.isEmpty()) {
+                    if (!entryName.endsWith("/"))
+                        entryName += "/";
+                    final JarEntry entry = new JarEntry(entryName);
+                    target.putNextEntry(entry);
+                    target.closeEntry();
+                }
+                for (File nestedFile : source.listFiles())
+                    add(sourceBase, nestedFile, target);
+                return;
+            }
+
+            final JarEntry entry = new JarEntry(entryName);
+            target.putNextEntry(entry);
+            in = new BufferedInputStream(new FileInputStream(source));
+
+            byte[] buffer = new byte[1024];
+            int count = 0;
+            while ((count = in.read(buffer)) != -1) {
+                target.write(buffer, 0, count);
+            }
+            target.closeEntry();
+        }
+        finally {
+            if (in != null) in.close();
+        }
+    }
+}
diff --git a/src/test/java/org/jboss/modules/LayeredModulePathTest.java b/src/test/java/org/jboss/modules/LayeredModulePathTest.java
new file mode 100644
index 0000000..d559e4f
--- /dev/null
+++ b/src/test/java/org/jboss/modules/LayeredModulePathTest.java
@@ -0,0 +1,621 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import junit.framework.Assert;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests of {@link LocalModuleLoader} when "layers" and "add-ons" are configured.
+ *
+ * @author Brian Stansberry (c) 2012 Red Hat Inc.
+ */
+public class LayeredModulePathTest extends AbstractModuleTestCase {
+
+    private static final String PATH = "test/layeredmodulepath/";
+
+    private static final ModuleIdentifier SHARED = ModuleIdentifier.create("test.shared");
+
+    private String originalModulePath;
+
+    private File reposRoot;
+    private File repoA;
+    private File repoB;
+
+    @Before
+    public void setUp() throws Exception {
+
+        originalModulePath = System.getProperty("module.path");
+
+        reposRoot = new File(getResource(PATH), "repos");
+        if (!reposRoot.mkdirs() && !reposRoot.isDirectory()) {
+            throw new IllegalStateException("Cannot create reposRoot");
+        }
+        repoA = new File(reposRoot, "root-a");
+        if (!repoA.mkdirs() && !repoA.isDirectory()) {
+            throw new IllegalStateException("Cannot create reposA");
+        }
+        repoB = new File(reposRoot, "root-b");
+        if (!repoB.mkdirs() && !repoB.isDirectory()) {
+            throw new IllegalStateException("Cannot create reposB");
+        }
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (reposRoot != null) {
+            cleanFile(reposRoot);
+        }
+
+        if (originalModulePath != null) {
+            System.setProperty("module.path", originalModulePath);
+        } else {
+            System.clearProperty("module.path");
+        }
+    }
+
+    private void cleanFile(File file) {
+        File[] children = file.listFiles();
+        if (children != null) {
+            for (File child : children) {
+                cleanFile(child);
+            }
+        }
+        if (!file.delete() && file.exists()) {
+            file.deleteOnExit();
+        }
+    }
+
+    @Test
+    public void testBaseLayer() throws Exception {
+        createRepo("root-a", false, false, Collections.singletonList("base"));
+
+        File[] standardPath = { repoA };
+        File[] modulePath = LayeredModulePathFactory.resolveLayeredModulePath(standardPath);
+        validateModulePath(modulePath, repoA, 0, -1, false, "base");
+
+        validateModuleLoading(standardPath, false, false, false, "base");
+    }
+
+    @Test
+    public void testSpecifiedBaseLayer() throws Exception {
+        // This setup puts "base" in layers.conf
+        createRepo("root-a", false, false, "base");
+
+        File[] standardPath = { repoA };
+        File[] modulePath = LayeredModulePathFactory.resolveLayeredModulePath(standardPath);
+        validateModulePath(modulePath, repoA, 0, -1, false, "base");
+
+        validateModuleLoading(standardPath, false, false, false, "base");
+    }
+
+    @Test
+    public void testSimpleOverlay() throws Exception {
+        createRepo("root-a", false, false, Collections.singletonList("base"), "top");
+
+        File[] standardPath = { repoA };
+        File[] modulePath = LayeredModulePathFactory.resolveLayeredModulePath(standardPath);
+        validateModulePath(modulePath, repoA, 0, -1, false, "top", "base");
+
+        validateModuleLoading(standardPath, false, false, false, "top", "base");
+    }
+
+    @Test
+    public void testSpecifiedBaseLayerWithSimpleOverlay() throws Exception {
+        // This setup puts "base" in layers.conf
+        createRepo("root-a", false, false, "top", "base");
+
+        File[] standardPath = { repoA };
+        File[] modulePath = LayeredModulePathFactory.resolveLayeredModulePath(standardPath);
+        validateModulePath(modulePath, repoA, 0, -1, false, "top", "base");
+
+        validateModuleLoading(standardPath, false, false, false, "top", "base");
+    }
+
+    @Test
+    public void testMultipleOverlays() throws Exception {
+        createRepo("root-a", false, false, Collections.singletonList("base"), "top", "mid");
+
+        File[] standardPath = { repoA };
+        File[] modulePath = LayeredModulePathFactory.resolveLayeredModulePath(standardPath);
+        validateModulePath(modulePath, repoA, 0, -1, false, "top", "mid", "base");
+
+        validateModuleLoading(standardPath, false, false, false, "top", "mid", "base");
+    }
+
+    @Test
+    public void testSpecifiedBaseLayerWithMultipleOverlays() throws Exception {
+        // This setup puts "base" in layers.conf
+        createRepo("root-a", false, false, "top", "mid", "base");
+
+        File[] standardPath = { repoA };
+        File[] modulePath = LayeredModulePathFactory.resolveLayeredModulePath(standardPath);
+        validateModulePath(modulePath, repoA, 0, -1, false, "top", "mid", "base");
+
+        validateModuleLoading(standardPath, false, false, false, "top", "mid", "base");
+    }
+
+    @Test
+    public void testBasePlusAddOns() throws Exception {
+        createRepo("root-a", true, false, Collections.singletonList("base"));
+
+        File[] standardPath = { repoA };
+        File[] modulePath = LayeredModulePathFactory.resolveLayeredModulePath(standardPath);
+        validateModulePath(modulePath, repoA, 0, -1, true, "base");
+
+        validateModuleLoading(standardPath, true, false, false, "base");
+    }
+
+    @Test
+    public void testSpecifiedBasePlusAddOns() throws Exception {
+        // This setup puts "base" in layers.conf
+        createRepo("root-a", true, false, "base");
+
+        File[] standardPath = { repoA };
+        File[] modulePath = LayeredModulePathFactory.resolveLayeredModulePath(standardPath);
+        validateModulePath(modulePath, repoA, 0, -1, true, "base");
+
+        validateModuleLoading(standardPath, true, false, false, "base");
+    }
+
+    @Test
+    public void testLayersAndAddOns() throws Exception {
+        createRepo("root-a", true, false, Collections.singletonList("base"), "top", "mid");
+
+        File[] standardPath = { repoA };
+        File[] modulePath = LayeredModulePathFactory.resolveLayeredModulePath(standardPath);
+        validateModulePath(modulePath, repoA, 0, -1, true, "top", "mid", "base");
+
+        validateModuleLoading(standardPath, true, false, false, "top", "mid", "base");
+    }
+
+    @Test
+    public void testSpecifiedBaseLayersAndAddOns() throws Exception {
+        // This setup puts "base" in layers.conf
+        createRepo("root-a", true, false, "top", "mid", "base");
+
+        File[] standardPath = { repoA };
+        File[] modulePath = LayeredModulePathFactory.resolveLayeredModulePath(standardPath);
+        validateModulePath(modulePath, repoA, 0, -1, true, "top", "mid", "base");
+
+        validateModuleLoading(standardPath, true, false, false, "top", "mid", "base");
+    }
+
+    @Test
+    public void testBaseLayerAndUser() throws Exception {
+        createRepo("root-a", false, false, Collections.singletonList("base"));
+
+        File[] standardPath = { repoA };
+        File[] modulePath = LayeredModulePathFactory.resolveLayeredModulePath(standardPath);
+        validateModulePath(modulePath, repoA, 0, -1, false, "base");
+
+        validateModuleLoading(standardPath, false, false, false, "base");
+    }
+
+    @Test
+    public void testSpecifiedBaseLayerAndUser() throws Exception {
+        // This setup puts "base" in layers.conf
+        createRepo("root-a", false, true, "base");
+
+        File[] standardPath = { repoA };
+        File[] modulePath = LayeredModulePathFactory.resolveLayeredModulePath(standardPath);
+        validateModulePath(modulePath, repoA, 0, -1, false, "base");
+
+        validateModuleLoading(standardPath, false, true, true, "base");
+    }
+
+    @Test
+    public void testSingleRootComplete() throws Exception {
+        createRepo("root-a", true, false, Collections.singletonList("base"), "top", "mid");
+
+        File[] standardPath = { repoA };
+        File[] modulePath = LayeredModulePathFactory.resolveLayeredModulePath(standardPath);
+        validateModulePath(modulePath, repoA, 0, -1, true, "top", "mid", "base");
+
+        validateModuleLoading(standardPath, true, false, false, "top", "mid", "base");
+    }
+
+    @Test
+    public void testSpecifiedBaseSingleRootComplete() throws Exception {
+        // This setup puts "base" in layers.conf
+        createRepo("root-a", true, true, "top", "mid", "base");
+
+        File[] standardPath = { repoA };
+        File[] modulePath = LayeredModulePathFactory.resolveLayeredModulePath(standardPath);
+        validateModulePath(modulePath, repoA, 0, -1, true, "top", "mid", "base");
+
+        validateModuleLoading(standardPath, true, true, true, "top", "mid", "base");
+    }
+
+    @Test
+    public void testSecondRepoHigherPrecedence() throws Exception {
+        createRepo("root-a", false, false, Collections.singletonList("base"));
+        createRepo("root-b", false, true);
+
+        File[] standardPath = { repoB, repoA };
+        File[] modulePath = LayeredModulePathFactory.resolveLayeredModulePath(standardPath);
+        validateModulePath(modulePath, repoA, 1, 0, false, "base");
+
+        validateModuleLoading(standardPath, false, true, true, "base");
+    }
+
+    @Test
+    public void testSecondRepoLowerPrecedence() throws Exception {
+        createRepo("root-a", false, false, Collections.singletonList("base"));
+        createRepo("root-b", false, true);
+
+        File[] standardPath = { repoA, repoB };
+        File[] modulePath = LayeredModulePathFactory.resolveLayeredModulePath(standardPath);
+        validateModulePath(modulePath, repoA, 0, 2, false, "base");
+
+        validateModuleLoading(standardPath, false, true, false, "base");
+    }
+
+    @Test
+    public void testExtraneousOverlay() throws Exception {
+        createRepo("root-a", false, false, Arrays.asList("base", "mid"), "top");
+
+        File[] standardPath = { repoA };
+        File[] modulePath = LayeredModulePathFactory.resolveLayeredModulePath(standardPath);
+        validateModulePath(modulePath, repoA, 0, -1, false, "top", "base");
+
+        validateModuleLoading(standardPath, false, false, false, "top", "base");
+    }
+
+    @Test
+    public void testSpecifiedBaseLayerWithExtraneousOverlay() throws Exception {
+        // This setup puts "base" in layers.conf
+        createRepo("root-a", false, false, Arrays.asList("mid"), "top", "base");
+
+        File[] standardPath = { repoA };
+        File[] modulePath = LayeredModulePathFactory.resolveLayeredModulePath(standardPath);
+        validateModulePath(modulePath, repoA, 0, -1, false, "top", "base");
+
+        validateModuleLoading(standardPath, false, false, false, "top", "base");
+    }
+
+    /** Tests that setting up add-ons has no effect without the layers structure */
+    @Test
+    public void testLayersRequiredForAddOns() throws Exception {
+        createRepo("root-a", true, false);
+
+        File[] standardPath = { repoA };
+        File[] modulePath = LayeredModulePathFactory.resolveLayeredModulePath(standardPath);
+        validateModulePath(modulePath, repoA, 0, -1, false);
+
+        validateModuleLoading(standardPath, false, false, false);
+
+        // Now add the layers/base dir
+        new File(repoA, "system/layers/base").mkdirs();
+
+        modulePath = LayeredModulePathFactory.resolveLayeredModulePath(standardPath);
+        validateModulePath(modulePath, repoA, 0, -1, true, "base");
+
+        validateModuleLoading(standardPath, true, false, false);
+    }
+
+    @Test
+    public void testRejectConfWithNoStructure() throws Exception {
+        createRepo("root-a", false, false);
+        writeLayersConf("root-a", "top");
+
+        File[] standardPath = { repoA };
+        try {
+            LayeredModulePathFactory.resolveLayeredModulePath(standardPath);
+            Assert.fail("layers.conf with no layers should fail");
+        } catch (Exception good) {
+            // good
+        }
+    }
+
+    @Test
+    public void testRejectConfWithMissingLayer() throws Exception {
+        createRepo("root-a", false, false, Arrays.asList("top", "base"));
+        writeLayersConf("root-a", "top", "mid");
+
+        File[] standardPath = { repoA };
+        try {
+            LayeredModulePathFactory.resolveLayeredModulePath(standardPath);
+            Assert.fail("layers.conf with no layers should fail");
+        } catch (Exception good) {
+            // good
+        }
+    }
+
+    @Test
+    public void testEmptyLayersConf() throws Exception {
+        createRepo("root-a", false, false, Collections.singletonList("base"));
+        writeLayersConf("root-a");
+
+        File[] standardPath = { repoA };
+        File[] modulePath = LayeredModulePathFactory.resolveLayeredModulePath(standardPath);
+        validateModulePath(modulePath, repoA, 0, -1, false, "base");
+
+        validateModuleLoading(standardPath, false, false, false, "base");
+    }
+
+    @Test
+    public void testLayersOverlayModulePath() throws Exception {
+        createRepo("root-a", false, false, Arrays.asList("top", "base"));
+        writeLayersConf("root-a", "top", "base");
+
+        createOverlays(repoA, "top", false, "top1", "top2");
+        createOverlays(repoA, "base", false, "base1", "base2");
+
+        File[] standardPath = { repoA };
+        File[] modulePath = LayeredModulePathFactory.resolveLayeredModulePath(standardPath);
+
+        Assert.assertEquals(7, modulePath.length);
+        Assert.assertEquals(repoA, modulePath[0]);
+        Assert.assertEquals(new File(repoA, "system/layers/top/.overlays/top1"), modulePath[1]);
+        Assert.assertEquals(new File(repoA, "system/layers/top/.overlays/top2"), modulePath[2]);
+        Assert.assertEquals(new File(repoA, "system/layers/top"), modulePath[3]);
+        Assert.assertEquals(new File(repoA, "system/layers/base/.overlays/base1"), modulePath[4]);
+        Assert.assertEquals(new File(repoA, "system/layers/base/.overlays/base2"), modulePath[5]);
+        Assert.assertEquals(new File(repoA, "system/layers/base"), modulePath[6]);
+
+    }
+
+    @Test
+    public void testAddOnsOverlayModulePath() throws Exception {
+        createRepo("root-a", true, false, Arrays.asList("base"));
+        writeLayersConf("root-a", "base");
+
+        createOverlays(repoA, "a", true, "a");
+        createOverlays(repoA, "b", true, "b");
+
+        File[] standardPath = { repoA };
+        File[] modulePath = LayeredModulePathFactory.resolveLayeredModulePath(standardPath);
+
+        Assert.assertEquals(6, modulePath.length);
+        Assert.assertEquals(repoA, modulePath[0]);
+        Assert.assertEquals(new File(repoA, "system/layers/base"), modulePath[1]);
+        // The order of the add-ons is non deterministic
+        Assert.assertEquals(".overlays", modulePath[2].getParentFile().getName());
+        final String firstOverlay = modulePath[2].getName();
+        Assert.assertEquals(new File(repoA, "system/add-ons/" + firstOverlay), modulePath[3]);
+        Assert.assertEquals(".overlays", modulePath[4].getParentFile().getName());
+        final String secondOverlays = modulePath[4].getName();
+        Assert.assertEquals(new File(repoA, "system/add-ons/" + secondOverlays), modulePath[5]);
+
+    }
+
+    private void writeLayersConf(String rootName, String... layers) throws IOException {
+        if (layers != null && layers.length > 0) {
+
+            StringBuilder sb = new StringBuilder("layers=");
+            for (int i = 0; i < layers.length; i++) {
+                if (i > 0) {
+                    sb.append(',');
+                }
+                sb.append(layers[i]);
+            }
+
+            File repo = "root-a".equals(rootName) ? repoA : repoB;
+            File layersConf = new File(repo, "layers.conf");
+            layersConf.createNewFile();
+            FileWriter fw = new FileWriter(layersConf);
+            try {
+                PrintWriter pw = new PrintWriter(fw);
+                pw.println(sb.toString());
+                pw.close();
+            } finally {
+                try {
+                    fw.close();
+                } catch (Exception e) {
+                    // meh
+                }
+            }
+        }
+    }
+
+    private void createRepo(String rootName, boolean includeAddons, boolean includeUser, String... layers) throws Exception {
+        List<String> empty = Collections.emptyList();
+        createRepo(rootName, includeAddons, includeUser, empty, layers);
+    }
+
+    private void createRepo(String rootName, boolean includeAddons, boolean includeUser, List<String> extraLayers, String... layers) throws Exception {
+        if (layers != null && layers.length > 0) {
+            writeLayersConf(rootName, layers);
+            for (String layer : layers) {
+                createLayer(rootName, layer);
+            }
+        }
+        if (extraLayers != null) {
+            for (String extraLayer : extraLayers) {
+                createLayer(rootName, extraLayer);
+            }
+        }
+
+        if (includeAddons) {
+            createAddOn(rootName, "a");
+            createAddOn(rootName, "b");
+        }
+
+        if (includeUser) {
+            createUserModules(rootName);
+        }
+    }
+
+    private void createLayer(String rootName, String layerName) throws Exception {
+        createModules("layers/" + layerName, rootName + "/system/layers/" + layerName, layerName);
+    }
+
+    private void createAddOn(String rootName, String addOnName) throws Exception {
+        createModules("add-ons/" + addOnName, rootName + "/system/add-ons/" + addOnName, addOnName);
+    }
+
+    private void createUserModules(String rootName) throws Exception {
+        createModules("user", rootName, "user");
+    }
+
+    private void createModules(String sourcePath, String relativeRepoPath, String uniqueName) throws Exception {
+        copyResource(PATH + sourcePath + "/shared/module.xml", PATH, "repos/" + relativeRepoPath + "/test/shared/main");
+        copyResource(PATH + sourcePath + "/unique/module.xml", PATH, "repos/" + relativeRepoPath + "/test/" + uniqueName + "/main");
+    }
+
+    private void validateModulePath(File[] modulePath, File repoRoot, int expectedStartPos,
+                                    int expectedOtherRootPos, boolean expectAddons, String... layers) {
+        int expectedLength = 1 + layers.length + (expectAddons ? 2 : 0);
+
+        // Validate positional parameters -- check for bad test writers ;)
+        if (expectedOtherRootPos < 0) {
+            Assert.assertEquals(0, expectedStartPos); //
+        } else if (expectedStartPos == 0) {
+            Assert.assertEquals(expectedLength, expectedOtherRootPos);
+        }
+
+        if (expectedOtherRootPos < 1) {
+            Assert.assertEquals("Correct module path length", expectedStartPos + expectedLength, modulePath.length);
+        } else {
+            Assert.assertTrue("Correct module path length", modulePath.length > expectedStartPos + expectedLength);
+        }
+
+        Assert.assertEquals(repoRoot, modulePath[expectedStartPos]);
+        for (int i = 0; i < layers.length; i++) {
+            File layer = new File(repoRoot, "system/layers/" + layers[i]);
+            Assert.assertEquals(layer, modulePath[expectedStartPos + i + 1]);
+        }
+        if (expectAddons) {
+            File addOnBase = new File(repoRoot, "system/add-ons");
+            Set<String> valid = new HashSet<String>(Arrays.asList("a", "b"));
+            for (int i = 0; i < 2; i++) {
+                File addOn = modulePath[expectedStartPos + layers.length + i + 1];
+                Assert.assertEquals(addOnBase, addOn.getParentFile());
+                String addOnName = addOn.getName();
+                Assert.assertTrue(addOnName, valid.remove(addOnName));
+            }
+
+        }
+
+        if (expectedOtherRootPos == 0) {
+            for (int i = 0; i < expectedStartPos; i++) {
+                validateNotChild(modulePath[i], repoRoot);
+            }
+        } else if (expectedOtherRootPos > 0) {
+            for (int i = expectedOtherRootPos; i < modulePath.length; i++) {
+                validateNotChild(modulePath[i], repoRoot);
+            }
+        }
+
+    }
+
+    private void validateNotChild(File file, File repoRoot) {
+        File stop = repoRoot.getParentFile();
+        File testee = file;
+        while (testee != null && !testee.equals(stop)) {
+            Assert.assertFalse(testee.equals(repoRoot));
+            testee = testee.getParentFile();
+        }
+    }
+
+    private void validateModuleLoading(File[] standardPath, boolean expectAddOns, boolean expectUser,
+                                       boolean expectUserPrecedence, String... layers) throws ModuleLoadException {
+        // This is nasty, but the alternative is exposing the layers config stuff in the LocalModuleLoader API
+        setUpModulePathProperty(standardPath);
+        ModuleLoader moduleLoader = new LocalModuleLoader();
+
+        // Validate the expected path produced the shared module
+        if (expectUser || layers.length > 0 || expectAddOns) {
+            Module shared = moduleLoader.loadModule(SHARED);
+            String sharedProp = shared.getProperty("test.prop");
+            if (expectUserPrecedence) {
+                Assert.assertEquals("user", sharedProp);
+            } else if (layers.length > 0) {
+                Assert.assertEquals(layers[0], sharedProp);
+            } else if (expectAddOns) {
+                Assert.assertTrue("a".equals(sharedProp) || "b".equals(sharedProp));
+            }
+        }
+
+        // Validate the expected unique modules are present
+        Set<String> layersSet = new HashSet<String>(Arrays.asList(layers));
+        loadModule(moduleLoader, "user", expectUser);
+        loadModule(moduleLoader, "top", layersSet.contains("top"));
+        loadModule(moduleLoader, "mid", layersSet.contains("mid"));
+        loadModule(moduleLoader, "base", layersSet.contains("base"));
+        loadModule(moduleLoader, "a", expectAddOns);
+        loadModule(moduleLoader, "b", expectAddOns);
+    }
+
+    private void setUpModulePathProperty(File[] standardPath) {
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < standardPath.length; i++) {
+            if (i > 0) {
+                sb.append(File.pathSeparatorChar);
+            }
+            sb.append(standardPath[i].getAbsolutePath());
+        }
+        System.setProperty("module.path", sb.toString());
+    }
+
+    private void loadModule(ModuleLoader moduleLoader, String moduleName, boolean expectAvailable) {
+        ModuleIdentifier id = ModuleIdentifier.create("test." + moduleName);
+        try {
+            Module module = moduleLoader.loadModule(id);
+            if (!expectAvailable) {
+                Assert.fail("test." + moduleName + " should not be loadable");
+            }
+            String prop = module.getProperty("test.prop");
+            Assert.assertEquals(moduleName, prop);
+        } catch (ModuleLoadException e) {
+            if (expectAvailable) {
+                Assert.fail(e.getMessage());
+            }
+        }
+    }
+
+    private void createOverlays(final File root, final String name, boolean addOn, String... overlays) throws IOException {
+        final File system = new File(root, "system");
+        final File layers = addOn ? new File(system, "add-ons") : new File(system, "layers");
+        final File repo = new File(layers, name);
+        final File overlaysRoot = new File(repo, ".overlays");
+        overlaysRoot.mkdir();
+        final File overlaysConfig = new File(overlaysRoot, ".overlays");
+        final OutputStream os = new FileOutputStream(overlaysConfig);
+        try {
+            for (final String overlay : overlays) {
+                os.write(overlay.getBytes());
+                os.write('\n');
+            }
+        } finally {
+            if (os != null) try {
+                os.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+}
diff --git a/src/test/java/org/jboss/modules/LocalModuleLoaderTest.java b/src/test/java/org/jboss/modules/LocalModuleLoaderTest.java
new file mode 100644
index 0000000..250856d
--- /dev/null
+++ b/src/test/java/org/jboss/modules/LocalModuleLoaderTest.java
@@ -0,0 +1,77 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+/**
+ * Test to verify the functionality of the LocalModuleLoader.
+ *
+ * @author John Bailey
+ */
+public class LocalModuleLoaderTest extends AbstractModuleTestCase {
+    private ModuleLoader moduleLoader;
+
+    @Before
+    public void setupModuleLoader() throws Exception {
+        final File repoRoot = getResource("test/repo");
+        moduleLoader = new LocalModuleLoader(new File[] {repoRoot});
+    }
+
+    @Test
+    public void testBasicLoad() throws Exception {
+        Module module = moduleLoader.loadModule(MODULE_ID);
+        assertNotNull(module);
+    }
+
+    @Test
+    public void testCurrent() throws Exception {
+        ModuleLoader loader = Module.getCallerModuleLoader();
+        System.out.println(loader);
+    }
+
+
+    @Test
+    public void testLoadWithDeps() throws Exception {
+        Module module = moduleLoader.loadModule(ModuleIdentifier.fromString("test.with-deps"));
+        assertNotNull(module);
+    }
+
+    @Test
+    public void testLoadWithBadDeps() throws Exception {
+        try {
+            moduleLoader.loadModule(ModuleIdentifier.fromString("test.bad-deps.1_0"));
+            fail("Should have thrown a ModuleNotFoundException");
+        } catch(ModuleNotFoundException expected) {}
+    }
+
+    @Test
+    public void testLoadWithCircularDeps() throws Exception {
+        assertNotNull(moduleLoader.loadModule(ModuleIdentifier.fromString("test.circular-deps-A")));
+        assertNotNull(moduleLoader.loadModule(ModuleIdentifier.fromString("test.circular-deps-B")));
+        assertNotNull(moduleLoader.loadModule(ModuleIdentifier.fromString("test.circular-deps-C")));
+        assertNotNull(moduleLoader.loadModule(ModuleIdentifier.fromString("test.circular-deps-D")));
+    }
+}
diff --git a/src/test/java/org/jboss/modules/MavenResourceTest.java b/src/test/java/org/jboss/modules/MavenResourceTest.java
new file mode 100755
index 0000000..03f60a0
--- /dev/null
+++ b/src/test/java/org/jboss/modules/MavenResourceTest.java
@@ -0,0 +1,87 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.io.File;
+import java.net.URL;
+
+import org.jboss.modules.util.Util;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+/**
+ * @author <a href="mailto:bill at burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class MavenResourceTest {
+
+    protected static final ModuleIdentifier MODULE_ID = ModuleIdentifier.fromString("test.maven");
+    protected static final ModuleIdentifier MODULE_ID2 = ModuleIdentifier.fromString("test.maven:non-main");
+
+    @Rule
+    public TemporaryFolder tmpdir = new TemporaryFolder();
+
+    private ModuleLoader moduleLoader;
+
+    @Before
+    public void setupRepo() throws Exception {
+        final File repoRoot = Util.getResourceFile(getClass(), "test/repo");
+        moduleLoader = new LocalModuleLoader(new File[]{repoRoot});
+    }
+
+    @Test
+    public void testWithPassedRepository() throws Exception {
+        System.setProperty("maven.repo.local", tmpdir.newFolder("repository").getAbsolutePath());
+        System.setProperty("remote.maven.repo", "http://repository.jboss.org/nexus/content/groups/public/");
+        try {
+            Module module = moduleLoader.loadModule(MODULE_ID);
+            URL url = module.getResource("org/jboss/resteasy/plugins/providers/jackson/ResteasyJacksonProvider.class");
+            System.out.println(url);
+            Assert.assertNotNull(url);
+        } finally {
+            System.clearProperty("maven.repo.local");
+            System.clearProperty("remote.repository");
+        }
+    }
+
+    @Test
+    public void testDefaultRepositories() throws Exception {
+        Module module = moduleLoader.loadModule(MODULE_ID2);
+        URL url = module.getResource("org/jboss/resteasy/plugins/providers/jackson/ResteasyJacksonProvider.class");
+        System.out.println(url);
+        Assert.assertNotNull(url);
+
+    }
+
+    /**
+     * we test if it uses repostiory user has configured in user.home/.m2/settings.xml or M2_HOME/conf/settings.xml
+     *
+     * @throws Exception
+     */
+    @Test
+    @Ignore("Test artifact must exists in local repo but nowhere else, mostly meant for manual testing")
+    public void testCustomRepository() throws Exception {
+        MavenArtifactUtil.resolveJarArtifact("org.wildfly:wildfly-clustering-infinispan:9.0.0.Alpha1-SNAPSHOT");
+
+    }
+}
diff --git a/src/test/java/org/jboss/modules/ModuleClassLoaderTest.java b/src/test/java/org/jboss/modules/ModuleClassLoaderTest.java
new file mode 100644
index 0000000..ffaace7
--- /dev/null
+++ b/src/test/java/org/jboss/modules/ModuleClassLoaderTest.java
@@ -0,0 +1,341 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import org.jboss.modules.filter.MultiplePathFilterBuilder;
+import org.jboss.modules.filter.PathFilters;
+import org.jboss.modules.test.ImportedClass;
+import org.jboss.modules.test.ImportedInterface;
+import org.jboss.modules.test.TestClass;
+import org.jboss.modules.util.TestModuleLoader;
+import org.jboss.modules.util.TestResourceLoader;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.List;
+
+import static org.jboss.modules.util.Util.toList;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Test to verify module functionality.
+ *
+ * @author John Bailey
+ * @author <a href="mailto:flavia.rainone at jboss.com">Flavia Rainone</a>
+ */
+public class ModuleClassLoaderTest extends AbstractModuleTestCase {
+
+    private static final ModuleIdentifier MODULE_WITH_CONTENT_ID = ModuleIdentifier.fromString("test-with-content");
+    private static final ModuleIdentifier MODULE_WITH_RESOURCE_ID = ModuleIdentifier.fromString("test-with-resource");
+    private static final ModuleIdentifier MODULE_TO_IMPORT_ID = ModuleIdentifier.fromString("test-to-import");
+    private static final ModuleIdentifier MODULE_WITH_EXPORT_ID = ModuleIdentifier.fromString("test-with-export");
+    private static final ModuleIdentifier MODULE_WITH_DOUBLE_EXPORT_ID = ModuleIdentifier.fromString("test-with-double-export");
+    private static final ModuleIdentifier MODULE_WITH_INVERTED_DOUBLE_EXPORT_ID = ModuleIdentifier.fromString("test-with-inverted-double-export");
+    private static final ModuleIdentifier MODULE_WITH_FILTERED_EXPORT_ID = ModuleIdentifier.fromString("test-with-filtered-export");
+    private static final ModuleIdentifier MODULE_WITH_FILTERED_IMPORT_ID = ModuleIdentifier.fromString("test-with-filtered-import");
+    private static final ModuleIdentifier MODULE_WITH_FILTERED_DOUBLE_EXPORT_ID = ModuleIdentifier.fromString("test-with-filtered-double-export");
+
+    private TestModuleLoader moduleLoader;
+
+    @Before
+    public void setupModuleLoader() throws Exception {
+        moduleLoader = new TestModuleLoader();
+
+        final ModuleSpec.Builder moduleWithContentBuilder = ModuleSpec.build(MODULE_WITH_CONTENT_ID);
+        moduleWithContentBuilder.addResourceRoot(ResourceLoaderSpec.createResourceLoaderSpec(
+                TestResourceLoader.build()
+                .addClass(TestClass.class)
+                .addResources(getResource("test/modulecontentloader/rootOne"))
+                .create()
+        ));
+        moduleWithContentBuilder.addDependency(DependencySpec.createModuleDependencySpec(MODULE_TO_IMPORT_ID));
+        moduleWithContentBuilder.addDependency(DependencySpec.createLocalDependencySpec());
+        moduleLoader.addModuleSpec(moduleWithContentBuilder.create());
+
+        final ModuleSpec.Builder moduleWithResourceBuilder = ModuleSpec.build(MODULE_WITH_RESOURCE_ID);
+        moduleWithResourceBuilder.addResourceRoot(ResourceLoaderSpec.createResourceLoaderSpec(
+                TestResourceLoader.build()
+                .addClass(TestClass.class)
+                .addResources(getResource("class-resources"))
+                .create()
+        ));
+        moduleWithResourceBuilder.addDependency(DependencySpec.createModuleDependencySpec(MODULE_TO_IMPORT_ID));
+        moduleWithResourceBuilder.addDependency(DependencySpec.createLocalDependencySpec());
+        moduleLoader.addModuleSpec(moduleWithResourceBuilder.create());
+
+        final ModuleSpec.Builder moduleToImportBuilder = ModuleSpec.build(MODULE_TO_IMPORT_ID);
+        moduleToImportBuilder.addResourceRoot(ResourceLoaderSpec.createResourceLoaderSpec(
+                TestResourceLoader.build()
+                .addClass(ImportedClass.class)
+                .addClass(ImportedInterface.class)
+                .addResources(getResource("test/modulecontentloader/rootTwo"))
+                .create()
+        ));
+        moduleToImportBuilder.addDependency(DependencySpec.createLocalDependencySpec());
+        moduleLoader.addModuleSpec(moduleToImportBuilder.create());
+
+        final ModuleSpec.Builder moduleWithExportBuilder = ModuleSpec.build(MODULE_WITH_EXPORT_ID);
+        moduleWithExportBuilder.addDependency(DependencySpec.createModuleDependencySpec(MODULE_TO_IMPORT_ID, true, false));
+        moduleWithExportBuilder.addDependency(DependencySpec.createLocalDependencySpec());
+        moduleLoader.addModuleSpec(moduleWithExportBuilder.create());
+
+        final MultiplePathFilterBuilder nestedAndOrgJBossExcludingBuilder = PathFilters.multiplePathFilterBuilder(true);
+        nestedAndOrgJBossExcludingBuilder.addFilter(PathFilters.match("org/jboss/**"), false);
+        nestedAndOrgJBossExcludingBuilder.addFilter(PathFilters.match("nested"), false);
+
+        final ModuleSpec.Builder moduleWithExportFilterBuilder = ModuleSpec.build(MODULE_WITH_FILTERED_EXPORT_ID);
+        moduleWithExportFilterBuilder.addDependency(DependencySpec.createModuleDependencySpec(nestedAndOrgJBossExcludingBuilder.create(), null, MODULE_TO_IMPORT_ID, false));
+        moduleWithExportFilterBuilder.addDependency(DependencySpec.createLocalDependencySpec());
+        moduleLoader.addModuleSpec(moduleWithExportFilterBuilder.create());
+
+        final ModuleSpec.Builder moduleWithImportFilterBuilder = ModuleSpec.build(MODULE_WITH_FILTERED_IMPORT_ID);
+        moduleWithImportFilterBuilder.addDependency(DependencySpec.createModuleDependencySpec(nestedAndOrgJBossExcludingBuilder.create(), PathFilters.rejectAll(), null, MODULE_TO_IMPORT_ID, false));
+        moduleWithImportFilterBuilder.addDependency(DependencySpec.createLocalDependencySpec());
+        moduleLoader.addModuleSpec(moduleWithImportFilterBuilder.create());
+
+        final ModuleSpec.Builder moduleWithDoubleExportBuilder = ModuleSpec.build(MODULE_WITH_DOUBLE_EXPORT_ID);
+        moduleWithDoubleExportBuilder.addDependency(DependencySpec.createModuleDependencySpec(MODULE_TO_IMPORT_ID, true));
+        moduleWithDoubleExportBuilder.addDependency(DependencySpec.createModuleDependencySpec(MODULE_WITH_CONTENT_ID, true));
+        moduleWithDoubleExportBuilder.addDependency(DependencySpec.createLocalDependencySpec());
+        moduleLoader.addModuleSpec(moduleWithDoubleExportBuilder.create());
+
+        final ModuleSpec.Builder moduleWithInvertedDoubleExportBuilder = ModuleSpec.build(MODULE_WITH_INVERTED_DOUBLE_EXPORT_ID);
+        moduleWithInvertedDoubleExportBuilder.addDependency(DependencySpec.createModuleDependencySpec(MODULE_WITH_CONTENT_ID, true));
+        moduleWithInvertedDoubleExportBuilder.addDependency(DependencySpec.createModuleDependencySpec(MODULE_TO_IMPORT_ID, true));
+        moduleWithInvertedDoubleExportBuilder.addDependency(DependencySpec.createLocalDependencySpec());
+        moduleLoader.addModuleSpec(moduleWithInvertedDoubleExportBuilder.create());
+
+        final ModuleSpec.Builder moduleWithFilteredDoubleExportBuilder = ModuleSpec.build(MODULE_WITH_FILTERED_DOUBLE_EXPORT_ID);
+        moduleWithFilteredDoubleExportBuilder.addDependency(DependencySpec.createModuleDependencySpec(PathFilters.not(PathFilters.match("nested")), PathFilters.acceptAll(), null, MODULE_TO_IMPORT_ID, false));
+        moduleWithFilteredDoubleExportBuilder.addDependency(DependencySpec.createModuleDependencySpec(MODULE_WITH_EXPORT_ID, true));
+        moduleWithFilteredDoubleExportBuilder.addDependency(DependencySpec.createLocalDependencySpec());
+        moduleLoader.addModuleSpec(moduleWithFilteredDoubleExportBuilder.create());
+    }
+
+    @Test
+    public void testLocalClassLoad() throws Exception {
+        final Module testModule = moduleLoader.loadModule(MODULE_WITH_CONTENT_ID);
+        final ModuleClassLoader classLoader = testModule.getClassLoader();
+
+        try {
+            Class<?> testClass = classLoader.loadClass("org.jboss.modules.test.TestClass");
+            assertNotNull(testClass);
+        } catch (ClassNotFoundException e) {
+            fail("Should have loaded local class");
+        }
+    }
+
+    @Test
+    public void testResourceLoad() throws Exception {
+        final Module testModule = moduleLoader.loadModule(MODULE_WITH_RESOURCE_ID);
+        final ModuleClassLoader classLoader = testModule.getClassLoader();
+
+        try {
+            Class<?> testClass = classLoader.loadClass("org.jboss.modules.test.TestClass");
+            // direct
+            assertNotNull(testClass.getResource("/file1.txt")); // translates to /file1.txt
+            assertNotNull(testClass.getResource("file2.txt")); // translates to /org/jboss/modules/test/file2.txt
+            // relative
+            assertNotNull(testClass.getResource("../../../../file1.txt")); // should translate to /file1.txt
+            assertNotNull(testClass.getResource("test/../file2.txt")); // should translate to /org/jboss/modules/test/file2.txt
+        } catch (ClassNotFoundException e) {
+            e.printStackTrace();
+            fail("Should have loaded local class");
+        }
+    }
+
+    @Test
+    public void testLocalClassLoadNotFound() throws Exception {
+        final Module testModule = moduleLoader.loadModule(MODULE_WITH_CONTENT_ID);
+        final ModuleClassLoader classLoader = testModule.getClassLoader();
+
+        try {
+            classLoader.loadClass("org.jboss.modules.test.BogusClass");
+            fail("Should have thrown ClassNotFoundException");
+        } catch (ClassNotFoundException expected) {
+        }
+    }
+
+    @Test
+    public void testImportClassLoad() throws Exception {
+        final Module testModule = moduleLoader.loadModule(MODULE_WITH_CONTENT_ID);
+        final ModuleClassLoader classLoader = testModule.getClassLoader();
+
+        try {
+            Class<?> testClass = classLoader.loadClass("org.jboss.modules.test.ImportedClass");
+            assertNotNull(testClass);
+        } catch (ClassNotFoundException e) {
+            fail("Should have loaded imported class");
+        }
+    }
+
+    @Test
+    public void testFilteredImportClassLoad() throws Exception {
+        final Module testModule = moduleLoader.loadModule(MODULE_WITH_FILTERED_IMPORT_ID);
+        final ModuleClassLoader classLoader = testModule.getClassLoader();
+
+        try {
+            classLoader.loadClass("org.jboss.modules.test.ImportedClass");
+            fail("Should have thrown ClassNotFoundException");
+        } catch (ClassNotFoundException expected) {
+        }
+    }
+
+    @Test
+    public void testDoubleExportCLassLoad() throws Exception {
+        final Module testModule = moduleLoader.loadModule(MODULE_WITH_DOUBLE_EXPORT_ID);
+        final ModuleClassLoader classLoader = testModule.getClassLoader();
+        Class<?> testClass = classLoader.loadExportedClass("org.jboss.modules.test.ImportedClass");
+        assertNotNull(testClass);
+
+        testClass = classLoader.loadExportedClass("org.jboss.modules.test.TestClass");
+        assertNotNull(testClass);
+    }
+
+    @Test
+    public void testInvertedDoubleExportCLassLoad() throws Exception {
+        final Module testModule = moduleLoader.loadModule(MODULE_WITH_INVERTED_DOUBLE_EXPORT_ID);
+        final ModuleClassLoader classLoader = testModule.getClassLoader();
+        Class<?> testClass = classLoader.loadExportedClass("org.jboss.modules.test.ImportedClass");
+        assertNotNull(testClass);
+
+        testClass = classLoader.loadExportedClass("org.jboss.modules.test.TestClass");
+        assertNotNull(testClass);
+    }
+
+    @Test
+    public void testFilteredDoubleExportCLassLoad() throws Exception {
+        final Module testModule = moduleLoader.loadModule(MODULE_WITH_FILTERED_DOUBLE_EXPORT_ID);
+        final ModuleClassLoader classLoader = testModule.getClassLoader();
+        Class<?> testClass = classLoader.loadExportedClass("org.jboss.modules.test.ImportedClass");
+        assertNotNull(testClass);
+    }
+
+    @Test
+    public void testLocalResourceRetrieval() throws Exception {
+        final Module testModule = moduleLoader.loadModule(MODULE_WITH_CONTENT_ID);
+        final ModuleClassLoader classLoader = testModule.getClassLoader();
+        final URL resUrl = classLoader.getResource("test.txt");
+        assertNotNull(resUrl);
+    }
+
+    @Test
+    public void testLocalResourceRetrievalNotFound() throws Exception {
+        final Module testModule = moduleLoader.loadModule(MODULE_WITH_CONTENT_ID);
+        final ModuleClassLoader classLoader = testModule.getClassLoader();
+        final URL resUrl = classLoader.getResource("bogus.txt");
+        assertNull(resUrl);
+    }
+
+    @Test
+    public void testImportResourceRetrieval() throws Exception {
+        final Module testModule = moduleLoader.loadModule(MODULE_WITH_CONTENT_ID);
+        final ModuleClassLoader classLoader = testModule.getClassLoader();
+        final URL resUrl = classLoader.getResource("testTwo.txt");
+        assertNotNull(resUrl);
+    }
+
+    @Test
+    public void testFilteredImportResourceRetrieval() throws Exception {
+        final Module testModule = moduleLoader.loadModule(MODULE_WITH_FILTERED_IMPORT_ID);
+        final ModuleClassLoader classLoader = testModule.getClassLoader();
+
+        URL resUrl = classLoader.getResource("nested/nested.txt");
+        assertNull(resUrl);
+    }
+
+    @Test
+    public void testLocalResourcesRetrieval() throws Exception {
+        final Module testModule = moduleLoader.loadModule(MODULE_WITH_CONTENT_ID);
+        final ModuleClassLoader classLoader = testModule.getClassLoader();
+        final Enumeration<URL> resUrls = classLoader.getResources("test.txt");
+        assertNotNull(resUrls);
+        final List<URL> resUrlList = toList(resUrls);
+        assertEquals(1, resUrlList.size());
+        assertTrue(resUrlList.get(0).getPath().contains("rootOne"));
+    }
+
+    @Test
+    public void testLocalResourcesRetrievalNotFound() throws Exception {
+        final Module testModule = moduleLoader.loadModule(MODULE_WITH_CONTENT_ID);
+        final ModuleClassLoader classLoader = testModule.getClassLoader();
+        final Enumeration<URL> resUrls = classLoader.getResources("bogus.txt");
+        assertNotNull(resUrls);
+        final List<URL> resUrlList = toList(resUrls);
+        assertTrue(resUrlList.isEmpty());
+    }
+
+    @Test
+    public void testImportResourcesRetrieval() throws Exception {
+        final Module testModule = moduleLoader.loadModule(MODULE_WITH_CONTENT_ID);
+        final ModuleClassLoader classLoader = testModule.getClassLoader();
+        final Enumeration<URL> resUrls = classLoader.getResources("testTwo.txt");
+        assertNotNull(resUrls);
+        final List<URL> resUrlList = toList(resUrls);
+        assertEquals(1, resUrlList.size());
+        assertTrue(resUrlList.get(0).getPath().contains("rootTwo"));
+    }
+
+    @Test
+    public void testLocalAndImportResourcesRetrieval() throws Exception {
+        final Module testModule = moduleLoader.loadModule(MODULE_WITH_CONTENT_ID);
+        final ModuleClassLoader classLoader = testModule.getClassLoader();
+        final Enumeration<URL> resUrls = classLoader.getResources("nested/nested.txt");
+        assertNotNull(resUrls);
+        final List<URL> resUrlList = toList(resUrls);
+        assertEquals(2, resUrlList.size());
+        boolean rootOne = false;
+        boolean rootTwo = false;
+        for(URL resUrl : resUrlList) {
+            if(!rootOne)
+                rootOne = resUrl.getPath().contains("rootOne");
+            if(!rootTwo)
+                rootTwo = resUrl.getPath().contains("rootTwo");
+        }
+        assertTrue(rootOne);
+        assertTrue(rootTwo);
+    }
+
+    @Test
+    public void testFilteredImportResourcesRetrieval() throws Exception {
+        final Module testModule = moduleLoader.loadModule(MODULE_WITH_FILTERED_IMPORT_ID);
+        final ModuleClassLoader classLoader = testModule.getClassLoader();
+
+        Enumeration<URL> resUrls = classLoader.getResources("nested/nested.txt");
+        List<URL> resUrlList = toList(resUrls);
+        assertTrue(resUrlList.isEmpty());
+    }
+
+    @Test
+    public void testManifest() throws Exception {
+        final Module testModule = moduleLoader.loadModule(MODULE_WITH_CONTENT_ID);
+        final ModuleClassLoader classLoader = testModule.getClassLoader();
+
+        final Class<?> testClass = classLoader.loadClass("org.jboss.modules.test.TestClass");
+        System.out.println(testClass.getClassLoader());
+        final Package pkg = testClass.getPackage();
+        assertEquals("JBoss Modules Test Classes", pkg.getSpecificationTitle());
+    }
+}
diff --git a/src/test/java/org/jboss/modules/ModuleExportTest.java b/src/test/java/org/jboss/modules/ModuleExportTest.java
new file mode 100644
index 0000000..4e83197
--- /dev/null
+++ b/src/test/java/org/jboss/modules/ModuleExportTest.java
@@ -0,0 +1,156 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.lang.reflect.Field;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.jboss.modules.filter.PathFilters;
+import org.jboss.modules.test.ImportedClass;
+import org.jboss.modules.util.TestModuleLoader;
+import org.jboss.modules.util.TestResourceLoader;
+import org.junit.Test;
+
+/**
+ * Test to verify the module export dependencies and imports are created correctly.  Each module should have an entry
+ * directly to the module that has an exported path.
+ *
+ * @author John E. Bailey
+ */
+public class ModuleExportTest extends AbstractModuleTestCase {
+
+    private static final ModuleIdentifier MODULE_A = ModuleIdentifier.fromString("a");
+    private static final ModuleIdentifier MODULE_B = ModuleIdentifier.fromString("b");
+    private static final ModuleIdentifier MODULE_C = ModuleIdentifier.fromString("c");
+    private static final ModuleIdentifier MODULE_D = ModuleIdentifier.fromString("d");
+
+    @Test
+    public void testExportDependencies() throws Exception {
+        final TestModuleLoader moduleLoader = new TestModuleLoader();
+
+        ModuleSpec.Builder builder = ModuleSpec.build(MODULE_A);
+        builder.addDependency(DependencySpec.createModuleDependencySpec(MODULE_B, true, false));
+        moduleLoader.addModuleSpec(builder.create());
+
+        builder = ModuleSpec.build(MODULE_B);
+        builder.addDependency(DependencySpec.createModuleDependencySpec(MODULE_C, true, false));
+        builder.addDependency(DependencySpec.createModuleDependencySpec(MODULE_D));
+        moduleLoader.addModuleSpec(builder.create());
+
+        builder = ModuleSpec.build(MODULE_C);
+        moduleLoader.addModuleSpec(builder.create());
+
+        builder = ModuleSpec.build(MODULE_D);
+        moduleLoader.addModuleSpec(builder.create());
+
+        Module module = moduleLoader.loadModule(MODULE_A);
+        final Set<ModuleIdentifier> dependencyExports = new HashSet<ModuleIdentifier>();
+        getExportedModuleDeps(module, dependencyExports);
+        assertEquals(2, dependencyExports.size());
+        assertTrue(dependencyExports.contains(MODULE_B));
+        assertTrue(dependencyExports.contains(MODULE_C));
+
+        dependencyExports.clear();
+        module = moduleLoader.loadModule(MODULE_B);
+        getExportedModuleDeps(module, dependencyExports);
+        assertEquals(1, dependencyExports.size());
+        assertTrue(dependencyExports.contains(MODULE_C));
+
+        dependencyExports.clear();
+        module = moduleLoader.loadModule(MODULE_C);
+        getExportedModuleDeps(module, dependencyExports);
+        assertEquals(0, dependencyExports.size());
+
+        dependencyExports.clear();
+        module = moduleLoader.loadModule(MODULE_D);
+        getExportedModuleDeps(module, dependencyExports);
+        assertEquals(0, dependencyExports.size());
+    }
+
+    private static void getExportedModuleDeps(final Module module, final Set<ModuleIdentifier> dependencyExports) throws ModuleLoadException {
+        getExportedModuleDeps(module, new HashSet<Module>(Collections.singleton(module)), dependencyExports);
+    }
+
+    private static void getExportedModuleDeps(final Module module, final Set<Module> visited, final Set<ModuleIdentifier> dependencyExports) throws ModuleLoadException {
+        for (Dependency dependency : module.getDependenciesInternal()) {
+            if (dependency instanceof ModuleDependency && dependency.getExportFilter() != PathFilters.rejectAll()) {
+                final ModuleDependency moduleDependency = (ModuleDependency) dependency;
+                final Module md = moduleDependency.getModuleLoader().loadModule(moduleDependency.getIdentifier());
+                if (md != null && moduleDependency.getExportFilter() != PathFilters.rejectAll()) {
+                    if (visited.add(md)) {
+                        dependencyExports.add(md.getIdentifier());
+                        getExportedModuleDeps(md, visited, dependencyExports);
+                    }
+                }
+            }
+        }
+    }
+
+    @SuppressWarnings({ "unchecked" })
+    @Test
+    public void testImportPaths() throws Exception {
+        final TestModuleLoader moduleLoader = new TestModuleLoader();
+
+        ModuleSpec.Builder builder = ModuleSpec.build(MODULE_A);
+        builder.addDependency(DependencySpec.createModuleDependencySpec(MODULE_B, true));
+        moduleLoader.addModuleSpec(builder.create());
+
+        builder = ModuleSpec.build(MODULE_B);
+        builder.addDependency(DependencySpec.createModuleDependencySpec(MODULE_C, true));
+        builder.addDependency(DependencySpec.createModuleDependencySpec(MODULE_D));
+        moduleLoader.addModuleSpec(builder.create());
+
+        builder = ModuleSpec.build(MODULE_C);
+        builder.addResourceRoot(ResourceLoaderSpec.createResourceLoaderSpec(TestResourceLoader.build()
+            .addClass(ImportedClass.class)
+            .create()
+        ));
+        builder.addDependency(DependencySpec.createLocalDependencySpec());
+        moduleLoader.addModuleSpec(builder.create());
+
+        builder = ModuleSpec.build(MODULE_D);
+        moduleLoader.addModuleSpec(builder.create());
+
+        Module module = moduleLoader.loadModule(MODULE_A);
+        module.getClassLoader().loadClass(ImportedClass.class.getName());
+
+        final Field pathsField = Module.class.getDeclaredField("linkage");
+        pathsField.setAccessible(true);
+        final Object paths = pathsField.get(module);
+        final Field allPathsField = paths.getClass().getDeclaredField("allPaths");
+        allPathsField.setAccessible(true);
+        final Map<String, List<LocalLoader>> allPaths = (Map<String, List<LocalLoader>>) allPathsField.get(paths);
+
+        Module moduleC = moduleLoader.loadModule(MODULE_C);
+
+        assertEquals(4, allPaths.size());
+
+        for(Map.Entry<String, List<LocalLoader>> entry : allPaths.entrySet()) {
+            assertEquals(1, entry.getValue().size());
+            assertEquals(moduleC.getClassLoaderPrivate().getLocalLoader(), entry.getValue().get(0));
+        }
+    }
+}
diff --git a/src/test/java/org/jboss/modules/ModuleIdentifierTest.java b/src/test/java/org/jboss/modules/ModuleIdentifierTest.java
new file mode 100644
index 0000000..9239244
--- /dev/null
+++ b/src/test/java/org/jboss/modules/ModuleIdentifierTest.java
@@ -0,0 +1,71 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+
+/**
+ * @author John E. Bailey
+ */
+public class ModuleIdentifierTest {
+
+    @Test
+    public void testFromString() throws Exception {
+        ModuleIdentifier identifier = ModuleIdentifier.fromString("test.module");
+        assertEquals("test.module", identifier.getName());
+        assertEquals("main", identifier.getSlot());
+
+        identifier = ModuleIdentifier.fromString("test.module:old");
+        assertEquals("test.module", identifier.getName());
+        assertEquals("old", identifier.getSlot());
+    }
+
+    @Test
+    public void testToString() {
+        ModuleIdentifier identifier = ModuleIdentifier.fromString("test.module");
+        assertEquals("test.module:main", identifier.toString());
+
+        identifier = ModuleIdentifier.fromString("test.module:old");
+        assertEquals("test.module:old", identifier.toString());
+    }
+
+    @Test
+    public void testInvalidCharacters() throws Exception {
+        try {
+            ModuleIdentifier.fromString("test.module\\test");
+        } catch (IllegalArgumentException unexpected) {
+            fail("Should not have thrown IllegalArgumentException");
+        }
+        try {
+            ModuleIdentifier.fromString("test/module/test");
+        } catch (IllegalArgumentException unexpected) {
+            fail("Should have thrown IllegalArgumentException");
+        }
+
+        try {
+            ModuleIdentifier.fromString("test,module,test");
+        } catch (IllegalArgumentException unexpected) {
+            fail("Should have thrown IllegalArgumentException");
+        }
+
+    }
+}
diff --git a/src/test/java/org/jboss/modules/ModuleIteratorTest.java b/src/test/java/org/jboss/modules/ModuleIteratorTest.java
new file mode 100644
index 0000000..ca938e0
--- /dev/null
+++ b/src/test/java/org/jboss/modules/ModuleIteratorTest.java
@@ -0,0 +1,134 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.jar.JarFile;
+
+import org.jboss.modules.filter.PathFilter;
+import org.jboss.modules.filter.PathFilters;
+import org.jboss.modules.util.TestModuleLoader;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.asset.StringAsset;
+import org.jboss.shrinkwrap.api.exporter.ZipExporter;
+import org.jboss.shrinkwrap.api.spec.JavaArchive;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ *
+ *
+ * @author Thomas.Diesler at jboss.com
+ */
+public class ModuleIteratorTest extends AbstractModuleTestCase {
+
+    private static final ModuleIdentifier MODULE_A = ModuleIdentifier.fromString("a");
+    private static final ModuleIdentifier MODULE_B = ModuleIdentifier.fromString("b");
+
+    @Test
+    public void testMetaInfServicesIterator() throws Exception {
+        TestModuleLoader moduleLoader = new TestModuleLoader();
+
+        ModuleSpec.Builder builder = ModuleSpec.build(MODULE_A);
+        builder.addDependency(DependencySpec.createModuleDependencySpec(MODULE_B, false, false));
+        PathFilter importFilter = PathFilters.getMetaInfServicesFilter();
+        PathFilter exportFilter = PathFilters.acceptAll();
+        builder.addDependency(DependencySpec.createModuleDependencySpec(importFilter, exportFilter, moduleLoader, MODULE_B, false));
+        moduleLoader.addModuleSpec(builder.create());
+
+        builder = ModuleSpec.build(MODULE_B);
+        ResourceLoader resB = new JarFileResourceLoader("jarB", toJarFile(getModuleB()));
+        builder.addResourceRoot(ResourceLoaderSpec.createResourceLoaderSpec(resB));
+        builder.addDependency(DependencySpec.createLocalDependencySpec());
+        moduleLoader.addModuleSpec(builder.create());
+
+        Module moduleA = moduleLoader.loadModule(MODULE_A);
+        Iterator<Resource> itres = moduleA.iterateResources(PathFilters.getMetaInfServicesFilter());
+        Assert.assertTrue("Found a resource", itres.hasNext());
+        Assert.assertEquals("META-INF/services/org/apache/camel/component/jms", itres.next().getName());
+        Assert.assertFalse("No other resource", itres.hasNext());
+    }
+
+    @Test
+    public void testIterateModules() throws Exception {
+        IterableModuleFinder fakeFinder = new IterableModuleFinder() {
+            private ModuleIdentifier[] modules = { ModuleIdentifier.create("a"), ModuleIdentifier.create("b")};
+
+            @Override
+            public Iterator<ModuleIdentifier> iterateModules(ModuleIdentifier baseIdentifier, boolean recursive) {
+                return new Iterator<ModuleIdentifier>() {
+                    private int pos = 0;
+
+                    @Override
+                    public boolean hasNext() {
+                        return pos < modules.length;
+                    }
+
+                    @Override
+                    public ModuleIdentifier next() {
+                        return modules[pos++];
+                    }
+
+                    @Override
+                    public void remove() {
+                        throw new UnsupportedOperationException();
+                    }
+                };
+            }
+
+            @Override
+            public ModuleSpec findModule(ModuleIdentifier identifier, ModuleLoader delegateLoader)
+                throws ModuleLoadException {
+                for (ModuleIdentifier m : modules) {
+                    if (m.equals(identifier)) {
+                        return ModuleSpec.build(m).create();
+                    }
+                }
+                return null;
+            }
+        };
+
+        ModuleLoader loader = new ModuleLoader(new ModuleFinder[]{fakeFinder});
+
+        Iterator<ModuleIdentifier> it = loader.iterateModules(null, true);
+        int count = 0;
+        while (it.hasNext()) {
+            it.next();
+            count++;
+        }
+
+        Assert.assertEquals(2, count);
+    }
+
+    private JarFile toJarFile(JavaArchive archive) throws IOException {
+        ZipExporter exporter = archive.as(ZipExporter.class);
+        File targetFile = new File("target/shrinkwrap/" + archive.getName());
+        targetFile.getParentFile().mkdirs();
+        exporter.exportTo(targetFile, true);
+        return new JarFile(targetFile);
+    }
+
+    private JavaArchive getModuleB() {
+        JavaArchive archive = ShrinkWrap.create(JavaArchive.class, "moduleB");
+        archive.addAsManifestResource(new StringAsset("someContent"), "services/org/apache/camel/component/jms");
+        return archive;
+    }
+}
diff --git a/src/test/java/org/jboss/modules/ModulePropertyTest.java b/src/test/java/org/jboss/modules/ModulePropertyTest.java
new file mode 100644
index 0000000..ea151c1
--- /dev/null
+++ b/src/test/java/org/jboss/modules/ModulePropertyTest.java
@@ -0,0 +1,50 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.io.File;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+/**
+ * Test to verify the functionality of module properties.
+ *
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public class ModulePropertyTest extends AbstractModuleTestCase {
+    private ModuleLoader moduleLoader;
+
+    @Before
+    public void setupModuleLoader() throws Exception {
+        final File repoRoot = getResource("test/repo");
+        moduleLoader = new LocalModuleLoader(new File[] {repoRoot});
+    }
+
+    @Test
+    public void testBasic() throws Exception {
+        Module module = moduleLoader.loadModule(MODULE_ID);
+        assertNull(module.getProperty("non-existent"));
+        assertEquals("blah", module.getProperty("non-existent", "blah"));
+        assertEquals("true", module.getProperty("test.prop.1"));
+        assertEquals("propertyValue", module.getProperty("test.prop.2"));
+    }
+}
diff --git a/src/test/java/org/jboss/modules/PathFilterTest.java b/src/test/java/org/jboss/modules/PathFilterTest.java
new file mode 100644
index 0000000..55273ce
--- /dev/null
+++ b/src/test/java/org/jboss/modules/PathFilterTest.java
@@ -0,0 +1,72 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import org.jboss.modules.filter.MultiplePathFilterBuilder;
+import org.jboss.modules.filter.PathFilter;
+import org.jboss.modules.filter.PathFilters;
+import org.junit.Test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test to verify the PathFilter functionality.
+ * 
+ * @author John E. Bailey
+ */
+public class PathFilterTest {
+
+    @Test
+    public void testMatch() throws Exception {
+        PathFilter pathFilter = PathFilters.match("foo/**");
+        assertFalse(pathFilter.accept("foo"));
+        assertTrue(pathFilter.accept("foo/bar"));
+        assertTrue(pathFilter.accept("foo/bar/baz"));
+
+        pathFilter = PathFilters.match("foo/*");
+        assertFalse(pathFilter.accept("foo"));
+        assertTrue(pathFilter.accept("foo/bar"));
+        assertTrue(pathFilter.accept("foo/bar/baz"));
+
+        pathFilter = PathFilters.match("foo");
+        assertTrue(pathFilter.accept("foo"));
+        assertTrue(pathFilter.accept("foo/bar"));
+        assertTrue(pathFilter.accept("foo/bar/baz"));
+
+        pathFilter = PathFilters.match("**/bar/**");
+        assertFalse(pathFilter.accept("foo"));
+        assertFalse(pathFilter.accept("foo/bar"));
+        assertTrue(pathFilter.accept("foo/bar/baz"));
+        assertTrue(pathFilter.accept("foo/baz/bar/biff"));
+    }
+
+    @Test
+    public void testDelegating() throws Exception {
+        final MultiplePathFilterBuilder builder = PathFilters.multiplePathFilterBuilder(true);
+        builder.addFilter(PathFilters.match("foo/*"), false);
+        builder.addFilter(PathFilters.match("**/bar/**"), false);
+        builder.addFilter(PathFilters.match("baz/**"), false);
+        PathFilter pathFilter = builder.create();
+        assertTrue(pathFilter.accept("foo"));
+        assertFalse(pathFilter.accept("foo/bar"));
+        assertFalse(pathFilter.accept("foo/bar/baz"));
+        assertFalse(pathFilter.accept("baz/foo/bar"));
+    }
+}
diff --git a/src/test/java/org/jboss/modules/SystemResourcesTest.java b/src/test/java/org/jboss/modules/SystemResourcesTest.java
new file mode 100644
index 0000000..9237441
--- /dev/null
+++ b/src/test/java/org/jboss/modules/SystemResourcesTest.java
@@ -0,0 +1,62 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules;
+
+import java.net.URL;
+import java.util.Enumeration;
+
+import org.jboss.modules.util.TestModuleLoader;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Test System pkgs resources
+ *
+ * @author Jason T. Greene
+ */
+public class SystemResourcesTest  {
+    private static final ModuleIdentifier MODULE_A = ModuleIdentifier.fromString("A");
+
+    static {
+        System.setProperty("jboss.modules.system.pkgs", "javax.activation");
+    }
+
+
+    @Test
+    public void testResources() throws Exception {
+        final TestModuleLoader moduleLoader = new TestModuleLoader();
+
+        ModuleSpec.Builder builder = ModuleSpec.build(MODULE_A);
+        //builder.addDependency(DependencySpec.createModuleDependencySpec(PathFilters.match("org/jboss/modules/**"), PathFilters.rejectAll(), null, ModuleIdentifier.SYSTEM, false));
+        moduleLoader.addModuleSpec(builder.create());
+
+        Module module = moduleLoader.loadModule(MODULE_A);
+        ClassLoader cl = module.getClassLoader();
+
+        Enumeration<URL> resources = cl.getResources("javax/activation/DataHandler.class");
+        Assert.assertTrue(resources.hasMoreElements());
+
+        resources = cl.getResources("javax/sql/RowSet.class");
+        Assert.assertFalse(resources.hasMoreElements());
+
+        resources = cl.getResources("org/jboss/");
+        Assert.assertFalse(resources.hasMoreElements());
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/org/jboss/modules/ref/AbstractReapableReferenceTest.java b/src/test/java/org/jboss/modules/ref/AbstractReapableReferenceTest.java
new file mode 100644
index 0000000..04bf2c5
--- /dev/null
+++ b/src/test/java/org/jboss/modules/ref/AbstractReapableReferenceTest.java
@@ -0,0 +1,34 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.ref;
+
+import org.jboss.modules.ref.util.Assert;
+
+/**
+ * Super class for all test cases that apply to reapable references.
+ * 
+ * @author <a href="mailto:flavia.rainone at jboss.com">Flavia Rainone</a>
+ * @see Reapable
+ */
+public abstract class AbstractReapableReferenceTest extends AbstractReferenceTest {
+
+    final <T, A> void assertReference(Reference<T, A> reference, T referenceValue, A attachment, Reaper<T, A> reaper) {
+        Assert.assertReference(reference, referenceValue, attachment, getTestedReferenceType(), reaper);
+    }
+}
diff --git a/src/test/java/org/jboss/modules/ref/AbstractReferenceTest.java b/src/test/java/org/jboss/modules/ref/AbstractReferenceTest.java
new file mode 100644
index 0000000..ae922fa
--- /dev/null
+++ b/src/test/java/org/jboss/modules/ref/AbstractReferenceTest.java
@@ -0,0 +1,81 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.ref;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.jboss.modules.ref.Reference.Type;
+import org.jboss.modules.ref.util.Assert;
+import org.junit.Test;
+
+/**
+ * Super class for all reference test cases, contains a few tests for functionality common to all
+ * references, and a reference assertion method.
+ * 
+ * @author <a href="mailto:flavia.rainone at jboss.com">Flavia Rainone</a>
+ */
+public abstract class AbstractReferenceTest {
+
+    abstract <T, A> Reference<T, A> createReference(T value, A attachment);
+    abstract Type getTestedReferenceType();
+
+    @Test
+    public void referenceEqualsReference() {
+        final Reference<StringBuffer, Number> reference1 = createReference(new StringBuffer().append("some text text"), (Number) Long.valueOf(5l));
+        final Reference<StringBuffer, Number> reference2 = createReference(new StringBuffer().append("some text"), (Number) Double.valueOf(5.5));
+        final Reference<StringBuffer, Number> reference3 = createReference(null, null);
+        final Reference<StringBuffer, Number> reference4 = createReference(new StringBuffer().append("some text text"), (Number) Long.valueOf(5l));
+
+        assertEquals(reference1, reference1);
+        assertEquals(reference1.hashCode(), reference1.hashCode());
+        assertFalse(reference1.equals(reference2));
+        assertFalse(reference1.equals(reference3));
+        assertFalse(reference1.equals(reference4));
+
+        assertEquals(reference2, reference2);
+        assertEquals(reference2.hashCode(), reference2.hashCode());
+        assertFalse(reference2.equals(reference3));
+        assertFalse(reference2.equals(reference4));
+
+        assertEquals(reference3, reference3);
+        assertEquals(reference3.hashCode(), reference3.hashCode());
+        assertFalse(reference3.equals(reference4));
+
+        assertEquals(reference4, reference4);
+        assertEquals(reference4.hashCode(), reference4.hashCode());
+    }
+
+    @Test
+    public void clearReference() {
+        final Reference<Boolean, String> reference = createReference(Boolean.TRUE, "attachment for true");
+        assertTrue(reference.get().booleanValue());
+        assertEquals("attachment for true", reference.getAttachment());
+
+        reference.clear();
+        assertNull(reference.get());
+        assertEquals("attachment for true", reference.getAttachment());
+    }
+
+    final <T, A> void assertReference(Reference<T, A> reference, T referent, A attachment) {
+        Assert.assertReference(reference, referent, attachment, getTestedReferenceType());
+    }
+}
diff --git a/src/test/java/org/jboss/modules/ref/PhantomReferenceTestCase.java b/src/test/java/org/jboss/modules/ref/PhantomReferenceTestCase.java
new file mode 100644
index 0000000..baac0e8
--- /dev/null
+++ b/src/test/java/org/jboss/modules/ref/PhantomReferenceTestCase.java
@@ -0,0 +1,96 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.ref;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+import java.lang.ref.ReferenceQueue;
+import java.util.ArrayList;
+import java.util.Collection;
+
+import org.jboss.modules.ref.Reference.Type;
+import org.jboss.modules.ref.util.TestReaper;
+import org.junit.Test;
+
+/**
+ * Test for PhantomReference.
+ * 
+ * @author <a href="mailto:flavia.rainone at jboss.com">Flavia Rainone</a>
+ * @see PhantomReference
+ */
+public class PhantomReferenceTestCase extends AbstractReapableReferenceTest {
+
+    @Test
+    public void plainPhantomReference() {
+        final Reference<String, String> reference = new PhantomReference<String, String>("referent", "attachment",
+                (ReferenceQueue<String>) null);
+        assertReference(reference, null, "attachment", null);
+    }
+
+    @Test
+    public void nullPhantomReference() throws Exception {
+        final Reference<Thing, Integer> reference = new PhantomReference<Thing, Integer>(null, Integer.valueOf(0), new ReferenceQueue<Thing>());
+        assertReference(reference, null, Integer.valueOf(0), null);
+    }
+
+    @Test
+    public void phantomReferenceWithReferenceQueue() throws Exception {
+        final ReferenceQueue<Collection<Object>> referenceQueue = new ReferenceQueue<Collection<Object>>();
+        Collection<Object> collection = new ArrayList<Object>();
+        final Reference<Collection<Object>, String> reference = new PhantomReference<Collection<Object>, String>(collection, "collection", referenceQueue);
+        assertReference(reference, null, "collection", null);
+        collection = null;
+        System.gc();
+        assertSame(reference, referenceQueue.remove(300));
+    }
+
+    @Test
+    public void phantomReferenceWithReaper() throws Exception {
+        Thing thing = new Thing();
+        final TestReaper<Thing, Void> reaper = new TestReaper<Thing, Void>();
+        final Reference<Thing, Void> reference = new PhantomReference<Thing, Void>(thing, null, reaper);
+        assertReference(reference, null, null, reaper);
+        thing = null;
+        System.gc();
+        assertSame(reference, reaper.getReapedReference());
+    }
+
+    @Override @Test
+    public void clearReference() {
+        final Reference<Boolean, String> reference = createReference(Boolean.TRUE, "attachment for true");
+        assertNull(reference.get());
+        assertEquals("attachment for true", reference.getAttachment());
+
+        reference.clear();
+        assertNull(reference.get());
+        assertEquals("attachment for true", reference.getAttachment());
+    }
+
+    @Override
+    <T, A> Reference<T, A> createReference(T value, A attachment) {
+        return new PhantomReference<T, A>(value, attachment, new TestReaper<T, A>());
+    }
+
+    @Override
+    Type getTestedReferenceType() {
+        return Type.PHANTOM;
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/org/jboss/modules/ref/ReferencesTestCase.java b/src/test/java/org/jboss/modules/ref/ReferencesTestCase.java
new file mode 100644
index 0000000..2a89c60
--- /dev/null
+++ b/src/test/java/org/jboss/modules/ref/ReferencesTestCase.java
@@ -0,0 +1,216 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.ref;
+
+import static org.jboss.modules.ref.util.Assert.assertReference;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.lang.ref.ReferenceQueue;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.concurrent.TimeUnit;
+
+import org.jboss.modules.ref.Reference.Type;
+import org.jboss.modules.ref.References.ReaperThread;
+import org.jboss.modules.ref.util.TestReaper;
+import org.junit.Test;
+
+/**
+ * Test for {@link References} class and internal classes.
+ * 
+ * @author <a href="mailto:flavia.rainone at jboss.com">Flavia Rainone</a>
+ */
+public class ReferencesTestCase {
+
+    @Test
+    public void nullReference() {
+        final Reference<?, ?> nullReference = References.getNullReference();
+        assertReference(nullReference, null, null, Type.NULL);
+        nullReference.clear();
+        assertReference(nullReference, null, null, Type.NULL);
+        assertEquals(nullReference, References.create(Type.NULL, null, null));
+        assertEquals(nullReference, References.create(Type.NULL, null, null, (Reaper<Object, Object>) null));
+        assertEquals(nullReference, References.create(Type.NULL, null, null, (ReferenceQueue<Object>) null));
+    }
+
+    @Test
+    public void createStrongReference() {
+        final Reference<String, String> reference = createReference(Type.STRONG);
+        assertTrue(reference instanceof StrongReference);
+    }
+
+    @Test
+    public void createSoftReference() {
+        final Reference<String, String> reference = createReference(Type.SOFT);
+        assertTrue(reference instanceof SoftReference);
+    }
+
+    @Test
+    public void createWeakReference() {
+        final Reference<String, String> reference = createReference(Type.WEAK);
+        assertTrue(reference instanceof WeakReference);
+    }
+
+    @Test
+    public void createIllegalPhantomReference() {
+        try {
+            References.create(Type.PHANTOM, "value", "attachment");
+            fail("IllegalArgumentException expected because of missing reaper/reference queue");
+        } catch (IllegalArgumentException e) {}
+    }
+
+    @Test
+    public void createStrongReferenceWithReaper() throws Exception {
+        final Object referent = new Object();
+        final Reference<Object, String> reference = References.create(Type.STRONG, referent, "attachment",
+                new TestReaper<Object, String>());
+        assertReference(reference, referent, "attachment", Type.STRONG);
+        assertTrue(reference instanceof StrongReference);
+    }
+
+    @Test
+    public void createSoftReferenceWithReaper() throws Exception {
+        final Reference<Object, String> reference = createReferenceWithReaper(Type.SOFT, false, false);
+        assertTrue(reference instanceof SoftReference);
+    }
+
+    @Test
+    public void createWeakReferenceWithReaper() throws Exception {
+        final Reference<Object, String> reference = createReferenceWithReaper(Type.WEAK, false, true);
+        assertTrue(reference instanceof WeakReference);
+    }
+
+    @Test
+    public void createPhantomReferenceWithReaper() throws Exception {
+        final Reference<Object, String> reference = createReferenceWithReaper(Type.PHANTOM, true, true);
+        assertTrue(reference instanceof PhantomReference);
+    }
+
+    @Test
+    public void createStrongReferenceWithQueue() throws Exception {
+        final Object referent = new Object();
+        final Reference<Object, String> reference = References.create(Type.STRONG, referent, "attachment", new ReferenceQueue<Object>());
+        assertReference(reference, referent, "attachment", Type.STRONG);
+        assertTrue(reference instanceof StrongReference);
+    }
+
+    @Test
+    public void createSoftReferenceWithQueue() throws Exception {
+        final Reference<?, ?> reference = createReferenceWithQueue(Type.SOFT, false, false);
+        assertTrue(reference instanceof SoftReference);
+    }
+
+    @Test
+    public void createWeakReferenceWithQueue() throws Exception {
+        final Reference<?, ?> reference = createReferenceWithQueue(Type.WEAK, false, true);
+        assertTrue(reference instanceof WeakReference);
+    }
+
+    @Test
+    public void createPhantomReferenceWithQueue() throws Exception {
+        final Reference<?, ?> reference = createReferenceWithQueue(Type.PHANTOM, true, true);
+        assertTrue(reference instanceof PhantomReference);
+    }
+
+    @Test
+    public void reapUnreapableReference() throws Exception {
+        Object referent = new Object();
+        final TestReaper<Object, Void> reaper = new TestReaper<Object, Void>();
+        final Reference<Object, Void> reference = new UnreapableWeakReference(referent);
+        assertReference(reference, referent, null, Type.WEAK);
+        referent = null;
+        System.gc();
+        assertNull(reaper.get(200, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void failToReapReference() throws Exception {
+        final TestReaper<Object, Void> reaper = new FailToReap<Object, Void>();
+        Object referent = new Object();
+        final Reference<Object, Void> reference = new WeakReference<Object, Void>(referent, null, reaper);
+        assertReference(reference, referent, null, Type.WEAK, reaper);
+        referent = null;
+        System.gc();
+        assertNull(reaper.get(200, TimeUnit.MILLISECONDS));
+    }
+
+    private Reference<String, String> createReference(Type type) {
+        final Reference<String, String> reference = References.create(type, "value", "attachment");
+        assertReference(reference, "value", "attachment", type);
+        return reference;
+    }
+
+    private Reference<Object, String> createReferenceWithReaper(Type type, boolean expectedNullValue, boolean testReaper) throws Exception {
+        Object referent = new Object();
+        final TestReaper<Object, String> reaper = new TestReaper<Object, String>();
+        final Reference<Object, String> reference = References.create(type, referent, "attachment", reaper);
+        assertReference(reference, expectedNullValue? null: referent, "attachment", type, reaper);
+        if (testReaper) {
+            referent = null;
+            System.gc();
+            assertSame(reference, reaper.get());
+        }
+        return reference;
+    }
+
+    private Reference<Collection<Integer>, Object> createReferenceWithQueue(Type type, boolean expectedNullValue, boolean testQueue) throws Exception {
+        final Object referenceAttachment = new Object();
+        Collection<Integer> referent = new ArrayList<Integer>();
+        referent.add(Integer.valueOf(1));
+        referent.add(Integer.valueOf(11));
+        referent.add(Integer.valueOf(111));
+        final ReferenceQueue<Collection<Integer>> referenceQueue = new ReferenceQueue<Collection<Integer>>();
+        final Reference<Collection<Integer>, Object> reference = References.create(type, referent, referenceAttachment, referenceQueue);
+        assertReference(reference, expectedNullValue? null: referent, referenceAttachment, type, null);
+        if (testQueue) {
+            referent = null;
+            System.gc();
+            assertSame(reference, referenceQueue.remove(300));
+        }
+        return reference;
+    }
+
+    private static final class UnreapableWeakReference extends java.lang.ref.WeakReference<Object> implements Reference<Object, Void> {
+
+        public UnreapableWeakReference(Object referent) {
+            super(referent, ReaperThread.REAPER_QUEUE);
+        }
+
+        @Override
+        public Void getAttachment() {
+            return null;
+        }
+
+        @Override
+        public Reference.Type getType() {
+            return Type.WEAK;
+        }
+    }
+
+    private static final class FailToReap<T, A> extends TestReaper<T, A> {
+        @Override
+        public void reap(Reference<T, A> reference) {
+            throw new RuntimeException("fail to reap");
+        }
+    }
+}
diff --git a/src/test/java/org/jboss/modules/ref/SoftReferenceTestCase.java b/src/test/java/org/jboss/modules/ref/SoftReferenceTestCase.java
new file mode 100644
index 0000000..d6fa86d
--- /dev/null
+++ b/src/test/java/org/jboss/modules/ref/SoftReferenceTestCase.java
@@ -0,0 +1,79 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.ref;
+
+import java.lang.ref.ReferenceQueue;
+import java.util.ArrayList;
+import java.util.Collection;
+
+import org.jboss.modules.ref.Reference.Type;
+import org.jboss.modules.ref.util.TestReaper;
+import org.junit.Test;
+
+/**
+ * Test for {@link SoftReference}.
+ * 
+ * @author <a href="mailto:flavia.rainone at jboss.com">Flavia Rainone</a>
+ */
+public class SoftReferenceTestCase extends AbstractReapableReferenceTest {
+
+    @Test
+    public void plainSoftReference() {
+        final Reference<String, String> reference = new SoftReference<String, String>("referent", "attachment");
+        assertReference(reference, "referent", "attachment", null);
+    }
+
+    @Test
+    public void softReferenceWithoutAttachment() {
+        final Reference<String, String> reference = new SoftReference<String, String>("referent");
+        assertReference(reference, "referent", null, null);
+    }
+
+    @Test
+    public void nullSoftReference() throws Exception {
+        final Reference<Thing, Integer> reference = new SoftReference<Thing, Integer>(null, Integer.valueOf(0));
+        assertReference(reference, null, Integer.valueOf(0), null);
+    }
+
+    @Test
+    public void softReferenceWithReferenceQueue() throws Exception {
+        final ReferenceQueue<Collection<Object>> referenceQueue = new ReferenceQueue<Collection<Object>>();
+        final Collection<Object> collection = new ArrayList<Object>();
+        final Reference<Collection<Object>, String> reference = new SoftReference<Collection<Object>, String>(collection, "collection", referenceQueue);
+        assertReference(reference, collection, "collection", null);
+    }
+
+    @Test
+    public void softReferenceWithReaper() throws Exception {
+        final Thing thing = new Thing();
+        final TestReaper<Thing, Void> reaper = new TestReaper<Thing, Void>();
+        final Reference<Thing, Void> reference = new SoftReference<Thing, Void>(thing, null, reaper);
+        assertReference(reference, thing, null, reaper);
+    }
+
+    @Override
+    <T, A> Reference<T, A> createReference(T value, A attachment) {
+        return new SoftReference<T, A>(value, attachment);
+    }
+
+    @Override
+    Type getTestedReferenceType() {
+        return Type.SOFT;
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/org/jboss/modules/ref/StrongReferenceTestCase.java b/src/test/java/org/jboss/modules/ref/StrongReferenceTestCase.java
new file mode 100644
index 0000000..0a65a8b
--- /dev/null
+++ b/src/test/java/org/jboss/modules/ref/StrongReferenceTestCase.java
@@ -0,0 +1,64 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.ref;
+
+import org.jboss.modules.ref.Reference.Type;
+import org.junit.Test;
+
+/**
+ * Test for {@link StrongReference}.
+ * 
+ * @author <a href="mailto:flavia.rainone at jboss.com">Flavia Rainone</a>
+ */
+public class StrongReferenceTestCase extends AbstractReferenceTest{
+
+    @Test
+    public void plainStrongReference() {
+        final Reference<String, String> reference = new StrongReference<String, String>("referent", "attachment");
+        assertReference(reference, "referent", "attachment");
+    }
+
+    @Test
+    public void noAttachmentReference() {
+        final Reference<String, Void> reference = new StrongReference<String, Void>("noAttachmentReference");
+        assertReference(reference, "noAttachmentReference", null);
+    }
+
+    @Test
+    public void nullReference() {
+        final Reference<Object, String> reference = new StrongReference<Object, String>(null, "attachment of null reference");
+        assertReference(reference, null, "attachment of null reference");
+    }
+
+    @Test
+    public void nullReferenceWithoutAttachment() {
+        final Reference<StringBuffer, Number> reference = new StrongReference<StringBuffer, Number>(null);
+        assertReference(reference, null, null);
+    }
+
+    @Override
+    <T, A> Reference<T, A> createReference(T value, A attachment) {
+        return new StrongReference<T, A>(value, attachment);
+    }
+
+    @Override
+    Type getTestedReferenceType() {
+        return Type.STRONG;
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/org/jboss/modules/ref/Thing.java b/src/test/java/org/jboss/modules/ref/Thing.java
new file mode 100644
index 0000000..2a7a7fb
--- /dev/null
+++ b/src/test/java/org/jboss/modules/ref/Thing.java
@@ -0,0 +1,35 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.ref;
+
+/**
+ * @author <a href="mailto:david.lloyd at redhat.com">David M. Lloyd</a>
+ */
+public final class Thing {
+    private final String blah = "blah!";
+    private final int boo = 123;
+
+    public String getBlah() {
+        return blah;
+    }
+
+    public int getBoo() {
+        return boo;
+    }
+}
diff --git a/src/test/java/org/jboss/modules/ref/WeakReferenceTestCase.java b/src/test/java/org/jboss/modules/ref/WeakReferenceTestCase.java
new file mode 100644
index 0000000..213653d
--- /dev/null
+++ b/src/test/java/org/jboss/modules/ref/WeakReferenceTestCase.java
@@ -0,0 +1,87 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.ref;
+
+import static org.junit.Assert.assertSame;
+
+import java.lang.ref.ReferenceQueue;
+import java.util.ArrayList;
+import java.util.Collection;
+
+import org.jboss.modules.ref.Reference.Type;
+import org.jboss.modules.ref.util.TestReaper;
+import org.junit.Test;
+
+/**
+ * Test for {@link WeakReference}.
+ * 
+ * @author <a href="mailto:flavia.rainone at jboss.com">Flavia Rainone</a>
+ */
+public class WeakReferenceTestCase extends AbstractReapableReferenceTest {
+
+    @Test
+    public void plainWeakReference() {
+        final Reference<String, String> reference = new WeakReference<String, String>("referent", "attachment");
+        assertReference(reference, "referent", "attachment", null);
+    }
+
+    @Test
+    public void weakReferenceWithoutAttachment() {
+        final Reference<String, String> reference = new WeakReference<String, String>("referent");
+        assertReference(reference, "referent", null, null);
+    }
+
+    @Test
+    public void nullWeakReference() throws Exception {
+        final Reference<Thing, Integer> reference = new WeakReference<Thing, Integer>(null, Integer.valueOf(0));
+        assertReference(reference, null, Integer.valueOf(0));
+    }
+
+    @Test
+    public void weakReferenceWithReferenceQueue() throws Exception {
+        final ReferenceQueue<Collection<Object>> referenceQueue = new ReferenceQueue<Collection<Object>>();
+        Collection<Object> collection = new ArrayList<Object>();
+        final Reference<Collection<Object>, String> reference = new WeakReference<Collection<Object>, String>(collection, "collection", referenceQueue);
+        assertReference(reference, collection, "collection", null);
+        collection = null;
+        System.gc();
+        assertSame(reference, referenceQueue.remove(300));
+    }
+
+    @Test
+    public void weakReferenceWithReaper() throws Exception {
+        Thing thing = new Thing();
+        final TestReaper<Thing, Void> reaper = new TestReaper<Thing, Void>();
+        final Reference<Thing, Void> reference = new WeakReference<Thing, Void>(thing, null, reaper);
+        assertReference(reference, thing, null, reaper);
+        thing = null;
+        System.gc();
+        assertSame(reference, reaper.getReapedReference());
+    }
+
+    @Override
+    <T, A> Reference<T, A> createReference(T value, A attachment) {
+        return new WeakReference<T, A>(value, attachment);
+    }
+
+    @Override
+    Type getTestedReferenceType() {
+        return Type.WEAK;
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/org/jboss/modules/ref/util/Assert.java b/src/test/java/org/jboss/modules/ref/util/Assert.java
new file mode 100644
index 0000000..8578e45
--- /dev/null
+++ b/src/test/java/org/jboss/modules/ref/util/Assert.java
@@ -0,0 +1,82 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.ref.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+
+import org.jboss.modules.ref.Reaper;
+import org.jboss.modules.ref.Reference;
+import org.jboss.modules.ref.Reference.Type;
+
+/**
+ * Assertion methods used by reference tests.
+ * 
+ * @author <a href="mailto:flavia.rainone at jboss.com">Flavia Rainone</a>
+ */
+public class Assert {
+
+    /**
+     * Assert that {@code reference} contains {@code referent}, {@code attachment}, and that its type is {@code type}.
+     * 
+     * @param <T>         the type of referent
+     * @param <A>         the type of attachment
+     * @param reference   the reference that needs to be checked
+     * @param referent    the expected content for {@code reference}
+     * @param attachment  the expected attachment contained in {@code reference}
+     * @param type        the expected type of {@code reference}
+     */
+    public static final <T, A> void assertReference(Reference<T, A> reference, T referent, A attachment, Type type) {
+        assertEquals(referent, reference.get());
+        assertEquals(attachment, reference.getAttachment());
+        assertSame(type, reference.getType());
+        assertNotNull(reference.toString()); // make sure that no exception is thrown and that null is never returned
+    }
+
+    /**
+     * Assert that {@code reference} contains {@code referent}, {@code attachment}, and that its type is {@code type}.
+     * Also asserts that {@code reaper} is the reaper used by {@code referent}.
+     * <p>
+     * Call this method only for {@link org.jboss.msc.ref.Reapable Reapable} references. This method assumes that 
+     * {@code reference} has a method called {@code getReaper} that will return the reaper.
+     * 
+     * @param <T>         the type of referent
+     * @param <A>         the type of attachment
+     * @param reference   the {@code Reapable} reference that needs to be checked
+     * @param referent    the expected content for {@code reference}
+     * @param attachment  the expected attachment contained in {@code reference}
+     * @param type        the expected type of {@code reference}
+     * @param reaper      the expected reaper
+     */
+    public static final <T, A> void assertReference(Reference<T, A> reference, T referent, A attachment, Type type, Reaper<T, A> reaper) {
+        assertReference(reference, referent, attachment, type);
+        try {
+            assertEquals(reaper, reference.getClass().getMethod("getReaper").invoke(reference));
+        } catch (NoSuchMethodException e) {
+            fail("Can't find getReaper method at clas " + reference.getClass());
+        } catch (RuntimeException e) {
+            throw e;
+        }
+        catch (Exception e) {
+            fail(e.getMessage());
+        }
+    }
+}
diff --git a/src/test/java/org/jboss/modules/ref/util/TestReaper.java b/src/test/java/org/jboss/modules/ref/util/TestReaper.java
new file mode 100644
index 0000000..ad52c9a
--- /dev/null
+++ b/src/test/java/org/jboss/modules/ref/util/TestReaper.java
@@ -0,0 +1,81 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.ref.util;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.jboss.modules.ref.Reaper;
+import org.jboss.modules.ref.Reference;
+
+/**
+ * Reaper used by tests.
+ * 
+ * @author <a href="mailto:flavia.rainone at jboss.com">Flavia Rainone</a>
+ *
+ */
+public class TestReaper<T, A> implements Reaper<T, A>, Future<Reference<T, A>> {
+
+    private Reference<T, A> reaped;
+    private final CountDownLatch countDownLatch = new CountDownLatch(1);
+
+    @Override
+    public void reap(Reference<T, A> reference) {
+        reaped = reference;
+        countDownLatch.countDown();
+    }
+
+    public Reference<T, A> getReapedReference() throws InterruptedException, ExecutionException {
+        return get();
+    }
+
+    @Override
+    public boolean cancel(boolean mayInterruptIfRunning) {
+        return false;
+    }
+
+    @Override
+    public boolean isCancelled() {
+        return false;
+    }
+
+    @Override
+    public boolean isDone() {
+        return reaped != null;
+    }
+
+    @Override
+    public Reference<T, A> get() throws InterruptedException, ExecutionException {
+        try {
+            return get(30l, TimeUnit.SECONDS);
+        } catch (TimeoutException e) {
+            throw new RuntimeException("Could not get reaped in 30 second timeout.");
+        }
+    }
+
+    @Override
+    public Reference<T, A> get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException,
+            TimeoutException {
+        countDownLatch.await(timeout, unit);
+        return reaped;
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/org/jboss/modules/test/BarImpl.java b/src/test/java/org/jboss/modules/test/BarImpl.java
new file mode 100644
index 0000000..67e6e9b
--- /dev/null
+++ b/src/test/java/org/jboss/modules/test/BarImpl.java
@@ -0,0 +1,22 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jboss.modules.test;
+
+public class BarImpl {
+
+}
diff --git a/src/test/java/org/jboss/modules/test/ClassA.java b/src/test/java/org/jboss/modules/test/ClassA.java
new file mode 100644
index 0000000..f56b817
--- /dev/null
+++ b/src/test/java/org/jboss/modules/test/ClassA.java
@@ -0,0 +1,25 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.test;
+
+/**
+ * @author John E. Bailey
+ */
+public class ClassA extends ClassB {
+}
diff --git a/src/test/java/org/jboss/modules/test/ClassB.java b/src/test/java/org/jboss/modules/test/ClassB.java
new file mode 100644
index 0000000..6c8e4b7
--- /dev/null
+++ b/src/test/java/org/jboss/modules/test/ClassB.java
@@ -0,0 +1,25 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.test;
+
+/**
+ * @author John E. Bailey
+ */
+public class ClassB {
+}
diff --git a/src/test/java/org/jboss/modules/test/ClassC.java b/src/test/java/org/jboss/modules/test/ClassC.java
new file mode 100644
index 0000000..35599e8
--- /dev/null
+++ b/src/test/java/org/jboss/modules/test/ClassC.java
@@ -0,0 +1,25 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.test;
+
+/**
+ * @author John E. Bailey
+ */
+public class ClassC extends ClassD {
+}
diff --git a/src/test/java/org/jboss/modules/test/ClassD.java b/src/test/java/org/jboss/modules/test/ClassD.java
new file mode 100644
index 0000000..5174687
--- /dev/null
+++ b/src/test/java/org/jboss/modules/test/ClassD.java
@@ -0,0 +1,25 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.test;
+
+/**
+ * @author John E. Bailey
+ */
+public class ClassD {
+}
diff --git a/src/test/java/org/jboss/modules/test/ImportedClass.java b/src/test/java/org/jboss/modules/test/ImportedClass.java
new file mode 100644
index 0000000..323b71b
--- /dev/null
+++ b/src/test/java/org/jboss/modules/test/ImportedClass.java
@@ -0,0 +1,27 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.test;
+
+/**
+ * ImportedClass -
+ *
+ * @author John Bailey
+ */
+public class ImportedClass {
+}
diff --git a/src/test/java/org/jboss/modules/test/ImportedInterface.java b/src/test/java/org/jboss/modules/test/ImportedInterface.java
new file mode 100644
index 0000000..d33eed2
--- /dev/null
+++ b/src/test/java/org/jboss/modules/test/ImportedInterface.java
@@ -0,0 +1,27 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.test;
+
+/**
+ * ImportedInterface -
+ *
+ * @author <a href="mailto:flavia.rainone at jboss.com">Flavia Rainone</a>
+ */
+public class ImportedInterface {
+}
diff --git a/src/test/java/org/jboss/modules/test/JAXPCaller.java b/src/test/java/org/jboss/modules/test/JAXPCaller.java
new file mode 100644
index 0000000..5e0e49d
--- /dev/null
+++ b/src/test/java/org/jboss/modules/test/JAXPCaller.java
@@ -0,0 +1,178 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.test;
+
+import javax.xml.XMLConstants;
+import javax.xml.datatype.DatatypeConfigurationException;
+import javax.xml.datatype.DatatypeFactory;
+import javax.xml.datatype.Duration;
+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 javax.xml.stream.XMLEventFactory;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.events.DTD;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.sax.SAXTransformerFactory;
+import javax.xml.transform.sax.TransformerHandler;
+import javax.xml.validation.Schema;
+import javax.xml.validation.SchemaFactory;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathFactory;
+
+import org.w3c.dom.Document;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.XMLReaderFactory;
+
+public class JAXPCaller {
+
+    public Document document() {
+        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+        try {
+            return factory.newDocumentBuilder().newDocument();
+        } catch (ParserConfigurationException e) {
+            throw new IllegalStateException();
+        }
+    }
+
+    public DocumentBuilderFactory documentFactory() {
+        return DocumentBuilderFactory.newInstance();
+    }
+
+    public DocumentBuilder documentBuilder() {
+        try {
+            return DocumentBuilderFactory.newInstance().newDocumentBuilder();
+        } catch (ParserConfigurationException e) {
+            throw new IllegalStateException();
+        }
+    }
+
+    public SchemaFactory schemaFactory() {
+        return SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+    }
+
+    public Schema schema() {
+        try {
+            return SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI).newSchema();
+        } catch (SAXException e) {
+            throw new IllegalStateException();
+        }
+    }
+
+    public XMLReader xmlReader() {
+        try {
+            return XMLReaderFactory.createXMLReader();
+        } catch (SAXException e) {
+            throw new IllegalStateException();
+        }
+    }
+
+    public SAXParserFactory saxParserFactory() {
+        return SAXParserFactory.newInstance();
+    }
+
+    public SAXParser saxParser() {
+        try {
+            return SAXParserFactory.newInstance().newSAXParser();
+        } catch (Exception e) {
+            throw new IllegalStateException();
+        }
+    }
+
+    public XPathFactory xpathFactory() {
+        return XPathFactory.newInstance();
+    }
+
+    public XPath xpath() {
+        try {
+            return XPathFactory.newInstance().newXPath();
+        } catch (Exception e) {
+            throw new IllegalStateException();
+        }
+    }
+
+    public TransformerFactory transformerFactory() {
+        return TransformerFactory.newInstance();
+    }
+
+    public Transformer transformer() {
+        try {
+            return transformerFactory().newTransformer();
+        } catch (Exception e) {
+            throw new IllegalStateException();
+        }
+    }
+
+    public TransformerHandler transformerHandler() {
+        try {
+            return ((SAXTransformerFactory)transformerFactory()).newTransformerHandler();
+        } catch (Exception e) {
+            throw new IllegalStateException();
+        }
+    }
+
+    public XMLEventFactory eventFactory() {
+        return XMLEventFactory.newInstance();
+    }
+
+    public DTD eventDTD() {
+        try {
+            return eventFactory().createDTD("blah");
+        } catch (Exception e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    public XMLInputFactory inputFactory() {
+        return XMLInputFactory.newInstance();
+    }
+
+    public String inputProperty() {
+        return String.valueOf(inputFactory().getProperty("javax.xml.stream.reporter"));
+    }
+
+    public XMLOutputFactory outputFactory() {
+        return XMLOutputFactory.newInstance();
+    }
+
+    public String outputProperty() {
+        return String.valueOf(outputFactory().getProperty("javax.xml.stream.isRepairingNamespaces"));
+    }
+
+    public DatatypeFactory datatypeFactory() {
+        try {
+            return DatatypeFactory.newInstance();
+        } catch (DatatypeConfigurationException e) {
+            throw new IllegalStateException();
+        }
+    }
+
+    public Duration duration() {
+        return datatypeFactory().newDuration(1);
+    }
+
+    public static void main(String[] args) {
+        // for main test, do nothing
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/org/jboss/modules/test/QuxBar.java b/src/test/java/org/jboss/modules/test/QuxBar.java
new file mode 100644
index 0000000..12e7f9d
--- /dev/null
+++ b/src/test/java/org/jboss/modules/test/QuxBar.java
@@ -0,0 +1,21 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jboss.modules.test;
+
+public class QuxBar {
+}
diff --git a/src/test/java/org/jboss/modules/test/QuxFoo.java b/src/test/java/org/jboss/modules/test/QuxFoo.java
new file mode 100644
index 0000000..7e2bf37
--- /dev/null
+++ b/src/test/java/org/jboss/modules/test/QuxFoo.java
@@ -0,0 +1,22 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jboss.modules.test;
+
+public class QuxFoo {
+
+}
diff --git a/src/test/java/org/jboss/modules/test/QuxImpl.java b/src/test/java/org/jboss/modules/test/QuxImpl.java
new file mode 100644
index 0000000..9d4e6f5
--- /dev/null
+++ b/src/test/java/org/jboss/modules/test/QuxImpl.java
@@ -0,0 +1,22 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jboss.modules.test;
+
+public class QuxImpl {
+
+}
diff --git a/src/test/java/org/jboss/modules/test/TestClass.java b/src/test/java/org/jboss/modules/test/TestClass.java
new file mode 100644
index 0000000..6d1913e
--- /dev/null
+++ b/src/test/java/org/jboss/modules/test/TestClass.java
@@ -0,0 +1,26 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.test;
+
+/**
+ * @author John Bailey
+ */
+public class TestClass {
+    
+}
diff --git a/src/test/java/org/jboss/modules/util/ModulesTestBase.java b/src/test/java/org/jboss/modules/util/ModulesTestBase.java
new file mode 100644
index 0000000..3d893a6
--- /dev/null
+++ b/src/test/java/org/jboss/modules/util/ModulesTestBase.java
@@ -0,0 +1,158 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jboss.modules.util;
+
+import org.jboss.modules.DependencySpec;
+import org.jboss.modules.Module;
+import org.jboss.modules.ModuleClassLoader;
+import org.jboss.modules.ModuleIdentifier;
+import org.jboss.modules.ModuleLoadException;
+import org.jboss.modules.ModuleLoader;
+import org.jboss.modules.ModuleSpec;
+import org.jboss.modules.filter.PathFilter;
+import org.jboss.modules.filter.PathFilters;
+import org.junit.Before;
+
+import java.net.URL;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+/**
+ * Test low level modules use cases.
+ * 
+ * @author Thomas.Diesler at jboss.com
+ * @since 15-Sep-2010
+ */
+public abstract class ModulesTestBase {
+
+    private ModuleLoaderSupport moduleLoader;
+
+    @Before
+    public void setUp() throws Exception {
+        moduleLoader = new ModuleLoaderSupport("default");
+    }
+
+    protected ModuleLoaderSupport getModuleLoader() {
+        return moduleLoader;
+    }
+
+    protected void addModuleSpec(ModuleSpec moduleSpec) {
+        moduleLoader.addModuleSpec(moduleSpec);
+    }
+
+    protected Module loadModule(ModuleIdentifier identifier) throws ModuleLoadException {
+        return moduleLoader.loadModule(identifier);
+    }
+
+    protected PathFilter getPathFilter(Class<?>... classes) {
+        Set<String> paths = getFilterPaths(classes);
+        return PathFilters.in(paths);
+    }
+
+    protected Set<String> getFilterPaths(Class<?>... classes) {
+        Set<String> paths = new HashSet<String>();
+        for (Class<?> clazz : classes) {
+            paths.add(getPathForClassName(clazz.getName()));
+        }
+        return Collections.unmodifiableSet(paths);
+    }
+
+    protected String getPathForClassName(String className) {
+        className = className.substring(0, className.lastIndexOf('.'));
+        className = className.replace('.', '/');
+        return className;
+    }
+
+    protected void assertLoadClass(ModuleIdentifier identifier, String className) throws Exception {
+        Class<?> clazz = loadClass(identifier, className);
+        assertNotNull(clazz);
+    }
+
+    protected void assertLoadClass(ModuleIdentifier identifier, String className, ModuleIdentifier exporterId) throws Exception {
+        Class<?> clazz = loadClass(identifier, className);
+        ClassLoader wasClassLoader = clazz.getClassLoader();
+        if (exporterId == null && wasClassLoader == null) {
+            return;
+        }
+        ModuleClassLoader expClassLoader = loadModule(exporterId).getClassLoader();
+        assertEquals(expClassLoader, wasClassLoader);
+    }
+
+    protected void assertLoadClassFail(ModuleIdentifier identifier, String className) throws Exception {
+        try {
+            Class<?> clazz = loadClass(identifier, className);
+            assertNotNull("ClassNotFoundException expected for [" + className + "], but was: " + clazz, clazz);
+            fail("ClassNotFoundException expected for [" + className + "], but was loaded from: " + clazz.getClassLoader());
+        } catch (ClassNotFoundException ex) {
+            // expected
+        } catch (NoClassDefFoundError ex) {
+            // expected
+        }
+    }
+
+    protected Class<?> loadClass(ModuleIdentifier identifier, String className) throws Exception {
+        // ClassLoader#resolveClass() only links the class; it doesn't necessarily force it to be initialized.
+        // To initialize the class you can do Class.forName(name, true, classLoader)
+        ModuleClassLoader classLoader = loadModule(identifier).getClassLoader();
+        Class<?> clazz = Class.forName(className, true, classLoader);
+        return clazz;
+    }
+
+    protected URL getResource(ModuleIdentifier identifier, String resourcePath) throws Exception {
+        ModuleClassLoader classLoader = loadModule(identifier).getClassLoader();
+        return classLoader.getResource(resourcePath);
+    }
+
+    static class ModuleLoaderSupport extends ModuleLoader {
+
+        private String loaderName;
+        private Map<ModuleIdentifier, ModuleSpec> modules = new HashMap<ModuleIdentifier, ModuleSpec>();
+
+        ModuleLoaderSupport(String loaderName) {
+            this.loaderName = loaderName;
+        }
+
+        void addModuleSpec(ModuleSpec moduleSpec) {
+            modules.put(moduleSpec.getModuleIdentifier(), moduleSpec);
+        }
+
+        @Override
+        protected ModuleSpec findModule(ModuleIdentifier identifier) throws ModuleLoadException {
+            ModuleSpec moduleSpec = modules.get(identifier);
+            return moduleSpec;
+        }
+
+        @Override
+        protected void setAndRelinkDependencies(Module module, List<DependencySpec> dependencies) throws ModuleLoadException {
+            super.setAndRelinkDependencies(module, dependencies);
+        }
+
+        @Override
+        public String toString() {
+            return "ModuleLoaderSupport[" + loaderName + "]";
+        }
+    }
+}
diff --git a/src/test/java/org/jboss/modules/util/TestModuleLoader.java b/src/test/java/org/jboss/modules/util/TestModuleLoader.java
new file mode 100644
index 0000000..59f0041
--- /dev/null
+++ b/src/test/java/org/jboss/modules/util/TestModuleLoader.java
@@ -0,0 +1,58 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.util;
+
+import org.jboss.modules.Module;
+import org.jboss.modules.ModuleIdentifier;
+import org.jboss.modules.ModuleLoadException;
+import org.jboss.modules.ModuleLoader;
+import org.jboss.modules.ModuleSpec;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Test module loader that allows for modules specs to be added at runtime and it will only load modules from the
+ * provided specs.
+ *
+ * @author John E. Bailey
+ */
+public class TestModuleLoader extends ModuleLoader {
+
+    private final Map<ModuleIdentifier, ModuleSpec> moduleSpecs = new HashMap<ModuleIdentifier, ModuleSpec>();
+
+    protected Module preloadModule(final ModuleIdentifier identifier) throws ModuleLoadException {
+        return super.preloadModule(identifier);
+    }
+
+    @Override
+    protected ModuleSpec findModule(ModuleIdentifier moduleIdentifier) throws ModuleLoadException {
+        final ModuleSpec moduleSpec = moduleSpecs.get(moduleIdentifier);
+        if(moduleSpec == null) throw new ModuleLoadException("No module spec found for module " + moduleIdentifier);
+        return moduleSpec;
+    }
+
+    public void addModuleSpec(final ModuleSpec moduleSpec) {
+        moduleSpecs.put(moduleSpec.getModuleIdentifier(), moduleSpec);
+    }
+
+    public String toString() {
+        return "test@" + System.identityHashCode(this);
+    }
+}
diff --git a/src/test/java/org/jboss/modules/util/TestResourceLoader.java b/src/test/java/org/jboss/modules/util/TestResourceLoader.java
new file mode 100644
index 0000000..6603645
--- /dev/null
+++ b/src/test/java/org/jboss/modules/util/TestResourceLoader.java
@@ -0,0 +1,251 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.util;
+
+import org.jboss.modules.AbstractResourceLoader;
+import org.jboss.modules.ClassSpec;
+import org.jboss.modules.PackageSpec;
+import org.jboss.modules.Resource;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+
+import static junit.framework.Assert.assertTrue;
+import static org.jboss.modules.util.Util.getClassBytes;
+
+/**
+ * A test resource loader that simple retrieves resources frm maps.  This allows tests to build
+ * arbitrary modules with arbitrary content without having to have the module on disk.
+ *
+ * @author John E. Bailey
+ */
+public class TestResourceLoader extends AbstractResourceLoader {
+    private final Map<String, ClassSpec> classSpecs = new HashMap<String, ClassSpec>();
+    private final Map<String, Resource> resources = new HashMap<String, Resource>();
+    private final Set<String> paths = new HashSet<String>();
+    private Manifest manifest;
+
+    public String getRootName() {
+        return "test";
+    }
+
+    @Override
+    public ClassSpec getClassSpec(final String fileName) throws IOException {
+        final Map<String, ClassSpec> classSpecs = this.classSpecs;
+        return classSpecs.get(fileName);
+    }
+
+    void addClassSpec(final String name, final ClassSpec classSpec) {
+        final Map<String, ClassSpec> classSpecs = this.classSpecs;
+        classSpecs.put(name.replace('.', '/') + ".class", classSpec);
+        addPaths(getPathFromClassName(name));
+    }
+
+    @Override
+    public PackageSpec getPackageSpec(final String name) throws IOException {
+        return getPackageSpec(name, getManifest(), null);
+    }
+
+    private Manifest getManifest() throws IOException {
+        if(manifest != null)
+            return manifest;
+
+        final Resource manifestResource = getResource("META-INF/MANIFEST.MF");
+        if(manifestResource  == null)
+            return null;
+        final InputStream manifestStream = manifestResource.openStream();
+        try {
+            manifest = new Manifest(manifestStream);
+        } finally {
+            if(manifestStream != null) manifestStream.close();
+        }
+        return manifest;
+    }
+
+    private static String getDefinedAttribute(Attributes.Name name, Attributes entryAttribute, Attributes mainAttribute) {
+        final String value = entryAttribute == null ? null : entryAttribute.getValue(name);
+        return value == null ? mainAttribute == null ? null : mainAttribute.getValue(name) : value;
+    }
+
+    @Override
+    public Resource getResource(final String name) {
+        String resourceName = name;
+        if (resourceName.startsWith("/"))
+            resourceName = resourceName.substring(1);
+        final Map<String, Resource> resources = this.resources;
+        return resources.get(resourceName);
+    }
+
+    void addResource(final String name, final Resource resource) {
+        final Map<String, Resource> resources = this.resources;
+        resources.put(name, resource);
+        addPaths(getPathFromResourceName(name));
+    }
+
+    private void addPaths(String path) {
+        final String[] parts = path.split("/");
+        String current = "";
+        for(String part : parts) {
+            current += part;
+            paths.add(current);
+            current += "/";
+        }
+    }
+
+    @Override
+    public String getLibrary(String name) {
+        return null;
+    }
+
+    @Override
+    public Collection<String> getPaths() {
+        return paths;
+    }
+
+    private String getPathFromResourceName(final String resourcePath) {
+        int idx = resourcePath.lastIndexOf('/');
+        final String path = idx > -1 ? resourcePath.substring(0, idx) : "";
+        return path;
+    }
+
+    private String getPathFromClassName(final String className) {
+        int idx = className.lastIndexOf('.');
+        return idx > -1 ? className.substring(0, idx).replace('.', '/') : "";
+    }
+
+    public static TestResourceLoaderBuilder build() {
+        return new TestResourceLoaderBuilder();
+    }
+
+    public static class TestResourceLoaderBuilder {
+        private final TestResourceLoader resourceLoader = new TestResourceLoader();
+
+        public TestResourceLoader create() {
+            return resourceLoader;
+        }
+
+        public TestResourceLoaderBuilder addResource(final String name, final URL resourceUrl) {
+            addResource(name, new Resource() {
+                @Override
+                public String getName() {
+                    return name;
+                }
+
+                @Override
+                public URL getURL() {
+                    return resourceUrl;
+                }
+
+                @Override
+                public InputStream openStream() throws IOException {
+                    return resourceUrl.openStream();
+                }
+
+                @Override
+                public long getSize() {
+                    return 0L;
+                }
+            });
+            return this;
+        }
+
+        public TestResourceLoaderBuilder addResource(final String name, final File resource) throws MalformedURLException {
+            final URL url = resource.toURI().toURL();
+            addResource(name, new Resource() {
+                @Override
+                public String getName() {
+                    return name;
+                }
+
+                @Override
+                public URL getURL() {
+                    return url;
+                }
+
+                @Override
+                public InputStream openStream() throws IOException {
+                    return new FileInputStream(resource);
+                }
+
+                @Override
+                public long getSize() {
+                    return resource.length();
+                }
+            });
+            return this;
+        }
+
+        public TestResourceLoaderBuilder addResource(final String name, final Resource resource) {
+            final TestResourceLoader resourceLoader = this.resourceLoader;
+            resourceLoader.addResource(name, resource);
+            return this;
+        }
+
+        public TestResourceLoaderBuilder addResources(final File base) throws Exception {
+            addResources("", base);
+            return this;
+        }
+
+        private void addResources(final String pathBase, final File base) throws Exception {
+            assertTrue(base.isDirectory());
+            final File[] children = base.listFiles();
+            for (File child : children) {
+                final String childPath = pathBase + child.getName();
+                if (child.isDirectory()) {
+                    addResources(childPath + "/", child);
+                } else {
+                    addResource(childPath, child);
+                }
+            }
+        }
+
+        public TestResourceLoaderBuilder addClass(final Class<?> aClass) throws Exception {
+            final ClassSpec classSpec = new ClassSpec();
+            classSpec.setCodeSource(aClass.getProtectionDomain().getCodeSource());
+            final byte[] classBytes = getClassBytes(aClass);
+            classSpec.setBytes(classBytes);
+            addClassSpec(aClass.getName(), classSpec);
+            return this;
+        }
+
+        public TestResourceLoaderBuilder addClasses(final Class<?>... classes) throws Exception {
+            for(Class<?> aClass : classes) {
+                addClass(aClass);
+            }
+            return this;
+        }
+
+        public TestResourceLoaderBuilder addClassSpec(final String name, final ClassSpec classSpec) {
+            final TestResourceLoader resourceLoader = this.resourceLoader;
+            resourceLoader.addClassSpec(name, classSpec);
+            return this;
+        }
+    }
+}
diff --git a/src/test/java/org/jboss/modules/util/Util.java b/src/test/java/org/jboss/modules/util/Util.java
new file mode 100644
index 0000000..9fa4e9e
--- /dev/null
+++ b/src/test/java/org/jboss/modules/util/Util.java
@@ -0,0 +1,82 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.modules.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+
+/**
+ * Utility class providing commonly used test utilities.
+ *
+ * @author John E. Bailey
+ */
+public class Util {
+
+    public static byte[] readBytes(final InputStream is) throws IOException {
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+        try {
+            byte[] buff = new byte[1024];
+            int read;
+            while((read = is.read(buff)) > -1) {
+                os.write(buff, 0, read);
+            }
+        } finally {
+            is.close();
+        }
+        return os.toByteArray();
+    }
+
+    public static URL getResource(final Class<?> baseClass, final String path) throws Exception {
+        final URL url = baseClass.getClassLoader().getResource(path);
+        return url;
+    }
+
+    public static File getResourceFile(final Class<?> baseClass, final String path) throws Exception {
+        return new File(getResource(baseClass, path).toURI());
+    }
+
+    public static <T> List<T> toList(Enumeration<T> enumeration) {
+        final List<T> list = new ArrayList<T>();
+        while(enumeration.hasMoreElements()) {
+            list.add(enumeration.nextElement());
+        }
+        return list;
+    }
+
+    public static byte[] getClassBytes(final Class<?> aClass) throws Exception {
+        final String resourcePath = getResourceNameOfClass(aClass);
+        final File classFile = Util.getResourceFile(aClass, resourcePath);
+        byte[] classBytes = Util.readBytes(new FileInputStream(classFile));
+        return classBytes;
+    }
+
+    public static String getResourceNameOfClass(final Class<?> aClass) throws IllegalArgumentException {
+        final String nameAsResourcePath = aClass.getName().replace('.', '/');
+        final String resourceName = nameAsResourcePath + ".class";
+        return resourceName;
+    }
+
+}
diff --git a/src/test/resources/class-resources/file1.txt b/src/test/resources/class-resources/file1.txt
new file mode 100644
index 0000000..e69de29
diff --git a/src/test/resources/class-resources/org/jboss/modules/test/file2.txt b/src/test/resources/class-resources/org/jboss/modules/test/file2.txt
new file mode 100644
index 0000000..e69de29
diff --git a/src/test/resources/test/fileresourceloader/META-INF/MANIFEST.MF b/src/test/resources/test/fileresourceloader/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..9ed9c9e
--- /dev/null
+++ b/src/test/resources/test/fileresourceloader/META-INF/MANIFEST.MF
@@ -0,0 +1,10 @@
+Manifest-Version: 1.0
+Specification-Title: MODULES-89
+
+Name: org/jboss/modules/test/
+Specification-Title: JBoss Modules Test Classes
+Specification-Version: 0.1
+Specification-Vendor: JBoss
+Implementation-Title: org.jboss.modules.test
+Implementation-Version: 1.0
+Implementation-Vendor: JBoss
diff --git a/src/test/resources/test/fileresourceloader/nested/nested.txt b/src/test/resources/test/fileresourceloader/nested/nested.txt
new file mode 100644
index 0000000..8f1c828
--- /dev/null
+++ b/src/test/resources/test/fileresourceloader/nested/nested.txt
@@ -0,0 +1 @@
+nested test file
\ No newline at end of file
diff --git a/src/test/resources/test/fileresourceloader/test.txt b/src/test/resources/test/fileresourceloader/test.txt
new file mode 100644
index 0000000..bdf08de
--- /dev/null
+++ b/src/test/resources/test/fileresourceloader/test.txt
@@ -0,0 +1 @@
+test file
\ No newline at end of file
diff --git a/src/test/resources/test/layeredmodulepath/add-ons/a/shared/module.xml b/src/test/resources/test/layeredmodulepath/add-ons/a/shared/module.xml
new file mode 100644
index 0000000..113491e
--- /dev/null
+++ b/src/test/resources/test/layeredmodulepath/add-ons/a/shared/module.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  ~ JBoss, Home of Professional Open Source.
+  ~ Copyright 2014 Red Hat, Inc., and individual contributors
+  ~ as indicated by the @author tags.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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 xmlns="urn:jboss:module:1.1" name="test.shared">
+    <properties>
+        <property name="test.prop" value="a"/>
+    </properties>
+
+    <resources/>
+
+    <dependencies/>
+</module>
diff --git a/src/test/resources/test/layeredmodulepath/add-ons/a/unique/module.xml b/src/test/resources/test/layeredmodulepath/add-ons/a/unique/module.xml
new file mode 100644
index 0000000..b7c41e5
--- /dev/null
+++ b/src/test/resources/test/layeredmodulepath/add-ons/a/unique/module.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  ~ JBoss, Home of Professional Open Source.
+  ~ Copyright 2014 Red Hat, Inc., and individual contributors
+  ~ as indicated by the @author tags.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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 xmlns="urn:jboss:module:1.1" name="test.a">
+    <properties>
+        <property name="test.prop" value="a"/>
+    </properties>
+
+    <resources/>
+
+    <dependencies/>
+</module>
diff --git a/src/test/resources/test/layeredmodulepath/add-ons/b/shared/module.xml b/src/test/resources/test/layeredmodulepath/add-ons/b/shared/module.xml
new file mode 100644
index 0000000..662b8bf
--- /dev/null
+++ b/src/test/resources/test/layeredmodulepath/add-ons/b/shared/module.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  ~ JBoss, Home of Professional Open Source.
+  ~ Copyright 2014 Red Hat, Inc., and individual contributors
+  ~ as indicated by the @author tags.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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 xmlns="urn:jboss:module:1.1" name="test.shared">
+    <properties>
+        <property name="test.prop" value="b"/>
+    </properties>
+
+    <resources/>
+
+    <dependencies/>
+</module>
diff --git a/src/test/resources/test/layeredmodulepath/add-ons/b/unique/module.xml b/src/test/resources/test/layeredmodulepath/add-ons/b/unique/module.xml
new file mode 100644
index 0000000..982140d
--- /dev/null
+++ b/src/test/resources/test/layeredmodulepath/add-ons/b/unique/module.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  ~ JBoss, Home of Professional Open Source.
+  ~ Copyright 2014 Red Hat, Inc., and individual contributors
+  ~ as indicated by the @author tags.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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 xmlns="urn:jboss:module:1.1" name="test.b">
+    <properties>
+        <property name="test.prop" value="b"/>
+    </properties>
+
+    <resources/>
+
+    <dependencies/>
+</module>
diff --git a/src/test/resources/test/layeredmodulepath/layers/base/shared/module.xml b/src/test/resources/test/layeredmodulepath/layers/base/shared/module.xml
new file mode 100644
index 0000000..80719bf
--- /dev/null
+++ b/src/test/resources/test/layeredmodulepath/layers/base/shared/module.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  ~ JBoss, Home of Professional Open Source.
+  ~ Copyright 2014 Red Hat, Inc., and individual contributors
+  ~ as indicated by the @author tags.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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 xmlns="urn:jboss:module:1.1" name="test.shared">
+    <properties>
+        <property name="test.prop" value="base"/>
+    </properties>
+
+    <resources/>
+
+    <dependencies/>
+</module>
diff --git a/src/test/resources/test/layeredmodulepath/layers/base/unique/module.xml b/src/test/resources/test/layeredmodulepath/layers/base/unique/module.xml
new file mode 100644
index 0000000..2726122
--- /dev/null
+++ b/src/test/resources/test/layeredmodulepath/layers/base/unique/module.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  ~ JBoss, Home of Professional Open Source.
+  ~ Copyright 2014 Red Hat, Inc., and individual contributors
+  ~ as indicated by the @author tags.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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 xmlns="urn:jboss:module:1.1" name="test.base">
+    <properties>
+        <property name="test.prop" value="base"/>
+    </properties>
+
+    <resources/>
+
+    <dependencies/>
+</module>
diff --git a/src/test/resources/test/layeredmodulepath/layers/mid/shared/module.xml b/src/test/resources/test/layeredmodulepath/layers/mid/shared/module.xml
new file mode 100644
index 0000000..be28808
--- /dev/null
+++ b/src/test/resources/test/layeredmodulepath/layers/mid/shared/module.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  ~ JBoss, Home of Professional Open Source.
+  ~ Copyright 2014 Red Hat, Inc., and individual contributors
+  ~ as indicated by the @author tags.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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 xmlns="urn:jboss:module:1.1" name="test.shared">
+    <properties>
+        <property name="test.prop" value="mid"/>
+    </properties>
+
+    <resources/>
+
+    <dependencies/>
+</module>
diff --git a/src/test/resources/test/layeredmodulepath/layers/mid/unique/module.xml b/src/test/resources/test/layeredmodulepath/layers/mid/unique/module.xml
new file mode 100644
index 0000000..709a70f
--- /dev/null
+++ b/src/test/resources/test/layeredmodulepath/layers/mid/unique/module.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  ~ JBoss, Home of Professional Open Source.
+  ~ Copyright 2014 Red Hat, Inc., and individual contributors
+  ~ as indicated by the @author tags.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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 xmlns="urn:jboss:module:1.1" name="test.mid">
+    <properties>
+        <property name="test.prop" value="mid"/>
+    </properties>
+
+    <resources/>
+
+    <dependencies/>
+</module>
diff --git a/src/test/resources/test/layeredmodulepath/layers/top/shared/module.xml b/src/test/resources/test/layeredmodulepath/layers/top/shared/module.xml
new file mode 100644
index 0000000..25caef6
--- /dev/null
+++ b/src/test/resources/test/layeredmodulepath/layers/top/shared/module.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  ~ JBoss, Home of Professional Open Source.
+  ~ Copyright 2014 Red Hat, Inc., and individual contributors
+  ~ as indicated by the @author tags.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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 xmlns="urn:jboss:module:1.1" name="test.shared">
+    <properties>
+        <property name="test.prop" value="top"/>
+    </properties>
+
+    <resources/>
+
+    <dependencies/>
+</module>
diff --git a/src/test/resources/test/layeredmodulepath/layers/top/unique/module.xml b/src/test/resources/test/layeredmodulepath/layers/top/unique/module.xml
new file mode 100644
index 0000000..9a6ebb5
--- /dev/null
+++ b/src/test/resources/test/layeredmodulepath/layers/top/unique/module.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  ~ JBoss, Home of Professional Open Source.
+  ~ Copyright 2014 Red Hat, Inc., and individual contributors
+  ~ as indicated by the @author tags.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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 xmlns="urn:jboss:module:1.1" name="test.top">
+    <properties>
+        <property name="test.prop" value="top"/>
+    </properties>
+
+    <resources/>
+
+    <dependencies/>
+</module>
diff --git a/src/test/resources/test/layeredmodulepath/user/shared/module.xml b/src/test/resources/test/layeredmodulepath/user/shared/module.xml
new file mode 100644
index 0000000..70372f0
--- /dev/null
+++ b/src/test/resources/test/layeredmodulepath/user/shared/module.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  ~ JBoss, Home of Professional Open Source.
+  ~ Copyright 2014 Red Hat, Inc., and individual contributors
+  ~ as indicated by the @author tags.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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 xmlns="urn:jboss:module:1.1" name="test.shared">
+    <properties>
+        <property name="test.prop" value="user"/>
+    </properties>
+
+    <resources/>
+
+    <dependencies/>
+</module>
diff --git a/src/test/resources/test/layeredmodulepath/user/unique/module.xml b/src/test/resources/test/layeredmodulepath/user/unique/module.xml
new file mode 100644
index 0000000..36926c2
--- /dev/null
+++ b/src/test/resources/test/layeredmodulepath/user/unique/module.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  ~ JBoss, Home of Professional Open Source.
+  ~ Copyright 2014 Red Hat, Inc., and individual contributors
+  ~ as indicated by the @author tags.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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 xmlns="urn:jboss:module:1.1" name="test.user">
+    <properties>
+        <property name="test.prop" value="user"/>
+    </properties>
+
+    <resources/>
+
+    <dependencies/>
+</module>
diff --git a/src/test/resources/test/modulecontentloader/jaxp/META-INF/services/javax.xml.datatype.DatatypeFactory b/src/test/resources/test/modulecontentloader/jaxp/META-INF/services/javax.xml.datatype.DatatypeFactory
new file mode 100644
index 0000000..c022039
--- /dev/null
+++ b/src/test/resources/test/modulecontentloader/jaxp/META-INF/services/javax.xml.datatype.DatatypeFactory
@@ -0,0 +1,19 @@
+#
+# JBoss, Home of Professional Open Source.
+# Copyright 2014 Red Hat, Inc., and individual contributors
+# as indicated by the @author tags.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+org.jboss.modules.JAXPModuleTest$FakeDatatypeFactory
diff --git a/src/test/resources/test/modulecontentloader/jaxp/META-INF/services/javax.xml.parsers.DocumentBuilderFactory b/src/test/resources/test/modulecontentloader/jaxp/META-INF/services/javax.xml.parsers.DocumentBuilderFactory
new file mode 100644
index 0000000..f52204f
--- /dev/null
+++ b/src/test/resources/test/modulecontentloader/jaxp/META-INF/services/javax.xml.parsers.DocumentBuilderFactory
@@ -0,0 +1,19 @@
+#
+# JBoss, Home of Professional Open Source.
+# Copyright 2014 Red Hat, Inc., and individual contributors
+# as indicated by the @author tags.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+org.jboss.modules.JAXPModuleTest$FakeDocumentBuilderFactory
diff --git a/src/test/resources/test/modulecontentloader/jaxp/META-INF/services/javax.xml.parsers.SAXParserFactory b/src/test/resources/test/modulecontentloader/jaxp/META-INF/services/javax.xml.parsers.SAXParserFactory
new file mode 100644
index 0000000..849a81f
--- /dev/null
+++ b/src/test/resources/test/modulecontentloader/jaxp/META-INF/services/javax.xml.parsers.SAXParserFactory
@@ -0,0 +1,19 @@
+#
+# JBoss, Home of Professional Open Source.
+# Copyright 2014 Red Hat, Inc., and individual contributors
+# as indicated by the @author tags.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+org.jboss.modules.JAXPModuleTest$FakeSAXParserFactory
diff --git a/src/test/resources/test/modulecontentloader/jaxp/META-INF/services/javax.xml.stream.XMLEventFactory b/src/test/resources/test/modulecontentloader/jaxp/META-INF/services/javax.xml.stream.XMLEventFactory
new file mode 100644
index 0000000..41df9bb
--- /dev/null
+++ b/src/test/resources/test/modulecontentloader/jaxp/META-INF/services/javax.xml.stream.XMLEventFactory
@@ -0,0 +1,19 @@
+#
+# JBoss, Home of Professional Open Source.
+# Copyright 2014 Red Hat, Inc., and individual contributors
+# as indicated by the @author tags.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+org.jboss.modules.JAXPModuleTest$FakeXMLEventFactory
diff --git a/src/test/resources/test/modulecontentloader/jaxp/META-INF/services/javax.xml.stream.XMLInputFactory b/src/test/resources/test/modulecontentloader/jaxp/META-INF/services/javax.xml.stream.XMLInputFactory
new file mode 100644
index 0000000..3d78d42
--- /dev/null
+++ b/src/test/resources/test/modulecontentloader/jaxp/META-INF/services/javax.xml.stream.XMLInputFactory
@@ -0,0 +1,19 @@
+#
+# JBoss, Home of Professional Open Source.
+# Copyright 2014 Red Hat, Inc., and individual contributors
+# as indicated by the @author tags.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+org.jboss.modules.JAXPModuleTest$FakeXMLInputFactory
diff --git a/src/test/resources/test/modulecontentloader/jaxp/META-INF/services/javax.xml.stream.XMLOutputFactory b/src/test/resources/test/modulecontentloader/jaxp/META-INF/services/javax.xml.stream.XMLOutputFactory
new file mode 100644
index 0000000..da47fcf
--- /dev/null
+++ b/src/test/resources/test/modulecontentloader/jaxp/META-INF/services/javax.xml.stream.XMLOutputFactory
@@ -0,0 +1,19 @@
+#
+# JBoss, Home of Professional Open Source.
+# Copyright 2014 Red Hat, Inc., and individual contributors
+# as indicated by the @author tags.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+org.jboss.modules.JAXPModuleTest$FakeXMLOutputFactory
diff --git a/src/test/resources/test/modulecontentloader/jaxp/META-INF/services/javax.xml.transform.TransformerFactory b/src/test/resources/test/modulecontentloader/jaxp/META-INF/services/javax.xml.transform.TransformerFactory
new file mode 100644
index 0000000..454a1ee
--- /dev/null
+++ b/src/test/resources/test/modulecontentloader/jaxp/META-INF/services/javax.xml.transform.TransformerFactory
@@ -0,0 +1,19 @@
+#
+# JBoss, Home of Professional Open Source.
+# Copyright 2014 Red Hat, Inc., and individual contributors
+# as indicated by the @author tags.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+org.jboss.modules.JAXPModuleTest$FakeTransformerFactory
diff --git a/src/test/resources/test/modulecontentloader/jaxp/META-INF/services/javax.xml.validation.SchemaFactory b/src/test/resources/test/modulecontentloader/jaxp/META-INF/services/javax.xml.validation.SchemaFactory
new file mode 100644
index 0000000..52bd39c
--- /dev/null
+++ b/src/test/resources/test/modulecontentloader/jaxp/META-INF/services/javax.xml.validation.SchemaFactory
@@ -0,0 +1,19 @@
+#
+# JBoss, Home of Professional Open Source.
+# Copyright 2014 Red Hat, Inc., and individual contributors
+# as indicated by the @author tags.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+org.jboss.modules.JAXPModuleTest$FakeSchemaFactory
diff --git a/src/test/resources/test/modulecontentloader/jaxp/META-INF/services/javax.xml.xpath.XPathFactory b/src/test/resources/test/modulecontentloader/jaxp/META-INF/services/javax.xml.xpath.XPathFactory
new file mode 100644
index 0000000..6c6e5b9
--- /dev/null
+++ b/src/test/resources/test/modulecontentloader/jaxp/META-INF/services/javax.xml.xpath.XPathFactory
@@ -0,0 +1,19 @@
+#
+# JBoss, Home of Professional Open Source.
+# Copyright 2014 Red Hat, Inc., and individual contributors
+# as indicated by the @author tags.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+org.jboss.modules.JAXPModuleTest$FakeXPathFactory
diff --git a/src/test/resources/test/modulecontentloader/jaxp/META-INF/services/org.xml.sax.driver b/src/test/resources/test/modulecontentloader/jaxp/META-INF/services/org.xml.sax.driver
new file mode 100644
index 0000000..75a258b
--- /dev/null
+++ b/src/test/resources/test/modulecontentloader/jaxp/META-INF/services/org.xml.sax.driver
@@ -0,0 +1,19 @@
+#
+# JBoss, Home of Professional Open Source.
+# Copyright 2014 Red Hat, Inc., and individual contributors
+# as indicated by the @author tags.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+org.jboss.modules.JAXPModuleTest$FakeXMLReader
diff --git a/src/test/resources/test/modulecontentloader/rootOne/META-INF/MANIFEST.MF b/src/test/resources/test/modulecontentloader/rootOne/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..b3384d3
--- /dev/null
+++ b/src/test/resources/test/modulecontentloader/rootOne/META-INF/MANIFEST.MF
@@ -0,0 +1,9 @@
+Manifest-Version: 1.0
+
+Name: org/jboss/modules/test/
+Specification-Title: JBoss Modules Test Classes
+Specification-Version: 0.1
+Specification-Vendor: JBoss
+Implementation-Title: org.jboss.modules.test
+Implementation-Version: 1.0
+Implementation-Vendor: JBoss
diff --git a/src/test/resources/test/modulecontentloader/rootOne/nested/nested.txt b/src/test/resources/test/modulecontentloader/rootOne/nested/nested.txt
new file mode 100644
index 0000000..8f1c828
--- /dev/null
+++ b/src/test/resources/test/modulecontentloader/rootOne/nested/nested.txt
@@ -0,0 +1 @@
+nested test file
\ No newline at end of file
diff --git a/src/test/resources/test/modulecontentloader/rootOne/test.txt b/src/test/resources/test/modulecontentloader/rootOne/test.txt
new file mode 100644
index 0000000..bdf08de
--- /dev/null
+++ b/src/test/resources/test/modulecontentloader/rootOne/test.txt
@@ -0,0 +1 @@
+test file
\ No newline at end of file
diff --git a/src/test/resources/test/modulecontentloader/rootTwo/nested/nested.txt b/src/test/resources/test/modulecontentloader/rootTwo/nested/nested.txt
new file mode 100644
index 0000000..8f1c828
--- /dev/null
+++ b/src/test/resources/test/modulecontentloader/rootTwo/nested/nested.txt
@@ -0,0 +1 @@
+nested test file
\ No newline at end of file
diff --git a/src/test/resources/test/modulecontentloader/rootTwo/nestedTwo/nested.txt b/src/test/resources/test/modulecontentloader/rootTwo/nestedTwo/nested.txt
new file mode 100644
index 0000000..f7c06cc
--- /dev/null
+++ b/src/test/resources/test/modulecontentloader/rootTwo/nestedTwo/nested.txt
@@ -0,0 +1 @@
+nested file two
\ No newline at end of file
diff --git a/src/test/resources/test/modulecontentloader/rootTwo/testTwo.txt b/src/test/resources/test/modulecontentloader/rootTwo/testTwo.txt
new file mode 100644
index 0000000..b193be2
--- /dev/null
+++ b/src/test/resources/test/modulecontentloader/rootTwo/testTwo.txt
@@ -0,0 +1 @@
+test two
\ No newline at end of file
diff --git a/src/test/resources/test/repo/test/bad-deps/main/module.xml b/src/test/resources/test/repo/test/bad-deps/main/module.xml
new file mode 100644
index 0000000..b43eb87
--- /dev/null
+++ b/src/test/resources/test/repo/test/bad-deps/main/module.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  ~ JBoss, Home of Professional Open Source.
+  ~ Copyright 2014 Red Hat, Inc., and individual contributors
+  ~ as indicated by the @author tags.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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 xmlns="urn:jboss:module:1.0" name="test.bad-deps">
+    <resources>
+        <!-- Insert resources here -->
+    </resources>
+
+    <dependencies>
+        <module name="missing"/>
+    </dependencies>
+</module>
diff --git a/src/test/resources/test/repo/test/circular-deps-A/main/module.xml b/src/test/resources/test/repo/test/circular-deps-A/main/module.xml
new file mode 100644
index 0000000..f89864a
--- /dev/null
+++ b/src/test/resources/test/repo/test/circular-deps-A/main/module.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  ~ JBoss, Home of Professional Open Source.
+  ~ Copyright 2014 Red Hat, Inc., and individual contributors
+  ~ as indicated by the @author tags.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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 xmlns="urn:jboss:module:1.0" name="test.circular-deps-A">
+    <resources>
+        <!-- Insert resources here -->
+    </resources>
+
+    <dependencies>
+        <module name="test.circular-deps-B"/>
+        <module name="test.circular-deps-A"/>
+    </dependencies>
+</module>
diff --git a/src/test/resources/test/repo/test/circular-deps-B/main/module.xml b/src/test/resources/test/repo/test/circular-deps-B/main/module.xml
new file mode 100644
index 0000000..fd0f508
--- /dev/null
+++ b/src/test/resources/test/repo/test/circular-deps-B/main/module.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  ~ JBoss, Home of Professional Open Source.
+  ~ Copyright 2014 Red Hat, Inc., and individual contributors
+  ~ as indicated by the @author tags.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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 xmlns="urn:jboss:module:1.0" name="test.circular-deps-B">
+    <resources>
+        <!-- Insert resources here -->
+    </resources>
+
+    <dependencies>
+        <module name="test.circular-deps-C"/>
+    </dependencies>
+</module>
diff --git a/src/test/resources/test/repo/test/circular-deps-C/main/module.xml b/src/test/resources/test/repo/test/circular-deps-C/main/module.xml
new file mode 100644
index 0000000..06b5039
--- /dev/null
+++ b/src/test/resources/test/repo/test/circular-deps-C/main/module.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  ~ JBoss, Home of Professional Open Source.
+  ~ Copyright 2014 Red Hat, Inc., and individual contributors
+  ~ as indicated by the @author tags.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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 xmlns="urn:jboss:module:1.0" name="test.circular-deps-C">
+    <resources>
+        <!-- Insert resources here -->
+    </resources>
+
+    <dependencies>
+        <module name="test.circular-deps-D"/>
+        <module name="test.circular-deps-A"/>
+    </dependencies>
+</module>
diff --git a/src/test/resources/test/repo/test/circular-deps-D/main/module.xml b/src/test/resources/test/repo/test/circular-deps-D/main/module.xml
new file mode 100644
index 0000000..d9ee65d
--- /dev/null
+++ b/src/test/resources/test/repo/test/circular-deps-D/main/module.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  ~ JBoss, Home of Professional Open Source.
+  ~ Copyright 2014 Red Hat, Inc., and individual contributors
+  ~ as indicated by the @author tags.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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 xmlns="urn:jboss:module:1.0" name="test.circular-deps-D">
+    <resources>
+        <!-- Insert resources here -->
+    </resources>
+
+    <dependencies>
+        <module name="test.circular-deps-A"/>
+    </dependencies>
+</module>
diff --git a/src/test/resources/test/repo/test/jaxrs-jaxb-provider/main/META-INF/services/javax.ws.rs.ext.Providers b/src/test/resources/test/repo/test/jaxrs-jaxb-provider/main/META-INF/services/javax.ws.rs.ext.Providers
new file mode 100644
index 0000000..aa08b13
--- /dev/null
+++ b/src/test/resources/test/repo/test/jaxrs-jaxb-provider/main/META-INF/services/javax.ws.rs.ext.Providers
@@ -0,0 +1,20 @@
+#
+# JBoss, Home of Professional Open Source.
+# Copyright 2014 Red Hat, Inc., and individual contributors
+# as indicated by the @author tags.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+jaxrs.jaxb.providers.P0
+jaxrs.jaxb.providers.P1
\ No newline at end of file
diff --git a/src/test/resources/test/repo/test/jaxrs-jaxb-provider/main/module.xml b/src/test/resources/test/repo/test/jaxrs-jaxb-provider/main/module.xml
new file mode 100644
index 0000000..1a93a71
--- /dev/null
+++ b/src/test/resources/test/repo/test/jaxrs-jaxb-provider/main/module.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  ~ JBoss, Home of Professional Open Source.
+  ~ Copyright 2014 Red Hat, Inc., and individual contributors
+  ~ as indicated by the @author tags.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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 xmlns="urn:jboss:module:1.0" name="test.jaxrs-jaxb-provider">
+    <resources>
+        <resource-root path="."/>
+        <!-- Insert resources here -->
+    </resources>
+
+    <dependencies>
+    </dependencies>
+</module>
+
diff --git a/src/test/resources/test/repo/test/jaxrs/main/META-INF/services/javax.ws.rs.ext.Providers b/src/test/resources/test/repo/test/jaxrs/main/META-INF/services/javax.ws.rs.ext.Providers
new file mode 100644
index 0000000..54e6510
--- /dev/null
+++ b/src/test/resources/test/repo/test/jaxrs/main/META-INF/services/javax.ws.rs.ext.Providers
@@ -0,0 +1,20 @@
+#
+# JBoss, Home of Professional Open Source.
+# Copyright 2014 Red Hat, Inc., and individual contributors
+# as indicated by the @author tags.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+jaxrs.core.providers.P0
+jaxrs.core.providers.P1
\ No newline at end of file
diff --git a/src/test/resources/test/repo/test/jaxrs/main/module.xml b/src/test/resources/test/repo/test/jaxrs/main/module.xml
new file mode 100644
index 0000000..c783cba
--- /dev/null
+++ b/src/test/resources/test/repo/test/jaxrs/main/module.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  ~ JBoss, Home of Professional Open Source.
+  ~ Copyright 2014 Red Hat, Inc., and individual contributors
+  ~ as indicated by the @author tags.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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 xmlns="urn:jboss:module:1.0" name="test.jaxrs">
+    <resources>
+        <resource-root path="."/>
+        <!-- Insert resources here -->
+    </resources>
+
+    <dependencies>
+      <module name="test.jaxrs-jaxb-provider" services="export" />
+    </dependencies>
+</module>
+
diff --git a/src/test/resources/test/repo/test/maven/main/module.xml b/src/test/resources/test/repo/test/maven/main/module.xml
new file mode 100755
index 0000000..3018ab8
--- /dev/null
+++ b/src/test/resources/test/repo/test/maven/main/module.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  ~ JBoss, Home of Professional Open Source.
+  ~ Copyright 2014 Red Hat, Inc., and individual contributors
+  ~ as indicated by the @author tags.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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 xmlns="urn:jboss:module:1.3" name="test.maven">
+    <resources>
+        <artifact name="org.jboss.resteasy:resteasy-jackson-provider:3.0.4.Final"/>
+        <artifact name="org.jboss.ws.cxf:jbossws-cxf-resources:4.1.3.Final:jboss711"/>
+    </resources>
+
+    <dependencies/>
+</module>
diff --git a/src/test/resources/test/repo/test/maven/non-main/module.xml b/src/test/resources/test/repo/test/maven/non-main/module.xml
new file mode 100644
index 0000000..14b7312
--- /dev/null
+++ b/src/test/resources/test/repo/test/maven/non-main/module.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  ~ JBoss, Home of Professional Open Source.
+  ~ Copyright 2014 Red Hat, Inc., and individual contributors
+  ~ as indicated by the @author tags.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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 xmlns="urn:jboss:module:1.3" name="test.maven" slot="non-main">
+    <resources>
+        <artifact name="org.jboss.resteasy:resteasy-jackson-provider:3.0.5.Final"/>
+        <artifact name="org.jboss.ws.cxf:jbossws-cxf-resources:4.3.0.Final:wildfly800"/>
+    </resources>
+
+    <dependencies/>
+</module>
diff --git a/src/test/resources/test/repo/test/service/main/META-INF/services/dummy b/src/test/resources/test/repo/test/service/main/META-INF/services/dummy
new file mode 100644
index 0000000..b9002c4
--- /dev/null
+++ b/src/test/resources/test/repo/test/service/main/META-INF/services/dummy
@@ -0,0 +1,19 @@
+#
+# JBoss, Home of Professional Open Source.
+# Copyright 2014 Red Hat, Inc., and individual contributors
+# as indicated by the @author tags.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+dummy
\ No newline at end of file
diff --git a/src/test/resources/test/repo/test/service/main/module.xml b/src/test/resources/test/repo/test/service/main/module.xml
new file mode 100644
index 0000000..fff41cf
--- /dev/null
+++ b/src/test/resources/test/repo/test/service/main/module.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  ~ JBoss, Home of Professional Open Source.
+  ~ Copyright 2014 Red Hat, Inc., and individual contributors
+  ~ as indicated by the @author tags.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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 xmlns="urn:jboss:module:1.0" name="test.service">
+    <resources>
+        <resource-root path="."/>
+        <!-- Insert resources here -->
+    </resources>
+
+    <dependencies>
+    </dependencies>
+</module>
diff --git a/src/test/resources/test/repo/test/test/main/module.xml b/src/test/resources/test/repo/test/test/main/module.xml
new file mode 100644
index 0000000..55e1745
--- /dev/null
+++ b/src/test/resources/test/repo/test/test/main/module.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  ~ JBoss, Home of Professional Open Source.
+  ~ Copyright 2014 Red Hat, Inc., and individual contributors
+  ~ as indicated by the @author tags.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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 xmlns="urn:jboss:module:1.1" name="test.test">
+    <properties>
+        <property name="test.prop.1"/>
+        <property name="test.prop.2" value="propertyValue"/>
+    </properties>
+
+    <resources/>
+
+    <dependencies/>
+</module>
diff --git a/src/test/resources/test/repo/test/with-deps/main/module.xml b/src/test/resources/test/repo/test/with-deps/main/module.xml
new file mode 100644
index 0000000..05ddc63
--- /dev/null
+++ b/src/test/resources/test/repo/test/with-deps/main/module.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  ~ JBoss, Home of Professional Open Source.
+  ~ Copyright 2014 Red Hat, Inc., and individual contributors
+  ~ as indicated by the @author tags.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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 xmlns="urn:jboss:module:1.0" name="test.with-deps">
+    <resources>
+        <!-- Insert resources here -->
+    </resources>
+
+    <dependencies>
+        <module name="test.test"/>
+    </dependencies>
+</module>

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



More information about the pkg-java-commits mailing list