[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 <, ampersnad with &
+ * 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 (&), lt (<), gt (>), quot ("), and 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.  ) and standard entities such as
+ * & < > " ' 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