initial commit
This commit is contained in:
commit
87aa1c7ca1
134 changed files with 17780 additions and 0 deletions
16
.gitignore
vendored
Normal file
16
.gitignore
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
/data
|
||||
/work
|
||||
/logs
|
||||
/.idea
|
||||
/target
|
||||
.DS_Store
|
||||
*.iml
|
||||
/.settings
|
||||
/.classpath
|
||||
/.project
|
||||
/.gradle
|
||||
/build
|
||||
/plugins
|
||||
/sessions
|
||||
*~
|
||||
*.MARC
|
202
LICENSE.txt
Normal file
202
LICENSE.txt
Normal file
|
@ -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.
|
11
README.adoc
Normal file
11
README.adoc
Normal file
|
@ -0,0 +1,11 @@
|
|||
# Network classes for Java
|
||||
|
||||
image:https://api.travis-ci.org/xbib/net.svg[title="Build status", link="https://travis-ci.org/xbib/net/"]
|
||||
image:https://img.shields.io/sonar/http/nemo.sonarqube.com/org.xbib%3Anet/coverage.svg?style=flat-square[title="Coverage", link="https://sonarqube.com/dashboard/index?id=org.xbib%3Anet"]
|
||||
image:https://maven-badges.herokuapp.com/maven-central/org.xbib/net/badge.svg[title="Maven Central", link="http://search.maven.org/#search%7Cga%7C1%7Cxbib%20net"]
|
||||
image:https://img.shields.io/badge/License-Apache%202.0-blue.svg[title="Apache License 2.0", link="https://opensource.org/licenses/Apache-2.0"]
|
||||
image:https://img.shields.io/twitter/url/https/twitter.com/xbib.svg?style=social&label=Follow%20%40xbib[title="Twitter", link="https://twitter.com/xbib"]
|
||||
|
||||
|
||||
image:https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif[title="PayPal", link="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=GVHFQYZ9WZ8HG"]
|
||||
|
114
build.gradle
Normal file
114
build.gradle
Normal file
|
@ -0,0 +1,114 @@
|
|||
|
||||
plugins {
|
||||
id "org.sonarqube" version "2.5"
|
||||
id "org.xbib.gradle.plugin.asciidoctor" version "1.5.4.1.0"
|
||||
id "io.codearte.nexus-staging" version "0.7.0"
|
||||
}
|
||||
|
||||
printf "Host: %s\nOS: %s %s %s\nJVM: %s %s %s %s\nGroovy: %s\nGradle: %s\n" +
|
||||
"Build: group: ${project.group} name: ${project.name} version: ${project.version}\n",
|
||||
InetAddress.getLocalHost(),
|
||||
System.getProperty("os.name"),
|
||||
System.getProperty("os.arch"),
|
||||
System.getProperty("os.version"),
|
||||
System.getProperty("java.version"),
|
||||
System.getProperty("java.vm.version"),
|
||||
System.getProperty("java.vm.vendor"),
|
||||
System.getProperty("java.vm.name"),
|
||||
GroovySystem.getVersion(),
|
||||
gradle.gradleVersion
|
||||
|
||||
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'maven'
|
||||
apply plugin: 'signing'
|
||||
apply plugin: 'findbugs'
|
||||
apply plugin: 'pmd'
|
||||
apply plugin: 'checkstyle'
|
||||
apply plugin: "jacoco"
|
||||
apply plugin: "io.codearte.nexus-staging"
|
||||
apply plugin: 'org.xbib.gradle.plugin.asciidoctor'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
configurations {
|
||||
asciidoclet
|
||||
wagon
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testCompile "junit:junit:${project.property('junit.version')}"
|
||||
wagon "org.apache.maven.wagon:wagon-ssh-external:2.12"
|
||||
testCompile "com.fasterxml.jackson.core:jackson-databind:${project.property('jackson.version')}"
|
||||
asciidoclet "org.asciidoctor:asciidoclet:${project.property('asciidoclet.version')}"
|
||||
wagon "org.apache.maven.wagon:wagon-ssh:${project.property('wagon.version')}"
|
||||
|
||||
}
|
||||
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
|
||||
[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'
|
||||
tasks.withType(JavaCompile) {
|
||||
options.compilerArgs << "-Xlint:all" << "-profile" << "compact1"
|
||||
}
|
||||
|
||||
jar {
|
||||
manifest {
|
||||
attributes('Implementation-Version': project.version)
|
||||
}
|
||||
}
|
||||
|
||||
test {
|
||||
testLogging {
|
||||
showStandardStreams = false
|
||||
exceptionFormat = 'full'
|
||||
}
|
||||
systemProperty 'java.net.preferIPv4Stack','false'
|
||||
systemProperty 'java.net.preferIPv6Addresses', 'true'
|
||||
}
|
||||
|
||||
asciidoctor {
|
||||
backends 'html5'
|
||||
separateOutputDirs = false
|
||||
attributes 'source-highlighter': 'coderay',
|
||||
toc : '',
|
||||
idprefix : '',
|
||||
idseparator : '-',
|
||||
stylesheet: "${projectDir}/src/docs/asciidoc/css/foundation.css"
|
||||
}
|
||||
|
||||
javadoc {
|
||||
options.docletpath = configurations.asciidoclet.files.asType(List)
|
||||
options.doclet = 'org.asciidoctor.Asciidoclet'
|
||||
options.overview = "src/docs/asciidoclet/overview.adoc"
|
||||
options.addStringOption "-base-dir", "${projectDir}"
|
||||
options.addStringOption "-attribute",
|
||||
"name=${project.name},version=${project.version},title-link=https://github.com/xbib/${project.name}"
|
||||
configure(options) {
|
||||
noTimestamp = true
|
||||
}
|
||||
}
|
||||
|
||||
task sourcesJar(type: Jar, dependsOn: classes) {
|
||||
classifier 'sources'
|
||||
from sourceSets.main.allSource
|
||||
}
|
||||
task javadocJar(type: Jar, dependsOn: javadoc) {
|
||||
classifier 'javadoc'
|
||||
}
|
||||
artifacts {
|
||||
archives sourcesJar, javadocJar
|
||||
}
|
||||
if (project.hasProperty('signing.keyId')) {
|
||||
signing {
|
||||
sign configurations.archives
|
||||
}
|
||||
}
|
||||
|
||||
apply from: "${rootProject.projectDir}/gradle/ext.gradle"
|
||||
apply from: "${rootProject.projectDir}/gradle/publish.gradle"
|
||||
apply from: "${rootProject.projectDir}/gradle/sonarqube.gradle"
|
||||
|
323
config/checkstyle/checkstyle.xml
Normal file
323
config/checkstyle/checkstyle.xml
Normal file
|
@ -0,0 +1,323 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE module PUBLIC
|
||||
"-//Puppy Crawl//DTD Check Configuration 1.3//EN"
|
||||
"http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
|
||||
|
||||
<!-- This is a checkstyle configuration file. For descriptions of
|
||||
what the following rules do, please see the checkstyle configuration
|
||||
page at http://checkstyle.sourceforge.net/config.html -->
|
||||
|
||||
<module name="Checker">
|
||||
|
||||
<module name="FileTabCharacter">
|
||||
<!-- Checks that there are no tab characters in the file.
|
||||
-->
|
||||
</module>
|
||||
|
||||
<module name="NewlineAtEndOfFile">
|
||||
<property name="lineSeparator" value="lf"/>
|
||||
</module>
|
||||
|
||||
<module name="RegexpSingleline">
|
||||
<!-- Checks that FIXME is not used in comments. TODO is preferred.
|
||||
-->
|
||||
<property name="format" value="((//.*)|(\*.*))FIXME" />
|
||||
<property name="message" value='TODO is preferred to FIXME. e.g. "TODO(johndoe): Refactor when v2 is released."' />
|
||||
</module>
|
||||
|
||||
<module name="RegexpSingleline">
|
||||
<!-- Checks that TODOs are named. (Actually, just that they are followed
|
||||
by an open paren.)
|
||||
-->
|
||||
<property name="format" value="((//.*)|(\*.*))TODO[^(]" />
|
||||
<property name="message" value='All TODOs should be named. e.g. "TODO(johndoe): Refactor when v2 is released."' />
|
||||
</module>
|
||||
|
||||
<module name="JavadocPackage">
|
||||
<!-- Checks that each Java package has a Javadoc file used for commenting.
|
||||
Only allows a package-info.java, not package.html. -->
|
||||
</module>
|
||||
|
||||
<!-- All Java AST specific tests live under TreeWalker module. -->
|
||||
<module name="TreeWalker">
|
||||
|
||||
<!--
|
||||
|
||||
IMPORT CHECKS
|
||||
|
||||
-->
|
||||
|
||||
<module name="RedundantImport">
|
||||
<!-- Checks for redundant import statements. -->
|
||||
<property name="severity" value="error"/>
|
||||
</module>
|
||||
|
||||
<module name="ImportOrder">
|
||||
<!-- Checks for out of order import statements. -->
|
||||
|
||||
<property name="severity" value="warning"/>
|
||||
<property name="groups" value="com,io,junit,net,org,javax,java"/>
|
||||
<!-- This ensures that static imports go last. -->
|
||||
<property name="option" value="under"/>
|
||||
<property name="tokens" value="IMPORT, STATIC_IMPORT"/>
|
||||
</module>
|
||||
|
||||
<!--
|
||||
|
||||
JAVADOC CHECKS
|
||||
|
||||
-->
|
||||
|
||||
<!-- Checks for Javadoc comments. -->
|
||||
<!-- See http://checkstyle.sf.net/config_javadoc.html -->
|
||||
<module name="JavadocMethod">
|
||||
<property name="scope" value="protected"/>
|
||||
<property name="severity" value="warning"/>
|
||||
<property name="allowMissingJavadoc" value="true"/>
|
||||
<property name="allowMissingParamTags" value="true"/>
|
||||
<property name="allowMissingReturnTag" value="true"/>
|
||||
<property name="allowMissingThrowsTags" value="true"/>
|
||||
<property name="allowThrowsTagsForSubclasses" value="true"/>
|
||||
<property name="allowUndeclaredRTE" value="true"/>
|
||||
</module>
|
||||
|
||||
<module name="JavadocType">
|
||||
<property name="scope" value="protected"/>
|
||||
<property name="severity" value="error"/>
|
||||
</module>
|
||||
|
||||
<module name="JavadocStyle">
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<!--
|
||||
|
||||
NAMING CHECKS
|
||||
|
||||
-->
|
||||
|
||||
<!-- Item 38 - Adhere to generally accepted naming conventions -->
|
||||
|
||||
<module name="PackageName">
|
||||
<!-- Validates identifiers for package names against the
|
||||
supplied expression. -->
|
||||
<!-- Here the default checkstyle rule restricts package name parts to
|
||||
seven characters, this is not in line with common practice at Google.
|
||||
-->
|
||||
<property name="format" value="^[a-z]+(\.[a-z][a-z0-9]{1,})*$"/>
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<module name="TypeNameCheck">
|
||||
<!-- Validates static, final fields against the
|
||||
expression "^[A-Z][a-zA-Z0-9]*$". -->
|
||||
<metadata name="altname" value="TypeName"/>
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<module name="ConstantNameCheck">
|
||||
<!-- Validates non-private, static, final fields against the supplied
|
||||
public/package final fields "^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$". -->
|
||||
<metadata name="altname" value="ConstantName"/>
|
||||
<property name="applyToPublic" value="true"/>
|
||||
<property name="applyToProtected" value="true"/>
|
||||
<property name="applyToPackage" value="true"/>
|
||||
<property name="applyToPrivate" value="false"/>
|
||||
<property name="format" value="^([A-Z][A-Z0-9]*(_[A-Z0-9]+)*|FLAG_.*)$"/>
|
||||
<message key="name.invalidPattern"
|
||||
value="Variable ''{0}'' should be in ALL_CAPS (if it is a constant) or be private (otherwise)."/>
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<module name="StaticVariableNameCheck">
|
||||
<!-- Validates static, non-final fields against the supplied
|
||||
expression "^[a-z][a-zA-Z0-9]*_?$". -->
|
||||
<metadata name="altname" value="StaticVariableName"/>
|
||||
<property name="applyToPublic" value="true"/>
|
||||
<property name="applyToProtected" value="true"/>
|
||||
<property name="applyToPackage" value="true"/>
|
||||
<property name="applyToPrivate" value="true"/>
|
||||
<property name="format" value="^[a-z][a-zA-Z0-9]*_?$"/>
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<module name="MemberNameCheck">
|
||||
<!-- Validates non-static members against the supplied expression. -->
|
||||
<metadata name="altname" value="MemberName"/>
|
||||
<property name="applyToPublic" value="true"/>
|
||||
<property name="applyToProtected" value="true"/>
|
||||
<property name="applyToPackage" value="true"/>
|
||||
<property name="applyToPrivate" value="true"/>
|
||||
<property name="format" value="^[a-z][a-zA-Z0-9]*$"/>
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<module name="MethodNameCheck">
|
||||
<!-- Validates identifiers for method names. -->
|
||||
<metadata name="altname" value="MethodName"/>
|
||||
<property name="format" value="^[a-z][a-zA-Z0-9]*(_[a-zA-Z0-9]+)*$"/>
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<module name="ParameterName">
|
||||
<!-- Validates identifiers for method parameters against the
|
||||
expression "^[a-z][a-zA-Z0-9]*$". -->
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<module name="LocalFinalVariableName">
|
||||
<!-- Validates identifiers for local final variables against the
|
||||
expression "^[a-z][a-zA-Z0-9]*$". -->
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<module name="LocalVariableName">
|
||||
<!-- Validates identifiers for local variables against the
|
||||
expression "^[a-z][a-zA-Z0-9]*$". -->
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
|
||||
<!--
|
||||
|
||||
LENGTH and CODING CHECKS
|
||||
|
||||
-->
|
||||
|
||||
<module name="LineLength">
|
||||
<!-- Checks if a line is too long. -->
|
||||
<property name="max" value="${com.puppycrawl.tools.checkstyle.checks.sizes.LineLength.max}" default="128"/>
|
||||
<property name="severity" value="error"/>
|
||||
|
||||
<!--
|
||||
The default ignore pattern exempts the following elements:
|
||||
- import statements
|
||||
- long URLs inside comments
|
||||
-->
|
||||
|
||||
<property name="ignorePattern"
|
||||
value="${com.puppycrawl.tools.checkstyle.checks.sizes.LineLength.ignorePattern}"
|
||||
default="^(package .*;\s*)|(import .*;\s*)|( *(\*|//).*https?://.*)$"/>
|
||||
</module>
|
||||
|
||||
<module name="LeftCurly">
|
||||
<!-- Checks for placement of the left curly brace ('{'). -->
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<module name="RightCurly">
|
||||
<!-- Checks right curlies on CATCH, ELSE, and TRY blocks are on
|
||||
the same line. e.g., the following example is fine:
|
||||
<pre>
|
||||
if {
|
||||
...
|
||||
} else
|
||||
</pre>
|
||||
-->
|
||||
<!-- This next example is not fine:
|
||||
<pre>
|
||||
if {
|
||||
...
|
||||
}
|
||||
else
|
||||
</pre>
|
||||
-->
|
||||
<property name="option" value="same"/>
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<!-- Checks for braces around if and else blocks -->
|
||||
<module name="NeedBraces">
|
||||
<property name="severity" value="warning"/>
|
||||
<property name="tokens" value="LITERAL_IF, LITERAL_ELSE, LITERAL_FOR, LITERAL_WHILE, LITERAL_DO"/>
|
||||
</module>
|
||||
|
||||
<module name="UpperEll">
|
||||
<!-- Checks that long constants are defined with an upper ell.-->
|
||||
<property name="severity" value="error"/>
|
||||
</module>
|
||||
|
||||
<module name="FallThrough">
|
||||
<!-- Warn about falling through to the next case statement. Similar to
|
||||
javac -Xlint:fallthrough, but the check is suppressed if a single-line comment
|
||||
on the last non-blank line preceding the fallen-into case contains 'fall through' (or
|
||||
some other variants which we don't publicized to promote consistency).
|
||||
-->
|
||||
<property name="reliefPattern"
|
||||
value="fall through|Fall through|fallthru|Fallthru|falls through|Falls through|fallthrough|Fallthrough|No break|NO break|no break|continue on"/>
|
||||
<property name="severity" value="error"/>
|
||||
</module>
|
||||
|
||||
|
||||
<!--
|
||||
|
||||
MODIFIERS CHECKS
|
||||
|
||||
-->
|
||||
|
||||
<module name="ModifierOrder">
|
||||
<!-- Warn if modifier order is inconsistent with JLS3 8.1.1, 8.3.1, and
|
||||
8.4.3. The prescribed order is:
|
||||
public, protected, private, abstract, static, final, transient, volatile,
|
||||
synchronized, native, strictfp
|
||||
-->
|
||||
</module>
|
||||
|
||||
|
||||
<!--
|
||||
|
||||
WHITESPACE CHECKS
|
||||
|
||||
-->
|
||||
|
||||
<module name="WhitespaceAround">
|
||||
<!-- Checks that various tokens are surrounded by whitespace.
|
||||
This includes most binary operators and keywords followed
|
||||
by regular or curly braces.
|
||||
-->
|
||||
<property name="tokens" value="ASSIGN, BAND, BAND_ASSIGN, BOR,
|
||||
BOR_ASSIGN, BSR, BSR_ASSIGN, BXOR, BXOR_ASSIGN, COLON, DIV, DIV_ASSIGN,
|
||||
EQUAL, GE, GT, LAND, LE, LITERAL_CATCH, LITERAL_DO, LITERAL_ELSE,
|
||||
LITERAL_FINALLY, LITERAL_FOR, LITERAL_IF, LITERAL_RETURN,
|
||||
LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_WHILE, LOR, LT, MINUS,
|
||||
MINUS_ASSIGN, MOD, MOD_ASSIGN, NOT_EQUAL, PLUS, PLUS_ASSIGN, QUESTION,
|
||||
SL, SL_ASSIGN, SR_ASSIGN, STAR, STAR_ASSIGN"/>
|
||||
<property name="severity" value="error"/>
|
||||
</module>
|
||||
|
||||
<module name="WhitespaceAfter">
|
||||
<!-- Checks that commas, semicolons and typecasts are followed by
|
||||
whitespace.
|
||||
-->
|
||||
<property name="tokens" value="COMMA, SEMI, TYPECAST"/>
|
||||
</module>
|
||||
|
||||
<module name="NoWhitespaceAfter">
|
||||
<!-- Checks that there is no whitespace after various unary operators.
|
||||
Linebreaks are allowed.
|
||||
-->
|
||||
<property name="tokens" value="BNOT, DEC, DOT, INC, LNOT, UNARY_MINUS,
|
||||
UNARY_PLUS"/>
|
||||
<property name="allowLineBreaks" value="true"/>
|
||||
<property name="severity" value="error"/>
|
||||
</module>
|
||||
|
||||
<module name="NoWhitespaceBefore">
|
||||
<!-- Checks that there is no whitespace before various unary operators.
|
||||
Linebreaks are allowed.
|
||||
-->
|
||||
<property name="tokens" value="SEMI, DOT, POST_DEC, POST_INC"/>
|
||||
<property name="allowLineBreaks" value="true"/>
|
||||
<property name="severity" value="error"/>
|
||||
</module>
|
||||
|
||||
<module name="ParenPad">
|
||||
<!-- Checks that there is no whitespace before close parens or after
|
||||
open parens.
|
||||
-->
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
</module>
|
||||
</module>
|
||||
|
8
gradle.properties
Normal file
8
gradle.properties
Normal file
|
@ -0,0 +1,8 @@
|
|||
group = org.xbib
|
||||
name = net
|
||||
version = 1.0.0
|
||||
|
||||
junit.version = 4.12
|
||||
asciidoclet.version = 1.5.4
|
||||
wagon.version = 2.12
|
||||
jackson.version = 2.8.4
|
8
gradle/ext.gradle
Normal file
8
gradle/ext.gradle
Normal file
|
@ -0,0 +1,8 @@
|
|||
ext {
|
||||
user = 'xbib'
|
||||
projectName = 'content'
|
||||
projectDescription = 'Content processing library for Java'
|
||||
scmUrl = 'https://github.com/xbib/content'
|
||||
scmConnection = 'scm:git:git://github.com/xbib/content.git'
|
||||
scmDeveloperConnection = 'scm:git:git://github.com/xbib/content.git'
|
||||
}
|
66
gradle/publish.gradle
Normal file
66
gradle/publish.gradle
Normal file
|
@ -0,0 +1,66 @@
|
|||
|
||||
task xbibUpload(type: Upload, dependsOn: build) {
|
||||
configuration = configurations.archives
|
||||
uploadDescriptor = true
|
||||
repositories {
|
||||
if (project.hasProperty('xbibUsername')) {
|
||||
mavenDeployer {
|
||||
configuration = configurations.wagon
|
||||
repository(url: uri('scpexe://xbib.org/repository')) {
|
||||
authentication(userName: xbibUsername, privateKey: xbibPrivateKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task sonatypeUpload(type: Upload, dependsOn: build) {
|
||||
configuration = configurations.archives
|
||||
uploadDescriptor = true
|
||||
repositories {
|
||||
if (project.hasProperty('ossrhUsername')) {
|
||||
mavenDeployer {
|
||||
beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
|
||||
repository(url: uri(ossrhReleaseUrl)) {
|
||||
authentication(userName: ossrhUsername, password: ossrhPassword)
|
||||
}
|
||||
snapshotRepository(url: uri(ossrhSnapshotUrl)) {
|
||||
authentication(userName: ossrhUsername, password: ossrhPassword)
|
||||
}
|
||||
pom.project {
|
||||
groupId project.group
|
||||
artifactId project.name
|
||||
version project.version
|
||||
name project.name
|
||||
description projectDescription
|
||||
packaging 'jar'
|
||||
inceptionYear '2016'
|
||||
url scmUrl
|
||||
organization {
|
||||
name 'xbib'
|
||||
url 'http://xbib.org'
|
||||
}
|
||||
developers {
|
||||
developer {
|
||||
id user
|
||||
name 'Jörg Prante'
|
||||
email 'joergprante@gmail.com'
|
||||
url 'https://github.com/jprante'
|
||||
}
|
||||
}
|
||||
scm {
|
||||
url scmUrl
|
||||
connection scmConnection
|
||||
developerConnection scmDeveloperConnection
|
||||
}
|
||||
licenses {
|
||||
license {
|
||||
name 'The Apache License, Version 2.0'
|
||||
url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
39
gradle/sonarqube.gradle
Normal file
39
gradle/sonarqube.gradle
Normal file
|
@ -0,0 +1,39 @@
|
|||
tasks.withType(FindBugs) {
|
||||
ignoreFailures = true
|
||||
reports {
|
||||
xml.enabled = false
|
||||
html.enabled = true
|
||||
}
|
||||
}
|
||||
tasks.withType(Pmd) {
|
||||
ignoreFailures = true
|
||||
reports {
|
||||
xml.enabled = true
|
||||
html.enabled = true
|
||||
}
|
||||
}
|
||||
tasks.withType(Checkstyle) {
|
||||
ignoreFailures = true
|
||||
reports {
|
||||
xml.enabled = true
|
||||
html.enabled = true
|
||||
}
|
||||
}
|
||||
|
||||
jacocoTestReport {
|
||||
reports {
|
||||
xml.enabled = true
|
||||
csv.enabled = false
|
||||
}
|
||||
}
|
||||
|
||||
sonarqube {
|
||||
properties {
|
||||
property "sonar.projectName", "${project.group} ${project.name}"
|
||||
property "sonar.sourceEncoding", "UTF-8"
|
||||
property "sonar.tests", "src/test/java"
|
||||
property "sonar.scm.provider", "git"
|
||||
property "sonar.java.coveragePlugin", "jacoco"
|
||||
property "sonar.junit.reportsPath", "build/test-results/test/"
|
||||
}
|
||||
}
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
#Thu Jul 13 21:48:43 CEST 2017
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.0.1-all.zip
|
172
gradlew
vendored
Executable file
172
gradlew
vendored
Executable file
|
@ -0,0 +1,172 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=$(save "$@")
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
84
gradlew.bat
vendored
Normal file
84
gradlew.bat
vendored
Normal file
|
@ -0,0 +1,84 @@
|
|||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
684
src/docs/asciidoc/css/foundation.css
vendored
Normal file
684
src/docs/asciidoc/css/foundation.css
vendored
Normal file
|
@ -0,0 +1,684 @@
|
|||
/*! normalize.css v2.1.2 | MIT License | git.io/normalize */
|
||||
/* ========================================================================== HTML5 display definitions ========================================================================== */
|
||||
/** Correct `block` display not defined in IE 8/9. */
|
||||
article, aside, details, figcaption, figure, footer, header, hgroup, main, nav, section, summary { display: block; }
|
||||
|
||||
/** Correct `inline-block` display not defined in IE 8/9. */
|
||||
audio, canvas, video { display: inline-block; }
|
||||
|
||||
/** Prevent modern browsers from displaying `audio` without controls. Remove excess height in iOS 5 devices. */
|
||||
audio:not([controls]) { display: none; height: 0; }
|
||||
|
||||
/** Address `[hidden]` styling not present in IE 8/9. Hide the `template` element in IE, Safari, and Firefox < 22. */
|
||||
[hidden], template { display: none; }
|
||||
|
||||
script { display: none !important; }
|
||||
|
||||
/* ========================================================================== Base ========================================================================== */
|
||||
/** 1. Set default font family to sans-serif. 2. Prevent iOS text size adjust after orientation change, without disabling user zoom. */
|
||||
html { font-family: sans-serif; /* 1 */ -ms-text-size-adjust: 100%; /* 2 */ -webkit-text-size-adjust: 100%; /* 2 */ }
|
||||
|
||||
/** Remove default margin. */
|
||||
body { margin: 0; }
|
||||
|
||||
/* ========================================================================== Links ========================================================================== */
|
||||
/** Remove the gray background color from active links in IE 10. */
|
||||
a { background: transparent; }
|
||||
|
||||
/** Address `outline` inconsistency between Chrome and other browsers. */
|
||||
a:focus { outline: thin dotted; }
|
||||
|
||||
/** Improve readability when focused and also mouse hovered in all browsers. */
|
||||
a:active, a:hover { outline: 0; }
|
||||
|
||||
/* ========================================================================== Typography ========================================================================== */
|
||||
/** Address variable `h1` font-size and margin within `section` and `article` contexts in Firefox 4+, Safari 5, and Chrome. */
|
||||
h1 { font-size: 2em; margin: 0.67em 0; }
|
||||
|
||||
/** Address styling not present in IE 8/9, Safari 5, and Chrome. */
|
||||
abbr[title] { border-bottom: 1px dotted; }
|
||||
|
||||
/** Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome. */
|
||||
b, strong { font-weight: bold; }
|
||||
|
||||
/** Address styling not present in Safari 5 and Chrome. */
|
||||
dfn { font-style: italic; }
|
||||
|
||||
/** Address differences between Firefox and other browsers. */
|
||||
hr { -moz-box-sizing: content-box; box-sizing: content-box; height: 0; }
|
||||
|
||||
/** Address styling not present in IE 8/9. */
|
||||
mark { background: #ff0; color: #000; }
|
||||
|
||||
/** Correct font family set oddly in Safari 5 and Chrome. */
|
||||
code, kbd, pre, samp { font-family: monospace, serif; font-size: 1em; }
|
||||
|
||||
/** Improve readability of pre-formatted text in all browsers. */
|
||||
pre { white-space: pre-wrap; }
|
||||
|
||||
/** Set consistent quote types. */
|
||||
q { quotes: "\201C" "\201D" "\2018" "\2019"; }
|
||||
|
||||
/** Address inconsistent and variable font size in all browsers. */
|
||||
small { font-size: 80%; }
|
||||
|
||||
/** Prevent `sub` and `sup` affecting `line-height` in all browsers. */
|
||||
sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; }
|
||||
|
||||
sup { top: -0.5em; }
|
||||
|
||||
sub { bottom: -0.25em; }
|
||||
|
||||
/* ========================================================================== Embedded content ========================================================================== */
|
||||
/** Remove border when inside `a` element in IE 8/9. */
|
||||
img { border: 0; }
|
||||
|
||||
/** Correct overflow displayed oddly in IE 9. */
|
||||
svg:not(:root) { overflow: hidden; }
|
||||
|
||||
/* ========================================================================== Figures ========================================================================== */
|
||||
/** Address margin not present in IE 8/9 and Safari 5. */
|
||||
figure { margin: 0; }
|
||||
|
||||
/* ========================================================================== Forms ========================================================================== */
|
||||
/** Define consistent border, margin, and padding. */
|
||||
fieldset { border: 1px solid #c0c0c0; margin: 0 2px; padding: 0.35em 0.625em 0.75em; }
|
||||
|
||||
/** 1. Correct `color` not being inherited in IE 8/9. 2. Remove padding so people aren't caught out if they zero out fieldsets. */
|
||||
legend { border: 0; /* 1 */ padding: 0; /* 2 */ }
|
||||
|
||||
/** 1. Correct font family not being inherited in all browsers. 2. Correct font size not being inherited in all browsers. 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome. */
|
||||
button, input, select, textarea { font-family: inherit; /* 1 */ font-size: 100%; /* 2 */ margin: 0; /* 3 */ }
|
||||
|
||||
/** Address Firefox 4+ setting `line-height` on `input` using `!important` in the UA stylesheet. */
|
||||
button, input { line-height: normal; }
|
||||
|
||||
/** Address inconsistent `text-transform` inheritance for `button` and `select`. All other form control elements do not inherit `text-transform` values. Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+. Correct `select` style inheritance in Firefox 4+ and Opera. */
|
||||
button, select { text-transform: none; }
|
||||
|
||||
/** 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` and `video` controls. 2. Correct inability to style clickable `input` types in iOS. 3. Improve usability and consistency of cursor style between image-type `input` and others. */
|
||||
button, html input[type="button"], input[type="reset"], input[type="submit"] { -webkit-appearance: button; /* 2 */ cursor: pointer; /* 3 */ }
|
||||
|
||||
/** Re-set default cursor for disabled elements. */
|
||||
button[disabled], html input[disabled] { cursor: default; }
|
||||
|
||||
/** 1. Address box sizing set to `content-box` in IE 8/9. 2. Remove excess padding in IE 8/9. */
|
||||
input[type="checkbox"], input[type="radio"] { box-sizing: border-box; /* 1 */ padding: 0; /* 2 */ }
|
||||
|
||||
/** 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome. 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome (include `-moz` to future-proof). */
|
||||
input[type="search"] { -webkit-appearance: textfield; /* 1 */ -moz-box-sizing: content-box; -webkit-box-sizing: content-box; /* 2 */ box-sizing: content-box; }
|
||||
|
||||
/** Remove inner padding and search cancel button in Safari 5 and Chrome on OS X. */
|
||||
input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; }
|
||||
|
||||
/** Remove inner padding and border in Firefox 4+. */
|
||||
button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; }
|
||||
|
||||
/** 1. Remove default vertical scrollbar in IE 8/9. 2. Improve readability and alignment in all browsers. */
|
||||
textarea { overflow: auto; /* 1 */ vertical-align: top; /* 2 */ }
|
||||
|
||||
/* ========================================================================== Tables ========================================================================== */
|
||||
/** Remove most spacing between table cells. */
|
||||
table { border-collapse: collapse; border-spacing: 0; }
|
||||
|
||||
meta.foundation-mq-small { font-family: "only screen and (min-width: 768px)"; width: 768px; }
|
||||
|
||||
meta.foundation-mq-medium { font-family: "only screen and (min-width:1280px)"; width: 1280px; }
|
||||
|
||||
meta.foundation-mq-large { font-family: "only screen and (min-width:1440px)"; width: 1440px; }
|
||||
|
||||
*, *:before, *:after { -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; }
|
||||
|
||||
html, body { font-size: 100%; }
|
||||
|
||||
body { background: white; color: #222222; padding: 0; margin: 0; font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif; font-weight: normal; font-style: normal; line-height: 1; position: relative; cursor: auto; }
|
||||
|
||||
a:hover { cursor: pointer; }
|
||||
|
||||
img, object, embed { max-width: 100%; height: auto; }
|
||||
|
||||
object, embed { height: 100%; }
|
||||
|
||||
img { -ms-interpolation-mode: bicubic; }
|
||||
|
||||
#map_canvas img, #map_canvas embed, #map_canvas object, .map_canvas img, .map_canvas embed, .map_canvas object { max-width: none !important; }
|
||||
|
||||
.left { float: left !important; }
|
||||
|
||||
.right { float: right !important; }
|
||||
|
||||
.text-left { text-align: left !important; }
|
||||
|
||||
.text-right { text-align: right !important; }
|
||||
|
||||
.text-center { text-align: center !important; }
|
||||
|
||||
.text-justify { text-align: justify !important; }
|
||||
|
||||
.hide { display: none; }
|
||||
|
||||
.antialiased { -webkit-font-smoothing: antialiased; }
|
||||
|
||||
img { display: inline-block; vertical-align: middle; }
|
||||
|
||||
textarea { height: auto; min-height: 50px; }
|
||||
|
||||
select { width: 100%; }
|
||||
|
||||
object, svg { display: inline-block; vertical-align: middle; }
|
||||
|
||||
.center { margin-left: auto; margin-right: auto; }
|
||||
|
||||
.spread { width: 100%; }
|
||||
|
||||
p.lead, .paragraph.lead > p, #preamble > .sectionbody > .paragraph:first-of-type p { font-size: 1.21875em; line-height: 1.6; }
|
||||
|
||||
.subheader, .admonitionblock td.content > .title, .audioblock > .title, .exampleblock > .title, .imageblock > .title, .listingblock > .title, .literalblock > .title, .stemblock > .title, .openblock > .title, .paragraph > .title, .quoteblock > .title, table.tableblock > .title, .verseblock > .title, .videoblock > .title, .dlist > .title, .olist > .title, .ulist > .title, .qlist > .title, .hdlist > .title { line-height: 1.4; color: #6f6f6f; font-weight: 300; margin-top: 0.2em; margin-bottom: 0.5em; }
|
||||
|
||||
/* Typography resets */
|
||||
div, dl, dt, dd, ul, ol, li, h1, h2, h3, #toctitle, .sidebarblock > .content > .title, h4, h5, h6, pre, form, p, blockquote, th, td { margin: 0; padding: 0; direction: ltr; }
|
||||
|
||||
/* Default Link Styles */
|
||||
a { color: #2ba6cb; text-decoration: none; line-height: inherit; }
|
||||
a:hover, a:focus { color: #2795b6; }
|
||||
a img { border: none; }
|
||||
|
||||
/* Default paragraph styles */
|
||||
p { font-family: inherit; font-weight: normal; font-size: 1em; line-height: 1.6; margin-bottom: 1.25em; text-rendering: optimizeLegibility; }
|
||||
p aside { font-size: 0.875em; line-height: 1.35; font-style: italic; }
|
||||
|
||||
/* Default header styles */
|
||||
h1, h2, h3, #toctitle, .sidebarblock > .content > .title, h4, h5, h6 { font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif; font-weight: bold; font-style: normal; color: #222222; text-rendering: optimizeLegibility; margin-top: 1em; margin-bottom: 0.5em; line-height: 1.2125em; }
|
||||
h1 small, h2 small, h3 small, #toctitle small, .sidebarblock > .content > .title small, h4 small, h5 small, h6 small { font-size: 60%; color: #6f6f6f; line-height: 0; }
|
||||
|
||||
h1 { font-size: 2.125em; }
|
||||
|
||||
h2 { font-size: 1.6875em; }
|
||||
|
||||
h3, #toctitle, .sidebarblock > .content > .title { font-size: 1.375em; }
|
||||
|
||||
h4 { font-size: 1.125em; }
|
||||
|
||||
h5 { font-size: 1.125em; }
|
||||
|
||||
h6 { font-size: 1em; }
|
||||
|
||||
hr { border: solid #dddddd; border-width: 1px 0 0; clear: both; margin: 1.25em 0 1.1875em; height: 0; }
|
||||
|
||||
/* Helpful Typography Defaults */
|
||||
em, i { font-style: italic; line-height: inherit; }
|
||||
|
||||
strong, b { font-weight: bold; line-height: inherit; }
|
||||
|
||||
small { font-size: 60%; line-height: inherit; }
|
||||
|
||||
code { font-family: Consolas, "Liberation Mono", Courier, monospace; font-weight: bold; color: #7f0a0c; }
|
||||
|
||||
/* Lists */
|
||||
ul, ol, dl { font-size: 1em; line-height: 1.6; margin-bottom: 1.25em; list-style-position: outside; font-family: inherit; }
|
||||
|
||||
ul, ol { margin-left: 1.5em; }
|
||||
ul.no-bullet, ol.no-bullet { margin-left: 1.5em; }
|
||||
|
||||
/* Unordered Lists */
|
||||
ul li ul, ul li ol { margin-left: 1.25em; margin-bottom: 0; font-size: 1em; /* Override nested font-size change */ }
|
||||
ul.square li ul, ul.circle li ul, ul.disc li ul { list-style: inherit; }
|
||||
ul.square { list-style-type: square; }
|
||||
ul.circle { list-style-type: circle; }
|
||||
ul.disc { list-style-type: disc; }
|
||||
ul.no-bullet { list-style: none; }
|
||||
|
||||
/* Ordered Lists */
|
||||
ol li ul, ol li ol { margin-left: 1.25em; margin-bottom: 0; }
|
||||
|
||||
/* Definition Lists */
|
||||
dl dt { margin-bottom: 0.3125em; font-weight: bold; }
|
||||
dl dd { margin-bottom: 1.25em; }
|
||||
|
||||
/* Abbreviations */
|
||||
abbr, acronym { text-transform: uppercase; font-size: 90%; color: #222222; border-bottom: 1px dotted #dddddd; cursor: help; }
|
||||
|
||||
abbr { text-transform: none; }
|
||||
|
||||
/* Blockquotes */
|
||||
blockquote { margin: 0 0 1.25em; padding: 0.5625em 1.25em 0 1.1875em; border-left: 1px solid #dddddd; }
|
||||
blockquote cite { display: block; font-size: 0.8125em; color: #555555; }
|
||||
blockquote cite:before { content: "\2014 \0020"; }
|
||||
blockquote cite a, blockquote cite a:visited { color: #555555; }
|
||||
|
||||
blockquote, blockquote p { line-height: 1.6; color: #6f6f6f; }
|
||||
|
||||
/* Microformats */
|
||||
.vcard { display: inline-block; margin: 0 0 1.25em 0; border: 1px solid #dddddd; padding: 0.625em 0.75em; }
|
||||
.vcard li { margin: 0; display: block; }
|
||||
.vcard .fn { font-weight: bold; font-size: 0.9375em; }
|
||||
|
||||
.vevent .summary { font-weight: bold; }
|
||||
.vevent abbr { cursor: auto; text-decoration: none; font-weight: bold; border: none; padding: 0 0.0625em; }
|
||||
|
||||
@media only screen and (min-width: 768px) { h1, h2, h3, #toctitle, .sidebarblock > .content > .title, h4, h5, h6 { line-height: 1.4; }
|
||||
h1 { font-size: 2.75em; }
|
||||
h2 { font-size: 2.3125em; }
|
||||
h3, #toctitle, .sidebarblock > .content > .title { font-size: 1.6875em; }
|
||||
h4 { font-size: 1.4375em; } }
|
||||
/* Tables */
|
||||
table { background: white; margin-bottom: 1.25em; border: solid 1px #dddddd; }
|
||||
table thead, table tfoot { background: whitesmoke; font-weight: bold; }
|
||||
table thead tr th, table thead tr td, table tfoot tr th, table tfoot tr td { padding: 0.5em 0.625em 0.625em; font-size: inherit; color: #222222; text-align: left; }
|
||||
table tr th, table tr td { padding: 0.5625em 0.625em; font-size: inherit; color: #222222; }
|
||||
table tr.even, table tr.alt, table tr:nth-of-type(even) { background: #f9f9f9; }
|
||||
table thead tr th, table tfoot tr th, table tbody tr td, table tr td, table tfoot tr td { display: table-cell; line-height: 1.4; }
|
||||
|
||||
body { -moz-osx-font-smoothing: grayscale; -webkit-font-smoothing: antialiased; tab-size: 4; }
|
||||
|
||||
h1, h2, h3, #toctitle, .sidebarblock > .content > .title, h4, h5, h6 { line-height: 1.4; }
|
||||
|
||||
.clearfix:before, .clearfix:after, .float-group:before, .float-group:after { content: " "; display: table; }
|
||||
.clearfix:after, .float-group:after { clear: both; }
|
||||
|
||||
*:not(pre) > code { font-size: inherit; font-style: normal !important; letter-spacing: 0; padding: 0; line-height: inherit; word-wrap: break-word; }
|
||||
*:not(pre) > code.nobreak { word-wrap: normal; }
|
||||
*:not(pre) > code.nowrap { white-space: nowrap; }
|
||||
|
||||
pre, pre > code { line-height: 1.4; color: black; font-family: monospace, serif; font-weight: normal; }
|
||||
|
||||
em em { font-style: normal; }
|
||||
|
||||
strong strong { font-weight: normal; }
|
||||
|
||||
.keyseq { color: #555555; }
|
||||
|
||||
kbd { font-family: Consolas, "Liberation Mono", Courier, monospace; display: inline-block; color: #222222; font-size: 0.65em; line-height: 1.45; background-color: #f7f7f7; border: 1px solid #ccc; -webkit-border-radius: 3px; border-radius: 3px; -webkit-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2), 0 0 0 0.1em white inset; box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2), 0 0 0 0.1em white inset; margin: 0 0.15em; padding: 0.2em 0.5em; vertical-align: middle; position: relative; top: -0.1em; white-space: nowrap; }
|
||||
|
||||
.keyseq kbd:first-child { margin-left: 0; }
|
||||
|
||||
.keyseq kbd:last-child { margin-right: 0; }
|
||||
|
||||
.menuseq, .menu { color: #090909; }
|
||||
|
||||
b.button:before, b.button:after { position: relative; top: -1px; font-weight: normal; }
|
||||
|
||||
b.button:before { content: "["; padding: 0 3px 0 2px; }
|
||||
|
||||
b.button:after { content: "]"; padding: 0 2px 0 3px; }
|
||||
|
||||
#header, #content, #footnotes, #footer { width: 100%; margin-left: auto; margin-right: auto; margin-top: 0; margin-bottom: 0; max-width: 62.5em; *zoom: 1; position: relative; padding-left: 0.9375em; padding-right: 0.9375em; }
|
||||
#header:before, #header:after, #content:before, #content:after, #footnotes:before, #footnotes:after, #footer:before, #footer:after { content: " "; display: table; }
|
||||
#header:after, #content:after, #footnotes:after, #footer:after { clear: both; }
|
||||
|
||||
#content { margin-top: 1.25em; }
|
||||
|
||||
#content:before { content: none; }
|
||||
|
||||
#header > h1:first-child { color: black; margin-top: 2.25rem; margin-bottom: 0; }
|
||||
#header > h1:first-child + #toc { margin-top: 8px; border-top: 1px solid #dddddd; }
|
||||
#header > h1:only-child, body.toc2 #header > h1:nth-last-child(2) { border-bottom: 1px solid #dddddd; padding-bottom: 8px; }
|
||||
#header .details { border-bottom: 1px solid #dddddd; line-height: 1.45; padding-top: 0.25em; padding-bottom: 0.25em; padding-left: 0.25em; color: #555555; display: -ms-flexbox; display: -webkit-flex; display: flex; -ms-flex-flow: row wrap; -webkit-flex-flow: row wrap; flex-flow: row wrap; }
|
||||
#header .details span:first-child { margin-left: -0.125em; }
|
||||
#header .details span.email a { color: #6f6f6f; }
|
||||
#header .details br { display: none; }
|
||||
#header .details br + span:before { content: "\00a0\2013\00a0"; }
|
||||
#header .details br + span.author:before { content: "\00a0\22c5\00a0"; color: #6f6f6f; }
|
||||
#header .details br + span#revremark:before { content: "\00a0|\00a0"; }
|
||||
#header #revnumber { text-transform: capitalize; }
|
||||
#header #revnumber:after { content: "\00a0"; }
|
||||
|
||||
#content > h1:first-child:not([class]) { color: black; border-bottom: 1px solid #dddddd; padding-bottom: 8px; margin-top: 0; padding-top: 1rem; margin-bottom: 1.25rem; }
|
||||
|
||||
#toc { border-bottom: 1px solid #dddddd; padding-bottom: 0.5em; }
|
||||
#toc > ul { margin-left: 0.125em; }
|
||||
#toc ul.sectlevel0 > li > a { font-style: italic; }
|
||||
#toc ul.sectlevel0 ul.sectlevel1 { margin: 0.5em 0; }
|
||||
#toc ul { font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif; list-style-type: none; }
|
||||
#toc li { line-height: 1.3334; margin-top: 0.3334em; }
|
||||
#toc a { text-decoration: none; }
|
||||
#toc a:active { text-decoration: underline; }
|
||||
|
||||
#toctitle { color: #6f6f6f; font-size: 1.2em; }
|
||||
|
||||
@media only screen and (min-width: 768px) { #toctitle { font-size: 1.375em; }
|
||||
body.toc2 { padding-left: 15em; padding-right: 0; }
|
||||
#toc.toc2 { margin-top: 0 !important; background-color: #f2f2f2; position: fixed; width: 15em; left: 0; top: 0; border-right: 1px solid #dddddd; border-top-width: 0 !important; border-bottom-width: 0 !important; z-index: 1000; padding: 1.25em 1em; height: 100%; overflow: auto; }
|
||||
#toc.toc2 #toctitle { margin-top: 0; margin-bottom: 0.8rem; font-size: 1.2em; }
|
||||
#toc.toc2 > ul { font-size: 0.9em; margin-bottom: 0; }
|
||||
#toc.toc2 ul ul { margin-left: 0; padding-left: 1em; }
|
||||
#toc.toc2 ul.sectlevel0 ul.sectlevel1 { padding-left: 0; margin-top: 0.5em; margin-bottom: 0.5em; }
|
||||
body.toc2.toc-right { padding-left: 0; padding-right: 15em; }
|
||||
body.toc2.toc-right #toc.toc2 { border-right-width: 0; border-left: 1px solid #dddddd; left: auto; right: 0; } }
|
||||
@media only screen and (min-width: 1280px) { body.toc2 { padding-left: 20em; padding-right: 0; }
|
||||
#toc.toc2 { width: 20em; }
|
||||
#toc.toc2 #toctitle { font-size: 1.375em; }
|
||||
#toc.toc2 > ul { font-size: 0.95em; }
|
||||
#toc.toc2 ul ul { padding-left: 1.25em; }
|
||||
body.toc2.toc-right { padding-left: 0; padding-right: 20em; } }
|
||||
#content #toc { border-style: solid; border-width: 1px; border-color: #d9d9d9; margin-bottom: 1.25em; padding: 1.25em; background: #f2f2f2; -webkit-border-radius: 0; border-radius: 0; }
|
||||
#content #toc > :first-child { margin-top: 0; }
|
||||
#content #toc > :last-child { margin-bottom: 0; }
|
||||
|
||||
#footer { max-width: 100%; background-color: #222222; padding: 1.25em; }
|
||||
|
||||
#footer-text { color: #dddddd; line-height: 1.44; }
|
||||
|
||||
.sect1 { padding-bottom: 0.625em; }
|
||||
|
||||
@media only screen and (min-width: 768px) { .sect1 { padding-bottom: 1.25em; } }
|
||||
.sect1 + .sect1 { border-top: 1px solid #dddddd; }
|
||||
|
||||
#content h1 > a.anchor, h2 > a.anchor, h3 > a.anchor, #toctitle > a.anchor, .sidebarblock > .content > .title > a.anchor, h4 > a.anchor, h5 > a.anchor, h6 > a.anchor { position: absolute; z-index: 1001; width: 1.5ex; margin-left: -1.5ex; display: block; text-decoration: none !important; visibility: hidden; text-align: center; font-weight: normal; }
|
||||
#content h1 > a.anchor:before, h2 > a.anchor:before, h3 > a.anchor:before, #toctitle > a.anchor:before, .sidebarblock > .content > .title > a.anchor:before, h4 > a.anchor:before, h5 > a.anchor:before, h6 > a.anchor:before { content: "\00A7"; font-size: 0.85em; display: block; padding-top: 0.1em; }
|
||||
#content h1:hover > a.anchor, #content h1 > a.anchor:hover, h2:hover > a.anchor, h2 > a.anchor:hover, h3:hover > a.anchor, #toctitle:hover > a.anchor, .sidebarblock > .content > .title:hover > a.anchor, h3 > a.anchor:hover, #toctitle > a.anchor:hover, .sidebarblock > .content > .title > a.anchor:hover, h4:hover > a.anchor, h4 > a.anchor:hover, h5:hover > a.anchor, h5 > a.anchor:hover, h6:hover > a.anchor, h6 > a.anchor:hover { visibility: visible; }
|
||||
#content h1 > a.link, h2 > a.link, h3 > a.link, #toctitle > a.link, .sidebarblock > .content > .title > a.link, h4 > a.link, h5 > a.link, h6 > a.link { color: #222222; text-decoration: none; }
|
||||
#content h1 > a.link:hover, h2 > a.link:hover, h3 > a.link:hover, #toctitle > a.link:hover, .sidebarblock > .content > .title > a.link:hover, h4 > a.link:hover, h5 > a.link:hover, h6 > a.link:hover { color: #151515; }
|
||||
|
||||
.audioblock, .imageblock, .literalblock, .listingblock, .stemblock, .videoblock { margin-bottom: 1.25em; }
|
||||
|
||||
.admonitionblock td.content > .title, .audioblock > .title, .exampleblock > .title, .imageblock > .title, .listingblock > .title, .literalblock > .title, .stemblock > .title, .openblock > .title, .paragraph > .title, .quoteblock > .title, table.tableblock > .title, .verseblock > .title, .videoblock > .title, .dlist > .title, .olist > .title, .ulist > .title, .qlist > .title, .hdlist > .title { text-rendering: optimizeLegibility; text-align: left; }
|
||||
|
||||
table.tableblock > caption.title { white-space: nowrap; overflow: visible; max-width: 0; }
|
||||
|
||||
.paragraph.lead > p, #preamble > .sectionbody > .paragraph:first-of-type p { color: black; }
|
||||
|
||||
table.tableblock #preamble > .sectionbody > .paragraph:first-of-type p { font-size: inherit; }
|
||||
|
||||
.admonitionblock > table { border-collapse: separate; border: 0; background: none; width: 100%; }
|
||||
.admonitionblock > table td.icon { text-align: center; width: 80px; }
|
||||
.admonitionblock > table td.icon img { max-width: initial; }
|
||||
.admonitionblock > table td.icon .title { font-weight: bold; font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif; text-transform: uppercase; }
|
||||
.admonitionblock > table td.content { padding-left: 1.125em; padding-right: 1.25em; border-left: 1px solid #dddddd; color: #555555; }
|
||||
.admonitionblock > table td.content > :last-child > :last-child { margin-bottom: 0; }
|
||||
|
||||
.exampleblock > .content { border-style: solid; border-width: 1px; border-color: #e6e6e6; margin-bottom: 1.25em; padding: 1.25em; background: white; -webkit-border-radius: 0; border-radius: 0; }
|
||||
.exampleblock > .content > :first-child { margin-top: 0; }
|
||||
.exampleblock > .content > :last-child { margin-bottom: 0; }
|
||||
|
||||
.sidebarblock { border-style: solid; border-width: 1px; border-color: #d9d9d9; margin-bottom: 1.25em; padding: 1.25em; background: #f2f2f2; -webkit-border-radius: 0; border-radius: 0; }
|
||||
.sidebarblock > :first-child { margin-top: 0; }
|
||||
.sidebarblock > :last-child { margin-bottom: 0; }
|
||||
.sidebarblock > .content > .title { color: #6f6f6f; margin-top: 0; }
|
||||
|
||||
.exampleblock > .content > :last-child > :last-child, .exampleblock > .content .olist > ol > li:last-child > :last-child, .exampleblock > .content .ulist > ul > li:last-child > :last-child, .exampleblock > .content .qlist > ol > li:last-child > :last-child, .sidebarblock > .content > :last-child > :last-child, .sidebarblock > .content .olist > ol > li:last-child > :last-child, .sidebarblock > .content .ulist > ul > li:last-child > :last-child, .sidebarblock > .content .qlist > ol > li:last-child > :last-child { margin-bottom: 0; }
|
||||
|
||||
.literalblock pre, .listingblock pre:not(.highlight), .listingblock pre[class="highlight"], .listingblock pre[class^="highlight "], .listingblock pre.CodeRay, .listingblock pre.prettyprint { background: #eeeeee; }
|
||||
.sidebarblock .literalblock pre, .sidebarblock .listingblock pre:not(.highlight), .sidebarblock .listingblock pre[class="highlight"], .sidebarblock .listingblock pre[class^="highlight "], .sidebarblock .listingblock pre.CodeRay, .sidebarblock .listingblock pre.prettyprint { background: #f2f1f1; }
|
||||
|
||||
.literalblock pre, .literalblock pre[class], .listingblock pre, .listingblock pre[class] { border: 1px solid #cccccc; -webkit-border-radius: 0; border-radius: 0; word-wrap: break-word; padding: 0.8em 0.8em 0.65em 0.8em; font-size: 0.8125em; }
|
||||
.literalblock pre.nowrap, .literalblock pre[class].nowrap, .listingblock pre.nowrap, .listingblock pre[class].nowrap { overflow-x: auto; white-space: pre; word-wrap: normal; }
|
||||
@media only screen and (min-width: 768px) { .literalblock pre, .literalblock pre[class], .listingblock pre, .listingblock pre[class] { font-size: 0.90625em; } }
|
||||
@media only screen and (min-width: 1280px) { .literalblock pre, .literalblock pre[class], .listingblock pre, .listingblock pre[class] { font-size: 1em; } }
|
||||
|
||||
.literalblock.output pre { color: #eeeeee; background-color: black; }
|
||||
|
||||
.listingblock pre.highlightjs { padding: 0; }
|
||||
.listingblock pre.highlightjs > code { padding: 0.8em 0.8em 0.65em 0.8em; -webkit-border-radius: 0; border-radius: 0; }
|
||||
|
||||
.listingblock > .content { position: relative; }
|
||||
|
||||
.listingblock code[data-lang]:before { display: none; content: attr(data-lang); position: absolute; font-size: 0.75em; top: 0.425rem; right: 0.5rem; line-height: 1; text-transform: uppercase; color: #999; }
|
||||
|
||||
.listingblock:hover code[data-lang]:before { display: block; }
|
||||
|
||||
.listingblock.terminal pre .command:before { content: attr(data-prompt); padding-right: 0.5em; color: #999; }
|
||||
|
||||
.listingblock.terminal pre .command:not([data-prompt]):before { content: "$"; }
|
||||
|
||||
table.pyhltable { border-collapse: separate; border: 0; margin-bottom: 0; background: none; }
|
||||
|
||||
table.pyhltable td { vertical-align: top; padding-top: 0; padding-bottom: 0; line-height: 1.4; }
|
||||
|
||||
table.pyhltable td.code { padding-left: .75em; padding-right: 0; }
|
||||
|
||||
pre.pygments .lineno, table.pyhltable td:not(.code) { color: #999; padding-left: 0; padding-right: .5em; border-right: 1px solid #dddddd; }
|
||||
|
||||
pre.pygments .lineno { display: inline-block; margin-right: .25em; }
|
||||
|
||||
table.pyhltable .linenodiv { background: none !important; padding-right: 0 !important; }
|
||||
|
||||
.quoteblock { margin: 0 1em 1.25em 1.5em; display: table; }
|
||||
.quoteblock > .title { margin-left: -1.5em; margin-bottom: 0.75em; }
|
||||
.quoteblock blockquote, .quoteblock blockquote p { color: #6f6f6f; font-size: 1.15rem; line-height: 1.75; word-spacing: 0.1em; letter-spacing: 0; font-style: italic; text-align: justify; }
|
||||
.quoteblock blockquote { margin: 0; padding: 0; border: 0; }
|
||||
.quoteblock blockquote:before { content: "\201c"; float: left; font-size: 2.75em; font-weight: bold; line-height: 0.6em; margin-left: -0.6em; color: #6f6f6f; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); }
|
||||
.quoteblock blockquote > .paragraph:last-child p { margin-bottom: 0; }
|
||||
.quoteblock .attribution { margin-top: 0.5em; margin-right: 0.5ex; text-align: right; }
|
||||
.quoteblock .quoteblock { margin-left: 0; margin-right: 0; padding: 0.5em 0; border-left: 3px solid #555555; }
|
||||
.quoteblock .quoteblock blockquote { padding: 0 0 0 0.75em; }
|
||||
.quoteblock .quoteblock blockquote:before { display: none; }
|
||||
|
||||
.verseblock { margin: 0 1em 1.25em 1em; }
|
||||
.verseblock pre { font-family: "Open Sans", "DejaVu Sans", sans; font-size: 1.15rem; color: #6f6f6f; font-weight: 300; text-rendering: optimizeLegibility; }
|
||||
.verseblock pre strong { font-weight: 400; }
|
||||
.verseblock .attribution { margin-top: 1.25rem; margin-left: 0.5ex; }
|
||||
|
||||
.quoteblock .attribution, .verseblock .attribution { font-size: 0.8125em; line-height: 1.45; font-style: italic; }
|
||||
.quoteblock .attribution br, .verseblock .attribution br { display: none; }
|
||||
.quoteblock .attribution cite, .verseblock .attribution cite { display: block; letter-spacing: -0.025em; color: #555555; }
|
||||
|
||||
.quoteblock.abstract { margin: 0 0 1.25em 0; display: block; }
|
||||
.quoteblock.abstract blockquote, .quoteblock.abstract blockquote p { text-align: left; word-spacing: 0; }
|
||||
.quoteblock.abstract blockquote:before, .quoteblock.abstract blockquote p:first-of-type:before { display: none; }
|
||||
|
||||
table.tableblock { max-width: 100%; border-collapse: separate; }
|
||||
table.tableblock td > .paragraph:last-child p > p:last-child, table.tableblock th > p:last-child, table.tableblock td > p:last-child { margin-bottom: 0; }
|
||||
|
||||
table.tableblock, th.tableblock, td.tableblock { border: 0 solid #dddddd; }
|
||||
|
||||
table.grid-all th.tableblock, table.grid-all td.tableblock { border-width: 0 1px 1px 0; }
|
||||
|
||||
table.grid-all tfoot > tr > th.tableblock, table.grid-all tfoot > tr > td.tableblock { border-width: 1px 1px 0 0; }
|
||||
|
||||
table.grid-cols th.tableblock, table.grid-cols td.tableblock { border-width: 0 1px 0 0; }
|
||||
|
||||
table.grid-all * > tr > .tableblock:last-child, table.grid-cols * > tr > .tableblock:last-child { border-right-width: 0; }
|
||||
|
||||
table.grid-rows th.tableblock, table.grid-rows td.tableblock { border-width: 0 0 1px 0; }
|
||||
|
||||
table.grid-all tbody > tr:last-child > th.tableblock, table.grid-all tbody > tr:last-child > td.tableblock, table.grid-all thead:last-child > tr > th.tableblock, table.grid-rows tbody > tr:last-child > th.tableblock, table.grid-rows tbody > tr:last-child > td.tableblock, table.grid-rows thead:last-child > tr > th.tableblock { border-bottom-width: 0; }
|
||||
|
||||
table.grid-rows tfoot > tr > th.tableblock, table.grid-rows tfoot > tr > td.tableblock { border-width: 1px 0 0 0; }
|
||||
|
||||
table.frame-all { border-width: 1px; }
|
||||
|
||||
table.frame-sides { border-width: 0 1px; }
|
||||
|
||||
table.frame-topbot { border-width: 1px 0; }
|
||||
|
||||
th.halign-left, td.halign-left { text-align: left; }
|
||||
|
||||
th.halign-right, td.halign-right { text-align: right; }
|
||||
|
||||
th.halign-center, td.halign-center { text-align: center; }
|
||||
|
||||
th.valign-top, td.valign-top { vertical-align: top; }
|
||||
|
||||
th.valign-bottom, td.valign-bottom { vertical-align: bottom; }
|
||||
|
||||
th.valign-middle, td.valign-middle { vertical-align: middle; }
|
||||
|
||||
table thead th, table tfoot th { font-weight: bold; }
|
||||
|
||||
tbody tr th { display: table-cell; line-height: 1.4; background: whitesmoke; }
|
||||
|
||||
tbody tr th, tbody tr th p, tfoot tr th, tfoot tr th p { color: #222222; font-weight: bold; }
|
||||
|
||||
p.tableblock > code:only-child { background: none; padding: 0; }
|
||||
|
||||
p.tableblock { font-size: 1em; }
|
||||
|
||||
td > div.verse { white-space: pre; }
|
||||
|
||||
ol { margin-left: 1.75em; }
|
||||
|
||||
ul li ol { margin-left: 1.5em; }
|
||||
|
||||
dl dd { margin-left: 1.125em; }
|
||||
|
||||
dl dd:last-child, dl dd:last-child > :last-child { margin-bottom: 0; }
|
||||
|
||||
ol > li p, ul > li p, ul dd, ol dd, .olist .olist, .ulist .ulist, .ulist .olist, .olist .ulist { margin-bottom: 0.625em; }
|
||||
|
||||
ul.unstyled, ol.unnumbered, ul.checklist, ul.none { list-style-type: none; }
|
||||
|
||||
ul.unstyled, ol.unnumbered, ul.checklist { margin-left: 0.625em; }
|
||||
|
||||
ul.checklist li > p:first-child > .fa-square-o:first-child, ul.checklist li > p:first-child > .fa-check-square-o:first-child { width: 1em; font-size: 0.85em; }
|
||||
|
||||
ul.checklist li > p:first-child > input[type="checkbox"]:first-child { width: 1em; position: relative; top: 1px; }
|
||||
|
||||
ul.inline { margin: 0 auto 0.625em auto; margin-left: -1.375em; margin-right: 0; padding: 0; list-style: none; overflow: hidden; }
|
||||
ul.inline > li { list-style: none; float: left; margin-left: 1.375em; display: block; }
|
||||
ul.inline > li > * { display: block; }
|
||||
|
||||
.unstyled dl dt { font-weight: normal; font-style: normal; }
|
||||
|
||||
ol.arabic { list-style-type: decimal; }
|
||||
|
||||
ol.decimal { list-style-type: decimal-leading-zero; }
|
||||
|
||||
ol.loweralpha { list-style-type: lower-alpha; }
|
||||
|
||||
ol.upperalpha { list-style-type: upper-alpha; }
|
||||
|
||||
ol.lowerroman { list-style-type: lower-roman; }
|
||||
|
||||
ol.upperroman { list-style-type: upper-roman; }
|
||||
|
||||
ol.lowergreek { list-style-type: lower-greek; }
|
||||
|
||||
.hdlist > table, .colist > table { border: 0; background: none; }
|
||||
.hdlist > table > tbody > tr, .colist > table > tbody > tr { background: none; }
|
||||
|
||||
td.hdlist1, td.hdlist2 { vertical-align: top; padding: 0 0.625em; }
|
||||
|
||||
td.hdlist1 { font-weight: bold; padding-bottom: 1.25em; }
|
||||
|
||||
.literalblock + .colist, .listingblock + .colist { margin-top: -0.5em; }
|
||||
|
||||
.colist > table tr > td:first-of-type { padding: 0 0.75em; line-height: 1; }
|
||||
.colist > table tr > td:first-of-type img { max-width: initial; }
|
||||
.colist > table tr > td:last-of-type { padding: 0.25em 0; }
|
||||
|
||||
.thumb, .th { line-height: 0; display: inline-block; border: solid 4px white; -webkit-box-shadow: 0 0 0 1px #dddddd; box-shadow: 0 0 0 1px #dddddd; }
|
||||
|
||||
.imageblock.left, .imageblock[style*="float: left"] { margin: 0.25em 0.625em 1.25em 0; }
|
||||
.imageblock.right, .imageblock[style*="float: right"] { margin: 0.25em 0 1.25em 0.625em; }
|
||||
.imageblock > .title { margin-bottom: 0; }
|
||||
.imageblock.thumb, .imageblock.th { border-width: 6px; }
|
||||
.imageblock.thumb > .title, .imageblock.th > .title { padding: 0 0.125em; }
|
||||
|
||||
.image.left, .image.right { margin-top: 0.25em; margin-bottom: 0.25em; display: inline-block; line-height: 0; }
|
||||
.image.left { margin-right: 0.625em; }
|
||||
.image.right { margin-left: 0.625em; }
|
||||
|
||||
a.image { text-decoration: none; display: inline-block; }
|
||||
a.image object { pointer-events: none; }
|
||||
|
||||
sup.footnote, sup.footnoteref { font-size: 0.875em; position: static; vertical-align: super; }
|
||||
sup.footnote a, sup.footnoteref a { text-decoration: none; }
|
||||
sup.footnote a:active, sup.footnoteref a:active { text-decoration: underline; }
|
||||
|
||||
#footnotes { padding-top: 0.75em; padding-bottom: 0.75em; margin-bottom: 0.625em; }
|
||||
#footnotes hr { width: 20%; min-width: 6.25em; margin: -0.25em 0 0.75em 0; border-width: 1px 0 0 0; }
|
||||
#footnotes .footnote { padding: 0 0.375em 0 0.225em; line-height: 1.3334; font-size: 0.875em; margin-left: 1.2em; text-indent: -1.05em; margin-bottom: 0.2em; }
|
||||
#footnotes .footnote a:first-of-type { font-weight: bold; text-decoration: none; }
|
||||
#footnotes .footnote:last-of-type { margin-bottom: 0; }
|
||||
#content #footnotes { margin-top: -0.625em; margin-bottom: 0; padding: 0.75em 0; }
|
||||
|
||||
.gist .file-data > table { border: 0; background: #fff; width: 100%; margin-bottom: 0; }
|
||||
.gist .file-data > table td.line-data { width: 99%; }
|
||||
|
||||
div.unbreakable { page-break-inside: avoid; }
|
||||
|
||||
.big { font-size: larger; }
|
||||
|
||||
.small { font-size: smaller; }
|
||||
|
||||
.underline { text-decoration: underline; }
|
||||
|
||||
.overline { text-decoration: overline; }
|
||||
|
||||
.line-through { text-decoration: line-through; }
|
||||
|
||||
.aqua { color: #00bfbf; }
|
||||
|
||||
.aqua-background { background-color: #00fafa; }
|
||||
|
||||
.black { color: black; }
|
||||
|
||||
.black-background { background-color: black; }
|
||||
|
||||
.blue { color: #0000bf; }
|
||||
|
||||
.blue-background { background-color: #0000fa; }
|
||||
|
||||
.fuchsia { color: #bf00bf; }
|
||||
|
||||
.fuchsia-background { background-color: #fa00fa; }
|
||||
|
||||
.gray { color: #606060; }
|
||||
|
||||
.gray-background { background-color: #7d7d7d; }
|
||||
|
||||
.green { color: #006000; }
|
||||
|
||||
.green-background { background-color: #007d00; }
|
||||
|
||||
.lime { color: #00bf00; }
|
||||
|
||||
.lime-background { background-color: #00fa00; }
|
||||
|
||||
.maroon { color: #600000; }
|
||||
|
||||
.maroon-background { background-color: #7d0000; }
|
||||
|
||||
.navy { color: #000060; }
|
||||
|
||||
.navy-background { background-color: #00007d; }
|
||||
|
||||
.olive { color: #606000; }
|
||||
|
||||
.olive-background { background-color: #7d7d00; }
|
||||
|
||||
.purple { color: #600060; }
|
||||
|
||||
.purple-background { background-color: #7d007d; }
|
||||
|
||||
.red { color: #bf0000; }
|
||||
|
||||
.red-background { background-color: #fa0000; }
|
||||
|
||||
.silver { color: #909090; }
|
||||
|
||||
.silver-background { background-color: #bcbcbc; }
|
||||
|
||||
.teal { color: #006060; }
|
||||
|
||||
.teal-background { background-color: #007d7d; }
|
||||
|
||||
.white { color: #bfbfbf; }
|
||||
|
||||
.white-background { background-color: #fafafa; }
|
||||
|
||||
.yellow { color: #bfbf00; }
|
||||
|
||||
.yellow-background { background-color: #fafa00; }
|
||||
|
||||
span.icon > .fa { cursor: default; }
|
||||
|
||||
.admonitionblock td.icon [class^="fa icon-"] { font-size: 2.5em; text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5); cursor: default; }
|
||||
.admonitionblock td.icon .icon-note:before { content: "\f05a"; color: #207c98; }
|
||||
.admonitionblock td.icon .icon-tip:before { content: "\f0eb"; text-shadow: 1px 1px 2px rgba(155, 155, 0, 0.8); color: #111; }
|
||||
.admonitionblock td.icon .icon-warning:before { content: "\f071"; color: #bf6900; }
|
||||
.admonitionblock td.icon .icon-caution:before { content: "\f06d"; color: #bf3400; }
|
||||
.admonitionblock td.icon .icon-important:before { content: "\f06a"; color: #bf0000; }
|
||||
|
||||
.conum[data-value] { display: inline-block; color: #fff !important; background-color: #222222; -webkit-border-radius: 100px; border-radius: 100px; text-align: center; font-size: 0.75em; width: 1.67em; height: 1.67em; line-height: 1.67em; font-family: "Open Sans", "DejaVu Sans", sans-serif; font-style: normal; font-weight: bold; }
|
||||
.conum[data-value] * { color: #fff !important; }
|
||||
.conum[data-value] + b { display: none; }
|
||||
.conum[data-value]:after { content: attr(data-value); }
|
||||
pre .conum[data-value] { position: relative; top: -0.125em; }
|
||||
|
||||
b.conum * { color: inherit !important; }
|
||||
|
||||
.conum:not([data-value]):empty { display: none; }
|
||||
|
||||
.literalblock pre, .listingblock pre { background: #eeeeee; }
|
11
src/docs/asciidoc/index.adoc
Normal file
11
src/docs/asciidoc/index.adoc
Normal file
|
@ -0,0 +1,11 @@
|
|||
= Net classes for Java
|
||||
Jörg Prante
|
||||
Version 1.0
|
||||
:sectnums:
|
||||
:toc: preamble
|
||||
:toclevels: 4
|
||||
:!toc-title: Content
|
||||
:experimental:
|
||||
:description: Net classes for URL
|
||||
:keywords: Java, Net, URL, URI, IRI
|
||||
:icons: font
|
4
src/docs/asciidoclet/overview.adoc
Normal file
4
src/docs/asciidoclet/overview.adoc
Normal file
|
@ -0,0 +1,4 @@
|
|||
= Net classes for Java
|
||||
Jörg Prante
|
||||
Version 1.0
|
||||
|
18
src/main/java/org/xbib/net/IRISyntaxException.java
Normal file
18
src/main/java/org/xbib/net/IRISyntaxException.java
Normal file
|
@ -0,0 +1,18 @@
|
|||
package org.xbib.net;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class IRISyntaxException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1813084470937980392L;
|
||||
|
||||
IRISyntaxException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
IRISyntaxException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
}
|
182
src/main/java/org/xbib/net/PercentDecoder.java
Normal file
182
src/main/java/org/xbib/net/PercentDecoder.java
Normal file
|
@ -0,0 +1,182 @@
|
|||
package org.xbib.net;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.CharsetDecoder;
|
||||
import java.nio.charset.CoderResult;
|
||||
import java.nio.charset.CodingErrorAction;
|
||||
import java.nio.charset.MalformedInputException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.charset.UnmappableCharacterException;
|
||||
|
||||
/**
|
||||
* Decodes percent-encoded strings.
|
||||
*/
|
||||
public class PercentDecoder {
|
||||
|
||||
/**
|
||||
* Written to with decoded chars by decoder.
|
||||
*/
|
||||
private final CharBuffer decodedCharBuf;
|
||||
|
||||
private final CharsetDecoder decoder;
|
||||
|
||||
/**
|
||||
* The decoded string for the current input.
|
||||
*/
|
||||
private final StringBuilder outputBuf;
|
||||
|
||||
/**
|
||||
* The bytes represented by the current sequence of %-triples. Resized as needed.
|
||||
*/
|
||||
private ByteBuffer encodedBuf;
|
||||
|
||||
public PercentDecoder() {
|
||||
this(StandardCharsets.UTF_8.newDecoder().onMalformedInput(CodingErrorAction.REPORT));
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new PercentDecoder with default buffer sizes.
|
||||
*
|
||||
* @param charsetDecoder Charset to decode bytes into chars with
|
||||
* @see PercentDecoder#PercentDecoder(CharsetDecoder, int, int)
|
||||
*/
|
||||
public PercentDecoder(CharsetDecoder charsetDecoder) {
|
||||
this(charsetDecoder, 16, 16);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param charsetDecoder Charset to decode bytes into chars with
|
||||
* @param initialEncodedByteBufSize Initial size of buffer that holds encoded bytes
|
||||
* @param decodedCharBufSize Size of buffer that encoded bytes are decoded into
|
||||
*/
|
||||
public PercentDecoder(CharsetDecoder charsetDecoder, int initialEncodedByteBufSize,
|
||||
int decodedCharBufSize) {
|
||||
this.outputBuf = new StringBuilder();
|
||||
this.encodedBuf = ByteBuffer.allocate(initialEncodedByteBufSize);
|
||||
this.decodedCharBuf = CharBuffer.allocate(decodedCharBufSize);
|
||||
this.decoder = charsetDecoder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a percent-encoded character sequuence to a string.
|
||||
*
|
||||
* @param input Input with %-encoded representation of characters in this instance's configured character set, e.g.
|
||||
* "%20" for a space character
|
||||
* @return Corresponding string with %-encoded data decoded and converted to their corresponding characters
|
||||
* @throws MalformedInputException if decoder is configured to report errors and malformed input is detected
|
||||
* @throws UnmappableCharacterException if decoder is configured to report errors and an unmappable character is
|
||||
* detected
|
||||
*/
|
||||
public String decode(CharSequence input) throws MalformedInputException, UnmappableCharacterException {
|
||||
if (input == null) {
|
||||
return null;
|
||||
}
|
||||
outputBuf.setLength(0);
|
||||
outputBuf.ensureCapacity((input.length() / 8));
|
||||
encodedBuf.clear();
|
||||
for (int i = 0; i < input.length(); i++) {
|
||||
char c = input.charAt(i);
|
||||
if (c != '%') {
|
||||
handleEncodedBytes();
|
||||
outputBuf.append(c);
|
||||
continue;
|
||||
}
|
||||
if (i + 2 >= input.length()) {
|
||||
throw new IllegalArgumentException("could not percent decode <"
|
||||
+ input
|
||||
+ ">: incomplete %-pair at position " + i);
|
||||
}
|
||||
if (encodedBuf.remaining() == 0) {
|
||||
ByteBuffer largerBuf = ByteBuffer.allocate(encodedBuf.capacity() * 2);
|
||||
encodedBuf.flip();
|
||||
largerBuf.put(encodedBuf);
|
||||
encodedBuf = largerBuf;
|
||||
}
|
||||
int c1 = input.charAt(++i);
|
||||
int c2 = input.charAt(++i);
|
||||
encodedBuf.put(decode((char) c1, (char) c2));
|
||||
}
|
||||
handleEncodedBytes();
|
||||
return outputBuf.toString();
|
||||
}
|
||||
|
||||
private static byte decode(char c1, char c2) {
|
||||
byte b1 = (byte) decode(c1);
|
||||
byte b2 = (byte) decode(c2);
|
||||
if (b1 == -1 || b2 == -1) {
|
||||
throw new IllegalArgumentException("invalid %-tuple <%" + c1 + c2 + ">");
|
||||
}
|
||||
return (byte) ((b1 & 0xf) << 4 | (b2 & 0xf));
|
||||
}
|
||||
|
||||
private static int decode(char c) {
|
||||
return (c >= '0' && c <= '9') ? c - '0' :
|
||||
(c >= 'A' && c <= 'F') ? c - 'A' + 10 :
|
||||
(c >= 'a' && c <= 'f') ? c - 'a' + 10 : -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode any buffered encoded bytes and write them to the output buf.
|
||||
*/
|
||||
private void handleEncodedBytes() throws MalformedInputException, UnmappableCharacterException {
|
||||
if (encodedBuf.position() == 0) {
|
||||
return;
|
||||
}
|
||||
decoder.reset();
|
||||
CoderResult coderResult = CoderResult.OVERFLOW;
|
||||
encodedBuf.flip();
|
||||
while (coderResult == CoderResult.OVERFLOW && encodedBuf.hasRemaining()) {
|
||||
decodedCharBuf.clear();
|
||||
coderResult = decoder.decode(encodedBuf, decodedCharBuf, false);
|
||||
throwIfError(coderResult);
|
||||
decodedCharBuf.flip();
|
||||
outputBuf.append(decodedCharBuf);
|
||||
}
|
||||
decodedCharBuf.clear();
|
||||
coderResult = decoder.decode(encodedBuf, decodedCharBuf, true);
|
||||
throwIfError(coderResult);
|
||||
if (encodedBuf.hasRemaining()) {
|
||||
throw new IllegalStateException("final decode didn't error, but didn't consume remaining input bytes");
|
||||
}
|
||||
if (coderResult != CoderResult.UNDERFLOW) {
|
||||
throw new IllegalStateException("expected underflow, but instead final decode returned " + coderResult);
|
||||
}
|
||||
decodedCharBuf.flip();
|
||||
outputBuf.append(decodedCharBuf);
|
||||
encodedBuf.clear();
|
||||
flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Must only be called when the input encoded bytes buffer is empty.
|
||||
*/
|
||||
private void flush() throws MalformedInputException, UnmappableCharacterException {
|
||||
CoderResult coderResult;
|
||||
decodedCharBuf.clear();
|
||||
coderResult = decoder.flush(decodedCharBuf);
|
||||
decodedCharBuf.flip();
|
||||
outputBuf.append(decodedCharBuf);
|
||||
throwIfError(coderResult);
|
||||
if (coderResult != CoderResult.UNDERFLOW) {
|
||||
throw new IllegalStateException("decoder flush resulted in " + coderResult);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the coder result is considered an error (i.e. not overflow or underflow), throw the corresponding
|
||||
* CharacterCodingException.
|
||||
*
|
||||
* @param coderResult result to check
|
||||
* @throws MalformedInputException if result represents malformed input
|
||||
* @throws UnmappableCharacterException if result represents an unmappable character
|
||||
*/
|
||||
private static void throwIfError(CoderResult coderResult) throws MalformedInputException, UnmappableCharacterException {
|
||||
if (coderResult.isMalformed()) {
|
||||
throw new MalformedInputException(coderResult.length());
|
||||
}
|
||||
if (coderResult.isUnmappable()) {
|
||||
throw new UnmappableCharacterException(coderResult.length());
|
||||
}
|
||||
}
|
||||
}
|
176
src/main/java/org/xbib/net/PercentEncoder.java
Normal file
176
src/main/java/org/xbib/net/PercentEncoder.java
Normal file
|
@ -0,0 +1,176 @@
|
|||
package org.xbib.net;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.CharsetEncoder;
|
||||
import java.nio.charset.CoderResult;
|
||||
import java.nio.charset.MalformedInputException;
|
||||
import java.nio.charset.UnmappableCharacterException;
|
||||
import java.util.BitSet;
|
||||
|
||||
/**
|
||||
* Encodes unsafe characters as a sequence of %XX hex-encoded bytes.
|
||||
*
|
||||
* This is typically done when encoding components of URLs. See {@link PercentEncoders} for pre-configured
|
||||
* PercentEncoder instances.
|
||||
*/
|
||||
public class PercentEncoder {
|
||||
|
||||
private static final char[] HEX_CODE = "0123456789ABCDEF".toCharArray();
|
||||
|
||||
private final BitSet safeChars;
|
||||
|
||||
private final CharsetEncoder encoder;
|
||||
|
||||
private final StringBuilderPercentEncoderOutputHandler stringHandler;
|
||||
|
||||
private final ByteBuffer encodedBytes;
|
||||
|
||||
private final CharBuffer unsafeCharsToEncode;
|
||||
|
||||
/**
|
||||
* @param safeChars the set of chars to NOT encode, stored as a bitset with the int positions corresponding to
|
||||
* those chars set to true. Treated as read only.
|
||||
* @param charsetEncoder charset encoder to encode characters with. Make sure to not re-use CharsetEncoder instances
|
||||
* across threads.
|
||||
*/
|
||||
PercentEncoder(BitSet safeChars, CharsetEncoder charsetEncoder) {
|
||||
this.safeChars = safeChars;
|
||||
this.encoder = charsetEncoder;
|
||||
this.stringHandler = new StringBuilderPercentEncoderOutputHandler();
|
||||
int maxBytesPerChar = 1 + (int) encoder.maxBytesPerChar();
|
||||
encodedBytes = ByteBuffer.allocate(maxBytesPerChar * 2);
|
||||
unsafeCharsToEncode = CharBuffer.allocate(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the input and pass output chars to a handler.
|
||||
*
|
||||
* @param input input string
|
||||
* @param handler handler to call on each output character
|
||||
* @throws MalformedInputException if encoder is configured to report errors and malformed input is detected
|
||||
* @throws UnmappableCharacterException if encoder is configured to report errors and an unmappable character is
|
||||
* detected
|
||||
*/
|
||||
private void encode(CharSequence input, StringBuilderPercentEncoderOutputHandler handler)
|
||||
throws MalformedInputException, UnmappableCharacterException {
|
||||
for (int i = 0; i < input.length(); i++) {
|
||||
char c = input.charAt(i);
|
||||
int cp = Character.codePointAt(String.valueOf(c), 0);
|
||||
if (safeChars.get(cp)) {
|
||||
handler.onOutputChar(c);
|
||||
continue;
|
||||
}
|
||||
unsafeCharsToEncode.clear();
|
||||
unsafeCharsToEncode.append(c);
|
||||
if (Character.isHighSurrogate(c)) {
|
||||
if (input.length() > i + 1) {
|
||||
char lowSurrogate = input.charAt(i + 1);
|
||||
if (Character.isLowSurrogate(lowSurrogate)) {
|
||||
unsafeCharsToEncode.append(lowSurrogate);
|
||||
i++;
|
||||
} else {
|
||||
throw new IllegalArgumentException("invalid UTF-16: character "
|
||||
+ i + " is a high surrogate (\\u"
|
||||
+ Integer.toHexString(cp) + "), but char " + (i + 1)
|
||||
+ " is not a low surrogate (\\u"
|
||||
+ Integer.toHexString(Character.codePointAt(String.valueOf(lowSurrogate), 0)) + ")");
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("invalid UTF-16: the last character in the input string "
|
||||
+ "was a high surrogate (\\u" + Integer.toHexString(cp) + ")");
|
||||
}
|
||||
}
|
||||
flushUnsafeCharBuffer(handler);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the input and return the resulting text as a String.
|
||||
*
|
||||
* @param input input string
|
||||
* @return the input string with every character that's not in safeChars turned into its byte representation via the
|
||||
* instance's encoder and then percent-encoded
|
||||
* @throws MalformedInputException if encoder is configured to report errors and malformed input is detected
|
||||
* @throws UnmappableCharacterException if encoder is configured to report errors and an unmappable character is
|
||||
* detected
|
||||
*/
|
||||
public String encode(CharSequence input) throws MalformedInputException, UnmappableCharacterException {
|
||||
if (input == null) {
|
||||
return null;
|
||||
}
|
||||
stringHandler.reset();
|
||||
stringHandler.ensureCapacity(input.length());
|
||||
encode(input, stringHandler);
|
||||
return stringHandler.getContents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode unsafeCharsToEncode to bytes as per charsetEncoder, then percent-encode those bytes into output.
|
||||
*
|
||||
* Side effects: unsafeCharsToEncode will be read from and cleared. encodedBytes will be cleared and written to.
|
||||
*
|
||||
* @param handler where the encoded versions of the contents of unsafeCharsToEncode will be written
|
||||
*/
|
||||
private void flushUnsafeCharBuffer(StringBuilderPercentEncoderOutputHandler handler)
|
||||
throws MalformedInputException, UnmappableCharacterException {
|
||||
// need to read from the char buffer, which was most recently written to
|
||||
unsafeCharsToEncode.flip();
|
||||
encodedBytes.clear();
|
||||
encoder.reset();
|
||||
CoderResult result = encoder.encode(unsafeCharsToEncode, encodedBytes, true);
|
||||
throwIfError(result);
|
||||
result = encoder.flush(encodedBytes);
|
||||
throwIfError(result);
|
||||
encodedBytes.flip();
|
||||
while (encodedBytes.hasRemaining()) {
|
||||
byte b = encodedBytes.get();
|
||||
handler.onOutputChar('%');
|
||||
handler.onOutputChar(HEX_CODE[b >> 4 & 0xF]);
|
||||
handler.onOutputChar(HEX_CODE[b & 0xF]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param result result to check
|
||||
* @throws IllegalStateException if result is overflow
|
||||
* @throws MalformedInputException if result represents malformed input
|
||||
* @throws UnmappableCharacterException if result represents an unmappable character
|
||||
*/
|
||||
private static void throwIfError(CoderResult result) throws MalformedInputException, UnmappableCharacterException {
|
||||
if (result.isOverflow()) {
|
||||
throw new IllegalStateException("Byte buffer overflow, this should not happen");
|
||||
}
|
||||
if (result.isMalformed()) {
|
||||
throw new MalformedInputException(result.length());
|
||||
}
|
||||
if (result.isUnmappable()) {
|
||||
throw new UnmappableCharacterException(result.length());
|
||||
}
|
||||
}
|
||||
|
||||
static class StringBuilderPercentEncoderOutputHandler {
|
||||
|
||||
private final StringBuilder stringBuilder;
|
||||
|
||||
StringBuilderPercentEncoderOutputHandler() {
|
||||
stringBuilder = new StringBuilder();
|
||||
}
|
||||
|
||||
String getContents() {
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
stringBuilder.setLength(0);
|
||||
}
|
||||
|
||||
void ensureCapacity(int length) {
|
||||
stringBuilder.ensureCapacity(length);
|
||||
}
|
||||
|
||||
void onOutputChar(char c) {
|
||||
stringBuilder.append(c);
|
||||
}
|
||||
}
|
||||
}
|
172
src/main/java/org/xbib/net/PercentEncoders.java
Normal file
172
src/main/java/org/xbib/net/PercentEncoders.java
Normal file
|
@ -0,0 +1,172 @@
|
|||
package org.xbib.net;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.BitSet;
|
||||
|
||||
import static java.nio.charset.CodingErrorAction.REPORT;
|
||||
|
||||
/**
|
||||
* See RFC 3986, RFC 1738 and http://www.lunatech-research.com/archives/2009/02/03/what-every-web-developer-must-know-about-url-encoding.
|
||||
*/
|
||||
public class PercentEncoders {
|
||||
|
||||
private static final BitSet UNRESERVED_BIT_SET = new BitSet();
|
||||
/**
|
||||
* an encoder for RFC 3986 reg-names.
|
||||
*/
|
||||
private static final BitSet REG_NAME_BIT_SET = new BitSet();
|
||||
private static final BitSet PATH_BIT_SET = new BitSet();
|
||||
private static final BitSet MATRIX_BIT_SET = new BitSet();
|
||||
private static final BitSet QUERY_BIT_SET = new BitSet();
|
||||
private static final BitSet QUERY_PARAM_BIT_SET = new BitSet();
|
||||
private static final BitSet FRAGMENT_BIT_SET = new BitSet();
|
||||
|
||||
static {
|
||||
// minimal encoding, for URI templates RFC 6570
|
||||
addUnreserved(UNRESERVED_BIT_SET);
|
||||
// RFC 3986 'reg-name'. This is not very aggressive.
|
||||
// It's quite possible to have DNS-illegal names out of this.
|
||||
// Regardless, it will at least be URI-compliant even if it's not HTTP URL-compliant.
|
||||
addUnreserved(REG_NAME_BIT_SET);
|
||||
addSubdelims(REG_NAME_BIT_SET);
|
||||
// Represents RFC 3986 'pchar'. Remove delimiter that starts matrix section.
|
||||
addPChar(PATH_BIT_SET);
|
||||
PATH_BIT_SET.clear((int) ';');
|
||||
// Remove delims for HTTP matrix params as per RFC 1738 S3.3.
|
||||
// The other reserved chars ('/' and '?') are already excluded.
|
||||
addPChar(MATRIX_BIT_SET);
|
||||
MATRIX_BIT_SET.clear((int) ';');
|
||||
MATRIX_BIT_SET.clear((int) '=');
|
||||
/*
|
||||
* At this point it represents RFC 3986 'query'. http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1 also
|
||||
* specifies that "+" can mean space in a query, so we will make sure to say that '+' is not safe to leave as-is
|
||||
*/
|
||||
addQuery(QUERY_BIT_SET);
|
||||
QUERY_BIT_SET.clear((int) '+');
|
||||
/*
|
||||
* Create more stringent requirements for HTML4 queries: remove delimiters for HTML query params so that key=value
|
||||
* pairs can be used.
|
||||
*/
|
||||
QUERY_PARAM_BIT_SET.or(QUERY_BIT_SET);
|
||||
QUERY_PARAM_BIT_SET.clear((int) '=');
|
||||
QUERY_PARAM_BIT_SET.clear((int) '&');
|
||||
addFragment(FRAGMENT_BIT_SET);
|
||||
}
|
||||
|
||||
public static PercentEncoder getUnreservedEncoder(Charset charset) {
|
||||
return new PercentEncoder(UNRESERVED_BIT_SET,
|
||||
charset.newEncoder().onMalformedInput(REPORT).onUnmappableCharacter(REPORT));
|
||||
}
|
||||
|
||||
public static PercentEncoder getCookieEncoder(Charset charset) {
|
||||
return new PercentEncoder(UNRESERVED_BIT_SET,
|
||||
charset.newEncoder().onMalformedInput(REPORT).onUnmappableCharacter(REPORT));
|
||||
}
|
||||
|
||||
public static PercentEncoder getRegNameEncoder(Charset charset) {
|
||||
return new PercentEncoder(REG_NAME_BIT_SET,
|
||||
charset.newEncoder().onMalformedInput(REPORT).onUnmappableCharacter(REPORT));
|
||||
}
|
||||
|
||||
public static PercentEncoder getPathEncoder(Charset charset) {
|
||||
return new PercentEncoder(PATH_BIT_SET,
|
||||
charset.newEncoder().onMalformedInput(REPORT).onUnmappableCharacter(REPORT));
|
||||
}
|
||||
|
||||
public static PercentEncoder getMatrixEncoder(Charset charset) {
|
||||
return new PercentEncoder(MATRIX_BIT_SET,
|
||||
charset.newEncoder().onMalformedInput(REPORT).onUnmappableCharacter(REPORT));
|
||||
}
|
||||
|
||||
public static PercentEncoder getQueryEncoder(Charset charset) {
|
||||
return new PercentEncoder(QUERY_BIT_SET,
|
||||
charset.newEncoder().onMalformedInput(REPORT).onUnmappableCharacter(REPORT));
|
||||
}
|
||||
|
||||
public static PercentEncoder getQueryParamEncoder(Charset charset) {
|
||||
return new PercentEncoder(QUERY_PARAM_BIT_SET,
|
||||
charset.newEncoder().onMalformedInput(REPORT).onUnmappableCharacter(REPORT));
|
||||
}
|
||||
|
||||
public static PercentEncoder getFragmentEncoder(Charset charset) {
|
||||
return new PercentEncoder(FRAGMENT_BIT_SET,
|
||||
charset.newEncoder().onMalformedInput(REPORT).onUnmappableCharacter(REPORT));
|
||||
}
|
||||
|
||||
private PercentEncoders() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add code points for 'fragment' chars.
|
||||
*
|
||||
* @param fragmentBitSet bit set
|
||||
*/
|
||||
private static void addFragment(BitSet fragmentBitSet) {
|
||||
addPChar(fragmentBitSet);
|
||||
fragmentBitSet.set((int) '/');
|
||||
fragmentBitSet.set((int) '?');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add code points for 'query' chars.
|
||||
*
|
||||
* @param queryBitSet bit set
|
||||
*/
|
||||
private static void addQuery(BitSet queryBitSet) {
|
||||
addPChar(queryBitSet);
|
||||
queryBitSet.set((int) '/');
|
||||
queryBitSet.set((int) '?');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add code points for 'pchar' chars.
|
||||
*
|
||||
* @param bs bitset
|
||||
*/
|
||||
private static void addPChar(BitSet bs) {
|
||||
addUnreserved(bs);
|
||||
addSubdelims(bs);
|
||||
bs.set((int) ':');
|
||||
bs.set((int) '@');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add codepoints for 'unreserved' chars.
|
||||
*
|
||||
* @param bs bitset to add codepoints to
|
||||
*/
|
||||
private static void addUnreserved(BitSet bs) {
|
||||
for (int i = 'a'; i <= 'z'; i++) {
|
||||
bs.set(i);
|
||||
}
|
||||
for (int i = 'A'; i <= 'Z'; i++) {
|
||||
bs.set(i);
|
||||
}
|
||||
for (int i = '0'; i <= '9'; i++) {
|
||||
bs.set(i);
|
||||
}
|
||||
bs.set((int) '-');
|
||||
bs.set((int) '.');
|
||||
bs.set((int) '_');
|
||||
bs.set((int) '~');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add codepoints for 'sub-delims' chars.
|
||||
*
|
||||
* @param bs bitset to add codepoints to
|
||||
*/
|
||||
private static void addSubdelims(BitSet bs) {
|
||||
bs.set((int) '!');
|
||||
bs.set((int) '$');
|
||||
bs.set((int) '&');
|
||||
bs.set((int) '\'');
|
||||
bs.set((int) '(');
|
||||
bs.set((int) ')');
|
||||
bs.set((int) '*');
|
||||
bs.set((int) '+');
|
||||
bs.set((int) ',');
|
||||
bs.set((int) ';');
|
||||
bs.set((int) '=');
|
||||
}
|
||||
}
|
9
src/main/java/org/xbib/net/ProtocolVersion.java
Normal file
9
src/main/java/org/xbib/net/ProtocolVersion.java
Normal file
|
@ -0,0 +1,9 @@
|
|||
package org.xbib.net;
|
||||
|
||||
/**
|
||||
* The TCP/IP network protocol versions.
|
||||
*/
|
||||
public enum ProtocolVersion {
|
||||
|
||||
IPV4, IPV6, IPV46, NONE
|
||||
}
|
67
src/main/java/org/xbib/net/QueryParameters.java
Normal file
67
src/main/java/org/xbib/net/QueryParameters.java
Normal file
|
@ -0,0 +1,67 @@
|
|||
package org.xbib.net;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class QueryParameters extends ArrayList<QueryParameters.Pair<String, String>> {
|
||||
|
||||
private static final long serialVersionUID = 1195469379836789386L;
|
||||
|
||||
private final int max;
|
||||
|
||||
public QueryParameters() {
|
||||
this(1024);
|
||||
}
|
||||
|
||||
public QueryParameters(int max) {
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
public List<String> get(String key) {
|
||||
return stream()
|
||||
.filter(p -> key.equals(p.getFirst()))
|
||||
.map(Pair::getSecond)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public QueryParameters add(String name, String value) {
|
||||
add(new Pair<>(name, value));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(QueryParameters.Pair<String, String> element) {
|
||||
return size() < max && super.add(element);
|
||||
}
|
||||
|
||||
/**
|
||||
* A pair of query parameters.
|
||||
* @param <K> the key type parameter
|
||||
* @param <V> the value type parameter
|
||||
*/
|
||||
public static class Pair<K, V> {
|
||||
private final K first;
|
||||
private final V second;
|
||||
|
||||
public Pair(K first, V second) {
|
||||
this.first = first;
|
||||
this.second = second;
|
||||
}
|
||||
|
||||
public K getFirst() {
|
||||
return first;
|
||||
}
|
||||
|
||||
public V getSecond() {
|
||||
return second;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return first + ":" + second;
|
||||
}
|
||||
}
|
||||
}
|
118
src/main/java/org/xbib/net/SimpleNamespaceContext.java
Normal file
118
src/main/java/org/xbib/net/SimpleNamespaceContext.java
Normal file
|
@ -0,0 +1,118 @@
|
|||
package org.xbib.net;
|
||||
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Locale;
|
||||
import java.util.MissingResourceException;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.Set;
|
||||
import java.util.SortedMap;
|
||||
import java.util.TreeMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Contains a simple context for namespaces.
|
||||
*/
|
||||
public class SimpleNamespaceContext {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(SimpleNamespaceContext.class.getName());
|
||||
|
||||
private static final String DEFAULT_RESOURCE =
|
||||
SimpleNamespaceContext.class.getPackage().getName().replace('.', '/') + '/' + "namespace";
|
||||
|
||||
private static final SimpleNamespaceContext DEFAULT_CONTEXT = newDefaultInstance();
|
||||
|
||||
// sort namespace by length in descending order, useful for compacting prefix
|
||||
protected final SortedMap<String, String> namespaces = new TreeMap<>();
|
||||
|
||||
private final SortedMap<String, Set<String>> prefixes = new TreeMap<>();
|
||||
|
||||
protected SimpleNamespaceContext() {
|
||||
}
|
||||
|
||||
protected SimpleNamespaceContext(ResourceBundle bundle) {
|
||||
Enumeration<String> en = bundle.getKeys();
|
||||
while (en.hasMoreElements()) {
|
||||
String prefix = en.nextElement();
|
||||
String namespace = bundle.getString(prefix);
|
||||
addNamespace(prefix, namespace);
|
||||
}
|
||||
}
|
||||
|
||||
public static SimpleNamespaceContext getInstance() {
|
||||
return DEFAULT_CONTEXT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty namespace context.
|
||||
*
|
||||
* @return an XML namespace context
|
||||
*/
|
||||
public static SimpleNamespaceContext newInstance() {
|
||||
return new SimpleNamespaceContext();
|
||||
}
|
||||
|
||||
public static SimpleNamespaceContext newDefaultInstance() {
|
||||
return newInstance(DEFAULT_RESOURCE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use thread context class laoder to instantiate a namespace context.
|
||||
* @param bundleName the resource bundle name
|
||||
* @return XML namespace context
|
||||
*/
|
||||
public static SimpleNamespaceContext newInstance(String bundleName) {
|
||||
return newInstance(bundleName, Locale.getDefault(), Thread.currentThread().getContextClassLoader());
|
||||
}
|
||||
|
||||
public static SimpleNamespaceContext newInstance(String bundleName, Locale locale, ClassLoader classLoader) {
|
||||
try {
|
||||
return new SimpleNamespaceContext(ResourceBundle.getBundle(bundleName, locale, classLoader));
|
||||
} catch (MissingResourceException e) {
|
||||
logger.log(Level.WARNING, e.getMessage(), e);
|
||||
return new SimpleNamespaceContext();
|
||||
}
|
||||
}
|
||||
|
||||
public void addNamespace(String prefix, String namespace) {
|
||||
namespaces.put(prefix, namespace);
|
||||
if (prefixes.containsKey(namespace)) {
|
||||
prefixes.get(namespace).add(prefix);
|
||||
} else {
|
||||
Set<String> set = new HashSet<>();
|
||||
set.add(prefix);
|
||||
prefixes.put(namespace, set);
|
||||
}
|
||||
}
|
||||
|
||||
public SortedMap<String, String> getNamespaces() {
|
||||
return namespaces;
|
||||
}
|
||||
|
||||
public String getNamespaceURI(String prefix) {
|
||||
if (prefix == null) {
|
||||
return null;
|
||||
}
|
||||
return namespaces.getOrDefault(prefix, null);
|
||||
}
|
||||
|
||||
public String getPrefix(String namespaceURI) {
|
||||
Iterator<String> it = getPrefixes(namespaceURI);
|
||||
return it != null && it.hasNext() ? it.next() : null;
|
||||
}
|
||||
|
||||
public Iterator<String> getPrefixes(String namespace) {
|
||||
if (namespace == null) {
|
||||
throw new IllegalArgumentException("namespace URI cannot be null");
|
||||
}
|
||||
return prefixes.containsKey(namespace) ?
|
||||
prefixes.get(namespace).iterator() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return namespaces.toString();
|
||||
}
|
||||
}
|
1259
src/main/java/org/xbib/net/URL.java
Executable file
1259
src/main/java/org/xbib/net/URL.java
Executable file
File diff suppressed because it is too large
Load diff
26
src/main/java/org/xbib/net/internal/LRUCache.java
Normal file
26
src/main/java/org/xbib/net/internal/LRUCache.java
Normal file
|
@ -0,0 +1,26 @@
|
|||
package org.xbib.net.internal;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A simple LRU cache, based on a {@link LinkedHashMap}.
|
||||
*
|
||||
* @param <K> the key type parameter
|
||||
* @param <V> the vale type parameter
|
||||
*/
|
||||
public class LRUCache<K, V> extends LinkedHashMap<K, V> {
|
||||
|
||||
private static final long serialVersionUID = -2795566703268944901L;
|
||||
|
||||
private final int cacheSize;
|
||||
|
||||
public LRUCache(int cacheSize) {
|
||||
super(16, 0.75f, true);
|
||||
this.cacheSize = cacheSize;
|
||||
}
|
||||
|
||||
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
|
||||
return size() >= cacheSize;
|
||||
}
|
||||
}
|
4
src/main/java/org/xbib/net/internal/package-info.java
Normal file
4
src/main/java/org/xbib/net/internal/package-info.java
Normal file
|
@ -0,0 +1,4 @@
|
|||
/**
|
||||
* Classes for internal use in the {@code org.xbib.net} package.
|
||||
*/
|
||||
package org.xbib.net.internal;
|
752
src/main/java/org/xbib/net/matcher/CharMatcher.java
Normal file
752
src/main/java/org/xbib/net/matcher/CharMatcher.java
Normal file
|
@ -0,0 +1,752 @@
|
|||
package org.xbib.net.matcher;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.BitSet;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public abstract class CharMatcher {
|
||||
|
||||
private static final String WHITESPACE_TABLE;
|
||||
|
||||
private static final int WHITESPACE_MULTIPLIER;
|
||||
|
||||
private static final int WHITESPACE_SHIFT;
|
||||
|
||||
private static final CharMatcher WHITESPACE;
|
||||
|
||||
private static final CharMatcher JAVA_ISO_CONTROL;
|
||||
|
||||
public static final CharMatcher LITERALS;
|
||||
|
||||
public static final CharMatcher PERCENT;
|
||||
|
||||
public static final CharMatcher HEXDIGIT;
|
||||
|
||||
static {
|
||||
WHITESPACE_TABLE = "\u2002\u3000\r\u0085\u200A\u2005\u2000\u3000\u2029\u000B\u3000\u2008\u2003\u205F\u3000" +
|
||||
"\u1680\u0009\u0020\u2006\u2001\u202F\u00A0\u000C\u2009\u3000\u2004\u3000\u3000\u2028\n\u2007\u3000";
|
||||
WHITESPACE_MULTIPLIER = 1682554634;
|
||||
WHITESPACE_SHIFT = Integer.numberOfLeadingZeros(WHITESPACE_TABLE.length() - 1);
|
||||
WHITESPACE = new FastMatcher() {
|
||||
@Override
|
||||
public boolean matches(char c) {
|
||||
return WHITESPACE_TABLE.charAt((WHITESPACE_MULTIPLIER * c) >>> WHITESPACE_SHIFT) == c;
|
||||
}
|
||||
|
||||
@Override
|
||||
void setBits(BitSet table) {
|
||||
for (int i = 0; i < WHITESPACE_TABLE.length(); i++) {
|
||||
table.set(WHITESPACE_TABLE.charAt(i));
|
||||
}
|
||||
}
|
||||
};
|
||||
JAVA_ISO_CONTROL = inRange('\u0000', '\u001f')
|
||||
.or(inRange('\u007f', '\u009f'));
|
||||
LITERALS = CharMatcher.JAVA_ISO_CONTROL
|
||||
.or(CharMatcher.WHITESPACE)
|
||||
.or(CharMatcher.anyOf("\"'<>\\^`{|}"))
|
||||
.precomputed().negate();
|
||||
PERCENT = CharMatcher.is('%');
|
||||
HEXDIGIT = CharMatcher.inRange('0', '9')
|
||||
.or(CharMatcher.inRange('a', 'f'))
|
||||
.or(CharMatcher.inRange('A', 'F'))
|
||||
.precomputed();
|
||||
}
|
||||
|
||||
private static final int DISTINCT_CHARS = (Character.MAX_VALUE) - (Character.MIN_VALUE) + 1;
|
||||
|
||||
private static <T> T checkNotNull(T reference) {
|
||||
if (reference == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
return reference;
|
||||
}
|
||||
|
||||
private static void checkArgument(boolean expression) {
|
||||
if (!expression) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
private static int checkPositionIndex(int index, int size) {
|
||||
if (index < 0 || index > size) {
|
||||
throw new IndexOutOfBoundsException("index=" + index + " size=" + size);
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
private static final CharMatcher ANY = new FastMatcher() {
|
||||
@Override
|
||||
public boolean matches(char c) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
int indexIn(CharSequence sequence) {
|
||||
return sequence.length() == 0 ? -1 : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
int indexIn(CharSequence sequence, int start) {
|
||||
int length = sequence.length();
|
||||
checkPositionIndex(start, length);
|
||||
return start == length ? -1 : start;
|
||||
}
|
||||
|
||||
@Override
|
||||
int lastIndexIn(CharSequence sequence) {
|
||||
return sequence.length() - 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matchesAllOf(CharSequence sequence) {
|
||||
checkNotNull(sequence);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matchesNoneOf(CharSequence sequence) {
|
||||
return sequence.length() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
String removeFrom(CharSequence sequence) {
|
||||
checkNotNull(sequence);
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
String replaceFrom(CharSequence sequence, char replacement) {
|
||||
char[] array = new char[sequence.length()];
|
||||
Arrays.fill(array, replacement);
|
||||
return new String(array);
|
||||
}
|
||||
|
||||
@Override
|
||||
int countIn(CharSequence sequence) {
|
||||
return sequence.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharMatcher and(CharMatcher other) {
|
||||
return checkNotNull(other);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharMatcher or(CharMatcher other) {
|
||||
checkNotNull(other);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharMatcher negate() {
|
||||
return NONE;
|
||||
}
|
||||
};
|
||||
|
||||
private static final CharMatcher NONE = new FastMatcher() {
|
||||
@Override
|
||||
public boolean matches(char c) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
int indexIn(CharSequence sequence) {
|
||||
checkNotNull(sequence);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
int indexIn(CharSequence sequence, int start) {
|
||||
int length = sequence.length();
|
||||
checkPositionIndex(start, length);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
int lastIndexIn(CharSequence sequence) {
|
||||
checkNotNull(sequence);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matchesAllOf(CharSequence sequence) {
|
||||
return sequence.length() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matchesNoneOf(CharSequence sequence) {
|
||||
checkNotNull(sequence);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
String removeFrom(CharSequence sequence) {
|
||||
return sequence.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
String replaceFrom(CharSequence sequence, char replacement) {
|
||||
return sequence.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
int countIn(CharSequence sequence) {
|
||||
checkNotNull(sequence);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharMatcher and(CharMatcher other) {
|
||||
checkNotNull(other);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharMatcher or(CharMatcher other) {
|
||||
return checkNotNull(other);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharMatcher negate() {
|
||||
return ANY;
|
||||
}
|
||||
};
|
||||
|
||||
public static CharMatcher is(char match) {
|
||||
return new FastMatcher() {
|
||||
@Override
|
||||
public boolean matches(char c) {
|
||||
return c == match;
|
||||
}
|
||||
|
||||
@Override
|
||||
String replaceFrom(CharSequence sequence, char replacement) {
|
||||
return sequence.toString().replace(match, replacement);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharMatcher and(CharMatcher other) {
|
||||
return other.matches(match) ? this : NONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharMatcher or(CharMatcher other) {
|
||||
return other.matches(match) ? other : super.or(other);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharMatcher negate() {
|
||||
return isNot(match);
|
||||
}
|
||||
|
||||
@Override
|
||||
void setBits(BitSet table) {
|
||||
table.set((int) match);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static CharMatcher isNot(char match) {
|
||||
return new FastMatcher() {
|
||||
@Override
|
||||
public boolean matches(char c) {
|
||||
return c != match;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharMatcher and(CharMatcher other) {
|
||||
return other.matches(match) ? super.and(other) : other;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharMatcher or(CharMatcher other) {
|
||||
return other.matches(match) ? ANY : this;
|
||||
}
|
||||
|
||||
@Override
|
||||
void setBits(BitSet table) {
|
||||
table.set(0, match);
|
||||
table.set((match) + 1, (Character.MAX_VALUE) + 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharMatcher negate() {
|
||||
return is(match);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static CharMatcher anyOf(CharSequence sequence) {
|
||||
switch (sequence.length()) {
|
||||
case 0:
|
||||
return NONE;
|
||||
case 1:
|
||||
return is(sequence.charAt(0));
|
||||
case 2:
|
||||
return isEither(sequence.charAt(0), sequence.charAt(1));
|
||||
default:
|
||||
break;
|
||||
}
|
||||
char[] chars = sequence.toString().toCharArray();
|
||||
Arrays.sort(chars);
|
||||
return new CharMatcher() {
|
||||
@Override
|
||||
public boolean matches(char c) {
|
||||
return Arrays.binarySearch(chars, c) >= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
void setBits(BitSet table) {
|
||||
for (char c : chars) {
|
||||
table.set(c);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static CharMatcher isEither(char match1, char match2) {
|
||||
return new FastMatcher() {
|
||||
@Override
|
||||
public boolean matches(char c) {
|
||||
return c == match1 || c == match2;
|
||||
}
|
||||
|
||||
@Override
|
||||
void setBits(BitSet table) {
|
||||
table.set(match1);
|
||||
table.set(match2);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static CharMatcher noneOf(CharSequence sequence) {
|
||||
return anyOf(sequence).negate();
|
||||
}
|
||||
|
||||
public static CharMatcher inRange(char startInclusive, char endInclusive) {
|
||||
checkArgument(endInclusive >= startInclusive);
|
||||
return new FastMatcher() {
|
||||
@Override
|
||||
public boolean matches(char c) {
|
||||
return startInclusive <= c && c <= endInclusive;
|
||||
}
|
||||
|
||||
@Override
|
||||
void setBits(BitSet table) {
|
||||
table.set(startInclusive, (endInclusive) + 1);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected CharMatcher() {
|
||||
}
|
||||
|
||||
public abstract boolean matches(char c);
|
||||
|
||||
public CharMatcher negate() {
|
||||
return new NegatedMatcher(this);
|
||||
}
|
||||
|
||||
public CharMatcher and(CharMatcher other) {
|
||||
return new And(this, checkNotNull(other));
|
||||
}
|
||||
|
||||
public CharMatcher or(CharMatcher other) {
|
||||
return new Or(this, other);
|
||||
}
|
||||
|
||||
public CharMatcher precomputed() {
|
||||
return precomputedInternal();
|
||||
}
|
||||
|
||||
private CharMatcher precomputedInternal() {
|
||||
BitSet table = new BitSet();
|
||||
setBits(table);
|
||||
int totalCharacters = table.cardinality();
|
||||
if (totalCharacters * 2 <= DISTINCT_CHARS) {
|
||||
return precomputedPositive(totalCharacters, table);
|
||||
} else {
|
||||
table.flip(Character.MIN_VALUE, (Character.MAX_VALUE) + 1);
|
||||
int negatedCharacters = DISTINCT_CHARS - totalCharacters;
|
||||
return new NegatedFastMatcher(precomputedPositive(negatedCharacters, table));
|
||||
}
|
||||
}
|
||||
|
||||
private static CharMatcher precomputedPositive(int totalCharacters, BitSet table) {
|
||||
switch (totalCharacters) {
|
||||
case 0:
|
||||
return NONE;
|
||||
case 1:
|
||||
return is((char) table.nextSetBit(0));
|
||||
case 2:
|
||||
char c1 = (char) table.nextSetBit(0);
|
||||
char c2 = (char) table.nextSetBit((c1) + 1);
|
||||
return isEither(c1, c2);
|
||||
default:
|
||||
return isSmall(totalCharacters, table.length()) ?
|
||||
SmallCharMatcher.from(table) : new BitSetMatcher(table);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isSmall(int totalCharacters, int tableLength) {
|
||||
return totalCharacters <= SmallCharMatcher.MAX_SIZE &&
|
||||
tableLength > (totalCharacters * 4 * Character.SIZE);
|
||||
}
|
||||
|
||||
void setBits(BitSet table) {
|
||||
for (int c = Character.MAX_VALUE; c >= Character.MIN_VALUE; c--) {
|
||||
if (matches((char) c)) {
|
||||
table.set(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean matchesAnyOf(CharSequence sequence) {
|
||||
return !matchesNoneOf(sequence);
|
||||
}
|
||||
|
||||
public boolean matchesAllOf(CharSequence sequence) {
|
||||
for (int i = sequence.length() - 1; i >= 0; i--) {
|
||||
if (!matches(sequence.charAt(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean matchesNoneOf(CharSequence sequence) {
|
||||
return indexIn(sequence) == -1;
|
||||
}
|
||||
|
||||
int indexIn(CharSequence sequence) {
|
||||
int length = sequence.length();
|
||||
for (int i = 0; i < length; i++) {
|
||||
if (matches(sequence.charAt(i))) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int indexIn(CharSequence sequence, int start) {
|
||||
int length = sequence.length();
|
||||
checkPositionIndex(start, length);
|
||||
for (int i = start; i < length; i++) {
|
||||
if (matches(sequence.charAt(i))) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int lastIndexIn(CharSequence sequence) {
|
||||
for (int i = sequence.length() - 1; i >= 0; i--) {
|
||||
if (matches(sequence.charAt(i))) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int countIn(CharSequence sequence) {
|
||||
int count = 0;
|
||||
for (int i = 0; i < sequence.length(); i++) {
|
||||
if (matches(sequence.charAt(i))) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
String removeFrom(CharSequence sequence) {
|
||||
String string = sequence.toString();
|
||||
int pos = indexIn(string);
|
||||
if (pos == -1) {
|
||||
return string;
|
||||
}
|
||||
char[] chars = string.toCharArray();
|
||||
int spread = 1;
|
||||
OUT:
|
||||
while (true) {
|
||||
pos++;
|
||||
while (true) {
|
||||
if (pos == chars.length) {
|
||||
break OUT;
|
||||
}
|
||||
if (matches(chars[pos])) {
|
||||
break;
|
||||
}
|
||||
chars[pos - spread] = chars[pos];
|
||||
pos++;
|
||||
}
|
||||
spread++;
|
||||
}
|
||||
return new String(chars, 0, pos - spread);
|
||||
}
|
||||
|
||||
String retainFrom(CharSequence sequence) {
|
||||
return negate().removeFrom(sequence);
|
||||
}
|
||||
|
||||
String replaceFrom(CharSequence sequence, char replacement) {
|
||||
String string = sequence.toString();
|
||||
int pos = indexIn(string);
|
||||
if (pos == -1) {
|
||||
return string;
|
||||
}
|
||||
char[] chars = string.toCharArray();
|
||||
chars[pos] = replacement;
|
||||
for (int i = pos + 1; i < chars.length; i++) {
|
||||
if (matches(chars[i])) {
|
||||
chars[i] = replacement;
|
||||
}
|
||||
}
|
||||
return new String(chars);
|
||||
}
|
||||
|
||||
boolean apply(Character character) {
|
||||
return matches(character);
|
||||
}
|
||||
|
||||
private abstract static class FastMatcher extends CharMatcher {
|
||||
FastMatcher() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharMatcher precomputed() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharMatcher negate() {
|
||||
return new NegatedFastMatcher(this);
|
||||
}
|
||||
}
|
||||
|
||||
private static class NegatedMatcher extends CharMatcher {
|
||||
CharMatcher original;
|
||||
|
||||
NegatedMatcher(CharMatcher original) {
|
||||
super();
|
||||
this.original = original;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(char c) {
|
||||
return !original.matches(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matchesAllOf(CharSequence sequence) {
|
||||
return original.matchesNoneOf(sequence);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matchesNoneOf(CharSequence sequence) {
|
||||
return original.matchesAllOf(sequence);
|
||||
}
|
||||
|
||||
@Override
|
||||
int countIn(CharSequence sequence) {
|
||||
return sequence.length() - original.countIn(sequence);
|
||||
}
|
||||
|
||||
@Override
|
||||
void setBits(BitSet table) {
|
||||
BitSet tmp = new BitSet();
|
||||
original.setBits(tmp);
|
||||
tmp.flip((Character.MIN_VALUE), (Character.MAX_VALUE) + 1);
|
||||
table.or(tmp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharMatcher negate() {
|
||||
return original;
|
||||
};
|
||||
}
|
||||
|
||||
private static class NegatedFastMatcher extends NegatedMatcher {
|
||||
NegatedFastMatcher(CharMatcher original) {
|
||||
super(original);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharMatcher precomputed() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
private static class And extends CharMatcher {
|
||||
private final CharMatcher first;
|
||||
private final CharMatcher second;
|
||||
|
||||
And(CharMatcher a, CharMatcher b) {
|
||||
super();
|
||||
first = checkNotNull(a);
|
||||
second = checkNotNull(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(char c) {
|
||||
return first.matches(c) && second.matches(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
void setBits(BitSet table) {
|
||||
BitSet tmp1 = new BitSet();
|
||||
first.setBits(tmp1);
|
||||
BitSet tmp2 = new BitSet();
|
||||
second.setBits(tmp2);
|
||||
tmp1.and(tmp2);
|
||||
table.or(tmp1);
|
||||
}
|
||||
}
|
||||
|
||||
private static class Or extends CharMatcher {
|
||||
private final CharMatcher first;
|
||||
private final CharMatcher second;
|
||||
|
||||
Or(CharMatcher a, CharMatcher b) {
|
||||
super();
|
||||
first = checkNotNull(a);
|
||||
second = checkNotNull(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
void setBits(BitSet table) {
|
||||
first.setBits(table);
|
||||
second.setBits(table);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(char c) {
|
||||
return first.matches(c) || second.matches(c);
|
||||
}
|
||||
}
|
||||
|
||||
private static class BitSetMatcher extends FastMatcher {
|
||||
private final BitSet table;
|
||||
|
||||
private BitSetMatcher(BitSet table) {
|
||||
if (table.length() + Long.SIZE < table.size()) {
|
||||
table = (BitSet) table.clone();
|
||||
}
|
||||
this.table = table;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(char c) {
|
||||
return table.get(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
void setBits(BitSet bitSet) {
|
||||
bitSet.or(table);
|
||||
}
|
||||
}
|
||||
|
||||
private static class SmallCharMatcher extends FastMatcher {
|
||||
|
||||
static final int MAX_SIZE = 1023;
|
||||
|
||||
private static final int C1 = 0xcc9e2d51;
|
||||
|
||||
private static final int C2 = 0x1b873593;
|
||||
|
||||
private static final double DESIRED_LOAD_FACTOR = 0.5d;
|
||||
|
||||
private final char[] table;
|
||||
|
||||
private final boolean containsZero;
|
||||
|
||||
private final long filter;
|
||||
|
||||
private SmallCharMatcher(char[] table, long filter, boolean containsZero) {
|
||||
super();
|
||||
this.table = table;
|
||||
this.filter = filter;
|
||||
this.containsZero = containsZero;
|
||||
}
|
||||
|
||||
static int smear(int hashCode) {
|
||||
return C2 * Integer.rotateLeft(hashCode * C1, 15);
|
||||
}
|
||||
|
||||
private boolean checkFilter(int c) {
|
||||
return 1 == (1 & (filter >> c));
|
||||
}
|
||||
|
||||
static int chooseTableSize(int setSize) {
|
||||
if (setSize == 1) {
|
||||
return 2;
|
||||
}
|
||||
int tableSize = Integer.highestOneBit(setSize - 1) << 1;
|
||||
while (tableSize * DESIRED_LOAD_FACTOR < setSize) {
|
||||
tableSize <<= 1;
|
||||
}
|
||||
return tableSize;
|
||||
}
|
||||
|
||||
static CharMatcher from(BitSet chars) {
|
||||
long filter = 0;
|
||||
int size = chars.cardinality();
|
||||
boolean containsZero = chars.get(0);
|
||||
char[] table = new char[chooseTableSize(size)];
|
||||
int mask = table.length - 1;
|
||||
for (int c = chars.nextSetBit(0); c != -1; c = chars.nextSetBit(c + 1)) {
|
||||
filter |= 1L << c;
|
||||
int index = smear(c) & mask;
|
||||
while (true) {
|
||||
if (table[index] == 0) {
|
||||
table[index] = (char) c;
|
||||
break;
|
||||
}
|
||||
index = (index + 1) & mask;
|
||||
}
|
||||
}
|
||||
return new SmallCharMatcher(table, filter, containsZero);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(char c) {
|
||||
if (c == 0) {
|
||||
return containsZero;
|
||||
}
|
||||
if (!checkFilter(c)) {
|
||||
return false;
|
||||
}
|
||||
int mask = table.length - 1;
|
||||
int startingIndex = smear(c) & mask;
|
||||
int index = startingIndex;
|
||||
while (true) {
|
||||
if (table[index] == 0) {
|
||||
return false;
|
||||
} else if (table[index] == c) {
|
||||
return true;
|
||||
} else {
|
||||
index = (index + 1) & mask;
|
||||
}
|
||||
if (index == startingIndex) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
void setBits(BitSet table) {
|
||||
if (containsZero) {
|
||||
table.set(0);
|
||||
}
|
||||
for (char c : this.table) {
|
||||
if (c != 0) {
|
||||
table.set(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
4
src/main/java/org/xbib/net/matcher/package-info.java
Normal file
4
src/main/java/org/xbib/net/matcher/package-info.java
Normal file
|
@ -0,0 +1,4 @@
|
|||
/**
|
||||
* Classes for URL matching.
|
||||
*/
|
||||
package org.xbib.net.matcher;
|
4
src/main/java/org/xbib/net/package-info.java
Normal file
4
src/main/java/org/xbib/net/package-info.java
Normal file
|
@ -0,0 +1,4 @@
|
|||
/**
|
||||
* Classes for URL building and parsing.
|
||||
*/
|
||||
package org.xbib.net;
|
121
src/main/java/org/xbib/net/path/PathDecoder.java
Normal file
121
src/main/java/org/xbib/net/path/PathDecoder.java
Normal file
|
@ -0,0 +1,121 @@
|
|||
package org.xbib.net.path;
|
||||
|
||||
import org.xbib.net.PercentDecoder;
|
||||
import org.xbib.net.QueryParameters;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.MalformedInputException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.charset.UnmappableCharacterException;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class PathDecoder {
|
||||
|
||||
private static final Integer MAX_PARAM_COUNT = 1000;
|
||||
|
||||
private PercentDecoder decoder;
|
||||
|
||||
private String path;
|
||||
|
||||
private String query;
|
||||
|
||||
private QueryParameters params;
|
||||
|
||||
public PathDecoder(String pathAndQuery) throws MalformedInputException, UnmappableCharacterException {
|
||||
this(pathAndQuery, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public PathDecoder(String pathAndQuery, Charset charset)
|
||||
throws MalformedInputException, UnmappableCharacterException {
|
||||
this(pathAndQuery, null, charset);
|
||||
}
|
||||
|
||||
public PathDecoder(String pathAndQuery, String queryString, Charset charset)
|
||||
throws MalformedInputException, UnmappableCharacterException {
|
||||
this.decoder = new PercentDecoder(charset.newDecoder());
|
||||
int pos = pathAndQuery.indexOf('?');
|
||||
String path = pos > 0 ? pathAndQuery.substring(0, pos) : pathAndQuery;
|
||||
this.query = pos > 0 ? pathAndQuery.substring(pos + 1) : null;
|
||||
this.path = PathNormalizer.normalize(path);
|
||||
this.params = new QueryParameters();
|
||||
if (query != null) {
|
||||
parse(query);
|
||||
}
|
||||
if (queryString != null) {
|
||||
parse(queryString);
|
||||
}
|
||||
}
|
||||
|
||||
public void parse(String queryString)
|
||||
throws MalformedInputException, UnmappableCharacterException {
|
||||
this.params.addAll(decodeQueryString(decoder, queryString));
|
||||
}
|
||||
|
||||
public String path() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public String query() {
|
||||
return query;
|
||||
}
|
||||
|
||||
public String decodedQuery() throws MalformedInputException, UnmappableCharacterException {
|
||||
return decoder.decode(query);
|
||||
}
|
||||
|
||||
public QueryParameters params() {
|
||||
return params;
|
||||
}
|
||||
|
||||
private static QueryParameters decodeQueryString(PercentDecoder decoder, String query)
|
||||
throws MalformedInputException, UnmappableCharacterException {
|
||||
QueryParameters params = new QueryParameters();
|
||||
if (query == null || query.isEmpty()) {
|
||||
return params;
|
||||
}
|
||||
String name = null;
|
||||
int count = 0;
|
||||
int pos = 0;
|
||||
int i;
|
||||
char c;
|
||||
for (i = 0; i < query.length(); i++) {
|
||||
c = query.charAt(i);
|
||||
if (c == '=' && name == null) {
|
||||
if (pos != i) {
|
||||
name = query.substring(pos, i).replaceAll("\\+", "%20");
|
||||
name = decoder.decode(name);
|
||||
}
|
||||
pos = i + 1;
|
||||
} else if (c == '&' || c == ';') {
|
||||
if (name == null && pos != i) {
|
||||
if (++count > MAX_PARAM_COUNT) {
|
||||
return params;
|
||||
}
|
||||
String s = query.substring(pos, i).replaceAll("\\+", "%20");
|
||||
params.add(decoder.decode(s), "");
|
||||
} else if (name != null) {
|
||||
if (++count > MAX_PARAM_COUNT) {
|
||||
return params;
|
||||
}
|
||||
String value = query.substring(pos, i).replaceAll("\\+", "%20");
|
||||
params.add(name, decoder.decode(value));
|
||||
name = null;
|
||||
}
|
||||
pos = i + 1;
|
||||
}
|
||||
}
|
||||
if (pos != i) {
|
||||
if (name == null) {
|
||||
params.add(decoder.decode(query.substring(pos, i)), "");
|
||||
} else {
|
||||
String value = query.substring(pos, i).replaceAll("\\+", "%20");
|
||||
params.add(name, decoder.decode(value));
|
||||
}
|
||||
} else if (name != null) {
|
||||
params.add(name, "");
|
||||
}
|
||||
return params;
|
||||
}
|
||||
}
|
313
src/main/java/org/xbib/net/path/PathMatcher.java
Normal file
313
src/main/java/org/xbib/net/path/PathMatcher.java
Normal file
|
@ -0,0 +1,313 @@
|
|||
package org.xbib.net.path;
|
||||
|
||||
import org.xbib.net.QueryParameters;
|
||||
import org.xbib.net.internal.LRUCache;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class PathMatcher {
|
||||
|
||||
private static final String DEFAULT_PATH_SEPARATOR = "/";
|
||||
|
||||
private final Map<String, List<String>> tokenizedPatternCache = Collections.synchronizedMap(new LRUCache<>(1024));
|
||||
|
||||
private final Map<String, PathStringMatcher> stringMatcherCache = Collections.synchronizedMap(new LRUCache<>(1024));
|
||||
|
||||
private String pathSeparator;
|
||||
|
||||
private PathSeparatorPatternCache pathSeparatorPatternCache;
|
||||
|
||||
private boolean caseSensitive = true;
|
||||
|
||||
private boolean trimTokens = true;
|
||||
|
||||
private volatile boolean cachePatterns = true;
|
||||
|
||||
public PathMatcher() {
|
||||
this(DEFAULT_PATH_SEPARATOR);
|
||||
}
|
||||
|
||||
public PathMatcher(String pathSeparator) {
|
||||
this.pathSeparator = pathSeparator;
|
||||
this.pathSeparatorPatternCache = new PathSeparatorPatternCache(pathSeparator);
|
||||
}
|
||||
|
||||
public void setPathSeparator(String pathSeparator) {
|
||||
this.pathSeparator = (pathSeparator != null ? pathSeparator : DEFAULT_PATH_SEPARATOR);
|
||||
this.pathSeparatorPatternCache = new PathSeparatorPatternCache(this.pathSeparator);
|
||||
}
|
||||
|
||||
public void setCaseSensitive(boolean caseSensitive) {
|
||||
this.caseSensitive = caseSensitive;
|
||||
}
|
||||
|
||||
public void setTrimTokens(boolean trimTokens) {
|
||||
this.trimTokens = trimTokens;
|
||||
}
|
||||
|
||||
public QueryParameters extractUriTemplateVariables(String pattern, String path) {
|
||||
QueryParameters queryParameters = new QueryParameters();
|
||||
if (!doMatch(pattern, path, true, queryParameters)) {
|
||||
throw new IllegalStateException("Pattern \"" + pattern + "\" is not a match for \"" + path + "\"");
|
||||
}
|
||||
return queryParameters;
|
||||
}
|
||||
|
||||
void setCachePatterns(boolean cachePatterns) {
|
||||
this.cachePatterns = cachePatterns;
|
||||
}
|
||||
|
||||
Map<String, PathStringMatcher> stringMatcherCache() {
|
||||
return stringMatcherCache;
|
||||
}
|
||||
|
||||
boolean match(String pattern, String path) {
|
||||
return doMatch(pattern, path, true, null);
|
||||
}
|
||||
|
||||
boolean matchStart(String pattern, String path) {
|
||||
return doMatch(pattern, path, false, null);
|
||||
}
|
||||
|
||||
String extractPathWithinPattern(String pattern, String path) {
|
||||
List<String> patternParts = tokenize(pattern, this.pathSeparator, this.trimTokens, true);
|
||||
List<String> pathParts = tokenize(path, this.pathSeparator, this.trimTokens, true);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean pathStarted = false;
|
||||
for (int segment = 0; segment < patternParts.size(); segment++) {
|
||||
String patternPart = patternParts.get(segment);
|
||||
if (patternPart.indexOf('*') > -1 || patternPart.indexOf('?') > -1) {
|
||||
while (segment < pathParts.size()) {
|
||||
if (pathStarted || (segment == 0 && !pattern.startsWith(this.pathSeparator))) {
|
||||
sb.append(pathSeparator);
|
||||
}
|
||||
sb.append(pathParts.get(segment));
|
||||
pathStarted = true;
|
||||
segment++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
String combine(String pattern1, String pattern2) {
|
||||
if (!hasText(pattern1) && !hasText(pattern2)) {
|
||||
return "";
|
||||
}
|
||||
if (!hasText(pattern1)) {
|
||||
return pattern2;
|
||||
}
|
||||
if (!hasText(pattern2)) {
|
||||
return pattern1;
|
||||
}
|
||||
boolean pattern1ContainsUriVar = pattern1.indexOf('{') != -1;
|
||||
if (!pattern1.equals(pattern2) && !pattern1ContainsUriVar && match(pattern1, pattern2)) {
|
||||
return pattern2;
|
||||
}
|
||||
if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnWildCard())) {
|
||||
return concat(pattern1.substring(0, pattern1.length() - 2), pattern2);
|
||||
}
|
||||
if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnDoubleWildCard())) {
|
||||
return concat(pattern1, pattern2);
|
||||
}
|
||||
int starDotPos1 = pattern1.indexOf("*.");
|
||||
if (pattern1ContainsUriVar || starDotPos1 == -1 || this.pathSeparator.equals(".")) {
|
||||
return concat(pattern1, pattern2);
|
||||
}
|
||||
String ext1 = pattern1.substring(starDotPos1 + 1);
|
||||
int dotPos2 = pattern2.indexOf('.');
|
||||
String file2 = (dotPos2 == -1 ? pattern2 : pattern2.substring(0, dotPos2));
|
||||
String ext2 = (dotPos2 == -1 ? "" : pattern2.substring(dotPos2));
|
||||
boolean ext1All = (ext1.equals(".*") || ext1.equals(""));
|
||||
boolean ext2All = (ext2.equals(".*") || ext2.equals(""));
|
||||
if (!ext1All && !ext2All) {
|
||||
throw new IllegalArgumentException("Cannot combine patterns: " + pattern1 + " vs " + pattern2);
|
||||
}
|
||||
String ext = ext1All ? ext2 : ext1;
|
||||
return file2 + ext;
|
||||
}
|
||||
|
||||
public Comparator<String> getPatternComparator(String path) {
|
||||
return new PathPatternComparator(path);
|
||||
}
|
||||
|
||||
private static boolean hasText(CharSequence str) {
|
||||
if (str == null || str.length() == 0) {
|
||||
return false;
|
||||
}
|
||||
int strLen = str.length();
|
||||
for (int i = 0; i < strLen; i++) {
|
||||
if (!Character.isWhitespace(str.charAt(i))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private String concat(String path1, String path2) {
|
||||
boolean path1EndsWithSeparator = path1.endsWith(this.pathSeparator);
|
||||
boolean path2StartsWithSeparator = path2.startsWith(this.pathSeparator);
|
||||
if (path1EndsWithSeparator && path2StartsWithSeparator) {
|
||||
return path1 + path2.substring(1);
|
||||
} else if (path1EndsWithSeparator || path2StartsWithSeparator) {
|
||||
return path1 + path2;
|
||||
} else {
|
||||
return path1 + this.pathSeparator + path2;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean doMatch(String pattern, String path, boolean fullMatch, QueryParameters queryParameters) {
|
||||
if (path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) {
|
||||
return false;
|
||||
}
|
||||
List<String> patternElements = tokenizePattern(pattern);
|
||||
List<String> pathElements = tokenizePath(path);
|
||||
int pattIdxStart = 0;
|
||||
int pattIdxEnd = patternElements.size() - 1;
|
||||
int pathIdxStart = 0;
|
||||
int pathIdxEnd = pathElements.size() - 1;
|
||||
while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
|
||||
String pattDir = patternElements.get(pattIdxStart);
|
||||
if ("**".equals(pattDir)) {
|
||||
break;
|
||||
}
|
||||
if (!matchStrings(pattDir, pathElements.get(pathIdxStart), queryParameters)) {
|
||||
return false;
|
||||
}
|
||||
pattIdxStart++;
|
||||
pathIdxStart++;
|
||||
}
|
||||
if (pathIdxStart > pathIdxEnd) {
|
||||
if (pattIdxStart > pattIdxEnd) {
|
||||
return pattern.endsWith(this.pathSeparator) == path.endsWith(this.pathSeparator);
|
||||
}
|
||||
if (!fullMatch) {
|
||||
return true;
|
||||
}
|
||||
if (pattIdxStart == pattIdxEnd
|
||||
&& patternElements.get(pattIdxStart).equals("*")
|
||||
&& path.endsWith(this.pathSeparator)) {
|
||||
return true;
|
||||
}
|
||||
for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
|
||||
if (!patternElements.get(i).equals("**")) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else if (pattIdxStart > pattIdxEnd) {
|
||||
return false;
|
||||
} else if (!fullMatch && "**".equals(patternElements.get(pattIdxStart))) {
|
||||
return true;
|
||||
}
|
||||
while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
|
||||
String pattDir = patternElements.get(pattIdxEnd);
|
||||
if (pattDir.equals("**")) {
|
||||
break;
|
||||
}
|
||||
if (!matchStrings(pattDir, pathElements.get(pathIdxEnd), queryParameters)) {
|
||||
return false;
|
||||
}
|
||||
pattIdxEnd--;
|
||||
pathIdxEnd--;
|
||||
}
|
||||
if (pathIdxStart > pathIdxEnd) {
|
||||
for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
|
||||
if (!patternElements.get(i).equals("**")) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) {
|
||||
int patIdxTmp = -1;
|
||||
for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) {
|
||||
if (patternElements.get(i).equals("**")) {
|
||||
patIdxTmp = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (patIdxTmp == pattIdxStart + 1) {
|
||||
pattIdxStart++;
|
||||
continue;
|
||||
}
|
||||
int patLength = patIdxTmp - pattIdxStart - 1;
|
||||
int strLength = pathIdxEnd - pathIdxStart + 1;
|
||||
int foundIdx = -1;
|
||||
boolean strLoop = true;
|
||||
while (strLoop) {
|
||||
for (int i = 0; i <= strLength - patLength; i++) {
|
||||
for (int j = 0; j < patLength; j++) {
|
||||
String subPat = patternElements.get(pattIdxStart + j + 1);
|
||||
String subStr = pathElements.get(pathIdxStart + i + j);
|
||||
if (matchStrings(subPat, subStr, queryParameters)) {
|
||||
strLoop = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (strLoop) {
|
||||
foundIdx = pathIdxStart + i;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (foundIdx == -1) {
|
||||
return false;
|
||||
}
|
||||
pattIdxStart = patIdxTmp;
|
||||
pathIdxStart = foundIdx + patLength;
|
||||
}
|
||||
for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
|
||||
if (!patternElements.get(i).equals("**")) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private List<String> tokenizePattern(String pattern) {
|
||||
return cachePatterns ?
|
||||
tokenizedPatternCache.computeIfAbsent(pattern, this::tokenizePath) :
|
||||
tokenizePath(pattern);
|
||||
}
|
||||
|
||||
private List<String> tokenizePath(String path) {
|
||||
return tokenize(path, this.pathSeparator, this.trimTokens, true);
|
||||
}
|
||||
|
||||
private static List<String> tokenize(String str, String delimiters, boolean trimTokens, boolean ignoreEmptyTokens) {
|
||||
if (str == null) {
|
||||
return null;
|
||||
}
|
||||
StringTokenizer st = new StringTokenizer(str, delimiters);
|
||||
List<String> tokens = new ArrayList<>();
|
||||
while (st.hasMoreTokens()) {
|
||||
String token = st.nextToken();
|
||||
if (trimTokens) {
|
||||
token = token.trim();
|
||||
}
|
||||
if (!ignoreEmptyTokens || token.length() > 0) {
|
||||
tokens.add(token);
|
||||
}
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
|
||||
private boolean matchStrings(String pattern, String str, QueryParameters queryParameters) {
|
||||
return getStringMatcher(pattern).matchStrings(str, queryParameters);
|
||||
}
|
||||
|
||||
private PathStringMatcher getStringMatcher(String pattern) {
|
||||
return cachePatterns ?
|
||||
stringMatcherCache.computeIfAbsent(pattern, p -> new PathStringMatcher(p, this.caseSensitive)) :
|
||||
new PathStringMatcher(pattern, this.caseSensitive);
|
||||
}
|
||||
}
|
194
src/main/java/org/xbib/net/path/PathNormalizer.java
Normal file
194
src/main/java/org/xbib/net/path/PathNormalizer.java
Normal file
|
@ -0,0 +1,194 @@
|
|||
package org.xbib.net.path;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class PathNormalizer {
|
||||
|
||||
private static final char separator = '/';
|
||||
|
||||
private PathNormalizer() {
|
||||
}
|
||||
|
||||
/*public static String normalizePath(String path) {
|
||||
return normalizePath(path, false);
|
||||
}
|
||||
|
||||
public static String normalizePath(String path, boolean keepSeparator) {
|
||||
if (path == null || path.equals("") || path.equals("/")) {
|
||||
return "/";
|
||||
}
|
||||
path = path.replaceAll("/+", "/");
|
||||
int size = path.length();
|
||||
if (size == 0) {
|
||||
return path;
|
||||
}
|
||||
int prefix = getPrefixLength(path);
|
||||
if (prefix < 0) {
|
||||
return "";
|
||||
}
|
||||
char[] ch = new char[size + 2];
|
||||
path.getChars(0, path.length(), ch, 0);
|
||||
boolean firstIsDirectory = true;
|
||||
if (ch[0] != separator) {
|
||||
firstIsDirectory = false;
|
||||
}
|
||||
boolean lastIsDirectory = true;
|
||||
if (ch[size - 1] != separator) {
|
||||
lastIsDirectory = false;
|
||||
}
|
||||
for (int i = prefix + 1; i < size; i++) {
|
||||
if (ch[i] == separator && ch[i - 1] == separator) {
|
||||
System.arraycopy(ch, i, ch, i - 1, size - i);
|
||||
size--;
|
||||
i--;
|
||||
}
|
||||
}
|
||||
for (int i = prefix + 1; i < size; i++) {
|
||||
if (ch[i] == separator && ch[i - 1] == '.'
|
||||
&& (i == prefix + 1 || ch[i - 2] == separator)) {
|
||||
if (i == size - 1) {
|
||||
lastIsDirectory = true;
|
||||
}
|
||||
System.arraycopy(ch, i + 1, ch, i - 1, size - i);
|
||||
size -=2;
|
||||
i--;
|
||||
}
|
||||
}
|
||||
int i = prefix + 2;
|
||||
while (i < size) {
|
||||
if (ch[i] == separator && ch[i - 1] == '.' && ch[i - 2] == '.'
|
||||
&& (i == prefix + 2 || ch[i - 3] == separator)) {
|
||||
if (i == prefix + 2) {
|
||||
return "";
|
||||
}
|
||||
if (i == size - 1) {
|
||||
lastIsDirectory = true;
|
||||
}
|
||||
int j;
|
||||
boolean b = false;
|
||||
for (j = i - 4 ; j >= prefix; j--) {
|
||||
if (ch[j] == separator) {
|
||||
System.arraycopy(ch, i + 1, ch, j + 1, size - i);
|
||||
size -= (i - j);
|
||||
i = j + 1;
|
||||
b = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (b) {
|
||||
continue;
|
||||
}
|
||||
System.arraycopy(ch, i + 1, ch, prefix, size - i);
|
||||
size -= (i + 1 - prefix);
|
||||
i = prefix + 1;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (size <= 0) {
|
||||
return "";
|
||||
}
|
||||
String s = new String(ch, 0, size);
|
||||
if (size <= prefix) {
|
||||
return s;
|
||||
}
|
||||
if (!keepSeparator) {
|
||||
if (firstIsDirectory && lastIsDirectory) {
|
||||
return s.substring(1, s.length() - 1);
|
||||
} else if (firstIsDirectory) {
|
||||
return s.substring(1);
|
||||
} else if (lastIsDirectory) {
|
||||
return s.substring(0, s.length() - 1);
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}*/
|
||||
|
||||
public static String normalize(String path) {
|
||||
if (path == null || "".equals(path) || "/".equals(path)) {
|
||||
return "/";
|
||||
}
|
||||
path = path.replaceAll("/+", "/");
|
||||
int leadingSlashes = 0;
|
||||
while (leadingSlashes < path.length() && path.charAt(leadingSlashes) == '/') {
|
||||
++leadingSlashes;
|
||||
}
|
||||
boolean isDir = (path.charAt(path.length() - 1) == '/');
|
||||
StringTokenizer st = new StringTokenizer(path, "/");
|
||||
LinkedList<String> list = new LinkedList<>();
|
||||
while (st.hasMoreTokens()) {
|
||||
String token = st.nextToken();
|
||||
if ("..".equals(token)) {
|
||||
if (!list.isEmpty() && !"..".equals(list.getLast())) {
|
||||
list.removeLast();
|
||||
if (!st.hasMoreTokens()) {
|
||||
isDir = true;
|
||||
}
|
||||
}
|
||||
} else if (!".".equals(token) && !"".equals(token)) {
|
||||
list.add(token);
|
||||
}
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
while (leadingSlashes-- > 0) {
|
||||
sb.append('/');
|
||||
}
|
||||
for (Iterator<String> it = list.iterator(); it.hasNext();) {
|
||||
sb.append(it.next());
|
||||
if (it.hasNext()) {
|
||||
sb.append('/');
|
||||
}
|
||||
}
|
||||
if (isDir && sb.length() > 0 && sb.charAt(sb.length() - 1) != '/') {
|
||||
sb.append('/');
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static int getPrefixLength(String filename) {
|
||||
if (filename == null) {
|
||||
return -1;
|
||||
}
|
||||
int len = filename.length();
|
||||
if (len == 0) {
|
||||
return 0;
|
||||
}
|
||||
char ch0 = filename.charAt(0);
|
||||
if (ch0 == ':') {
|
||||
return -1;
|
||||
}
|
||||
if (len == 1) {
|
||||
if (ch0 == '~') {
|
||||
return 2;
|
||||
}
|
||||
return ch0 == separator ? 1 : 0;
|
||||
} else {
|
||||
if (ch0 == '~') {
|
||||
int pos = filename.indexOf(separator, 1);
|
||||
return pos == -1 ? len + 1 : pos + 1;
|
||||
}
|
||||
char ch1 = filename.charAt(1);
|
||||
if (ch1 == ':') {
|
||||
ch0 = Character.toUpperCase(ch0);
|
||||
if (ch0 >= ('A') && ch0 <= ('Z')) {
|
||||
if (len == 2 || filename.charAt(2) != separator) {
|
||||
return 2;
|
||||
}
|
||||
return 3;
|
||||
}
|
||||
return -1;
|
||||
} else if (ch0 == separator && ch1 == separator) {
|
||||
int pos = filename.indexOf(separator, 2);
|
||||
if (pos == -1 || pos == 2) {
|
||||
return -1;
|
||||
}
|
||||
return pos + 1;
|
||||
} else {
|
||||
return ch0 == separator ? 1 : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
61
src/main/java/org/xbib/net/path/PathPatternComparator.java
Normal file
61
src/main/java/org/xbib/net/path/PathPatternComparator.java
Normal file
|
@ -0,0 +1,61 @@
|
|||
package org.xbib.net.path;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Comparator;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class PathPatternComparator implements Comparator<String>, Serializable {
|
||||
|
||||
private static final long serialVersionUID = -5286803094119345841L;
|
||||
|
||||
private final String path;
|
||||
|
||||
PathPatternComparator(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(String pattern1, String pattern2) {
|
||||
PathPatternInfo info1 = new PathPatternInfo(pattern1);
|
||||
PathPatternInfo info2 = new PathPatternInfo(pattern2);
|
||||
if (info1.isLeastSpecific() && info2.isLeastSpecific()) {
|
||||
return 0;
|
||||
} else if (info1.isLeastSpecific()) {
|
||||
return 1;
|
||||
} else if (info2.isLeastSpecific()) {
|
||||
return -1;
|
||||
}
|
||||
boolean pattern1EqualsPath = pattern1.equals(path);
|
||||
boolean pattern2EqualsPath = pattern2.equals(path);
|
||||
if (pattern1EqualsPath && pattern2EqualsPath) {
|
||||
return 0;
|
||||
} else if (pattern1EqualsPath) {
|
||||
return -1;
|
||||
} else if (pattern2EqualsPath) {
|
||||
return 1;
|
||||
}
|
||||
if (info1.isPrefixPattern() && info2.getDoubleWildcards() == 0) {
|
||||
return 1;
|
||||
} else if (info2.isPrefixPattern() && info1.getDoubleWildcards() == 0) {
|
||||
return -1;
|
||||
}
|
||||
if (info1.getTotalCount() != info2.getTotalCount()) {
|
||||
return info1.getTotalCount() - info2.getTotalCount();
|
||||
}
|
||||
if (info1.getLength() != info2.getLength()) {
|
||||
return info2.getLength() - info1.getLength();
|
||||
}
|
||||
if (info1.getSingleWildcards() < info2.getSingleWildcards()) {
|
||||
return -1;
|
||||
} else if (info2.getSingleWildcards() < info1.getSingleWildcards()) {
|
||||
return 1;
|
||||
}
|
||||
if (info1.getUriVars() < info2.getUriVars()) {
|
||||
return -1;
|
||||
} else if (info2.getUriVars() < info1.getUriVars()) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
89
src/main/java/org/xbib/net/path/PathPatternInfo.java
Normal file
89
src/main/java/org/xbib/net/path/PathPatternInfo.java
Normal file
|
@ -0,0 +1,89 @@
|
|||
package org.xbib.net.path;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
*/
|
||||
class PathPatternInfo {
|
||||
|
||||
private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{[^/]+?\\}");
|
||||
|
||||
private final String pattern;
|
||||
|
||||
private int uriVars;
|
||||
|
||||
private int singleWildcards;
|
||||
|
||||
private int doubleWildcards;
|
||||
|
||||
private boolean catchAllPattern;
|
||||
|
||||
private boolean prefixPattern;
|
||||
|
||||
private Integer length;
|
||||
|
||||
PathPatternInfo(String pattern) {
|
||||
this.pattern = pattern;
|
||||
if (this.pattern != null) {
|
||||
initCounters();
|
||||
this.catchAllPattern = this.pattern.equals("/**");
|
||||
this.prefixPattern = !this.catchAllPattern && this.pattern.endsWith("/**");
|
||||
}
|
||||
if (this.uriVars == 0) {
|
||||
this.length = this.pattern != null ? this.pattern.length() : 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void initCounters() {
|
||||
int pos = 0;
|
||||
while (pos < this.pattern.length()) {
|
||||
if (this.pattern.charAt(pos) == '{') {
|
||||
this.uriVars++;
|
||||
pos++;
|
||||
} else if (this.pattern.charAt(pos) == '*') {
|
||||
if (pos + 1 < this.pattern.length() && this.pattern.charAt(pos + 1) == '*') {
|
||||
this.doubleWildcards++;
|
||||
pos += 2;
|
||||
} else if (pos > 0 && !this.pattern.substring(pos - 1).equals(".*")) {
|
||||
this.singleWildcards++;
|
||||
pos++;
|
||||
} else {
|
||||
pos++;
|
||||
}
|
||||
} else {
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int getUriVars() {
|
||||
return uriVars;
|
||||
}
|
||||
|
||||
int getSingleWildcards() {
|
||||
return singleWildcards;
|
||||
}
|
||||
|
||||
int getDoubleWildcards() {
|
||||
return doubleWildcards;
|
||||
}
|
||||
|
||||
boolean isLeastSpecific() {
|
||||
return this.pattern == null || this.catchAllPattern;
|
||||
}
|
||||
|
||||
boolean isPrefixPattern() {
|
||||
return this.prefixPattern;
|
||||
}
|
||||
|
||||
int getTotalCount() {
|
||||
return this.uriVars + this.singleWildcards + (2 * this.doubleWildcards);
|
||||
}
|
||||
|
||||
int getLength() {
|
||||
if (this.length == null) {
|
||||
this.length = VARIABLE_PATTERN.matcher(this.pattern).replaceAll("#").length();
|
||||
}
|
||||
return length;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package org.xbib.net.path;
|
||||
|
||||
/**
|
||||
*/
|
||||
class PathSeparatorPatternCache {
|
||||
|
||||
private final String endsOnWildCard;
|
||||
|
||||
private final String endsOnDoubleWildCard;
|
||||
|
||||
PathSeparatorPatternCache(String pathSeparator) {
|
||||
this.endsOnWildCard = pathSeparator + "*";
|
||||
this.endsOnDoubleWildCard = pathSeparator + "**";
|
||||
}
|
||||
|
||||
String getEndsOnWildCard() {
|
||||
return endsOnWildCard;
|
||||
}
|
||||
|
||||
String getEndsOnDoubleWildCard() {
|
||||
return endsOnDoubleWildCard;
|
||||
}
|
||||
}
|
80
src/main/java/org/xbib/net/path/PathStringMatcher.java
Normal file
80
src/main/java/org/xbib/net/path/PathStringMatcher.java
Normal file
|
@ -0,0 +1,80 @@
|
|||
package org.xbib.net.path;
|
||||
|
||||
import org.xbib.net.QueryParameters;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
*/
|
||||
class PathStringMatcher {
|
||||
|
||||
private static final Pattern GLOB_PATTERN = Pattern.compile("\\?|\\*|\\{((?:\\{[^/]+?\\}|[^/{}]|\\\\[{}])+?)\\}");
|
||||
|
||||
private static final String DEFAULT_VARIABLE_PATTERN = "(.*)";
|
||||
|
||||
private final List<String> variableNames = new ArrayList<>();
|
||||
|
||||
private final Pattern pattern;
|
||||
|
||||
PathStringMatcher(String pattern, boolean caseSensitive) {
|
||||
StringBuilder patternBuilder = new StringBuilder();
|
||||
Matcher matcher = GLOB_PATTERN.matcher(pattern);
|
||||
int end = 0;
|
||||
while (matcher.find()) {
|
||||
patternBuilder.append(quote(pattern, end, matcher.start()));
|
||||
String match = matcher.group();
|
||||
if ("?".equals(match)) {
|
||||
patternBuilder.append('.');
|
||||
} else if ("*".equals(match)) {
|
||||
patternBuilder.append(".*");
|
||||
} else if (match.startsWith("{") && match.endsWith("}")) {
|
||||
int colonIdx = match.indexOf(':');
|
||||
if (colonIdx == -1) {
|
||||
patternBuilder.append(DEFAULT_VARIABLE_PATTERN);
|
||||
this.variableNames.add(matcher.group(1));
|
||||
} else {
|
||||
String variablePattern = match.substring(colonIdx + 1, match.length() - 1);
|
||||
patternBuilder.append('(').append(variablePattern).append(')');
|
||||
String variableName = match.substring(1, colonIdx);
|
||||
this.variableNames.add(variableName);
|
||||
}
|
||||
}
|
||||
end = matcher.end();
|
||||
}
|
||||
patternBuilder.append(quote(pattern, end, pattern.length()));
|
||||
this.pattern = caseSensitive ? Pattern.compile(patternBuilder.toString()) :
|
||||
Pattern.compile(patternBuilder.toString(), Pattern.CASE_INSENSITIVE);
|
||||
}
|
||||
|
||||
private static String quote(String s, int start, int end) {
|
||||
if (start == end) {
|
||||
return "";
|
||||
}
|
||||
return Pattern.quote(s.substring(start, end));
|
||||
}
|
||||
|
||||
boolean matchStrings(String str, QueryParameters queryParameters) {
|
||||
Matcher matcher = this.pattern.matcher(str);
|
||||
if (matcher.matches()) {
|
||||
if (queryParameters != null) {
|
||||
if (this.variableNames.size() != matcher.groupCount()) {
|
||||
throw new IllegalArgumentException("The number of capturing groups in the pattern segment " +
|
||||
this.pattern + " does not match the number of URI template variables it defines, " +
|
||||
"which can occur if capturing groups are used in a URI template regex. " +
|
||||
"Use non-capturing groups instead.");
|
||||
}
|
||||
for (int i = 1; i <= matcher.groupCount(); i++) {
|
||||
String name = this.variableNames.get(i - 1);
|
||||
String value = matcher.group(i);
|
||||
queryParameters.add(name, value);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
4
src/main/java/org/xbib/net/path/package-info.java
Normal file
4
src/main/java/org/xbib/net/path/package-info.java
Normal file
|
@ -0,0 +1,4 @@
|
|||
/**
|
||||
* Classes for URL path nomalizing, decoding, and matching.
|
||||
*/
|
||||
package org.xbib.net.path;
|
34
src/main/java/org/xbib/net/scheme/AbstractScheme.java
Normal file
34
src/main/java/org/xbib/net/scheme/AbstractScheme.java
Normal file
|
@ -0,0 +1,34 @@
|
|||
package org.xbib.net.scheme;
|
||||
|
||||
import org.xbib.net.URL;
|
||||
|
||||
/**
|
||||
* Base implementation for scheme.
|
||||
*/
|
||||
public abstract class AbstractScheme implements Scheme {
|
||||
|
||||
protected final String name;
|
||||
|
||||
protected final int defaultPort;
|
||||
|
||||
protected AbstractScheme(String name, int defaultPort) {
|
||||
this.name = name;
|
||||
this.defaultPort = defaultPort;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDefaultPort() {
|
||||
return defaultPort;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL normalize(URL url) {
|
||||
return url;
|
||||
}
|
||||
|
||||
}
|
12
src/main/java/org/xbib/net/scheme/DefaultScheme.java
Normal file
12
src/main/java/org/xbib/net/scheme/DefaultScheme.java
Normal file
|
@ -0,0 +1,12 @@
|
|||
package org.xbib.net.scheme;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class DefaultScheme extends AbstractScheme {
|
||||
|
||||
public DefaultScheme(String name) {
|
||||
super(name, -1);
|
||||
}
|
||||
|
||||
}
|
17
src/main/java/org/xbib/net/scheme/DnsScheme.java
Normal file
17
src/main/java/org/xbib/net/scheme/DnsScheme.java
Normal file
|
@ -0,0 +1,17 @@
|
|||
package org.xbib.net.scheme;
|
||||
|
||||
/**
|
||||
* The DNS URI scheme.
|
||||
* @see <a href="https://www.w3.org/Addressing/draft-mirashi-url-irc-01.txt">DNS RFC</a>
|
||||
*/
|
||||
class DnsScheme extends HttpScheme {
|
||||
|
||||
DnsScheme() {
|
||||
super("dns", 53);
|
||||
}
|
||||
|
||||
DnsScheme(String name, int port) {
|
||||
super(name, port);
|
||||
}
|
||||
|
||||
}
|
12
src/main/java/org/xbib/net/scheme/FileScheme.java
Normal file
12
src/main/java/org/xbib/net/scheme/FileScheme.java
Normal file
|
@ -0,0 +1,12 @@
|
|||
package org.xbib.net.scheme;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class FileScheme extends HttpScheme {
|
||||
|
||||
FileScheme() {
|
||||
super("file", -1);
|
||||
}
|
||||
|
||||
}
|
16
src/main/java/org/xbib/net/scheme/FtpScheme.java
Normal file
16
src/main/java/org/xbib/net/scheme/FtpScheme.java
Normal file
|
@ -0,0 +1,16 @@
|
|||
package org.xbib.net.scheme;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class FtpScheme extends HttpScheme {
|
||||
|
||||
FtpScheme() {
|
||||
super("ftp", 21);
|
||||
}
|
||||
|
||||
FtpScheme(String name, int port) {
|
||||
super(name, port);
|
||||
}
|
||||
|
||||
}
|
16
src/main/java/org/xbib/net/scheme/GitScheme.java
Normal file
16
src/main/java/org/xbib/net/scheme/GitScheme.java
Normal file
|
@ -0,0 +1,16 @@
|
|||
package org.xbib.net.scheme;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class GitScheme extends HttpScheme {
|
||||
|
||||
GitScheme() {
|
||||
super("git", 443);
|
||||
}
|
||||
|
||||
GitScheme(String name, int port) {
|
||||
super(name, port);
|
||||
}
|
||||
|
||||
}
|
16
src/main/java/org/xbib/net/scheme/GitSecureHttpScheme.java
Normal file
16
src/main/java/org/xbib/net/scheme/GitSecureHttpScheme.java
Normal file
|
@ -0,0 +1,16 @@
|
|||
package org.xbib.net.scheme;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class GitSecureHttpScheme extends HttpScheme {
|
||||
|
||||
GitSecureHttpScheme() {
|
||||
super("git+https", 443);
|
||||
}
|
||||
|
||||
GitSecureHttpScheme(String name, int port) {
|
||||
super(name, port);
|
||||
}
|
||||
|
||||
}
|
12
src/main/java/org/xbib/net/scheme/GopherScheme.java
Normal file
12
src/main/java/org/xbib/net/scheme/GopherScheme.java
Normal file
|
@ -0,0 +1,12 @@
|
|||
package org.xbib.net.scheme;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class GopherScheme extends AbstractScheme {
|
||||
|
||||
GopherScheme() {
|
||||
super("gopher", 70);
|
||||
}
|
||||
|
||||
}
|
35
src/main/java/org/xbib/net/scheme/HttpScheme.java
Normal file
35
src/main/java/org/xbib/net/scheme/HttpScheme.java
Normal file
|
@ -0,0 +1,35 @@
|
|||
package org.xbib.net.scheme;
|
||||
|
||||
import org.xbib.net.URL;
|
||||
import org.xbib.net.path.PathNormalizer;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class HttpScheme extends AbstractScheme {
|
||||
|
||||
HttpScheme() {
|
||||
super("http", 80);
|
||||
}
|
||||
|
||||
HttpScheme(String name, int port) {
|
||||
super(name, port);
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL normalize(URL url) {
|
||||
String host = url.getHost();
|
||||
if (host != null) {
|
||||
host = host.toLowerCase();
|
||||
}
|
||||
return URL.builder()
|
||||
.scheme(url.getScheme())
|
||||
.userInfo(url.getUserInfo())
|
||||
.host(host, url.getProtocolVersion())
|
||||
.port(url.getPort())
|
||||
.path(PathNormalizer.normalize(url.getPath()))
|
||||
.query(url.getQuery()/*PercentEncoders.getQueryEncoder().encode(url.getDecodedQuery())*/)
|
||||
.fragment(url.getFragment()/*PercentEncoders.getFragmentEncoder().encode(url.getDecodedFragment())*/)
|
||||
.build();
|
||||
}
|
||||
}
|
17
src/main/java/org/xbib/net/scheme/ImapScheme.java
Normal file
17
src/main/java/org/xbib/net/scheme/ImapScheme.java
Normal file
|
@ -0,0 +1,17 @@
|
|||
package org.xbib.net.scheme;
|
||||
|
||||
/**
|
||||
* The IMAP scheme.
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc5092">IMAP RFC</a>
|
||||
*/
|
||||
class ImapScheme extends AbstractScheme {
|
||||
|
||||
ImapScheme() {
|
||||
super("imap", 143);
|
||||
}
|
||||
|
||||
ImapScheme(String name, int port) {
|
||||
super(name, port);
|
||||
}
|
||||
}
|
18
src/main/java/org/xbib/net/scheme/IrcScheme.java
Normal file
18
src/main/java/org/xbib/net/scheme/IrcScheme.java
Normal file
|
@ -0,0 +1,18 @@
|
|||
package org.xbib.net.scheme;
|
||||
|
||||
/**
|
||||
* The IRC scheme.
|
||||
*
|
||||
* @see <a href="https://www.w3.org/Addressing/draft-mirashi-url-irc-01.txt">IRC draft</a>
|
||||
*/
|
||||
class IrcScheme extends HttpScheme {
|
||||
|
||||
IrcScheme() {
|
||||
super("irc", 194);
|
||||
}
|
||||
|
||||
IrcScheme(String name, int port) {
|
||||
super(name, port);
|
||||
}
|
||||
|
||||
}
|
17
src/main/java/org/xbib/net/scheme/LdapScheme.java
Normal file
17
src/main/java/org/xbib/net/scheme/LdapScheme.java
Normal file
|
@ -0,0 +1,17 @@
|
|||
package org.xbib.net.scheme;
|
||||
|
||||
/**
|
||||
* The LDAP scheme.
|
||||
* @see <a href="https://tools.ietf.org/html/rfc4516">LDAP RFC</a>
|
||||
*/
|
||||
class LdapScheme extends AbstractScheme {
|
||||
|
||||
LdapScheme() {
|
||||
super("ldap", 143);
|
||||
}
|
||||
|
||||
LdapScheme(String name, int port) {
|
||||
super(name, port);
|
||||
}
|
||||
|
||||
}
|
14
src/main/java/org/xbib/net/scheme/MailtoScheme.java
Normal file
14
src/main/java/org/xbib/net/scheme/MailtoScheme.java
Normal file
|
@ -0,0 +1,14 @@
|
|||
package org.xbib.net.scheme;
|
||||
|
||||
/**
|
||||
* The mailto scheme.
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc2368">mailto RFC</a>
|
||||
*/
|
||||
public class MailtoScheme extends AbstractScheme {
|
||||
|
||||
public MailtoScheme() {
|
||||
super("mailto", -1);
|
||||
}
|
||||
|
||||
}
|
18
src/main/java/org/xbib/net/scheme/NewsScheme.java
Normal file
18
src/main/java/org/xbib/net/scheme/NewsScheme.java
Normal file
|
@ -0,0 +1,18 @@
|
|||
package org.xbib.net.scheme;
|
||||
|
||||
/**
|
||||
* The news scheme.
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc5538">news RFC</a>
|
||||
*/
|
||||
class NewsScheme extends AbstractScheme {
|
||||
|
||||
NewsScheme() {
|
||||
super("nntp", 119);
|
||||
}
|
||||
|
||||
NewsScheme(String name, int port) {
|
||||
super(name, port);
|
||||
}
|
||||
|
||||
}
|
14
src/main/java/org/xbib/net/scheme/NntpScheme.java
Normal file
14
src/main/java/org/xbib/net/scheme/NntpScheme.java
Normal file
|
@ -0,0 +1,14 @@
|
|||
package org.xbib.net.scheme;
|
||||
|
||||
/**
|
||||
* The nttp scheme.
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc5538">NNTP RFC</a>
|
||||
*/
|
||||
class NntpScheme extends AbstractScheme {
|
||||
|
||||
NntpScheme() {
|
||||
super("nntp", 119);
|
||||
}
|
||||
|
||||
}
|
18
src/main/java/org/xbib/net/scheme/Pop3Scheme.java
Normal file
18
src/main/java/org/xbib/net/scheme/Pop3Scheme.java
Normal file
|
@ -0,0 +1,18 @@
|
|||
package org.xbib.net.scheme;
|
||||
|
||||
/**
|
||||
* The POP3 scheme.
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc1081">POP3 RFC</a>
|
||||
*/
|
||||
class Pop3Scheme extends AbstractScheme {
|
||||
|
||||
Pop3Scheme() {
|
||||
super("pop3", 110);
|
||||
}
|
||||
|
||||
Pop3Scheme(String name, int port) {
|
||||
super(name, port);
|
||||
}
|
||||
|
||||
}
|
12
src/main/java/org/xbib/net/scheme/RedisScheme.java
Normal file
12
src/main/java/org/xbib/net/scheme/RedisScheme.java
Normal file
|
@ -0,0 +1,12 @@
|
|||
package org.xbib.net.scheme;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class RedisScheme extends AbstractScheme {
|
||||
|
||||
RedisScheme() {
|
||||
super("redis", 6379);
|
||||
}
|
||||
|
||||
}
|
12
src/main/java/org/xbib/net/scheme/RsyncScheme.java
Normal file
12
src/main/java/org/xbib/net/scheme/RsyncScheme.java
Normal file
|
@ -0,0 +1,12 @@
|
|||
package org.xbib.net.scheme;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class RsyncScheme extends SshScheme {
|
||||
|
||||
RsyncScheme() {
|
||||
super("rsync", 873);
|
||||
}
|
||||
|
||||
}
|
12
src/main/java/org/xbib/net/scheme/RtmpScheme.java
Normal file
12
src/main/java/org/xbib/net/scheme/RtmpScheme.java
Normal file
|
@ -0,0 +1,12 @@
|
|||
package org.xbib.net.scheme;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class RtmpScheme extends AbstractScheme {
|
||||
|
||||
RtmpScheme() {
|
||||
super("rtmp", 1935);
|
||||
}
|
||||
|
||||
}
|
14
src/main/java/org/xbib/net/scheme/RtspScheme.java
Normal file
14
src/main/java/org/xbib/net/scheme/RtspScheme.java
Normal file
|
@ -0,0 +1,14 @@
|
|||
package org.xbib.net.scheme;
|
||||
|
||||
/**
|
||||
* The RTSP scheme.
|
||||
*
|
||||
* @see <a href="https://www.ietf.org/rfc/rfc2326.txt">RTSP RFC</a>
|
||||
*/
|
||||
class RtspScheme extends AbstractScheme {
|
||||
|
||||
RtspScheme() {
|
||||
super("rtsp", 554);
|
||||
}
|
||||
|
||||
}
|
48
src/main/java/org/xbib/net/scheme/Scheme.java
Normal file
48
src/main/java/org/xbib/net/scheme/Scheme.java
Normal file
|
@ -0,0 +1,48 @@
|
|||
package org.xbib.net.scheme;
|
||||
|
||||
import org.xbib.net.URL;
|
||||
|
||||
/**
|
||||
* Interface implemented by custom scheme parsers.
|
||||
*/
|
||||
public interface Scheme {
|
||||
|
||||
String DNS = "dns";
|
||||
String FILE = "file";
|
||||
String FTP = "ftp";
|
||||
String GIT = "git";
|
||||
String GIT_HTTPS = "git+https";
|
||||
String GOPHER = "gopher";
|
||||
String HTTP = "http";
|
||||
String HTTPS = "https";
|
||||
String IMAP = "imap";
|
||||
String IMAPS = "imaps";
|
||||
String IRC = "irc";
|
||||
String LDAP = "ldap";
|
||||
String LDAPS = "ldaps";
|
||||
String MAILTO = "mailto";
|
||||
String NEWS = "news";
|
||||
String NNTP = "nntp";
|
||||
String POP3 = "pop3";
|
||||
String POP3S = "pop3s";
|
||||
String REDIS = "redis";
|
||||
String RSYNC = "rsync";
|
||||
String RTMP = "rtmp";
|
||||
String RTSP = "rtsp";
|
||||
String SFTP = "sftp";
|
||||
String SMTP = "smtp";
|
||||
String SMTPS = "smtps";
|
||||
String SNEWS = "snews";
|
||||
String SSH = "ssh";
|
||||
String TELNET = "telnet";
|
||||
String TFTP = "tftp";
|
||||
String URN = "urn";
|
||||
String WS = "ws";
|
||||
String WSS = "wss";
|
||||
|
||||
String getName();
|
||||
|
||||
int getDefaultPort();
|
||||
|
||||
URL normalize(URL url);
|
||||
}
|
79
src/main/java/org/xbib/net/scheme/SchemeRegistry.java
Normal file
79
src/main/java/org/xbib/net/scheme/SchemeRegistry.java
Normal file
|
@ -0,0 +1,79 @@
|
|||
package org.xbib.net.scheme;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
/**
|
||||
* Registry of URL schemes.
|
||||
*/
|
||||
public final class SchemeRegistry {
|
||||
|
||||
private static final SchemeRegistry registry = new SchemeRegistry();
|
||||
|
||||
private final Map<String, Scheme> schemes;
|
||||
|
||||
private SchemeRegistry() {
|
||||
schemes = new HashMap<>();
|
||||
schemes.put(Scheme.DNS , new DnsScheme());
|
||||
schemes.put(Scheme.FILE , new FileScheme());
|
||||
schemes.put(Scheme.FTP, new FtpScheme());
|
||||
schemes.put(Scheme.GIT, new GitScheme());
|
||||
schemes.put(Scheme.GIT_HTTPS, new GitSecureHttpScheme());
|
||||
schemes.put(Scheme.GOPHER, new GopherScheme());
|
||||
schemes.put(Scheme.HTTP, new HttpScheme());
|
||||
schemes.put(Scheme.HTTPS, new SecureHttpScheme());
|
||||
schemes.put(Scheme.IMAP, new ImapScheme());
|
||||
schemes.put(Scheme.IMAPS, new SecureImapScheme());
|
||||
schemes.put(Scheme.IRC, new IrcScheme());
|
||||
schemes.put(Scheme.LDAP, new LdapScheme());
|
||||
schemes.put(Scheme.LDAPS, new SecureLdapScheme());
|
||||
schemes.put(Scheme.MAILTO, new MailtoScheme());
|
||||
schemes.put(Scheme.NEWS, new NewsScheme());
|
||||
schemes.put(Scheme.NNTP, new NntpScheme());
|
||||
schemes.put(Scheme.POP3, new Pop3Scheme());
|
||||
schemes.put(Scheme.POP3S, new SecurePop3Scheme());
|
||||
schemes.put(Scheme.REDIS, new RedisScheme());
|
||||
schemes.put(Scheme.RSYNC, new RsyncScheme());
|
||||
schemes.put(Scheme.RTMP, new RtmpScheme());
|
||||
schemes.put(Scheme.RTSP, new RtspScheme());
|
||||
schemes.put(Scheme.SFTP, new SftpScheme());
|
||||
schemes.put(Scheme.SMTP, new SmtpScheme());
|
||||
schemes.put(Scheme.SMTPS, new SecureSmtpScheme());
|
||||
schemes.put(Scheme.SNEWS, new SecureNewsScheme());
|
||||
schemes.put(Scheme.SSH, new SshScheme());
|
||||
schemes.put(Scheme.TELNET, new TelnetScheme());
|
||||
schemes.put(Scheme.TFTP, new TftpScheme());
|
||||
schemes.put(Scheme.URN, new UrnScheme());
|
||||
schemes.put(Scheme.WS, new WebSocketScheme());
|
||||
schemes.put(Scheme.WSS, new SecureWebSocketScheme());
|
||||
for (Scheme scheme : ServiceLoader.load(Scheme.class)) {
|
||||
register(scheme);
|
||||
}
|
||||
}
|
||||
|
||||
public static SchemeRegistry getInstance() {
|
||||
return registry;
|
||||
}
|
||||
|
||||
public boolean register(Scheme scheme) {
|
||||
String name = scheme.getName();
|
||||
if (name == null) {
|
||||
return false;
|
||||
}
|
||||
if (!schemes.containsKey(name)) {
|
||||
schemes.put(name.toLowerCase(), scheme);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public Scheme getScheme(String scheme) {
|
||||
if (scheme == null) {
|
||||
return null;
|
||||
}
|
||||
Scheme s = schemes.get(scheme.toLowerCase());
|
||||
return s != null ? s : new DefaultScheme(scheme);
|
||||
}
|
||||
}
|
12
src/main/java/org/xbib/net/scheme/SecureHttpScheme.java
Normal file
12
src/main/java/org/xbib/net/scheme/SecureHttpScheme.java
Normal file
|
@ -0,0 +1,12 @@
|
|||
package org.xbib.net.scheme;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class SecureHttpScheme extends HttpScheme {
|
||||
|
||||
SecureHttpScheme() {
|
||||
super("https", 443);
|
||||
}
|
||||
|
||||
}
|
13
src/main/java/org/xbib/net/scheme/SecureImapScheme.java
Normal file
13
src/main/java/org/xbib/net/scheme/SecureImapScheme.java
Normal file
|
@ -0,0 +1,13 @@
|
|||
package org.xbib.net.scheme;
|
||||
|
||||
/**
|
||||
* The IMAP scheme.
|
||||
* @see <a href="https://tools.ietf.org/html/rfc5092">IMAP scheme RFC</a>
|
||||
*/
|
||||
class SecureImapScheme extends ImapScheme {
|
||||
|
||||
SecureImapScheme() {
|
||||
super("imaps", 993);
|
||||
}
|
||||
|
||||
}
|
14
src/main/java/org/xbib/net/scheme/SecureLdapScheme.java
Normal file
14
src/main/java/org/xbib/net/scheme/SecureLdapScheme.java
Normal file
|
@ -0,0 +1,14 @@
|
|||
package org.xbib.net.scheme;
|
||||
|
||||
/**
|
||||
* The LDAPS scheme.
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc4516">LDAP RFC</a>
|
||||
*/
|
||||
class SecureLdapScheme extends LdapScheme {
|
||||
|
||||
SecureLdapScheme() {
|
||||
super("ldaps", 636);
|
||||
}
|
||||
|
||||
}
|
14
src/main/java/org/xbib/net/scheme/SecureNewsScheme.java
Normal file
14
src/main/java/org/xbib/net/scheme/SecureNewsScheme.java
Normal file
|
@ -0,0 +1,14 @@
|
|||
package org.xbib.net.scheme;
|
||||
|
||||
/**
|
||||
* The snews scheme.
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc5538">news RFC</a>
|
||||
*/
|
||||
class SecureNewsScheme extends NewsScheme {
|
||||
|
||||
SecureNewsScheme() {
|
||||
super("snews", 563);
|
||||
}
|
||||
|
||||
}
|
13
src/main/java/org/xbib/net/scheme/SecurePop3Scheme.java
Normal file
13
src/main/java/org/xbib/net/scheme/SecurePop3Scheme.java
Normal file
|
@ -0,0 +1,13 @@
|
|||
package org.xbib.net.scheme;
|
||||
|
||||
/**
|
||||
* The POP3S scheme.
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc2595">POP3 RFC</a>
|
||||
*/
|
||||
class SecurePop3Scheme extends Pop3Scheme {
|
||||
|
||||
SecurePop3Scheme() {
|
||||
super("pop3s", 995);
|
||||
}
|
||||
}
|
14
src/main/java/org/xbib/net/scheme/SecureSmtpScheme.java
Normal file
14
src/main/java/org/xbib/net/scheme/SecureSmtpScheme.java
Normal file
|
@ -0,0 +1,14 @@
|
|||
package org.xbib.net.scheme;
|
||||
|
||||
/**
|
||||
* The SMTPS scheme.
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc4409">SMTP RFC</a>
|
||||
*/
|
||||
class SecureSmtpScheme extends SmtpScheme {
|
||||
|
||||
SecureSmtpScheme() {
|
||||
super("smtps", 587);
|
||||
}
|
||||
|
||||
}
|
12
src/main/java/org/xbib/net/scheme/SecureWebSocketScheme.java
Normal file
12
src/main/java/org/xbib/net/scheme/SecureWebSocketScheme.java
Normal file
|
@ -0,0 +1,12 @@
|
|||
package org.xbib.net.scheme;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class SecureWebSocketScheme extends WebSocketScheme {
|
||||
|
||||
SecureWebSocketScheme() {
|
||||
super("wss", 443);
|
||||
}
|
||||
|
||||
}
|
12
src/main/java/org/xbib/net/scheme/SftpScheme.java
Normal file
12
src/main/java/org/xbib/net/scheme/SftpScheme.java
Normal file
|
@ -0,0 +1,12 @@
|
|||
package org.xbib.net.scheme;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class SftpScheme extends SshScheme {
|
||||
|
||||
SftpScheme() {
|
||||
super("sftp", 22);
|
||||
}
|
||||
|
||||
}
|
18
src/main/java/org/xbib/net/scheme/SmtpScheme.java
Normal file
18
src/main/java/org/xbib/net/scheme/SmtpScheme.java
Normal file
|
@ -0,0 +1,18 @@
|
|||
package org.xbib.net.scheme;
|
||||
|
||||
/**
|
||||
* The SMTP scheme.
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc5321">SMTP RFC</a>
|
||||
*/
|
||||
class SmtpScheme extends AbstractScheme {
|
||||
|
||||
SmtpScheme() {
|
||||
super("smtp", 25);
|
||||
}
|
||||
|
||||
SmtpScheme(String name, int port) {
|
||||
super(name, port);
|
||||
}
|
||||
|
||||
}
|
15
src/main/java/org/xbib/net/scheme/SshScheme.java
Normal file
15
src/main/java/org/xbib/net/scheme/SshScheme.java
Normal file
|
@ -0,0 +1,15 @@
|
|||
package org.xbib.net.scheme;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class SshScheme extends HttpScheme {
|
||||
|
||||
SshScheme() {
|
||||
super("ssh", 22);
|
||||
}
|
||||
|
||||
SshScheme(String name, int port) {
|
||||
super(name, port);
|
||||
}
|
||||
}
|
14
src/main/java/org/xbib/net/scheme/TelnetScheme.java
Normal file
14
src/main/java/org/xbib/net/scheme/TelnetScheme.java
Normal file
|
@ -0,0 +1,14 @@
|
|||
package org.xbib.net.scheme;
|
||||
|
||||
/**
|
||||
* The TELNET scheme.
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc4248">TELNET RFC</a>
|
||||
*/
|
||||
class TelnetScheme extends AbstractScheme {
|
||||
|
||||
TelnetScheme() {
|
||||
super("telnet", 23);
|
||||
}
|
||||
|
||||
}
|
13
src/main/java/org/xbib/net/scheme/TftpScheme.java
Normal file
13
src/main/java/org/xbib/net/scheme/TftpScheme.java
Normal file
|
@ -0,0 +1,13 @@
|
|||
package org.xbib.net.scheme;
|
||||
|
||||
/**
|
||||
* The TFTP scheme.
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc1350">TFTP RFC</a>
|
||||
*/
|
||||
class TftpScheme extends FtpScheme {
|
||||
|
||||
TftpScheme() {
|
||||
super("tftp", 69);
|
||||
}
|
||||
}
|
14
src/main/java/org/xbib/net/scheme/UrnScheme.java
Normal file
14
src/main/java/org/xbib/net/scheme/UrnScheme.java
Normal file
|
@ -0,0 +1,14 @@
|
|||
package org.xbib.net.scheme;
|
||||
|
||||
/**
|
||||
* The URN scheme.
|
||||
*
|
||||
* @see <a href="https://www.ietf.org/rfc/rfc2141.txt">URN RFC</a>
|
||||
*/
|
||||
class UrnScheme extends AbstractScheme {
|
||||
|
||||
UrnScheme() {
|
||||
super("urn", -1);
|
||||
}
|
||||
|
||||
}
|
16
src/main/java/org/xbib/net/scheme/WebSocketScheme.java
Normal file
16
src/main/java/org/xbib/net/scheme/WebSocketScheme.java
Normal file
|
@ -0,0 +1,16 @@
|
|||
package org.xbib.net.scheme;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class WebSocketScheme extends HttpScheme {
|
||||
|
||||
WebSocketScheme() {
|
||||
super("ws", 80);
|
||||
}
|
||||
|
||||
WebSocketScheme(String name, int port) {
|
||||
super(name, port);
|
||||
}
|
||||
|
||||
}
|
4
src/main/java/org/xbib/net/scheme/package-info.java
Normal file
4
src/main/java/org/xbib/net/scheme/package-info.java
Normal file
|
@ -0,0 +1,4 @@
|
|||
/**
|
||||
* Classes for schemes.
|
||||
*/
|
||||
package org.xbib.net.scheme;
|
48
src/main/java/org/xbib/net/template/URITemplate.java
Normal file
48
src/main/java/org/xbib/net/template/URITemplate.java
Normal file
|
@ -0,0 +1,48 @@
|
|||
package org.xbib.net.template;
|
||||
|
||||
import org.xbib.net.URL;
|
||||
import org.xbib.net.template.expression.URITemplateExpression;
|
||||
import org.xbib.net.template.parse.URITemplateParser;
|
||||
import org.xbib.net.template.vars.Variables;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class URITemplate {
|
||||
|
||||
private final List<URITemplateExpression> expressions;
|
||||
|
||||
public URITemplate(String input) {
|
||||
this.expressions = URITemplateParser.parse(input);
|
||||
}
|
||||
|
||||
public List<URITemplateExpression> expressions() {
|
||||
return expressions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand this template to a string given a list of variables.
|
||||
*
|
||||
* @param vars the variable map (names as keys, contents as values)
|
||||
* @return expanded string
|
||||
*/
|
||||
public String toString(Variables vars) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (URITemplateExpression expression : expressions) {
|
||||
sb.append(expression.expand(vars));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand this template to a URL given a set of variables.
|
||||
*
|
||||
* @param vars the variables
|
||||
* @return a URL
|
||||
*/
|
||||
public URL toURL(Variables vars) {
|
||||
return URL.from(toString(vars));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
package org.xbib.net.template.expression;
|
||||
|
||||
/**
|
||||
*/
|
||||
public enum ExpressionType {
|
||||
/*
|
||||
* Simple character expansion.
|
||||
*/
|
||||
SIMPLE("", ',', false, ""),
|
||||
/*
|
||||
* Reserved character expansion.
|
||||
*/
|
||||
RESERVED("", ',', false, ""),
|
||||
/*
|
||||
* Name labels expansion.
|
||||
*/
|
||||
NAME_LABELS(".", '.', false, ""),
|
||||
/*
|
||||
* Path segments expansion.
|
||||
*/
|
||||
PATH_SEGMENTS("/", '/', false, ""),
|
||||
/*
|
||||
* Path parameters expansion.
|
||||
*/
|
||||
PATH_PARAMETERS(";", ';', true, ""),
|
||||
/*
|
||||
* Query string expansion.
|
||||
*/
|
||||
QUERY_STRING("?", '&', true, "="),
|
||||
/*
|
||||
* Query string continuation expansion.
|
||||
*/
|
||||
QUERY_CONT("&", '&', true, "="),
|
||||
/*
|
||||
* Fragment expansion.
|
||||
*/
|
||||
FRAGMENT("#", ',', false, "");
|
||||
|
||||
/**
|
||||
* Prefix string of expansion (requires at least one expanded token).
|
||||
*/
|
||||
private final String prefix;
|
||||
|
||||
/**
|
||||
* Separator if several tokens are present.
|
||||
*/
|
||||
private final char separator;
|
||||
|
||||
/**
|
||||
* Whether the variable (string, list) or key (map) name should be included
|
||||
* if no explode modifier is found.
|
||||
*/
|
||||
private final boolean named;
|
||||
|
||||
/**
|
||||
* String to append to a name if the matching value is empty (empty string,
|
||||
* empty list element, empty map value).
|
||||
*/
|
||||
private final String ifEmpty;
|
||||
|
||||
ExpressionType(String prefix, char separator, boolean named, String ifEmpty) {
|
||||
this.prefix = prefix;
|
||||
this.separator = separator;
|
||||
this.named = named;
|
||||
this.ifEmpty = ifEmpty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the prefix string for this expansion type.
|
||||
*
|
||||
* @return the prefix string
|
||||
*/
|
||||
public String getPrefix() {
|
||||
return prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the separator between token expansion elements.
|
||||
*
|
||||
* @return the separator
|
||||
*/
|
||||
public char getSeparator() {
|
||||
return separator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell whether the variable name should be used in expansion.
|
||||
*
|
||||
* @return true if this is the case
|
||||
*/
|
||||
public boolean isNamed() {
|
||||
return named;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the substitution string for empty values.
|
||||
*
|
||||
* @return the substitution string
|
||||
*/
|
||||
public String getIfEmpty() {
|
||||
return ifEmpty;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
package org.xbib.net.template.expression;
|
||||
|
||||
import org.xbib.net.template.render.ValueRenderer;
|
||||
import org.xbib.net.template.vars.Variables;
|
||||
import org.xbib.net.template.vars.specs.VariableSpec;
|
||||
import org.xbib.net.template.vars.values.VariableValue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class TemplateExpression implements URITemplateExpression {
|
||||
|
||||
private final ExpressionType expressionType;
|
||||
|
||||
private final List<VariableSpec> variableSpecs;
|
||||
|
||||
public TemplateExpression(ExpressionType expressionType, List<VariableSpec> variableSpecs) {
|
||||
this.expressionType = expressionType;
|
||||
this.variableSpecs = variableSpecs;
|
||||
if (expressionType == null) {
|
||||
throw new IllegalArgumentException("expression type must not be null");
|
||||
}
|
||||
if (variableSpecs == null) {
|
||||
throw new IllegalArgumentException("variables must not be null");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String expand(Variables vars) {
|
||||
List<String> expansions = new ArrayList<>();
|
||||
VariableValue value;
|
||||
ValueRenderer renderer;
|
||||
for (VariableSpec varspec : variableSpecs) {
|
||||
value = vars.get(varspec.getName());
|
||||
if (value == null) {
|
||||
continue;
|
||||
}
|
||||
renderer = value.getType().selectRenderer(expressionType);
|
||||
List<String> list = renderer.render(varspec, value);
|
||||
if (list != null) {
|
||||
expansions.addAll(list);
|
||||
}
|
||||
}
|
||||
if (expansions.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
return expressionType.getPrefix() + String.join(Character.toString(expressionType.getSeparator()), expansions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 31 * expressionType.hashCode() + variableSpecs.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
TemplateExpression other = (TemplateExpression) obj;
|
||||
return expressionType == other.expressionType && variableSpecs.equals(other.variableSpecs);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package org.xbib.net.template.expression;
|
||||
|
||||
import org.xbib.net.template.vars.Variables;
|
||||
|
||||
/**
|
||||
*/
|
||||
public
|
||||
class TemplateLiteral implements URITemplateExpression {
|
||||
|
||||
private final String literal;
|
||||
|
||||
public TemplateLiteral(String literal) {
|
||||
this.literal = literal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String expand(Variables vars) {
|
||||
return literal;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package org.xbib.net.template.expression;
|
||||
|
||||
import org.xbib.net.template.vars.Variables;
|
||||
|
||||
/**
|
||||
*/
|
||||
public interface URITemplateExpression {
|
||||
|
||||
String expand(Variables vars);
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
/**
|
||||
* Classes for URL template expressions.
|
||||
*/
|
||||
package org.xbib.net.template.expression;
|
4
src/main/java/org/xbib/net/template/package-info.java
Normal file
4
src/main/java/org/xbib/net/template/package-info.java
Normal file
|
@ -0,0 +1,4 @@
|
|||
/**
|
||||
* Classes for URL templates.
|
||||
*/
|
||||
package org.xbib.net.template;
|
|
@ -0,0 +1,63 @@
|
|||
package org.xbib.net.template.parse;
|
||||
|
||||
import org.xbib.net.matcher.CharMatcher;
|
||||
import org.xbib.net.template.expression.ExpressionType;
|
||||
import org.xbib.net.template.expression.TemplateExpression;
|
||||
import org.xbib.net.template.expression.URITemplateExpression;
|
||||
import org.xbib.net.template.vars.specs.VariableSpec;
|
||||
|
||||
import java.nio.CharBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class ExpressionParser implements TemplateParser {
|
||||
|
||||
private static final Map<Character, ExpressionType> EXPRESSION_TYPE_MAP;
|
||||
static {
|
||||
EXPRESSION_TYPE_MAP = new HashMap<>();
|
||||
EXPRESSION_TYPE_MAP.put('+', ExpressionType.RESERVED);
|
||||
EXPRESSION_TYPE_MAP.put('#', ExpressionType.FRAGMENT);
|
||||
EXPRESSION_TYPE_MAP.put('.', ExpressionType.NAME_LABELS);
|
||||
EXPRESSION_TYPE_MAP.put('/', ExpressionType.PATH_SEGMENTS);
|
||||
EXPRESSION_TYPE_MAP.put(';', ExpressionType.PATH_PARAMETERS);
|
||||
EXPRESSION_TYPE_MAP.put('?', ExpressionType.QUERY_STRING);
|
||||
EXPRESSION_TYPE_MAP.put('&', ExpressionType.QUERY_CONT);
|
||||
}
|
||||
private static final CharMatcher COMMA = CharMatcher.is(',');
|
||||
private static final CharMatcher END_EXPRESSION = CharMatcher.is('}');
|
||||
|
||||
|
||||
@Override
|
||||
public URITemplateExpression parse(CharBuffer buffer) {
|
||||
buffer.get();
|
||||
if (!buffer.hasRemaining()) {
|
||||
throw new IllegalArgumentException("early end of expression");
|
||||
}
|
||||
ExpressionType type = ExpressionType.SIMPLE;
|
||||
char c = buffer.charAt(0);
|
||||
if (EXPRESSION_TYPE_MAP.containsKey(c)) {
|
||||
char s = buffer.get();
|
||||
type = EXPRESSION_TYPE_MAP.get(s);
|
||||
}
|
||||
List<VariableSpec> varspecs = new ArrayList<>();
|
||||
while (true) {
|
||||
varspecs.add(VariableSpecParser.parse(buffer));
|
||||
if (!buffer.hasRemaining()) {
|
||||
throw new IllegalArgumentException("early end of expression");
|
||||
}
|
||||
c = buffer.get();
|
||||
if (COMMA.matches(c)) {
|
||||
continue;
|
||||
}
|
||||
if (END_EXPRESSION.matches(c)) {
|
||||
break;
|
||||
}
|
||||
throw new IllegalArgumentException("unexpected token");
|
||||
}
|
||||
return new TemplateExpression(type, varspecs);
|
||||
}
|
||||
}
|
45
src/main/java/org/xbib/net/template/parse/LiteralParser.java
Normal file
45
src/main/java/org/xbib/net/template/parse/LiteralParser.java
Normal file
|
@ -0,0 +1,45 @@
|
|||
package org.xbib.net.template.parse;
|
||||
|
||||
import org.xbib.net.matcher.CharMatcher;
|
||||
import org.xbib.net.template.expression.TemplateLiteral;
|
||||
import org.xbib.net.template.expression.URITemplateExpression;
|
||||
|
||||
import java.nio.CharBuffer;
|
||||
|
||||
/**
|
||||
*/
|
||||
public
|
||||
class LiteralParser implements TemplateParser {
|
||||
|
||||
@Override
|
||||
public URITemplateExpression parse(CharBuffer buffer) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
char c;
|
||||
while (buffer.hasRemaining()) {
|
||||
c = buffer.charAt(0);
|
||||
if (!CharMatcher.LITERALS.matches(c)) {
|
||||
break;
|
||||
}
|
||||
sb.append(buffer.get());
|
||||
if (CharMatcher.PERCENT.matches(c)) {
|
||||
parsePercentEncoded(buffer, sb);
|
||||
}
|
||||
}
|
||||
return new TemplateLiteral(sb.toString());
|
||||
}
|
||||
|
||||
private static void parsePercentEncoded(CharBuffer buffer, StringBuilder sb) {
|
||||
if (buffer.remaining() < 2) {
|
||||
throw new IllegalArgumentException("short read");
|
||||
}
|
||||
char first = buffer.get();
|
||||
if (!CharMatcher.HEXDIGIT.matches(first)) {
|
||||
throw new IllegalArgumentException("illegal percent encoding");
|
||||
}
|
||||
char second = buffer.get();
|
||||
if (!CharMatcher.HEXDIGIT.matches(second)) {
|
||||
throw new IllegalArgumentException("illegal percent encoding");
|
||||
}
|
||||
sb.append(first).append(second);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package org.xbib.net.template.parse;
|
||||
|
||||
import org.xbib.net.template.expression.URITemplateExpression;
|
||||
|
||||
import java.nio.CharBuffer;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public interface TemplateParser {
|
||||
URITemplateExpression parse(CharBuffer buffer);
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package org.xbib.net.template.parse;
|
||||
|
||||
import org.xbib.net.matcher.CharMatcher;
|
||||
import org.xbib.net.template.expression.URITemplateExpression;
|
||||
|
||||
import java.nio.CharBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class URITemplateParser {
|
||||
|
||||
private static final CharMatcher BEGIN_EXPRESSION = CharMatcher.is('{');
|
||||
|
||||
private URITemplateParser() {
|
||||
}
|
||||
|
||||
public static List<URITemplateExpression> parse(String input) {
|
||||
return parse(CharBuffer.wrap(input).asReadOnlyBuffer());
|
||||
}
|
||||
|
||||
public static List<URITemplateExpression> parse(CharBuffer buffer) {
|
||||
List<URITemplateExpression> ret = new ArrayList<>();
|
||||
TemplateParser templateParser;
|
||||
URITemplateExpression expression;
|
||||
while (buffer.hasRemaining()) {
|
||||
templateParser = selectParser(buffer);
|
||||
expression = templateParser.parse(buffer);
|
||||
ret.add(expression);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static TemplateParser selectParser(CharBuffer buffer) {
|
||||
char c = buffer.charAt(0);
|
||||
TemplateParser parser;
|
||||
if (CharMatcher.LITERALS.matches(c)) {
|
||||
parser = new LiteralParser();
|
||||
} else if (BEGIN_EXPRESSION.matches(c)) {
|
||||
parser = new ExpressionParser();
|
||||
} else {
|
||||
throw new IllegalArgumentException("no parser");
|
||||
}
|
||||
return parser;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
package org.xbib.net.template.parse;
|
||||
|
||||
import org.xbib.net.matcher.CharMatcher;
|
||||
import org.xbib.net.template.vars.specs.ExplodedVariable;
|
||||
import org.xbib.net.template.vars.specs.PrefixVariable;
|
||||
import org.xbib.net.template.vars.specs.SimpleVariable;
|
||||
import org.xbib.net.template.vars.specs.VariableSpec;
|
||||
|
||||
import java.nio.CharBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class VariableSpecParser {
|
||||
|
||||
private static final CharMatcher DIGIT = CharMatcher.inRange('0', '9')
|
||||
.precomputed();
|
||||
|
||||
private static final CharMatcher VARCHAR = DIGIT
|
||||
.or(CharMatcher.inRange('a', 'z'))
|
||||
.or(CharMatcher.inRange('A', 'Z'))
|
||||
.or(CharMatcher.is('_'))
|
||||
.or(CharMatcher.PERCENT)
|
||||
.precomputed();
|
||||
|
||||
private static final CharMatcher DOT = CharMatcher.is('.');
|
||||
|
||||
private static final CharMatcher COLON = CharMatcher.is(':');
|
||||
|
||||
private static final CharMatcher STAR = CharMatcher.is('*');
|
||||
|
||||
private VariableSpecParser() {
|
||||
}
|
||||
|
||||
public static VariableSpec parse(CharBuffer buffer) {
|
||||
String name = parseFullName(buffer);
|
||||
if (!buffer.hasRemaining()) {
|
||||
return new SimpleVariable(name);
|
||||
}
|
||||
char c = buffer.charAt(0);
|
||||
if (STAR.matches(c)) {
|
||||
buffer.get();
|
||||
return new ExplodedVariable(name);
|
||||
}
|
||||
if (COLON.matches(c)) {
|
||||
buffer.get();
|
||||
return new PrefixVariable(name, getPrefixLength(buffer));
|
||||
}
|
||||
return new SimpleVariable(name);
|
||||
}
|
||||
|
||||
private static String parseFullName(CharBuffer buffer) {
|
||||
List<String> components = new ArrayList<>();
|
||||
while (true) {
|
||||
components.add(readName(buffer));
|
||||
if (!buffer.hasRemaining()) {
|
||||
break;
|
||||
}
|
||||
if (!DOT.matches(buffer.charAt(0))) {
|
||||
break;
|
||||
}
|
||||
buffer.get();
|
||||
}
|
||||
return String.join(".", components);
|
||||
}
|
||||
|
||||
private static String readName(CharBuffer buffer) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
char c;
|
||||
while (buffer.hasRemaining()) {
|
||||
c = buffer.charAt(0);
|
||||
if (!VARCHAR.matches(c)) {
|
||||
break;
|
||||
}
|
||||
sb.append(buffer.get());
|
||||
if (CharMatcher.PERCENT.matches(c)) {
|
||||
parsePercentEncoded(buffer, sb);
|
||||
}
|
||||
}
|
||||
String ret = sb.toString();
|
||||
if (ret.isEmpty()) {
|
||||
throw new IllegalArgumentException("empty var name");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static void parsePercentEncoded(CharBuffer buffer, StringBuilder sb) {
|
||||
if (buffer.remaining() < 2) {
|
||||
throw new IllegalArgumentException("short read");
|
||||
}
|
||||
char first = buffer.get();
|
||||
if (!CharMatcher.HEXDIGIT.matches(first)) {
|
||||
throw new IllegalArgumentException("illegal percent encoding");
|
||||
}
|
||||
char second = buffer.get();
|
||||
if (!CharMatcher.HEXDIGIT.matches(second)) {
|
||||
throw new IllegalArgumentException("illegal percent encoding");
|
||||
}
|
||||
sb.append(first).append(second);
|
||||
}
|
||||
|
||||
private static int getPrefixLength(CharBuffer buffer) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
char c;
|
||||
while (buffer.hasRemaining()) {
|
||||
c = buffer.charAt(0);
|
||||
if (!DIGIT.matches(c)) {
|
||||
break;
|
||||
}
|
||||
sb.append(buffer.get());
|
||||
}
|
||||
String s = sb.toString();
|
||||
if (s.isEmpty()) {
|
||||
throw new IllegalArgumentException("empty prefix");
|
||||
}
|
||||
int ret;
|
||||
try {
|
||||
ret = Integer.parseInt(s);
|
||||
if (ret > 10000) {
|
||||
throw new NumberFormatException();
|
||||
}
|
||||
return ret;
|
||||
} catch (NumberFormatException ignored) {
|
||||
throw new IllegalArgumentException("prefix invalid / too large");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
/**
|
||||
* Classes for URL template parsers.
|
||||
*/
|
||||
package org.xbib.net.template.parse;
|
50
src/main/java/org/xbib/net/template/render/ListRenderer.java
Normal file
50
src/main/java/org/xbib/net/template/render/ListRenderer.java
Normal file
|
@ -0,0 +1,50 @@
|
|||
package org.xbib.net.template.render;
|
||||
|
||||
import org.xbib.net.template.expression.ExpressionType;
|
||||
import org.xbib.net.template.vars.values.VariableValue;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class ListRenderer extends MultiValueRenderer {
|
||||
|
||||
public ListRenderer(ExpressionType type) {
|
||||
super(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> renderNamedExploded(String varname, VariableValue value) {
|
||||
return value.getListValue().stream().map(element ->
|
||||
element.isEmpty() ? varname + ifEmpty : varname + '=' + pctEncode(element)
|
||||
).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> renderUnnamedExploded(VariableValue value) {
|
||||
return value.getListValue().stream().map(this::pctEncode).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> renderNamedNormal(String varname, VariableValue value) {
|
||||
StringBuilder sb = new StringBuilder(varname);
|
||||
if (value.isEmpty()) {
|
||||
return Collections.singletonList(sb.append(ifEmpty).toString());
|
||||
}
|
||||
sb.append('=');
|
||||
List<String> elements = value.getListValue().stream().map(this::pctEncode).collect(Collectors.toList());
|
||||
return Collections.singletonList(sb.toString() + String.join(",", elements));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> renderUnnamedNormal(VariableValue value) {
|
||||
if (value.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<String> elements = value.getListValue().stream().map(this::pctEncode).collect(Collectors.toList());
|
||||
return Collections.singletonList(String.join(",", elements));
|
||||
}
|
||||
}
|
62
src/main/java/org/xbib/net/template/render/MapRenderer.java
Normal file
62
src/main/java/org/xbib/net/template/render/MapRenderer.java
Normal file
|
@ -0,0 +1,62 @@
|
|||
package org.xbib.net.template.render;
|
||||
|
||||
import org.xbib.net.template.expression.ExpressionType;
|
||||
import org.xbib.net.template.vars.values.VariableValue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class MapRenderer extends MultiValueRenderer {
|
||||
|
||||
public MapRenderer(ExpressionType type) {
|
||||
super(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> renderNamedExploded(String varname, VariableValue value) {
|
||||
List<String> ret = new ArrayList<>();
|
||||
value.getMapValue().forEach((k, v) -> ret.add(pctEncode(k) + (v.isEmpty() ? ifEmpty : '=' + pctEncode(v))));
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> renderUnnamedExploded(VariableValue value) {
|
||||
List<String> ret = new ArrayList<>();
|
||||
value.getMapValue().forEach((k, v) -> ret.add(pctEncode(k) + '=' + pctEncode(v)));
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> renderNamedNormal(String varname, VariableValue value) {
|
||||
StringBuilder sb = new StringBuilder(varname);
|
||||
if (value.isEmpty()) {
|
||||
return Collections.singletonList(sb.append(ifEmpty).toString());
|
||||
}
|
||||
sb.append('=');
|
||||
List<String> elements = mapAsList(value).stream().map(this::pctEncode).collect(Collectors.toList());
|
||||
return Collections.singletonList(sb.toString() + String.join(",", elements));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> renderUnnamedNormal(VariableValue value) {
|
||||
if (value.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<String> elements = mapAsList(value).stream().map(this::pctEncode).collect(Collectors.toList());
|
||||
return Collections.singletonList(String.join(",", elements));
|
||||
}
|
||||
|
||||
private static List<String> mapAsList(VariableValue value) {
|
||||
List<String> ret = new ArrayList<>();
|
||||
value.getMapValue().forEach((k, v) -> {
|
||||
ret.add(k);
|
||||
ret.add(v);
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package org.xbib.net.template.render;
|
||||
|
||||
import org.xbib.net.template.expression.ExpressionType;
|
||||
import org.xbib.net.template.vars.specs.VariableSpec;
|
||||
import org.xbib.net.template.vars.values.VariableValue;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
abstract class MultiValueRenderer extends ValueRenderer {
|
||||
|
||||
MultiValueRenderer(ExpressionType type) {
|
||||
super(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> render(VariableSpec varspec, VariableValue value) {
|
||||
if (varspec.getPrefixLength() != -1) {
|
||||
throw new IllegalArgumentException("incompatible var spec value");
|
||||
}
|
||||
String varname = varspec.getName();
|
||||
return named ?
|
||||
(varspec.isExploded() ? renderNamedExploded(varname, value) : renderNamedNormal(varname, value)) :
|
||||
(varspec.isExploded() ? renderUnnamedExploded(value) : renderUnnamedNormal(value));
|
||||
}
|
||||
|
||||
protected abstract List<String> renderNamedExploded(String varname, VariableValue value);
|
||||
|
||||
protected abstract List<String> renderUnnamedExploded(VariableValue value);
|
||||
|
||||
/**
|
||||
* Rendering method for named expressions and non exploded varspecs.
|
||||
*
|
||||
* @param varname name of the variable (used in lists)
|
||||
* @param value value of the variable
|
||||
* @return list of rendered elements
|
||||
*/
|
||||
protected abstract List<String> renderNamedNormal(String varname, VariableValue value);
|
||||
|
||||
/**
|
||||
* Rendering method for non named expressions and non exploded varspecs.
|
||||
*
|
||||
* @param value value of the variable
|
||||
* @return list of rendered elements
|
||||
*/
|
||||
protected abstract List<String> renderUnnamedNormal(VariableValue value);
|
||||
}
|
22
src/main/java/org/xbib/net/template/render/NullRenderer.java
Normal file
22
src/main/java/org/xbib/net/template/render/NullRenderer.java
Normal file
|
@ -0,0 +1,22 @@
|
|||
package org.xbib.net.template.render;
|
||||
|
||||
import org.xbib.net.template.expression.ExpressionType;
|
||||
import org.xbib.net.template.vars.specs.VariableSpec;
|
||||
import org.xbib.net.template.vars.values.VariableValue;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class NullRenderer extends ValueRenderer {
|
||||
|
||||
public NullRenderer(ExpressionType type) {
|
||||
super(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> render(VariableSpec varspec, VariableValue value) {
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
package org.xbib.net.template.render;
|
||||
|
||||
import org.xbib.net.template.expression.ExpressionType;
|
||||
import org.xbib.net.template.vars.specs.VariableSpec;
|
||||
import org.xbib.net.template.vars.values.VariableValue;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class StringRenderer extends ValueRenderer {
|
||||
|
||||
public StringRenderer(ExpressionType type) {
|
||||
super(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> render(VariableSpec varspec, VariableValue value) {
|
||||
return Collections.singletonList(doRender(varspec, value.getScalarValue()));
|
||||
}
|
||||
|
||||
private String doRender(VariableSpec varspec, String value) {
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
StringBuilder sb = new StringBuilder(value.length());
|
||||
if (named) {
|
||||
sb.append(varspec.getName());
|
||||
if (value.isEmpty()) {
|
||||
return sb.append(ifEmpty).toString();
|
||||
}
|
||||
sb.append('=');
|
||||
}
|
||||
int prefixLen = varspec.getPrefixLength();
|
||||
if (prefixLen == -1) {
|
||||
return sb.append(pctEncode(value)).toString();
|
||||
}
|
||||
int len = value.codePointCount(0, value.length());
|
||||
return len <= prefixLen ?
|
||||
sb.append(pctEncode(value)).toString() :
|
||||
sb.append(pctEncode(nFirstChars(value, prefixLen))).toString();
|
||||
}
|
||||
|
||||
private static String nFirstChars(String s, int n) {
|
||||
int realIndex = n;
|
||||
while (s.codePointCount(0, realIndex) != n) {
|
||||
realIndex++;
|
||||
}
|
||||
return s.substring(0, realIndex);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
package org.xbib.net.template.render;
|
||||
|
||||
import org.xbib.net.PercentEncoder;
|
||||
import org.xbib.net.PercentEncoders;
|
||||
import org.xbib.net.template.expression.ExpressionType;
|
||||
import org.xbib.net.template.vars.specs.VariableSpec;
|
||||
import org.xbib.net.template.vars.values.VariableValue;
|
||||
|
||||
import java.nio.charset.CharacterCodingException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The algorithm used for rendering is centered around this class,and is
|
||||
* adapted from the algorithm suggested in the RFC's appendix.
|
||||
*
|
||||
* Eventually, rendering can be viewed as joining a list of rendered strings
|
||||
* with the expression type separator; if the resulting list is empty, the end
|
||||
* result is the empty string; otherwise, it is the expression's prefix string
|
||||
* (if any) followed by the joined list of rendered strings.
|
||||
*
|
||||
* This class renders one variable value according to the expression type and
|
||||
* value type. The rendering method returns a list, which can be empty.
|
||||
*/
|
||||
public abstract class ValueRenderer {
|
||||
/**
|
||||
* Whether variable values are named during expansion.
|
||||
*/
|
||||
protected final boolean named;
|
||||
|
||||
/**
|
||||
* Substitution string for an empty value/list member/map value.
|
||||
*/
|
||||
protected final String ifEmpty;
|
||||
|
||||
/**
|
||||
* The percent encoder.
|
||||
*/
|
||||
private final PercentEncoder percentEncoder;
|
||||
|
||||
protected ValueRenderer(ExpressionType type) {
|
||||
named = type.isNamed();
|
||||
ifEmpty = type.getIfEmpty();
|
||||
switch (type) {
|
||||
case RESERVED:
|
||||
case FRAGMENT:
|
||||
percentEncoder = PercentEncoders.getQueryEncoder(StandardCharsets.UTF_8);
|
||||
break;
|
||||
default:
|
||||
percentEncoder = PercentEncoders.getUnreservedEncoder(StandardCharsets.UTF_8);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a value given a varspec and value.
|
||||
*
|
||||
* @param varspec the varspec
|
||||
* @param value the matching variable value
|
||||
* @return a list of rendered strings
|
||||
*/
|
||||
public abstract List<String> render(VariableSpec varspec, VariableValue value);
|
||||
|
||||
/**
|
||||
* Render a string value, doing character percent-encoding where needed.
|
||||
*
|
||||
* The character set on which to perform percent encoding is dependent
|
||||
* on the expression type.
|
||||
*
|
||||
* @param s the string to encode
|
||||
* @return an encoded string
|
||||
*/
|
||||
protected String pctEncode(String s) {
|
||||
try {
|
||||
return percentEncoder.encode(s);
|
||||
} catch (CharacterCodingException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
/**
|
||||
* Classes for URL template renderers.
|
||||
*/
|
||||
package org.xbib.net.template.render;
|
131
src/main/java/org/xbib/net/template/vars/Variables.java
Normal file
131
src/main/java/org/xbib/net/template/vars/Variables.java
Normal file
|
@ -0,0 +1,131 @@
|
|||
package org.xbib.net.template.vars;
|
||||
|
||||
import org.xbib.net.template.vars.values.ListValue;
|
||||
import org.xbib.net.template.vars.values.MapValue;
|
||||
import org.xbib.net.template.vars.values.ScalarValue;
|
||||
import org.xbib.net.template.vars.values.VariableValue;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class Variables {
|
||||
|
||||
private final Map<String, VariableValue> vars;
|
||||
|
||||
Variables(Builder builder) {
|
||||
this.vars = builder.vars;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new builder for this class.
|
||||
*
|
||||
* @return a {@link Builder}
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value associated with a variable name.
|
||||
*
|
||||
* @param varname the variable name
|
||||
* @return the value, or {@code null} if there is no matching value
|
||||
*/
|
||||
public VariableValue get(String varname) {
|
||||
return vars.get(varname);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return vars.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public static class Builder {
|
||||
|
||||
private final Map<String, VariableValue> vars = new LinkedHashMap<>();
|
||||
|
||||
Builder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate a map, list, or object to a variable name.
|
||||
*
|
||||
* @param varname the variable name
|
||||
* @param value the value, as a {@link VariableValue}
|
||||
* @return this
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public Builder add(String varname, Object value) {
|
||||
if (value instanceof VariableValue) {
|
||||
addValue(varname, (VariableValue) value);
|
||||
} else if (value instanceof Map) {
|
||||
addValue(varname, (Map) value);
|
||||
} else if (value instanceof List) {
|
||||
addValue(varname, (List) value);
|
||||
} else {
|
||||
addValue(varname, new ScalarValue(value));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate a value to a variable name.
|
||||
*
|
||||
* @param varname the variable name
|
||||
* @param value the value, as a {@link VariableValue}
|
||||
* @return this
|
||||
*/
|
||||
private Builder addValue(String varname, VariableValue value) {
|
||||
vars.put(varname, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut method to associate a name with a list value.
|
||||
* Any {@link Iterable} can be used (thereby including all collections:
|
||||
* sets, lists, etc). Note that it is your responsibility that objects in
|
||||
* this iterable implement {@link Object#toString()} correctly.
|
||||
*
|
||||
* @param varname the variable name
|
||||
* @param iterable the iterable
|
||||
* @return this
|
||||
*/
|
||||
private Builder addValue(String varname, Iterable<Object> iterable) {
|
||||
return add(varname, ListValue.copyOf(iterable));
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to associate a variable name to a map value.
|
||||
* Values of the map can be of any type. You should ensure that they
|
||||
* implement {@link Object#toString()} correctly.
|
||||
*
|
||||
* @param varname the variable name
|
||||
* @param map the map
|
||||
* @return this
|
||||
*/
|
||||
private Builder addValue(String varname, Map<String, ?> map) {
|
||||
return add(varname, MapValue.copyOf(map));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all variable definitions from another variable map.
|
||||
* @param other the other variable map to copy definitions from
|
||||
* @return this
|
||||
* @throws NullPointerException other variable map is null
|
||||
*/
|
||||
public Builder add(Variables other) {
|
||||
vars.putAll(other.vars);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Variables build() {
|
||||
return new Variables(this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
/**
|
||||
* Classes for URL template variables.
|
||||
*/
|
||||
package org.xbib.net.template.vars;
|
|
@ -0,0 +1,46 @@
|
|||
package org.xbib.net.template.vars.specs;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class ExplodedVariable extends VariableSpec {
|
||||
|
||||
public ExplodedVariable(String name) {
|
||||
super(VariableSpecType.EXPLODED, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExploded() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPrefixLength() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return name.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
ExplodedVariable other = (ExplodedVariable) obj;
|
||||
return name.equals(other.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name + " (exploded)";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package org.xbib.net.template.vars.specs;
|
||||
|
||||
/**
|
||||
* A varspec with a prefix modifier (for instance, {@code foo:3} in {@code {foo:3}}.
|
||||
*/
|
||||
public class PrefixVariable extends VariableSpec {
|
||||
|
||||
private final int length;
|
||||
|
||||
public PrefixVariable(String name, int length) {
|
||||
super(VariableSpecType.PREFIX, name);
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExploded() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPrefixLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 31 * name.hashCode() + length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
PrefixVariable other = (PrefixVariable) obj;
|
||||
return name.equals(other.name) && length == other.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name + " (prefix length: " + length + ')';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package org.xbib.net.template.vars.specs;
|
||||
|
||||
/**
|
||||
* A varspec without modifier (for instance, {@code foo} in {@code {foo}}.
|
||||
*/
|
||||
public class SimpleVariable extends VariableSpec {
|
||||
|
||||
public SimpleVariable(String name) {
|
||||
super(VariableSpecType.SIMPLE, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExploded() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPrefixLength() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return name.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
SimpleVariable other = (SimpleVariable) obj;
|
||||
return name.equals(other.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name + " (simple)";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
package org.xbib.net.template.vars.specs;
|
||||
|
||||
/**
|
||||
* A variable specifier.
|
||||
*
|
||||
* A template expression can have one or more variable specifiers. For
|
||||
* instance, in {@code {+path:3,var}}, variable specifiers are {@code path:3}
|
||||
* and {@code var}.
|
||||
*
|
||||
* This class records the name of this specifier and its modifier, if any.
|
||||
*/
|
||||
public abstract class VariableSpec {
|
||||
|
||||
protected final String name;
|
||||
|
||||
private final VariableSpecType type;
|
||||
|
||||
protected VariableSpec(VariableSpecType type, String name) {
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the modifier type for this var spec.
|
||||
*
|
||||
* @return the modifier type
|
||||
*/
|
||||
public final VariableSpecType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name for this var spec.
|
||||
*
|
||||
* @return the name
|
||||
*/
|
||||
public final String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell whether this varspec has an explode modifier.
|
||||
*
|
||||
* @return true if an explode modifier is present
|
||||
*/
|
||||
public abstract boolean isExploded();
|
||||
|
||||
/**
|
||||
* Return the prefix length for this varspec.
|
||||
*
|
||||
* Returns -1 if no prefix length is specified. Recall: valid values are
|
||||
* integers between 0 and 10000.
|
||||
*
|
||||
* @return the prefix length, or -1 if no prefix modidifer
|
||||
*/
|
||||
public abstract int getPrefixLength();
|
||||
|
||||
@Override
|
||||
public abstract int hashCode();
|
||||
|
||||
@Override
|
||||
public abstract boolean equals(Object obj);
|
||||
|
||||
@Override
|
||||
public abstract String toString();
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue