initial commit

This commit is contained in:
Jörg Prante 2017-07-24 16:02:50 +02:00
commit 87aa1c7ca1
134 changed files with 17780 additions and 0 deletions

16
.gitignore vendored Normal file
View 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
View 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
View 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
View 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"

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

Binary file not shown.

View 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
View 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
View 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
View 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; }

View 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

View file

@ -0,0 +1,4 @@
= Net classes for Java
Jörg Prante
Version 1.0

View 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);
}
}

View 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());
}
}
}

View 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);
}
}
}

View 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) '=');
}
}

View file

@ -0,0 +1,9 @@
package org.xbib.net;
/**
* The TCP/IP network protocol versions.
*/
public enum ProtocolVersion {
IPV4, IPV6, IPV46, NONE
}

View 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;
}
}
}

View 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();
}
}

File diff suppressed because it is too large Load diff

View 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;
}
}

View file

@ -0,0 +1,4 @@
/**
* Classes for internal use in the {@code org.xbib.net} package.
*/
package org.xbib.net.internal;

View 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);
}
}
}
}
}

View file

@ -0,0 +1,4 @@
/**
* Classes for URL matching.
*/
package org.xbib.net.matcher;

View file

@ -0,0 +1,4 @@
/**
* Classes for URL building and parsing.
*/
package org.xbib.net;

View 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;
}
}

View 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);
}
}

View 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;
}
}
}
}

View 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;
}
}

View 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;
}
}

View file

@ -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;
}
}

View 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;
}
}
}

View file

@ -0,0 +1,4 @@
/**
* Classes for URL path nomalizing, decoding, and matching.
*/
package org.xbib.net.path;

View 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;
}
}

View file

@ -0,0 +1,12 @@
package org.xbib.net.scheme;
/**
*
*/
public class DefaultScheme extends AbstractScheme {
public DefaultScheme(String name) {
super(name, -1);
}
}

View 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);
}
}

View file

@ -0,0 +1,12 @@
package org.xbib.net.scheme;
/**
*
*/
class FileScheme extends HttpScheme {
FileScheme() {
super("file", -1);
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View file

@ -0,0 +1,12 @@
package org.xbib.net.scheme;
/**
*
*/
class GopherScheme extends AbstractScheme {
GopherScheme() {
super("gopher", 70);
}
}

View 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();
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View file

@ -0,0 +1,12 @@
package org.xbib.net.scheme;
/**
*
*/
class RedisScheme extends AbstractScheme {
RedisScheme() {
super("redis", 6379);
}
}

View file

@ -0,0 +1,12 @@
package org.xbib.net.scheme;
/**
*
*/
class RsyncScheme extends SshScheme {
RsyncScheme() {
super("rsync", 873);
}
}

View file

@ -0,0 +1,12 @@
package org.xbib.net.scheme;
/**
*
*/
class RtmpScheme extends AbstractScheme {
RtmpScheme() {
super("rtmp", 1935);
}
}

View 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);
}
}

View 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);
}

View 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);
}
}

View file

@ -0,0 +1,12 @@
package org.xbib.net.scheme;
/**
*
*/
class SecureHttpScheme extends HttpScheme {
SecureHttpScheme() {
super("https", 443);
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View file

@ -0,0 +1,12 @@
package org.xbib.net.scheme;
/**
*
*/
class SecureWebSocketScheme extends WebSocketScheme {
SecureWebSocketScheme() {
super("wss", 443);
}
}

View file

@ -0,0 +1,12 @@
package org.xbib.net.scheme;
/**
*
*/
class SftpScheme extends SshScheme {
SftpScheme() {
super("sftp", 22);
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View file

@ -0,0 +1,4 @@
/**
* Classes for schemes.
*/
package org.xbib.net.scheme;

View 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));
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -0,0 +1,10 @@
package org.xbib.net.template.expression;
import org.xbib.net.template.vars.Variables;
/**
*/
public interface URITemplateExpression {
String expand(Variables vars);
}

View file

@ -0,0 +1,4 @@
/**
* Classes for URL template expressions.
*/
package org.xbib.net.template.expression;

View file

@ -0,0 +1,4 @@
/**
* Classes for URL templates.
*/
package org.xbib.net.template;

View file

@ -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);
}
}

View 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);
}
}

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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");
}
}
}

View file

@ -0,0 +1,4 @@
/**
* Classes for URL template parsers.
*/
package org.xbib.net.template.parse;

View 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));
}
}

View 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;
}
}

View file

@ -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);
}

View 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;
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}
}

View file

@ -0,0 +1,4 @@
/**
* Classes for URL template renderers.
*/
package org.xbib.net.template.render;

View 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);
}
}
}

View file

@ -0,0 +1,4 @@
/**
* Classes for URL template variables.
*/
package org.xbib.net.template.vars;

View file

@ -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)";
}
}

View file

@ -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 + ')';
}
}

View file

@ -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)";
}
}

View file

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