initial commit

main
Jörg Prante 8 years ago
commit 6905991002

16
.gitignore vendored

@ -0,0 +1,16 @@
/data
/work
/logs
/.idea
/target
.DS_Store
*.iml
/.settings
/.classpath
/.project
/.gradle
build
/plugins
/sessions
*~
*.MARC

@ -0,0 +1,12 @@
language: java
sudo: required
jdk:
- oraclejdk8
cache:
directories:
- $HOME/.m2
after_success:
- ./gradlew sonarqube -Dsonar.host.url=https://sonarqube.com -Dsonar.login=$SONAR_TOKEN
env:
global:
secure: n1Ai4q/yMLn/Pg5pA4lTavoJoe7mQYB1PSKnZAqwbgyla94ySzK6iyBCBiNs/foMPisB/x+DHvmUXTsjvquw9Ay48ZITCV3xhcWzD0eZM2TMoG19CpRAEe8L8LNuYiti9k89ijDdUGZ5ifsvQNTGNHksouayAuApC3PrTUejJfR6SYrp1ZsQTbsMlr+4XU3p7QknK5rGgOwATIMP28F+bVnB05WJtlJA3b0SeucCurn3wJ4FGBQXRYmdlT7bQhNE4QgZM1VzcUFD/K0TBxzzq/otb/lNRSifyoekktDmJwQnaT9uQ4R8R6KdQ2Kb38Rvgjur+TKm5i1G8qS2+6LnIxQJG1aw3JvKK6W0wWCgnAVVRrXaCLday9NuY59tuh1mfjQ10UcsMNKcTdcKEMrLow506wSETcXc7L/LEnneWQyJJeV4vhPqR7KJfsBbeqgz3yIfsCn1GZVWFlfegzYCN52YTl0Y0uRD2Z+TnzQu+Bf4DzaWXLge1rz31xkhyeNNspub4h024+XqBjcMm6M9mlMzmmK8t2DIwPy/BlQbFBUyhrxziuR/5/2NEDPyHltvWkRb4AUIa25WJqkV0gTBegbMadZ9DyOo6Ea7aoVFBae2WGR08F1kzABsWrd1S7UJmWxW35iyMEtoAIayXphIK98qO5aCutwZ+3iOQazxbAs=

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

@ -0,0 +1,111 @@
plugins {
id "org.sonarqube" version "2.1-rc1"
id "org.ajoberstar.github-pages" version "1.6.0-rc.1"
id "org.xbib.gradle.plugin.jbake" version "1.1.0"
}
println "Host: " + java.net.InetAddress.getLocalHost()
println "Gradle: " + gradle.gradleVersion + " JVM: " + org.gradle.internal.jvm.Jvm.current() + " Groovy: " + GroovySystem.getVersion()
println "Build: group: '${project.group}', name: '${project.name}', version: '${project.version}'"
allprojects {
apply plugin: 'java'
apply plugin: 'maven'
apply plugin: 'signing'
apply plugin: 'findbugs'
apply plugin: 'pmd'
apply plugin: 'checkstyle'
apply plugin: "jacoco"
repositories {
mavenLocal()
mavenCentral()
}
configurations {
wagon
provided
testCompile.extendsFrom(provided)
}
dependencies {
testCompile 'junit:junit:4.12'
wagon 'org.apache.maven.wagon:wagon-ssh-external:2.10'
}
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'
tasks.withType(JavaCompile) {
options.compilerArgs << "-Xlint:all" << "-profile" << "compact2"
}
test {
classpath += configurations.provided
testLogging {
showStandardStreams = false
exceptionFormat = 'full'
}
}
tasks.withType(FindBugs) {
ignoreFailures = true
reports {
xml.enabled = true
html.enabled = false
}
}
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
xml.destination "${buildDir}/reports/jacoco-xml"
html.destination "${buildDir}/reports/jacoco-html"
}
}
sonarqube {
properties {
property "sonar.projectName", "xbib content"
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/"
}
}
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"
}

@ -0,0 +1,3 @@
dependencies {
compile "com.fasterxml.jackson.core:jackson-core:2.8.3"
}

@ -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,junit,net,org,java,javax"/>
<!-- This ensures that static imports go first. -->
<property name="option" value="top"/>
<property name="tokens" value="STATIC_IMPORT, 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>

@ -0,0 +1,246 @@
package org.xbib.content;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
/**
*
*/
public abstract class AbstractXContentGenerator implements XContentGenerator {
protected XContentGenerator generator;
public AbstractXContentGenerator setGenerator(XContentGenerator generator) {
this.generator = generator;
return this;
}
@Override
public void writeStartArray() throws IOException {
generator.writeStartArray();
}
@Override
public void writeEndArray() throws IOException {
generator.writeEndArray();
}
@Override
public void writeStartObject() throws IOException {
generator.writeStartObject();
}
@Override
public void writeEndObject() throws IOException {
generator.writeEndObject();
}
@Override
public void writeFieldName(String name) throws IOException {
generator.writeFieldName(name);
}
@Override
public void writeFieldName(XContentString name) throws IOException {
generator.writeFieldName(name);
}
@Override
public void writeString(String text) throws IOException {
generator.writeString(text);
}
@Override
public void writeString(char[] text, int offset, int len) throws IOException {
generator.writeString(text, offset, len);
}
@Override
public void writeUTF8String(byte[] text, int offset, int length) throws IOException {
generator.writeUTF8String(text, offset, length);
}
@Override
public void writeBinary(byte[] data, int offset, int len) throws IOException {
generator.writeBinary(data, offset, len);
}
@Override
public void writeBinary(byte[] data) throws IOException {
generator.writeBinary(data);
}
@Override
public void writeNumber(int v) throws IOException {
generator.writeNumber(v);
}
@Override
public void writeNumber(long v) throws IOException {
generator.writeNumber(v);
}
@Override
public void writeNumber(double d) throws IOException {
generator.writeNumber(d);
}
@Override
public void writeNumber(float f) throws IOException {
generator.writeNumber(f);
}
@Override
public void writeNumber(BigInteger bi) throws IOException {
generator.writeNumber(bi);
}
@Override
public void writeNumber(BigDecimal bd) throws IOException {
generator.writeNumber(bd);
}
@Override
public void writeBoolean(boolean b) throws IOException {
generator.writeBoolean(b);
}
@Override
public void writeNull() throws IOException {
generator.writeNull();
}
@Override
public void writeStringField(String fieldName, String value) throws IOException {
generator.writeStringField(fieldName, value);
}
@Override
public void writeStringField(XContentString fieldName, String value) throws IOException {
generator.writeFieldName(fieldName);
}
@Override
public void writeBooleanField(String fieldName, boolean value) throws IOException {
generator.writeBooleanField(fieldName, value);
}
@Override
public void writeBooleanField(XContentString fieldName, boolean value) throws IOException {
generator.writeFieldName(fieldName);
generator.writeBoolean(value);
}
@Override
public void writeNullField(String fieldName) throws IOException {
generator.writeNullField(fieldName);
}
@Override
public void writeNumberField(String fieldName, int value) throws IOException {
generator.writeNumberField(fieldName, value);
}
@Override
public void writeNumberField(XContentString fieldName, int value) throws IOException {
generator.writeFieldName(fieldName);
generator.writeNumber(value);
}
@Override
public void writeNumberField(String fieldName, long value) throws IOException {
generator.writeNumberField(fieldName, value);
}
@Override
public void writeNumberField(XContentString fieldName, long value) throws IOException {
generator.writeFieldName(fieldName);
generator.writeNumber(value);
}
@Override
public void writeNumberField(String fieldName, double value) throws IOException {
generator.writeNumberField(fieldName, value);
}
@Override
public void writeNumberField(XContentString fieldName, double value) throws IOException {
generator.writeFieldName(fieldName);
generator.writeNumber(value);
}
@Override
public void writeNumberField(String fieldName, float value) throws IOException {
generator.writeNumberField(fieldName, value);
}
@Override
public void writeNumberField(XContentString fieldName, float value) throws IOException {
generator.writeFieldName(fieldName);
generator.writeNumber(value);
}
@Override
public void writeNumberField(String fieldName, BigInteger value) throws IOException {
generator.writeFieldName(fieldName);
generator.writeNumber(value);
}
@Override
public void writeNumberField(XContentString fieldName, BigInteger value) throws IOException {
generator.writeFieldName(fieldName);
generator.writeNumber(value);
}
@Override
public void writeNumberField(String fieldName, BigDecimal value) throws IOException {
generator.writeNumberField(fieldName, value);
}
@Override
public void writeNumberField(XContentString fieldName, BigDecimal value) throws IOException {
generator.writeFieldName(fieldName);
generator.writeNumber(value);
}
@Override
public void writeBinaryField(String fieldName, byte[] data) throws IOException {
generator.writeBinaryField(fieldName, data);
}
@Override
public void writeBinaryField(XContentString fieldName, byte[] data) throws IOException {
generator.writeFieldName(fieldName);
generator.writeBinary(data);
}
@Override
public void writeArrayFieldStart(String fieldName) throws IOException {
generator.writeArrayFieldStart(fieldName);
}
@Override
public void writeArrayFieldStart(XContentString fieldName) throws IOException {
generator.writeFieldName(fieldName);
generator.writeStartArray();
}
@Override
public void writeObjectFieldStart(String fieldName) throws IOException {
generator.writeObjectFieldStart(fieldName);
}
@Override
public void writeObjectFieldStart(XContentString fieldName) throws IOException {
generator.writeFieldName(fieldName);
generator.writeStartObject();
}
@Override
public void copy(XContentBuilder builder, OutputStream outputStream) throws IOException {
flush();
builder.bytes().streamOutput(outputStream);
}
}

@ -0,0 +1,265 @@
package org.xbib.content;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
*
*/
public abstract class AbstractXContentParser implements XContentParser {
private static final MapFactory SIMPLE_MAP_FACTORY = HashMap::new;
private static final MapFactory ORDERED_MAP_FACTORY = LinkedHashMap::new;
private boolean losslessDecimals;
private boolean base16Checks;
private static Map<String, Object> readMap(XContentParser parser) throws IOException {
return readMap(parser, SIMPLE_MAP_FACTORY);
}
private static Map<String, Object> readOrderedMap(XContentParser parser) throws IOException {
return readMap(parser, ORDERED_MAP_FACTORY);
}
private static Map<String, Object> readMap(XContentParser parser, MapFactory mapFactory) throws IOException {
Map<String, Object> map = mapFactory.newMap();
XContentParser.Token t = parser.currentToken();
if (t == null) {
t = parser.nextToken();
}
if (t == XContentParser.Token.START_OBJECT) {
t = parser.nextToken();
}
for (; t == XContentParser.Token.FIELD_NAME; t = parser.nextToken()) {
String fieldName = parser.currentName();
t = parser.nextToken();
Object value = readValue(parser, mapFactory, t);
map.put(fieldName, value);
}
return map;
}
private static List<Object> readList(XContentParser parser, MapFactory mapFactory) throws IOException {
ArrayList<Object> list = new ArrayList<>();
Token t;
while ((t = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
list.add(readValue(parser, mapFactory, t));
}
return list;
}
private static Object readValue(XContentParser parser, MapFactory mapFactory, XContentParser.Token t) throws IOException {
if (t == XContentParser.Token.VALUE_NULL) {
return null;
} else if (t == XContentParser.Token.VALUE_STRING) {
if (parser.isBase16Checks()) {
return XContentHelper.parseBase16(parser.text());
}
return parser.text();
} else if (t == XContentParser.Token.VALUE_NUMBER) {
XContentParser.NumberType numberType = parser.numberType();
if (numberType == XContentParser.NumberType.INT) {
return parser.isLosslessDecimals() ? parser.bigIntegerValue() : parser.intValue();
} else if (numberType == XContentParser.NumberType.LONG) {
return parser.isLosslessDecimals() ? parser.bigIntegerValue() : parser.longValue();
} else if (numberType == XContentParser.NumberType.FLOAT) {
return parser.isLosslessDecimals() ? parser.bigDecimalValue() : parser.floatValue();
} else if (numberType == XContentParser.NumberType.DOUBLE) {
return parser.isLosslessDecimals() ? parser.bigDecimalValue() : parser.doubleValue();
} else if (numberType == NumberType.BIG_INTEGER) {
return parser.bigIntegerValue();
} else if (numberType == NumberType.BIG_DECIMAL) {
return parser.bigDecimalValue();
}
} else if (t == XContentParser.Token.VALUE_BOOLEAN) {
return parser.booleanValue();
} else if (t == XContentParser.Token.START_OBJECT) {
return readMap(parser, mapFactory);
} else if (t == XContentParser.Token.START_ARRAY) {
return readList(parser, mapFactory);
} else if (t == XContentParser.Token.VALUE_EMBEDDED_OBJECT) {
return parser.binaryValue();
}
return null;
}
@Override
public boolean isBooleanValue() throws IOException {
switch (currentToken()) {
case VALUE_BOOLEAN:
return true;
case VALUE_NUMBER:
NumberType numberType = numberType();
return numberType == NumberType.LONG || numberType == NumberType.INT;
case VALUE_STRING:
return isBoolean(textCharacters(), textOffset(), textLength());
default:
return false;
}
}
@Override
public boolean booleanValue() throws IOException {
Token token = currentToken();
if (token == Token.VALUE_NUMBER) {
return intValue() != 0;
} else if (token == Token.VALUE_STRING) {
String s = new String(textCharacters(), textOffset(), textLength());
return Boolean.parseBoolean(s);
}
return doBooleanValue();
}
protected abstract boolean doBooleanValue() throws IOException;
@Override
public short shortValue() throws IOException {
Token token = currentToken();
if (token == Token.VALUE_STRING) {
return Short.parseShort(text());
}
return doShortValue();
}
protected abstract short doShortValue() throws IOException;
@Override
public int intValue() throws IOException {
Token token = currentToken();
if (token == Token.VALUE_STRING) {
return Integer.parseInt(text());
}
return doIntValue();
}
protected abstract int doIntValue() throws IOException;
@Override
public long longValue() throws IOException {
Token token = currentToken();
if (token == Token.VALUE_STRING) {
return Long.parseLong(text());
}
return doLongValue();
}
protected abstract long doLongValue() throws IOException;
@Override
public float floatValue() throws IOException {
Token token = currentToken();
if (token == Token.VALUE_STRING) {
return Float.parseFloat(text());
}
return doFloatValue();
}
protected abstract float doFloatValue() throws IOException;
@Override
public double doubleValue() throws IOException {
Token token = currentToken();
if (token == Token.VALUE_STRING) {
return Double.parseDouble(text());
}
return doDoubleValue();
}
protected abstract double doDoubleValue() throws IOException;
@Override
public XContentParser losslessDecimals(boolean losslessDecimals) {
this.losslessDecimals = losslessDecimals;
return this;
}
@Override
public boolean isLosslessDecimals() {
return losslessDecimals;
}
@Override
public XContentParser enableBase16Checks(boolean base16Checks) {
this.base16Checks = base16Checks;
return this;
}
@Override
public boolean isBase16Checks() {
return base16Checks;
}
@Override
public String textOrNull() throws IOException {
if (currentToken() == Token.VALUE_NULL) {
return null;
}
return text();
}
@Override
public Map<String, Object> map() throws IOException {
return readMap(this);
}
@Override
public Map<String, Object> mapOrdered() throws IOException {
return readOrderedMap(this);
}
@Override
public Map<String, Object> mapAndClose() throws IOException {
try {
return map();
} finally {
close();
}
}
@Override
public Map<String, Object> mapOrderedAndClose() throws IOException {
try {
return mapOrdered();
} finally {
close();
}
}
@FunctionalInterface
interface MapFactory {
Map<String, Object> newMap();
}
/**
* Returns true if the a sequence of chars is one of "true","false","on","off","yes","no","0","1".
*
* @param text sequence to check
* @param offset offset to start
* @param length length to check
* @return true if it is a boolean
*/
private static boolean isBoolean(char[] text, int offset, int length) {
if (text == null || length == 0) {
return false;
}
if (length == 1) {
return text[offset] == '0' || text[offset] == '1';
}
if (length == 2) {
return (text[offset] == 'n' && text[offset + 1] == 'o') || (text[offset] == 'o' && text[offset + 1] == 'n');
}
if (length == 3) {
return (text[offset] == 'o' && text[offset + 1] == 'f' && text[offset + 2] == 'f') ||
(text[offset] == 'y' && text[offset + 1] == 'e' && text[offset + 2] == 's');
}
if (length == 4) {
return text[offset] == 't' && text[offset + 1] == 'r' && text[offset + 2] == 'u' && text[offset + 3] == 'e';
}
return length == 5 && (text[offset] == 'f' && text[offset + 1] == 'a' && text[offset + 2] == 'l'
&& text[offset + 3] == 's' && text[offset + 4] == 'e');
}
}

@ -0,0 +1,36 @@
package org.xbib.content;
import java.io.IOException;
/**
* An interface allowing to transfer an object to content using an {@link XContentBuilder}.
*/
@FunctionalInterface
public interface ToXContent {
XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException;
/**
*
*/
interface Params {
String param(String key);
String param(String key, String defaultValue);
}
Params EMPTY_PARAMS = new Params() {
@Override
public String param(String key) {
return null;
}
@Override
public String param(String key, String defaultValue) {
return defaultValue;
}
};
}

@ -0,0 +1,98 @@
package org.xbib.content;
import org.xbib.content.io.BytesReference;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
/**
* A generic abstraction on top of handling content, inspired by JSON and pull parsing.
*/
public interface XContent {
String name();
/**
* Creates a new generator using the provided output stream.
*
* @param outputStream output stream
* @return content generator
* @throws IOException if creation fails
*/
XContentGenerator createGenerator(OutputStream outputStream) throws IOException;
/**
* Creates a new generator using the provided writer.
*
* @param writer writer
* @return content generator
* @throws IOException if creation fails
*/
XContentGenerator createGenerator(Writer writer) throws IOException;
/**
* Creates a parser over the provided input stream.
*
* @param inputStream input stream
* @return content parser
* @throws IOException if creation fails
*/
XContentParser createParser(InputStream inputStream) throws IOException;
/**
* Creates a parser over the provided reader.
*
* @param reader reader
* @return content parser
* @throws IOException if creation fails
*/
XContentParser createParser(Reader reader) throws IOException;
/**
* Creates a parser over the provided string content.
*
* @param content string
* @return content parser
* @throws IOException if creation fails
*/
XContentParser createParser(String content) throws IOException;
/**
* Creates a parser over the provided bytes.
*
* @param bytes bytes
* @return content parser
* @throws IOException if creation fails
*/
XContentParser createParser(byte[] bytes) throws IOException;
/**
* Creates a parser over the provided bytes.
*
* @param bytes bytes
* @param offset offset
* @param length length
* @return content parser
* @throws IOException if creation fails
*/
XContentParser createParser(byte[] bytes, int offset, int length) throws IOException;
/**
* Creates a parser over the provided bytes.
*
* @param bytes bytes
* @return content parser
* @throws IOException if creation fails
*/
XContentParser createParser(BytesReference bytes) throws IOException;
/**
* Returns true if content can be parsed/generated.
* @param bytes bytes
* @return true if content can be parsed/generated.
*/
boolean isXContent(BytesReference bytes);
}

@ -0,0 +1,924 @@
package org.xbib.content;
import org.xbib.content.io.BytesReference;
import org.xbib.content.io.BytesStreamOutput;
import org.xbib.content.util.geo.GeoPoint;
import java.io.Closeable;
import java.io.Flushable;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
*/
public final class XContentBuilder implements ToXContent, Flushable, Closeable {
private static final Logger logger = Logger.getLogger(XContentBuilder.class.getName());
private final OutputStream outputStream;
private final XContentGenerator generator;
/**
* Constructs a new builder using the provided xcontent and an OutputStream. Make sure
* to call {@link #close()} when the builder is done with.
* @param xContent content
* @param outputStream output stream
* @throws IOException if construction fails
*/
public XContentBuilder(XContent xContent, OutputStream outputStream) throws IOException {
this.outputStream = outputStream;
this.generator = xContent.createGenerator(outputStream);
}
/**
* Constructs a new builder using a fresh {@link org.xbib.content.io.BytesStreamOutput}.
* @param xContent the content
* @return content builder
* @throws IOException exception
*/
public static XContentBuilder builder(XContent xContent) throws IOException {
return new XContentBuilder(xContent, new BytesStreamOutput());
}
/**
* Constructs a new content builder.
* @param xContent the content
* @param out out
* @return content builder
* @throws IOException if build fails
*/
public static XContentBuilder builder(XContent xContent, OutputStream out) throws IOException {
return new XContentBuilder(xContent, out);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
return builder.copy(this);
}
public XContent content() {
return generator.content();
}
public XContentGenerator generator() {
return generator;
}
public XContentBuilder prettyPrint() {
generator.usePrettyPrint();
return this;
}
public XContentBuilder field(String name, ToXContent xContent) throws IOException {
field(name);
xContent.toXContent(this, ToXContent.EMPTY_PARAMS);
return this;
}
public XContentBuilder field(String name, ToXContent xContent, ToXContent.Params params) throws IOException {
field(name);
xContent.toXContent(this, params);
return this;
}
public XContentBuilder startObject(String name) throws IOException {
field(name);
startObject();
return this;
}
public XContentBuilder startObject(XContentBuilderString name) throws IOException {
field(name);
startObject();
return this;
}
public XContentBuilder startObject() throws IOException {
generator.writeStartObject();
return this;
}
public XContentBuilder endObject() throws IOException {
generator.writeEndObject();
return this;
}
public XContentBuilder array(String name, Collection<?> values) throws IOException {
startArray(name);
for (Object value : values) {
value(value);
}
endArray();
return this;
}
public XContentBuilder array(String name, String... values) throws IOException {
startArray(name);
for (String value : values) {
value(value);
}
endArray();
return this;
}
public XContentBuilder array(String name, Object... values) throws IOException {
startArray(name);
for (Object value : values) {
value(value);
}
endArray();
return this;
}
public XContentBuilder startArray(String name) throws IOException {
field(name);
startArray();
return this;
}
public XContentBuilder startArray(XContentBuilderString name) throws IOException {
field(name);
startArray();
return this;
}
public XContentBuilder startArray() throws IOException {
generator.writeStartArray();
return this;
}
public XContentBuilder endArray() throws IOException {
generator.writeEndArray();
return this;
}
public XContentBuilder field(XContentBuilderString name) throws IOException {
generator.writeFieldName(name.string());
return this;
}
public XContentBuilder field(String name) throws IOException {
Objects.requireNonNull(name);
generator.writeFieldName(name);
return this;
}
public XContentBuilder field(String name, char[] value, int offset, int length) throws IOException {
field(name);
if (value == null) {
generator.writeNull();
} else {
generator.writeString(value, offset, length);
}
return this;
}
public XContentBuilder field(String name, String value) throws IOException {
field(name);
if (value == null) {
generator.writeNull();
} else {
generator.writeString(value);
}
return this;
}
public XContentBuilder field(XContentBuilderString name, String value) throws IOException {
field(name);
if (value == null) {
generator.writeNull();
} else {
generator.writeString(value);
}
return this;
}
public XContentBuilder fieldIfNotNull(String name, String value) throws IOException {
if (value != null) {
field(name);
generator.writeString(value);
}
return this;
}
public XContentBuilder field(String name, Integer value) throws IOException {
field(name);
if (value == null) {
generator.writeNull();
} else {
generator.writeNumber(value);
}
return this;
}
public XContentBuilder fieldIfNotNull(String name, Integer value) throws IOException {
if (value != null) {
field(name);
generator.writeNumber(value);
}
return this;
}
public XContentBuilder field(XContentBuilderString name, Integer value) throws IOException {
field(name);
if (value == null) {
generator.writeNull();
} else {
generator.writeNumber(value);
}
return this;
}
public XContentBuilder field(String name, int value) throws IOException {
field(name);
generator.writeNumber(value);
return this;
}
public XContentBuilder field(XContentBuilderString name, int value) throws IOException {
field(name);
generator.writeNumber(value);
return this;
}
public XContentBuilder field(String name, Long value) throws IOException {
field(name);
if (value == null) {
generator.writeNull();
} else {
generator.writeNumber(value);
}
return this;
}
public XContentBuilder field(XContentBuilderString name, Long value) throws IOException {
field(name);
if (value == null) {
generator.writeNull();
} else {
generator.writeNumber(value);
}
return this;
}
public XContentBuilder fieldIfNotNull(String name, Long value) throws IOException {
if (value != null) {
field(name);
generator.writeNumber(value);
}
return this;
}
public XContentBuilder field(String name, long value) throws IOException {
field(name);
generator.writeNumber(value);
return this;
}
public XContentBuilder field(XContentBuilderString name, long value) throws IOException {
field(name);
generator.writeNumber(value);
return this;
}
public XContentBuilder field(String name, Float value) throws IOException {
field(name);
if (value == null) {
generator.writeNull();
} else {
generator.writeNumber(value);
}
return this;
}
public XContentBuilder field(XContentBuilderString name, Float value) throws IOException {
field(name);
if (value == null) {
generator.writeNull();
} else {
generator.writeNumber(value);
}
return this;
}
public XContentBuilder fieldIfNotNull(String name, Float value) throws IOException {
if (value != null) {
field(name);
generator.writeNumber(value);
}
return this;
}
public XContentBuilder field(String name, float value) throws IOException {
field(name);
generator.writeNumber(value);
return this;
}
public XContentBuilder field(XContentBuilderString name, float value) throws IOException {
field(name);
generator.writeNumber(value);
return this;
}
public XContentBuilder field(String name, Double value) throws IOException {
field(name);
if (value == null) {
generator.writeNull();
} else {
generator.writeNumber(value);
}
return this;
}
public XContentBuilder fieldIfNotNull(String name, Double value) throws IOException {
if (value != null) {
field(name);
generator.writeNumber(value);
}
return this;
}
public XContentBuilder field(XContentBuilderString name, Double value) throws IOException {
field(name);
if (value == null) {
generator.writeNull();
} else {
generator.writeNumber(value);
}
return this;
}
public XContentBuilder field(String name, double value) throws IOException {
field(name);
generator.writeNumber(value);
return this;
}
public XContentBuilder field(XContentBuilderString name, double value) throws IOException {
field(name);
generator.writeNumber(value);
return this;
}
public XContentBuilder field(String name, BigInteger value) throws IOException {
field(name);
generator.writeNumber(value);
return this;
}
public XContentBuilder field(XContentBuilderString name, BigInteger value) throws IOException {
field(name);
generator.writeNumber(value);
return this;
}
public XContentBuilder field(String name, BigDecimal value) throws IOException {
field(name);
generator.writeNumber(value);
return this;
}
public XContentBuilder field(XContentBuilderString name, BigDecimal value) throws IOException {
field(name);
generator.writeNumber(value);
return this;
}
public XContentBuilder field(String name, BytesReference value) throws IOException {
field(name);
byte[] b = value.toBytes();
generator.writeBinary(b, 0, b.length);
return this;
}
public XContentBuilder field(XContentBuilderString name, BytesReference value) throws IOException {
field(name);
byte[] b = value.toBytes();
generator.writeBinary(b, 0, b.length);
return this;
}
public XContentBuilder field(String name, byte[] value, int offset, int length) throws IOException {
field(name);
generator.writeBinary(value, offset, length);
return this;
}
public XContentBuilder field(String name, Map<String, Object> value) throws IOException {
field(name);
value(value);
return this;
}
public XContentBuilder field(XContentBuilderString name, Map<String, Object> value) throws IOException {
field(name);
value(value);
return this;
}
public XContentBuilder field(String name, Iterable<?> value) throws IOException {
startArray(name);
for (Object o : value) {
value(o);
}
endArray();
return this;
}
public XContentBuilder field(XContentBuilderString name, Iterable<?> value) throws IOException {
startArray(name);
for (Object o : value) {
value(o);
}
endArray();
return this;
}
public XContentBuilder field(String name, String... value) throws IOException {
startArray(name);
for (String o : value) {
value(o);
}
endArray();
return this;
}
public XContentBuilder field(XContentBuilderString name, String... value) throws IOException {
startArray(name);
for (String o : value) {
value(o);
}
endArray();
return this;
}
public XContentBuilder field(String name, Object... value) throws IOException {
startArray(name);
for (Object o : value) {
value(o);
}
endArray();
return this;
}
public XContentBuilder field(XContentBuilderString name, Object... value) throws IOException {
startArray(name);
for (Object o : value) {
value(o);
}
endArray();
return this;
}
public XContentBuilder field(String name, int... value) throws IOException {
startArray(name);
for (Object o : value) {
value(o);
}
endArray();
return this;
}
public XContentBuilder field(XContentBuilderString name, int offset, int length, int... value) throws IOException {
startArray(name);
for (int i = offset; i < length; i++) {
value(value[i]);
}
endArray();
return this;
}
public XContentBuilder field(XContentBuilderString name, int... value) throws IOException {
startArray(name);
for (Object o : value) {
value(o);
}
endArray();
return this;
}
public XContentBuilder field(String name, long... value) throws IOException {
startArray(name);
for (Object o : value) {
value(o);
}
endArray();
return this;
}
public XContentBuilder field(XContentBuilderString name, long... value) throws IOException {
startArray(name);
for (Object o : value) {
value(o);
}
endArray();
return this;
}
public XContentBuilder field(String name, float... value) throws IOException {
startArray(name);
for (Object o : value) {
value(o);
}
endArray();
return this;
}
public XContentBuilder field(XContentBuilderString name, float... value) throws IOException {
startArray(name);
for (Object o : value) {
value(o);
}
endArray();
return this;
}
public XContentBuilder field(String name, double... value) throws IOException {
startArray(name);
for (Object o : value) {
value(o);
}
endArray();
return this;
}
public XContentBuilder field(XContentBuilderString name, double... value) throws IOException {
startArray(name);
for (Object o : value) {
value(o);
}
endArray();
return this;
}
public XContentBuilder field(String name, Object value) throws IOException {
field(name);
writeValue(value);
return this;
}
public XContentBuilder field(XContentBuilderString name, Object value) throws IOException {
field(name);
writeValue(value);
return this;
}
public XContentBuilder fieldIfNotNull(String name, Object value) throws IOException {
if (value != null) {
return field(name, value);
}
return this;
}
public XContentBuilder value(Object value) throws IOException {
writeValue(value);
return this;
}
public XContentBuilder field(String name, boolean value) throws IOException {
field(name);
generator.writeBoolean(value);
return this;
}
public XContentBuilder field(String name, byte[] value) throws IOException {
field(name);
if (value == null) {
generator.writeNull();
} else {
generator.writeBinary(value);
}
return this;
}
public XContentBuilder nullField(String name) throws IOException {
generator.writeNullField(name);
return this;
}
public XContentBuilder nullValue() throws IOException {
generator.writeNull();
return this;
}
public XContentBuilder rawField(String fieldName, byte[] content) throws IOException {
generator.writeRawField(fieldName, content, outputStream);
return this;
}
public XContentBuilder rawField(String fieldName, byte[] content, int offset, int length) throws IOException {
generator.writeRawField(fieldName, content, offset, length, outputStream);
return this;
}
public XContentBuilder value(XContentBuilder builder) throws IOException {
generator.writeValue(builder);
return this;
}
public XContentBuilder copy(XContentBuilder builder) throws IOException {
generator.copy(builder, outputStream);
return this;
}
public XContentBuilder copy(List<XContentBuilder> builder) throws IOException {
for (int i = 0; i < builder.size(); i++) {
if (i > 0) {
outputStream.write(',');
}
generator.copy(builder.get(i), outputStream);
}
return this;
}
public XContentBuilder value(Boolean value) throws IOException {
if (value == null) {
return nullValue();
}
return value(value.booleanValue());
}
public XContentBuilder value(boolean value) throws IOException {
generator.writeBoolean(value);
return this;
}
public XContentBuilder value(Integer value) throws IOException {
if (value == null) {
return nullValue();
}
return value(value.intValue());
}
public XContentBuilder value(int value) throws IOException {
generator.writeNumber(value);
return this;
}
public XContentBuilder value(Long value) throws IOException {
if (value == null) {
return nullValue();
}
return value(value.longValue());
}
public XContentBuilder value(long value) throws IOException {
generator.writeNumber(value);
return this;
}
public XContentBuilder value(Float value) throws IOException {
if (value == null) {
return nullValue();
}
return value(value.floatValue());
}
public XContentBuilder value(float value) throws IOException {
generator.writeNumber(value);
return this;
}
public XContentBuilder value(Double value) throws IOException {
if (value == null) {
return nullValue();
}
return value(value.doubleValue());
}
public XContentBuilder value(double value) throws IOException {
generator.writeNumber(value);
return this;
}
public XContentBuilder value(BigInteger bi) throws IOException {
generator.writeNumber(bi);
return this;
}
public XContentBuilder value(BigDecimal bd) throws IOException {
generator.writeNumber(bd);
return this;
}
public XContentBuilder value(String value) throws IOException {
if (value == null) {
return nullValue();
}
generator.writeString(value);
return this;
}
public XContentBuilder value(byte[] value) throws IOException {
if (value == null) {
return nullValue();
}
generator.writeBinary(value);
return this;
}
public XContentBuilder value(byte[] value, int offset, int length) throws IOException {
if (value == null) {
return nullValue();
}
generator.writeBinary(value, offset, length);
return this;
}
public XContentBuilder value(BytesReference value) throws IOException {
if (value == null) {
return nullValue();
}
byte[] b = value.toBytes();
generator.writeBinary(b, 0, b.length);
return this;
}
public XContentBuilder map(Map<String, Object> map) throws IOException {
if (map == null) {
return nullValue();
}
writeMap(map);
return this;
}
public XContentBuilder value(Map<String, Object> map) throws IOException {
if (map == null) {
return nullValue();
}
writeMap(map);
return this;
}
public XContentBuilder value(Iterable<?> value) throws IOException {
if (value == null) {
return nullValue();
}
startArray();
for (Object o : value) {
value(o);
}
endArray();
return this;
}
public XContentBuilder copyCurrentStructure(XContentParser parser) throws IOException {
generator.copyCurrentStructure(parser);
return this;
}
@Override
public void flush() throws IOException {
generator.flush();
}
@Override
public void close() throws IOException {
generator.close();
}
public BytesReference bytes() {
try {
generator.close();
} catch (IOException e) {
logger.log(Level.FINE, e.getMessage(), e);
}
return ((BytesStreamOutput) outputStream).bytes();
}
/**
* Returns a string representation of the builder (only applicable for text based xcontent).
* Only applicable when the builder is constructed with {@link BytesStreamOutput}.
* @return string
*/
public String string() {
return bytes().toUtf8();
}
private void writeMap(Map<String, Object> map) throws IOException {
generator.writeStartObject();
for (Map.Entry<String, Object> entry : map.entrySet()) {
field(entry.getKey());
Object value = entry.getValue();
if (value == null) {
generator.writeNull();
} else {
writeValue(value);
}
}
generator.writeEndObject();
}
@SuppressWarnings("unchecked")
private void writeValue(Object value) throws IOException {
if (value == null) {
generator.writeNull();
return;
}
Class<?> type = value.getClass();
if (type == String.class) {
generator.writeString((String) value);
} else if (type == Integer.class) {
generator.writeNumber((Integer) value);
} else if (type == Long.class) {
generator.writeNumber((Long) value);
} else if (type == Float.class) {
generator.writeNumber((Float) value);
} else if (type == Double.class) {
generator.writeNumber((Double) value);
} else if (type == Short.class) {
generator.writeNumber((Short) value);
} else if (type == Boolean.class) {
generator.writeBoolean((Boolean) value);
} else if (type == GeoPoint.class) {
generator.writeStartObject();
generator.writeNumberField("lat", ((GeoPoint) value).lat());
generator.writeNumberField("lon", ((GeoPoint) value).lon());
generator.writeEndObject();
} else if (value instanceof Map) {
writeMap((Map) value);
} else if (value instanceof Iterable) {
generator.writeStartArray();
for (Object v : (Iterable) value) {
writeValue(v);
}
generator.writeEndArray();
} else if (value instanceof Object[]) {
generator.writeStartArray();
for (Object v : (Object[]) value) {
writeValue(v);
}
generator.writeEndArray();
} else if (type == byte[].class) {
generator.writeBinary((byte[]) value);
} else if (value instanceof Date) {
Date date = (Date) value;
Instant instant = Instant.ofEpochMilli(date.getTime());
ZonedDateTime zdt = ZonedDateTime.ofInstant(instant, ZoneId.systemDefault());
generator.writeString(zdt.format(DateTimeFormatter.ISO_INSTANT));
} else if (value instanceof Calendar) {
Calendar calendar = (Calendar) value;
Instant instant = Instant.ofEpochMilli(calendar.getTime().getTime());
ZonedDateTime zdt = ZonedDateTime.ofInstant(instant, ZoneId.systemDefault());
generator.writeString(zdt.format(DateTimeFormatter.ISO_INSTANT));
} else if (value instanceof BytesReference) {
BytesReference bytes = (BytesReference) value;
byte[] b = bytes.toBytes();
generator.writeBinary(b, 0, b.length);
} else if (value instanceof XContentBuilder) {
value((XContentBuilder) value);
} else if (value instanceof ToXContent) {
((ToXContent) value).toXContent(this, ToXContent.EMPTY_PARAMS);
} else if (value instanceof double[]) {
generator.writeStartArray();
for (double v : (double[]) value) {
generator.writeNumber(v);
}
generator.writeEndArray();
} else if (value instanceof long[]) {
generator.writeStartArray();
for (long v : (long[]) value) {
generator.writeNumber(v);
}
generator.writeEndArray();
} else if (value instanceof int[]) {
generator.writeStartArray();
for (int v : (int[]) value) {
generator.writeNumber(v);
}
generator.writeEndArray();
} else if (value instanceof float[]) {
generator.writeStartArray();
for (float v : (float[]) value) {
generator.writeNumber(v);
}
generator.writeEndArray();
} else if (value instanceof short[]) {
generator.writeStartArray();
for (float v : (short[]) value) {
generator.writeNumber(v);
}
generator.writeEndArray();
} else {
// if this is a "value" object, like enum, DistanceUnit, ..., just toString it
// yea, it can be misleading when toString a Java class, but really, jackson should be used in that case
generator.writeString(value.toString());
}
}
}

@ -0,0 +1,18 @@
package org.xbib.content;
/**
*
*/
public class XContentBuilderString {
private final XContentString string;
public XContentBuilderString(String value) {
string = new XContentString(value);
}
public XContentString string() {
return string;
}
}

@ -0,0 +1,121 @@
package org.xbib.content;
import org.xbib.content.io.BytesReference;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
/**
*
*/
public interface XContentGenerator {
XContent content();
void usePrettyPrint();
void writeStartArray() throws IOException;
void writeEndArray() throws IOException;
void writeStartObject() throws IOException;
void writeEndObject() throws IOException;
void writeFieldName(String name) throws IOException;
void writeFieldName(XContentString name) throws IOException;
void writeString(String text) throws IOException;
void writeString(char[] text, int offset, int len) throws IOException;
void writeUTF8String(byte[] text, int offset, int length) throws IOException;
void writeBinary(byte[] data, int offset, int len) throws IOException;
void writeBinary(byte[] data) throws IOException;
void writeNumber(int v) throws IOException;
void writeNumber(long v) throws IOException;
void writeNumber(double d) throws IOException;
void writeNumber(float f) throws IOException;
void writeNumber(BigDecimal bd) throws IOException;
void writeNumber(BigInteger bi) throws IOException;
void writeBoolean(boolean state) throws IOException;
void writeNull() throws IOException;
void writeStringField(String fieldName, String value) throws IOException;
void writeStringField(XContentString fieldName, String value) throws IOException;
void writeBooleanField(String fieldName, boolean value) throws IOException;
void writeBooleanField(XContentString fieldName, boolean value) throws IOException;
void writeNullField(String fieldName) throws IOException;
void writeNumberField(String fieldName, int value) throws IOException;
void writeNumberField(XContentString fieldName, int value) throws IOException;
void writeNumberField(String fieldName, long value) throws IOException;
void writeNumberField(XContentString fieldName, long value) throws IOException;
void writeNumberField(String fieldName, double value) throws IOException;
void writeNumberField(XContentString fieldName, double value) throws IOException;
void writeNumberField(String fieldName, float value) throws IOException;
void writeNumberField(XContentString fieldName, float value) throws IOException;
void writeNumberField(String fieldName, BigInteger value) throws IOException;
void writeNumberField(XContentString fieldName, BigInteger value) throws IOException;
void writeNumberField(String fieldName, BigDecimal value) throws IOException;
void writeNumberField(XContentString fieldName, BigDecimal value) throws IOException;
void writeBinaryField(String fieldName, byte[] data) throws IOException;
void writeBinaryField(XContentString fieldName, byte[] data) throws IOException;
void writeArrayFieldStart(String fieldName) throws IOException;
void writeArrayFieldStart(XContentString fieldName) throws IOException;
void writeObjectFieldStart(String fieldName) throws IOException;
void writeObjectFieldStart(XContentString fieldName) throws IOException;
void writeRawField(String fieldName, byte[] content, OutputStream outputStream)
throws IOException;
void writeRawField(String fieldName, byte[] content, int offset, int length, OutputStream outputStream)
throws IOException;
void writeRawField(String fieldName, BytesReference content, OutputStream outputStream)
throws IOException;
void writeValue(XContentBuilder builder) throws IOException;
void copy(XContentBuilder builder, OutputStream outputStream) throws IOException;
void copyCurrentStructure(XContentParser parser) throws IOException;
void flush() throws IOException;
void close() throws IOException;
}

@ -0,0 +1,252 @@
package org.xbib.content;
import org.xbib.content.io.BytesReference;
import org.xbib.content.json.JsonXContent;
import java.io.IOException;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
*/
public class XContentHelper {
private static final Logger logger = Logger.getLogger(XContentHelper.class.getName());
private static final String UNKNOWN_FORMAT = "unknown format";
private XContentHelper() {
}
public static XContentParser createParser(BytesReference bytes) throws IOException {
XContent content = XContentService.xContent(bytes);
if (content == null) {
throw new IOException(UNKNOWN_FORMAT);
}
return content.createParser(bytes.streamInput());
}
public static XContentParser createParser(byte[] data, int offset, int length) throws IOException {
return XContentService.xContent(data, offset, length).createParser(data, offset, length);
}
public static Map<String, Object> convertFromJsonToMap(Reader reader) {
try {
return JsonXContent.jsonContent().createParser(reader).mapOrderedAndClose();
} catch (IOException e) {
throw new IllegalArgumentException("Failed to parse content to map", e);
}
}
public static Map<String, Object> convertToMap(String data) {
try {
XContent content = XContentService.xContent(data);
return content.createParser(data).mapOrderedAndClose();
} catch (IOException e) {
throw new IllegalArgumentException("failed to parse content to map", e);
}
}
public static Map<String, Object> convertToMap(BytesReference bytes, boolean ordered) {
XContent content = XContentService.xContent(bytes);
if (content == null) {
throw new IllegalArgumentException(UNKNOWN_FORMAT);
}
try {
XContentParser parser = content.createParser(bytes.streamInput());
if (ordered) {
return parser.mapOrderedAndClose();
} else {
return parser.mapAndClose();
}
} catch (IOException e) {
throw new IllegalArgumentException("failed to parse content to map", e);
}
}
public static Map<String, Object> convertToMap(byte[] data, boolean ordered) throws IOException {
return convertToMap(data, 0, data.length, ordered);
}
public static Map<String, Object> convertToMap(byte[] data, int offset, int length, boolean ordered) throws IOException {
XContent content = XContentService.xContent(data, offset, length);
XContentParser parser = content.createParser(data, offset, length);
if (ordered) {
return parser.mapOrderedAndClose();
} else {
return parser.mapAndClose();
}
}
public static String convertToJson(BytesReference bytes, boolean reformatJson, boolean prettyPrint) throws IOException {
XContent xContent = XContentService.xContent(bytes);
if (xContent == null) {
throw new IOException(UNKNOWN_FORMAT);
}
if (xContent == JsonXContent.jsonContent() && !reformatJson) {
return bytes.toUtf8();
}
try (XContentParser parser = xContent.createParser(bytes.streamInput());
XContentBuilder builder = XContentBuilder.builder(JsonXContent.jsonContent())) {
parser.nextToken();
if (prettyPrint) {
builder.prettyPrint();
}
builder.copyCurrentStructure(parser);
return builder.string();
}
}
public static String convertToJson(byte[] data, int offset, int length, boolean reformatJson, boolean prettyPrint)
throws IOException {
XContent xContent = XContentService.xContent(data, offset, length);
if (xContent == JsonXContent.jsonContent() && !reformatJson) {
return new String(data, offset, length, StandardCharsets.UTF_8);
}
try (XContentParser parser = xContent.createParser(data, offset, length);
XContentBuilder builder = XContentBuilder.builder(JsonXContent.jsonContent())) {
parser.nextToken();
if (prettyPrint) {
builder.prettyPrint();
}
builder.copyCurrentStructure(parser);
return builder.string();
}
}
public static void copyCurrentStructure(XContentGenerator generator, XContentParser parser) throws IOException {
XContentParser.Token t = parser.currentToken();
if (t == XContentParser.Token.FIELD_NAME) {
generator.writeFieldName(parser.currentName());
t = parser.nextToken();
}
switch (t) {
case START_ARRAY:
generator.writeStartArray();
while (parser.nextToken() != XContentParser.Token.END_ARRAY) {
copyCurrentStructure(generator, parser);
}
generator.writeEndArray();
break;
case START_OBJECT:
generator.writeStartObject();
while (parser.nextToken() != XContentParser.Token.END_OBJECT) {
copyCurrentStructure(generator, parser);
}
generator.writeEndObject();
break;
default:
copyCurrentEvent(generator, parser);
}
}
private static void copyCurrentEvent(XContentGenerator generator, XContentParser parser) throws IOException {
switch (parser.currentToken()) {
case START_OBJECT:
generator.writeStartObject();
break;
case END_OBJECT:
generator.writeEndObject();
break;
case START_ARRAY:
generator.writeStartArray();
break;
case END_ARRAY:
generator.writeEndArray();
break;
case FIELD_NAME:
generator.writeFieldName(parser.currentName());
break;
case VALUE_STRING:
if (parser.hasTextCharacters()) {
generator.writeString(parser.textCharacters(), parser.textOffset(), parser.textLength());
} else {
if (parser.isBase16Checks()) {
try {
generator.writeBinary(parseBase16(parser.text()));
} catch (IllegalArgumentException e) {
logger.log(Level.FINE, e.getMessage(), e);
generator.writeString(parser.text());
}
} else {
generator.writeString(parser.text());
}
}
break;
case VALUE_NUMBER:
switch (parser.numberType()) {
case INT:
generator.writeNumber(parser.intValue());
break;
case LONG:
generator.writeNumber(parser.longValue());
break;
case FLOAT:
generator.writeNumber(parser.floatValue());
break;
case DOUBLE:
if (parser.isLosslessDecimals()) {
generator.writeNumber(parser.bigDecimalValue());
} else {
generator.writeNumber(parser.doubleValue());
}
break;
case BIG_INTEGER:
generator.writeNumber(parser.bigIntegerValue());
break;
case BIG_DECIMAL:
generator.writeNumber(parser.bigDecimalValue());
break;
default:
break;
}
break;
case VALUE_BOOLEAN:
generator.writeBoolean(parser.booleanValue());
break;
case VALUE_NULL:
generator.writeNull();
break;
case VALUE_EMBEDDED_OBJECT:
generator.writeBinary(parser.binaryValue());
break;
default:
break;
}
}
static byte[] parseBase16(String s) {
final int len = s.length();
if (len % 2 != 0) {
throw new IllegalArgumentException("hex string needs to be of even length: " + s);
}
byte[] out = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
int h = hexToBin(s.charAt(i));
int l = hexToBin(s.charAt(i + 1));
if (h == -1 || l == -1) {
throw new IllegalArgumentException("contains illegal character for hex string: " + s);
}
out[i / 2] = (byte) (h * 16 + l);
}
return out;
}
private static int hexToBin(char ch) {
if ('0' <= ch && ch <= '9') {
return ch - '0';
}
if ('A' <= ch && ch <= 'F') {
return ch - 'A' + 10;
}
if ('a' <= ch && ch <= 'f') {
return ch - 'a' + 10;
}
return -1;
}
}

@ -0,0 +1,167 @@
package org.xbib.content;
import java.io.Closeable;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Map;
/**
*
*/
public interface XContentParser extends Closeable {
XContent content();
Token nextToken() throws IOException;
void skipChildren() throws IOException;
Token currentToken();
String currentName() throws IOException;
Map<String, Object> map() throws IOException;
Map<String, Object> mapOrdered() throws IOException;
Map<String, Object> mapAndClose() throws IOException;
Map<String, Object> mapOrderedAndClose() throws IOException;
String text() throws IOException;
String textOrNull() throws IOException;
boolean hasTextCharacters();
char[] textCharacters() throws IOException;
int textLength() throws IOException;
int textOffset() throws IOException;
Number numberValue() throws IOException;
NumberType numberType() throws IOException;
/**
* Is the number type estimated or not (i.e. an int might actually be a long, its just low enough
* to be an int).
* @return true if number is estimated
*/
boolean estimatedNumberType();
short shortValue() throws IOException;
int intValue() throws IOException;
long longValue() throws IOException;
float floatValue() throws IOException;
double doubleValue() throws IOException;
XContentParser losslessDecimals(boolean b);
boolean isLosslessDecimals();
BigInteger bigIntegerValue() throws IOException;
BigDecimal bigDecimalValue() throws IOException;
boolean isBooleanValue() throws IOException;
boolean booleanValue() throws IOException;
byte[] binaryValue() throws IOException;
XContentParser enableBase16Checks(boolean b);
boolean isBase16Checks();
/**
*
*/
enum Token {
START_OBJECT {
@Override
public boolean isValue() {
return false;
}
},
END_OBJECT {
@Override
public boolean isValue() {
return false;
}
},
START_ARRAY {
@Override
public boolean isValue() {
return false;
}
},
END_ARRAY {
@Override
public boolean isValue() {
return false;
}
},
FIELD_NAME {
@Override
public boolean isValue() {
return false;
}
},
VALUE_STRING {
@Override
public boolean isValue() {
return true;
}
},
VALUE_NUMBER {
@Override
public boolean isValue() {
return true;
}
},
VALUE_BOOLEAN {
@Override
public boolean isValue() {
return true;
}
},
// usually a binary value
VALUE_EMBEDDED_OBJECT {
@Override
public boolean isValue() {
return true;
}
},
VALUE_NULL {
@Override
public boolean isValue() {
return false;
}
};
public abstract boolean isValue();
}
/**
*
*/
enum NumberType {
INT, LONG, FLOAT, DOUBLE, BIG_DECIMAL, BIG_INTEGER
}
}

@ -0,0 +1,63 @@
package org.xbib.content;
import org.xbib.content.io.BytesArray;
import org.xbib.content.io.BytesReference;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
*/
public class XContentService {
private static final Logger logger = Logger.getLogger(XContentService.class.getName());
private static final Map<String, XContent> xcontents = new HashMap<>();
private static final XContentService instance = new XContentService();
private XContentService() {
try {
ServiceLoader<XContent> loader = ServiceLoader.load(XContent.class);
for (XContent xContent : loader) {
if (!xcontents.containsKey(xContent.name())) {
xcontents.put(xContent.name(), xContent);
}
}
} catch (Exception e) {
logger.log(Level.SEVERE, e.getMessage(), e);
}
}
public static XContentService getInstance() {
return instance;
}
public static XContentBuilder builder(String name) throws IOException {
return xcontents.containsKey(name) ? XContentBuilder.builder(xcontents.get(name)) : null;
}
public static XContent xContent(byte[] data, int offset, int length) {
return xContent(new BytesArray(data, offset, length));
}
public static XContent xContent(String charSequence) {
return xContent(new BytesArray(charSequence.getBytes(StandardCharsets.UTF_8)));
}
public static XContent xContent(BytesReference bytes) {
for (XContent xcontent : xcontents.values()) {
if (xcontent.isXContent(bytes)) {
return xcontent;
}
}
return null;
}
}

@ -0,0 +1,15 @@
package org.xbib.content;
import com.fasterxml.jackson.core.io.SerializedString;
/**
*
*/
public class XContentString extends SerializedString {
private static final long serialVersionUID = -127711532459894341L;
public XContentString(String v) {
super(v);
}
}

@ -0,0 +1,101 @@
package org.xbib.content.io;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
/**
* A byte array, wrapped in a {@link BytesReference}.
*/
public class BytesArray implements BytesReference {
private static final String EMPTY_STRING = "";
private byte[] bytes;
private int offset;
private int length;
/**
* Create {@link BytesArray} from a byte array.
* @param bytes the byte array
*/
public BytesArray(byte[] bytes) {
this.bytes = bytes;
this.offset = 0;
this.length = bytes.length;
}
/**
* Create {@link BytesArray} from a part of a byte array.
* @param bytes the byte array
* @param offset the offset
* @param length the length
*/
public BytesArray(byte[] bytes, int offset, int length) {
this.bytes = bytes;
this.offset = offset;
this.length = length;
}
@Override
public byte get(int index) {
return bytes[offset + index];
}
@Override
public int length() {
return length;
}
@Override
public int indexOf(byte b, int offset, int len) {
if (offset < 0 || (offset + length) > this.length) {
throw new IllegalArgumentException();
}
for (int i = offset; i < offset + len; i++) {
if (bytes[i] == b) {
return i;
}
}
return -1;
}
@Override
public BytesReference slice(int from, int length) {
if (from < 0 || (from + length) > this.length) {
throw new IllegalArgumentException("can't slice a buffer with length [" + this.length +
"], with slice parameters from [" + from + "], length [" + length + "]");
}
return new BytesArray(bytes, offset + from, length);
}
@Override
public byte[] toBytes() {
if (offset == 0 && bytes.length == length) {
return bytes;
}
return Arrays.copyOfRange(bytes, offset, offset + length);
}
@Override
public String toUtf8() {
if (length == 0) {
return EMPTY_STRING;
}
return new String(bytes, offset, length, StandardCharsets.UTF_8);
}
@Override
public BytesStreamInput streamInput() {
return new BytesStreamInput(bytes, offset, length);
}
@Override
public void streamOutput(OutputStream os) throws IOException {
os.write(bytes, offset, length);
}
}

@ -0,0 +1,61 @@
package org.xbib.content.io;
import java.io.IOException;
import java.io.OutputStream;
/**
* A reference to bytes.
*/
public interface BytesReference {
/**
* Returns the byte at the specified index. Need to be between 0 and length.
*
* @param index index
* @return byte at specified index
*/
byte get(int index);
/**
* The length.
*
* @return length
*/
int length();
/**
* Find the index of a given byte, in the given area.
* @param b the byte
* @param offset offset
* @param len len
* @return -1 if not found, otherwise the position, counting from offset
*/
int indexOf(byte b, int offset, int len);
/**
* Slice the bytes from the <tt>from</tt> index up to <tt>length</tt>.
*
* @param from from
* @param length length
* @return bytes reference
*/
BytesReference slice(int from, int length);
/**
* Returns the bytes as a single byte array.
*
* @return bytes
*/
byte[] toBytes();
/**
* Converts to a string based on utf8.
*
* @return UTF-8 encoded string
*/
String toUtf8();
BytesStreamInput streamInput();
void streamOutput(OutputStream outputStream) throws IOException;
}

@ -0,0 +1,72 @@
package org.xbib.content.io;
import java.io.IOException;
import java.io.InputStream;
/**
*
*/
public class BytesStreamInput extends InputStream {
private byte[] buf;
private int pos;
private int count;
public BytesStreamInput(byte[] buf) {
this(buf, 0, buf.length);
}
public BytesStreamInput(byte[] buf, int offset, int length) {
this.buf = buf;
this.pos = offset;
this.count = Math.min(offset + length, buf.length);
}
@Override
public long skip(long n) throws IOException {
long res = n;
if (pos + res > count) {
res = (long) count - pos;
}
if (res < 0) {
return 0;
}
pos += res;
return res;
}
@Override
public int read() throws IOException {
return pos < count ? buf[pos++] & 0xff : -1;
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
}
if (pos >= count) {
return -1;
}
int l = len;
if (pos + l > count) {
l = count - pos;
}
if (l <= 0) {
return 0;
}
System.arraycopy(buf, pos, b, off, l);
pos += l;
return l;
}
@Override
public void reset() throws IOException {
pos = 0;
}
@Override
public void close() throws IOException {
// nothing to do
}
}

@ -0,0 +1,207 @@
package org.xbib.content.io;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
/**
* A growable stream of bytes, with random access methods.
*/
public class BytesStreamOutput extends OutputStream {
/**
* Small default buffer size, to reduce heap pressure.
*/
private static final int DEFAULT_BUFFER_SIZE = 1024;
private static final boolean JRE_IS_64BIT;
static {
String oaarch = System.getProperty("os.arch");
String sunarch = System.getProperty("sun.arch.data.model");
JRE_IS_64BIT = sunarch != null ? sunarch.contains("64") :
oaarch != null && oaarch.contains("64");
}
/**
* The buffer where data is stored.
*/
private byte[] buf;
/**
* The number of valid bytes in the buffer.
*/
private int count;
/**
* Create a new {@code BytesStreamOutput} with default buffer size.
*/
public BytesStreamOutput() {
this(DEFAULT_BUFFER_SIZE);
}
/**
* Create a new {@code BytesStreamOutput} with given buffer size.
* @param size size
*/
public BytesStreamOutput(int size) {
this.buf = new byte[size];
}
/**
* Return the position in the stream.
* @return the position
*/
public long position() {
return count;
}
/**
* Set to new position in stream. Must be in the current buffer.
* @param position the new position.
*/
public void seek(long position) {
if (position > Integer.MAX_VALUE) {
throw new UnsupportedOperationException();
}
count = (int) position;
}
/**
* Write an integer.
*
* @param b int
* @throws IOException if write fails
*/
@Override
public void write(int b) throws IOException {
int newcount = count + 1;
if (newcount > buf.length) {
buf = Arrays.copyOf(buf, oversize(newcount));
}
buf[count] = (byte) b;
count = newcount;
}
/**
* Write byte array.
*
* @param b byte array
* @param offset offset
* @param length length
* @throws IOException if write fails
*/
@Override
public void write(byte[] b, int offset, int length) throws IOException {
if (length == 0) {
return;
}
int newcount = count + length;
if (newcount > buf.length) {
buf = Arrays.copyOf(buf, oversize(newcount));
}
System.arraycopy(b, offset, buf, count, length);
count = newcount;
}
/**
* Skip a number of bytes.
* @param length the number of bytes to skip.
*/
public void skip(int length) {
int newcount = count + length;
if (newcount > buf.length) {
buf = Arrays.copyOf(buf, oversize(newcount));
}
count = newcount;
}
/**
* Seek to absolute position. Must be in buffer.
* @param pos the position.
*/
public void seek(int pos) {
count = pos;
}
public void reset() {
count = 0;
}
@Override
public void flush() throws IOException {
// nothing to do there
}
@Override
public void close() throws IOException {
// nothing to do here
}
/**
* Return a {@link BytesReference} to the buffer of this output stream.
* @return the byets reference
*/
public BytesReference bytes() {
return new BytesArray(buf, 0, count);
}
/**
* Returns the current size of the buffer.
*
* @return the value of the <code>count</code> field, which is the number
* of valid bytes in this output stream.
* @see java.io.ByteArrayOutputStream#count
*/
public int size() {
return count;
}
/**
* Returns an array size &gt;= minTargetSize, generally
* over-allocating exponentially to achieve amortized
* linear-time cost as the array grows.
* NOTE: this was originally borrowed from Python 2.4.2
* listobject.c sources (attribution in LICENSE.txt), but
* has now been substantially changed based on
* discussions from java-dev thread with subject "Dynamic
* array reallocation algorithms", started on Jan 12
* 2010.
*
* @param minTargetSize Minimum required value to be returned.
* @return int
*/
private static int oversize(int minTargetSize) {
if (minTargetSize < 0) {
// catch usage that accidentally overflows int
throw new IllegalArgumentException("invalid array size " + minTargetSize);
}
if (minTargetSize == 0) {
// wait until at least one element is requested
return 0;
}
// asymptotic exponential growth by 1/8th, favors
// spending a bit more CPU to not tie up too much wasted
// RAM:
int extra = minTargetSize >> 3;
if (extra < 3) {
// for very small arrays, where constant overhead of
// realloc is presumably relatively high, we grow
// faster
extra = 3;
}
int newSize = minTargetSize + extra;
// add 7 to allow for worst case byte alignment addition below:
if (newSize + 7 < 0) {
// int overflowed -- return max allowed array size
return Integer.MAX_VALUE;
}
if (JRE_IS_64BIT) {
// round up to multiple of 8
return (newSize + 7) & 0x7ffffff8;
} else {
// round up to multiple of 4
return (newSize + 3) & 0x7ffffffc;
}
}
}

@ -0,0 +1,4 @@
/**
* Classes for input/output with content.
*/
package org.xbib.content.io;

@ -0,0 +1,32 @@
package org.xbib.content.json;
import org.xbib.content.XContent;
import org.xbib.content.settings.AbstractSettingsLoader;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* Settings loader that loads (parses) the settings in a json format by flattening them
* into a map.
*/
public class JsonSettingsLoader extends AbstractSettingsLoader {
private static final Set<String> JSON_SUFFIXES = new HashSet<>(Collections.singletonList("json"));
@Override
public XContent content() {
return JsonXContent.jsonContent();
}
@Override
public Set<String> suffixes() {
return JSON_SUFFIXES;
}
@Override
public boolean canLoad(String source) {
return source.indexOf('{') != -1 && source.indexOf('}') != -1;
}
}

@ -0,0 +1,115 @@
package org.xbib.content.json;
import com.fasterxml.jackson.core.JsonEncoding;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import org.xbib.content.XContent;
import org.xbib.content.XContentBuilder;
import org.xbib.content.XContentGenerator;
import org.xbib.content.XContentParser;
import org.xbib.content.io.BytesReference;
import org.xbib.content.io.BytesStreamInput;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
/**
* A JSON content implementation using Jackson.
*/
public class JsonXContent implements XContent {
private static final JsonXContent jsonXContent;
private static final JsonFactory jsonFactory;
static {
jsonFactory = new JsonFactory();
jsonFactory.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
jsonFactory.configure(JsonGenerator.Feature.QUOTE_FIELD_NAMES, true);
jsonXContent = new JsonXContent();
}
public JsonXContent() {
// nothing to do
}
public static JsonXContent jsonContent() {
return jsonXContent;
}
public static XContentBuilder contentBuilder() throws IOException {
return XContentBuilder.builder(jsonXContent);
}
public static XContentBuilder contentBuilder(OutputStream outputStream) throws IOException {
return XContentBuilder.builder(jsonXContent, outputStream);
}
@Override
public String name() {
return "json";
}
@Override
public XContentGenerator createGenerator(OutputStream outputStream) throws IOException {
return new JsonXContentGenerator(jsonFactory.createGenerator(outputStream, JsonEncoding.UTF8));
}
@Override
public XContentGenerator createGenerator(Writer writer) throws IOException {
return new JsonXContentGenerator(jsonFactory.createGenerator(writer));
}
@Override
public XContentParser createParser(String content) throws IOException {
return new JsonXContentParser(jsonFactory
.createParser(new BytesStreamInput(content.getBytes(StandardCharsets.UTF_8))));
}
@Override
public XContentParser createParser(InputStream inputStream) throws IOException {
return new JsonXContentParser(jsonFactory.createParser(inputStream));
}
@Override
public XContentParser createParser(byte[] data) throws IOException {
return new JsonXContentParser(jsonFactory.createParser(data));
}
@Override
public XContentParser createParser(byte[] data, int offset, int length) throws IOException {
return new JsonXContentParser(jsonFactory.createParser(data, offset, length));
}
@Override
public XContentParser createParser(BytesReference bytes) throws IOException {
return createParser(bytes.streamInput());
}
@Override
public XContentParser createParser(Reader reader) throws IOException {
return new JsonXContentParser(jsonFactory.createParser(reader));
}
@Override
public boolean isXContent(BytesReference bytes) {
int length = bytes.length() < 20 ? bytes.length() : 20;
if (length == 0) {
return false;
}
byte first = bytes.get(0);
if (first == '{') {
return true;
}
for (int i = 0; i < length; i++) {
if (bytes.get(i) == '{') {
return true;
}
}
return false;
}
}

@ -0,0 +1,380 @@
package org.xbib.content.json;
import com.fasterxml.jackson.core.JsonGenerator;
import org.xbib.content.AbstractXContentGenerator;
import org.xbib.content.XContent;
import org.xbib.content.XContentBuilder;
import org.xbib.content.XContentGenerator;
import org.xbib.content.XContentHelper;
import org.xbib.content.XContentParser;
import org.xbib.content.XContentString;
import org.xbib.content.io.BytesReference;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
/**
*
*/
public class JsonXContentGenerator extends AbstractXContentGenerator {
private final JsonGeneratorDelegate delegate;
public JsonXContentGenerator(JsonGenerator generator) {
this.delegate = new JsonGeneratorDelegate(generator);
super.setGenerator(delegate);
}
@Override
public XContent content() {
return delegate.content();
}
@Override
public void usePrettyPrint() {
delegate.usePrettyPrint();
}
@Override
public void writeRawField(String fieldName, byte[] content, OutputStream outputStream) throws IOException {
delegate.writeRawField(fieldName, content, outputStream);
}
@Override
public void writeRawField(String fieldName, byte[] content, int offset, int length, OutputStream outputStream)
throws IOException {
delegate.writeRawField(fieldName, content, offset, length, outputStream);
}
@Override
public void writeRawField(String fieldName, BytesReference content, OutputStream outputStream) throws IOException {
delegate.writeRawField(fieldName, content, outputStream);
}
@Override
public void writeValue(XContentBuilder builder) throws IOException {
delegate.writeValue(builder);
}
@Override
public void copyCurrentStructure(XContentParser parser) throws IOException {
delegate.copyCurrentStructure(parser);
}
@Override
public void flush() throws IOException {
delegate.flush();
}
@Override
public void close() throws IOException {
delegate.close();
}
private static class JsonGeneratorDelegate implements XContentGenerator {
final JsonGenerator jsonGenerator;
JsonGeneratorDelegate(JsonGenerator jsonGenerator) {
this.jsonGenerator = jsonGenerator;
}
@Override
public XContent content() {
return JsonXContent.jsonContent();
}
@Override
public void usePrettyPrint() {
jsonGenerator.useDefaultPrettyPrinter();
}
@Override
public void writeStartArray() throws IOException {
jsonGenerator.writeStartArray();
}
@Override
public void writeEndArray() throws IOException {
jsonGenerator.writeEndArray();
}
@Override
public void writeStartObject() throws IOException {
jsonGenerator.writeStartObject();
}
@Override
public void writeEndObject() throws IOException {
jsonGenerator.writeEndObject();
}
@Override
public void writeFieldName(String name) throws IOException {
jsonGenerator.writeFieldName(name);
}
@Override
public void writeFieldName(XContentString name) throws IOException {
jsonGenerator.writeFieldName(name);
}
@Override
public void writeString(String text) throws IOException {
jsonGenerator.writeString(text);
}
@Override
public void writeString(char[] text, int offset, int len) throws IOException {
jsonGenerator.writeString(text, offset, len);
}
@Override
public void writeUTF8String(byte[] text, int offset, int length) throws IOException {
jsonGenerator.writeUTF8String(text, offset, length);
}
@Override
public void writeBinary(byte[] data, int offset, int len) throws IOException {
jsonGenerator.writeBinary(data, offset, len);
}
@Override
public void writeBinary(byte[] data) throws IOException {
jsonGenerator.writeBinary(data);
}
@Override
public void writeNumber(int v) throws IOException {
jsonGenerator.writeNumber(v);
}
@Override
public void writeNumber(long v) throws IOException {
jsonGenerator.writeNumber(v);
}
@Override
public void writeNumber(double d) throws IOException {
jsonGenerator.writeNumber(d);
}
@Override
public void writeNumber(float f) throws IOException {
jsonGenerator.writeNumber(f);
}
@Override
public void writeNumber(BigInteger bi) throws IOException {
jsonGenerator.writeNumber(bi);
}
@Override
public void writeNumber(BigDecimal bd) throws IOException {
jsonGenerator.writeNumber(bd);
}
@Override
public void writeBoolean(boolean b) throws IOException {
jsonGenerator.writeBoolean(b);
}
@Override
public void writeNull() throws IOException {
jsonGenerator.writeNull();
}
@Override
public void writeStringField(String fieldName, String value) throws IOException {
jsonGenerator.writeStringField(fieldName, value);
}
@Override
public void writeStringField(XContentString fieldName, String value) throws IOException {
jsonGenerator.writeFieldName(fieldName);
}
@Override
public void writeBooleanField(String fieldName, boolean value) throws IOException {
jsonGenerator.writeBooleanField(fieldName, value);
}
@Override
public void writeBooleanField(XContentString fieldName, boolean value) throws IOException {
jsonGenerator.writeFieldName(fieldName);
jsonGenerator.writeBoolean(value);
}
@Override
public void writeNullField(String fieldName) throws IOException {
jsonGenerator.writeNullField(fieldName);
}
@Override
public void writeNumberField(String fieldName, int value) throws IOException {
jsonGenerator.writeNumberField(fieldName, value);
}
@Override
public void writeNumberField(XContentString fieldName, int value) throws IOException {
jsonGenerator.writeFieldName(fieldName);
jsonGenerator.writeNumber(value);
}
@Override
public void writeNumberField(String fieldName, long value) throws IOException {
jsonGenerator.writeNumberField(fieldName, value);
}
@Override
public void writeNumberField(XContentString fieldName, long value) throws IOException {
jsonGenerator.writeFieldName(fieldName);
jsonGenerator.writeNumber(value);
}
@Override
public void writeNumberField(String fieldName, double value) throws IOException {
jsonGenerator.writeNumberField(fieldName, value);
}
@Override
public void writeNumberField(XContentString fieldName, double value) throws IOException {
jsonGenerator.writeFieldName(fieldName);
jsonGenerator.writeNumber(value);
}
@Override
public void writeNumberField(String fieldName, float value) throws IOException {
jsonGenerator.writeNumberField(fieldName, value);
}
@Override
public void writeNumberField(XContentString fieldName, float value) throws IOException {
jsonGenerator.writeFieldName(fieldName);
jsonGenerator.writeNumber(value);
}
@Override
public void writeNumberField(String fieldName, BigInteger value) throws IOException {
jsonGenerator.writeFieldName(fieldName);
jsonGenerator.writeNumber(value);
}
@Override
public void writeNumberField(XContentString fieldName, BigInteger value) throws IOException {
jsonGenerator.writeFieldName(fieldName);
jsonGenerator.writeNumber(value);
}
@Override
public void writeNumberField(String fieldName, BigDecimal value) throws IOException {
jsonGenerator.writeNumberField(fieldName, value);
}
@Override
public void writeNumberField(XContentString fieldName, BigDecimal value) throws IOException {
jsonGenerator.writeFieldName(fieldName);
jsonGenerator.writeNumber(value);
}
@Override
public void writeBinaryField(String fieldName, byte[] data) throws IOException {
jsonGenerator.writeBinaryField(fieldName, data);
}
@Override
public void writeBinaryField(XContentString fieldName, byte[] data) throws IOException {
jsonGenerator.writeFieldName(fieldName);
jsonGenerator.writeBinary(data);
}
@Override
public void writeArrayFieldStart(String fieldName) throws IOException {
jsonGenerator.writeArrayFieldStart(fieldName);
}
@Override
public void writeArrayFieldStart(XContentString fieldName) throws IOException {
jsonGenerator.writeFieldName(fieldName);
jsonGenerator.writeStartArray();
}
@Override
public void writeObjectFieldStart(String fieldName) throws IOException {
jsonGenerator.writeObjectFieldStart(fieldName);
}
@Override
public void writeObjectFieldStart(XContentString fieldName) throws IOException {
jsonGenerator.writeFieldName(fieldName);
jsonGenerator.writeStartObject();
}
@Override
public void writeRawField(String fieldName, byte[] content, OutputStream outputStream) throws IOException {
jsonGenerator.writeRaw(",\"");
jsonGenerator.writeRaw(fieldName);
jsonGenerator.writeRaw("\":");
flush();
outputStream.write(content);
}
@Override
public void writeRawField(String fieldName, byte[] content, int offset, int length, OutputStream outputStream)
throws IOException {
jsonGenerator.writeRaw(",\"");
jsonGenerator.writeRaw(fieldName);
jsonGenerator.writeRaw("\":");
flush();
outputStream.write(content, offset, length);
}
@Override
public void writeRawField(String fieldName, BytesReference content, OutputStream outputStream) throws IOException {
jsonGenerator.writeRaw(",\"");
jsonGenerator.writeRaw(fieldName);
jsonGenerator.writeRaw("\":");
flush();
content.streamOutput(outputStream);
}
@Override
public void writeValue(XContentBuilder builder) throws IOException {
jsonGenerator.writeRawValue(builder.string());
}
@Override
public void copy(XContentBuilder builder, OutputStream outputStream) throws IOException {
flush();
builder.bytes().streamOutput(outputStream);
}
@Override
public void copyCurrentStructure(XContentParser parser) throws IOException {
if (parser.currentToken() == null) {
parser.nextToken();
}
if (parser instanceof JsonXContentParser) {
jsonGenerator.copyCurrentStructure(((JsonXContentParser) parser).parser);
} else {
XContentHelper.copyCurrentStructure(this, parser);
}
}
@Override
public void flush() throws IOException {
jsonGenerator.flush();
}
@Override
public void close() throws IOException {
if (jsonGenerator.isClosed()) {
return;
}
jsonGenerator.close();
}
}
}

@ -0,0 +1,191 @@
package org.xbib.content.json;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import org.xbib.content.AbstractXContentParser;
import org.xbib.content.XContent;
import org.xbib.content.XContentParser;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
/**
*
*/
public class JsonXContentParser extends AbstractXContentParser {
protected final JsonParser parser;
public JsonXContentParser(JsonParser parser) {
this.parser = parser;
}
@Override
public XContent content() {
return JsonXContent.jsonContent();
}
@Override
public XContentParser.Token nextToken() throws IOException {
return convertToken(parser.nextToken());
}
@Override
public void skipChildren() throws IOException {
parser.skipChildren();
}
@Override
public XContentParser.Token currentToken() {
return convertToken(parser.getCurrentToken());
}
@Override
public XContentParser.NumberType numberType() throws IOException {
return convertNumberType(parser.getNumberType());
}
@Override
public boolean estimatedNumberType() {
return true;
}
@Override
public String currentName() throws IOException {
return parser.getCurrentName();
}
@Override
protected boolean doBooleanValue() throws IOException {
return parser.getBooleanValue();
}
@Override
public String text() throws IOException {
return parser.getText();
}
@Override
public boolean hasTextCharacters() {
return parser.hasTextCharacters();
}
@Override
public char[] textCharacters() throws IOException {
return parser.getTextCharacters();
}
@Override
public int textLength() throws IOException {
return parser.getTextLength();
}
@Override
public int textOffset() throws IOException {
return parser.getTextOffset();
}
@Override
public Number numberValue() throws IOException {
return parser.getNumberValue();
}
@Override
public BigInteger bigIntegerValue() throws IOException {
return parser.getBigIntegerValue();
}
@Override
public BigDecimal bigDecimalValue() throws IOException {
return parser.getDecimalValue();
}
@Override
public short doShortValue() throws IOException {
return parser.getShortValue();
}
@Override
public int doIntValue() throws IOException {
return parser.getIntValue();
}
@Override
public long doLongValue() throws IOException {
return parser.getLongValue();
}
@Override
public float doFloatValue() throws IOException {
return parser.getFloatValue();
}
@Override
public double doDoubleValue() throws IOException {
return parser.getDoubleValue();
}
@Override
public byte[] binaryValue() throws IOException {
return parser.getBinaryValue();
}
@Override
public void close() throws IOException {
parser.close();
}
private NumberType convertNumberType(JsonParser.NumberType numberType) {
switch (numberType) {
case INT:
return NumberType.INT;
case LONG:
return NumberType.LONG;
case FLOAT:
return NumberType.FLOAT;
case DOUBLE:
return NumberType.DOUBLE;
case BIG_DECIMAL:
return NumberType.BIG_DECIMAL;
case BIG_INTEGER:
return NumberType.BIG_INTEGER;
default:
break;
}
throw new IllegalStateException("No matching token for number_type [" + numberType + "]");
}
private Token convertToken(JsonToken token) {
if (token == null) {
return null;
}
switch (token) {
case FIELD_NAME:
return Token.FIELD_NAME;
case VALUE_FALSE:
case VALUE_TRUE:
return Token.VALUE_BOOLEAN;
case VALUE_STRING:
return Token.VALUE_STRING;
case VALUE_NUMBER_INT:
case VALUE_NUMBER_FLOAT:
return Token.VALUE_NUMBER;
case VALUE_NULL:
return Token.VALUE_NULL;
case START_OBJECT:
return Token.START_OBJECT;
case END_OBJECT:
return Token.END_OBJECT;
case START_ARRAY:
return Token.START_ARRAY;
case END_ARRAY:
return Token.END_ARRAY;
case VALUE_EMBEDDED_OBJECT:
return Token.VALUE_EMBEDDED_OBJECT;
default:
break;
}
throw new IllegalStateException("No matching token for json_token [" + token + "]");
}
}

@ -0,0 +1,4 @@
/**
* Classes for JSON content.
*/
package org.xbib.content.json;

@ -0,0 +1,4 @@
/**
* Classes for content parsing and generating.
*/
package org.xbib.content;

@ -0,0 +1,91 @@
package org.xbib.content.settings;
import org.xbib.content.XContent;
import org.xbib.content.XContentParser;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Settings loader that loads (parses) the settings in a XContent format by flattening them
* into a map.
*/
public abstract class AbstractSettingsLoader implements SettingsLoader {
public abstract XContent content();
@Override
public Map<String, String> load(String source) throws IOException {
try (XContentParser parser = content().createParser(source)) {
return load(parser);
}
}
public Map<String, String> load(XContentParser xContentParser) throws IOException {
StringBuilder sb = new StringBuilder();
Map<String, String> map = new HashMap<>();
List<String> path = new ArrayList<>();
XContentParser.Token token = xContentParser.nextToken();
if (token == null) {
return map;
}
parseObject(map, sb, path, xContentParser, null);
return map;
}
private void parseObject(Map<String, String> settings, StringBuilder sb, List<String> path,
XContentParser parser, String objFieldName) throws IOException {
if (objFieldName != null) {
path.add(objFieldName);
}
String currentFieldName = null;
XContentParser.Token token;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.START_OBJECT) {
parseObject(settings, sb, path, parser, currentFieldName);
} else if (token == XContentParser.Token.START_ARRAY) {
parseArray(settings, sb, path, parser, currentFieldName);
} else if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else {
parseValue(settings, sb, path, parser, currentFieldName);
}
}
if (objFieldName != null) {
path.remove(path.size() - 1);
}
}
private void parseArray(Map<String, String> settings, StringBuilder sb, List<String> path,
XContentParser parser, String name) throws IOException {
XContentParser.Token token;
int counter = 0;
String fieldName = name;
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
if (token == XContentParser.Token.START_OBJECT) {
parseObject(settings, sb, path, parser, fieldName + '.' + (counter++));
} else if (token == XContentParser.Token.START_ARRAY) {
parseArray(settings, sb, path, parser, fieldName + '.' + (counter++));
} else if (token == XContentParser.Token.FIELD_NAME) {
fieldName = parser.currentName();
} else {
parseValue(settings, sb, path, parser, fieldName + '.' + (counter++));
}
}
}
private void parseValue(Map<String, String> settings, StringBuilder sb, List<String> path,
XContentParser parser, String fieldName) throws IOException {
sb.setLength(0);
for (String s : path) {
sb.append(s).append('.');
}
sb.append(fieldName);
settings.put(sb.toString(), parser.text());
}
}

@ -0,0 +1,836 @@
package org.xbib.content.settings;
import static org.xbib.content.util.unit.ByteSizeValue.parseBytesSizeValue;
import static org.xbib.content.util.unit.TimeValue.parseTimeValue;
import org.xbib.content.XContentBuilder;
import org.xbib.content.json.JsonSettingsLoader;
import org.xbib.content.json.JsonXContent;
import org.xbib.content.util.unit.ByteSizeValue;
import org.xbib.content.util.unit.TimeValue;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.time.DateTimeException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
*/
public class Settings {
private static final Logger logger = Logger.getLogger(Settings.class.getName());
public static final Settings EMPTY_SETTINGS = new Builder().build();
public static final String[] EMPTY_ARRAY = new String[0];
public static final int BUFFER_SIZE = 1024 * 8;
private final Map<String, String> settings;
public Settings() {
this(new HashMap<>());
}
public Settings(Map<String, String> settings) {
this.settings = new HashMap<>(settings);
}
public static Settings readSettingsFromMap(Map<String, Object> map) throws IOException {
Builder builder = new Builder();
for (Map.Entry<String, Object> entry : map.entrySet()) {
builder.put(entry.getKey(), entry.getValue() != null ? entry.getValue().toString() : null);
}
return builder.build();
}
public static void writeSettingsToMap(Settings settings, Map<String, Object> map) throws IOException {
for (String key : settings.getAsMap().keySet()) {
map.put(key, settings.get(key));
}
}
/**
* Returns a builder to be used in order to build settings.
* @return a builder
*/
public static Builder settingsBuilder() {
return new Builder();
}
public static String[] splitStringByCommaToArray(final String s) {
return splitStringToArray(s, ',');
}
public static String[] splitStringToArray(final String s, final char c) {
if (s.length() == 0) {
return EMPTY_ARRAY;
}
final char[] chars = s.toCharArray();
int count = 1;
for (final char x : chars) {
if (x == c) {
count++;
}
}
final String[] result = new String[count];
final int len = chars.length;
int start = 0;
int pos = 0;
int i = 0;
for (; pos < len; pos++) {
if (chars[pos] == c) {
int size = pos - start;
if (size > 0) {
result[i++] = new String(chars, start, size);
}
start = pos + 1;
}
}
int size = pos - start;
if (size > 0) {
result[i++] = new String(chars, start, size);
}
if (i != count) {
String[] result1 = new String[i];
System.arraycopy(result, 0, result1, 0, i);
return result1;
}
return result;
}
public static String copyToString(Reader in) throws IOException {
StringWriter out = new StringWriter();
copy(in, out);
return out.toString();
}
public static int copy(final Reader in, final Writer out) throws IOException {
try (Reader reader = in; Writer writer = out) {
int byteCount = 0;
char[] buffer = new char[BUFFER_SIZE];
int bytesRead;
while ((bytesRead = reader.read(buffer)) != -1) {
writer.write(buffer, 0, bytesRead);
byteCount += bytesRead;
}
writer.flush();
return byteCount;
}
}
public Map<String, String> getAsMap() {
return this.settings;
}
public Map<String, Object> getAsStructuredMap() {
Map<String, Object> map = new HashMap<>(2);
for (Map.Entry<String, String> entry : settings.entrySet()) {
processSetting(map, "", entry.getKey(), entry.getValue());
}
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (entry.getValue() instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> valMap = (Map<String, Object>) entry.getValue();
entry.setValue(convertMapsToArrays(valMap));
}
}
return map;
}
public Settings getByPrefix(String prefix) {
Builder builder = new Builder();
for (Map.Entry<String, String> entry : settings.entrySet()) {
if (entry.getKey().startsWith(prefix)) {
if (entry.getKey().length() < prefix.length()) {
continue;
}
builder.put(entry.getKey().substring(prefix.length()), entry.getValue());
}
}
return builder.build();
}
public Settings getAsSettings(String setting) {
return getByPrefix(setting + ".");
}
public boolean containsSetting(String setting) {
if (settings.containsKey(setting)) {
return true;
}
for (Map.Entry<String, String> entry : settings.entrySet()) {
if (entry.getKey().startsWith(setting)) {
return true;
}
}
return false;
}
public String get(String setting) {
String retVal = settings.get(setting);
if (retVal != null) {
return retVal;
}
return null;
}
public String get(String setting, String defaultValue) {
String retVal = settings.get(setting);
return retVal == null ? defaultValue : retVal;
}
public Float getAsFloat(String setting, Float defaultValue) {
String sValue = get(setting);
if (sValue == null) {
return defaultValue;
}
try {
return Float.parseFloat(sValue);
} catch (NumberFormatException e) {
throw new SettingsException("Failed to parse float setting [" + setting + "] with value [" + sValue + "]", e);
}
}
public Double getAsDouble(String setting, Double defaultValue) {
String sValue = get(setting);
if (sValue == null) {
return defaultValue;
}
try {
return Double.parseDouble(sValue);
} catch (NumberFormatException e) {
throw new SettingsException("Failed to parse double setting [" + setting + "] with value [" + sValue + "]", e);
}
}
public Integer getAsInt(String setting, Integer defaultValue) {
String sValue = get(setting);
if (sValue == null) {
return defaultValue;
}
try {
return Integer.parseInt(sValue);
} catch (NumberFormatException e) {
throw new SettingsException("Failed to parse int setting [" + setting + "] with value [" + sValue + "]", e);
}
}
public Long getAsLong(String setting, Long defaultValue) {
String sValue = get(setting);
if (sValue == null) {
return defaultValue;
}
try {
return Long.parseLong(sValue);
} catch (NumberFormatException e) {
throw new SettingsException("Failed to parse long setting [" + setting + "] with value [" + sValue + "]", e);
}
}
public Boolean getAsBoolean(String setting, Boolean defaultValue) {
String value = get(setting);
if (value == null) {
return defaultValue;
}
return !("false".equals(value) || "0".equals(value) || "off".equals(value) || "no".equals(value));
}
public TimeValue getAsTime(String setting, TimeValue defaultValue) {
return parseTimeValue(get(setting), defaultValue);
}
public ByteSizeValue getAsBytesSize(String setting, ByteSizeValue defaultValue) throws SettingsException {
return parseBytesSizeValue(get(setting), defaultValue);
}
public String[] getAsArray(String settingPrefix) throws SettingsException {
return getAsArray(settingPrefix, EMPTY_ARRAY);
}
public String[] getAsArray(String settingPrefix, String[] defaultArray) throws SettingsException {
List<String> result = new ArrayList<>();
if (get(settingPrefix) != null) {
String[] strings = splitStringByCommaToArray(get(settingPrefix));
if (strings.length > 0) {
for (String string : strings) {
result.add(string.trim());
}
}
}
int counter = 0;
while (true) {
String value = get(settingPrefix + '.' + (counter++));
if (value == null) {
break;
}
result.add(value.trim());
}
if (result.isEmpty()) {
return defaultArray;
}
return result.toArray(new String[result.size()]);
}
public Map<String, Settings> getGroups(String prefix) throws SettingsException {
String settingPrefix = prefix;
if (settingPrefix.charAt(settingPrefix.length() - 1) != '.') {
settingPrefix = settingPrefix + ".";
}
// we don't really care that it might happen twice
Map<String, Map<String, String>> map = new LinkedHashMap<>();
for (Object o : settings.keySet()) {
String setting = (String) o;
if (setting.startsWith(settingPrefix)) {
String nameValue = setting.substring(settingPrefix.length());
int dotIndex = nameValue.indexOf('.');
if (dotIndex == -1) {
throw new SettingsException("Failed to get setting group for ["
+ settingPrefix
+ "] setting prefix and setting [" + setting + "] because of a missing '.'");
}
String name = nameValue.substring(0, dotIndex);
String value = nameValue.substring(dotIndex + 1);
Map<String, String> groupSettings = map.get(name);
if (groupSettings == null) {
groupSettings = new LinkedHashMap<>();
map.put(name, groupSettings);
}
groupSettings.put(value, get(setting));
}
}
Map<String, Settings> retVal = new LinkedHashMap<>();
for (Map.Entry<String, Map<String, String>> entry : map.entrySet()) {
retVal.put(entry.getKey(), new Settings(Collections.unmodifiableMap(entry.getValue())));
}
return Collections.unmodifiableMap(retVal);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Settings that = (Settings) o;
return settings != null ? settings.equals(that.settings) : that.settings == null;
}
@Override
public int hashCode() {
return settings != null ? settings.hashCode() : 0;
}
private void processSetting(Map<String, Object> map, String prefix, String setting, String value) {
int prefixLength = setting.indexOf('.');
if (prefixLength == -1) {
@SuppressWarnings("unchecked")
Map<String, Object> innerMap = (Map<String, Object>) map.get(prefix + setting);
if (innerMap != null) {
for (Map.Entry<String, Object> entry : innerMap.entrySet()) {
map.put(prefix + setting + "." + entry.getKey(), entry.getValue());
}
}
map.put(prefix + setting, value);
} else {
String key = setting.substring(0, prefixLength);
String rest = setting.substring(prefixLength + 1);
Object existingValue = map.get(prefix + key);
if (existingValue == null) {
Map<String, Object> newMap = new HashMap<>(2);
processSetting(newMap, "", rest, value);
map.put(key, newMap);
} else {
if (existingValue instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> innerMap = (Map<String, Object>) existingValue;
processSetting(innerMap, "", rest, value);
map.put(key, innerMap);
} else {
processSetting(map, prefix + key + ".", rest, value);
}
}
}
}
private Object convertMapsToArrays(Map<String, Object> map) {
if (map.isEmpty()) {
return map;
}
boolean isArray = true;
int maxIndex = -1;
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (isArray) {
try {
int index = Integer.parseInt(entry.getKey());
if (index >= 0) {
maxIndex = Math.max(maxIndex, index);
} else {
isArray = false;
}
} catch (NumberFormatException ex) {
isArray = false;
}
}
if (entry.getValue() instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> valMap = (Map<String, Object>) entry.getValue();
entry.setValue(convertMapsToArrays(valMap));
}
}
if (isArray && (maxIndex + 1) == map.size()) {
ArrayList<Object> newValue = new ArrayList<>(maxIndex + 1);
for (int i = 0; i <= maxIndex; i++) {
Object obj = map.get(Integer.toString(i));
if (obj == null) {
return map;
}
newValue.add(obj);
}
return newValue;
}
return map;
}
/**
*
*/
public static class Builder {
private final Map<String, String> map = new LinkedHashMap<>();
private Builder() {
}
public Map<String, String> internalMap() {
return this.map;
}
public String remove(String key) {
return map.remove(key);
}
public String get(String key) {
return map.get(key);
}
/**
* Sets a setting with the provided setting key and value.
*
* @param key The setting key
* @param value The setting value
* @return The builder
*/
public Builder put(String key, String value) {
map.put(key, value);
return this;
}
/**
* Sets a setting with the provided setting key and class as value.
*
* @param key The setting key
* @param clazz The setting class value
* @return The builder
*/
public Builder put(String key, Class<?> clazz) {
map.put(key, clazz.getName());
return this;
}
/**
* Sets the setting with the provided setting key and the boolean value.
*
* @param setting The setting key
* @param value The boolean value
* @return The builder
*/
public Builder put(String setting, boolean value) {
put(setting, String.valueOf(value));
return this;
}
/**
* Sets the setting with the provided setting key and the int value.
*
* @param setting The setting key
* @param value The int value
* @return The builder
*/
public Builder put(String setting, int value) {
put(setting, String.valueOf(value));
return this;
}
/**
* Sets the setting with the provided setting key and the long value.
*
* @param setting The setting key
* @param value The long value
* @return The builder
*/
public Builder put(String setting, long value) {
put(setting, String.valueOf(value));
return this;
}
/**
* Sets the setting with the provided setting key and the float value.
*
* @param setting The setting key
* @param value The float value
* @return The builder
*/
public Builder put(String setting, float value) {
put(setting, String.valueOf(value));
return this;
}
/**
* Sets the setting with the provided setting key and the double value.
*
* @param setting The setting key
* @param value The double value
* @return The builder
*/
public Builder put(String setting, double value) {
put(setting, String.valueOf(value));
return this;
}
/**
* Sets the setting with the provided setting key and an array of values.
*
* @param setting The setting key
* @param values The values
* @return The builder
*/
public Builder putArray(String setting, String... values) {
remove(setting);
int counter = 0;
while (true) {
String value = map.remove(setting + '.' + (counter++));
if (value == null) {
break;
}
}
for (int i = 0; i < values.length; i++) {
put(setting + '.' + i, values[i]);
}
return this;
}
/**
* Sets the setting with the provided setting key and an array of values.
*
* @param setting The setting key
* @param values The values
* @return The builder
*/
public Builder putArray(String setting, List<String> values) {
remove(setting);
int counter = 0;
while (true) {
String value = map.remove(setting + '.' + (counter++));
if (value == null) {
break;
}
}
for (int i = 0; i < values.size(); i++) {
put(setting + '.' + i, values.get(i));
}
return this;
}
/**
* Sets the setting group.
* @param settingPrefix setting prefix
* @param groupName group name
* @param settings settings
* @param values values
* @return a builder
* @throws SettingsException if setting fails
*/
public Builder put(String settingPrefix, String groupName, String[] settings, String[] values)
throws SettingsException {
if (settings.length != values.length) {
throw new SettingsException("The settings length must match the value length");
}
for (int i = 0; i < settings.length; i++) {
if (values[i] == null) {
continue;
}
put(settingPrefix + "" + groupName + "." + settings[i], values[i]);
}
return this;
}
/**
* Sets all the provided settings.
* @param settings settings
* @return builder
*/
public Builder put(Settings settings) {
map.putAll(settings.getAsMap());
return this;
}
/**
* Sets all the provided settings.
*
* @param settings settings
* @return a builder
*/
public Builder put(Map<String, String> settings) {
map.putAll(settings);
return this;
}
/**
* Loads settings from the actual string content that represents them using the
* {@link SettingsLoaderService#loaderFromString(String)}.
*
* @param source source
* @return builder
*/
public Builder loadFromString(String source) {
SettingsLoader settingsLoader = SettingsLoaderService.loaderFromString(source);
try {
Map<String, String> loadedSettings = settingsLoader.load(source);
put(loadedSettings);
} catch (Exception e) {
throw new SettingsException("Failed to load settings from [" + source + "]", e);
}
return this;
}
/**
* Loads settings from a map.
* @param map map
* @return builder
*/
public Builder loadFromMap(Map<String, Object> map) {
try (XContentBuilder builder = JsonXContent.contentBuilder()) {
put(new JsonSettingsLoader().load(builder.map(map).string()));
} catch (Exception e) {
throw new SettingsException("Failed to load settings from [" + map + "]", e);
}
return this;
}
/**
* Loads settings from an URL.
* @param url url
* @return builder
*/
public Builder loadFromUrl(URL url) throws SettingsException {
try {
return loadFromStream(url.toExternalForm(), url.openStream());
} catch (IOException e) {
throw new SettingsException("Failed to open stream for url [" + url.toExternalForm() + "]", e);
}
}
/**
* Loads settings from a stream.
* @param resourceName resource name
* @param inputStream input stream
* @return builder
*/
public Builder loadFromStream(String resourceName, InputStream inputStream) throws SettingsException {
SettingsLoader settingsLoader = SettingsLoaderService.loaderFromResource(resourceName);
try {
Map<String, String> loadedSettings = settingsLoader
.load(copyToString(new InputStreamReader(inputStream, StandardCharsets.UTF_8)));
put(loadedSettings);
} catch (Exception e) {
throw new SettingsException("Failed to load settings from [" + resourceName + "]", e);
}
return this;
}
/**
* Runs across all the settings set on this builder and replaces <tt>${...}</tt> elements in the
* each setting value according to the following logic:
*
* First, tries to resolve it against a System property ({@link System#getProperty(String)}), next,
* tries and resolve it against an environment variable ({@link System#getenv(String)}), next,
* tries and resolve it against a date pattern to resolve the current date,
* and last, tries and replace it with another setting already set on this builder.
* @return builder
*/
public Builder replacePropertyPlaceholders() {
PropertyPlaceholder propertyPlaceholder = new PropertyPlaceholder("${", "}", false);
PropertyPlaceholder.PlaceholderResolver placeholderResolver = placeholderName -> {
String value = System.getProperty(placeholderName);
if (value != null) {
return value;
}
value = System.getenv(placeholderName);
if (value != null) {
return value;
}
try {
return DateTimeFormatter.ofPattern(placeholderName).format(LocalDate.now());
} catch (IllegalArgumentException | DateTimeException e) {
logger.log(Level.FINER, e.getMessage(), e);
return map.get(placeholderName);
}
};
for (Map.Entry<String, String> entry : map.entrySet()) {
map.put(entry.getKey(), propertyPlaceholder.replacePlaceholders(entry.getValue(), placeholderResolver));
}
return this;
}
public Settings build() {
return new Settings(map);
}
}
private static class PropertyPlaceholder {
private final String placeholderPrefix;
private final String placeholderSuffix;
private final boolean ignoreUnresolvablePlaceholders;
/**
* Creates a new <code>PropertyPlaceholderHelper</code> that uses the supplied prefix and suffix.
*
* @param placeholderPrefix the prefix that denotes the start of a placeholder.
* @param placeholderSuffix the suffix that denotes the end of a placeholder.
* @param ignoreUnresolvablePlaceholders indicates whether unresolvable placeholders should be ignored
* (<code>true</code>) or cause an exception (<code>false</code>).
*/
public PropertyPlaceholder(String placeholderPrefix, String placeholderSuffix,
boolean ignoreUnresolvablePlaceholders) {
this.placeholderPrefix = placeholderPrefix;
this.placeholderSuffix = placeholderSuffix;
this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders;
}
/**
* Replaces all placeholders of format <code>${name}</code> with the value returned from the supplied {@link
* PlaceholderResolver}.
*
* @param value the value containing the placeholders to be replaced.
* @param placeholderResolver the <code>PlaceholderResolver</code> to use for replacement.
* @return the supplied value with placeholders replaced inline.
*/
public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
return parseStringValue(value, placeholderResolver, new HashSet<String>());
}
protected String parseStringValue(String strVal, PlaceholderResolver placeholderResolver,
Set<String> visitedPlaceholders) {
StringBuilder buf = new StringBuilder(strVal);
int startIndex = strVal.indexOf(this.placeholderPrefix);
while (startIndex != -1) {
int endIndex = findPlaceholderEndIndex(buf, startIndex);
if (endIndex != -1) {
String placeholder = buf.substring(startIndex + this.placeholderPrefix.length(), endIndex);
if (!visitedPlaceholders.add(placeholder)) {
throw new IllegalArgumentException(
"Circular placeholder reference '" + placeholder + "' in property definitions");
}
// Recursive invocation, parsing placeholders contained in the placeholder key.
placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
// Now obtain the value for the fully resolved key...
int defaultValueIdx = placeholder.indexOf(':');
String defaultValue = null;
if (defaultValueIdx != -1) {
defaultValue = placeholder.substring(defaultValueIdx + 1);
placeholder = placeholder.substring(0, defaultValueIdx);
}
String propVal = placeholderResolver.resolvePlaceholder(placeholder);
if (propVal == null) {
propVal = defaultValue;
}
if (propVal != null) {
// Recursive invocation, parsing placeholders contained in the
// previously resolved placeholder value.
propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
buf.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
startIndex = buf.indexOf(this.placeholderPrefix, startIndex + propVal.length());
} else if (this.ignoreUnresolvablePlaceholders) {
// Proceed with unprocessed value.
startIndex = buf.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
} else {
throw new IllegalArgumentException("Could not resolve placeholder '" + placeholder + "'");
}
visitedPlaceholders.remove(placeholder);
} else {
startIndex = -1;
}
}
return buf.toString();
}
private int findPlaceholderEndIndex(CharSequence buf, int startIndex) {
int index = startIndex + this.placeholderPrefix.length();
int withinNestedPlaceholder = 0;
while (index < buf.length()) {
if (substringMatch(buf, index, this.placeholderSuffix)) {
if (withinNestedPlaceholder > 0) {
withinNestedPlaceholder--;
index = index + this.placeholderPrefix.length() - 1;
} else {
return index;
}
} else if (substringMatch(buf, index, this.placeholderPrefix)) {
withinNestedPlaceholder++;
index = index + this.placeholderPrefix.length();
} else {
index++;
}
}
return -1;
}
private boolean substringMatch(CharSequence str, int index, CharSequence substring) {
for (int j = 0; j < substring.length(); j++) {
int i = index + j;
if (i >= str.length() || str.charAt(i) != substring.charAt(j)) {
return false;
}
}
return true;
}
/**
* Strategy interface used to resolve replacement values for placeholders contained in Strings.
*/
@FunctionalInterface
public interface PlaceholderResolver {
/**
* Resolves the supplied placeholder name into the replacement value.
*
* @param placeholderName the name of the placeholder to resolve.
* @return the replacement value or <code>null</code> if no replacement is to be made.
*/
String resolvePlaceholder(String placeholderName);
}
}
}

@ -0,0 +1,17 @@
package org.xbib.content.settings;
/**
* A generic failure to handle settings.
*/
public class SettingsException extends RuntimeException {
private static final long serialVersionUID = -1833327708622505101L;
public SettingsException(String message) {
super(message);
}
public SettingsException(String message, Throwable cause) {
super(message, cause);
}
}

@ -0,0 +1,24 @@
package org.xbib.content.settings;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
/**
* Provides the ability to load settings from
* the actual source content that represents them.
*/
public interface SettingsLoader {
Set<String> suffixes();
/**
* Loads the settings from a source string.
* @param source the source
* @return a Map
* @throws IOException if load fails
*/
Map<String, String> load(String source) throws IOException;
boolean canLoad(String source);
}

@ -0,0 +1,63 @@
package org.xbib.content.settings;
import org.xbib.content.json.JsonSettingsLoader;
import java.util.HashMap;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A settings loader service for loading {@link SettingsLoader} implementations.
*/
public final class SettingsLoaderService {
private static final Logger logger = Logger.getLogger(SettingsLoaderService.class.getName());
private static final Map<Set<String>, SettingsLoader> settingsLoaderMap = new HashMap<>();
private SettingsLoaderService() {
try {
ServiceLoader<SettingsLoader> serviceLoader = ServiceLoader.load(SettingsLoader.class);
for (SettingsLoader settingsLoader : serviceLoader) {
if (!settingsLoaderMap.containsKey(settingsLoader.suffixes())) {
settingsLoaderMap.put(settingsLoader.suffixes(), settingsLoader);
}
}
} catch (Exception e) {
logger.log(Level.SEVERE, e.getMessage(), e);
}
}
/**
* Returns a {@link SettingsLoader} based on the resource name.
* @param resourceName the resource
* @return the settings loader
*/
public static SettingsLoader loaderFromResource(String resourceName) {
for (Map.Entry<Set<String>, SettingsLoader> entry : settingsLoaderMap.entrySet()) {
Set<String> suffixes = entry.getKey();
for (String suffix : suffixes) {
if (resourceName.endsWith("." + suffix)) {
return entry.getValue();
}
}
}
return new JsonSettingsLoader();
}
/**
* Returns a {@link SettingsLoader} based on the actual source.
* @param source the source
* @return the settings loader
*/
public static SettingsLoader loaderFromString(String source) {
for (SettingsLoader loader : settingsLoaderMap.values()) {
if (loader.canLoad(source)) {
return loader;
}
}
return new JsonSettingsLoader();
}
}

@ -0,0 +1,4 @@
/**
* Classes for settings.
*/
package org.xbib.content.settings;

@ -0,0 +1,470 @@
package org.xbib.content.util.geo;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
/**
* Utilities for encoding and decoding geohashes. Based on
* http://en.wikipedia.org/wiki/Geohash.
*/
public class GeoHashUtils {
public static final int PRECISION = 12;
private static final char[] BASE_32 = {'0', '1', '2', '3', '4', '5', '6',
'7', '8', '9', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'm', 'n',
'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
private static final int[] BITS = {16, 8, 4, 2, 1};
private GeoHashUtils() {
}
public static String encode(double latitude, double longitude) {
return encode(latitude, longitude, PRECISION);
}
/**
* Encodes the given latitude and longitude into a geohash.
*
* @param latitude Latitude to encode
* @param longitude Longitude to encode
* @param precision precision
* @return Geohash encoding of the longitude and latitude
*/
public static String encode(double latitude, double longitude, int precision) {
double latInterval0 = -90.0;
double latInterval1 = 90.0;
double lngInterval0 = -180.0;
double lngInterval1 = 180.0;
final StringBuilder geohash = new StringBuilder();
boolean isEven = true;
int bit = 0;
int ch = 0;
while (geohash.length() < precision) {
double mid;
if (isEven) {
mid = (lngInterval0 + lngInterval1) / 2D;
if (longitude > mid) {
ch |= BITS[bit];
lngInterval0 = mid;
} else {
lngInterval1 = mid;
}
} else {
mid = (latInterval0 + latInterval1) / 2D;
if (latitude > mid) {
ch |= BITS[bit];
latInterval0 = mid;
} else {
latInterval1 = mid;
}
}
isEven = !isEven;
if (bit < 4) {
bit++;
} else {
geohash.append(BASE_32[ch]);
bit = 0;
ch = 0;
}
}
return geohash.toString();
}
private static char encode(int x, int y) {
return BASE_32[((x & 1) + ((y & 1) * 2) + ((x & 2) * 2) + ((y & 2) * 4) + ((x & 4) * 4)) % 32];
}
/**
* Calculate all neighbors of a given geohash cell.
*
* @param geohash Geohash of the defined cell
* @return geohashes of all neighbor cells
*/
public static Collection<? extends CharSequence> neighbors(String geohash) {
return addNeighbors(geohash, geohash.length(), new ArrayList<CharSequence>(8));
}
/**
* Create an {@link Iterable} which allows to iterate over the cells that
* contain a given geohash.
*
* @param geohash Geohash of a cell
* @return {@link Iterable} of path
*/
public static Iterable<String> path(final String geohash) {
return new Iterable<String>() {
@Override
public Iterator<String> iterator() {
return new GeohashPathIterator(geohash);
}
};
}
/**
* Calculate the geohash of a neighbor of a geohash.
*
* @param geohash the geohash of a cell
* @param level non-negative level of the geohash
* @param dx delta of the first grid coordinate (must be -1, 0 or +1)
* @param dy delta of the second grid coordinate (must be -1, 0 or +1)
* @return geohash of the defined cell
*/
private static String neighbor(String geohash, int level, int dx, int dy) {
int cell = decode(geohash.charAt(level - 1));
// Decoding the Geohash bit pattern to determine grid coordinates
int x0 = cell & 1; // first bit of x
int y0 = cell & 2; // first bit of y
int x1 = cell & 4; // second bit of x
int y1 = cell & 8; // second bit of y
int x2 = cell & 16; // third bit of x
// combine the bitpattern to grid coordinates.
// note that the semantics of x and y are swapping
// on each level
int x = x0 + (x1 / 2) + (x2 / 4);
int y = (y0 / 2) + (y1 / 4);
if (level == 1) {
// Root cells at north (namely "bcfguvyz") or at
// south (namely "0145hjnp") do not have neighbors
// in north/south direction
if ((dy < 0 && y == 0) || (dy > 0 && y == 3)) {
return null;
} else {
return Character.toString(encode(x + dx, y + dy));
}
} else if (level > 1) {
boolean odd = (level % 2) != 0;
// define grid coordinates for next level
final int nx = odd ? (x + dx) : (x + dy);
final int ny = odd ? (y + dy) : (y + dx);
boolean even = (level % 2) == 0;
// define grid limits for current level
final int xLimit = even ? 7 : 3;
final int yLimit = even ? 3 : 7;
// if the defined neighbor has the same parent a the current cell
// encode the cell directly. Otherwise find the cell next to this
// cell recursively. Since encoding wraps around within a cell
// it can be encoded here.
if (nx >= 0 && nx <= xLimit && ny >= 0 && ny < yLimit) {
return geohash.substring(0, level - 1) + encode(nx, ny);
} else {
String neighbor = neighbor(geohash, level - 1, dx, dy);
if (neighbor != null) {
return neighbor + encode(nx, ny);
} else {
return null;
}
}
}
return null;
}
/**
* Add all geohashes of the cells next to a given geohash to a list.
*
* @param geohash Geohash of a specified cell
* @param neighbors list to add the neighbors to
* @param <E> the neighbor type
* @return the given list
*/
public static <E extends Collection<? super String>> E addNeighbors(String geohash, E neighbors) {
return addNeighbors(geohash, geohash.length(), neighbors);
}
/**
* Add all geohashes of the cells next to a given geohash to a list.
*
* @param geohash Geohash of a specified cell
* @param length level of the given geohash
* @param neighbors list to add the neighbors to
* @param <E> the neighbor type
* @return the given list
*/
public static <E extends Collection<? super String>> E addNeighbors(String geohash, int length, E neighbors) {
String south = neighbor(geohash, length, 0, -1);
String north = neighbor(geohash, length, 0, +1);
if (north != null) {
neighbors.add(neighbor(north, length, -1, 0));
neighbors.add(north);
neighbors.add(neighbor(north, length, +1, 0));
}
neighbors.add(neighbor(geohash, length, -1, 0));
neighbors.add(neighbor(geohash, length, +1, 0));
if (south != null) {
neighbors.add(neighbor(south, length, -1, 0));
neighbors.add(south);
neighbors.add(neighbor(south, length, +1, 0));
}
return neighbors;
}
private static int decode(char geo) {
switch (geo) {
case '0':
return 0;
case '1':
return 1;
case '2':
return 2;
case '3':
return 3;
case '4':
return 4;
case '5':
return 5;
case '6':
return 6;
case '7':
return 7;
case '8':
return 8;
case '9':
return 9;
case 'b':
return 10;
case 'c':
return 11;
case 'd':
return 12;
case 'e':
return 13;
case 'f':
return 14;
case 'g':
return 15;
case 'h':
return 16;
case 'j':
return 17;
case 'k':
return 18;
case 'm':
return 19;
case 'n':
return 20;
case 'p':
return 21;
case 'q':
return 22;
case 'r':
return 23;
case 's':
return 24;
case 't':
return 25;
case 'u':
return 26;
case 'v':
return 27;
case 'w':
return 28;
case 'x':
return 29;
case 'y':
return 30;
case 'z':
return 31;
default:
throw new IllegalArgumentException("the character '" + geo + "' is not a valid geohash character");
}
}
/**
* Decodes the given geohash.
*
* @param geohash Geohash to decocde
* @return {@link GeoPoint} at the center of cell, given by the geohash
*/
public static GeoPoint decode(String geohash) {
return decode(geohash, new GeoPoint());
}
/**
* Decodes the given geohash into a latitude and longitude.
*
* @param geohash Geohash to decocde
* @param ret the ret geo point
* @return the given {@link GeoPoint} reseted to the center of
* cell, given by the geohash
*/
public static GeoPoint decode(String geohash, GeoPoint ret) {
double[] interval = decodeCell(geohash);
return ret.reset((interval[0] + interval[1]) / 2D, (interval[2] + interval[3]) / 2D);
}
/**
* Decodes the given geohash into a geohash cell defined by the points nothWest and southEast.
*
* @param geohash Geohash to deocde
* @param northWest the point north/west of the cell
* @param southEast the point south/east of the cell
*/
public static void decodeCell(String geohash, GeoPoint northWest, GeoPoint southEast) {
double[] interval = decodeCell(geohash);
northWest.reset(interval[1], interval[2]);
southEast.reset(interval[0], interval[3]);
}
private static double[] decodeCell(String geohash) {
double[] interval = {-90.0, 90.0, -180.0, 180.0};
boolean isEven = true;
for (int i = 0; i < geohash.length(); i++) {
final int cd = decode(geohash.charAt(i));
for (int mask : BITS) {
if (isEven) {
if ((cd & mask) != 0) {
interval[2] = (interval[2] + interval[3]) / 2D;
} else {
interval[3] = (interval[2] + interval[3]) / 2D;
}
} else {
if ((cd & mask) != 0) {
interval[0] = (interval[0] + interval[1]) / 2D;
} else {
interval[1] = (interval[0] + interval[1]) / 2D;
}
}
isEven = !isEven;
}
}
return interval;
}
/**
* Encodes latitude and longitude information into a single long with variable precision.
* Up to 12 levels of precision are supported which should offer sub-metre resolution.
*
* @param latitude latitude
* @param longitude longitude
* @param precision The required precision between 1 and 12
* @return A single long where 4 bits are used for holding the precision and the remaining
* 60 bits are reserved for 5 bit cell identifiers giving up to 12 layers.
*/
public static long encodeAsLong(double latitude, double longitude, int precision) {
if ((precision > 12) || (precision < 1)) {
throw new IllegalArgumentException("Illegal precision length of " + precision +
". Long-based geohashes only support precisions between 1 and 12");
}
double latInterval0 = -90.0;
double latInterval1 = 90.0;
double lngInterval0 = -180.0;
double lngInterval1 = 180.0;
long geohash = 0L;
boolean isEven = true;
int bit = 0;
int ch = 0;
int geohashLength = 0;
while (geohashLength < precision) {
double mid;
if (isEven) {
mid = (lngInterval0 + lngInterval1) / 2D;
if (longitude > mid) {
ch |= BITS[bit];
lngInterval0 = mid;
} else {
lngInterval1 = mid;
}
} else {
mid = (latInterval0 + latInterval1) / 2D;
if (latitude > mid) {
ch |= BITS[bit];
latInterval0 = mid;
} else {
latInterval1 = mid;
}
}
isEven = !isEven;
if (bit < 4) {
bit++;
} else {
geohashLength++;
geohash |= ch;
if (geohashLength < precision) {
geohash <<= 5;
}
bit = 0;
ch = 0;
}
}
geohash <<= 4;
geohash |= precision;
return geohash;
}
/**
* Formats a geohash held as a long as a more conventional
* String-based geohash.
*
* @param geohashAsLong a geohash encoded as a long
* @return A traditional base32-based String representation of a geohash
*/
public static String toString(long geohashAsLong) {
int precision = (int) (geohashAsLong & 15);
char[] chars = new char[precision];
long l = geohashAsLong;
l >>= 4;
for (int i = precision - 1; i >= 0; i--) {
chars[i] = BASE_32[(int) (l & 31)];
l >>= 5;
}
return new String(chars);
}
public static GeoPoint decode(long geohash) {
GeoPoint point = new GeoPoint();
decode(geohash, point);
return point;
}
/**
* Decodes the given long-format geohash into a latitude and longitude.
*
* @param geohash long format Geohash to decode
* @param ret The Geopoint into which the latitude and longitude will be stored
*/
public static void decode(long geohash, GeoPoint ret) {
double[] interval = decodeCell(geohash);
ret.reset((interval[0] + interval[1]) / 2D, (interval[2] + interval[3]) / 2D);
}
private static double[] decodeCell(long geohash) {
double[] interval = {-90.0, 90.0, -180.0, 180.0};
boolean isEven = true;
int precision = (int) (geohash & 15);
long l = geohash;
l >>= 4;
int[] cds = new int[precision];
for (int i = precision - 1; i >= 0; i--) {
cds[i] = (int) (l & 31);
l >>= 5;
}
for (final int cd : cds) {
for (int mask : BITS) {
if (isEven) {
if ((cd & mask) != 0) {
interval[2] = (interval[2] + interval[3]) / 2D;
} else {
interval[3] = (interval[2] + interval[3]) / 2D;
}
} else {
if ((cd & mask) != 0) {
interval[0] = (interval[0] + interval[1]) / 2D;
} else {
interval[1] = (interval[0] + interval[1]) / 2D;
}
}
isEven = !isEven;
}
}
return interval;
}
}

@ -0,0 +1,123 @@
package org.xbib.content.util.geo;
/**
*
*/
public final class GeoPoint {
private double lat;
private double lon;
public GeoPoint() {
}
/**
* Create a new Geopointform a string. This String must either be a geohash
* or a lat-lon tuple.
*
* @param value String to create the point from
*/
public GeoPoint(String value) {
this.resetFromString(value);
}
public GeoPoint(double lat, double lon) {
this.lat = lat;
this.lon = lon;
}
public static GeoPoint parseFromLatLon(String latLon) {
GeoPoint point = new GeoPoint();
point.resetFromString(latLon);
return point;
}
public GeoPoint reset(double lat, double lon) {
this.lat = lat;
this.lon = lon;
return this;
}
public GeoPoint resetLat(double lat) {
this.lat = lat;
return this;
}
public GeoPoint resetLon(double lon) {
this.lon = lon;
return this;
}
public GeoPoint resetFromString(String value) {
int comma = value.indexOf(',');
if (comma != -1) {
lat = Double.parseDouble(value.substring(0, comma).trim());
lon = Double.parseDouble(value.substring(comma + 1).trim());
} else {
resetFromGeoHash(value);
}
return this;
}
public GeoPoint resetFromGeoHash(String hash) {
GeoHashUtils.decode(hash, this);
return this;
}
void latlon(double lat, double lon) {
this.lat = lat;
this.lon = lon;
}
public final double lat() {
return this.lat;
}
public final double getLat() {
return this.lat;
}
public final double lon() {
return this.lon;
}
public final double getLon() {
return this.lon;
}
public final String geohash() {
return GeoHashUtils.encode(lat, lon);
}
public final String getGeohash() {
return GeoHashUtils.encode(lat, lon);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
GeoPoint geoPoint = (GeoPoint) o;
return Double.compare(geoPoint.lat, lat) == 0 && Double.compare(geoPoint.lon, lon) == 0;
}
@Override
public int hashCode() {
int result;
long temp;
temp = Double.compare(lat, 0.0d) == 0 ? 0L : Double.doubleToLongBits(lat);
result = (int) (temp ^ (temp >>> 32));
temp = Double.compare(lon, 0.0d) == 0 ? 0L : Double.doubleToLongBits(lon);
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
@Override
public String toString() {
return "[" + lat + ", " + lon + "]";
}
}

@ -0,0 +1,45 @@
package org.xbib.content.util.geo;
import java.util.Iterator;
import java.util.NoSuchElementException;
/**
* This class iterates over the cells of a given geohash. Assume geohashes
* form a tree, this iterator traverses this tree form a leaf (actual gehash)
* to the root (geohash of length 1).
*/
public final class GeohashPathIterator implements Iterator<String> {
private final String geohash;
private int currentLength;
/**
* Create a new {@link GeohashPathIterator} for a given geohash.
*
* @param geohash The geohash to traverse
*/
public GeohashPathIterator(String geohash) {
this.geohash = geohash;
this.currentLength = geohash.length();
}
@Override
public boolean hasNext() {
return currentLength > 0;
}
@Override
public String next() {
if (currentLength <= 0) {
throw new NoSuchElementException();
}
String result = geohash.substring(0, currentLength);
currentLength--;
return result;
}
@Override
public void remove() {
throw new UnsupportedOperationException("unable to remove a geohash from this path");
}
}

@ -0,0 +1,4 @@
/**
* Utility classes for geo points and geo hashes.
*/
package org.xbib.content.util.geo;

@ -0,0 +1,4 @@
/**
* Utility classes for cotent.
*/
package org.xbib.content.util;

@ -0,0 +1,233 @@
package org.xbib.content.util.unit;
/**
* A <tt>SizeUnit</tt> represents size at a given unit of
* granularity and provides utility methods to convert across units.
* A <tt>SizeUnit</tt> does not maintain size information, but only
* helps organize and use size representations that may be maintained
* separately across various contexts.
*/
public enum ByteSizeUnit {
BYTES {
@Override
public long toBytes(long size) {
return size;
}
@Override
public long toKB(long size) {
return size / (C1 / C0);
}
@Override
public long toMB(long size) {
return size / (C2 / C0);
}
@Override
public long toGB(long size) {
return size / (C3 / C0);
}
@Override
public long toTB(long size) {
return size / (C4 / C0);
}
@Override
public long toPB(long size) {
return size / (C5 / C0);
}
},
KB {
@Override
public long toBytes(long size) {
return x(size, C1 / C0, MAX / (C1 / C0));
}
@Override
public long toKB(long size) {
return size;
}
@Override
public long toMB(long size) {
return size / (C2 / C1);
}
@Override
public long toGB(long size) {
return size / (C3 / C1);
}
@Override
public long toTB(long size) {
return size / (C4 / C1);
}
@Override
public long toPB(long size) {
return size / (C5 / C1);
}
},
MB {
@Override
public long toBytes(long size) {
return x(size, C2 / C0, MAX / (C2 / C0));
}
@Override
public long toKB(long size) {
return x(size, C2 / C1, MAX / (C2 / C1));
}
@Override
public long toMB(long size) {
return size;
}
@Override
public long toGB(long size) {
return size / (C3 / C2);
}
@Override
public long toTB(long size) {
return size / (C4 / C2);
}
@Override
public long toPB(long size) {
return size / (C5 / C2);
}
},
GB {
@Override
public long toBytes(long size) {
return x(size, C3 / C0, MAX / (C3 / C0));
}
@Override
public long toKB(long size) {
return x(size, C3 / C1, MAX / (C3 / C1));
}
@Override
public long toMB(long size) {
return x(size, C3 / C2, MAX / (C3 / C2));
}
@Override
public long toGB(long size) {
return size;
}
@Override
public long toTB(long size) {
return size / (C4 / C3);
}
@Override
public long toPB(long size) {
return size / (C5 / C3);
}
},
TB {
@Override
public long toBytes(long size) {
return x(size, C4 / C0, MAX / (C4 / C0));
}
@Override
public long toKB(long size) {
return x(size, C4 / C1, MAX / (C4 / C1));
}
@Override
public long toMB(long size) {
return x(size, C4 / C2, MAX / (C4 / C2));
}
@Override
public long toGB(long size) {
return x(size, C4 / C3, MAX / (C4 / C3));
}
@Override
public long toTB(long size) {
return size;
}
@Override
public long toPB(long size) {
return size / (C5 / C4);
}
},
PB {
@Override
public long toBytes(long size) {
return x(size, C5 / C0, MAX / (C5 / C0));
}
@Override
public long toKB(long size) {
return x(size, C5 / C1, MAX / (C5 / C1));
}
@Override
public long toMB(long size) {
return x(size, C5 / C2, MAX / (C5 / C2));
}
@Override
public long toGB(long size) {
return x(size, C5 / C3, MAX / (C5 / C3));
}
@Override
public long toTB(long size) {
return x(size, C5 / C4, MAX / (C5 / C4));
}
@Override
public long toPB(long size) {
return size;
}
};
static final long C0 = 1L;
static final long C1 = C0 * 1024L;
static final long C2 = C1 * 1024L;
static final long C3 = C2 * 1024L;
static final long C4 = C3 * 1024L;
static final long C5 = C4 * 1024L;
static final long MAX = Long.MAX_VALUE;
/**
* Scale d by m, checking for overflow.
* This has a short name to make above code more readable.
*/
static long x(long d, long m, long over) {
if (d > over) {
return Long.MAX_VALUE;
}
if (d < -over) {
return Long.MIN_VALUE;
}
return d * m;
}
public abstract long toBytes(long size);
public abstract long toKB(long size);
public abstract long toMB(long size);
public abstract long toGB(long size);
public abstract long toTB(long size);
public abstract long toPB(long size);
}

@ -0,0 +1,228 @@
package org.xbib.content.util.unit;
import java.util.Locale;
/**
*
*/
public class ByteSizeValue {
private long size;
private ByteSizeUnit sizeUnit;
private ByteSizeValue() {
}
public ByteSizeValue(long size, ByteSizeUnit sizeUnit) {
this.size = size;
this.sizeUnit = sizeUnit;
}
/**
* Format the double value with a single decimal points, trimming trailing '.0'.
* @param value value
* @param suffix suffix
* @return formatted decimal
*/
public static String format1Decimals(double value, String suffix) {
String p = String.valueOf(value);
int ix = p.indexOf('.') + 1;
int ex = p.indexOf('E');
char fraction = p.charAt(ix);
if (fraction == '0') {
if (ex != -1) {
return p.substring(0, ix - 1) + p.substring(ex) + suffix;
} else {
return p.substring(0, ix - 1) + suffix;
}
} else {
if (ex != -1) {
return p.substring(0, ix) + fraction + p.substring(ex) + suffix;
} else {
return p.substring(0, ix) + fraction + suffix;
}
}
}
public static ByteSizeValue parseBytesSizeValue(String sValue) {
return parseBytesSizeValue(sValue, null);
}
public static ByteSizeValue parseBytesSizeValue(String sValue, ByteSizeValue defaultValue) {
if (sValue == null) {
return defaultValue;
}
long bytes;
try {
String lastTwoChars = sValue.substring(sValue.length() - Math.min(2, sValue.length())).toLowerCase(Locale.ROOT);
if (lastTwoChars.endsWith("k")) {
bytes = (long) (Double.parseDouble(sValue.substring(0, sValue.length() - 1)) * ByteSizeUnit.C1);
} else if (lastTwoChars.endsWith("kb")) {
bytes = (long) (Double.parseDouble(sValue.substring(0, sValue.length() - 2)) * ByteSizeUnit.C1);
} else if (lastTwoChars.endsWith("m")) {
bytes = (long) (Double.parseDouble(sValue.substring(0, sValue.length() - 1)) * ByteSizeUnit.C2);
} else if (lastTwoChars.endsWith("mb")) {
bytes = (long) (Double.parseDouble(sValue.substring(0, sValue.length() - 2)) * ByteSizeUnit.C2);
} else if (lastTwoChars.endsWith("g")) {
bytes = (long) (Double.parseDouble(sValue.substring(0, sValue.length() - 1)) * ByteSizeUnit.C3);
} else if (lastTwoChars.endsWith("gb")) {
bytes = (long) (Double.parseDouble(sValue.substring(0, sValue.length() - 2)) * ByteSizeUnit.C3);
} else if (lastTwoChars.endsWith("t")) {
bytes = (long) (Double.parseDouble(sValue.substring(0, sValue.length() - 1)) * ByteSizeUnit.C4);
} else if (lastTwoChars.endsWith("tb")) {
bytes = (long) (Double.parseDouble(sValue.substring(0, sValue.length() - 2)) * ByteSizeUnit.C4);
} else if (lastTwoChars.endsWith("p")) {
bytes = (long) (Double.parseDouble(sValue.substring(0, sValue.length() - 1)) * ByteSizeUnit.C5);
} else if (lastTwoChars.endsWith("pb")) {
bytes = (long) (Double.parseDouble(sValue.substring(0, sValue.length() - 2)) * ByteSizeUnit.C5);
} else if (lastTwoChars.endsWith("b")) {
bytes = Long.parseLong(sValue.substring(0, sValue.length() - 1));
} else {
bytes = Long.parseLong(sValue);
}
} catch (NumberFormatException e) {
return defaultValue;
}
return new ByteSizeValue(bytes, ByteSizeUnit.BYTES);
}
public int bytesAsInt() throws IllegalArgumentException {
long bytes = bytes();
if (bytes > Integer.MAX_VALUE) {
throw new IllegalArgumentException("size [" + toString() + "] is bigger than max int");
}
return (int) bytes;
}
public long bytes() {
return sizeUnit.toBytes(size);
}
public long getBytes() {
return bytes();
}
public long kb() {
return sizeUnit.toKB(size);
}
public long getKb() {
return kb();
}
public long mb() {
return sizeUnit.toMB(size);
}
public long getMb() {
return mb();
}
public long gb() {
return sizeUnit.toGB(size);
}
public long getGb() {
return gb();
}
public long tb() {
return sizeUnit.toTB(size);
}
public long getTb() {
return tb();
}
public long pb() {
return sizeUnit.toPB(size);
}
public long getPb() {
return pb();
}
public double kbFrac() {
return ((double) bytes()) / ByteSizeUnit.C1;
}
public double getKbFrac() {
return kbFrac();
}
public double mbFrac() {
return ((double) bytes()) / ByteSizeUnit.C2;
}
public double getMbFrac() {
return mbFrac();
}
public double gbFrac() {
return ((double) bytes()) / ByteSizeUnit.C3;
}
public double getGbFrac() {
return gbFrac();
}
public double tbFrac() {
return ((double) bytes()) / ByteSizeUnit.C4;
}
public double getTbFrac() {
return tbFrac();
}
public double pbFrac() {
return ((double) bytes()) / ByteSizeUnit.C5;
}
public double getPbFrac() {
return pbFrac();
}
@Override
public String toString() {
long bytes = bytes();
double value = bytes;
String suffix = "b";
if (bytes >= ByteSizeUnit.C5) {
value = pbFrac();
suffix = "pb";
} else if (bytes >= ByteSizeUnit.C4) {
value = tbFrac();
suffix = "tb";
} else if (bytes >= ByteSizeUnit.C3) {
value = gbFrac();
suffix = "gb";
} else if (bytes >= ByteSizeUnit.C2) {
value = mbFrac();
suffix = "mb";
} else if (bytes >= ByteSizeUnit.C1) {
value = kbFrac();
suffix = "kb";
}
return format1Decimals(value, suffix);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ByteSizeValue sizeValue = (ByteSizeValue) o;
return size == sizeValue.size && sizeUnit == sizeValue.sizeUnit;
}
@Override
public int hashCode() {
int result = (int) (size ^ (size >>> 32));
result = 31 * result + (sizeUnit != null ? sizeUnit.hashCode() : 0);
return result;
}
}

@ -0,0 +1,229 @@
package org.xbib.content.util.unit;
/**
*
*/
public enum SizeUnit {
SCALAR {
@Override
public long toScalar(long size) {
return size;
}
@Override
public long toKilo(long size) {
return size / (C1 / C0);
}
@Override
public long toMega(long size) {
return size / (C2 / C0);
}
@Override
public long toGiga(long size) {
return size / (C3 / C0);
}
@Override
public long toTera(long size) {
return size / (C4 / C0);
}
@Override
public long toPeta(long size) {
return size / (C5 / C0);
}
},
KILO {
@Override
public long toScalar(long size) {
return x(size, C1 / C0, MAX / (C1 / C0));
}
@Override
public long toKilo(long size) {
return size;
}
@Override
public long toMega(long size) {
return size / (C2 / C1);
}
@Override
public long toGiga(long size) {
return size / (C3 / C1);
}
@Override
public long toTera(long size) {
return size / (C4 / C1);
}
@Override
public long toPeta(long size) {
return size / (C5 / C1);
}
},
MEGA {
@Override
public long toScalar(long size) {
return x(size, C2 / C0, MAX / (C2 / C0));
}
@Override
public long toKilo(long size) {
return x(size, C2 / C1, MAX / (C2 / C1));
}
@Override
public long toMega(long size) {
return size;
}
@Override
public long toGiga(long size) {
return size / (C3 / C2);
}
@Override
public long toTera(long size) {
return size / (C4 / C2);
}
@Override
public long toPeta(long size) {
return size / (C5 / C2);
}
},
GIGA {
@Override
public long toScalar(long size) {
return x(size, C3 / C0, MAX / (C3 / C0));
}
@Override
public long toKilo(long size) {
return x(size, C3 / C1, MAX / (C3 / C1));
}
@Override
public long toMega(long size) {
return x(size, C3 / C2, MAX / (C3 / C2));
}
@Override
public long toGiga(long size) {
return size;
}
@Override
public long toTera(long size) {
return size / (C4 / C3);
}
@Override
public long toPeta(long size) {
return size / (C5 / C3);
}
},
TERA {
@Override
public long toScalar(long size) {
return x(size, C4 / C0, MAX / (C4 / C0));
}
@Override
public long toKilo(long size) {
return x(size, C4 / C1, MAX / (C4 / C1));
}
@Override
public long toMega(long size) {
return x(size, C4 / C2, MAX / (C4 / C2));
}
@Override
public long toGiga(long size) {
return x(size, C4 / C3, MAX / (C4 / C3));
}
@Override
public long toTera(long size) {
return size;
}
@Override
public long toPeta(long size) {
return size / (C5 / C0);
}
},
PETA {
@Override
public long toScalar(long size) {
return x(size, C5 / C0, MAX / (C5 / C0));
}
@Override
public long toKilo(long size) {
return x(size, C5 / C1, MAX / (C5 / C1));
}
@Override
public long toMega(long size) {
return x(size, C5 / C2, MAX / (C5 / C2));
}
@Override
public long toGiga(long size) {
return x(size, C5 / C3, MAX / (C5 / C3));
}
@Override
public long toTera(long size) {
return x(size, C5 / C4, MAX / (C5 / C4));
}
@Override
public long toPeta(long size) {
return size;
}
};
static final long C0 = 1L;
static final long C1 = C0 * 1000L;
static final long C2 = C1 * 1000L;
static final long C3 = C2 * 1000L;
static final long C4 = C3 * 1000L;
static final long C5 = C4 * 1000L;
static final long MAX = Long.MAX_VALUE;
/**
* Scale d by m, checking for overflow.
* This has a short name to make above code more readable.
*/
static long x(long d, long m, long over) {
if (d > over) {
return Long.MAX_VALUE;
}
if (d < -over) {
return Long.MIN_VALUE;
}
return d * m;
}
public abstract long toScalar(long size);
public abstract long toKilo(long size);
public abstract long toMega(long size);
public abstract long toGiga(long size);
public abstract long toTera(long size);
public abstract long toPeta(long size);
}

@ -0,0 +1,259 @@
package org.xbib.content.util.unit;
import java.util.concurrent.TimeUnit;
/**
*
*/
public class TimeValue {
private static final long C0 = 1L;
private static final long C1 = C0 * 1000L;
private static final long C2 = C1 * 1000L;
private static final long C3 = C2 * 1000L;
private static final long C4 = C3 * 60L;
private static final long C5 = C4 * 60L;
private static final long C6 = C5 * 24L;
private long duration;
private TimeUnit timeUnit;
private TimeValue() {
}
public TimeValue(long millis) {
this(millis, TimeUnit.MILLISECONDS);
}
public TimeValue(long duration, TimeUnit timeUnit) {
this.duration = duration;
this.timeUnit = timeUnit;
}
public static TimeValue timeValueNanos(long nanos) {
return new TimeValue(nanos, TimeUnit.NANOSECONDS);
}
public static TimeValue timeValueMillis(long millis) {
return new TimeValue(millis, TimeUnit.MILLISECONDS);
}
public static TimeValue timeValueSeconds(long seconds) {
return new TimeValue(seconds, TimeUnit.SECONDS);
}
public static TimeValue timeValueMinutes(long minutes) {
return new TimeValue(minutes, TimeUnit.MINUTES);
}
public static TimeValue timeValueHours(long hours) {
return new TimeValue(hours, TimeUnit.HOURS);
}
/**
* Format the double value with a single decimal points, trimming trailing '.0'.
*
* @param value value
* @param suffix suffix
* @return string
*/
public static String format1Decimals(double value, String suffix) {
String p = String.valueOf(value);
int ix = p.indexOf('.') + 1;
int ex = p.indexOf('E');
char fraction = p.charAt(ix);
if (fraction == '0') {
if (ex != -1) {
return p.substring(0, ix - 1) + p.substring(ex) + suffix;
} else {
return p.substring(0, ix - 1) + suffix;
}
} else {
if (ex != -1) {
return p.substring(0, ix) + fraction + p.substring(ex) + suffix;
} else {
return p.substring(0, ix) + fraction + suffix;
}
}
}
public static TimeValue parseTimeValue(String sValue, TimeValue defaultValue) {
if (sValue == null) {
return defaultValue;
}
long millis;
if (sValue.endsWith("S")) {
millis = Long.parseLong(sValue.substring(0, sValue.length() - 1));
} else if (sValue.endsWith("ms")) {
millis = (long) (Double.parseDouble(sValue.substring(0, sValue.length() - 2)));
} else if (sValue.endsWith("s")) {
millis = (long) (Double.parseDouble(sValue.substring(0, sValue.length() - 1)) * 1000);
} else if (sValue.endsWith("m")) {
millis = (long) (Double.parseDouble(sValue.substring(0, sValue.length() - 1)) * 60 * 1000);
} else if (sValue.endsWith("H") || sValue.endsWith("h")) {
millis = (long) (Double.parseDouble(sValue.substring(0, sValue.length() - 1)) * 60 * 60 * 1000);
} else if (sValue.endsWith("d")) {
millis = (long) (Double.parseDouble(sValue.substring(0, sValue.length() - 1)) * 24 * 60 * 60 * 1000);
} else if (sValue.endsWith("w")) {
millis = (long) (Double.parseDouble(sValue.substring(0, sValue.length() - 1)) * 7 * 24 * 60 * 60 * 1000);
} else {
millis = Long.parseLong(sValue);
}
return new TimeValue(millis, TimeUnit.MILLISECONDS);
}
public long nanos() {
return timeUnit.toNanos(duration);
}
public long getNanos() {
return nanos();
}
public long micros() {
return timeUnit.toMicros(duration);
}
public long getMicros() {
return micros();
}
public long millis() {
return timeUnit.toMillis(duration);
}
public long getMillis() {
return millis();
}
public long seconds() {
return timeUnit.toSeconds(duration);
}
public long getSeconds() {
return seconds();
}
public long minutes() {
return timeUnit.toMinutes(duration);
}
public long getMinutes() {
return minutes();
}
public long hours() {
return timeUnit.toHours(duration);
}
public long getHours() {
return hours();
}
public long days() {
return timeUnit.toDays(duration);
}
public long getDays() {
return days();
}
public double microsFrac() {
return ((double) nanos()) / C1;
}
public double getMicrosFrac() {
return microsFrac();
}
public double millisFrac() {
return ((double) nanos()) / C2;
}
public double getMillisFrac() {
return millisFrac();
}
public double secondsFrac() {
return ((double) nanos()) / C3;
}
public double getSecondsFrac() {
return secondsFrac();
}
public double minutesFrac() {
return ((double) nanos()) / C4;
}
public double getMinutesFrac() {
return minutesFrac();
}
public double hoursFrac() {
return ((double) nanos()) / C5;
}
public double getHoursFrac() {
return hoursFrac();
}
public double daysFrac() {
return ((double) nanos()) / C6;
}
public double getDaysFrac() {
return daysFrac();
}
@Override
public String toString() {
if (duration < 0) {
return Long.toString(duration);
}
long nanos = nanos();
if (nanos == 0) {
return "0s";
}
double value = nanos;
String suffix = "nanos";
if (nanos >= C6) {
value = daysFrac();
suffix = "d";
} else if (nanos >= C5) {
value = hoursFrac();
suffix = "h";
} else if (nanos >= C4) {
value = minutesFrac();
suffix = "m";
} else if (nanos >= C3) {
value = secondsFrac();
suffix = "s";
} else if (nanos >= C2) {
value = millisFrac();
suffix = "ms";
} else if (nanos >= C1) {
value = microsFrac();
suffix = "micros";
}
return format1Decimals(value, suffix);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
TimeValue timeValue = (TimeValue) o;
return duration == timeValue.duration && timeUnit == timeValue.timeUnit;
}
@Override
public int hashCode() {
int result = (int) (duration ^ (duration >>> 32));
result = 31 * result + (timeUnit != null ? timeUnit.hashCode() : 0);
return result;
}
}

@ -0,0 +1,4 @@
/**
* Classes for units.
*/
package org.xbib.content.util.unit;

@ -0,0 +1,150 @@
package org.xbib.content;
import static org.xbib.content.json.JsonXContent.contentBuilder;
import org.junit.Assert;
import org.junit.Test;
import org.xbib.content.json.JsonXContent;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Date;
import java.util.Map;
/**
*
*/
public class XContentBuilderTest extends Assert {
@Test
public void testCopy() throws IOException {
XContentBuilder builder = contentBuilder();
builder.startObject().field("hello", "world").endObject();
builder.close();
XContentBuilder builder2 = contentBuilder();
builder2.copy(builder);
builder2.close();
assertEquals(builder2.string(), "{\"hello\":\"world\"}");
}
@Test
public void testCopyList() throws IOException {
XContentBuilder builder1 = contentBuilder();
builder1.startObject().field("hello", "world").endObject();
builder1.close();
XContentBuilder builder2 = contentBuilder();
builder2.startObject().field("hello", "world").endObject();
builder2.close();
XContentBuilder builder = contentBuilder();
builder.startObject().startArray("list");
builder.copy(Arrays.asList(builder1, builder2));
builder.endArray().endObject();
builder.close();
assertEquals(builder.string(), "{\"list\":[{\"hello\":\"world\"},{\"hello\":\"world\"}]}");
}
@Test
public void testBuilderAsXContentList() throws IOException {
XContentBuilder builder1 = contentBuilder();
builder1.startObject().field("hello", "world").endObject();
builder1.close();
XContentBuilder builder2 = contentBuilder();
builder2.startObject().field("hello", "world").endObject();
builder2.close();
XContentBuilder builder = contentBuilder();
builder.startObject().array("list", builder1, builder2).endObject();
builder.close();
assertEquals(builder.string(), "{\"list\":[{\"hello\":\"world\"},{\"hello\":\"world\"}]}");
}
@Test
public void testBigDecimal() throws IOException {
XContentBuilder builder = contentBuilder();
builder.startObject().field("value", new BigDecimal("57683974591503.00")).endObject();
assertEquals(builder.string(), "{\"value\":57683974591503.00}");
XContent content = XContentService.xContent(builder.string());
Map<String, Object> map = content
.createParser(builder.string())
.losslessDecimals(true)
.mapAndClose();
assertEquals(map.toString(), "{value=57683974591503.00}");
assertEquals(map.get("value").getClass().toString(), "class java.math.BigDecimal");
map = content
.createParser(builder.string())
.losslessDecimals(false)
.mapAndClose();
assertEquals(map.toString(), "{value=5.7683974591503E13}");
assertEquals(map.get("value").getClass().toString(), "class java.lang.Double");
}
@Test
public void testBigInteger() throws IOException {
XContentBuilder builder = contentBuilder();
builder.startObject().field("value", new BigInteger("1234567891234567890123456789")).endObject();
assertEquals(builder.string(), "{\"value\":1234567891234567890123456789}");
XContent content = XContentService.xContent(builder.string());
Map<String, Object> map = content
.createParser(builder.string())
.losslessDecimals(true)
.mapAndClose();
assertEquals(map.toString(), "{value=1234567891234567890123456789}");
assertEquals(map.get("value").getClass().toString(), "class java.math.BigInteger");
map = content
.createParser(builder.string())
.losslessDecimals(false)
.mapAndClose();
assertEquals(map.toString(), "{value=1234567891234567890123456789}");
assertEquals(map.get("value").getClass().toString(), "class java.math.BigInteger");
}
@Test
public void testDateFormatting() throws IOException {
XContentBuilder builder = contentBuilder();
Date d = new Date();
d.setTime(1398175311488L);
builder.startObject().field("value", d).endObject();
Map<String, Object> map = JsonXContent.jsonContent()
.createParser(builder.string())
.losslessDecimals(false)
.mapAndClose();
assertEquals("{value=2014-04-22T14:01:51.488Z}", map.toString());
}
@Test
public void testBase16() throws IOException {
XContentBuilder builder = contentBuilder();
builder.startObject().field("value", "4AC3B67267").endObject();
assertEquals(builder.string(), "{\"value\":\"4AC3B67267\"}");
XContent content = XContentService.xContent(builder.string());
Map<String, Object> map = content
.createParser(builder.string())
.enableBase16Checks(true)
.mapAndClose();
assertEquals(new String((byte[]) map.get("value")), "Jörg");
map = content.createParser(builder.string())
.enableBase16Checks(false)
.mapAndClose();
assertEquals(map.toString(), "{value=4AC3B67267}");
}
@Test(expected = NullPointerException.class)
public void testNullKey() throws IOException {
XContentBuilder builder = contentBuilder();
builder.field((String) null);
}
}

@ -0,0 +1,4 @@
/**
* Classes for testing content parsing and generating.
*/
package org.xbib.content;

@ -0,0 +1,75 @@
package org.xbib.content.settings;
import static org.xbib.content.settings.Settings.settingsBuilder;
import org.junit.Assert;
import org.junit.Test;
import org.xbib.content.XContentHelper;
import java.io.StringReader;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
/**
*
*/
public class SettingsTest extends Assert {
@Test
public void testArray() {
Settings settings = Settings.settingsBuilder()
.putArray("input", Arrays.asList("a", "b", "c")).build();
assertEquals("a", settings.getAsArray("input")[0]);
assertEquals("b", settings.getAsArray("input")[1]);
assertEquals("c", settings.getAsArray("input")[2]);
}
@Test
public void testGroups() {
Settings settings = Settings.settingsBuilder()
.put("prefix.group1.k1", "v1")
.put("prefix.group1.k2", "v2")
.put("prefix.group1.k3", "v3")
.put("prefix.group2.k1", "v1")
.put("prefix.group2.k2", "v2")
.put("prefix.group2.k3", "v3")
.build();
Map<String, Settings> groups = settings.getGroups("prefix");
assertEquals("[group1, group2]", groups.keySet().toString());
assertTrue(groups.get("group1").getAsMap().containsKey("k1"));
assertTrue(groups.get("group1").getAsMap().containsKey("k2"));
assertTrue(groups.get("group1").getAsMap().containsKey("k3"));
assertTrue(groups.get("group2").getAsMap().containsKey("k1"));
assertTrue(groups.get("group2").getAsMap().containsKey("k2"));
assertTrue(groups.get("group2").getAsMap().containsKey("k3"));
}
@Test
public void testMapForSettings() {
Map<String, Object> map = new HashMap<>();
map.put("hello", "world");
Map<String, Object> settingsMap = new HashMap<>();
settingsMap.put("map", map);
Settings settings = settingsBuilder().loadFromMap(settingsMap).build();
assertEquals("{map.hello=world}", settings.getAsMap().toString());
}
@Test
public void testMapSettingsFromReader() {
StringReader reader = new StringReader("{\"map\":{\"hello\":\"world\"}}");
Map<String, Object> spec = XContentHelper.convertFromJsonToMap(reader);
Settings settings = settingsBuilder().loadFromMap(spec).build();
assertEquals("{map.hello=world}", settings.getAsMap().toString());
}
@Test
public void testCurrentYearInSettings() {
Settings settings = Settings.settingsBuilder()
.put("date", "${yyyy}")
.replacePropertyPlaceholders()
.build();
assertTrue(Integer.parseInt(settings.get("date")) > 2000);
}
}

@ -0,0 +1,4 @@
/**
* Classes for testing settings parsing and generating.
*/
package org.xbib.content.settings;

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="OFF">
<appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="[%d{ABSOLUTE}][%-5p][%-25c][%t] %m%n"/>
</Console>
</appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</configuration>

@ -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,junit,net,org,java,javax"/>
<!-- This ensures that static imports go first. -->
<property name="option" value="top"/>
<property name="tokens" value="STATIC_IMPORT, 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>

@ -0,0 +1,16 @@
package org.xbib.content.csv;
/**
*
*/
interface CSVConstants {
char BACKSPACE = '\b';
char CR = '\r';
char FF = '\f';
char LF = '\n';
char TAB = '\t';
char COMMA = ',';
char QUOTE = '\"';
char ESCAPE_CHARACTER = '\"';
}

@ -0,0 +1,99 @@
package org.xbib.content.csv;
import java.io.Closeable;
import java.io.Flushable;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
/**
*
*/
public class CSVGenerator implements CSVConstants, Closeable, Flushable {
private static final String LF = System.getProperty("line.separator");
private Writer writer;
private int col;
private int row;
private List<String> keys;
public CSVGenerator(Writer writer) {
this.writer = writer;
this.col = 0;
this.keys = new ArrayList<>();
}
public CSVGenerator keys(List<String> keys) {
this.keys = keys;
return this;
}
public CSVGenerator writeKeys() throws IOException {
for (String k : keys) {
write(k);
}
return this;
}
public CSVGenerator write(String value) throws IOException {
if (col > 0) {
writer.write(COMMA);
}
if (value != null) {
writer.write(escape(value));
}
col++;
if (col > keys.size()) {
writer.write(LF);
row++;
col = 0;
}
return this;
}
public int getColumn() {
return col;
}
public int getRow() {
return row;
}
@Override
public void close() throws IOException {
writer.close();
}
@Override
public void flush() throws IOException {
writer.flush();
}
private String escape(String value) {
if (value.indexOf(QUOTE) < 0
&& value.indexOf(ESCAPE_CHARACTER) < 0
&& value.indexOf(COMMA) < 0
&& value.indexOf(TAB) < 0
&& !value.contains(LF)) {
return value;
}
int length = value.length();
StringBuilder sb = new StringBuilder(length + 2);
sb.append(QUOTE);
for (int i = 0; i < length; i++) {
char ch = value.charAt(i);
if (ch == QUOTE) {
sb.append(QUOTE);
}
sb.append(ch);
}
sb.append(QUOTE);
return sb.toString();
}
}

@ -0,0 +1,257 @@
package org.xbib.content.csv;
import java.io.Closeable;
import java.io.IOException;
/**
*
*/
class CSVLexer implements CSVConstants, Closeable {
private final CSVLookAheadReader reader;
private final char delimiter;
private final char escape;
private final char quoteChar;
private final char commentStart;
private final boolean ignoreSurroundingSpaces;
private final boolean ignoreEmptyLines;
CSVLexer(CSVLookAheadReader reader, char delimiter, char escape, char quoteChar, char commentStart,
boolean ignoreSurroundingSpaces, boolean ignoreEmptyLines) {
this.reader = reader;
this.delimiter = delimiter;
this.escape = escape;
this.quoteChar = quoteChar;
this.commentStart = commentStart;
this.ignoreSurroundingSpaces = ignoreSurroundingSpaces;
this.ignoreEmptyLines = ignoreEmptyLines;
}
CSVToken nextToken(final CSVToken csvToken) throws IOException {
int lastChar = reader.getLastChar();
int c = reader.read();
boolean eol = readEndOfLine(c);
if (ignoreEmptyLines) {
while (eol && isStartOfLine(lastChar)) {
lastChar = c;
c = reader.read();
eol = readEndOfLine(c);
if (isEndOfFile(c)) {
csvToken.type = CSVToken.Type.EOF;
return csvToken;
}
}
}
if (isEndOfFile(lastChar) || (!isDelimiter(lastChar) && isEndOfFile(c))) {
csvToken.type = CSVToken.Type.EOF;
return csvToken;
}
if (isStartOfLine(lastChar) && isCommentStart(c)) {
final String line = reader.readLine();
if (line == null) {
csvToken.type = CSVToken.Type.EOF;
return csvToken;
}
final String comment = line.trim();
csvToken.content.append(comment);
csvToken.type = CSVToken.Type.COMMENT;
return csvToken;
}
while (csvToken.type == CSVToken.Type.INVALID) {
if (ignoreSurroundingSpaces) {
while (isWhitespace(c) && !eol) {
c = reader.read();
eol = readEndOfLine(c);
}
}
if (isDelimiter(c)) {
csvToken.type = CSVToken.Type.TOKEN;
} else if (eol) {
csvToken.type = CSVToken.Type.EORECORD;
} else if (isQuoteChar(c)) {
parseEncapsulatedToken(csvToken);
} else if (isEndOfFile(c)) {
csvToken.type = CSVToken.Type.EOF;
csvToken.isReady = true;
} else {
parseSimpleToken(csvToken, c);
}
}
return csvToken;
}
private CSVToken parseSimpleToken(final CSVToken csvToken, int c) throws IOException {
int ch = c;
while (true) {
if (readEndOfLine(ch)) {
csvToken.type = CSVToken.Type.EORECORD;
break;
} else if (isEndOfFile(ch)) {
csvToken.type = CSVToken.Type.EOF;
csvToken.isReady = true;
break;
} else if (isDelimiter(ch)) {
csvToken.type = CSVToken.Type.TOKEN;
break;
} else if (isEscape(ch)) {
final int unescaped = readEscape();
if (unescaped == -1) {
csvToken.content.append((char) ch).append((char) reader.getLastChar());
} else {
csvToken.content.append((char) unescaped);
}
ch = reader.read();
} else {
csvToken.content.append((char) ch);
ch = reader.read();
}
}
if (ignoreSurroundingSpaces) {
trimTrailingSpaces(csvToken.content);
}
return csvToken;
}
private CSVToken parseEncapsulatedToken(final CSVToken csvToken) throws IOException {
final long startLineNumber = getCurrentLineNumber();
int c;
while (true) {
c = reader.read();
if (isEscape(c)) {
final int unescaped = readEscape();
if (unescaped == -1) {
csvToken.content.append((char) c).append((char) reader.getLastChar());
} else {
csvToken.content.append((char) unescaped);
}
} else if (isQuoteChar(c)) {
if (isQuoteChar(reader.lookAhead())) {
c = reader.read();
csvToken.content.append((char) c);
} else {
while (true) {
c = reader.read();
if (isDelimiter(c)) {
csvToken.type = CSVToken.Type.TOKEN;
return csvToken;
} else if (isEndOfFile(c)) {
csvToken.type = CSVToken.Type.EOF;
csvToken.isReady = true;
return csvToken;
} else if (readEndOfLine(c)) {
csvToken.type = CSVToken.Type.EORECORD;
return csvToken;
} else if (!isWhitespace(c)) {
throw new IOException("(line "
+ getCurrentLineNumber()
+ ") invalid char between encapsulated token and delimiter");
}
}
}
} else if (isEndOfFile(c)) {
throw new IOException("(startline "
+ startLineNumber
+ ") EOF reached before encapsulated token finished");
} else {
csvToken.content.append((char) c);
}
}
}
long getCurrentLineNumber() {
return reader.getCurrentLineNumber();
}
private int readEscape() throws IOException {
final int ch = reader.read();
switch (ch) {
case 'r':
return CR;
case 'n':
return LF;
case 't':
return TAB;
case 'b':
return BACKSPACE;
case 'f':
return FF;
case CR:
case LF:
case FF:
case TAB:
case BACKSPACE:
return ch;
case -1:
throw new IOException("EOF whilst processing escape sequence");
default:
if (isMetaChar(ch)) {
return ch;
}
return -1;
}
}
private void trimTrailingSpaces(final StringBuilder buffer) {
int length = buffer.length();
while (length > 0 && Character.isWhitespace(buffer.charAt(length - 1))) {
length = length - 1;
}
if (length != buffer.length()) {
buffer.setLength(length);
}
}
private boolean readEndOfLine(final int ch) throws IOException {
int c = ch;
if (c == CR && reader.lookAhead() == LF) {
c = reader.read();
}
return c == LF || c == CR;
}
private boolean isWhitespace(final int ch) {
return !isDelimiter(ch) && Character.isWhitespace((char) ch);
}
private boolean isStartOfLine(final int ch) {
return ch == LF || ch == CR || ch == -2;
}
private boolean isEndOfFile(final int ch) {
return ch == -1;
}
private boolean isDelimiter(final int ch) {
return ch == delimiter;
}
private boolean isEscape(final int ch) {
return ch == escape;
}
private boolean isQuoteChar(final int ch) {
return ch == quoteChar;
}
private boolean isCommentStart(final int ch) {
return ch == commentStart;
}
private boolean isMetaChar(final int ch) {
return ch == delimiter ||
ch == escape ||
ch == quoteChar ||
ch == commentStart;
}
@Override
public void close() throws IOException {
reader.close();
}
}

@ -0,0 +1,91 @@
package org.xbib.content.csv;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
/**
*
*/
class CSVLookAheadReader extends BufferedReader implements CSVConstants {
private int lastChar = -2;
private long eolCounter = 0;
CSVLookAheadReader(Reader reader) {
super(reader);
}
@Override
public int read() throws IOException {
final int current = super.read();
if (current == CR || (current == LF && lastChar != CR)) {
eolCounter++;
}
lastChar = current;
return lastChar;
}
int getLastChar() {
return lastChar;
}
@Override
public int read(final char[] buf, final int offset, final int length) throws IOException {
if (length == 0) {
return 0;
}
final int len = super.read(buf, offset, length);
if (len > 0) {
for (int i = offset; i < offset + len; i++) {
final char ch = buf[i];
if (ch == LF) {
if (CR != (i > 0 ? buf[i - 1] : lastChar)) {
eolCounter++;
}
} else if (ch == CR) {
eolCounter++;
}
}
lastChar = buf[offset + len - 1];
} else if (len == -1) {
lastChar = -1;
}
return len;
}
@Override
public String readLine() throws IOException {
final String line = super.readLine();
if (line != null) {
lastChar = LF;
eolCounter++;
} else {
lastChar = -1;
}
return line;
}
int lookAhead() throws IOException {
super.mark(1);
final int c = super.read();
super.reset();
return c;
}
long getCurrentLineNumber() {
if (lastChar == CR || lastChar == LF || lastChar == -2 || lastChar == -1) {
return eolCounter;
}
return eolCounter + 1;
}
@Override
public void close() throws IOException {
lastChar = -1;
super.close();
}
}

@ -0,0 +1,118 @@
package org.xbib.content.csv;
import java.io.IOException;
import java.io.Reader;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
*/
public class CSVParser {
private static final Logger logger = Logger.getLogger(CSVParser.class.getName());
private final CSVLexer lexer;
private final List<String> row;
private final CSVToken reusableCSVToken;
public CSVParser(Reader reader) throws IOException {
lexer = new CSVLexer(new CSVLookAheadReader(reader), ',', '\\', '"', '#', true, true);
row = new LinkedList<>();
reusableCSVToken = new CSVToken();
}
public CSVParser(Reader reader, char sep) throws IOException {
lexer = new CSVLexer(new CSVLookAheadReader(reader), sep, '\\', '"', '#', true, true);
row = new LinkedList<>();
reusableCSVToken = new CSVToken();
}
public void close() throws IOException {
lexer.close();
}
public long getCurrentLineNumber() {
return lexer.getCurrentLineNumber();
}
public Iterator<List<String>> iterator() {
return new Iterator<List<String>>() {
private List<String> current;
private List<String> getNextRow() throws IOException {
return CSVParser.this.nextRow();
}
@Override
public boolean hasNext() {
if (current == null) {
try {
current = getNextRow();
} catch (IOException e) {
logger.log(Level.FINE, e.getMessage(), e);
throw new NoSuchElementException(e.getMessage());
}
}
return current != null && !current.isEmpty();
}
@Override
public List<String> next() {
if (current == null || current.isEmpty()) {
throw new NoSuchElementException();
}
List<String> list = current;
current = null;
return list;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
protected List<String> nextRow() throws IOException {
row.clear();
StringBuilder sb = null;
do {
reusableCSVToken.reset();
lexer.nextToken(reusableCSVToken);
String s = reusableCSVToken.content.toString();
switch (reusableCSVToken.type) {
case TOKEN:
case EORECORD:
row.add(s);
break;
case EOF:
if (reusableCSVToken.isReady) {
row.add(s);
}
break;
case INVALID:
throw new IOException("(line " + getCurrentLineNumber() + ") invalid parse sequence");
case COMMENT:
if (sb == null) {
sb = new StringBuilder();
} else {
sb.append(CSVConstants.LF);
}
sb.append(reusableCSVToken.content);
reusableCSVToken.type = CSVToken.Type.TOKEN;
break;
default:
throw new IllegalStateException("unexpected token type: " + reusableCSVToken.type);
}
} while (reusableCSVToken.type == CSVToken.Type.TOKEN);
return row;
}
}

@ -0,0 +1,30 @@
package org.xbib.content.csv;
/**
*
*/
final class CSVToken {
private static final int INITIAL_TOKEN_LENGTH = 50;
enum Type {
INVALID,
TOKEN,
EOF,
EORECORD,
COMMENT
}
CSVToken.Type type = Type.INVALID;
StringBuilder content = new StringBuilder(INITIAL_TOKEN_LENGTH);
boolean isReady;
void reset() {
content.setLength(0);
type = Type.INVALID;
isReady = false;
}
}

@ -0,0 +1,4 @@
/**
* Classes for CSV content.
*/
package org.xbib.content.csv;

@ -0,0 +1,26 @@
package org.xbib.content.csv;
import org.junit.Test;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Arrays;
/**
*
*/
public class CSVGeneratorTest {
@Test
public void test() throws IOException {
StringWriter writer = new StringWriter();
CSVGenerator gen = new CSVGenerator(writer);
gen.keys(Arrays.asList("a", "b", "c"));
for (int i = 0; i < 10; i++) {
gen.write("val" + i);
gen.write("\"Hello, World\"");
gen.write("hey look a line seperator \n");
}
gen.close();
}
}

@ -0,0 +1,56 @@
package org.xbib.content.csv;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.text.MessageFormat;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
*/
public class CSVParserTest {
private static final Logger logger = Logger.getLogger(CSVParserTest.class.getName());
@Test
public void testCommaSeparated() throws IOException {
InputStream in = getClass().getResourceAsStream("test.csv");
int count = 0;
try (BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
CSVParser csvParser = new CSVParser(reader);
Iterator<List<String>> it = csvParser.iterator();
while (it.hasNext()) {
List<String> row = it.next();
//logger.log(Level.INFO, MessageFormat.format("count={0} row={1}", count, row));
count++;
}
}
assertEquals(2, count);
}
@Test
public void testLargeFile() throws IOException {
InputStream in = getClass().getResourceAsStream("titleFile.csv");
int count = 0;
try (BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
CSVParser csvParser = new CSVParser(reader);
Iterator<List<String>> it = csvParser.iterator();
while (it.hasNext()) {
List<String> row = it.next();
//logger.log(Level.INFO, MessageFormat.format("count={0} row={1}", count, row));
count++;
}
}
assertEquals(44447, count);
}
}

@ -0,0 +1,47 @@
package org.xbib.content.csv;
import org.junit.Test;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
/**
*
*/
public class TSVParserTest {
@Test
public void testTabSeparated() throws IOException {
InputStream in = getClass().getResourceAsStream("2076831-X-web.txt");
InputStreamReader r = new InputStreamReader(in, "UTF-8");
BufferedReader reader = new BufferedReader(r);
// skip 3 lines
reader.readLine();
reader.readLine();
reader.readLine();
String line;
while ((line = reader.readLine()) != null) {
String[] s = line.split("\\t");
//logger.info("len={} line={}", s.length, Arrays.asList(s));
int i = 0;
String sigel = i < s.length ? s[i++] : "";
String isil = i < s.length ? s[i++] : "";
String name = i < s.length ? s[i++] : ""; // unused
String code1 = i < s.length ? s[i++] : "";
String code2 = i < s.length ? s[i++] : "";
String code3 = i < s.length ? s[i++] : "";
String comment = i < s.length ? s[i++] : "";
String firstDate = i < s.length ? s[i++] : "";
String firstVolume = i < s.length ? s[i++] : "";
String firstIssue = i < s.length ? s[i++] : "";
String lastDate = i < s.length ? s[i++] : "";
String lastVolume = i < s.length ? s[i++] : "";
String lastIssue = i < s.length ? s[i++] : "";
String movingWall = i < s.length ? s[i] : "";
//logger.info("lastDate={}", lastDate);
}
}
}

@ -0,0 +1,4 @@
/**
* Classes for testing CSV content.
*/
package org.xbib.content.csv;

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="OFF">
<appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="[%d{ABSOLUTE}][%-5p][%-25c][%t] %m%n"/>
</Console>
</appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console" />
</Root>
</Loggers>
</configuration>

@ -0,0 +1,615 @@
ZDB-Id: 2076831-X
Treffer: 612
B 785 DE-B785 Berlin Abgeordnetenhaus k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Po 62 DE-Po62 Potsdam MPI Gravitationsphys. k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
B 33 DE-B33 Potsdam Astrophysik k n 1998 117 0 0 0
B 33 DE-B33 Potsdam Astrophysik k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Bv 2 DE-Bv2 Bremerhaven A.-Wegener-Inst. k p 2004 123 0 2012 131 0
Bv 2 DE-Bv2 Bremerhaven A.-Wegener-Inst. k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
D 161 DE-D161 Dresden Berufsakademie k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
D 275 DE-D275 Dresden EHS k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Gla 1 DE-Gla1 Glauchau Berufsakademie k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Hed 2 DE-Hed2 Heidenheim DHBW Bibliothek k p 2003 122 0 0 0
Hed 2 DE-Hed2 Heidenheim DHBW Bibliothek k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Hed 2 DE-Hed2 Heidenheim DHBW Bibliothek k p 1998 117 0 2014 133 0
Bn 3 DE-Bn3 Bautzen Berufsakademie k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Lör 2 DE-Loer2 Lörrach Berufsakademie k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Lör 2 DE-Loer2 Lörrach Berufsakademie k p 2003 122 0 0 0
B 43 DE-B43 Berlin BAM k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Mh 35 DE-Mh35 Mannheim Berufsakademie k p 2003 122 0 0 0
Mh 35 DE-Mh35 Mannheim Berufsakademie k p 1998 117 0 2014 133 0
Mh 35 DE-Mh35 Mannheim Berufsakademie k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
941 DE-941 Mosbach Duale Hochschule k p 2003 122 0 0 0
941 DE-941 Mosbach Duale Hochschule k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Rs 1 DE-Rs1 Riesa Berufsakademie k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Rav 1 DE-Rav1 Ravensburg Berufsakademie k p 2003 122 0 0 0
Rav 1 DE-Rav1 Ravensburg Berufsakademie k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Rav 1 DE-Rav1 Ravensburg Berufsakademie k p 1998 117 0 2014 133 0
Vil 2 DE-Vil2 Villingen-S. Berufsakad. k p 2003 122 0 0 0
Vil 2 DE-Vil2 Villingen-S. Berufsakad. k p 1998 117 0 2014 133 0
B 4 DE-B4 Berlin AdW <strong>B</strong> k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
H 140 DE-H140 Hamburg vTI, FIZ Fischerei k p 1857 1 1 0 0
H 140 DE-H140 Hamburg vTI, FIZ Fischerei k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
B 1512 DE-B1512 Bonn BfArM k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Ki 29 DE-Ki29 Kiel MRI, BID k p 1857 1 1 0 0
Ka 51 DE-Ka51 Karlsruhe MRI, BID k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Ku 1 DE-Ku1 Kulmbach MRI, BID k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Ka 51 DE-Ka51 Karlsruhe MRI, BID k p 1857 1 1 0 0
Det 2 DE-Det2 Detmold MRI, BID k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Ku 1 DE-Ku1 Kulmbach MRI, BID k p 1857 1 1 0 0
Ki 29 DE-Ki29 Kiel MRI, BID k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Det 2 DE-Det2 Detmold MRI, BID k p 1857 1 1 0 0
H 105 DE-H105 Hamburg vTI, FIZ Wald k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
H 105 DE-H105 Hamburg vTI, FIZ Wald k p 1857 1 1 0 0
208 DE-208 Karlsruhe BGH k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
B 12 DE-B12 Berlin BI Risikobewertung k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
B 12 DE-B12 Berlin BI Risikobewertung k p 1857 1 1 0 0
B 1509 DE-B1509 Bonn BI f. Berufsbildung k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Lan 1 DE-Lan1 Landau UB k p 1997 0 0 0 0
Lan 1 DE-Lan1 Landau UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Kob 7 DE-Kob7 Koblenz UB k p 1997 0 0 0 0
Kob 7 DE-Kob7 Koblenz UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
715 DE-715 Oldenburg IBIT k n 1997 116 0 0 0
715 DE-715 Oldenburg IBIT k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
31 DE-31 Karlsruhe LB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
12 DE-12 München BSB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
12 DE-12 München BSB k 1998 117 0 0 0
12 DE-12 München BSB k 2002 121 0 2003 122 0
82 DE-82 Aachen BTH k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Bo 408 DE-Bo408 Bonn Stiftung caesar k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
D 267 DE-D267 Dresden MPI Mol.Zellbiol. k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
D 210 DE-D210 Dresden MPI Chem. Physik k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
982 DE-982 Wadern IBF für Informatik WGL k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
M 491 DE-M491 München ArchäologInst k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Ha 2 DE-Ha2 Halle/S Dt. Akad. Naturfor. k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
B 1503 DE-B1503 Bonn Inst. Entwicklpol. k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Lg 3 DE-Lg3 Ludwigsburg Dt.-Franz. Inst. k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Y 7 DE-Y7 Paris DFK k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
90/8 DE-90-8 Fachbibliothek der Dualen Hochschule Baden-Württemberg Karlsruhe (FBD k p 2003 122 0 0 0
Po 6 DE-Po6 Nuthetal/Bergh.-Rehbr.DIfE WGL k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
B 1547 DE-B1547 Berlin InstMenschenrechte k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
B 554 DE-B554 Berlin DIW WGL k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Mar 1 DE-Mar1 Marbach Dt. Literaturarchiv k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
210 DE-210 München Deutsches Museum WGL k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
B 23 DE-B23 Offenbach Meteorolog. Bibl. k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
201 DE-201 München PatAmt k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
B 1531 DE-B1531 Berlin Dt. RheumaforschZ k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
1126 DE-1126 Idstein HS Fresenius k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
1126 DE-1126 Idstein HS Fresenius k p n 1998 117 0 0 0
1052 DE-1052 Nürnberg Evang. HS k 1998 117 0 0 0
521 DE-521 Frankfurt/O UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Dm 1 DE-Dm1 Dortmund MPI f. mol. Physiol. k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
H 256 DE-H256 Hamburg Führungsakad.Bundesw. k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
253 DE-253 Braunschweig vTI,FIZ LändlRäum k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
253 DE-253 Braunschweig vTI,FIZ LändlRäum k p 1857 1 1 0 0
90/4 DE-90-4 Karlsruhe KIT-B./FB TuW k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
90/4 DE-90-4 Karlsruhe KIT-B./FB TuW k p 2003 122 0 0 0
90/4 DE-90-4 Karlsruhe KIT-B./FB TuW k p 1998 117 0 2014 133 0
R 48 DE-R48 Dummerstorf Inst Nutztierbiol. k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Bo 133 DE-Bo133 Bonn F.-Ebert-Stiftung k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
944 DE-944 Aalen HS k p 1998 117 0 2014 133 0
944 DE-944 Aalen HS k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
944 DE-944 Aalen HS k p 2003 122 0 0 0
A 96 DE-A96 Aachen FHB k n 0 0 2014 0
A 96 DE-A96 Aachen FHB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
1043 DE-1043 Aschaffenburg HS k 1998 117 0 0 0
1102 DE-1102 Ansbach HS k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
1047 DE-1047 Weiden FH Amberg k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
1046 DE-1046 Amberg/Oberpfalz HS k 1998 117 0 0 0
1047 DE-1047 Weiden FH Amberg k 1998 117 0 0 0
1046 DE-1046 Amberg/Oberpfalz HS k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
949 DE-949 Biberach HBC k p 2003 122 0 0 0
949 DE-949 Biberach HBC k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
949 DE-949 Biberach HBC k p 1998 117 0 2014 133 0
858 DE-858 Coburg FH k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
858 DE-858 Coburg FH k 1998 117 0 0 0
Dm 13 DE-Dm13 Dortmund FH k n 0 0 2014 0
Dm 13 DE-Dm13 Dortmund FH k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
1010 DE-1010 Gelsenkirchen FH k n 0 0 2014 0
1010 DE-1010 Gelsenkirchen FH k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
1051 DE-1051 Hof HS k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Bi 10 DE-Bi10 Bielefeld FH k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Bi 10 DE-Bi10 Bielefeld FH k n 0 0 2014 0
573 DE-573 Ingolstadt HS k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
573 DE-573 Ingolstadt HS k 1998 117 0 0 0
832 DE-832 Köln FH k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
859 DE-859 Kempten FH k 1998 117 0 0 0
859 DE-859 Kempten FH k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
860 DE-860 Landshut FH k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
860 DE-860 Landshut FH k 1998 117 0 0 0
836 DE-836 Münster FH k n 0 0 2014 0
836 DE-836 Münster FH k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
1049 DE-1049 Neu-Ulm FH k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Ofb 1 DE-Ofb1 Offenburg HS k p 2003 122 0 0 0
Ofb 1 DE-Ofb1 Offenburg HS k p 1998 117 0 2014 133 0
Ofb 1 DE-Ofb1 Offenburg HS k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
898 DE-898 Regensburg HSBR k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
898 DE-898 Regensburg HSBR k 1998 117 0 0 0
522 DE-522 Brandenburg FH k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
861 DE-861 Rosenheim HSB k 1998 117 0 0 0
1044 DE-1044 St. Augustin FH k n 0 0 2014 0
1044 DE-1044 St. Augustin FH k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
862 DE-862 Schweinfurt FH Würzburg k 1998 117 0 0 0
863 DE-863 Würzburg FH k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
863 DE-863 Würzburg FH k 1998 117 0 0 0
862 DE-862 Schweinfurt FH Würzburg k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Dü 62 DE-Due62 Düsseldorf FH k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Dü 62 DE-Due62 Düsseldorf FH k n 0 0 2014 0
1050 DE-1050 Deggendorf HochschulB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
546 DE-546 Erfurt FH k p n 1998 117 1 0 0
546 DE-546 Erfurt FH k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
946 DE-946 Frankfurt/M FH k p n 1998 117 0 0 0
946 DE-946 Frankfurt/M FH k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
66 DE-66 Fulda HLB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
66 DE-66 Fulda HLB k p n 1998 117 0 0 0
Fn 1/VS DE-Fn1-VS Villingen-S. HS Furtwangen k p 2003 122 0 0 0
Fn 1/VS DE-Fn1-VS Villingen-S. HS Furtwangen k p 1998 117 0 2014 133 0
Fn 1 DE-Fn1 Furtwangen HS k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Fn 1/TUT DE-Fn1-TUT Tuttlingen HS Furtwangen k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Fn 1 DE-Fn1 Furtwangen HS k p 2003 122 0 0 0
Fn 1/VS DE-Fn1-VS Villingen-S. HS Furtwangen k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Fn 1 DE-Fn1 Furtwangen HS k p 1998 117 0 2014 133 0
Fn 1/TUT DE-Fn1-TUT Tuttlingen HS Furtwangen k p 2003 122 0 0 0
Fn 1/TUT DE-Fn1-TUT Tuttlingen HS Furtwangen k p 1998 117 0 2014 133 0
974 DE-974 Gießen FH k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
974 DE-974 Gießen FH k p n 1998 117 0 0 0
960/1 DE-960-1 Hannover HS ZentralB Linden k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
960/1 DE-960-1 Hannover HS ZentralB Linden k n 1997 116 0 0 0
840 DE-840 Heilbronn HS k p 2003 122 0 0 0
840 DE-840 Heilbronn HS k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
840 DE-840 Heilbronn HS k p 1998 117 0 2014 133 0
B 113 DE-B113 Berlin F.-Haber-Inst. k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
J 59 DE-J59 Jena FH k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Ki 95 DE-Ki95 Kiel FH k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Ki 95 DE-Ki95 Kiel FH k p n 1998 117 1 0 0
Kon 4 DE-Kon4 Konstanz HS Techn.Wirt.Gestalt k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Kon 4 DE-Kon4 Konstanz HS Techn.Wirt.Gestalt k p 2003 122 0 0 0
Kon 4 DE-Kon4 Konstanz HS Techn.Wirt.Gestalt k p 1998 117 0 2014 133 0
1147 DE-1147 Ludwigsburg HVF k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
1147 DE-1147 Ludwigsburg HVF k p 2003 122 0 0 0
743 DE-743 Lemgo HS OWL S(KIM) k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
743 DE-743 Lemgo HS OWL S(KIM) k n 0 0 2014 0
M 347 DE-M347 München HMB k 1998 117 0 0 0
M 347 DE-M347 München HMB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
953 DE-953 Mannheim Hochschule k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
953 DE-953 Mannheim Hochschule k p 1998 117 0 2014 133 0
953 DE-953 Mannheim Hochschule k p 2003 122 0 0 0
542 DE-542 Merseburg FH k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
542 DE-542 Merseburg FH k p n 1998 117 1 0 0
92 DE-92 Nürnberg Ohm-HSB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
92 DE-92 Nürnberg Ohm-HSB k 1998 117 0 0 0
519 DE-519 Neubrandenburg HS k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
755 DE-755 Emden FH Emden/Leer k n 1997 116 0 0 0
755 DE-755 Emden FH Emden/Leer k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
959 DE-959 Osnabrück HS ZentralB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
959 DE-959 Osnabrück HS ZentralB k n 1997 116 0 0 0
525 DE-525 Potsdam FH k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Rt 2 DE-Rt2 Reutlingen HSB k p 1998 117 0 2014 133 0
Rt 2 DE-Rt2 Reutlingen HSB k p 2003 122 0 0 0
Rt 2 DE-Rt2 Reutlingen HSB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Shm 2 DE-Shm2 Schmalkalden FH k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
943 DE-943 Ulm Hochschule k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
943 DE-943 Ulm Hochschule k p 1998 117 0 2014 133 0
943 DE-943 Ulm Hochschule k p 2003 122 0 0 0
1029 DE-1029 Triesdorf HSWT k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
1028 DE-1028 Freising HSWT k 1998 117 0 0 0
1028 DE-1028 Freising HSWT k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
1029 DE-1029 Triesdorf HSWT k 1998 117 0 0 0
916 DE-916 Wolfenbüttel FH k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
916 DE-916 Wolfenbüttel FH k n 1997 116 0 0 0
1117 DE-1117 Worms FH k p 1997 0 0 0 0
1117 DE-1117 Worms FH k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
897/1 DE-897-1 Elsfleth Jade Hochschule k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
839 DE-839 Wilhelmshaven Jade Hochschule k n 1997 116 0 0 0
897 DE-897 Oldenburg Jade Hochschule k n 1997 116 0 0 0
897/1 DE-897-1 Elsfleth Jade Hochschule k n 1997 116 0 0 0
839 DE-839 Wilhelmshaven Jade Hochschule k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
897 DE-897 Oldenburg Jade Hochschule k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Stg 191 DE-Stg191 Stuttgart FHG:IAO k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
188/e DE-188-e Berlin FU E-Medien k p 1998 117 0 0 0
188/e DE-188-e Berlin FU E-Medien k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
188 DE-188 Berlin UBFU k p 1998 117 0 0 0
188 DE-188 Berlin UBFU k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
708 DE-708 Hagen FernUB k n 0 0 2014 0
708 DE-708 Hagen FernUB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Ki 131 DE-Ki131 Kiel FA Bundesw.f.Wasserschall k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Jül 1 DE-Juel1 Jülich ForschZ k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Jül 1 DE-Juel1 Jülich ForschZ k p 2003 122 0 0 0
Ka 85 DE-Ka85 Karlsruhe KIT Campus Nord k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
D 120 DE-D120 Dresden HZDR k p 2004 123 0 2012 131 0
D 120 DE-D120 Dresden HZDR k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Bs 80 DE-Bs80 Braunschweig HZI k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Bs 80 DE-Bs80 Braunschweig HZI k p 2004 123 0 2012 131 0
Bs 78 DE-Bs78 Braunschweig Georg-Eckert-Inst k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
H 221 DE-H221 Hamburg GIGA FB Afrika k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
H 222 DE-H222 Hamburg GIGA FB Asien k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Gt 1 DE-Gt1 Geesthacht HZG k p 2004 123 0 2012 131 0
Gt 1 DE-Gt1 Geesthacht HZG k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Ds 200 DE-Ds200 Darmstadt GSI k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
35 DE-35 Hannover NLB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
185 DE-185 Leipzig Inst. Länderkunde WGL k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
18/285 DE-18-285 Hamburg FB Design k p n 1998 117 1 0 0
18/287 DE-18-287 Hamburg FB Soziale Arbeit k p n 1998 117 1 0 0
18/302 DE-18-302 Hamburg HAW FB Technik Wirtsch k p n 1998 117 1 0 0
18/284 DE-18-284 Hamburg FB Life Sciences k p n 1998 117 1 0 0
Hil 3/1 DE-Hil3-1 Göttingen HAWK Bibliothek N k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Hil 3/1 DE-Hil3-1 Göttingen HAWK Bibliothek N k n 1997 116 0 0 0
753 DE-753 Esslingen HS k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
972 DE-972 Göppingen HS Esslingen k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
753 DE-753 Esslingen HS k p 1998 117 0 2014 133 0
972 DE-972 Göppingen HS Esslingen k p 1998 117 0 2014 133 0
753 DE-753 Esslingen HS k p 2003 122 0 0 0
972 DE-972 Göppingen HS Esslingen k p 2003 122 0 0 0
551 DE-551 Magdeburg FHB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
551 DE-551 Magdeburg FHB k p 1998 117 1 0 0
747 DE-747 Weingarten HSB k p 2003 122 0 0 0
747 DE-747 Weingarten HSB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
747 DE-747 Weingarten HSB k p 1998 117 0 2014 133 0
1373 DE-1373 Hamburg HC,Uni Baukunst k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Fl 3 DE-Fl3 Flensburg UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
958 DE-958 Stuttgart HdM Nobelstraße k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
958 DE-958 Stuttgart HdM Nobelstraße k p 2003 122 0 0 0
Y 2 DE-Y2 Rom Hertziana MPI k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Po 75 DE-Po75 Potsdam Filmuniversität k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
1866 DE-1866 Bochum HSG k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
1866 DE-1866 Bochum HSG k n 0 0 2014 0
955 DE-955 Rottenburg HS Forstw k p 1998 117 0 2014 133 0
955 DE-955 Rottenburg HS Forstw k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
955 DE-955 Rottenburg HS Forstw k p 2003 122 0 0 0
1033 DE-1033 Stuttgart HS Technik k p 1998 117 0 2014 133 0
1033 DE-1033 Stuttgart HS Technik k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
1033 DE-1033 Stuttgart HS Technik k p 2003 122 0 0 0
950 DE-950 Nürtingen HS Wirts.Umwelt k p 2003 122 0 0 0
1090 DE-1090 Geislingen HS Wirtsch.Nürt.-G. k p 2003 122 0 0 0
950 DE-950 Nürtingen HS Wirts.Umwelt k p 1998 117 0 2014 133 0
950 DE-950 Nürtingen HS Wirts.Umwelt k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
1090 DE-1090 Geislingen HS Wirtsch.Nürt.-G. k p 1998 117 0 2014 133 0
1090 DE-1090 Geislingen HS Wirtsch.Nürt.-G. k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
43 DE-43 Wiesbaden LB k p n 1998 117 0 0 0
43 DE-43 Wiesbaden LB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
B 1505 DE-B1505 Berlin Helmholtz-Zentrum k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
B 1505 DE-B1505 Berlin Helmholtz-Zentrum k p 2004 123 0 2012 131 0
L 152 DE-L152 Leipzig HS Musik k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
M 29 DE-M29 München, HS f. Musik u.Theater k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
1393 DE-1393 Mülheim/Ruhr HS Ruhr West k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
991 DE-991 Sigmaringen HS Albstadt-Sigmar k p 1998 117 0 2014 133 0
991 DE-991 Sigmaringen HS Albstadt-Sigmar k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
991 DE-991 Sigmaringen HS Albstadt-Sigmar k p 2003 122 0 0 0
Kt 1 DE-Kt1 Köthen FH k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
751 DE-751 Karlsruhe HSB k p 1998 117 0 2014 133 0
751 DE-751 Karlsruhe HSB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
751 DE-751 Karlsruhe HSB k p 2003 122 0 0 0
747 DE-747 Weingarten HSB k p 1998 117 0 2014 133 0
747 DE-747 Weingarten HSB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
747 DE-747 Weingarten HSB k p 2003 122 0 0 0
B 1570 DE-B1570 Berlin HertieSchool k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
527 DE-527 Wernigerode HS Harz k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Wis 1 DE-Wis1 Wismar HS k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
829 DE-829 Mönchengladbach HS Niederrhein k n 0 0 2014 0
829 DE-829 Mönchengladbach HS Niederrhein k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
523 DE-523 Berlin HTW k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
520 DE-520 Dresden HS TuW k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
L 189 DE-L189 Leipzig FH HTWK k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Mit 1 DE-Mit1 Mittweida HS k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Sa 16 DE-Sa16 Saarbrücken HTW/Goebenstr k p 1997 0 0 0 0
Sa 16 DE-Sa16 Saarbrücken HTW/Goebenstr k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
206 H DE-206H Hamburg ZBW WGL k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Zi 4 DE-Zi4 Zittau/Görlitz HS k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
N 38 DE-N38 Nürnberg Fachb.Arbeitsmarktfor k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Mh 39 DE-Mh39 Mannheim IDS WGL k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Dm 21 DE-Dm21 Dortmund IfADo WGL k n 1998 117 0 0 0
Dm 21 DE-Dm21 Dortmund IfADo WGL k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Ki 109 DE-Ki109 Kiel GEOMAR Bibl.West k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Ki 130 DE-Ki130 Kiel GEOMAR Bibl.Ost k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
M 352 DE-M352 München Inst.f.Zeitgeschichte k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
B 259 DE-B259 Berlin Gewässerökologie WGL k n 1998 117 0 0 0
B 259 DE-B259 Berlin Gewässerökologie WGL k n 1998 117 0 0 0
B 259 DE-B259 Berlin Gewässerökologie WGL k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
291/415 DE-291-415 Saarbrücken INM WGL k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Wa 1 DE-Wa1 Rostock Inst.f.Ostseefor. WGL k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Stg 183 DE-Stg183 Stuttgart Fraunhofer IPA k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Ha 93 DE-Ha93 Halle/S Pflanzenbiochemie k n 1998 117 0 0 0
Ha 93 DE-Ha93 Halle/S Pflanzenbiochemie k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Ha 93 DE-Ha93 Halle/S Pflanzenbiochemie k n 1998 117 0 0 0
Gat 1 DE-Gat1 Gatersleben Pflanzengenetik k n 1998 117 0 0 0
Gat 1 DE-Gat1 Gatersleben Pflanzengenetik k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
M 382 DE-M382 München MPIs Im.GüterR/SteuerR k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Ki 128 DE-Ki128 Kiel IPN k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
1826 DE-1826 Berlin Bibliothek der IPU k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
B 567 DE-B567 Erkner IRS WGL k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
918 DE-918 St.Augustin K-Adenauer-Stiftg. k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Ha 125 DE-Ha125 Halle/S HS Kunst k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Y 3 DE-Y3 Florenz Kunsthist. Inst. k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
1032 DE-1032 Köln Kath. HS k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
90 DE-90 Karlsruhe KIT-Bibliothek k p 1857 1 1 0 0
Ka 85 DE-Ka85 Karlsruhe KIT Campus Nord k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Ka 93 DE-Ka93 Karlsruher Institut für Technologie, KIT-Archiv k p 1998 117 0 2014 133 0
90 DE-90 Karlsruhe KIT-Bibliothek k p 2003 122 0 0 0
Ka 85 DE-Ka85 Karlsruhe KIT Campus Nord k p 1857 1 1 0 0
Ka 93 DE-Ka93 Karlsruher Institut für Technologie, KIT-Archiv k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Ka 85 DE-Ka85 Karlsruhe KIT Campus Nord k p 2003 122 0 0 0
Ka 93 DE-Ka93 Karlsruher Institut für Technologie, KIT-Archiv k p 1857 1 1 0 0
Ka 93 DE-Ka93 Karlsruher Institut für Technologie, KIT-Archiv k p 2003 122 0 0 0
90 DE-90 Karlsruhe KIT-Bibliothek k p 1998 117 0 2014 133 0
90 DE-90 Karlsruhe KIT-Bibliothek k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Ka 85 DE-Ka85 Karlsruhe KIT Campus Nord k p 1998 117 0 2014 133 0
70 DE-70 Coburg LB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
33 DE-33 Schwerin LBMV k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
45 DE-45 Oldenburg LB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Ma 45 DE-Ma45 Magdeburg Inst. Neurobiologie k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
51 DE-51 Detmold LB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
578/M DE-578-M Berlin Charité Med.Bib.Magazin k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
578/821 DE-578-821 Berlin Charité Med.Bib. ZMK k n 1857 1 1 0 0
578/M DE-578-M Berlin Charité Med.Bib.Magazin k n 1857 1 1 0 0
578/e DE-578-e Berlin Charité Med.Bibl.eZsn k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
578/3 DE-578-3 Berlin Charité Med. Bibl. CVK k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
578/e DE-578-e Berlin Charité Med.Bibl.eZsn k n 1857 1 1 0 0
578/821 DE-578-821 Berlin Charité Med.Bib. ZMK k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
578/3 DE-578-3 Berlin Charité Med. Bibl. CVK k n 1857 1 1 0 0
Hag 4/4 DE-Hag4-4 Soest FH Südwestfalen k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Hag 4/2 DE-Hag4-2 Iserlohn FH Südwestfalen k n 0 0 2014 0
Hag 4/E DE-Hag4-E Hagen FH Südwestf.E-Ressourcen k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Hag 4/4 DE-Hag4-4 Soest FH Südwestfalen k n 0 0 2014 0
Hag 4/3 DE-Hag4-3 Meschede FH Südwestfalen k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Hag 4/E DE-Hag4-E Hagen FH Südwestf.E-Ressourcen k n 0 0 2014 0
Hag 4 DE-Hag4 Hagen FH Südwestfalen k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Hag 4/3 DE-Hag4-3 Meschede FH Südwestfalen k n 0 0 2014 0
Hag 4/2 DE-Hag4-2 Iserlohn FH Südwestfalen k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Hag 4 DE-Hag4 Hagen FH Südwestfalen k n 0 0 2014 0
B 16 DE-B16 Berlin Museum für Naturkunde k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Frei 3c DE-Frei3c Oberwolfach Math. FI k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
354 DE-354 Hannover MedHS k n 1997 116 0 0 0
354 DE-354 Hannover MedHS k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Trs 1 DE-Trs1 Trossingen HS Musik k p 2003 122 0 0 0
B 787 DE-B787 Berlin MPI Molek.Genetik k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Lin 1b DE-Lin1b Katlenburg MPI Sonnensystem k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
B 161a DE-B161a Garching AstroB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Frei 85 DE-Frei85 Freiburg MPI Ausländ.Recht k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Star 1 DE-Star1 Seewiesen MPI Ornithologie k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
2177 DE-2177 Frankfurt/M MPI f.emp.Ästhetik k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
He 81 DE-He81 Heidelberg MPI f. Astronomie k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
B 1532 DE-B1532 Berlin MPI Bildungsforschung k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Gö 116 DE-Goe116 Göttingen Otto-Hahn-B k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Mz 4 DE-Mz4 Mainz MPI Chemie k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Ma 54 DE-Ma54 Magdeburg MPI DKTS k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Dü 57 DE-Due57 Düsseldorf MPI Eisenforschung k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Ha 163 DE-Ha163 Halle/S. MPI Ethnol. Forschung k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Gö 134 DE-Goe134 Göttingen MPI exp. Med. k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
F 137 DE-F137 Frankfurt/M MPI Rechtgesch. k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
J 152 DE-J152 Jena MPI Menschheitsgeschichte k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Kn 188 DE-Kn188 Köln MPI Gesellschaftsforsch. k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Bo 146 DE-Bo146 Bonn MPI Radioastronomie k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Gö 164 DE-Goe164 Göttingen MPI Erforsch.multi. k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Sa 18 DE-Sa18 Saarbrücken MPI Informatik k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Frei 106 DE-Frei106 Freiburg MPI f. Immunbiologie k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Po 81 DE-Po81 Potsdam MPI Grenzflächenforsch k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
He 74 DE-He74 Heidelberg MPI Kernphysik k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Pn 1 DE-Pn1 Plön MPI Evolutionsbiologie k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Bo 206 DE-Bo206 Bonn MPI Mathematik k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
He 43 DE-He43 Heidelberg MPI Med.Forschung k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Bre 11 DE-Bre11 Bremen MPI Marine Mikrobiol. k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
L 323 DE-L323 Leipzig MPI Mathematik Naturw. k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Po 80 DE-Po80 Potsdam MPI Pflanzenphysiolog. k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Ha 94 DE-Ha94 Halle/S MPI Mikrostrukturph. k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
L 322 DE-L322 Leipzig MPI Neurowiss. k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Mz 116 DE-Mz116 Mainz MPI Polymerforschung k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Nau 1 DE-Nau1 Bad Nauheim MPI Herz/Lungen k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Y 4 DE-Y4 Nijmegen MPI Psycholinguistik k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
M 359 DE-M359 Garching MPI Plasmaphysik k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
B 212 DE-B212 Hamburg MPI IntPrivatrecht k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
M 477 DE-M477 München MPI Sozialrecht k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
B 107 DE-B107 Tübingen MP-Haus k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Mb 105 DE-Mb105 Marburg MPI f. terre Mikrob. k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
B 208 DE-B208 Heidelberg MPI Völkerrecht k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Vol 1 DE-Vol1 Köln MPI Züchtungsforschung k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Bo 407 DE-Bo407 Bonn MPI Gemeinschaftsgüter k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
M 484 DE-M484 Garching MPI Quantenoptik k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Mh 31 DE-Mh31 Mannheim HS Musik k p 2003 122 0 0 0
1933 DE-1933 Mannheim Popakademie k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Frei 129 DE-Frei129 Freiburg PH k p 2003 122 0 0 0
Frei 129 DE-Frei129 Freiburg PH k p 1998 117 0 2014 133 0
Frei 129 DE-Frei129 Freiburg PH k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
752 DE-752 Schwäbisch Gmünd PH k p 2003 122 0 0 0
752 DE-752 Schwäbisch Gmünd PH k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
He 76 DE-He76 Heidelberg PH k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
He 76 DE-He76 Heidelberg PH k p 1998 117 0 2014 133 0
He 76 DE-He76 Heidelberg PH k p 2003 122 0 0 0
Lg 1 DE-Lg1 Ludwigsburg PH k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Lg 1 DE-Lg1 Ludwigsburg PH k p 2003 122 0 0 0
D 206 DE-D206 Dresden MPI Phys.komplex.Syst. k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
107 DE-107 Speyer Pfälzische LB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
107 DE-107 Speyer Pfälzische LB k p 1997 0 0 0 0
B 106 DE-B106 Berlin R. Koch-Inst k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
929 DE-929 Koblenz LB k p 1997 0 0 0 0
929 DE-929 Koblenz LB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
294 DE-294 Bochum UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
36 DE-36 Mainz StBi k p 1997 0 0 0 0
36 DE-36 Mainz StBi k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
150 DE-150 Neuburg/Donau SB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
14 DE-14 Dresden SLUB, ZB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Stg 265 DE-Stg265 Stuttgart Mus.f.Naturkunde k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
L 229 DE-L229 Leipzig Berufsakademie k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
155 DE-155 Regensburg SB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
282 DE-282 Wiesbaden StaBA k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
7 DE-7 Göttingen SUB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
7 DE-7 Göttingen SUB k n 1998 117 0 0 0
18 DE-18 Hamburg SUB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
18 DE-18 Hamburg SUB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1998 117 1 0 0
291 DE-291 Saarbrücken SULB k p 1997 0 0 0 0
291 DE-291 Saarbrücken SULB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
37 DE-37 Augsburg SuStB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
B 768 DE-B768 Berlin Technische FH k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
B 768 DE-B768 Berlin Technische FH k n 1857 1 1 0 0
526 DE-526 Wildau TH k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
27 DE-27 Jena UuLB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
95 DE-95 Hannover TierHS k n 1997 116 0 0 0
95 DE-95 Hannover TierHS k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Wim 7 DE-Wim7 Weimar TLDA k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
83 DE-83 Berlin UBTU k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
105 DE-105 Freiberg TU BA k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
84 DE-84 Braunschweig UB k n 1997 116 0 0 0
84 DE-84 Braunschweig UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Co 1 DE-Co1 Cottbus BTU k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Ch 1 DE-Ch1 Chemnitz UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
17 DE-17 Darmstadt ULB k p n 1998 117 0 0 0
17 DE-17 Darmstadt ULB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
830 DE-830 Hamburg TU k p Nur für den persönlichen Gebrauch oder für wissenschaftliche, Bildungs- oder Forschungszwecke, nicht jedoch zu gewerblichen Zwecken. 1998 117 1 0 0
830 DE-830 Hamburg TU k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
91 S DE-91S München TU/TeilB Straubing k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
M 49 DE-M49 Freising TU München/Weihenst k 1998 117 0 0 0
M 49 DE-M49 Freising TU München/Weihenst k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
91 DE-91 München UBTU k 1998 117 0 0 0
91 G DE-91G München TU/TeilB Garching k 1998 117 0 0 0
91 DE-91 München UBTU k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
91 S DE-91S München TU/TeilB Straubing k 1998 117 0 0 0
91 G DE-91G München TU/TeilB Garching k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
384 DE-384 Augsburg UB k 1998 117 0 0 0
384 DE-384 Augsburg UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
473 DE-473 Bamberg UB k 1998 117 0 0 0
473 DE-473 Bamberg UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
703 DE-703 Bayreuth UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
703 DE-703 Bayreuth UB k 1998 117 0 0 0
361 DE-361 Bielefeld UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
361 DE-361 Bielefeld UB k n 0 0 2014 0
Wim 2 DE-Wim2 Weimar UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
104 DE-104 Clausthal-Z. UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
104 DE-104 Clausthal-Z. UB k n 1997 116 0 0 0
290 DE-290 Dortmund UB k n 0 0 2014 0
290 DE-290 Dortmund UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
464 DE-464 Duisburg UB k n 0 0 2014 0
464 DE-464 Duisburg UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
824 DE-824 Eichstätt UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
945 DE-945 Ingolstadt UB Eichstätt/Wirt. k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
N 2 DE-N2 Nürnberg WSZB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
29 DE-29 Erlangen-N UB k 1998 117 0 0 0
N 32 DE-N32 Nürnberg UB Erlangen-N/Erzwiss k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
N 2 DE-N2 Nürnberg WSZB k 1998 117 0 0 0
29 T DE-29T Erlangen-N UB Technik/Naturw. k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
N 32 DE-N32 Nürnberg UB Erlangen-N/Erzwiss k 1998 117 0 0 0
29 T DE-29T Erlangen-N UB Technik/Naturw. k 1998 117 0 0 0
29 DE-29 Erlangen-N UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
F 21 DE-F21 Frankfurt/M Med. HauptBi k p n 1998 117 0 0 0
30 DE-30 Frankfurt/M UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
F 21 DE-F21 Frankfurt/M Med. HauptBi k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
30 DE-30 Frankfurt/M UB k p n 1998 117 0 0 0
25 DE-25 Freiburg UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
25 DE-25 Freiburg UB k p 2003 122 0 0 0
25 DE-25 Freiburg UB k p 1998 117 0 2014 133 0
26 DE-26 Gießen UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
26 DE-26 Gießen UB k p n 1998 117 0 0 0
9 DE-9 Greifswald UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
16 DE-16 Heidelberg UB k p 2003 122 0 0 0
16 DE-16 Heidelberg UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
16 DE-16 Heidelberg UB k p 1998 117 0 2014 133 0
Hil 2 DE-Hil2 Hildesheim UB k n 1997 116 0 0 0
Hil 2 DE-Hil2 Hildesheim UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
841 DE-841 Lübeck ZHB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
100 DE-100 Stuttgart-Hoh. KIM k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
100 DE-100 Stuttgart-Hoh. KIM k p 2003 122 0 0 0
11/10 DE-11-10 Berlin UBHU, Elektron. Res. k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
11/10 DE-11-10 Berlin UBHU, Elektron. Res. k n 1857 1 1 0 0
11 DE-11 Berlin UB Humboldt k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
11 DE-11 Berlin UB Humboldt k n 1857 1 1 0 0
Ilm 1 DE-Ilm1 Ilmenau UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
8 DE-8 Kiel UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
8 DE-8 Kiel UB k p n 1998 117 1 0 0
34 DE-34 Kassel UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
34 DE-34 Kassel UB k p n 1998 117 0 0 0
386 DE-386 Kaiserslautern UB k p 1997 0 0 0 0
386 DE-386 Kaiserslautern UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
352 DE-352 Konstanz UB k p 1998 117 0 2014 133 0
352 DE-352 Konstanz UB k p 2003 122 0 0 0
352 DE-352 Konstanz UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
15 DE-15 Leipzig UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
15/292 DE-15-292 Leipzig ZB Medizin k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Lün 4 DE-Luen4 Lüneburg UB k n 1997 116 0 0 0
Lün 4 DE-Luen4 Lüneburg UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
19 DE-19 München UB k 1998 117 0 0 0
19 DE-19 München UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
4 DE-4 Marburg UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
4 DE-4 Marburg UB k p n 1998 117 0 0 0
180 DE-180 Mannheim UB k p 2003 122 0 0 0
180 DE-180 Mannheim UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
180 DE-180 Mannheim UB k p 1998 117 0 2014 133 0
Ma 9 DE-Ma9 Magdeburg UB k p 1857 1 1 0 0
Ma 9 DE-Ma9 Magdeburg UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
77 DE-77 Mainz UB k p n 1998 117 0 0 0
Mz 19 DE-Mz19 Mainz FB TSK k p n 1998 117 0 0 0
77 DE-77 Mainz UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Mz 19 DE-Mz19 Mainz FB TSK k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
700 DE-700 Osnabrück UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
700 DE-700 Osnabrück UB k n 1997 116 0 0 0
739 DE-739 Passau UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
739 DE-739 Passau UB k 1998 117 0 0 0
466 DE-466 Paderborn UB k n 0 0 2014 0
466 DE-466 Paderborn UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
517 DE-517 Potsdam UB k n 1857 1 1 0 0
517 DE-517 Potsdam UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
355 DE-355 Regensburg UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
355 DE-355 Regensburg UB k 1998 117 0 0 0
28 DE-28 Rostock UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
93 DE-93 Stuttgart UB k p 2003 122 0 0 0
93 DE-93 Stuttgart UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
93 DE-93 Stuttgart UB k p 1998 117 0 2014 133 0
467 DE-467 Siegen UB k n 0 0 2014 0
467 DE-467 Siegen UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
89 DE-89 Hannover TIB/UB k n 1997 116 0 0 0
89 DE-89 Hannover TIB/UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
385 DE-385 Trier UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
385 DE-385 Trier UB k p 1997 0 0 0 0
21 DE-21 Tübingen UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
21 DE-21 Tübingen UB k p 2003 122 0 0 0
21 DE-21 Tübingen UB k p 1998 117 0 2014 133 0
289 DE-289 Ulm UB k p 1998 117 0 2014 133 0
289 DE-289 Ulm UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
289 DE-289 Ulm UB k p 2003 122 0 0 0
Va 1 DE-Va1 Vechta UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Va 1 DE-Va1 Vechta UB k n 1997 116 0 0 0
20 DE-20 Würzburg UB k keine Fernleihe an kommerzielle Bibliotheken liefern 2002 121 1 2003 122 4
20 DE-20 Würzburg UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
705 DE-705 Hamburg HSU k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
706 DE-706 Neubiberg UniBundeswehr k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
468 DE-468 Wuppertal UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
468 DE-468 Wuppertal UB k n 0 0 2014 0
L 97 DE-L97 Leipzig Helmholtz-Zentrum UFZ k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
L 97 DE-L97 Leipzig Helmholtz-Zentrum UFZ k p 2004 123 0 2012 131 0
465 DE-465 Essen UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
464 DE-464 Duisburg UB k n 0 0 2014 0
464 DE-464 Duisburg UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
465 M DE-465M Essen Duisburg-Essen FB Med. k n 0 0 2014 0
465 M DE-465M Essen Duisburg-Essen FB Med. k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
465 DE-465 Essen UB k n 0 0 2014 0
18/64 DE-18-64 Hamburg Ärztl. ZB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1998 117 1 0 0
18/64 DE-18-64 Hamburg Ärztl. ZB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
5 DE-5 Bonn ULB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
61 DE-61 Düsseldorf UuLB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
61 DE-61 Düsseldorf UuLB k n 0 0 2014 0
3 DE-3 Halle/S UuLB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
6 DE-6 Münster UuLB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
6 DE-6 Münster UuLB k n 0 0 2014 0
1018 DE-1018 Witten UB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
38 DE-38 Köln USB k n 0 0 2014 0
38 DE-38 Köln USB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Zwi 2 DE-Zwi2 Zwickau HS k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
B 513 DE-B513 Berlin Weierstraß-Inst. k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
24 DE-24 Stuttgart WLB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
B 1543 DE-B1543 Berlin WZB WGL k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
38 M DE-38M Köln ZBMedizin k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
98 DE-98 Bonn ZB MED k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
38 M DE-38M Köln ZBMedizin k p n non-commercial libraries<br> (Springer) 1857 1 1 0 0
98 DE-98 Bonn ZB MED k p n non-commercial libraries<br> (Springer) 1857 1 1 0 0
Kn 41 DE-Kn41 Köln SportHS ZB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Kn 41 DE-Kn41 Köln SportHS ZB k p 1857 1 1 2011 130 6
206 DE-206 Kiel ZBW WGL k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Mh 36 DE-Mh36 Mannheim ZEW k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
255 DE-255 München ZI Kunstgeschichte k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Mh 32 DE-Mh32 Mannheim ZI Seel. Gesundh. k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
109 DE-109 Berlin ZLB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
B 2138 DE-B2138 Berlin Zentrum Modern. Orient k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Bre 14 DE-Bre14 Bremen Zentr. Tropenökologie k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Bre 14 DE-Bre14 Bremen Zentr. Tropenökologie k n 1998 117 0 0 0
Bre 14 DE-Bre14 Bremen Zentr. Tropenökologie k n 1998 117 0 0 0
B 1536 DE-B1536 Berlin ZIB k n 1857 1 1 0 0
B 1536 DE-B1536 Berlin ZIB k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0
Po 82 DE-Po82 Potsdam Zeithist.Forsch. k p n keine Fernleihe an kommerzielle Bibliotheken liefern 1857 1 0 2002 121 0

@ -0,0 +1,27 @@
Credits
org.xbib.json.jackson is originally based on com.github.fge.jackson
org.xbib.json.pointer is originally based on com.github.fge.jackson,jsonpointer
org.xbib.json.patch is originally bases on com.github.fge.jsonpatch
Original copyright notice:
/*
* Copyright (c) 2014, Francis Galiegue (fgaliegue@gmail.com)
*
* This software is dual-licensed under:
*
* - the Lesser General Public License (LGPL) version 3.0 or, at your option, any
* later version;
* - the Apache Software License (ASL) version 2.0.
*
* The text of this file and of both licenses is available at the root of this
* project or, if you have the jar distribution, in directory META-INF/, under
* the names LGPL-3.0.txt and ASL-2.0.txt respectively.
*
* Direct link to the sources:
*
* - LGPL 3.0: https://www.gnu.org/licenses/lgpl-3.0.txt
* - ASL 2.0: http://www.apache.org/licenses/LICENSE-2.0.txt
*/

@ -0,0 +1,10 @@
dependencies {
compile "com.fasterxml.jackson.core:jackson-databind:2.8.3"
testCompile('junit:junit:4.12') {
exclude group: 'org.hamcrest'
}
testCompile('org.mockito:mockito-core:1.9.5') {
exclude group: 'org.hamcrest'
}
testCompile 'org.hamcrest:hamcrest-all:1.3'
}

@ -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,junit,net,org,java,javax"/>
<!-- This ensures that static imports go first. -->
<property name="option" value="top"/>
<property name="tokens" value="STATIC_IMPORT, 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>

@ -0,0 +1,159 @@
package org.xbib.content.json.diff;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.xbib.content.json.jackson.JsonNumEquals;
import org.xbib.content.json.jackson.Wrapper;
import org.xbib.content.json.pointer.JsonPointer;
import java.util.Arrays;
import java.util.Objects;
/**
* Difference representation. Captures diff information required to
* generate JSON patch operations and factorize differences.
*/
final class Diff {
final JsonNode value;
DiffOperation operation;
JsonPointer path;
JsonPointer arrayPath;
int firstArrayIndex;
int secondArrayIndex;
JsonPointer fromPath;
Diff pairedDiff;
boolean firstOfPair;
private Diff(final DiffOperation operation, final JsonPointer path,
final JsonNode value) {
this.operation = operation;
this.path = path;
this.value = value;
}
private Diff(final DiffOperation operation, final JsonPointer arrayPath,
final int firstArrayIndex, final int secondArrayIndex,
final JsonNode value) {
this.operation = operation;
this.arrayPath = arrayPath;
this.firstArrayIndex = firstArrayIndex;
this.secondArrayIndex = secondArrayIndex;
this.value = value;
}
static Diff simpleDiff(final DiffOperation operation,
final JsonPointer path, final JsonNode value) {
return new Diff(operation, path, value.deepCopy());
}
/*
* "Stateless" removal of a given node from an array given a base path (the
* immediate parent of an array) and an array index; as the name suggests,
* this factory method is called only when a node is removed from the tail
* of a target array; in other words, the source node has extra elements.
*/
static Diff tailArrayRemove(final JsonPointer basePath, final int index,
final int removeIndex, final JsonNode victim) {
return new Diff(DiffOperation.REMOVE, basePath, index, removeIndex,
victim.deepCopy());
}
static Diff arrayRemove(final JsonPointer basePath,
final IndexedJsonArray array1, final IndexedJsonArray array2) {
return new Diff(DiffOperation.REMOVE, basePath, array1.getIndex(),
array2.getIndex(), array1.getElement().deepCopy());
}
static Diff arrayAdd(final JsonPointer basePath, final JsonNode node) {
return new Diff(DiffOperation.ADD, basePath, -1, -1, node.deepCopy());
}
static Diff arrayInsert(final JsonPointer basePath,
final IndexedJsonArray array1, final IndexedJsonArray array2) {
return new Diff(DiffOperation.ADD, basePath, array1.getIndex(),
array2.getIndex(), array2.getElement().deepCopy());
}
JsonNode asJsonPatch() {
final JsonPointer ptr = arrayPath != null ? getSecondArrayPath()
: path;
final ObjectNode patch = operation.newOp(ptr);
/*
* A remove only has a path
*/
if (operation == DiffOperation.REMOVE) {
return patch;
}
/*
* A move has a "source path" (the "from" member), other defined
* operations (add and replace) have a value instead.
*/
if (operation == DiffOperation.MOVE
|| operation == DiffOperation.COPY) {
patch.put("from", fromPath.toString());
} else {
patch.set("value", value);
}
return patch;
}
JsonPointer getSecondArrayPath() {
// compute path from array path and index
if (secondArrayIndex != -1) {
return arrayPath.append(secondArrayIndex);
}
return arrayPath.append("-");
}
@SuppressWarnings("unchecked")
@Override
public int hashCode() {
return hashCode(operation, path, arrayPath, firstArrayIndex,
secondArrayIndex, new Wrapper<>(JsonNumEquals.getInstance(), value),
fromPath, pairedDiff != null, firstOfPair);
}
private int hashCode(Object... objects) {
return Arrays.hashCode(objects);
}
@Override
public boolean equals(final Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Diff other = (Diff) obj;
return operation == other.operation
&& Objects.equals(path, other.path)
&& Objects.equals(arrayPath, other.arrayPath)
&& firstArrayIndex == other.firstArrayIndex
&& secondArrayIndex == other.secondArrayIndex
&& JsonNumEquals.getInstance().equivalent(value, other.value)
&& Objects.equals(fromPath, other.fromPath)
&& Objects.equals(pairedDiff != null, other.pairedDiff != null)
&& firstOfPair == other.firstOfPair;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getClass().getName())
.append("{op=").append(operation)
.append("}{path=").append(path)
.append("}{arrayPath=").append(arrayPath)
.append("}{firstArrayIndex=").append(firstArrayIndex)
.append("}{secondArrayIndex").append(secondArrayIndex)
.append("}{value").append(value)
.append("}{fromPath").append(fromPath)
.append("}{paired").append(pairedDiff != null)
.append("}{firstOfPair").append(firstOfPair);
return Objects.toString(sb.toString());
}
}

@ -0,0 +1,285 @@
package org.xbib.content.json.diff;
import static org.xbib.content.json.diff.DiffOperation.ADD;
import static org.xbib.content.json.diff.DiffOperation.REMOVE;
import com.fasterxml.jackson.databind.JsonNode;
import org.xbib.content.json.jackson.Equivalence;
import org.xbib.content.json.jackson.JsonNumEquals;
import org.xbib.content.json.pointer.JsonPointer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
final class DiffFactorizer {
private static final Equivalence<JsonNode> EQUIVALENCE = JsonNumEquals.getInstance();
private DiffFactorizer() {
}
/**
* Factorize list of ordered differences. Where removed values are
* equivalent to added values, merge add and remove to move
* differences. Because remove differences are relocated in the
* process of merging, other differences can be side effected.
* Add differences with equivalent values to previous add
* differences are converted to copy differences.
*
* @param diffs list of ordered differences.
*/
public static void factorizeDiffs(final List<Diff> diffs) {
findPairs(diffs);
factorizePairs(diffs);
// Factorize add diffs with equivalent non-empty object or array
// values into copy diffs; from paths for copy diffs can be set using
// previous add diff paths and/or array paths because diff order is
// acyclic and immutable for this factorization. The only exception
// to this rule are adds that append to arrays: these have no concrete
// path that can serve as a copy diff from path.
final List<Diff> addDiffs = new ArrayList<>();
for (final Diff diff : diffs) {
/*
* Ignore non add operations
*/
if (diff.operation != ADD) {
continue;
}
/*
* Skip value nodes or empty objects/arrays
*/
if (diff.value.size() == 0) {
continue;
}
// check add diff value against list of previous add diffs
Diff addDiff = null;
for (final Diff testAddDiff : addDiffs) {
if (EQUIVALENCE.equivalent(diff.value, testAddDiff.value)) {
addDiff = testAddDiff;
break;
}
}
// if not found previously, save add diff, (if not appending
// to an array which can have no concrete from path), and continue
if (addDiff == null) {
if (diff.arrayPath == null || diff.secondArrayIndex != -1) {
addDiffs.add(diff);
}
continue;
}
// previous add diff found by value: convert add diff to copy
// diff with from path set to concrete add diff path
diff.operation = DiffOperation.COPY;
diff.fromPath = addDiff.arrayPath != null
? addDiff.getSecondArrayPath() : addDiff.path;
}
}
/**
* Find additions/removal pairs
* <p>
* <p>Find addition operations which can be paired value-wise with removal
* operations.</p>
* <p>
* <p>Note that only the first pair is considered.</p>
*
* @param diffs the list of diffs
*/
private static void findPairs(final List<Diff> diffs) {
final int diffsSize = diffs.size();
Diff addition, removal;
for (int addIndex = 0; addIndex < diffsSize; addIndex++) {
addition = diffs.get(addIndex);
if (addition.operation != ADD) {
continue;
}
/*
* Found an addition: try and find a matching removal
*/
for (int removeIndex = 0; removeIndex < diffsSize; removeIndex++) {
removal = diffs.get(removeIndex);
if (removal.operation == REMOVE && EQUIVALENCE.equivalent(removal.value, addition.value)) {
addition.pairedDiff = removal;
addition.firstOfPair = addIndex < removeIndex;
removal.pairedDiff = addition;
removal.firstOfPair = removeIndex < addIndex;
break;
}
}
}
}
/**
* Factorize additions/removals
* <p>Removals, when paired with additions, are removed from the list.</p>
* <p>
* <p>Special care must be taken for additions/removal pairs happening
* within the same array, so that array indices can be adjusted properly.
* </p>
*
* @param diffs the list of diffs
*/
private static void factorizePairs(final List<Diff> diffs) {
/*
* Those two arrays will hold array removals seen before or after their
* paired additions.
*/
final List<Diff> seenBefore = new ArrayList<>();
final List<Diff> seenAfter = new ArrayList<>();
final Iterator<Diff> iterator = diffs.iterator();
Diff diff;
while (iterator.hasNext()) {
diff = iterator.next();
if (diff.pairedDiff != null) {
if (diff.operation == REMOVE) {
/*
* If removal is from an array and we reach this point,
* it means the matching addition has not been seen yet.
* Add this diff to the relevant list.
*/
if (diff.arrayPath != null && diff.firstOfPair) {
seenBefore.add(diff);
}
// remove paired remove and continue
iterator.remove();
} else if (diff.operation == ADD) {
/*
* Turn paired additions into move operations
*/
transformAddition(seenBefore, seenAfter, diff);
} else {
throw new IllegalStateException();
}
}
// adjust secondary index for all array diffs with matching
// deferred array removes; note: all non remove array diffs
// have a valid second array index
if (diff.arrayPath != null) {
diff.secondArrayIndex = adjustSecondArrayIndex(seenBefore,
diff.arrayPath, diff.secondArrayIndex);
}
}
}
private static void transformAddition(final List<Diff> seenBefore,
final List<Diff> seenAfter, final Diff diff) {
final Diff removal = diff.pairedDiff;
// convert paired add diff into a move
diff.operation = DiffOperation.MOVE;
diff.pairedDiff = null;
/*
* Compute the "from" path of this move operation
*/
if (removal.arrayPath == null) {
/*
* If removal was not from an array, we just need to grab
* the path of this remove operation as the origin path
* for this move
*/
diff.fromPath = removal.path;
return;
}
if (diff.firstOfPair) {
// move diff is first of pair: remove will be advanced
// and will use original first indexes into array
int removeIndex = removal.firstArrayIndex;
// adjust remove index for operations on arrays with
// matching advanced array removes
removeIndex = adjustFirstArrayIndex(seenAfter, removal.arrayPath,
removeIndex);
// if move diff and remove diff are from the same array,
// remove index must be based on an original index offset
// from the move diff secondary index; this is reflecting
// the fact that array diff operations up to the move diff
// have been applied, but those following the move diff to
// the remove diff have not and thus require original
// first array index adjustments
if (removal.arrayPath.equals(diff.arrayPath)) {
final int moveSecondArrayIndex = adjustSecondArrayIndex(
seenBefore, diff.arrayPath, diff.secondArrayIndex);
final int moveFirstArrayIndex = adjustFirstArrayIndex(seenAfter,
diff.arrayPath, diff.firstArrayIndex);
removeIndex += moveSecondArrayIndex - moveFirstArrayIndex;
}
// set move diff from using adjusted remove index
diff.fromPath = removal.arrayPath.append(removeIndex);
// track advanced array removes
seenAfter.add(removal);
} else {
// remove diff is first of pair: remove has been deferred
// for this move; remove tracked deferred array remove
seenBefore.remove(removal);
// remove can now be moved using second index
int removeIndex = removal.secondArrayIndex;
// adjust remove index for operations on arrays with
// matching deferred array removes
removeIndex = adjustSecondArrayIndex(seenBefore, removal.arrayPath,
removeIndex);
// set move diff from using adjusted remove index
diff.fromPath = removal.arrayPath.append(removeIndex);
}
}
/**
* Adjust array index based on array removes seen before their matching
* additions. Missing second array indexes (-1) are not adjusted.
*
* @param seenAfter list of removals seen before their matching additions
* @param arrayPath array path of array index to adjust
* @param arrayIndex index to adjust and upper range of removes
* @return index adjusted by advanced array moves in range
*/
private static int adjustFirstArrayIndex(final List<Diff> seenAfter,
final JsonPointer arrayPath, final int arrayIndex) {
/*
* Adjust index of removal operations on arrays with matching advanced
* array removes: for each advanced remove, decrement the index assuming
* remove will have been done before remaining diffs on array
*/
int arrayRemoves = 0;
for (final Diff removal : seenAfter) {
if (arrayPath.equals(removal.arrayPath)
&& arrayIndex > removal.firstArrayIndex) {
arrayRemoves++;
}
}
return arrayIndex - arrayRemoves;
}
/**
* Adjust array index of removal operations seen after their matching
* additions. Missing second array indexes (-1) are not adjusted.
*
* @param seenAfter list of removals seen before their matching additions
* @param arrayPath array path of array index to adjust
* @param arrayIndex index to adjust and upper range of moves
* @return index adjusted by deferred array moves in range
*/
private static int adjustSecondArrayIndex(final List<Diff> seenAfter,
final JsonPointer arrayPath, final int arrayIndex) {
if (arrayIndex == -1) {
return arrayIndex;
}
/*
* adjust secondary index for operations on arrays with matching
* deferred array removes: for each deferred remove, increment the index
* assuming remove will not be done until the move diff is performed
*/
int arrayRemoves = 0;
for (final Diff removal : seenAfter) {
if (arrayPath.equals(removal.arrayPath)
&& arrayIndex >= removal.secondArrayIndex) {
arrayRemoves++;
}
}
return arrayIndex + arrayRemoves;
}
}

@ -0,0 +1,36 @@
package org.xbib.content.json.diff;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.xbib.content.json.jackson.JacksonUtils;
import org.xbib.content.json.pointer.JsonPointer;
/**
* Difference operation types. Add, remove, and replace operations
* are directly generated by node comparison. Move operations are
* the result of factorized add and remove operations.
*/
enum DiffOperation {
ADD("add"),
REMOVE("remove"),
REPLACE("replace"),
MOVE("move"),
COPY("copy");
private final String opName;
DiffOperation(final String opName) {
this.opName = opName;
}
ObjectNode newOp(final JsonPointer ptr) {
final ObjectNode ret = JacksonUtils.nodeFactory().objectNode();
ret.put("op", opName);
ret.put("path", ptr.toString());
return ret;
}
@Override
public String toString() {
return opName;
}
}

@ -0,0 +1,46 @@
package org.xbib.content.json.diff;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import org.xbib.content.json.jackson.JacksonUtils;
import java.util.List;
final class IndexedJsonArray {
private final int size;
private final JsonNode node;
private int index = 0;
IndexedJsonArray(final JsonNode node) {
this.node = node;
size = node.size();
}
IndexedJsonArray(final List<JsonNode> list) {
final ArrayNode arrayNode = JacksonUtils.nodeFactory().arrayNode();
arrayNode.addAll(list);
node = arrayNode;
size = arrayNode.size();
}
int getIndex() {
return isEmpty() ? -1 : index;
}
void shift() {
index++;
}
JsonNode getElement() {
return node.get(index);
}
boolean isEmpty() {
return index >= size;
}
int size() {
return size;
}
}

@ -0,0 +1,401 @@
package org.xbib.content.json.diff;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import org.xbib.content.json.jackson.Equivalence;
import org.xbib.content.json.jackson.JacksonUtils;
import org.xbib.content.json.jackson.JsonNumEquals;
import org.xbib.content.json.jackson.NodeType;
import org.xbib.content.json.pointer.JsonPointer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* "Reverse" factorizing JSON Patch implementation.
*
* This class only has one method, {@link #asJson(com.fasterxml.jackson.databind.JsonNode,
* com.fasterxml.jackson.databind.JsonNode)}, which
* takes two JSON values as arguments and returns a patch as a {@link com.fasterxml.jackson.databind.JsonNode}.
* This generated patch can then be used in {@link
* org.xbib.content.json.patch.JsonPatch#fromJson(com.fasterxml.jackson.databind.JsonNode)}.
*
* Numeric equivalence is respected. Operations are always generated in the
* following order:
*
* <ul>
* <li>additions,</li>
* <li>removals,</li>
* <li>replacements.</li>
* </ul>
*
* Array values generate operations in the order of elements. Factorizing is
* done to merge add and remove into move operations and convert duplicate add
* to copy operations if values are equivalent. No test operations are
* generated (they don't really make sense for diffs anyway).
*
* Note that due to the way {@link com.fasterxml.jackson.databind.JsonNode} is implemented, this class is
* inherently not thread safe (since {@code JsonNode} is mutable). It is
* therefore the responsibility of the caller to ensure that the calling context
* is safe (by ensuring, for instance, that only the diff operation has
* references to the values to be diff'ed).
*/
public final class JsonDiff {
private static final JsonNodeFactory FACTORY = JacksonUtils.nodeFactory();
private static final Equivalence<JsonNode> EQUIVALENCE
= JsonNumEquals.getInstance();
private JsonDiff() {
}
/**
* Generate a JSON patch for transforming the source node into the target node.
*
* @param source the node to be patched
* @param target the expected result after applying the patch
* @return the patch as a {@link com.fasterxml.jackson.databind.JsonNode}
*/
public static JsonNode asJson(final JsonNode source, final JsonNode target) {
final List<Diff> diffs = new ArrayList<>();
generateDiffs(diffs, JsonPointer.empty(), source, target);
DiffFactorizer.factorizeDiffs(diffs);
final ArrayNode patch = FACTORY.arrayNode();
for (final Diff diff : diffs) {
patch.add(diff.asJsonPatch());
}
return patch;
}
/**
* Generate differences between source and target node.
*
* @param diffs list of differences (in order)
* @param path parent path for both nodes
* @param source source node
* @param target target node
*/
private static void generateDiffs(final List<Diff> diffs,
final JsonPointer path, final JsonNode source, final JsonNode target) {
/*
* If both nodes are equivalent, there is nothing to do
*/
if (EQUIVALENCE.equivalent(source, target)) {
return;
}
/*
* Get both node types. We shortcut to a simple replace operation in the
* following scenarios:
*
* - nodes are not the same type; or
* - they are the same type, but are not containers (ie, they are
* neither objects nor arrays).
*/
final NodeType sourceType = NodeType.getNodeType(source);
final NodeType targetType = NodeType.getNodeType(target);
if (sourceType != targetType || !source.isContainerNode()) {
diffs.add(Diff.simpleDiff(DiffOperation.REPLACE, path, target));
return;
}
if (sourceType == NodeType.OBJECT) {
generateObjectDiffs(diffs, path, source, target);
} else {
generateArrayDiffs(diffs, path, source, target);
}
}
/**
* Generate differences between two object nodes.
*
* Differences are generated in the following order: added members,
* removed members, modified members.
*
* @param diffs list of differences (modified)
* @param path parent path common to both nodes
* @param source node to patch
* @param target node to attain
*/
private static void generateObjectDiffs(final List<Diff> diffs,
final JsonPointer path, final JsonNode source, final JsonNode target) {
final List<String> inFirst = new ArrayList<>();
Iterator<String> it = source.fieldNames();
while (it.hasNext()) {
inFirst.add(it.next());
}
final List<String> inSecond = new ArrayList<>();
it = target.fieldNames();
while (it.hasNext()) {
inSecond.add(it.next());
}
List<String> fields;
fields = new ArrayList<>(inSecond);
fields.removeAll(inFirst);
for (final String s : fields) {
diffs.add(Diff.simpleDiff(DiffOperation.ADD, path.append(s), target.get(s)));
}
fields = new ArrayList<>(inFirst);
fields.removeAll(inSecond);
for (final String s : fields) {
diffs.add(Diff.simpleDiff(DiffOperation.REMOVE, path.append(s), source.get(s)));
}
fields = new ArrayList<>(inFirst);
fields.retainAll(inSecond);
for (final String s : fields) {
generateDiffs(diffs, path.append(s), source.get(s), target.get(s));
}
}
/**
* Generate differences between two array nodes.
* Differences are generated in order by comparing elements against the
* longest common subsequence of elements in both arrays.
*
* @param diffs list of differences (modified)
* @param path parent pointer of both array nodes
* @param source array node to be patched
* @param target target node after patching
* @see LeastCommonSubsequence#getLCS(com.fasterxml.jackson.databind.JsonNode, com.fasterxml.jackson.databind.JsonNode)
*/
private static void generateArrayDiffs(final List<Diff> diffs,
final JsonPointer path, final JsonNode source, final JsonNode target) {
// compare array elements linearly using longest common subsequence
// algorithm applied to the array elements
final IndexedJsonArray src = new IndexedJsonArray(source);
final IndexedJsonArray dst = new IndexedJsonArray(target);
final IndexedJsonArray lcs = LeastCommonSubsequence.doLCS(source, target);
preLCS(diffs, path, lcs, src, dst);
inLCS(diffs, path, lcs, src, dst);
postLCS(diffs, path, src, dst);
}
/*
* First method entered when computing array diffs. It will exit early if
* the LCS is empty.
*
* If the LCS is not empty, it means that both the source and target arrays
* have at least one element left. In such a situation, this method will run
* until elements extracted from both arrays are equivalent to the first
* element of the LCS.
*/
private static void preLCS(final List<Diff> diffs, final JsonPointer path,
final IndexedJsonArray lcs, final IndexedJsonArray source,
final IndexedJsonArray target) {
if (lcs.isEmpty()) {
return;
}
/*
* This is our sentinel: if nodes from both the first array and the
* second array are equivalent to this node, we are done.
*/
final JsonNode sentinel = lcs.getElement();
/*
* Those two variables hold nodes for the first and second array in the
* main loop.
*/
JsonNode srcNode;
JsonNode dstNode;
/*
* This records the number of equivalences between the LCS node and
* nodes from the source and target arrays.
*/
int nrEquivalences;
while (true) {
/*
* At each step, we reset the number of equivalences to 0.
*/
nrEquivalences = 0;
srcNode = source.getElement();
dstNode = target.getElement();
if (EQUIVALENCE.equivalent(sentinel, srcNode)) {
nrEquivalences++;
}
if (EQUIVALENCE.equivalent(sentinel, dstNode)) {
nrEquivalences++;
}
/*
* If both srcNode and dstNode are equivalent to our sentinel, we
* are done; this is our exit condition.
*/
if (nrEquivalences == 2) {
return;
}
/*
* If none of them are equivalent to the LCS node, compute diffs
* in first array so that the element in this array's index be
* transformed into the matching element in the second array; then
* restart the loop.
*
* Note that since we are using an LCS, and no element of either
* array is equivalent to the first element of the LCS (our
* sentinel), a consequence is that indices in both arrays are
* equal. In the path below, we could have equally used the index
* from the target array.
*/
if (nrEquivalences == 0) {
generateDiffs(diffs, path.append(source.getIndex()), srcNode,
dstNode);
source.shift();
target.shift();
continue;
}
/*
* If we reach this point, one array has to catch up in order to
* reach the first element of the LCS. The logic is as follows:
*
* <ul>
* <li>if the source array has to catch up, it means its elements have
* been removed from the target array;</li>
* <li>if the target array has to catch up, it means the source
* array's elements are being inserted into the target array.</li>
* </ul>
*/
if (!EQUIVALENCE.equivalent(sentinel, srcNode)) {
diffs.add(Diff.arrayRemove(path, source, target));
source.shift();
} else {
diffs.add(Diff.arrayInsert(path, source, target));
target.shift();
}
}
}
/*
* This method is called after preLCS(). Its role is to deplete the LCS.
*
* One particularity of using LCS is that as long as the LCS is not empty,
* we can be sure that there is at least one element left in both the source
* and target array.
*/
private static void inLCS(final List<Diff> diffs, final JsonPointer path,
final IndexedJsonArray lcs, final IndexedJsonArray source,
final IndexedJsonArray target) {
JsonNode sourceNode;
JsonNode targetNode;
JsonNode lcsNode;
boolean sourceMatch;
boolean targetMatch;
while (!lcs.isEmpty()) {
sourceNode = source.getElement();
targetNode = target.getElement();
lcsNode = lcs.getElement();
sourceMatch = EQUIVALENCE.equivalent(sourceNode, lcsNode);
targetMatch = EQUIVALENCE.equivalent(targetNode, lcsNode);
if (!sourceMatch) {
/*
* At this point, the first element of our source array has
* failed to "reach" a matching element in the target array.
*
* Such an element therefore needs to be removed from the target
* array. We therefore generate a "remove event", shift the
* source array and restart the loop.
*/
diffs.add(Diff.arrayRemove(path, source, target));
source.shift();
continue;
}
/*
* When we reach this point, we know that the element extracted
* from the source array is equivalent to the LCS element.
*
* Note that from this point on, whatever the target element is, we
* need to shift our target array; there are two different scenarios
* we must account for:
*
* <ul>
* <li>if the target element is equivalent to the LCS element, we have
* a common subsequence element (remember that the source element
* is also equivalent to this same LCS element at this point); no
* mutation of the target array takes place; we must therefore
* shift all three arrays (source, target, LCS);</li>
* <li>otherwise (target element is not equivalent to the LCS
* element), we need to emit an insertion event of the target
* element, and advance the target array only.</li>
* </ul>
*/
if (targetMatch) {
source.shift();
lcs.shift();
} else {
diffs.add(Diff.arrayInsert(path, source, target));
}
/*
* Shift/advance the target array; always performed, see above
*/
target.shift();
}
}
/*
* This function is run once the LCS has been exhausted.
*
* Since the LCS has been exhausted, it means that for whatever nodes node1
* and node2 extracted from source and target, they can never be equal.
*
* The algorithm is therefore as follows:
*
* <ul>
* <li>as long as both are not empty, grab both elements from both arrays and
* generate diff operations on them recursively;</li>
* <li>when we are out of this loop, add any elements remaining in the second
* array (if any), and remove any elements remaining in the first array
* (if any).</li>
* </ul>
*
* Note that at the second step, only one of the two input arrays will ever
* have any elements left; it is therefore safe to call the appropriate
* functions for _both_ possibilities since only one will ever produce any
* results.
*/
private static void postLCS(final List<Diff> diffs, final JsonPointer path,
final IndexedJsonArray source, final IndexedJsonArray target) {
JsonNode src, dst;
while (!(source.isEmpty() || target.isEmpty())) {
src = source.getElement();
dst = target.getElement();
generateDiffs(diffs, path.append(source.getIndex()), src, dst);
source.shift();
target.shift();
}
addRemaining(diffs, path, target);
removeRemaining(diffs, path, source, target.size());
}
private static void addRemaining(final List<Diff> diffs,
final JsonPointer path, final IndexedJsonArray array) {
Diff diff;
JsonNode node;
while (!array.isEmpty()) {
node = array.getElement().deepCopy();
diff = Diff.arrayAdd(path, node);
diffs.add(diff);
array.shift();
}
}
private static void removeRemaining(final List<Diff> diffs,
final JsonPointer path, final IndexedJsonArray array,
final int removeIndex) {
Diff diff;
JsonNode node;
while (!array.isEmpty()) {
node = array.getElement();
diff = Diff.tailArrayRemove(path, array.getIndex(),
removeIndex, node);
diffs.add(diff);
array.shift();
}
}
}

@ -0,0 +1,167 @@
package org.xbib.content.json.diff;
import com.fasterxml.jackson.databind.JsonNode;
import org.xbib.content.json.jackson.Equivalence;
import org.xbib.content.json.jackson.JsonNumEquals;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Longest common subsequence algorithm implementation
* <p>
* <p>This is an adaptation of the code found at <a
* href="http://rosettacode.org/wiki/Longest_common_subsequence#Dynamic_Programming_2">Rosetta
* Code</a> for {@link com.fasterxml.jackson.databind.node.ArrayNode} instances.</p>
* <p>
* <p>For instance, given these two arrays:</p>
* <p>
* <ul>
* <li>{@code [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]},</li>
* <li>{@code [ 1, 2, 10, 11, 5, 12, 8, 9 ]}</li>
* </ul>
* <p>
* <p>this code will return {@code [ 1, 2, 5, 8, 9 ]}.</p>
*/
final class LeastCommonSubsequence {
private static final Equivalence<JsonNode> EQUIVALENCE = JsonNumEquals.getInstance();
private LeastCommonSubsequence() {
}
/**
* Get the longest common subsequence of elements of two array nodes
* <p>
* <p>This is an implementation of the classic 'diff' algorithm often used
* to compare text files line by line.</p>
*
* @param first first array node to compare
* @param second second array node to compare
*/
static List<JsonNode> getLCS(final JsonNode first, final JsonNode second) {
final int minSize = Math.min(first.size(), second.size());
List<JsonNode> l1 = Arrays.asList(first);
List<JsonNode> l2 = Arrays.asList(second);
final List<JsonNode> ret = head(l1, l2);
final int headSize = ret.size();
l1 = l1.subList(headSize, l1.size());
l2 = l2.subList(headSize, l2.size());
final List<JsonNode> tail = tail(l1, l2);
final int trim = tail.size();
l1 = l1.subList(0, l1.size() - trim);
l2 = l2.subList(0, l2.size() - trim);
if (headSize < minSize) {
ret.addAll(doLCS(l1, l2));
}
ret.addAll(tail);
return ret;
}
static IndexedJsonArray doLCS(final JsonNode first, final JsonNode second) {
return new IndexedJsonArray(getLCS(first, second));
}
/**
* Compute longest common subsequence out of two lists
* <p>
* <p>When entering this function, both lists are trimmed from their
* common leading and trailing nodes.</p>
*
* @param l1 the first list
* @param l2 the second list
* @return the longest common subsequence
*/
private static List<JsonNode> doLCS(final List<JsonNode> l1,
final List<JsonNode> l2) {
final List<JsonNode> lcs = new ArrayList<>();
// construct LCS lengths matrix
final int size1 = l1.size();
final int size2 = l2.size();
final int[][] lengths = new int[size1 + 1][size2 + 1];
JsonNode node1;
JsonNode node2;
int len;
for (int i = 0; i < size1; i++) {
for (int j = 0; j < size2; j++) {
node1 = l1.get(i);
node2 = l2.get(j);
len = EQUIVALENCE.equivalent(node1, node2) ? lengths[i][j] + 1
: Math.max(lengths[i + 1][j], lengths[i][j + 1]);
lengths[i + 1][j + 1] = len;
}
}
// return result out of the LCS lengths matrix
int x = size1, y = size2;
while (x > 0 && y > 0) {
if (lengths[x][y] == lengths[x - 1][y]) {
x--;
} else if (lengths[x][y] == lengths[x][y - 1]) {
y--;
} else {
lcs.add(l1.get(x - 1));
x--;
y--;
}
}
Collections.reverse(lcs);
return lcs;
}
/**
* Return a list with common head elements of two lists
* <p>
* <p>Note that the arguments are NOT altered.</p>
*
* @param l1 first list
* @param l2 second list
* @return a list of common head elements
*/
private static List<JsonNode> head(final List<JsonNode> l1,
final List<JsonNode> l2) {
final List<JsonNode> ret = new ArrayList<>();
final int len = Math.min(l1.size(), l2.size());
JsonNode node;
for (int index = 0; index < len; index++) {
node = l1.get(index);
if (!EQUIVALENCE.equivalent(node, l2.get(index))) {
break;
}
ret.add(node);
}
return ret;
}
/**
* Return the list of common tail elements of two lists
* <p>
* <p>Note that the arguments are NOT altered. Elements are returned in
* their order of appearance.</p>
*
* @param l1 first list
* @param l2 second list
* @return a list of common tail elements
*/
private static List<JsonNode> tail(final List<JsonNode> l1,
final List<JsonNode> l2) {
Collections.reverse(l1);
Collections.reverse(l2);
final List<JsonNode> l = head(l1, l2);
Collections.reverse(l);
return l;
}
}

@ -0,0 +1,4 @@
/**
* Classes for JSON diff operation.
*/
package org.xbib.content.json.diff;

@ -0,0 +1,45 @@
package org.xbib.content.json.jackson;
/**
* A strategy for determining whether two instances are considered equivalent.
* @param <T> type parameter
*/
public interface Equivalence<T> {
/**
* Returns {@code true} if the given objects are considered equivalent.
*
* The <code>equivalent</code> method implements an equivalence relation on non-null object
* references:
* <ul>
* <li>It is <i>reflexive</i>: for any non-null reference value {@code x}, {@code x.equals(x)}
* should return {@code true}.
* <li>It is <i>symmetric</i>: for any non-null reference values {@code x} and {@code y}, {@code
* x.equals(y)} should return {@code true} if and only if {@code y.equals(x)} returns {@code
* true}.
* <li>It is <i>transitive</i>: for any non-null reference values {@code x}, {@code y}, and {@code
* z}, if {@code x.equals(y)} returns {@code true} and {@code y.equals(z)} returns {@code
* true}, then {@code x.equals(z)} should return {@code true}.
* <li>It is <i>consistent</i>: for any non-null reference values {@code x} and {@code y},
* multiple invocations of {@code x.equals(y)} consistently return {@code true} or
* consistently return {@code false}, provided no information used in {@code equals}
* comparisons on the objects is modified.
* <li>For any non-null reference value {@code x}, {@code x.equals(null)} should return {@code
* false}.
* </ul>
* @param a a
* @param b b
* @return true if a and b are equivalent
*/
boolean equivalent(T a, T b);
/**
* Returns a hash code for {@code object}. This function must return the same value for
* any two instances which are {@link #equivalent}, and should as often as possible return a
* distinct value for instances which are not equivalent.
*
* @param t the object of type t
* @return hash code
* @see Object#hashCode the same contractual obligations apply here
*/
int hash(T t);
}

@ -0,0 +1,124 @@
package org.xbib.content.json.jackson;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Utility class for Jackson.
*
* This class provides utility methods to get a {@link com.fasterxml.jackson.databind.node.JsonNodeFactory} and
* a preconfigured {@link com.fasterxml.jackson.databind.ObjectReader}. It can also be used to return
* preconfigured instances of {@link com.fasterxml.jackson.databind.ObjectMapper} (see {@link #newMapper()}.
*
*/
public final class JacksonUtils {
private static final Logger logger = Logger.getLogger(JacksonUtils.class.getName());
private static final JsonNodeFactory FACTORY = JsonNodeFactory.instance;
private static final ObjectReader READER;
private static final ObjectWriter WRITER;
static {
final ObjectMapper mapper = newMapper();
READER = mapper.reader();
WRITER = mapper.writer();
}
private JacksonUtils() {
}
/**
* Return a preconfigured {@link com.fasterxml.jackson.databind.ObjectReader} to read JSON inputs.
*
* @return the reader
* @see #newMapper()
*/
public static ObjectReader getReader() {
return READER;
}
/**
* Return a preconfigured {@link com.fasterxml.jackson.databind.node.JsonNodeFactory} to generate JSON data as
* {@link com.fasterxml.jackson.databind.JsonNode}s.
*
* @return the factory
*/
public static JsonNodeFactory nodeFactory() {
return FACTORY;
}
/**
* Return a map out of an object's members.
* If the node given as an argument is not a map, an empty map is
* returned.
*
* @param node the node
* @return a map
*/
public static Map<String, JsonNode> asMap(final JsonNode node) {
if (!node.isObject()) {
return Collections.emptyMap();
}
final Iterator<Map.Entry<String, JsonNode>> iterator = node.fields();
final Map<String, JsonNode> ret = new HashMap<>();
Map.Entry<String, JsonNode> entry;
while (iterator.hasNext()) {
entry = iterator.next();
ret.put(entry.getKey(), entry.getValue());
}
return ret;
}
/**
* Pretty print a JSON value.
*
* @param node the JSON value to print
* @return the pretty printed value as a string
* @see #newMapper()
*/
public static String prettyPrint(final JsonNode node) {
final StringWriter writer = new StringWriter();
try {
WRITER.writeValue(writer, node);
writer.flush();
} catch (IOException e) {
logger.log(Level.FINE, e.getMessage(), e);
}
return writer.toString();
}
/**
* Return a preconfigured {@link com.fasterxml.jackson.databind.ObjectMapper}.
* The returned mapper will have the following features enabled:
* <ul>
* <li>{@link com.fasterxml.jackson.databind.DeserializationFeature#USE_BIG_DECIMAL_FOR_FLOATS};</li>
* <li>{@link com.fasterxml.jackson.databind.SerializationFeature#INDENT_OUTPUT}.</li>
* </ul>
* This returns a new instance each time.
*
* @return an {@link com.fasterxml.jackson.databind.ObjectMapper}
*/
public static ObjectMapper newMapper() {
return new ObjectMapper().setNodeFactory(FACTORY)
.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)
.enable(SerializationFeature.INDENT_OUTPUT);
}
}

@ -0,0 +1,84 @@
package org.xbib.content.json.jackson;
import com.fasterxml.jackson.databind.JsonNode;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.net.URL;
/**
* Utility class to load JSON values from various sources as {@link com.fasterxml.jackson.databind.JsonNode}s.
*
* This class uses a {@link JsonNodeReader} to parse JSON inputs.
*
* @see JsonNodeReader
*/
public final class JsonLoader {
/**
* The reader.
*/
private static final JsonNodeReader READER = new JsonNodeReader();
private JsonLoader() {
}
/**
* Read a {@link com.fasterxml.jackson.databind.JsonNode} from a resource path.
*
* This method first tries and loads the resource using {@link
* Class#getResource(String)}; if not found, is tries and uses the context
* classloader and if this is not found, this class's classloader.
*
* This method throws an {@link java.io.IOException} if the resource does not
* exist.
*
* @param classLoader the class loader
* @param resource the path to the resource (must begin
* with a {@code /})
* @return the JSON document at the resource
* @throws IllegalArgumentException resource path does not begin with a
* {@code /}
* @throws java.io.IOException there was a problem loading the resource, or the JSON
* document is invalid
*/
public static JsonNode fromResource(ClassLoader classLoader, final String resource)
throws IOException {
URL url = JsonLoader.class.getResource(resource);
InputStream in = url != null ? url.openStream() : classLoader.getResourceAsStream(resource);
final JsonNode ret;
try {
ret = READER.fromInputStream(in);
} finally {
if (in != null) {
in.close();
}
}
return ret;
}
/**
* Read a {@link com.fasterxml.jackson.databind.JsonNode} from a user supplied {@link java.io.Reader}.
*
* @param reader The reader
* @return the document
* @throws java.io.IOException if the reader has problems
*/
public static JsonNode fromReader(final Reader reader)
throws IOException {
return READER.fromReader(reader);
}
/**
* Read a {@link com.fasterxml.jackson.databind.JsonNode} from a string input.
*
* @param json the JSON as a string
* @return the document
* @throws java.io.IOException could not read from string
*/
public static JsonNode fromString(final String json)
throws IOException {
return fromReader(new StringReader(json));
}
}

@ -0,0 +1,142 @@
package org.xbib.content.json.jackson;
import com.fasterxml.jackson.core.JsonLocation;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.MappingIterator;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Class dedicated to reading JSON values from {@link java.io.InputStream}s and {@link
* java.io.Reader}s.
* This class wraps a Jackson {@link com.fasterxml.jackson.databind.ObjectMapper} so that it read one, and
* only one, JSON text from a source. By default, when you read and map an
* input source, Jackson will stop after it has read the first valid JSON text;
* this means, for instance, that with this as an input:
* <pre>
* []]]
* </pre>
* it will read the initial empty array ({@code []}) and stop there. This
* class, instead, will peek to see whether anything is after the initial array,
* and throw an exception if it finds anything.
* Note: the input sources are closed by the read methods.
*
* @see com.fasterxml.jackson.databind.ObjectMapper#readValues(com.fasterxml.jackson.core.JsonParser, Class)
*/
public final class JsonNodeReader {
private static final Logger logger = Logger.getLogger(JsonNodeReader.class.getName());
private final ObjectReader reader;
public JsonNodeReader(final ObjectMapper mapper) {
reader = mapper.configure(JsonParser.Feature.AUTO_CLOSE_SOURCE, true)
.readerFor(JsonNode.class);
}
public JsonNodeReader() {
this(JacksonUtils.newMapper());
}
private static JsonNode readNode(final MappingIterator<JsonNode> iterator) throws IOException {
final Object source = iterator.getParser().getInputSource();
final JsonParseExceptionBuilder builder = new JsonParseExceptionBuilder(null, source);
if (!iterator.hasNextValue()) {
throw builder.build();
}
final JsonNode ret = iterator.nextValue();
builder.setLocation(iterator.getCurrentLocation());
try {
if (iterator.hasNextValue()) {
throw builder.build();
}
} catch (JsonParseException e) {
logger.log(Level.FINE, e.getMessage(), e);
throw builder.setLocation(e.getLocation()).build();
}
return ret;
}
/**
* Read a JSON value from an {@link java.io.InputStream}.
*
* @param in the input stream
* @return the value
* @throws java.io.IOException malformed input, or problem encountered when reading
* from the stream
*/
public JsonNode fromInputStream(final InputStream in) throws IOException {
JsonParser parser = null;
MappingIterator<JsonNode> iterator = null;
try {
parser = reader.getFactory().createParser(in);
iterator = reader.readValues(parser);
return readNode(iterator);
} finally {
if (parser != null) {
parser.close();
}
if (iterator != null) {
iterator.close();
}
}
}
/**
* Read a JSON value from a {@link java.io.Reader}.
*
* @param r the reader
* @return the value
* @throws java.io.IOException malformed input, or problem encountered when reading
* from the reader
*/
public JsonNode fromReader(final Reader r)
throws IOException {
JsonParser parser = null;
MappingIterator<JsonNode> iterator = null;
try {
parser = reader.getFactory().createParser(r);
iterator = reader.readValues(parser);
return readNode(iterator);
} finally {
if (parser != null) {
parser.close();
}
if (iterator != null) {
iterator.close();
}
}
}
/**
*
*/
private static final class JsonParseExceptionBuilder {
private JsonParser jsonParser;
private JsonLocation location;
private JsonParseExceptionBuilder(final JsonParser jsonParser, final Object source) {
this.jsonParser = jsonParser;
location = new JsonLocation(source, 0L, 1, 1);
}
private JsonParseExceptionBuilder setLocation(final JsonLocation location) {
this.location = location;
return this;
}
public JsonParseException build() {
return new JsonParseException(jsonParser, "", location);
}
}
}

@ -0,0 +1,193 @@
package org.xbib.content.json.jackson;
import com.fasterxml.jackson.databind.JsonNode;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
* An equivalence strategy for JSON Schema equality
* {@link com.fasterxml.jackson.databind.JsonNode} does a pretty good job of obeying the {@link
* Object#equals(Object) equals()}/{@link Object#hashCode() hashCode()}
* contract. And in fact, it does it too well for JSON Schema.
*
* For instance, it considers numeric nodes {@code 1} and {@code 1.0} to be
* different nodes, which is true. But some IETF RFCs and drafts (among them,
* JSON Schema and JSON Patch) mandate that numeric JSON values be considered
* equal if their mathematical value is the same. This class implements this
* kind of equality.
*/
public final class JsonNumEquals implements Equivalence<JsonNode> {
private static final Equivalence<JsonNode> INSTANCE = new JsonNumEquals();
private JsonNumEquals() {
}
public static Equivalence<JsonNode> getInstance() {
return INSTANCE;
}
private static boolean numEquals(final JsonNode a, final JsonNode b) {
/*
* If both numbers are integers, delegate to JsonNode.
*/
if (a.isIntegralNumber() && b.isIntegralNumber()) {
return a.equals(b);
}
/*
* Otherwise, compare decimal values.
*/
return a.decimalValue().compareTo(b.decimalValue()) == 0;
}
@Override
public boolean equivalent(final JsonNode a, final JsonNode b) {
/*
* If both are numbers, delegate to the helper method
*/
if (a.isNumber() && b.isNumber()) {
return numEquals(a, b);
}
final NodeType typeA = NodeType.getNodeType(a);
final NodeType typeB = NodeType.getNodeType(b);
/*
* If they are of different types, no dice
*/
if (typeA != typeB) {
return false;
}
/*
* For all other primitive types than numbers, trust JsonNode
*/
if (!a.isContainerNode()) {
return a.equals(b);
}
/*
* OK, so they are containers (either both arrays or objects due to the
* test on types above). They are obviously not equal if they do not
* have the same number of elements/members.
*/
if (a.size() != b.size()) {
return false;
}
/*
* Delegate to the appropriate method according to their type.
*/
return typeA == NodeType.ARRAY ? arrayEquals(a, b) : objectEquals(a, b);
}
@Override
public int hash(final JsonNode t) {
/*
* If this is a numeric node, we want the same hashcode for the same
* mathematical values. Go with double, its range is good enough for
* 99+% of use cases.
*/
if (t.isNumber()) {
return Double.valueOf(t.doubleValue()).hashCode();
}
/*
* If this is a primitive type (other than numbers, handled above),
* delegate to JsonNode.
*/
if (!t.isContainerNode()) {
return t.hashCode();
}
/*
* The following hash calculations work, yes, but they are poor at best.
* And probably slow, too.
*/
int ret = 0;
/*
* If the container is empty, just return
*/
if (t.size() == 0) {
return ret;
}
/*
* Array
*/
if (t.isArray()) {
for (final JsonNode element : t) {
ret = 31 * ret + hash(element);
}
return ret;
}
/*
* Not an array? An object.
*/
final Iterator<Map.Entry<String, JsonNode>> iterator = t.fields();
Map.Entry<String, JsonNode> entry;
while (iterator.hasNext()) {
entry = iterator.next();
ret = 31 * ret + (entry.getKey().hashCode() ^ hash(entry.getValue()));
}
return ret;
}
private boolean arrayEquals(final JsonNode a, final JsonNode b) {
/*
* We are guaranteed here that arrays are the same size.
*/
final int size = a.size();
for (int i = 0; i < size; i++) {
if (!equivalent(a.get(i), b.get(i))) {
return false;
}
}
return true;
}
private boolean objectEquals(final JsonNode a, final JsonNode b) {
/*
* Grab the key set from the first node
*/
final Set<String> keys = new HashSet<>();
Iterator<String> it = a.fieldNames();
while (it.hasNext()) {
keys.add(it.next());
}
/*
* Grab the key set from the second node, and see if both sets are the
* same. If not, objects are not equal, no need to check for children.
*/
final Set<String> set = new HashSet<>();
it = b.fieldNames();
while (it.hasNext()) {
set.add(it.next());
}
if (!set.equals(keys)) {
return false;
}
/*
* Test each member individually.
*/
for (final String key : keys) {
if (!equivalent(a.get(key), b.get(key))) {
return false;
}
}
return true;
}
}

@ -0,0 +1,109 @@
package org.xbib.content.json.jackson;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.JsonNode;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
/**
* Enumeration for the different types of JSON instances which can be
* encountered.
* In addition to what the JSON RFC defines, JSON Schema has an {@code
* integer} type, which is a numeric value without any fraction or exponent
* part.
*/
public enum NodeType {
/**
* Array nodes.
*/
ARRAY("array"),
/**
* Boolean nodes.
*/
BOOLEAN("boolean"),
/**
* Integer nodes.
*/
INTEGER("integer"),
/**
* Number nodes (ie, decimal numbers).
*/
NULL("null"),
/**
* Object nodes.
*/
NUMBER("number"),
/**
* Null nodes.
*/
OBJECT("object"),
/**
* String nodes.
*/
STRING("string");
/**
* Reverse map to find a node type out of this type's name.
*/
private static final Map<String, NodeType> NAME_MAP
= new HashMap<String, NodeType>();
/**
* Mapping of {@link com.fasterxml.jackson.core.JsonToken} back to node types (used in {@link
* #getNodeType(com.fasterxml.jackson.databind.JsonNode)}).
*/
private static final Map<JsonToken, NodeType> TOKEN_MAP
= new EnumMap<JsonToken, NodeType>(JsonToken.class);
static {
TOKEN_MAP.put(JsonToken.START_ARRAY, ARRAY);
TOKEN_MAP.put(JsonToken.VALUE_TRUE, BOOLEAN);
TOKEN_MAP.put(JsonToken.VALUE_FALSE, BOOLEAN);
TOKEN_MAP.put(JsonToken.VALUE_NUMBER_INT, INTEGER);
TOKEN_MAP.put(JsonToken.VALUE_NUMBER_FLOAT, NUMBER);
TOKEN_MAP.put(JsonToken.VALUE_NULL, NULL);
TOKEN_MAP.put(JsonToken.START_OBJECT, OBJECT);
TOKEN_MAP.put(JsonToken.VALUE_STRING, STRING);
for (final NodeType type : NodeType.values()) {
NAME_MAP.put(type.name, type);
}
}
/**
* The name for this type, as encountered in a JSON schema.
*/
private final String name;
NodeType(final String name) {
this.name = name;
}
/**
* Given a type name, return the corresponding node type.
*
* @param name the type name
* @return the node type, or null if not found
*/
public static NodeType fromName(final String name) {
return NAME_MAP.get(name);
}
/**
* Given a {@link com.fasterxml.jackson.databind.JsonNode} as an argument, return its type. The argument
* MUST NOT BE NULL, and MUST NOT be a {@link com.fasterxml.jackson.databind.node.MissingNode}.
*
* @param node the node to determine the type of
* @return the type for this node
*/
public static NodeType getNodeType(final JsonNode node) {
final JsonToken token = node.asToken();
return TOKEN_MAP.get(token);
}
@Override
public String toString() {
return name;
}
}

@ -0,0 +1,66 @@
package org.xbib.content.json.jackson;
/**
*
* @param <T> the type parameter
*/
public final class Wrapper<T> {
private final Equivalence<? super T> equivalence;
private final T reference;
public Wrapper(Equivalence<? super T> equivalence, T reference) {
this.equivalence = equivalence;
this.reference = reference;
}
/**
* Returns the (possibly null) reference wrapped by this instance.
* @return the reference
*/
public T get() {
return reference;
}
/**
* Returns {@code true} if {@link Equivalence#equivalent(Object, Object)} applied to the wrapped
* references is {@code true} and both wrappers use the {@link Object#equals(Object) same}
* equivalence.
*/
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
} else if (obj instanceof Wrapper) {
Wrapper<?> that = (Wrapper<?>) obj;
/*
* We cast to Equivalence<Object> here because we can't check the type of the reference held
* by the other wrapper. But, by checking that the Equivalences are equal, we know that
* whatever type it is, it is assignable to the type handled by this wrapper's equivalence.
*/
@SuppressWarnings("unchecked")
Equivalence<Object> equivalence = (Equivalence<Object>) this.equivalence;
return equivalence.equals(that.equivalence)
&& equivalence.equivalent(this.reference, that.reference);
} else {
return false;
}
}
/**
* Returns the result of {@link Equivalence#hash(Object)} applied to the the wrapped reference.
*/
@Override
public int hashCode() {
return equivalence.hash(reference);
}
/**
* Returns a string representation for this equivalence wrapper. The form of this string
* representation is not specified.
*/
@Override
public String toString() {
return equivalence + ".wrap(" + reference + ")";
}
}

@ -0,0 +1,4 @@
/**
* Classes for Jackson support.
*/
package org.xbib.content.json.jackson;

@ -0,0 +1,21 @@
package org.xbib.content.json.mergepatch;
import com.fasterxml.jackson.databind.JsonNode;
/**
*
*/
final class ArrayMergePatch extends JsonMergePatch {
private final JsonNode content;
ArrayMergePatch(final JsonNode content) {
super(content);
this.content = clearNulls(content);
}
@Override
public JsonNode apply(final JsonNode input) {
return content;
}
}

@ -0,0 +1,123 @@
package org.xbib.content.json.mergepatch;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.JsonSerializable;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.xbib.content.json.jackson.JacksonUtils;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
/**
* Implementation of <a href="http://tools.ietf.org/html/draft-ietf-appsawg-json-merge-patch-02">JSON merge patch</a>.
* Unlike JSON Patch, JSON Merge Patch only applies to JSON Objects or JSON
* arrays.
*/
@JsonDeserialize(using = JsonMergePatchDeserializer.class)
public abstract class JsonMergePatch implements JsonSerializable {
protected static final JsonNodeFactory FACTORY = JacksonUtils.nodeFactory();
protected final JsonNode origPatch;
/**
* Protected constructor.
*
* Only necessary for serialization purposes. The patching process
* itself never requires the full node to operate.
*
* @param node the original patch node
*/
protected JsonMergePatch(final JsonNode node) {
origPatch = node;
}
public static JsonMergePatch fromJson(final JsonNode input) {
return input.isArray() ? new ArrayMergePatch(input)
: new ObjectMergePatch(input);
}
/**
* Clear "null values" from a JSON value.
* Non container values are unchanged. For arrays, null elements are
* removed. From objects, members whose values are null are removed.
* This method is recursive, therefore arrays within objects, or objects
* within arrays, or arrays within arrays etc are also affected.
*
* @param node the original JSON value
* @return a JSON value without null values (see description)
*/
protected static JsonNode clearNulls(final JsonNode node) {
if (!node.isContainerNode()) {
return node;
}
return node.isArray() ? clearNullsFromArray(node)
: clearNullsFromObject(node);
}
private static JsonNode clearNullsFromArray(final JsonNode node) {
final ArrayNode ret = FACTORY.arrayNode();
/*
* Cycle through array elements. If the element is a null node itself,
* skip it. Otherwise, add a "cleaned up" element to the result.
*/
for (final JsonNode element : node) {
if (!element.isNull()) {
ret.add(clearNulls(element));
}
}
return ret;
}
private static JsonNode clearNullsFromObject(final JsonNode node) {
final ObjectNode ret = FACTORY.objectNode();
final Iterator<Map.Entry<String, JsonNode>> iterator
= node.fields();
Map.Entry<String, JsonNode> entry;
JsonNode value;
/*
* When faces with an object, cycle through this object's entries.
*
* If the value of the entry is a JSON null, don't include it in the
* result. If not, include a "cleaned up" value for this key instead of
* the original element.
*/
while (iterator.hasNext()) {
entry = iterator.next();
value = entry.getValue();
if (!value.isNull()) {
ret.set(entry.getKey(), clearNulls(value));
}
}
return ret;
}
public abstract JsonNode apply(final JsonNode input);
@Override
public final void serialize(final JsonGenerator jgen,
final SerializerProvider provider)
throws IOException {
jgen.writeTree(origPatch);
}
@Override
public final void serializeWithType(final JsonGenerator jgen,
final SerializerProvider provider, final TypeSerializer typeSer)
throws IOException {
serialize(jgen, provider);
}
}

@ -0,0 +1,30 @@
package org.xbib.content.json.mergepatch;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import java.io.IOException;
/**
* Custom {@link com.fasterxml.jackson.databind.JsonDeserializer} for {@link JsonMergePatch} instances
* Unlike "real" JSON Patches (ie, as defined by RFC 6902), JSON merge patch
* instances are "free form", they can be either JSON arrays or JSON objects
* without any restriction on the contents; only the content itself may guide
* the patching process (null elements in arrays, null values in objects).
* Jackson does not provide a deserializer for such a case; we therefore
* write our own here.
*/
public final class JsonMergePatchDeserializer extends JsonDeserializer<JsonMergePatch> {
@Override
public JsonMergePatch deserialize(final JsonParser jp, final DeserializationContext ctxt)
throws IOException {
final JsonNode node = jp.readValueAs(JsonNode.class);
if (!node.isContainerNode()) {
throw new IOException("expected either an array or an object");
}
return node.isArray() ? new ArrayMergePatch(node)
: new ObjectMergePatch(node);
}
}

@ -0,0 +1,83 @@
package org.xbib.content.json.mergepatch;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.xbib.content.json.jackson.JacksonUtils;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* Merge patch for a JSON Object
* <p>
* <p>This only takes care of the top level, and delegates to other {@link
* JsonMergePatch} instances for deeper levels.</p>
*/
final class ObjectMergePatch extends JsonMergePatch {
private final Map<String, JsonNode> fields;
private final Set<String> removals;
ObjectMergePatch(final JsonNode content) {
super(content);
fields = JacksonUtils.asMap(content);
removals = new HashSet<>();
for (final Map.Entry<String, JsonNode> entry : fields.entrySet()) {
if (entry.getValue().isNull()) {
removals.add(entry.getKey());
}
}
fields.keySet().removeAll(removals);
}
private static JsonNode mapToNode(final Map<String, JsonNode> map) {
final ObjectNode ret = FACTORY.objectNode();
return ret.setAll(map);
}
@Override
public JsonNode apply(final JsonNode input) {
if (!input.isObject()) {
return mapToNode(fields);
}
final Map<String, JsonNode> map = JacksonUtils.asMap(input);
// Remove all entries which must be removed
map.keySet().removeAll(removals);
// Now cycle through what is left
String memberName;
JsonNode patchNode;
for (final Map.Entry<String, JsonNode> entry : map.entrySet()) {
memberName = entry.getKey();
patchNode = fields.get(memberName);
// Leave untouched if no mention in the patch
if (patchNode == null) {
continue;
}
// If the patch node is a primitive type, replace in the result.
// Reminder: there cannot be a JSON null anymore
if (!patchNode.isContainerNode()) {
entry.setValue(patchNode); // no need for .deepCopy()
continue;
}
final JsonMergePatch patch = JsonMergePatch.fromJson(patchNode);
entry.setValue(patch.apply(entry.getValue()));
}
// Finally, if there are members in the patch not present in the input,
// fill in members
fields.keySet().removeAll(map.keySet());
for (Map.Entry<String, JsonNode> entry : fields.entrySet()) {
map.put(entry.getKey(), clearNulls(entry.getValue()));
}
return mapToNode(map);
}
}

@ -0,0 +1,4 @@
/**
* Classes for JSON merge/patch operation.
*/
package org.xbib.content.json.mergepatch;

@ -0,0 +1,105 @@
package org.xbib.content.json.patch;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.xbib.content.json.pointer.JsonPointer;
import org.xbib.content.json.pointer.ReferenceToken;
import org.xbib.content.json.pointer.TokenResolver;
/**
* JSON Patch {@code add} operation.
* For this operation, {@code path} is the JSON Pointer where the value
* should be added, and {@code value} is the value to add.
*
* Note that if the target value pointed to by {@code path} already exists,
* it is replaced. In this case, {@code add} is equivalent to {@code replace}.
*
*
* Note also that a value will be created at the target path <b>if and only
* if</b> the immediate parent of that value exists (and is of the correct
* type).
*
* Finally, if the last reference token of the JSON Pointer is {@code -} and
* the immediate parent is an array, the given value is added at the end of the
* array. For instance, applying:
*
* <pre>
* { "op": "add", "path": "/-", "value": 3 }
* </pre>
*
* to
*
* <pre>
* [ 1, 2 ]
* </pre>
*
* will give
*
* <pre>
* [ 1, 2, 3 ]
* </pre>
*/
public final class AddOperation extends PathValueOperation {
private static final ReferenceToken LAST_ARRAY_ELEMENT = ReferenceToken.fromRaw("-");
@JsonCreator
public AddOperation(@JsonProperty("path") final JsonPointer path,
@JsonProperty("value") final JsonNode value) {
super("add", path, value);
}
@Override
public JsonNode apply(final JsonNode node) throws JsonPatchException {
if (path.isEmpty()) {
return value;
}
/*
* Check the parent node: it must exist and be a container (ie an array
* or an object) for the add operation to work.
*/
final JsonNode parentNode = path.parent().path(node);
if (parentNode.isMissingNode()) {
throw new JsonPatchException("no such parent");
}
if (!parentNode.isContainerNode()) {
throw new JsonPatchException("parent not container");
}
return parentNode.isArray()
? addToArray(path, node)
: addToObject(path, node);
}
private JsonNode addToArray(final JsonPointer path, final JsonNode node) throws JsonPatchException {
final JsonNode ret = node.deepCopy();
final ArrayNode target = (ArrayNode) path.parent().get(ret);
final TokenResolver<JsonNode> token = path.getLast();
if (token.getToken().equals(LAST_ARRAY_ELEMENT)) {
target.add(value);
return ret;
}
final int size = target.size();
final int index;
try {
index = Integer.parseInt(token.toString());
} catch (NumberFormatException ignored) {
throw new JsonPatchException("not an index: " + token.toString());
}
if (index < 0 || index > size) {
throw new JsonPatchException("no such index: " + index);
}
target.insert(index, value);
return ret;
}
private JsonNode addToObject(final JsonPointer path, final JsonNode node) {
final JsonNode ret = node.deepCopy();
final ObjectNode target = (ObjectNode) path.parent().get(ret);
target.set(path.getLast().getToken().getRaw(), value);
return ret;
}
}

@ -0,0 +1,38 @@
package org.xbib.content.json.patch;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonNode;
import org.xbib.content.json.pointer.JsonPointer;
/**
* JSON Patch {@code copy} operation.
* For this operation, {@code from} is the JSON Pointer of the value to copy,
* and {@code path} is the destination where the value should be copied.
* As for {@code add}:
*
* <ul>
* <li>the value at the destination path is either created or replaced;</li>
* <li>it is created only if the immediate parent exists;</li>
* <li>{@code -} appends at the end of an array.</li>
* </ul>
*
* It is an error if {@code from} fails to resolve to a JSON value.
*/
public final class CopyOperation extends DualPathOperation {
@JsonCreator
public CopyOperation(@JsonProperty("from") final JsonPointer from,
@JsonProperty("path") final JsonPointer path) {
super("copy", from, path);
}
@Override
public JsonNode apply(final JsonNode node)
throws JsonPatchException {
final JsonNode dupData = from.path(node).deepCopy();
if (dupData.isMissingNode()) {
throw new JsonPatchException("no such path");
}
return new AddOperation(path, dupData).apply(node);
}
}

@ -0,0 +1,55 @@
package org.xbib.content.json.patch;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import org.xbib.content.json.pointer.JsonPointer;
import java.io.IOException;
/**
* Base class for JSON Patch operations taking two JSON Pointers as arguments.
*/
public abstract class DualPathOperation extends JsonPatchOperation {
@JsonSerialize(using = ToStringSerializer.class)
protected final JsonPointer from;
/**
* Protected constructor.
*
* @param op operation name
* @param from source path
* @param path destination path
*/
protected DualPathOperation(final String op, final JsonPointer from,
final JsonPointer path) {
super(op, path);
this.from = from;
}
@Override
public final void serialize(final JsonGenerator jgen,
final SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeStartObject();
jgen.writeStringField("op", op);
jgen.writeStringField("path", path.toString());
jgen.writeStringField("from", from.toString());
jgen.writeEndObject();
}
@Override
public final void serializeWithType(final JsonGenerator jgen,
final SerializerProvider provider, final TypeSerializer typeSer)
throws IOException, JsonProcessingException {
serialize(jgen, provider);
}
@Override
public final String toString() {
return "op: " + op + "; from: \"" + from + "\"; path: \"" + path + '"';
}
}

@ -0,0 +1,135 @@
package org.xbib.content.json.patch;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.JsonSerializable;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import org.xbib.content.json.jackson.JacksonUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* Implementation of JSON Patch.
* <a href="http://tools.ietf.org/html/draft-ietf-appsawg-json-patch-10">JSON
* Patch</a>, as its name implies, is an IETF draft describing a mechanism to
* apply a patch to any JSON value. This implementation covers all operations
* according to the specification; however, there are some subtle differences
* with regards to some operations which are covered in these operations'
* respective documentation.
* An example of a JSON Patch is as follows:
* <code>
* [
* {
* "op": "add",
* "path": "/-",
* "value": {
* "productId": 19,
* "name": "Duvel",
* "type": "beer"
* }
* }
* ]
* </code>
* This patch contains a single operation which adds an item at the end of
* an array. A JSON Patch can contain more than one operation; in this case, all
* operations are applied to the input JSON value in their order of appearance,
* until all operations are applied or an error condition is encountered.
* The main point where this implementation differs from the specification
* is initial JSON parsing. The draft says:
*
* <pre>
* Operation objects MUST have exactly one "op" member
* </pre>
*
* and
*
* <pre>
* Additionally, operation objects MUST have exactly one "path" member.
* </pre>
*
* However, obeying these to the letter forces constraints on the JSON
* parser. Here, these constraints are not enforced, which means:
*
* <pre>
* [ { "op": "add", "op": "remove", "path": "/x" } ]
* </pre>
*
* is parsed (as a {@code remove} operation, since it appears last).
* IMPORTANT NOTE: the JSON Patch is supposed to be VALID when the
* constructor for this class ({@link JsonPatch#fromJson(com.fasterxml.jackson.databind.JsonNode)} is used.
*/
public final class JsonPatch
implements JsonSerializable {
/**
* List of operations.
*/
private final List<JsonPatchOperation> operations;
/**
* Package-visible constructor.
*
* Visible only for testing purposes. Also used for deserialization.
*
* @param operations the list of operations for this patch
* @see JsonPatchOperation
*/
@JsonCreator
JsonPatch(final List<JsonPatchOperation> operations) {
this.operations = new ArrayList<>(operations);
}
/**
* Static factory method to build a JSON Patch out of a JSON representation.
*
* @param node the JSON representation of the generated JSON Patch
* @return a JSON Patch
* @throws java.io.IOException input is not a valid JSON patch
*/
public static JsonPatch fromJson(final JsonNode node) throws IOException {
return JacksonUtils.getReader().forType(JsonPatch.class)
.readValue(node);
}
/**
* Apply this patch to a JSON value.
*
* @param node the value to apply the patch to
* @return the patched JSON value
* @throws JsonPatchException if patch fails
*/
public JsonNode apply(final JsonNode node) throws JsonPatchException {
JsonNode ret = node;
for (final JsonPatchOperation operation : operations) {
ret = operation.apply(ret);
}
return ret;
}
@Override
public String toString() {
return operations.toString();
}
@Override
public void serialize(final JsonGenerator jgen,
final SerializerProvider provider)
throws IOException {
jgen.writeStartArray();
for (final JsonPatchOperation op : operations) {
op.serialize(jgen, provider);
}
jgen.writeEndArray();
}
@Override
public void serializeWithType(final JsonGenerator jgen,
final SerializerProvider provider, final TypeSerializer typeSer)
throws IOException {
serialize(jgen, provider);
}
}

@ -0,0 +1,12 @@
package org.xbib.content.json.patch;
/**
*
*/
public final class JsonPatchException extends Exception {
private static final long serialVersionUID = -7086990008150217950L;
public JsonPatchException(final String message) {
super(message);
}
}

@ -0,0 +1,70 @@
package org.xbib.content.json.patch;
import static com.fasterxml.jackson.annotation.JsonSubTypes.Type;
import static com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import static com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.JsonSerializable;
import org.xbib.content.json.pointer.JsonPointer;
/**
* Base abstract class for one patch operation
*
* <p>Two more abstract classes extend this one according to the arguments of
* the operation:</p>
*
* <ul>
* <li>{@link DualPathOperation} for operations taking a second pointer as
* an argument ({@code copy} and {@code move});</li>
* <li>{@link PathValueOperation} for operations taking a value as an
* argument ({@code add}, {@code replace} and {@code test}).</li>
* </ul>
*/
@JsonTypeInfo(use = Id.NAME, include = As.PROPERTY, property = "op")
@JsonSubTypes({
@Type(name = "add", value = AddOperation.class),
@Type(name = "copy", value = CopyOperation.class),
@Type(name = "move", value = MoveOperation.class),
@Type(name = "remove", value = RemoveOperation.class),
@Type(name = "replace", value = ReplaceOperation.class),
@Type(name = "test", value = TestOperation.class)
})
@JsonIgnoreProperties(ignoreUnknown = true)
public abstract class JsonPatchOperation implements JsonSerializable {
protected final String op;
/*
* Note: no need for a custom deserializer, Jackson will try and find a
* constructor with a single string argument and use it.
*
* However, we need to serialize using .toString().
*/
protected final JsonPointer path;
/**
* Constructor.
*
* @param op the operation name
* @param path the JSON Pointer for this operation
*/
protected JsonPatchOperation(final String op, final JsonPointer path) {
this.op = op;
this.path = path;
}
/**
* Apply this operation to a JSON value.
*
* @param node the value to patch
* @return the patched value
* @throws JsonPatchException if patch fails
*/
public abstract JsonNode apply(final JsonNode node) throws JsonPatchException;
@Override
public abstract String toString();
}

@ -0,0 +1,56 @@
package org.xbib.content.json.patch;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonNode;
import org.xbib.content.json.pointer.JsonPointer;
/**
* JSON Patch {@code move} operation.
* For this operation, {@code from} points to the value to move, and {@code
* path} points to the new location of the moved value.
* As for {@code add}:
* <ul>
* <li>the value at the destination path is either created or replaced;</li>
* <li>it is created only if the immediate parent exists;</li>
* <li>{@code -} appends at the end of an array.</li>
* </ul>
* It is an error condition if {@code from} does not point to a JSON value.
* The specification adds another rule that the {@code from} path must not be
* an immediate parent of {@code path}. Unfortunately, that doesn't really work.
* Consider this patch:
* <pre>
* { "op": "move", "from": "/0", "path": "/0/x" }
* </pre>
* Even though {@code /0} is an immediate parent of {@code /0/x}, when this
* patch is applied to:
* <pre>
* [ "victim", {} ]
* </pre>
* it actually succeeds and results in the patched value:
* <pre>
* [ { "x": "victim" } ]
* </pre>
*/
public final class MoveOperation extends DualPathOperation {
@JsonCreator
public MoveOperation(@JsonProperty("from") final JsonPointer from,
@JsonProperty("path") final JsonPointer path) {
super("move", from, path);
}
@Override
public JsonNode apply(final JsonNode node) throws JsonPatchException {
if (from.equals(path)) {
return node.deepCopy();
}
final JsonNode movedNode = from.path(node);
if (movedNode.isMissingNode()) {
throw new JsonPatchException("no such path: " + path);
}
final JsonPatchOperation remove = new RemoveOperation(from);
final JsonPatchOperation add = new AddOperation(path, movedNode);
return add.apply(remove.apply(node));
}
}

@ -0,0 +1,57 @@
package org.xbib.content.json.patch;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import org.xbib.content.json.pointer.JsonPointer;
import java.io.IOException;
/**
* Base class for patch operations taking a value in addition to a path.
*/
public abstract class PathValueOperation
extends JsonPatchOperation {
@JsonSerialize
protected final JsonNode value;
/**
* Protected constructor.
*
* @param op operation name
* @param path affected path
* @param value JSON value
*/
protected PathValueOperation(final String op, final JsonPointer path,
final JsonNode value) {
super(op, path);
this.value = value.deepCopy();
}
@Override
public final void serialize(final JsonGenerator jgen,
final SerializerProvider provider)
throws IOException {
jgen.writeStartObject();
jgen.writeStringField("op", op);
jgen.writeStringField("path", path.toString());
jgen.writeFieldName("value");
jgen.writeTree(value);
jgen.writeEndObject();
}
@Override
public final void serializeWithType(final JsonGenerator jgen,
final SerializerProvider provider, final TypeSerializer typeSer)
throws IOException, JsonProcessingException {
serialize(jgen, provider);
}
@Override
public final String toString() {
return "op: " + op + "; path: \"" + path + "\"; value: " + value;
}
}

@ -0,0 +1,69 @@
package org.xbib.content.json.patch;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.MissingNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.xbib.content.json.pointer.JsonPointer;
import java.io.IOException;
/**
* JSON Path {@code remove} operation
* This operation only takes one pointer ({@code path}) as an argument. It
* is an error condition if no JSON value exists at that pointer.
*/
public final class RemoveOperation
extends JsonPatchOperation {
@JsonCreator
public RemoveOperation(@JsonProperty("path") final JsonPointer path) {
super("remove", path);
}
@Override
public JsonNode apply(final JsonNode node)
throws JsonPatchException {
if (path.isEmpty()) {
return MissingNode.getInstance();
}
if (path.path(node).isMissingNode()) {
throw new JsonPatchException("no such path");
}
final JsonNode ret = node.deepCopy();
final JsonNode parentNode = path.parent().get(ret);
final String raw = path.getLast().getToken().getRaw();
if (parentNode.isObject()) {
((ObjectNode) parentNode).remove(raw);
} else {
((ArrayNode) parentNode).remove(Integer.parseInt(raw));
}
return ret;
}
@Override
public void serialize(final JsonGenerator jgen,
final SerializerProvider provider)
throws IOException {
jgen.writeStartObject();
jgen.writeStringField("op", "remove");
jgen.writeStringField("path", path.toString());
jgen.writeEndObject();
}
@Override
public void serializeWithType(final JsonGenerator jgen,
final SerializerProvider provider, final TypeSerializer typeSer)
throws IOException {
serialize(jgen, provider);
}
@Override
public String toString() {
return "op: " + op + "; path: \"" + path + '"';
}
}

@ -0,0 +1,45 @@
package org.xbib.content.json.patch;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.xbib.content.json.pointer.JsonPointer;
/**
* JSON Patch {@code replace} operation
* For this operation, {@code path} points to the value to replace, and
* {@code value} is the replacement value.
* It is an error condition if {@code path} does not point to an actual JSON
* value.
*/
public final class ReplaceOperation
extends PathValueOperation {
@JsonCreator
public ReplaceOperation(@JsonProperty("path") final JsonPointer path,
@JsonProperty("value") final JsonNode value) {
super("replace", path, value);
}
@Override
public JsonNode apply(final JsonNode node)
throws JsonPatchException {
if (path.path(node).isMissingNode()) {
throw new JsonPatchException("no such path");
}
final JsonNode replacement = value.deepCopy();
if (path.isEmpty()) {
return replacement;
}
final JsonNode ret = node.deepCopy();
final JsonNode parent = path.parent().get(ret);
final String rawToken = path.getLast().getToken().getRaw();
if (parent.isObject()) {
((ObjectNode) parent).set(rawToken, replacement);
} else {
((ArrayNode) parent).set(Integer.parseInt(rawToken), replacement);
}
return ret;
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save