From 14643b140f566eaf567435ca75c98ea3cb386a00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=CC=88rg=20Prante?= Date: Mon, 14 Aug 2017 23:00:13 +0200 Subject: [PATCH] add xbib net dependency, update to Gradle 4.1, add some convenience methods --- build.gradle | 39 +- content-core/build.gradle | 2 +- .../org/xbib/content/XContentGenerator.java | 7 +- .../java/org/xbib/content/io/BytesArray.java | 8 + .../settings/AbstractSettingsLoader.java | 29 ++ .../xbib/content/settings/SettingsTest.java | 12 +- content-json/build.gradle | 4 +- .../rdf/io/xml/AbstractXmlHandler.java | 3 +- .../content/rdf/io/xml/XmlContentParser.java | 3 +- .../content/rdf/util/NormalizeEolFilter.java | 86 ++++ .../content/rdf/util/SimpleFilterReader.java | 98 ++++ .../org/xbib/content/rdf/io/xml/OAITest.java | 11 +- .../content/rdf/io/xml/XmlReaderTest.java | 10 +- .../org/xbib/content/rdf/io/xml/dc.ttl | 6 +- .../org/xbib/content/rdf/io/xml/oai.ttl | 2 +- content-resource/build.gradle | 5 + .../java/org/xbib/content/resource/IRI.java | 62 +-- .../resource/scheme/AbstractScheme.java | 43 -- .../resource/scheme/DefaultScheme.java | 12 - .../content/resource/scheme/FtpScheme.java | 16 - .../content/resource/scheme/HttpScheme.java | 56 --- .../content/resource/scheme/HttpsScheme.java | 12 - .../xbib/content/resource/scheme/Scheme.java | 17 - .../resource/scheme/SchemeRegistry.java | 59 --- .../content/resource/scheme/package-info.java | 4 - .../content/resource/url/PercentDecoder.java | 195 -------- .../content/resource/url/PercentEncoder.java | 187 ------- .../xbib/content/resource/url/UrlBuilder.java | 472 ------------------ .../content/resource/url/UrlEncoding.java | 148 ------ .../resource/url/UrlPercentEncoders.java | 166 ------ .../content/resource/url/package-info.java | 4 - .../resource/url/PercentEncoderTest.java | 87 ---- .../content/resource/url/UrlBuilderTest.java | 433 ---------------- .../content/resource/url/package-info.java | 4 - content-smile/build.gradle | 4 +- content-xml/build.gradle | 3 +- .../content/xml/XmlXContentGenerator.java | 4 +- .../org/xbib/content/xml/util/XMLUtil.java | 18 +- .../content/xml/XContentXmlBuilderTest.java | 2 +- content-yaml/build.gradle | 2 +- gradle.properties | 5 +- gradle/publish.gradle | 6 +- gradle/sonarqube.gradle | 10 +- gradle/wrapper/gradle-wrapper.jar | Bin 54227 -> 54708 bytes gradle/wrapper/gradle-wrapper.properties | 4 +- gradlew | 6 +- 46 files changed, 347 insertions(+), 2019 deletions(-) create mode 100644 content-rdf/src/main/java/org/xbib/content/rdf/util/NormalizeEolFilter.java create mode 100644 content-rdf/src/main/java/org/xbib/content/rdf/util/SimpleFilterReader.java delete mode 100644 content-resource/src/main/java/org/xbib/content/resource/scheme/AbstractScheme.java delete mode 100644 content-resource/src/main/java/org/xbib/content/resource/scheme/DefaultScheme.java delete mode 100644 content-resource/src/main/java/org/xbib/content/resource/scheme/FtpScheme.java delete mode 100644 content-resource/src/main/java/org/xbib/content/resource/scheme/HttpScheme.java delete mode 100644 content-resource/src/main/java/org/xbib/content/resource/scheme/HttpsScheme.java delete mode 100644 content-resource/src/main/java/org/xbib/content/resource/scheme/Scheme.java delete mode 100644 content-resource/src/main/java/org/xbib/content/resource/scheme/SchemeRegistry.java delete mode 100644 content-resource/src/main/java/org/xbib/content/resource/scheme/package-info.java delete mode 100755 content-resource/src/main/java/org/xbib/content/resource/url/PercentDecoder.java delete mode 100755 content-resource/src/main/java/org/xbib/content/resource/url/PercentEncoder.java delete mode 100755 content-resource/src/main/java/org/xbib/content/resource/url/UrlBuilder.java delete mode 100644 content-resource/src/main/java/org/xbib/content/resource/url/UrlEncoding.java delete mode 100755 content-resource/src/main/java/org/xbib/content/resource/url/UrlPercentEncoders.java delete mode 100644 content-resource/src/main/java/org/xbib/content/resource/url/package-info.java delete mode 100755 content-resource/src/test/java/org/xbib/content/resource/url/PercentEncoderTest.java delete mode 100755 content-resource/src/test/java/org/xbib/content/resource/url/UrlBuilderTest.java delete mode 100644 content-resource/src/test/java/org/xbib/content/resource/url/package-info.java diff --git a/build.gradle b/build.gradle index 877bb0e..c543546 100644 --- a/build.gradle +++ b/build.gradle @@ -1,16 +1,25 @@ + plugins { - id "org.sonarqube" version "2.2" - id "org.ajoberstar.github-pages" version "1.6.0-rc.1" - id "org.xbib.gradle.plugin.jbake" version "1.2.1" + id "org.sonarqube" version "2.5" + id "org.xbib.gradle.plugin.asciidoctor" version "1.5.4.1.0" + id "io.codearte.nexus-staging" version "0.7.0" } -ext { - versions = [ - 'jackson' : '2.8.4' - ] -} +printf "Host: %s\nOS: %s %s %s\nJVM: %s %s %s %s\nGroovy: %s\nGradle: %s\n" + + "Build: group: ${project.group} name: ${project.name} version: ${project.version}\n", + InetAddress.getLocalHost(), + System.getProperty("os.name"), + System.getProperty("os.arch"), + System.getProperty("os.version"), + System.getProperty("java.version"), + System.getProperty("java.vm.version"), + System.getProperty("java.vm.vendor"), + System.getProperty("java.vm.name"), + GroovySystem.getVersion(), + gradle.gradleVersion apply plugin: 'build-dashboard' +apply plugin: "io.codearte.nexus-staging" allprojects { @@ -21,18 +30,20 @@ allprojects { apply plugin: 'pmd' apply plugin: 'checkstyle' apply plugin: "jacoco" + apply plugin: 'org.xbib.gradle.plugin.asciidoctor' repositories { mavenCentral() } configurations { + asciidoclet wagon } dependencies { testCompile 'junit:junit:4.12' - wagon 'org.apache.maven.wagon:wagon-ssh-external:2.10' + wagon 'org.apache.maven.wagon:wagon-ssh:2.12' } sourceCompatibility = JavaVersion.VERSION_1_8 @@ -43,6 +54,12 @@ allprojects { options.compilerArgs << "-Xlint:all" << "-profile" << "compact1" } + jar { + manifest { + attributes('Implementation-Version': project.version) + } + } + test { testLogging { showStandardStreams = false @@ -50,6 +67,10 @@ allprojects { } } + clean { + delete 'out' + } + task sourcesJar(type: Jar, dependsOn: classes) { classifier 'sources' from sourceSets.main.allSource diff --git a/content-core/build.gradle b/content-core/build.gradle index e45e07a..0d26d6f 100644 --- a/content-core/build.gradle +++ b/content-core/build.gradle @@ -1,3 +1,3 @@ dependencies { - compile "com.fasterxml.jackson.core:jackson-core:${versions.jackson}" + compile "com.fasterxml.jackson.core:jackson-core:${project.property('jackson.version')}" } diff --git a/content-core/src/main/java/org/xbib/content/XContentGenerator.java b/content-core/src/main/java/org/xbib/content/XContentGenerator.java index 2ad5fc3..02d20c3 100644 --- a/content-core/src/main/java/org/xbib/content/XContentGenerator.java +++ b/content-core/src/main/java/org/xbib/content/XContentGenerator.java @@ -2,6 +2,8 @@ package org.xbib.content; import org.xbib.content.io.BytesReference; +import java.io.Closeable; +import java.io.Flushable; import java.io.IOException; import java.io.OutputStream; import java.math.BigDecimal; @@ -10,7 +12,7 @@ import java.math.BigInteger; /** * */ -public interface XContentGenerator { +public interface XContentGenerator extends Flushable, Closeable { XContent content(); @@ -115,7 +117,4 @@ public interface XContentGenerator { void copyCurrentStructure(XContentParser parser) throws IOException; - void flush() throws IOException; - - void close() throws IOException; } diff --git a/content-core/src/main/java/org/xbib/content/io/BytesArray.java b/content-core/src/main/java/org/xbib/content/io/BytesArray.java index 153528e..f228c5a 100644 --- a/content-core/src/main/java/org/xbib/content/io/BytesArray.java +++ b/content-core/src/main/java/org/xbib/content/io/BytesArray.java @@ -40,6 +40,14 @@ public class BytesArray implements BytesReference { this.length = length; } + public void write(byte[] b) { + byte[] c = new byte[length + b.length]; + System.arraycopy(bytes, 0, c, 0, length); + System.arraycopy(b, 0, c, bytes.length, b.length); + this.bytes = c; + this.offset = 0; + this.length = c.length; + } @Override public byte get(int index) { diff --git a/content-core/src/main/java/org/xbib/content/settings/AbstractSettingsLoader.java b/content-core/src/main/java/org/xbib/content/settings/AbstractSettingsLoader.java index 3078777..a24aff7 100644 --- a/content-core/src/main/java/org/xbib/content/settings/AbstractSettingsLoader.java +++ b/content-core/src/main/java/org/xbib/content/settings/AbstractSettingsLoader.java @@ -1,7 +1,10 @@ package org.xbib.content.settings; import org.xbib.content.XContent; +import org.xbib.content.XContentGenerator; import org.xbib.content.XContentParser; +import org.xbib.content.io.BytesReference; +import org.xbib.content.io.BytesStreamOutput; import java.io.IOException; import java.util.ArrayList; @@ -24,6 +27,32 @@ public abstract class AbstractSettingsLoader implements SettingsLoader { } } + public Map load(BytesReference bytesReference) throws IOException { + try (XContentParser parser = content().createParser(bytesReference)) { + return load(parser); + } + } + + public String flatMapAsString(BytesReference bytesReference) throws IOException { + try (XContentParser parser = content().createParser(bytesReference); + BytesStreamOutput bytesStreamOutput = new BytesStreamOutput(); + XContentGenerator generator = content().createGenerator(bytesStreamOutput)) { + generator.writeStartObject(); + for (Map.Entry entry : load(parser).entrySet()) { + generator.writeFieldName(entry.getKey()); + String value = entry.getValue(); + if (value == null) { + generator.writeNull(); + } else { + generator.writeString(value); + } + } + generator.writeEndObject(); + generator.flush(); + return bytesStreamOutput.bytes().toUtf8(); + } + } + public Map load(XContentParser xContentParser) throws IOException { StringBuilder sb = new StringBuilder(); Map map = new HashMap<>(); diff --git a/content-core/src/test/java/org/xbib/content/settings/SettingsTest.java b/content-core/src/test/java/org/xbib/content/settings/SettingsTest.java index 422b95b..6a18d64 100644 --- a/content-core/src/test/java/org/xbib/content/settings/SettingsTest.java +++ b/content-core/src/test/java/org/xbib/content/settings/SettingsTest.java @@ -1,10 +1,10 @@ package org.xbib.content.settings; - import org.junit.Assert; import org.junit.Test; -import org.xbib.content.XContentBuilder; import org.xbib.content.XContentHelper; +import org.xbib.content.io.BytesArray; +import org.xbib.content.io.BytesReference; import org.xbib.content.json.JsonSettingsLoader; import org.xbib.content.json.JsonXContent; @@ -123,4 +123,12 @@ public class SettingsTest extends Assert { assertEquals("{\"a.b\":\"c\"}", result); } + @Test + public void testFlatMapAsString() throws IOException { + String s = "{\"a\":{\"b\":\"c\"}}"; + BytesReference ref = new BytesArray(s.getBytes(StandardCharsets.UTF_8)); + JsonSettingsLoader loader = new JsonSettingsLoader(); + String result = loader.flatMapAsString(ref); + assertEquals("{\"a.b\":\"c\"}", result); + } } diff --git a/content-json/build.gradle b/content-json/build.gradle index 3006b13..8ec155f 100644 --- a/content-json/build.gradle +++ b/content-json/build.gradle @@ -1,5 +1,5 @@ dependencies { - compile "com.fasterxml.jackson.core:jackson-databind:${versions.jackson}" + compile "com.fasterxml.jackson.core:jackson-databind:${project.property('jackson.version')}" testCompile('junit:junit:4.12') { exclude group: 'org.hamcrest' } @@ -7,4 +7,4 @@ dependencies { exclude group: 'org.hamcrest' } testCompile 'org.hamcrest:hamcrest-all:1.3' -} \ No newline at end of file +} diff --git a/content-rdf/src/main/java/org/xbib/content/rdf/io/xml/AbstractXmlHandler.java b/content-rdf/src/main/java/org/xbib/content/rdf/io/xml/AbstractXmlHandler.java index 6f38534..b72d206 100644 --- a/content-rdf/src/main/java/org/xbib/content/rdf/io/xml/AbstractXmlHandler.java +++ b/content-rdf/src/main/java/org/xbib/content/rdf/io/xml/AbstractXmlHandler.java @@ -5,6 +5,7 @@ import org.xbib.content.rdf.RdfContentParams; import org.xbib.content.rdf.Resource; import org.xbib.content.rdf.internal.DefaultAnonymousResource; import org.xbib.content.resource.IRI; +import org.xbib.content.xml.util.XMLUtil; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; @@ -185,7 +186,7 @@ public abstract class AbstractXmlHandler

} public String content() { - String s = content.toString().trim(); + String s = XMLUtil.sanitizeToLineFeed(content.toString()).trim(); return s.length() > 0 ? s : null; } diff --git a/content-rdf/src/main/java/org/xbib/content/rdf/io/xml/XmlContentParser.java b/content-rdf/src/main/java/org/xbib/content/rdf/io/xml/XmlContentParser.java index b167ee4..ec5cc1d 100644 --- a/content-rdf/src/main/java/org/xbib/content/rdf/io/xml/XmlContentParser.java +++ b/content-rdf/src/main/java/org/xbib/content/rdf/io/xml/XmlContentParser.java @@ -5,6 +5,7 @@ import org.xbib.content.rdf.RdfContentParams; import org.xbib.content.rdf.RdfContentParser; import org.xbib.content.rdf.RdfContentType; import org.xbib.content.rdf.StandardRdfContentType; +import org.xbib.content.rdf.util.NormalizeEolFilter; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; @@ -37,7 +38,7 @@ public class XmlContentParser

implements RdfContentP } public XmlContentParser(Reader reader) { - this.reader = reader; + this.reader = new NormalizeEolFilter(reader, System.getProperty("line.separator"), true); } @Override diff --git a/content-rdf/src/main/java/org/xbib/content/rdf/util/NormalizeEolFilter.java b/content-rdf/src/main/java/org/xbib/content/rdf/util/NormalizeEolFilter.java new file mode 100644 index 0000000..09dec7a --- /dev/null +++ b/content-rdf/src/main/java/org/xbib/content/rdf/util/NormalizeEolFilter.java @@ -0,0 +1,86 @@ +package org.xbib.content.rdf.util; + +import java.io.IOException; +import java.io.Reader; + +/** + * + */ +public class NormalizeEolFilter extends SimpleFilterReader { + + private boolean previousWasEOL; + + private boolean fixLast; + + private int normalizedEOL = 0; + + private char[] eol = null; + + public NormalizeEolFilter(Reader in, String eolString, boolean fixLast) { + super(in); + eol = eolString.toCharArray(); + this.fixLast = fixLast; + } + + public int read() throws IOException { + int thisChar = super.read(); + if (normalizedEOL == 0) { + int numEOL = 0; + boolean atEnd = false; + switch (thisChar) { + case '\u001A': + int c = super.read(); + if (c == -1) { + atEnd = true; + if (fixLast && !previousWasEOL) { + numEOL = 1; + push(thisChar); + } + } else { + push(c); + } + break; + case -1: + atEnd = true; + if (fixLast && !previousWasEOL) { + numEOL = 1; + } + break; + case '\n': + numEOL = 1; + break; + case '\r': + numEOL = 1; + int c1 = super.read(); + int c2 = super.read(); + if (c1 != '\r' || c2 != '\n') { + if (c1 == '\r') { + numEOL = 2; + push(c2); + } else if (c1 == '\n') { + push(c2); + } else { + push(c2); + push(c1); + } + } + break; + default: + break; + } + if (numEOL > 0) { + while (numEOL-- > 0) { + push(eol); + normalizedEOL += eol.length; + } + previousWasEOL = true; + thisChar = read(); + } else if (!atEnd) { + previousWasEOL = false; + } + } else { + normalizedEOL--; + } + return thisChar; + } +} diff --git a/content-rdf/src/main/java/org/xbib/content/rdf/util/SimpleFilterReader.java b/content-rdf/src/main/java/org/xbib/content/rdf/util/SimpleFilterReader.java new file mode 100644 index 0000000..5e7d85f --- /dev/null +++ b/content-rdf/src/main/java/org/xbib/content/rdf/util/SimpleFilterReader.java @@ -0,0 +1,98 @@ +package org.xbib.content.rdf.util; + +import java.io.IOException; +import java.io.Reader; + +/** + * This filter reader redirects all read I/O methods through its own read() method. + */ +public class SimpleFilterReader extends Reader { + + private static final int PREEMPT_BUFFER_LENGTH = 16; + + private Reader in; + + private int[] preempt = new int[PREEMPT_BUFFER_LENGTH]; + + private int preemptIndex = 0; + + public SimpleFilterReader(Reader in) { + this.in = in; + } + + public void push(char c) { + push((int) c); + } + + public void push(int c) { + try { + preempt[preemptIndex++] = c; + } catch (ArrayIndexOutOfBoundsException e) { + int[] p2 = new int[preempt.length * 2]; + System.arraycopy(preempt, 0, p2, 0, preempt.length); + preempt = p2; + push(c); + } + } + + public void push(char[] cs, int start, int length) { + for (int i = start + length - 1; i >= start;) { + push(cs[i--]); + } + } + + public void push(char[] cs) { + push(cs, 0, cs.length); + } + + @Override + public int read() throws IOException { + return preemptIndex > 0 ? preempt[--preemptIndex] : in.read(); + } + + @Override + public void close() throws IOException { + in.close(); + } + + @Override + public void reset() throws IOException { + in.reset(); + } + + @Override + public boolean markSupported() { + return in.markSupported(); + } + + @Override + public boolean ready() throws IOException { + return in.ready(); + } + + @Override + public void mark(int i) throws IOException { + in.mark(i); + } + + @Override + public long skip(long i) throws IOException { + return in.skip(i); + } + + @Override + public int read(char[] buf) throws IOException { + return read(buf, 0, buf.length); + } + + @Override + public int read(char[] buf, int start, int length) throws IOException { + int count = 0; + int c = 0; + while (length-- > 0 && (c = this.read()) != -1) { + buf[start++] = (char) c; + count++; + } + return (count == 0 && c == -1) ? -1 : count; + } +} diff --git a/content-rdf/src/test/java/org/xbib/content/rdf/io/xml/OAITest.java b/content-rdf/src/test/java/org/xbib/content/rdf/io/xml/OAITest.java index 1f53796..634356d 100644 --- a/content-rdf/src/test/java/org/xbib/content/rdf/io/xml/OAITest.java +++ b/content-rdf/src/test/java/org/xbib/content/rdf/io/xml/OAITest.java @@ -9,12 +9,12 @@ import org.xbib.content.rdf.io.IOTests; import org.xbib.content.rdf.io.turtle.TurtleContentParams; import org.xbib.content.resource.IRI; import org.xbib.content.resource.IRINamespaceContext; -import org.xbib.content.resource.text.CharUtils; -import org.xbib.content.resource.url.UrlEncoding; import org.xbib.helper.StreamTester; +import org.xbib.net.PercentEncoders; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.logging.Level; import java.util.logging.Logger; @@ -50,8 +50,8 @@ public class OAITest extends StreamTester { if ("identifier".equals(name.getLocalPart())) { // make sure we can build an opaque IRI, whatever is out there try { - getResource().setId(IRI.create("id:" - + UrlEncoding.encode(value, CharUtils.Profile.SCHEMESPECIFICPART.filter()))); + getResource().setId(IRI.create("id:" + + PercentEncoders.getRegNameEncoder(StandardCharsets.UTF_8).encode(value))); } catch (IOException e) { logger.log(Level.FINE, e.getMessage(), e); } @@ -80,8 +80,7 @@ public class OAITest extends StreamTester { .setDefaultNamespace("oai", "http://www.openarchives.org/OAI/2.0/oai_dc/"); XmlContentParser parser = new XmlContentParser<>(in); parser.builder(builder); - parser.setHandler(xmlHandler) - .parse(); + parser.setHandler(xmlHandler).parse(); assertStream(getClass().getResourceAsStream("oai.ttl"), builder.streamInput()); } } diff --git a/content-rdf/src/test/java/org/xbib/content/rdf/io/xml/XmlReaderTest.java b/content-rdf/src/test/java/org/xbib/content/rdf/io/xml/XmlReaderTest.java index 56dc8bc..b28da42 100644 --- a/content-rdf/src/test/java/org/xbib/content/rdf/io/xml/XmlReaderTest.java +++ b/content-rdf/src/test/java/org/xbib/content/rdf/io/xml/XmlReaderTest.java @@ -15,12 +15,12 @@ import org.xbib.content.rdf.io.ntriple.NTripleContentParams; import org.xbib.content.rdf.io.turtle.TurtleContentParams; import org.xbib.content.resource.IRI; import org.xbib.content.resource.IRINamespaceContext; -import org.xbib.content.resource.text.CharUtils.Profile; -import org.xbib.content.resource.url.UrlEncoding; import org.xbib.helper.StreamTester; +import org.xbib.net.PercentEncoders; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.LinkedList; import java.util.List; import java.util.logging.Level; @@ -61,8 +61,8 @@ public class XmlReaderTest extends StreamTester { if ("identifier".equals(name.getLocalPart()) && DefaultResource.isBlank(getResource())) { try { // make sure we can build an opaque IRI, whatever is out there - String s = UrlEncoding.encode(value, Profile.SCHEMESPECIFICPART.filter()); - getResource().setId(IRI.create("id:" + s)); + getResource().setId(IRI.create("id:" + + PercentEncoders.getRegNameEncoder(StandardCharsets.UTF_8).encode(value))); } catch (IOException e) { logger.log(Level.FINE, e.getMessage(), e); } @@ -210,7 +210,7 @@ public class XmlReaderTest extends StreamTester { @Override public MyBuilder receive(Resource resource) throws IOException { - resource.triples().forEach(triples::add); + triples.addAll(resource.triples()); return this; } diff --git a/content-rdf/src/test/resources/org/xbib/content/rdf/io/xml/dc.ttl b/content-rdf/src/test/resources/org/xbib/content/rdf/io/xml/dc.ttl index dd31b77..2f27b12 100644 --- a/content-rdf/src/test/resources/org/xbib/content/rdf/io/xml/dc.ttl +++ b/content-rdf/src/test/resources/org/xbib/content/rdf/io/xml/dc.ttl @@ -1,10 +1,10 @@ @prefix dc: . @prefix oaidc: . - dc:title "Improving the tokenisation of identifier names"; + dc:title "Improving the tokenisation of identifier names"; dc:creator "Butler, Simon", "Wermelinger, Michel", "Yu, Yijun", "Sharp, Helen"; - dc:description """Identifier names are the main vehicle for semantic information during program comprehension. For tool-supported program comprehension tasks, including concept location and requirements traceability, identifier names need to be tokenised into their semantic constituents. In this paper we present an approach to the automated tokenisation of identifier names that improves on existing techniques in two ways. First, it improves the tokenisation accuracy for single-case identifier names and for identifier names containing digits, which existing techniques largely ignore. Second, performance gains over existing techniques are achieved using smaller oracles, making the approach easier to deploy. - + dc:description """Identifier names are the main vehicle for semantic information during program comprehension. For tool-supported program comprehension tasks, including concept location and requirements traceability, identifier names need to be tokenised into their semantic constituents. In this paper we present an approach to the automated tokenisation of identifier names that improves on existing techniques in two ways. First, it improves the tokenisation accuracy for single-case identifier names and for identifier names containing digits, which existing techniques largely ignore. Second, performance gains over existing techniques are achieved using smaller oracles, making the approach easier to deploy. + Accuracy was evaluated by comparing our algorithm to manual tokenizations of 28,000 identifier names drawn from 60 well-known open source Java projects totalling 16.5 MSLOC. Moreover, the projects were used to perform a study of identifier tokenisation features (single case, camel case, use of digits, etc.) per object-oriented construct (class names, method names, local variable names, etc.), thus providing an insight into naming conventions in industrial-scale object-oriented code. Our tokenisation tool and datasets are publicly available."""; dc:publisher "Springer Verlag"; dc:contributor "Mira, Mezini"; diff --git a/content-rdf/src/test/resources/org/xbib/content/rdf/io/xml/oai.ttl b/content-rdf/src/test/resources/org/xbib/content/rdf/io/xml/oai.ttl index e36c666..63412c2 100644 --- a/content-rdf/src/test/resources/org/xbib/content/rdf/io/xml/oai.ttl +++ b/content-rdf/src/test/resources/org/xbib/content/rdf/io/xml/oai.ttl @@ -1,6 +1,6 @@ @prefix oai: . - oai:OAI-PMH [ + oai:OAI-PMH [ oai:responseDate "2013-06-29T18:31:40Z"; oai:request "http://www.doaj.org/oai.article"; oai:ListRecords [ diff --git a/content-resource/build.gradle b/content-resource/build.gradle index cb6526b..49954cc 100644 --- a/content-resource/build.gradle +++ b/content-resource/build.gradle @@ -1,4 +1,9 @@ tasks.withType(JavaCompile) { options.compilerArgs << "-Xlint:all" << "-profile" << "compact2" +} + +dependencies { + compile "org.xbib:net:${project.property('xbib-net.version')}" + testCompile "com.fasterxml.jackson.core:jackson-databind:${project.property('jackson.version')}" } \ No newline at end of file diff --git a/content-resource/src/main/java/org/xbib/content/resource/IRI.java b/content-resource/src/main/java/org/xbib/content/resource/IRI.java index 8f543cc..14eefa9 100644 --- a/content-resource/src/main/java/org/xbib/content/resource/IRI.java +++ b/content-resource/src/main/java/org/xbib/content/resource/IRI.java @@ -1,18 +1,19 @@ package org.xbib.content.resource; -import org.xbib.content.resource.scheme.HttpScheme; -import org.xbib.content.resource.scheme.Scheme; -import org.xbib.content.resource.scheme.SchemeRegistry; import org.xbib.content.resource.text.CharUtils; import org.xbib.content.resource.text.CharUtils.Profile; import org.xbib.content.resource.text.InvalidCharacterException; -import org.xbib.content.resource.url.UrlEncoding; +import org.xbib.net.PercentDecoder; +import org.xbib.net.PercentEncoders; +import org.xbib.net.scheme.Scheme; +import org.xbib.net.scheme.SchemeRegistry; import java.io.IOException; import java.net.IDN; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; @@ -177,25 +178,6 @@ public class IRI implements Comparable, Node { return new IRI(schemeClass, scheme, authority, userinfo, host, port, path, query, fragment); } - public static IRI normalize(IRI iri) { - if (iri.isOpaque() || iri.getPath() == null) { - return iri; - } - IRI normalized = null; - if (iri.schemeClass != null) { - normalized = iri.schemeClass.normalize(iri); - } - try { - return normalized != null ? normalized : new IRI(iri.schemeClass, iri.getScheme(), iri.getAuthority(), iri - .getUserInfo(), iri.getHost(), iri.getPort(), normalize(iri.getPath()), UrlEncoding.encode(UrlEncoding - .decode(iri.getQuery()), Profile.IQUERY.filter()), UrlEncoding - .encode(UrlEncoding.decode(iri.getFragment()), Profile.IFRAGMENT.filter())); - } catch (IOException e) { - logger.log(Level.FINE, e.getMessage(), e); - return null; - } - } - private static String normalize(String path) { if (path == null || path.length() == 0) { return "/"; @@ -211,13 +193,14 @@ public class IRI implements Comparable, Node { segments[n] = null; } } + PercentDecoder percentDecoder = new PercentDecoder(); for (String segment : segments) { if (segment != null) { if (buf.length() > 1) { buf.append('/'); } try { - buf.append(UrlEncoding.encode(UrlEncoding.decode(segment), Profile.IPATHNODELIMS_SEG.filter())); + buf.append(PercentEncoders.getMatrixEncoder(StandardCharsets.UTF_8).encode(percentDecoder.decode(segment))); } catch (IOException e) { logger.log(Level.FINE, e.getMessage(), e); } @@ -440,13 +423,13 @@ public class IRI implements Comparable, Node { if (authority != null && asciiAuthority == null) { asciiAuthority = buildASCIIAuthority(); } - return (asciiAuthority != null && asciiAuthority.length() > 0) ? asciiAuthority : null; + return asciiAuthority != null && asciiAuthority.length() > 0 ? asciiAuthority : null; } public String getASCIIFragment() { if (fragment != null && asciiFragment == null) { try { - asciiFragment = UrlEncoding.encode(fragment, Profile.FRAGMENT.filter()); + asciiFragment = PercentEncoders.getFragmentEncoder(StandardCharsets.UTF_8).encode(fragment); } catch (IOException e) { logger.log(Level.FINE, e.getMessage(), e); } @@ -457,7 +440,7 @@ public class IRI implements Comparable, Node { public String getASCIIPath() { if (path != null && asciiPath == null) { try { - asciiPath = UrlEncoding.encode(path, Profile.PATH.filter()); + asciiPath = PercentEncoders.getPathEncoder(StandardCharsets.UTF_8).encode(path); } catch (IOException e) { logger.log(Level.FINE, e.getMessage(), e); } @@ -468,7 +451,7 @@ public class IRI implements Comparable, Node { public String getASCIIQuery() { if (query != null && asciiQuery == null) { try { - asciiQuery = UrlEncoding.encode(query, Profile.QUERY.filter(), Profile.PATH.filter()); + asciiQuery = PercentEncoders.getQueryEncoder(StandardCharsets.UTF_8).encode(query); } catch (IOException e) { logger.log(Level.FINE, e.getMessage(), e); } @@ -479,7 +462,7 @@ public class IRI implements Comparable, Node { public String getASCIIUserInfo() { if (userinfo != null && asciiUserinfo == null) { try { - asciiUserinfo = UrlEncoding.encode(userinfo, Profile.USERINFO.filter()); + asciiUserinfo = PercentEncoders.getUnreservedEncoder(StandardCharsets.UTF_8).encode(userinfo); } catch (IOException e) { logger.log(Level.FINE, e.getMessage(), e); } @@ -497,18 +480,9 @@ public class IRI implements Comparable, Node { } private String buildASCIIAuthority() { - if (schemeClass instanceof HttpScheme) { - StringBuilder buf = new StringBuilder(); - buildAuthority(buf, getASCIIUserInfo(), getASCIIHost(), getPort()); - return buf.toString(); - } else { - try { - return UrlEncoding.encode(authority, Profile.AUTHORITY.filter()); - } catch (IOException e) { - logger.log(Level.FINE, e.getMessage(), e); - return null; - } - } + StringBuilder buf = new StringBuilder(); + buildAuthority(buf, getASCIIUserInfo(), getASCIIHost(), getPort()); + return buf.toString(); } public boolean isAbsolute() { @@ -538,10 +512,6 @@ public class IRI implements Comparable, Node { return resolve(this, new IRI(iri)); } - public IRI normalize() { - return normalize(this); - } - @Override public String toString() { StringBuilder buf = new StringBuilder(); @@ -555,7 +525,7 @@ public class IRI implements Comparable, Node { public String toEncodedString() { try { - return UrlEncoding.encode(toString(), Profile.SCHEMESPECIFICPART.filter()); + return PercentEncoders.getUnreservedEncoder(StandardCharsets.UTF_8).encode(toString()); } catch (IOException e) { logger.log(Level.FINE, e.getMessage(), e); return null; diff --git a/content-resource/src/main/java/org/xbib/content/resource/scheme/AbstractScheme.java b/content-resource/src/main/java/org/xbib/content/resource/scheme/AbstractScheme.java deleted file mode 100644 index 9f705ff..0000000 --- a/content-resource/src/main/java/org/xbib/content/resource/scheme/AbstractScheme.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.xbib.content.resource.scheme; - -import org.xbib.content.resource.IRI; - -/** - * Base implementation for IRI scheme providers. - */ -public abstract class AbstractScheme implements Scheme { - - protected final String name; - protected final int port; - - protected AbstractScheme(String name, int port) { - this.name = name; - this.port = port; - } - - @Override - public int getDefaultPort() { - return port; - } - - @Override - public String getName() { - return name; - } - - /** - * Default return unmodified. - */ - @Override - public IRI normalize(IRI iri) { - return iri; - } - - /** - * Default return unmodified. - */ - @Override - public String normalizePath(String path) { - return path; - } -} diff --git a/content-resource/src/main/java/org/xbib/content/resource/scheme/DefaultScheme.java b/content-resource/src/main/java/org/xbib/content/resource/scheme/DefaultScheme.java deleted file mode 100644 index 4f70611..0000000 --- a/content-resource/src/main/java/org/xbib/content/resource/scheme/DefaultScheme.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.xbib.content.resource.scheme; - -/** - * - */ -public class DefaultScheme extends AbstractScheme { - - public DefaultScheme(String name) { - super(name, -1); - } - -} diff --git a/content-resource/src/main/java/org/xbib/content/resource/scheme/FtpScheme.java b/content-resource/src/main/java/org/xbib/content/resource/scheme/FtpScheme.java deleted file mode 100644 index 405ccb5..0000000 --- a/content-resource/src/main/java/org/xbib/content/resource/scheme/FtpScheme.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.xbib.content.resource.scheme; - -/** - * - */ -public class FtpScheme extends HttpScheme { - - static final String FTP_SCHEME_NAME = "ftp"; - - private static final int DEFAULT_PORT = 21; - - public FtpScheme() { - super(FTP_SCHEME_NAME, DEFAULT_PORT); - } - -} diff --git a/content-resource/src/main/java/org/xbib/content/resource/scheme/HttpScheme.java b/content-resource/src/main/java/org/xbib/content/resource/scheme/HttpScheme.java deleted file mode 100644 index bd9d69c..0000000 --- a/content-resource/src/main/java/org/xbib/content/resource/scheme/HttpScheme.java +++ /dev/null @@ -1,56 +0,0 @@ -package org.xbib.content.resource.scheme; - -import org.xbib.content.resource.IRI; -import org.xbib.content.resource.text.CharUtils.Profile; -import org.xbib.content.resource.url.UrlEncoding; - -import java.io.IOException; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * - */ -public class HttpScheme extends AbstractScheme { - - static final String HTTP_SCHEME_NAME = "http"; - private static final Logger logger = Logger.getLogger(HttpScheme.class.getName()); - private static final int DEFAULT_PORT = 80; - - HttpScheme() { - super(HTTP_SCHEME_NAME, DEFAULT_PORT); - } - - HttpScheme(String name, int port) { - super(name, port); - } - - @Override - public IRI normalize(IRI iri) { - int port = (iri.getPort() == getDefaultPort()) ? -1 : iri.getPort(); - String host = iri.getHost(); - if (host != null) { - host = host.toLowerCase(); - } - try { - return IRI.builder() - .scheme(iri.getScheme()) - .userinfo(iri.getUserInfo()) - .host(host) - .port(port) - .path(iri.getPath()) - .query(UrlEncoding.encode(UrlEncoding.decode(iri.getQuery()), Profile.IQUERY.filter())) - .fragment(UrlEncoding.encode(UrlEncoding.decode(iri.getFragment()), Profile.IFRAGMENT.filter())) - .build(); - } catch (IOException e) { - logger.log(Level.FINE, e.getMessage(), e); - return null; - } - } - - @Override - public String normalizePath(String path) { - return null; - } - -} diff --git a/content-resource/src/main/java/org/xbib/content/resource/scheme/HttpsScheme.java b/content-resource/src/main/java/org/xbib/content/resource/scheme/HttpsScheme.java deleted file mode 100644 index 34ea3f9..0000000 --- a/content-resource/src/main/java/org/xbib/content/resource/scheme/HttpsScheme.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.xbib.content.resource.scheme; - -class HttpsScheme extends HttpScheme { - - static final String HTTPS_SCHEME_NAME = "https"; - private static final int DEFAULT_PORT = 443; - - public HttpsScheme() { - super(HTTPS_SCHEME_NAME, DEFAULT_PORT); - } - -} diff --git a/content-resource/src/main/java/org/xbib/content/resource/scheme/Scheme.java b/content-resource/src/main/java/org/xbib/content/resource/scheme/Scheme.java deleted file mode 100644 index 800dd48..0000000 --- a/content-resource/src/main/java/org/xbib/content/resource/scheme/Scheme.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.xbib.content.resource.scheme; - -import org.xbib.content.resource.IRI; - -/** - * Interface implemented by custom IRI scheme parsers. - */ -public interface Scheme { - - String getName(); - - IRI normalize(IRI iri); - - String normalizePath(String path); - - int getDefaultPort(); -} diff --git a/content-resource/src/main/java/org/xbib/content/resource/scheme/SchemeRegistry.java b/content-resource/src/main/java/org/xbib/content/resource/scheme/SchemeRegistry.java deleted file mode 100644 index 5cb9294..0000000 --- a/content-resource/src/main/java/org/xbib/content/resource/scheme/SchemeRegistry.java +++ /dev/null @@ -1,59 +0,0 @@ -package org.xbib.content.resource.scheme; - -import java.util.HashMap; -import java.util.Map; - -/** - * Static registry of custom IRI schemes. - */ -public final class SchemeRegistry { - - private static SchemeRegistry registry; - private final Map schemes; - - SchemeRegistry() { - schemes = new HashMap<>(); - schemes.put(HttpScheme.HTTP_SCHEME_NAME, new HttpScheme()); - schemes.put(HttpsScheme.HTTPS_SCHEME_NAME, new HttpsScheme()); - schemes.put(FtpScheme.FTP_SCHEME_NAME, new FtpScheme()); - } - - public static SchemeRegistry getInstance() { - if (registry == null) { - registry = new SchemeRegistry(); - } - return registry; - } - - @SuppressWarnings("unchecked") - public boolean register(String schemeClass) throws ClassNotFoundException, IllegalAccessException, - InstantiationException { - Class klass = (Class) Thread.currentThread().getContextClassLoader().loadClass(schemeClass); - return register(klass); - } - - public boolean register(Class schemeClass) throws IllegalAccessException, - InstantiationException { - Scheme scheme = schemeClass.newInstance(); - return register(scheme); - } - - public boolean register(Scheme scheme) { - String name = scheme.getName(); - if (schemes.get(name) == null) { - schemes.put(name.toLowerCase(), scheme); - return true; - } else { - return false; - } - } - - public Scheme getScheme(String scheme) { - if (scheme == null) { - return null; - } - Scheme s = schemes.get(scheme.toLowerCase()); - return (s != null) ? s : new DefaultScheme(scheme); - } - -} diff --git a/content-resource/src/main/java/org/xbib/content/resource/scheme/package-info.java b/content-resource/src/main/java/org/xbib/content/resource/scheme/package-info.java deleted file mode 100644 index e587a7f..0000000 --- a/content-resource/src/main/java/org/xbib/content/resource/scheme/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Classes for resource schemes. - */ -package org.xbib.content.resource.scheme; diff --git a/content-resource/src/main/java/org/xbib/content/resource/url/PercentDecoder.java b/content-resource/src/main/java/org/xbib/content/resource/url/PercentDecoder.java deleted file mode 100755 index 7b0033a..0000000 --- a/content-resource/src/main/java/org/xbib/content/resource/url/PercentDecoder.java +++ /dev/null @@ -1,195 +0,0 @@ -package org.xbib.content.resource.url; - -import static java.nio.charset.CoderResult.OVERFLOW; -import static java.nio.charset.CoderResult.UNDERFLOW; - -import java.nio.ByteBuffer; -import java.nio.CharBuffer; -import java.nio.charset.CharsetDecoder; -import java.nio.charset.CoderResult; -import java.nio.charset.MalformedInputException; -import java.nio.charset.UnmappableCharacterException; - -/** - * Decodes percent-encoded (%XX) Unicode text. - */ -public final class PercentDecoder { - - /** - * Written to with decoded chars by decoder. - */ - private final CharBuffer decodedCharBuf; - private final CharsetDecoder decoder; - /** - * The decoded string for the current input. - */ - private final StringBuilder outputBuf = new StringBuilder(); - /** - * bytes represented by the current sequence of %-triples. Resized as needed. - */ - private ByteBuffer encodedBuf; - - /** - * Construct a new PercentDecoder with default buffer sizes. - * - * @param charsetDecoder Charset to decode bytes into chars with - * @see PercentDecoder#PercentDecoder(CharsetDecoder, int, int) - */ - public PercentDecoder(CharsetDecoder charsetDecoder) { - this(charsetDecoder, 16, 16); - } - - /** - * @param charsetDecoder Charset to decode bytes into chars with - * @param initialEncodedByteBufSize Initial size of buffer that holds encoded bytes - * @param decodedCharBufSize Size of buffer that encoded bytes are decoded into - */ - public PercentDecoder(CharsetDecoder charsetDecoder, int initialEncodedByteBufSize, - int decodedCharBufSize) { - encodedBuf = ByteBuffer.allocate(initialEncodedByteBufSize); - decodedCharBuf = CharBuffer.allocate(decodedCharBufSize); - decoder = charsetDecoder; - } - - /** - * @param input Input with %-encoded representation of characters in this instance's configured character set, e.g. - * "%20" for a space character - * @return Corresponding string with %-encoded data decoded and converted to their corresponding characters - * @throws MalformedInputException if decoder is configured to report errors and malformed input is detected - * @throws UnmappableCharacterException if decoder is configured to report errors and an unmappable character is - * detected - */ - public String decode(CharSequence input) throws MalformedInputException, UnmappableCharacterException { - outputBuf.setLength(0); - // this is almost always an underestimate of the size needed: - // only a 4-byte encoding (which is 12 characters input) would case this to be an overestimate - outputBuf.ensureCapacity(input.length() / 8); - encodedBuf.clear(); - - for (int i = 0; i < input.length(); i++) { - char c = input.charAt(i); - if (c != '%') { - handleEncodedBytes(); - - outputBuf.append(c); - continue; - } - - if (i + 2 >= input.length()) { - throw new IllegalArgumentException( - "Could not percent decode <" + input + ">: incomplete %-pair at position " + i); - } - - // grow the byte buf if needed - if (encodedBuf.remaining() == 0) { - ByteBuffer largerBuf = ByteBuffer.allocate(encodedBuf.capacity() * 2); - encodedBuf.flip(); - largerBuf.put(encodedBuf); - encodedBuf = largerBuf; - } - - // note that we advance i here as we consume chars - int msBits = Character.digit(input.charAt(++i), 16); - int lsBits = Character.digit(input.charAt(++i), 16); - - if (msBits == -1 || lsBits == -1) { - throw new IllegalArgumentException("Invalid %-tuple <" + input.subSequence(i - 2, i + 1) + ">"); - } - - msBits <<= 4; - msBits |= lsBits; - - // msBits can only have 8 bits set, so cast is safe - encodedBuf.put((byte) msBits); - } - - handleEncodedBytes(); - - return outputBuf.toString(); - } - - /** - * Decode any buffered encoded bytes and write them to the output buf. - */ - private void handleEncodedBytes() throws MalformedInputException, UnmappableCharacterException { - if (encodedBuf.position() == 0) { - // nothing to do - return; - } - - decoder.reset(); - CoderResult coderResult; - - // switch to reading mode - encodedBuf.flip(); - - // loop while we're filling up the decoded char buf, or there's any encoded bytes - // decode() in practice seems to only consume bytes when it can decode an entire char... - do { - decodedCharBuf.clear(); - coderResult = decoder.decode(encodedBuf, decodedCharBuf, false); - throwIfError(coderResult); - appendDecodedChars(); - } while (coderResult == OVERFLOW && encodedBuf.hasRemaining()); - - // final decode with end-of-input flag - decodedCharBuf.clear(); - coderResult = decoder.decode(encodedBuf, decodedCharBuf, true); - throwIfError(coderResult); - - if (encodedBuf.hasRemaining()) { - throw new IllegalStateException("Final decode didn't error, but didn't consume remaining input bytes"); - } - if (coderResult != UNDERFLOW) { - throw new IllegalStateException("Expected underflow, but instead final decode returned " + coderResult); - } - - appendDecodedChars(); - - // we've finished the input, wrap it up - encodedBuf.clear(); - flush(); - } - - /** - * Must only be called when the input encoded bytes buffer is empty. - */ - private void flush() throws MalformedInputException, UnmappableCharacterException { - CoderResult coderResult; - decodedCharBuf.clear(); - - coderResult = decoder.flush(decodedCharBuf); - appendDecodedChars(); - - throwIfError(coderResult); - - if (coderResult != UNDERFLOW) { - throw new IllegalStateException("Decoder flush resulted in " + coderResult); - } - } - - /** - * If coderResult is considered an error (i.e. not overflow or underflow), throw the corresponding - * CharacterCodingException. - * - * @param coderResult result to check - * @throws MalformedInputException if result represents malformed input - * @throws UnmappableCharacterException if result represents an unmappable character - */ - private void throwIfError(CoderResult coderResult) throws MalformedInputException, UnmappableCharacterException { - if (coderResult.isMalformed()) { - throw new MalformedInputException(coderResult.length()); - } - if (coderResult.isUnmappable()) { - throw new UnmappableCharacterException(coderResult.length()); - } - } - - /** - * Flip the decoded char buf and append it to the string bug. - */ - private void appendDecodedChars() { - decodedCharBuf.flip(); - outputBuf.append(decodedCharBuf); - } -} diff --git a/content-resource/src/main/java/org/xbib/content/resource/url/PercentEncoder.java b/content-resource/src/main/java/org/xbib/content/resource/url/PercentEncoder.java deleted file mode 100755 index fb4c61b..0000000 --- a/content-resource/src/main/java/org/xbib/content/resource/url/PercentEncoder.java +++ /dev/null @@ -1,187 +0,0 @@ -package org.xbib.content.resource.url; - -import static java.lang.Character.isHighSurrogate; -import static java.lang.Character.isLowSurrogate; - -import java.nio.ByteBuffer; -import java.nio.CharBuffer; -import java.nio.charset.CharsetEncoder; -import java.nio.charset.CoderResult; -import java.nio.charset.MalformedInputException; -import java.nio.charset.UnmappableCharacterException; -import java.util.BitSet; - -/** - * Encodes unsafe characters as a sequence of %XX hex-encoded bytes. - * - * This is typically done when encoding components of URLs. See {@link UrlPercentEncoders} for pre-configured - * PercentEncoder instances. - */ -public final class PercentEncoder { - - private static final char[] HEX_CODE = "0123456789ABCDEF".toCharArray(); - - private final BitSet safeChars; - private final CharsetEncoder encoder; - /** - * Pre-allocate a string handler to make the common case of encoding to a string faster. - */ - private final StringBuilderPercentEncoderOutputHandler stringHandler = new StringBuilderPercentEncoderOutputHandler(); - private final ByteBuffer encodedBytes; - private final CharBuffer unsafeCharsToEncode; - - /** - * @param safeChars the set of chars to NOT encode, stored as a bitset with the int positions corresponding to - * those chars set to true. Treated as read only. - * @param charsetEncoder charset encoder to encode characters with. Make sure to not re-use CharsetEncoder - * instances - * across threads. - */ - public PercentEncoder(BitSet safeChars, CharsetEncoder charsetEncoder) { - this.safeChars = safeChars; - this.encoder = charsetEncoder; - - // why is this a float? sigh. - int maxBytesPerChar = 1 + (int) encoder.maxBytesPerChar(); - // need to handle surrogate pairs, so need to be able to handle 2 chars worth of stuff at once - encodedBytes = ByteBuffer.allocate(maxBytesPerChar * 2); - unsafeCharsToEncode = CharBuffer.allocate(2); - } - - /** - * @param result result to check - * @throws IllegalStateException if result is overflow - * @throws MalformedInputException if result represents malformed input - * @throws UnmappableCharacterException if result represents an unmappable character - */ - private static void checkResult(CoderResult result) throws MalformedInputException, UnmappableCharacterException { - if (result.isOverflow()) { - throw new IllegalStateException("Byte buffer overflow; this should not happen."); - } - if (result.isMalformed()) { - throw new MalformedInputException(result.length()); - } - if (result.isUnmappable()) { - throw new UnmappableCharacterException(result.length()); - } - } - - /** - * Encode the input and pass output chars to a handler. - * - * @param input input string - * @param handler handler to call on each output character - * @throws MalformedInputException if encoder is configured to report errors and malformed input is detected - * @throws UnmappableCharacterException if encoder is configured to report errors and an unmappable character is - * detected - */ - public void encode(CharSequence input, StringBuilderPercentEncoderOutputHandler handler) throws - MalformedInputException, UnmappableCharacterException { - - for (int i = 0; i < input.length(); i++) { - - char c = input.charAt(i); - - if (safeChars.get(c)) { - handler.onOutputChar(c); - continue; - } - - // not a safe char - unsafeCharsToEncode.clear(); - unsafeCharsToEncode.append(c); - if (isHighSurrogate(c)) { - if (input.length() > i + 1) { - // get the low surrogate as well - char lowSurrogate = input.charAt(i + 1); - if (isLowSurrogate(lowSurrogate)) { - unsafeCharsToEncode.append(lowSurrogate); - i++; - } else { - throw new IllegalArgumentException( - "Invalid UTF-16: Char " + (i) + " is a high surrogate (\\u" + Integer - .toHexString(c) + "), but char " + (i + 1) + " is not a low surrogate (\\u" + Integer - .toHexString(lowSurrogate) + ")"); - } - } else { - throw new IllegalArgumentException( - "Invalid UTF-16: The last character in the input string was a high surrogate (\\u" + Integer - .toHexString(c) + ")"); - } - } - - flushUnsafeCharBuffer(handler); - } - } - - /** - * Encode the input and return the resulting text as a String. - * - * @param input input string - * @return the input string with every character that's not in safeChars turned into its byte representation via the - * instance's encoder and then percent-encoded - * @throws MalformedInputException if encoder is configured to report errors and malformed input is detected - * @throws UnmappableCharacterException if encoder is configured to report errors and an unmappable character is - * detected - */ - public String encode(CharSequence input) throws MalformedInputException, UnmappableCharacterException { - stringHandler.reset(); - stringHandler.ensureCapacity(input.length()); - encode(input, stringHandler); - return stringHandler.getContents(); - } - - /** - * Encode unsafeCharsToEncode to bytes as per charsetEncoder, then percent-encode those bytes into output. - * - * Side effects: unsafeCharsToEncode will be read from and cleared. encodedBytes will be cleared and written to. - */ - private void flushUnsafeCharBuffer(StringBuilderPercentEncoderOutputHandler handler) throws MalformedInputException, - UnmappableCharacterException { - // need to read from the char buffer, which was most recently written to - unsafeCharsToEncode.flip(); - - encodedBytes.clear(); - - encoder.reset(); - CoderResult result = encoder.encode(unsafeCharsToEncode, encodedBytes, true); - checkResult(result); - result = encoder.flush(encodedBytes); - checkResult(result); - - // read contents of bytebuffer - encodedBytes.flip(); - - while (encodedBytes.hasRemaining()) { - byte b = encodedBytes.get(); - handler.onOutputChar('%'); - handler.onOutputChar(HEX_CODE[b >> 4 & 0xF]); - handler.onOutputChar(HEX_CODE[b & 0xF]); - } - } - - private static class StringBuilderPercentEncoderOutputHandler { - - private final StringBuilder stringBuilder; - - StringBuilderPercentEncoderOutputHandler() { - stringBuilder = new StringBuilder(); - } - - String getContents() { - return stringBuilder.toString(); - } - - void reset() { - stringBuilder.setLength(0); - } - - void ensureCapacity(int length) { - stringBuilder.ensureCapacity(length); - } - - void onOutputChar(char c) { - stringBuilder.append(c); - } - } -} diff --git a/content-resource/src/main/java/org/xbib/content/resource/url/UrlBuilder.java b/content-resource/src/main/java/org/xbib/content/resource/url/UrlBuilder.java deleted file mode 100755 index 0a0d760..0000000 --- a/content-resource/src/main/java/org/xbib/content/resource/url/UrlBuilder.java +++ /dev/null @@ -1,472 +0,0 @@ -package org.xbib.content.resource.url; - -import static org.xbib.content.resource.url.UrlPercentEncoders.getFragmentEncoder; -import static org.xbib.content.resource.url.UrlPercentEncoders.getMatrixEncoder; -import static org.xbib.content.resource.url.UrlPercentEncoders.getPathEncoder; -import static org.xbib.content.resource.url.UrlPercentEncoders.getQueryParamEncoder; -import static org.xbib.content.resource.url.UrlPercentEncoders.getRegNameEncoder; -import static org.xbib.content.resource.url.UrlPercentEncoders.getUnstructuredQueryEncoder; - -import java.net.URL; -import java.nio.charset.CharacterCodingException; -import java.nio.charset.CharsetDecoder; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.regex.Pattern; - -/** - * Builder for urls with url-encoding applied to path, query param, etc. - * - * Escaping rules are from RFC 3986, RFC 1738 and the HTML 4 spec (http://www.w3.org/TR/html401/interact/forms.html#form-content-type). - * This means that this diverges from the canonical URI/URL rules for the sake of being what you want to actually make - * HTTP-useful URLs. - */ -public final class UrlBuilder { - - /** - * IPv6 address, taken from Stack Overflow. - */ - private static final Pattern IPV6_PATTERN = Pattern.compile( - "\\A\\[((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)]\\z"); - - /** - * IPv4 dotted quad. - */ - private static final Pattern IPV4_PATTERN = Pattern - .compile("\\A(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z"); - - private final String scheme; - - private final String host; - - private final Integer port; - - private final List> queryParams = new ArrayList<>(); - private final List pathSegments = new ArrayList<>(); - private final PercentEncoder pathEncoder = getPathEncoder(); - private final PercentEncoder regNameEncoder = getRegNameEncoder(); - private final PercentEncoder matrixEncoder = getMatrixEncoder(); - private final PercentEncoder queryParamEncoder = getQueryParamEncoder(); - private final PercentEncoder unstructuredQueryEncoder = getUnstructuredQueryEncoder(); - private final PercentEncoder fragmentEncoder = getFragmentEncoder(); - /** - * If this is non-null, queryParams must be empty, and vice versa. - */ - private String unstructuredQuery; - private String fragment; - - private boolean forceTrailingSlash = false; - - /** - * Create a URL with UTF-8 encoding. - * - * @param scheme scheme (e.g. http) - * @param host host (e.g. foo.com or 1.2.3.4 or [::1]) - * @param port null or a positive integer - */ - private UrlBuilder(String scheme, String host, Integer port) { - this.host = host; - this.scheme = scheme; - this.port = port; - } - - /** - * Create a URL with an null port and UTF-8 encoding. - * - * @param scheme scheme (e.g. http) - * @param host host in any of the valid syntaxes: reg-name (a dns name), ipv4 literal (1.2.3.4), ipv6 literal - * ([::1]), excluding IPvFuture since no one uses that in practice - * @return a url builder - * @see UrlBuilder#forHost(String scheme, String host, int port) - */ - public static UrlBuilder forHost(String scheme, String host) { - return new UrlBuilder(scheme, host, null); - } - - /** - * @param scheme scheme (e.g. http) - * @param host host in any of the valid syntaxes: reg-name ( a dns name), ipv4 literal (1.2.3.4), ipv6 literal - * ([::1]), excluding IPvFuture since no one uses that in practice - * @param port port - * @return a url builder - */ - public static UrlBuilder forHost(String scheme, String host, int port) { - return new UrlBuilder(scheme, host, port); - } - - /** - * Calls {@link UrlBuilder#fromUrl(URL, CharsetDecoder)} with a UTF-8 CharsetDecoder. The same semantics about the - * query string apply. - * - * @param url url to initialize builder with - * @return a UrlBuilder containing the host, path, etc. from the url - * @throws CharacterCodingException if char decoding fails - * @see UrlBuilder#fromUrl(URL, CharsetDecoder) - */ - public static UrlBuilder fromUrl(URL url) throws CharacterCodingException { - return fromUrl(url, StandardCharsets.UTF_8.newDecoder()); - } - - /** - * Create a UrlBuilder initialized with the contents of a {@link URL}. - * - * The query string will be parsed into HTML4 query params if it can be separated into a - * &-separated sequence of key=value pairs. The sequence of query params can then be - * appended to by continuing to call {@link UrlBuilder#queryParam(String, String)}. The concept of query params is - * only part of the HTML spec (and common HTTP usage), though, so it's perfectly legal to have a query string that - * is in some other form. To represent this case, if the aforementioned param-parsing attempt fails, the query - * string will be treated as just a monolithic, unstructured, string. In this case, calls to {@link - * UrlBuilder#queryParam(String, String)} on the resulting instance will throw IllegalStateException, and only calls - * to {@link UrlBuilder#unstructuredQuery(String)}}, which replaces the entire query string, are allowed. - * - * @param url url to initialize builder with - * @param charsetDecoder the decoder to decode encoded bytes with (except for reg names, which are always UTF-8) - * @return a UrlBuilder containing the host, path, etc. from the url - * @throws CharacterCodingException if decoding percent-encoded bytes fails and charsetDecoder is configured to - * report errors - * @see UrlBuilder#fromUrl(URL, CharsetDecoder) - */ - public static UrlBuilder fromUrl(URL url, CharsetDecoder charsetDecoder) throws - CharacterCodingException { - - PercentDecoder decoder = new PercentDecoder(charsetDecoder); - // reg names must be encoded UTF-8 - PercentDecoder regNameDecoder; - if (charsetDecoder.charset().equals(StandardCharsets.UTF_8)) { - regNameDecoder = decoder; - } else { - regNameDecoder = new PercentDecoder(StandardCharsets.UTF_8.newDecoder()); - } - - Integer port = url.getPort(); - if (port == -1) { - port = null; - } - - UrlBuilder builder = new UrlBuilder(url.getProtocol(), regNameDecoder.decode(url.getHost()), port); - - buildFromPath(builder, decoder, url); - - buildFromQuery(builder, decoder, url); - - if (url.getRef() != null) { - builder.fragment(decoder.decode(url.getRef())); - } - - return builder; - } - - /** - * Populate a url builder based on the query of an URL. - * - * @param builder builder - * @param decoder decoder - * @param url url - * @throws CharacterCodingException if build fails - */ - private static void buildFromQuery(UrlBuilder builder, PercentDecoder decoder, URL url) throws - CharacterCodingException { - if (url.getQuery() != null) { - String q = url.getQuery(); - List> pairs = new ArrayList<>(); - boolean parseOk = true; - for (String queryChunk : q.split("&")) { - String[] queryParamChunks = queryChunk.split("="); - if (queryParamChunks.length != 2) { - parseOk = false; - break; - } - pairs.add(Pair.of(decoder.decode(queryParamChunks[0]), - decoder.decode(queryParamChunks[1]))); - } - if (parseOk) { - for (Pair pair : pairs) { - builder.queryParam(pair.getKey(), pair.getValue()); - } - } else { - builder.unstructuredQuery(decoder.decode(q)); - } - } - } - - /** - * Populate the path segments of a url builder from an URL. - * - * @param builder builder - * @param decoder decoder - * @param url url - * @throws CharacterCodingException if build fails - */ - private static void buildFromPath(UrlBuilder builder, PercentDecoder decoder, URL url) throws - CharacterCodingException { - for (String pathChunk : url.getPath().split("/")) { - if ("".equals(pathChunk)) { - continue; - } - if (pathChunk.charAt(0) == ';') { - builder.pathSegment(""); - for (String matrixChunk : pathChunk.substring(1).split(";")) { - buildFromMatrixParamChunk(decoder, builder, matrixChunk); - } - continue; - } - String[] matrixChunks = pathChunk.split(";"); - builder.pathSegment(decoder.decode(matrixChunks[0])); - for (int i = 1; i < matrixChunks.length; i++) { - buildFromMatrixParamChunk(decoder, builder, matrixChunks[i]); - } - } - } - - private static void buildFromMatrixParamChunk(PercentDecoder decoder, UrlBuilder ub, String pathMatrixChunk) throws - CharacterCodingException { - String[] mtxPair = pathMatrixChunk.split("="); - if (mtxPair.length != 2) { - throw new IllegalArgumentException("Malformed matrix param: <" + pathMatrixChunk + ">"); - } - - String mtxName = mtxPair[0]; - String mtxVal = mtxPair[1]; - ub.matrixParam(decoder.decode(mtxName), decoder.decode(mtxVal)); - } - - /** - * Add a path segment. - * - * @param segment a path segment - * @return this - */ - public UrlBuilder pathSegment(String segment) { - pathSegments.add(new PathSegment(segment)); - return this; - } - - /** - * Add multiple path segments. Equivalent to successive calls to {@link UrlBuilder#pathSegment(String)}. - * - * @param segments path segments - * @return this - */ - public UrlBuilder pathSegments(String... segments) { - for (String segment : segments) { - pathSegment(segment); - } - - return this; - } - - /** - * Add an HTML query parameter. Query parameters will be encoded in the order added. - * - * Using query strings to encode key=value pairs is not part of the URI/URL specification; it is specified by - * http://www.w3.org/TR/html401/interact/forms.html#form-content-type. - * - * If you use this method to build a query string, or created this builder from a url with a query string that can - * successfully be parsed into query param pairs, you cannot subsequently use {@link - * UrlBuilder#unstructuredQuery(String)}. See {@link UrlBuilder#fromUrl(URL, CharsetDecoder)}. - * - * @param name param name - * @param value param value - * @return this - */ - public UrlBuilder queryParam(String name, String value) { - if (unstructuredQuery != null) { - throw new IllegalStateException( - "Cannot call queryParam() when this already has an unstructured query specified"); - } - - queryParams.add(Pair.of(name, value)); - return this; - } - - /** - * Set the complete query string of arbitrary structure. This is useful when you want to specify a query string that - * is not of key=value format. If the query has previously been set via this method, subsequent calls will overwrite - * that query. - * - * If you use this method, or create a builder from a URL whose query is not parseable into query param pairs, you - * cannot subsequently use {@link UrlBuilder#queryParam(String, String)}. See {@link UrlBuilder#fromUrl(URL, - * CharsetDecoder)}. - * - * @param query Complete URI query, as specified by https://tools.ietf.org/html/rfc3986#section-3.4 - * @return this - */ - public UrlBuilder unstructuredQuery(String query) { - if (!queryParams.isEmpty()) { - throw new IllegalStateException( - "Cannot call unstructuredQuery() when this already has queryParam pairs specified"); - } - - unstructuredQuery = query; - - return this; - } - - /** - * Clear the unstructured query and any query params. - * - * Since the query / query param situation is a little complicated, this method will let you remove all query - * information and start again from scratch. This may be useful when taking an existing url, parsing it into a - * builder, and then re-doing its query params, for instance. - * - * @return this - */ - public UrlBuilder clearQuery() { - queryParams.clear(); - unstructuredQuery = null; - - return this; - } - - /** - * Add a matrix param to the last added path segment. If no segments have been added, the param will be added to the - * root. Matrix params will be encoded in the order added. - * - * @param name param name - * @param value param value - * @return this - */ - public UrlBuilder matrixParam(String name, String value) { - if (pathSegments.isEmpty()) { - // create an empty path segment to represent a matrix param applied to the root - pathSegment(""); - } - PathSegment seg = pathSegments.get(pathSegments.size() - 1); - seg.matrixParams.add(Pair.of(name, value)); - return this; - } - - /** - * Set the fragment. - * - * @param fragment fragment string - * @return this - */ - public UrlBuilder fragment(String fragment) { - this.fragment = fragment; - return this; - } - - /** - * Force the generated URL to have a trailing slash at the end of the path. - * - * @return this - */ - public UrlBuilder forceTrailingSlash() { - forceTrailingSlash = true; - return this; - } - - /** - * Encode the current builder state into a URL string. - * - * @return a well-formed URL string - * @throws CharacterCodingException if character encoding fails and the encoder is configured to report errors - */ - public String toUrlString() throws CharacterCodingException { - StringBuilder buf = new StringBuilder(); - - buf.append(scheme); - buf.append("://"); - - buf.append(encodeHost(host)); - if (port != null) { - buf.append(':'); - buf.append(port); - } - - for (PathSegment pathSegment : pathSegments) { - buf.append('/'); - buf.append(pathEncoder.encode(pathSegment.segment)); - - for (Pair matrixParam : pathSegment.matrixParams) { - buf.append(';'); - buf.append(matrixEncoder.encode(matrixParam.getKey())); - buf.append('='); - buf.append(matrixEncoder.encode(matrixParam.getValue())); - } - } - - if (forceTrailingSlash) { - buf.append('/'); - } - - if (!queryParams.isEmpty()) { - buf.append("?"); - Iterator> qpIter = queryParams.iterator(); - while (qpIter.hasNext()) { - Pair queryParam = qpIter.next(); - buf.append(queryParamEncoder.encode(queryParam.getKey())); - buf.append('='); - buf.append(queryParamEncoder.encode(queryParam.getValue())); - if (qpIter.hasNext()) { - buf.append('&'); - } - } - } else if (unstructuredQuery != null) { - buf.append("?"); - buf.append(unstructuredQueryEncoder.encode(unstructuredQuery)); - } - - if (fragment != null) { - buf.append('#'); - buf.append(fragmentEncoder.encode(fragment)); - } - - return buf.toString(); - } - - /** - * @param host original host string - * @return host encoded as in RFC 3986 section 3.2.2 - */ - private String encodeHost(String host) throws CharacterCodingException { - // matching order: IP-literal, IPv4, reg-name - if (IPV4_PATTERN.matcher(host).matches() || IPV6_PATTERN.matcher(host).matches()) { - return host; - } - - // it's a reg-name, which MUST be encoded as UTF-8 (regardless of the rest of the URL) - return regNameEncoder.encode(host); - } - - /** - * Bundle of a path segment name and any associated matrix params. - */ - private static class PathSegment { - private final String segment; - private final List> matrixParams = new ArrayList<>(); - - PathSegment(String segment) { - this.segment = segment; - } - } - - private static class Pair { - - K key; - - V value; - - Pair(K key, V value) { - this.key = key; - this.value = value; - } - - @SuppressWarnings("unchecked") - static Pair of(K key, V value) { - return new Pair<>(key, value); - } - - K getKey() { - return key; - } - - V getValue() { - return value; - } - - } -} diff --git a/content-resource/src/main/java/org/xbib/content/resource/url/UrlEncoding.java b/content-resource/src/main/java/org/xbib/content/resource/url/UrlEncoding.java deleted file mode 100644 index dffe827..0000000 --- a/content-resource/src/main/java/org/xbib/content/resource/url/UrlEncoding.java +++ /dev/null @@ -1,148 +0,0 @@ -package org.xbib.content.resource.url; - -import org.xbib.content.resource.text.CharUtils; -import org.xbib.content.resource.text.Filter; - -import java.io.EOFException; -import java.io.FilterReader; -import java.io.IOException; -import java.io.Reader; -import java.io.StringReader; - -/** - * Performs URL Percent Encoding. - */ -public final class UrlEncoding { - - private static final char[] HEX = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; - - private UrlEncoding() { - } - - private static void encode(Appendable sb, byte... bytes) throws IOException { - encode(sb, 0, bytes.length, bytes); - } - - private static void encode(Appendable sb, int offset, int length, byte... bytes) throws IOException { - for (int n = offset, i = 0; n < bytes.length && i < length; n++, i++) { - byte c = bytes[n]; - sb.append("%"); - sb.append(HEX[(c >> 4) & 0x0f]); - sb.append(HEX[c & 0x0f]); - } - } - - public static String encode(CharSequence s, Filter filter) throws IOException { - return encode(s, new Filter[]{filter}); - } - - public static String encode(CharSequence s, Filter... filters) throws IOException { - if (s == null) { - return null; - } - return encode(s, "utf-8", filters); - } - - private static boolean check(int codepoint, Filter... filters) { - for (Filter filter : filters) { - if (filter.accept(codepoint)) { - return true; - } - } - return false; - } - - public static String encode(CharSequence s, String enc, Filter... filters) throws IOException { - if (s == null) { - return null; - } - StringBuilder sb = new StringBuilder(); - for (int n = 0; n < s.length(); n++) { - char c = s.charAt(n); - if (!CharUtils.isHighSurrogate(c) && check(c, filters)) { - encode(sb, String.valueOf(c).getBytes(enc)); - } else if (CharUtils.isHighSurrogate(c)) { - if (check(c, filters)) { - String buf = String.valueOf(c) + s.charAt(++n); - byte[] b = buf.getBytes(enc); - encode(sb, b); - } else { - sb.append(c); - sb.append(s.charAt(++n)); - } - } else { - sb.append(c); - } - } - return sb.toString(); - } - - public static String decode(String string) throws IOException { - String e = string; - char[] buf = new char[e.length()]; - try (DecodingReader r = new DecodingReader(new StringReader(e))) { - int l = r.read(buf); - e = new String(buf, 0, l); - } - return e; - } - - /** - * - */ - private static class DecodingReader extends FilterReader { - - DecodingReader(Reader in) { - super(in); - } - - @Override - public int read() throws IOException { - int c = super.read(); - if (c == '%') { - int c1 = super.read(); - int c2 = super.read(); - return decode((char) c1, (char) c2); - } else { - return c; - } - } - - @Override - public int read(char[] b, int off, int len) throws IOException { - int n = off; - int i; - while ((i = read()) != -1 && n < off + len) { - b[n++] = (char) i; - } - return n - off; - } - - @Override - public int read(char[] b) throws IOException { - return read(b, 0, b.length); - } - - @Override - public long skip(long n) throws IOException { - long i = 0; - int c; - for (; i < n; i++) { - c = read(); - if (c == -1) { - throw new EOFException(); - } - } - return i; - } - - private static byte decode(char c, int shift) { - return (byte) ((((c >= '0' && c <= '9') ? c - '0' : (c >= 'A' && c <= 'F') ? c - 'A' + 10 - : (c >= 'a' && c <= 'f') ? c - 'a' + 10 : -1) & 0xf) << shift); - } - - private static byte decode(char c1, char c2) { - return (byte) (decode(c1, 4) | decode(c2, 0)); - } - } -} diff --git a/content-resource/src/main/java/org/xbib/content/resource/url/UrlPercentEncoders.java b/content-resource/src/main/java/org/xbib/content/resource/url/UrlPercentEncoders.java deleted file mode 100755 index 925932e..0000000 --- a/content-resource/src/main/java/org/xbib/content/resource/url/UrlPercentEncoders.java +++ /dev/null @@ -1,166 +0,0 @@ -package org.xbib.content.resource.url; - -import static java.nio.charset.CodingErrorAction.REPLACE; - -import java.nio.charset.StandardCharsets; -import java.util.BitSet; - -/** - * See RFC 3986, RFC 1738 and Lunatech research. - */ -public final class UrlPercentEncoders { - - /** - * An encoder for RFC 3986 reg-names. - */ - - private static final BitSet REG_NAME_BIT_SET = new BitSet(); - - private static final BitSet PATH_BIT_SET = new BitSet(); - private static final BitSet MATRIX_BIT_SET = new BitSet(); - private static final BitSet UNSTRUCTURED_QUERY_BIT_SET = new BitSet(); - private static final BitSet QUERY_PARAM_BIT_SET = new BitSet(); - private static final BitSet FRAGMENT_BIT_SET = new BitSet(); - - static { - // RFC 3986 'reg-name'. This is not very aggressive... it's quite possible to have DNS-illegal names out of this. - // Regardless, it will at least be URI-compliant even if it's not HTTP URL-compliant. - addUnreserved(REG_NAME_BIT_SET); - addSubdelims(REG_NAME_BIT_SET); - - // Represents RFC 3986 'pchar'. Remove delimiter that starts matrix section. - addPChar(PATH_BIT_SET); - PATH_BIT_SET.clear((int) ';'); - - // Remove delims for HTTP matrix params as per RFC 1738 S3.3. The other reserved chars ('/' and '?') - // are already excluded. - addPChar(MATRIX_BIT_SET); - MATRIX_BIT_SET.clear((int) ';'); - MATRIX_BIT_SET.clear((int) '='); - - /* - * At this point it represents RFC 3986 'query'. http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1 also - * specifies that "+" can mean space in a query, so we will make sure to say that '+' is not safe to leave as-is - */ - addQuery(UNSTRUCTURED_QUERY_BIT_SET); - UNSTRUCTURED_QUERY_BIT_SET.clear((int) '+'); - - /* - * Create more stringent requirements for HTML4 queries: remove delimiters for HTML query params so that key=value - * pairs can be used. - */ - QUERY_PARAM_BIT_SET.or(UNSTRUCTURED_QUERY_BIT_SET); - QUERY_PARAM_BIT_SET.clear((int) '='); - QUERY_PARAM_BIT_SET.clear((int) '&'); - - addFragment(FRAGMENT_BIT_SET); - } - - private UrlPercentEncoders() { - } - - public static PercentEncoder getRegNameEncoder() { - return new PercentEncoder(REG_NAME_BIT_SET, StandardCharsets.UTF_8.newEncoder().onMalformedInput(REPLACE) - .onUnmappableCharacter(REPLACE)); - } - - public static PercentEncoder getPathEncoder() { - return new PercentEncoder(PATH_BIT_SET, StandardCharsets.UTF_8.newEncoder().onMalformedInput(REPLACE) - .onUnmappableCharacter(REPLACE)); - } - - public static PercentEncoder getMatrixEncoder() { - return new PercentEncoder(MATRIX_BIT_SET, StandardCharsets.UTF_8.newEncoder().onMalformedInput(REPLACE) - .onUnmappableCharacter(REPLACE)); - } - - public static PercentEncoder getUnstructuredQueryEncoder() { - return new PercentEncoder(UNSTRUCTURED_QUERY_BIT_SET, StandardCharsets.UTF_8.newEncoder().onMalformedInput(REPLACE) - .onUnmappableCharacter(REPLACE)); - } - - public static PercentEncoder getQueryParamEncoder() { - return new PercentEncoder(QUERY_PARAM_BIT_SET, StandardCharsets.UTF_8.newEncoder().onMalformedInput(REPLACE) - .onUnmappableCharacter(REPLACE)); - } - - public static PercentEncoder getFragmentEncoder() { - return new PercentEncoder(FRAGMENT_BIT_SET, StandardCharsets.UTF_8.newEncoder().onMalformedInput(REPLACE) - .onUnmappableCharacter(REPLACE)); - } - - /** - * Add code points for 'fragment' chars. - * - * @param fragmentBitSet bit set - */ - private static void addFragment(BitSet fragmentBitSet) { - addPChar(fragmentBitSet); - fragmentBitSet.set((int) '/'); - fragmentBitSet.set((int) '?'); - } - - /** - * Add code points for 'query' chars. - * - * @param queryBitSet bit set - */ - private static void addQuery(BitSet queryBitSet) { - addPChar(queryBitSet); - queryBitSet.set((int) '/'); - queryBitSet.set((int) '?'); - } - - /** - * Add code points for 'pchar' chars. - * - * @param bs bitset - */ - private static void addPChar(BitSet bs) { - addUnreserved(bs); - addSubdelims(bs); - bs.set((int) ':'); - bs.set((int) '@'); - } - - /** - * Add codepoints for 'unreserved' chars. - * - * @param bs bitset to add codepoints to - */ - private static void addUnreserved(BitSet bs) { - - for (int i = 'a'; i <= 'z'; i++) { - bs.set(i); - } - for (int i = 'A'; i <= 'Z'; i++) { - bs.set(i); - } - for (int i = '0'; i <= '9'; i++) { - bs.set(i); - } - bs.set((int) '-'); - bs.set((int) '.'); - bs.set((int) '_'); - bs.set((int) '~'); - } - - /** - * Add codepoints for 'sub-delims' chars. - * - * @param bs bitset to add codepoints to - */ - private static void addSubdelims(BitSet bs) { - bs.set((int) '!'); - bs.set((int) '$'); - bs.set((int) '&'); - bs.set((int) '\''); - bs.set((int) '('); - bs.set((int) ')'); - bs.set((int) '*'); - bs.set((int) '+'); - bs.set((int) ','); - bs.set((int) ';'); - bs.set((int) '='); - } -} diff --git a/content-resource/src/main/java/org/xbib/content/resource/url/package-info.java b/content-resource/src/main/java/org/xbib/content/resource/url/package-info.java deleted file mode 100644 index 020d1d8..0000000 --- a/content-resource/src/main/java/org/xbib/content/resource/url/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Classes for URL encoding and decoding. - */ -package org.xbib.content.resource.url; diff --git a/content-resource/src/test/java/org/xbib/content/resource/url/PercentEncoderTest.java b/content-resource/src/test/java/org/xbib/content/resource/url/PercentEncoderTest.java deleted file mode 100755 index 6b7c2de..0000000 --- a/content-resource/src/test/java/org/xbib/content/resource/url/PercentEncoderTest.java +++ /dev/null @@ -1,87 +0,0 @@ -package org.xbib.content.resource.url; - -import static org.junit.Assert.assertEquals; -import static java.nio.charset.CodingErrorAction.REPLACE; - -import org.junit.Before; -import org.junit.Test; - -import java.nio.charset.CharacterCodingException; -import java.nio.charset.MalformedInputException; -import java.nio.charset.StandardCharsets; -import java.nio.charset.UnmappableCharacterException; -import java.util.BitSet; - -/** - * - */ -public final class PercentEncoderTest { - - private PercentEncoder alnum; - private PercentEncoder alnum16; - - @Before - public void setUp() { - BitSet bs = new BitSet(); - for (int i = 'a'; i <= 'z'; i++) { - bs.set(i); - } - for (int i = 'A'; i <= 'Z'; i++) { - bs.set(i); - } - for (int i = '0'; i <= '9'; i++) { - bs.set(i); - } - - this.alnum = new PercentEncoder(bs, StandardCharsets.UTF_8.newEncoder().onMalformedInput(REPLACE) - .onUnmappableCharacter(REPLACE)); - this.alnum16 = new PercentEncoder(bs, StandardCharsets.UTF_16BE.newEncoder().onMalformedInput(REPLACE) - .onUnmappableCharacter(REPLACE)); - } - - @Test - public void testDoesntEncodeSafe() throws CharacterCodingException { - BitSet set = new BitSet(); - for (int i = 'a'; i <= 'z'; i++) { - set.set(i); - } - - PercentEncoder pe = new PercentEncoder(set, StandardCharsets.UTF_8.newEncoder().onMalformedInput(REPLACE) - .onUnmappableCharacter(REPLACE)); - assertEquals("abcd%41%42%43%44", pe.encode("abcdABCD")); - } - - @Test - public void testEncodeInBetweenSafe() throws MalformedInputException, UnmappableCharacterException { - assertEquals("abc%20123", alnum.encode("abc 123")); - } - - @Test - public void testSafeInBetweenEncoded() throws MalformedInputException, UnmappableCharacterException { - assertEquals("%20abc%20", alnum.encode(" abc ")); - } - - @Test - public void testEncodeUtf8() throws CharacterCodingException { - // 1 UTF-16 char (unicode snowman) - assertEquals("snowman%E2%98%83", alnum.encode("snowman\u2603")); - } - - @Test - public void testEncodeUtf8SurrogatePair() throws CharacterCodingException { - // musical G clef: 1d11e, has to be represented in surrogate pair form - assertEquals("clef%F0%9D%84%9E", alnum.encode("clef\ud834\udd1e")); - } - - @Test - public void testEncodeUtf16() throws CharacterCodingException { - // 1 UTF-16 char (unicode snowman) - assertEquals("snowman%26%03", alnum16.encode("snowman\u2603")); - } - - @Test - public void testUrlEncodedUtf16SurrogatePair() throws CharacterCodingException { - // musical G clef: 1d11e, has to be represented in surrogate pair form - assertEquals("clef%D8%34%DD%1E", alnum16.encode("clef\ud834\udd1e")); - } -} diff --git a/content-resource/src/test/java/org/xbib/content/resource/url/UrlBuilderTest.java b/content-resource/src/test/java/org/xbib/content/resource/url/UrlBuilderTest.java deleted file mode 100755 index e51b72e..0000000 --- a/content-resource/src/test/java/org/xbib/content/resource/url/UrlBuilderTest.java +++ /dev/null @@ -1,433 +0,0 @@ -package org.xbib.content.resource.url; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; -import static org.xbib.content.resource.url.UrlBuilder.forHost; -import static org.xbib.content.resource.url.UrlBuilder.fromUrl; - -import org.junit.Test; - -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.charset.CharacterCodingException; - -/** - * - */ -public final class UrlBuilderTest { - - private static void assertUrlEquals(String expected, String actual) - throws URISyntaxException, MalformedURLException { - assertEquals(expected, actual); - assertEquals(expected, new URI(actual).toString()); - assertEquals(expected, new URL(actual).toString()); - } - - @Test - public void testNoUrlParts() throws Exception { - assertUrlEquals("http://foo.com", forHost("http", "foo.com").toUrlString()); - } - - @Test - public void testWithPort() throws Exception { - assertUrlEquals("http://foo.com:33", forHost("http", "foo.com", 33).toUrlString()); - } - - @Test - public void testSimplePath() throws Exception { - UrlBuilder ub = forHost("http", "foo.com"); - ub.pathSegment("seg1").pathSegment("seg2"); - assertUrlEquals("http://foo.com/seg1/seg2", ub.toUrlString()); - } - - @Test - public void testPathWithReserved() throws Exception { - // RFC 1738 S3.3 - UrlBuilder ub = forHost("http", "foo.com"); - ub.pathSegment("seg/;?ment").pathSegment("seg=&2"); - assertUrlEquals("http://foo.com/seg%2F%3B%3Fment/seg=&2", ub.toUrlString()); - } - - @Test - public void testPathSegments() throws Exception { - UrlBuilder ub = forHost("http", "foo.com"); - ub.pathSegments("seg1", "seg2", "seg3"); - assertUrlEquals("http://foo.com/seg1/seg2/seg3", ub.toUrlString()); - } - - @Test - public void testMatrixWithoutPathHasLeadingSlash() throws Exception { - UrlBuilder ub = forHost("http", "foo.com"); - ub.matrixParam("foo", "bar"); - assertUrlEquals("http://foo.com/;foo=bar", ub.toUrlString()); - } - - @Test - public void testMatrixWithReserved() throws Exception { - UrlBuilder ub = forHost("http", "foo.com") - .pathSegment("foo") - .matrixParam("foo", "bar") - .matrixParam("res;=?#/erved", "value") - .pathSegment("baz"); - assertUrlEquals("http://foo.com/foo;foo=bar;res%3B%3D%3F%23%2Ferved=value/baz", ub.toUrlString()); - } - - @Test - public void testUrlEncodedPathSegmentUtf8() throws Exception { - // 1 UTF-16 char - UrlBuilder ub = forHost("http", "foo.com"); - ub.pathSegment("snowman").pathSegment("\u2603"); - assertUrlEquals("http://foo.com/snowman/%E2%98%83", ub.toUrlString()); - } - - @Test - public void testUrlEncodedPathSegmentUtf8SurrogatePair() throws Exception { - UrlBuilder ub = forHost("http", "foo.com"); - // musical G clef: 1d11e, has to be represented in surrogate pair form - ub.pathSegment("clef").pathSegment("\ud834\udd1e"); - assertUrlEquals("http://foo.com/clef/%F0%9D%84%9E", ub.toUrlString()); - } - - @Test - public void testQueryParamNoPath() throws Exception { - UrlBuilder ub = forHost("http", "foo.com"); - ub.queryParam("foo", "bar"); - String s = ub.toUrlString(); - assertUrlEquals("http://foo.com?foo=bar", s); - } - - @Test - public void testQueryParamsDuplicated() throws Exception { - UrlBuilder ub = forHost("http", "foo.com"); - ub.queryParam("foo", "bar"); - ub.queryParam("foo", "bar2"); - ub.queryParam("baz", "quux"); - ub.queryParam("baz", "quux2"); - assertUrlEquals("http://foo.com?foo=bar&foo=bar2&baz=quux&baz=quux2", ub.toUrlString()); - } - - @Test - public void testEncodeQueryParams() throws Exception { - UrlBuilder ub = forHost("http", "foo.com"); - ub.queryParam("foo", "bar&=#baz"); - ub.queryParam("foo", "bar?/2"); - assertUrlEquals("http://foo.com?foo=bar%26%3D%23baz&foo=bar?/2", ub.toUrlString()); - } - - @Test - public void testEncodeQueryParamWithSpaceAndPlus() throws Exception { - UrlBuilder ub = forHost("http", "foo.com"); - ub.queryParam("foo", "spa ce"); - ub.queryParam("fo+o", "plus+"); - assertUrlEquals("http://foo.com?foo=spa%20ce&fo%2Bo=plus%2B", ub.toUrlString()); - } - - @Test - public void testPlusInVariousParts() throws Exception { - UrlBuilder ub = forHost("http", "foo.com"); - - ub.pathSegment("has+plus") - .matrixParam("plusMtx", "pl+us") - .queryParam("plusQp", "pl+us") - .fragment("plus+frag"); - - assertUrlEquals("http://foo.com/has+plus;plusMtx=pl+us?plusQp=pl%2Bus#plus+frag", ub.toUrlString()); - } - - @Test - public void testFragment() throws Exception { - UrlBuilder ub = forHost("http", "foo.com"); - ub.queryParam("foo", "bar"); - ub.fragment("#frag/?"); - assertUrlEquals("http://foo.com?foo=bar#%23frag/?", ub.toUrlString()); - } - - @Test - public void testAllParts() throws Exception { - UrlBuilder ub = forHost("https", "foo.bar.com", 3333); - ub.pathSegment("foo"); - ub.pathSegment("bar"); - ub.matrixParam("mtx1", "val1"); - ub.matrixParam("mtx2", "val2"); - ub.queryParam("q1", "v1"); - ub.queryParam("q2", "v2"); - ub.fragment("zomg it's a fragment"); - - assertEquals("https://foo.bar.com:3333/foo/bar;mtx1=val1;mtx2=val2?q1=v1&q2=v2#zomg%20it's%20a%20fragment", - ub.toUrlString()); - } - - @Test - public void testIPv4Literal() throws Exception { - UrlBuilder ub = forHost("http", "127.0.0.1"); - assertUrlEquals("http://127.0.0.1", ub.toUrlString()); - } - - @Test - public void testBadIPv4LiteralDoesntChoke() throws Exception { - UrlBuilder ub = forHost("http", "300.100.50.1"); - assertUrlEquals("http://300.100.50.1", ub.toUrlString()); - } - - @Test - public void testIPv6LiteralLocalhost() throws Exception { - UrlBuilder ub = forHost("http", "[::1]"); - assertUrlEquals("http://[::1]", ub.toUrlString()); - } - - @Test - public void testIPv6Literal() throws Exception { - UrlBuilder ub = forHost("http", "[2001:db8:85a3::8a2e:370:7334]"); - assertUrlEquals("http://[2001:db8:85a3::8a2e:370:7334]", ub.toUrlString()); - } - - @Test - public void testEncodedRegNameSingleByte() throws Exception { - UrlBuilder ub = forHost("http", "host?name;"); - assertUrlEquals("http://host%3Fname;", ub.toUrlString()); - } - - @Test - public void testEncodedRegNameMultiByte() throws Exception { - UrlBuilder ub = forHost("http", "snow\u2603man"); - assertUrlEquals("http://snow%E2%98%83man", ub.toUrlString()); - } - - @Test - public void testForceTrailingSlash() throws Exception { - UrlBuilder ub = forHost("https", "foo.com").forceTrailingSlash().pathSegments("a", "b", "c"); - - assertUrlEquals("https://foo.com/a/b/c/", ub.toUrlString()); - } - - @Test - public void testForceTrailingSlashWithQueryParams() throws Exception { - UrlBuilder ub = - forHost("https", "foo.com").forceTrailingSlash().pathSegments("a", "b", "c").queryParam("foo", "bar"); - - assertUrlEquals("https://foo.com/a/b/c/?foo=bar", ub.toUrlString()); - } - - @Test - public void testForceTrailingSlashNoPathSegmentsWithMatrixParams() throws Exception { - UrlBuilder ub = forHost("https", "foo.com").forceTrailingSlash().matrixParam("m1", "v1"); - - assertUrlEquals("https://foo.com/;m1=v1/", ub.toUrlString()); - } - - @Test - public void testIntermingledMatrixParamsAndPathSegments() throws Exception { - - UrlBuilder ub = forHost("http", "foo.com") - .pathSegments("seg1", "seg2") - .matrixParam("m1", "v1") - .pathSegment("seg3") - .matrixParam("m2", "v2"); - - assertUrlEquals("http://foo.com/seg1/seg2;m1=v1/seg3;m2=v2", ub.toUrlString()); - } - - @Test - public void testFromUrlWithEverything() throws Exception { - String orig = - "https://foo.bar.com:33/foo/ba%20r;mtx1=val1;mtx2=val%202/seg%203;m2=v2?q1=v1&q2=v%202#zomg%20it's%20a%20fragm"; - assertUrlBuilderRoundtrip(orig); - } - - @Test - public void testFromUrlWithEmptyPath() throws Exception { - assertUrlBuilderRoundtrip("http://foo.com"); - } - - @Test - public void testFromUrlWithEmptyPathAndSlash() throws Exception { - assertUrlBuilderRoundtrip("http://foo.com/", "http://foo.com"); - } - - @Test - public void testFromUrlWithPort() throws Exception { - assertUrlBuilderRoundtrip("http://foo.com:1234"); - } - - @Test - public void testFromUrlWithEmptyPathSegent() throws Exception { - assertUrlBuilderRoundtrip("http://foo.com/foo//", "http://foo.com/foo"); - } - - @Test - public void testFromUrlWithEncodedHost() throws Exception { - assertUrlBuilderRoundtrip("http://f%20oo.com/bar"); - } - - @Test - public void testFromUrlWithEncodedPathSegment() throws Exception { - assertUrlBuilderRoundtrip("http://foo.com/foo/b%20ar"); - } - - @Test - public void testFromUrlWithEncodedMatrixParam() throws Exception { - assertUrlBuilderRoundtrip("http://foo.com/foo;m1=v1;m%202=v%202"); - } - - @Test - public void testFromUrlWithEncodedQueryParam() throws Exception { - assertUrlBuilderRoundtrip("http://foo.com/foo?q%201=v%202&q2=v2"); - } - - @Test - public void testFromUrlWithEncodedQueryParamDelimiter() throws Exception { - assertUrlBuilderRoundtrip("http://foo.com/foo?q1=%3Dv1&%26q2=v2"); - } - - @Test - public void testFromUrlWithEncodedFragment() throws Exception { - assertUrlBuilderRoundtrip("http://foo.com/foo#b%20ar"); - } - - @Test - public void testFromUrlWithMalformedMatrixPair() throws Exception { - try { - fromUrl(new URL("http://foo.com/foo;m1=v1=v2")); - fail(); - } catch (IllegalArgumentException e) { - assertEquals("Malformed matrix param: ", e.getMessage()); - } - } - - @Test - public void testFromUrlWithEmptyPathSegmentWithMatrixParams() throws Exception { - assertUrlBuilderRoundtrip("http://foo.com/foo/;m1=v1"); - } - - @Test - public void testFromUrlWithEmptyPathWithMatrixParams() throws Exception { - assertUrlBuilderRoundtrip("http://foo.com/;m1=v1"); - } - - @Test - public void testFromUrlWithEmptyPathWithMultipleMatrixParams() throws Exception { - assertUrlBuilderRoundtrip("http://foo.com/;m1=v1;m2=v2"); - } - - @Test - public void testFromUrlWithPathSegmentEndingWithSemicolon() throws Exception { - assertUrlBuilderRoundtrip("http://foo.com/foo;", "http://foo.com/foo"); - } - - @Test - public void testPercentDecodeInvalidPair() throws MalformedURLException, CharacterCodingException { - try { - fromUrl(new URL("http://foo.com/fo%2o")); - fail(); - } catch (IllegalArgumentException e) { - assertEquals("Invalid %-tuple <%2o>", e.getMessage()); - } - } - - @Test - public void testFromUrlMalformedQueryParamMultiValues() throws Exception { - assertUrlBuilderRoundtrip("http://foo.com/foo?q1=v1=v2"); - } - - @Test - public void testFromUrlMalformedQueryParamNoValue() throws Exception { - assertUrlBuilderRoundtrip("http://foo.com/foo?q1=v1&q2"); - } - - @Test - public void testFromUrlUnstructuredQueryWithEscapedChars() throws Exception { - assertUrlBuilderRoundtrip("http://foo.com/foo?query==&%23"); - } - - @Test - public void testCantUseQueryParamAfterQuery() { - UrlBuilder ub = forHost("http", "foo.com").unstructuredQuery("q"); - - try { - ub.queryParam("foo", "bar"); - fail(); - } catch (IllegalStateException e) { - assertEquals("Cannot call queryParam() when this already has an unstructured query specified", - e.getMessage()); - } - } - - @Test - public void testCantUseQueryAfterQueryParam() { - UrlBuilder ub = forHost("http", "foo.com").queryParam("foo", "bar"); - - try { - ub.unstructuredQuery("q"); - - fail(); - } catch (IllegalStateException e) { - assertEquals("Cannot call unstructuredQuery() when this already has queryParam pairs specified", - e.getMessage()); - } - } - - @Test - public void testUnstructuredQueryWithNoSpecialChars() throws Exception { - assertUrlEquals("http://foo.com?q", forHost("http", "foo.com").unstructuredQuery("q").toUrlString()); - } - - @Test - public void testUnstructuredQueryWithOkSpecialChars() throws Exception { - assertUrlEquals("http://foo.com?q?/&=", forHost("http", "foo.com").unstructuredQuery("q?/&=").toUrlString()); - } - - @Test - public void testUnstructuredQueryWithEscapedSpecialChars() throws Exception { - assertUrlEquals("http://foo.com?q%23%2B", forHost("http", "foo.com").unstructuredQuery("q#+").toUrlString()); - } - - @Test - public void testClearQueryRemovesQueryParam() throws Exception { - UrlBuilder ub = forHost("http", "host") - .queryParam("foo", "bar") - .clearQuery(); - assertUrlEquals("http://host", ub.toUrlString()); - } - - @Test - public void testClearQueryRemovesUnstructuredQuery() throws Exception { - UrlBuilder ub = forHost("http", "host") - .unstructuredQuery("foobar") - .clearQuery(); - assertUrlEquals("http://host", ub.toUrlString()); - } - - @Test - public void testClearQueryAfterQueryParamAllowsQuery() throws Exception { - UrlBuilder ub = forHost("http", "host") - .queryParam("foo", "bar") - .clearQuery() - .unstructuredQuery("foobar"); - assertUrlEquals("http://host?foobar", ub.toUrlString()); - } - - @Test - public void testClearQueryAfterQueryAllowsQueryParam() throws Exception { - UrlBuilder ub = forHost("http", "host") - .unstructuredQuery("foobar") - .clearQuery() - .queryParam("foo", "bar"); - assertUrlEquals("http://host?foo=bar", ub.toUrlString()); - } - - private void assertUrlBuilderRoundtrip(String url) - throws MalformedURLException, CharacterCodingException, URISyntaxException { - assertUrlBuilderRoundtrip(url, url); - } - - /** - * @param origUrl the url that will be used to create a URL - * @param finalUrl the URL string it should end up as - */ - private void assertUrlBuilderRoundtrip(String origUrl, String finalUrl) - throws MalformedURLException, CharacterCodingException, URISyntaxException { - assertUrlEquals(finalUrl, fromUrl(new URL(origUrl)).toUrlString()); - } -} diff --git a/content-resource/src/test/java/org/xbib/content/resource/url/package-info.java b/content-resource/src/test/java/org/xbib/content/resource/url/package-info.java deleted file mode 100644 index fb8c573..0000000 --- a/content-resource/src/test/java/org/xbib/content/resource/url/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Classes for testing URL pocessing. - */ -package org.xbib.content.resource.url; diff --git a/content-smile/build.gradle b/content-smile/build.gradle index 4dadf7a..df5ed6f 100644 --- a/content-smile/build.gradle +++ b/content-smile/build.gradle @@ -1,4 +1,4 @@ dependencies { compile project(':content-core') - compile "com.fasterxml.jackson.dataformat:jackson-dataformat-smile:${versions.jackson}" -} \ No newline at end of file + compile "com.fasterxml.jackson.dataformat:jackson-dataformat-smile:${project.property('jackson.version')}" +} diff --git a/content-xml/build.gradle b/content-xml/build.gradle index 475a405..b50f419 100644 --- a/content-xml/build.gradle +++ b/content-xml/build.gradle @@ -1,10 +1,9 @@ dependencies { compile project(':content-core') compile project(':content-resource') - compile "com.fasterxml.jackson.dataformat:jackson-dataformat-xml:${versions.jackson}" + compile "com.fasterxml.jackson.dataformat:jackson-dataformat-xml:${project.property('jackson.version')}" } - tasks.withType(JavaCompile) { options.compilerArgs << "-Xlint:all" << "-profile" << "compact2" } \ No newline at end of file diff --git a/content-xml/src/main/java/org/xbib/content/xml/XmlXContentGenerator.java b/content-xml/src/main/java/org/xbib/content/xml/XmlXContentGenerator.java index 133106a..f2bd16a 100644 --- a/content-xml/src/main/java/org/xbib/content/xml/XmlXContentGenerator.java +++ b/content-xml/src/main/java/org/xbib/content/xml/XmlXContentGenerator.java @@ -206,12 +206,12 @@ public class XmlXContentGenerator extends AbstractXContentGenerator { @Override public void writeString(String text) throws IOException { - generator.writeString(XMLUtil.sanitizeXml10(text)); + generator.writeString(XMLUtil.sanitize(text)); } @Override public void writeString(char[] text, int offset, int len) throws IOException { - generator.writeString(XMLUtil.sanitizeXml10(text, offset, len)); + generator.writeString(XMLUtil.sanitize(new String(text, offset, len))); } @Override diff --git a/content-xml/src/main/java/org/xbib/content/xml/util/XMLUtil.java b/content-xml/src/main/java/org/xbib/content/xml/util/XMLUtil.java index 7d9f57c..2644cbc 100644 --- a/content-xml/src/main/java/org/xbib/content/xml/util/XMLUtil.java +++ b/content-xml/src/main/java/org/xbib/content/xml/util/XMLUtil.java @@ -265,10 +265,24 @@ public final class XMLUtil { return sb.toString(); } + public static String sanitizeToLineFeed(CharSequence string) { + StringBuilder sb = new StringBuilder(); + for (int i = 0, len = string.length(); i < len; i++) { + char c = string.charAt(i); + boolean legal = c == '\u0009' || c == '\n' + || (c >= '\u0020' && c <= '\uD7FF') + || (c >= '\uE000' && c <= '\uFFFD'); + if (legal) { + sb.append(c); + } + } + return sb.toString(); + } + /** - * Does not work. + * The pattern matching does not work. * - * @param sequence the charatcer sequence + * @param sequence the character sequence * @return sanitized string */ public static String sanitizeXml10(CharSequence sequence) { diff --git a/content-xml/src/test/java/org/xbib/content/xml/XContentXmlBuilderTest.java b/content-xml/src/test/java/org/xbib/content/xml/XContentXmlBuilderTest.java index 28653cf..30b8799 100644 --- a/content-xml/src/test/java/org/xbib/content/xml/XContentXmlBuilderTest.java +++ b/content-xml/src/test/java/org/xbib/content/xml/XContentXmlBuilderTest.java @@ -189,7 +189,7 @@ public class XContentXmlBuilderTest extends Assert { QName root = new QName("root"); XContentBuilder builder = XmlXContent.contentBuilder(new XmlXParams(root)); builder.startObject().field("Hello", "World\u001b").endObject(); - assertEquals("World\ufffd", builder.string()); + assertEquals("World", builder.string()); } @Test diff --git a/content-yaml/build.gradle b/content-yaml/build.gradle index 34d412f..4daf6d8 100644 --- a/content-yaml/build.gradle +++ b/content-yaml/build.gradle @@ -1,4 +1,4 @@ dependencies { compile project(':content-core') - compile "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:${versions.jackson}" + compile "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:${project.property('jackson.version')}" } \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 0a02f90..9ffd55c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,6 @@ group = org.xbib name = content -version = 1.0.7 +version = 1.1.0 + +jackson.version = 2.8.4 +xbib-net.version = 1.0.0 diff --git a/gradle/publish.gradle b/gradle/publish.gradle index caf0531..05d7199 100644 --- a/gradle/publish.gradle +++ b/gradle/publish.gradle @@ -6,7 +6,7 @@ task xbibUpload(type: Upload, dependsOn: build) { if (project.hasProperty('xbibUsername')) { mavenDeployer { configuration = configurations.wagon - repository(url: uri('scpexe://xbib.org/repository')) { + repository(url: uri('sftp://xbib.org/repository')) { authentication(userName: xbibUsername, privateKey: xbibPrivateKey) } } @@ -64,3 +64,7 @@ task sonatypeUpload(type: Upload, dependsOn: build) { } } } + +nexusStaging { + packageGroup = "org.xbib" +} diff --git a/gradle/sonarqube.gradle b/gradle/sonarqube.gradle index 6d4c3fa..3985a4f 100644 --- a/gradle/sonarqube.gradle +++ b/gradle/sonarqube.gradle @@ -1,8 +1,8 @@ tasks.withType(FindBugs) { ignoreFailures = true reports { - xml.enabled = true - html.enabled = false + xml.enabled = false + html.enabled = true } } tasks.withType(Pmd) { @@ -22,10 +22,8 @@ tasks.withType(Checkstyle) { jacocoTestReport { reports { - xml.enabled true - csv.enabled false - xml.destination "${buildDir}/reports/jacoco-xml" - html.destination "${buildDir}/reports/jacoco-html" + xml.enabled = true + csv.enabled = false } } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 51288f9c2f05faf8d42e1a751a387ca7923882c3..7a3265ee94c0ab25cf079ac8ccdf87f41d455d42 100644 GIT binary patch delta 20025 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 KF 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 n*k#`9_82_Zz@t0ayJxha