diff --git a/.gitignore b/.gitignore index b429e1ac..aeafccd3 100755 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ target/ .idea *~ pom.xml.versionsBackup +dependency-reduced-pom.xml diff --git a/.travis.yml b/.travis.yml index 36aa3a3e..a311838b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,15 @@ language: java jdk: - - oraclejdk7 - - oraclejdk8 + - openjdk8 + - openjdk11 notifications: - email: - - ansell.peter@gmail.com - - tristan.king@gmail.com + email: false after_success: - mvn clean test jacoco:report coveralls:report +arch: + - amd64 + - ppc64le + +cache: + directories: + - $HOME/.m2 diff --git a/LICENCE b/LICENCE index e03ca9bb..2b584c5e 100644 --- a/LICENCE +++ b/LICENCE @@ -1,4 +1,5 @@ Copyright (c) 2012, Deutsche Forschungszentrum für Künstliche Intelligenz GmbH +Copyright (c) 2012-2017, JSONLD-Java contributors All rights reserved. Redistribution and use in source and binary forms, with or without @@ -21,4 +22,4 @@ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index 9e028ea8..f1102e69 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ +**JSONLD-Java is looking for a maintainer** + JSONLD-JAVA =========== -This is a Java implementation of the [JSON-LD specification](http://www.w3.org/TR/json-ld/) and the [JSON-LD-API specification](http://www.w3.org/TR/json-ld-api/). +This is a Java implementation of the [JSON-LD 1.0 specification](https://www.w3.org/TR/2014/REC-json-ld-20140116/) and the [JSON-LD-API 1.0 specification](https://www.w3.org/TR/2014/REC-json-ld-api-20140116/). [![Build Status](https://travis-ci.org/jsonld-java/jsonld-java.svg?branch=master)](https://travis-ci.org/jsonld-java/jsonld-java) [![Coverage Status](https://coveralls.io/repos/jsonld-java/jsonld-java/badge.svg?branch=master)](https://coveralls.io/r/jsonld-java/jsonld-java?branch=master) @@ -14,11 +16,12 @@ From Maven com.github.jsonld-java jsonld-java - 0.8.2 + 0.13.5 Code example ------------ + ```java // Open a valid json(-ld) input file InputStream inputStream = new FileInputStream("input.json"); @@ -36,11 +39,11 @@ Object compact = JsonLdProcessor.compact(jsonObject, context, options); // Print out the result (or don't, it's your call!) System.out.println(JsonUtils.toPrettyString(compact)); ``` + Processor options ----------------- -The Options specified by the [JSON-LD API Specification](http://json-ld.org/spec/latest/json-ld-api/#jsonldoptions) are accessible via the `com.github.jsonldjava.core.JsonLdOptions` class, and each `JsonLdProcessor.*` function has an optional input to take an instance of this class. - +The Options specified by the [JSON-LD API Specification](https://json-ld.org/spec/latest/json-ld-api/#the-jsonldoptions-type) are accessible via the `com.github.jsonldjava.core.JsonLdOptions` class, and each `JsonLdProcessor.*` function has an optional input to take an instance of this class. Controlling network traffic --------------------------- @@ -57,8 +60,7 @@ The default HTTP Client is wrapped with a [CachingHttpClient](https://hc.apache.org/httpcomponents-client-ga/httpclient-cache/apidocs/org/apache/http/impl/client/cache/CachingHttpClient.html) to provide a small memory-based cache (1000 objects, max 128 kB each) of regularly accessed contexts. - -### Loading contexts from classpath/JAR +### Loading contexts from classpath Your application might be parsing JSONLD documents which always use the same external `@context` IRIs. Although the default HTTP cache (see above) will @@ -107,8 +109,10 @@ automatically injected together with the current `Date`, meaning that the resource loaded from the JAR will effectively never expire (the real HTTP server will never be consulted by the Apache HTTP client): - Date: Wed, 19 Mar 2014 13:25:08 GMT - Cache-Control: max-age=2147483647 +``` +Date: Wed, 19 Mar 2014 13:25:08 GMT +Cache-Control: max-age=2147483647 +``` The mechanism for loading `jarcache.json` relies on [Thread.currentThread().getContextClassLoader()](http://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#getContextClassLoader%28%29) @@ -116,26 +120,55 @@ to locate resources from the classpath - if you are running on a command line, within a framework (e.g. OSGi) or Servlet container (e.g. Tomcat) this should normally be set correctly. If not, try: - ClassLoader oldContextCL = Thread.currentThread().getContextClassLoader(); - try { - Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); - JsonLdProcessor.expand(input); // or any other JsonLd operation - } finally { - // Restore, in case the current thread was doing something else - // with the context classloader before calling our method - Thread.currentThread().setContextClassLoader(oldContextCL); - } +```java +ClassLoader oldContextCL = Thread.currentThread().getContextClassLoader(); +try { + Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); + JsonLdProcessor.expand(input); // or any other JsonLd operation +} finally { + // Restore, in case the current thread was doing something else + // with the context classloader before calling our method + Thread.currentThread().setContextClassLoader(oldContextCL); +} +``` To disable all remote document fetching, when using the default DocumentLoader, set the following Java System Property to "true" using: - System.setProperty("com.github.jsonldjava.disallowRemoteContextLoading", "true"); +```java +System.setProperty("com.github.jsonldjava.disallowRemoteContextLoading", "true"); +``` You can also use the constant provided in DocumentLoader for the same purpose: - System.setProperty(DocumentLoader.DISALLOW_REMOTE_CONTEXT_LOADING, "true"); +```java +System.setProperty(DocumentLoader.DISALLOW_REMOTE_CONTEXT_LOADING, "true"); +``` + +Note that if you override DocumentLoader you should also support this setting for consistency and security. -Note that if you override DocumentLoader you should also support this setting for consistency. +### Loading contexts from a string + +Your application might be parsing JSONLD documents which reference external `@context` IRIs +that are not available as file URIs on the classpath. In this case, the `jarcache.json` +approach will not work. Instead you can inject the literal context file strings through +the `JsonLdOptions` object, as follows: + +```java +// Inject a context document into the options as a literal string +DocumentLoader dl = new DocumentLoader(); +JsonLdOptions options = new JsonLdOptions(); +// ... the contents of "contexts/example.jsonld" +String jsonContext = "{ \"@context\": { ... } }"; +dl.addInjectedDoc("http://www.example.com/context", jsonContext); +options.setDocumentLoader(dl); + +InputStream inputStream = new FileInputStream("input.json"); +Object jsonObject = JsonUtils.fromInputStream(inputStream); +Map context = new HashMap(); +Object compact = JsonLdProcessor.compact(jsonObject, context, options); +System.out.println(JsonUtils.toPrettyString(compact)); +``` ### Customizing the Apache HttpClient @@ -149,40 +182,40 @@ and passed as an argument to `JsonLdProcessor` arguments. Example of inserting a credential provider (e.g. to load a `@context` protected by HTTP Basic Auth): - - Object input = JsonUtils.fromInputStream(..); - DocumentLoader documentLoader = new DocumentLoader(); - - CredentialsProvider credsProvider = new BasicCredentialsProvider(); - credsProvider.setCredentials( - new AuthScope("localhost", 443), - new UsernamePasswordCredentials("username", "password")); + +```java +Object input = JsonUtils.fromInputStream(..); +DocumentLoader documentLoader = new DocumentLoader(); - CacheConfig cacheConfig = CacheConfig.custom().setMaxCacheEntries(1000) - .setMaxObjectSize(1024 * 128).build(); - - CloseableHttpClient httpClient = CachingHttpClientBuilder - .create() - // allow caching - .setCacheConfig(cacheConfig) - // Wrap the local JarCacheStorage around a BasicHttpCacheStorage - .setHttpCacheStorage( - new JarCacheStorage(null, cacheConfig, new BasicHttpCacheStorage( - cacheConfig))).... +CredentialsProvider credsProvider = new BasicCredentialsProvider(); +credsProvider.setCredentials( + new AuthScope("localhost", 443), + new UsernamePasswordCredentials("username", "password")); + +CacheConfig cacheConfig = CacheConfig.custom().setMaxCacheEntries(1000) + .setMaxObjectSize(1024 * 128).build(); + +CloseableHttpClient httpClient = CachingHttpClientBuilder + .create() + // allow caching + .setCacheConfig(cacheConfig) + // Wrap the local JarCacheStorage around a BasicHttpCacheStorage + .setHttpCacheStorage( + new JarCacheStorage(null, cacheConfig, new BasicHttpCacheStorage( + cacheConfig))).... - // Add in the credentials provider - .setDefaultCredentialsProvider(credsProvider); - + // Add in the credentials provider + .setDefaultCredentialsProvider(credsProvider); + // When you are finished setting the properties, call build + .build(); - // When you are finished setting the properties, call build - .build(); - - documentLoader.setHttpClient(httpClient); +documentLoader.setHttpClient(httpClient); - JsonLdOptions options = new JsonLdOptions(); - options.setDocumentLoader(documentLoader); - // .. and any other options - Object rdf = JsonLdProcessor.toRDF(input, options); +JsonLdOptions options = new JsonLdOptions(); +options.setDocumentLoader(documentLoader); +// .. and any other options +Object rdf = JsonLdProcessor.toRDF(input, options); +``` PLAYGROUND ---------- @@ -191,14 +224,18 @@ The [jsonld-java-tools](https://github.com/jsonld-java/jsonld-java-tools) reposi ### Initial clone and setup - git clone git@github.com:jsonld-java/jsonld-java-tools.git - chmod +x ./jsonldplayground +```bash +git clone git@github.com:jsonld-java/jsonld-java-tools.git +chmod +x ./jsonldplayground +``` ### Usage run the following to get usage details: - ./jsonldplayground --help +```bash +./jsonldplayground --help +``` For Developers -------------- @@ -207,14 +244,17 @@ For Developers `jsonld-java` uses maven to compile. From the base `jsonld-java` module run `mvn clean install` to install the jar into your local maven repository. - ### Running tests - mvn test +```bash +mvn test +``` or - mvn test -pl core +```bash +mvn test -pl core +``` to run only core package tests @@ -243,7 +283,9 @@ https://github.com/jsonld-java/jsonld-java/tree/master/core/reports Implementation Reports conforming to the [JSON-LD Implementation Report](http://json-ld.org/test-suite/reports/#instructions-for-submitting-implementation-reports) document can be regenerated using the following command: - mvn test -pl core -Dtest=JsonLdProcessorTest -Dreport.format= +```bash +mvn test -pl core -Dtest=JsonLdProcessorTest -Dreport.format= +``` Current possible values for `` include JSON-LD (`application/ld+json` or `jsonld`), NQuads (`text/plain`, `nquads`, `ntriples`, `nq` or `nt`) and Turtle (`text/turtle`, `turtle` or `ttl`). `*` can be used to generate reports in all available formats. @@ -255,7 +297,7 @@ This is the base package for JSONLD-Java. Integration with other Java packages a Existing integrations --------------------- -* [OpenRDF Sesame](https://bitbucket.org/openrdf/sesame) +* [Eclipse RDF4J](https://github.com/eclipse/rdf4j) * [Apache Jena](https://github.com/apache/jena/) * [RDF2GO](https://github.com/jsonld-java/jsonld-java-rdf2go) * [Apache Clerezza](https://github.com/jsonld-java/jsonld-java-clerezza) @@ -274,54 +316,57 @@ Create maven module Here is the basic outline for what your module's pom.xml should look like - - - - jsonld-java-integration - com.github.jsonld-java-parent - 0.8.1-SNAPSHOT - - 4.0.0 - jsonld-java-{your module} - JSONLD Java :: {your module name} - JSON-LD Java integration module for {RDF Library your module integrates} - jar - - - - {YOU} - {YOUR EMAIL ADDRESS} - - - - - - ${project.groupId} - jsonld-java - ${project.version} - jar - compile - - - ${project.groupId} - jsonld-java - ${project.version} - test-jar - test - - - junit - junit - test - - - org.slf4j - slf4j-jdk14 - test - - - +```xml + + + + com.github.jsonld-java + jsonld-java-parent + 0.13.5 + + 4.0.0 + jsonld-java-{your module} + 0.13.5-SNAPSHOT + JSONLD Java :: {your module name} + JSON-LD Java integration module for {RDF Library your module integrates} + jar + + + + {YOU} + {YOUR EMAIL ADDRESS} + + + + + + ${project.groupId} + jsonld-java + ${project.version} + jar + compile + + + ${project.groupId} + jsonld-java + ${project.version} + test-jar + test + + + junit + junit + test + + + org.slf4j + slf4j-jdk14 + test + + + +``` Make sure you edit the following: * `project/artifactId` : set this to `jsonld-java-{module id}`, where `{module id}` usually represents the RDF library you're integrating (e.g. `jsonld-java-jena`) @@ -356,12 +401,16 @@ There are two ways to use your `RDFParser` implementation. Register your parser with the `JSONLD` class and set `options.format` when you call `fromRDF` - JSONLD.registerRDFParser("format/identifier", new YourRDFParser()); - Object jsonld = JSONLD.fromRDF(yourInput, new Options("") {{ format = "format/identifier" }}); +```java +JSONLD.registerRDFParser("format/identifier", new YourRDFParser()); +Object jsonld = JSONLD.fromRDF(yourInput, new Options("") {{ format = "format/identifier" }}); +``` or pass an instance of your `RDFParser` into the `fromRDF` function - Object jsonld = JSONLD.fromRDF(yourInput, new YourRDFParser()); +```java +Object jsonld = JSONLD.fromRDF(yourInput, new YourRDFParser()); +``` ### JSONLDTripleCallback @@ -370,7 +419,9 @@ RDF model from JSON-LD - being called for each triple (technically quad). Pass an instance of your `TripleCallback` to `JSONLD.toRDF` - Object yourOutput = JSONLD.toRDF(jsonld, new YourTripleCallback()); +```java +Object yourOutput = JSONLD.toRDF(jsonld, new YourTripleCallback()); +``` Integrate with your framework ----------------------------- @@ -398,6 +449,134 @@ Alternatively, we can also host your repository in the jsonld-java organisation CHANGELOG ========= +### 2023-11-06 +* Release 0.13.6 +* Bump Jackson-databind version to latest for security update + +### 2023-11-03 +* Release 0.13.5 +* Bump Jackson and Guava versions to latest for security updates + +### 2021-12-13 +* Release 0.13.4 +* Switch test logging from log4j to logback (Patch by @ansell) +* Improve Travis CI build Performance (Patch by @YunLemon) + +### 2021-03-06 +* Release 0.13.3 +* Fix @type when subject and object are the same (Reported by @barthanssens, Patch by @umbreak) +* Ignore @base if remote context is not relative (Reported by @whikloj, Patch by @dr0i) +* Fix throwing recursive context inclusion (Patch by @umbreak) + +### 2020-09-24 +* Release 0.13.2 +* Fix Guava dependency shading (Reported by @ggrasso) +* Fix @context issues when using a remote context (Patch by @umbreak) +* Deprecate Context.serialize (Patch by @umbreak) + +### 2020-09-09 +* Release 0.13.1 +* Fix java.net.URI resolution (Reported by @ebremer and @afs, Patch by @dr0i) +* Shade Guava failureaccess module (Patch by @peacekeeper) +* Don't minimize Guava class shading (Patch by @elahrvivaz) +* Follow link headers to @context files (Patch by @dr0i and @fsteeg) + +### 2019-11-28 +* Release 0.13.0 +* Bump Jackson versions to latest for security updates (Patch by @afs) +* Do not canonicalise XSD Decimal typed values (Patch by @jhg023) +* Bump dependency and plugin versions + +### 2019-08-03 +* Release 0.12.5 +* Bump Jackson versions to latest for security updates (Patches by @afs) +* IRI resolution fixes (Patch by @fsteeg) + +### 2019-04-20 +* Release 0.12.4 +* Bump Jackson version to 2.9.8 +* Add a regression test for a past framing bug +* Throw error on empty key +* Add regression tests for workarounds to Text/URL dual definitions +* Persist JsonLdOptions through normalize/toRDF + +### 2018-11-24 +* Release 0.12.3 +* Fix NaN/Inf/-Inf raw value types on conversion to RDF +* Added fix for wrong rdf:type to @type conversion (Path by @umbreak) +* Open up Context.getTypeMapping and Context.getLanguageMapping for reuse + +### 2018-11-03 +* W3c json ld syntax 34 allow container set on aliased type (Patch by @dr0i) +* Release 0.12.2 + +### 2018-09-05 +* handle omit graph flag (Patch by @eroux) +* Release 0.12.1 +* Make pruneBlankNodeIdentifiers false by default in 1.0 mode and always true in 1.1 mode (Patch by @eroux) +* Fix issue with blank node identifier pruning when @id is aliased (Patch by @eroux) +* Allow wildcard {} for @id in framing (Patch by @eroux) + +### 2018-07-07 +* Fix tests setup for schema.org with HttpURLConnection that break because of the inability of HttpURLConnection to redirect from HTTP to HTTPS + +### 2018-04-08 +* Release 0.12.0 +* Encapsulate RemoteDocument and make it immutable + +### 2018-04-03 +* Fix performance issue caused by not caching schema.org and others that use ``Cache-Control: private`` (Patch by @HansBrende) +* Cache classpath scans for jarcache.json to fix a similar performance issue +* Add internal shaded dependency on Google Guava to use maintained soft and weak reference maps rather than adhoc versions +* Make JsonLdError a RuntimeException to improve its use in closures +* Bump minor version to 0.12 to reflect the API incompatibility caused by JsonLdError and protected field change and hiding in JarCacheStorage + +### 2018-01-25 +* Fix resource leak in JsonUtils.fromURL on unsuccessful requests (Patch by @plaplaige) + +### 2017-11-15 +* Ignore UTF BOM (Patch by @christopher-johnson) + +### 2017-08-26 +* Release 0.11.1 +* Fix @embed:@always support (Patch by @dr0i) + +### 2017-08-24 +* Release 0.11.0 + +### 2017-08-22 +* Add implicit "flag only" subframe to fix incomplete list recursion (Patch by @christopher-johnson) +* Support pruneBlankNodeIdentifiers framing option in 1.1 mode (Patch by @fsteeg and @eroux) +* Support new @embed values (Patch by @eroux) + +### 2017-07-11 +* Add injection of contexts directly into DocumentLoader (Patch by @ryankenney) +* Fix N-Quads content type (Patch by @NicolasRouquette) +* Add JsonUtils.fromJsonParser (Patch by @dschulten) + +### 2017-02-16 +* Make literals compare consistently (Patch by @stain) +* Release 0.10.0 + +### 2017-01-09 +* Propagate causes for JsonLdError instances where they were caused by other Exceptions +* Remove schema.org hack as it appears to work again now... +* Remove deprecated and unused APIs +* Bump version to 0.10.0-SNAPSHOT per the removed/changed APIs + +### 2016-12-23 +* Release 0.9.0 +* Fixes schema.org support that is broken with Apache HTTP Client but works with java.net.URL + +### 2016-05-20 +* Fix reported NPE in JsonLdApi.removeDependents + +### 2016-05-18 +* Release 0.8.3 +* Fix @base in remote contexts corrupting the local context + +### 2016-04-23 +* Support @default inside of sets for framing ### 2016-02-29 * Fix ConcurrentModificationException in the implementation of the Framing API diff --git a/core/pom.xml b/core/pom.xml index e317d19e..f14fb287 100755 --- a/core/pom.xml +++ b/core/pom.xml @@ -4,7 +4,7 @@ jsonld-java-parent com.github.jsonld-java - 0.8.3-SNAPSHOT + 0.13.6 4.0.0 jsonld-java @@ -43,14 +43,18 @@ commons-io commons-io + + com.google.guava + guava + junit junit test - org.slf4j - slf4j-log4j12 + ch.qos.logback + logback-classic test @@ -61,6 +65,52 @@ + + + org.apache.maven.plugins + maven-shade-plugin + + + + com.google.guava:guava + com.google.guava:failureaccess + + + + + com.google.common + com.github.jsonldjava.shaded.com.google.common + + + com.google.thirdparty + com.github.jsonldjava.shaded.com.google.thirdparty + + + + + com.google.guava:guava + + META-INF/maven/** + + + + com.google.guava:failureaccess + + META-INF/maven/** + + + + + + + package + + shade + + + + org.codehaus.mojo animal-sniffer-maven-plugin @@ -72,6 +122,7 @@ + !com.google.common.*, org.slf4j.*; version="[1.0.0,2)", * diff --git a/core/src/main/java/com/github/jsonldjava/core/Context.java b/core/src/main/java/com/github/jsonldjava/core/Context.java index 572c385e..4e563d78 100644 --- a/core/src/main/java/com/github/jsonldjava/core/Context.java +++ b/core/src/main/java/com/github/jsonldjava/core/Context.java @@ -9,6 +9,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.regex.Pattern; import com.github.jsonldjava.core.JsonLdError.Error; import com.github.jsonldjava.utils.JsonLdUrl; @@ -23,6 +24,9 @@ */ public class Context extends LinkedHashMap { + private static final long serialVersionUID = 2894534897574805571L; + + private static final Pattern URL_PATTERN = Pattern.compile("^https?://.*$", Pattern.CASE_INSENSITIVE); private JsonLdOptions options; private Map termDefinitions; public Map inverse = null; @@ -38,11 +42,13 @@ public Context(JsonLdOptions opts) { public Context(Map map, JsonLdOptions opts) { super(map); + checkEmptyKey(map); init(opts); } public Context(Map map) { super(map); + checkEmptyKey(map); init(new JsonLdOptions()); } @@ -55,7 +61,7 @@ public Context(Object context, JsonLdOptions opts) { private void init(JsonLdOptions options) { this.options = options; if (options.getBase() != null) { - this.put("@base", options.getBase()); + this.put(JsonLdConsts.BASE, options.getBase()); } this.termDefinitions = newMap(); } @@ -75,7 +81,8 @@ public Object compactValue(String activeProperty, Map value) { // 1) int numberMembers = value.size(); // 2) - if (value.containsKey("@index") && "@index".equals(this.getContainer(activeProperty))) { + if (value.containsKey(JsonLdConsts.INDEX) + && JsonLdConsts.INDEX.equals(this.getContainer(activeProperty))) { numberMembers--; } // 3) @@ -85,36 +92,38 @@ public Object compactValue(String activeProperty, Map value) { // 4) final String typeMapping = getTypeMapping(activeProperty); final String languageMapping = getLanguageMapping(activeProperty); - if (value.containsKey("@id")) { + if (value.containsKey(JsonLdConsts.ID)) { // 4.1) - if (numberMembers == 1 && "@id".equals(typeMapping)) { - return compactIri((String) value.get("@id")); + if (numberMembers == 1 && JsonLdConsts.ID.equals(typeMapping)) { + return compactIri((String) value.get(JsonLdConsts.ID)); } // 4.2) - if (numberMembers == 1 && "@vocab".equals(typeMapping)) { - return compactIri((String) value.get("@id"), true); + if (numberMembers == 1 && JsonLdConsts.VOCAB.equals(typeMapping)) { + return compactIri((String) value.get(JsonLdConsts.ID), true); } // 4.3) return value; } - final Object valueValue = value.get("@value"); + final Object valueValue = value.get(JsonLdConsts.VALUE); // 5) - if (value.containsKey("@type") && Obj.equals(value.get("@type"), typeMapping)) { + if (value.containsKey(JsonLdConsts.TYPE) + && Obj.equals(value.get(JsonLdConsts.TYPE), typeMapping)) { return valueValue; } // 6) - if (value.containsKey("@language")) { + if (value.containsKey(JsonLdConsts.LANGUAGE)) { // TODO: SPEC: doesn't specify to check default language as well - if (Obj.equals(value.get("@language"), languageMapping) - || Obj.equals(value.get("@language"), this.get("@language"))) { + if (Obj.equals(value.get(JsonLdConsts.LANGUAGE), languageMapping) || Obj + .equals(value.get(JsonLdConsts.LANGUAGE), this.get(JsonLdConsts.LANGUAGE))) { return valueValue; } } // 7) - if (numberMembers == 1 - && (!(valueValue instanceof String) || !this.containsKey("@language") || (termDefinitions - .containsKey(activeProperty) - && getTermDefinition(activeProperty).containsKey("@language") && languageMapping == null))) { + if (numberMembers == 1 && (!(valueValue instanceof String) + || !this.containsKey(JsonLdConsts.LANGUAGE) + || (termDefinitions.containsKey(activeProperty) + && getTermDefinition(activeProperty).containsKey(JsonLdConsts.LANGUAGE) + && languageMapping == null))) { return valueValue; } // 8) @@ -138,6 +147,28 @@ public Context parse(Object localContext, List remoteContexts) throws Js if (remoteContexts == null) { remoteContexts = new ArrayList(); } + return parse(localContext, remoteContexts, false); + } + + /** + * Helper method used to work around logic errors related to the recursive + * nature of the JSONLD-API Context Processing Algorithm. + * + * @param localContext + * The Local Context object. + * @param remoteContexts + * The list of Strings denoting the remote Context URLs. + * @param parsingARemoteContext + * True if localContext represents a remote context that has been + * parsed and sent into this method and false otherwise. This + * must be set to know whether to propagate the @code{@base} key + * from the context to the result. + * @return The parsed and merged Context. + * @throws JsonLdError + * If there is an error parsing the contexts. + */ + private Context parse(Object localContext, final List remoteContexts, + boolean parsingARemoteContext) throws JsonLdError { // 1. Initialize result to the result of cloning active context. Context result = this.clone(); // TODO: clone? // 2) @@ -147,7 +178,7 @@ public Context parse(Object localContext, List remoteContexts) throws Js ((List) localContext).add(temp); } // 3) - for (Object context : ((List) localContext)) { + for (final Object context : ((List) localContext)) { // 3.1) if (context == null) { result = new Context(this.options); @@ -157,63 +188,75 @@ public Context parse(Object localContext, List remoteContexts) throws Js } // 3.2) else if (context instanceof String) { - String uri = (String) result.get("@base"); + String uri = null; + // @base is ignored when processing remote contexts, https://github.com/jsonld-java/jsonld-java/issues/304 + if (!URL_PATTERN.matcher(context.toString()).matches()) { + uri = (String) result.get(JsonLdConsts.BASE); + } uri = JsonLdUrl.resolve(uri, (String) context); // 3.2.2 if (remoteContexts.contains(uri)) { throw new JsonLdError(Error.RECURSIVE_CONTEXT_INCLUSION, uri); } - remoteContexts.add(uri); + List nextRemoteContexts = new ArrayList<>(remoteContexts); + nextRemoteContexts.add(uri); // 3.2.3: Dereference context final RemoteDocument rd = this.options.getDocumentLoader().loadDocument(uri); - final Object remoteContext = rd.document; - if (!(remoteContext instanceof Map) - || !((Map) remoteContext).containsKey("@context")) { + final Object remoteContext = rd.getDocument(); + if (!(remoteContext instanceof Map) || !((Map) remoteContext) + .containsKey(JsonLdConsts.CONTEXT)) { // If the dereferenced document has no top-level JSON object // with an @context member throw new JsonLdError(Error.INVALID_REMOTE_CONTEXT, context); } - context = ((Map) remoteContext).get("@context"); + final Object tempContext = ((Map) remoteContext) + .get(JsonLdConsts.CONTEXT); // 3.2.4 - result = result.parse(context, remoteContexts); + result = result.parse(tempContext, nextRemoteContexts, true); // 3.2.5 continue; } else if (!(context instanceof Map)) { // 3.3 throw new JsonLdError(Error.INVALID_LOCAL_CONTEXT, context); } - + checkEmptyKey((Map) context); // 3.4 - if (remoteContexts.isEmpty() && ((Map) context).containsKey("@base")) { - final Object value = ((Map) context).get("@base"); + if (!parsingARemoteContext + && ((Map) context).containsKey(JsonLdConsts.BASE)) { + // 3.4.1 + final Object value = ((Map) context).get(JsonLdConsts.BASE); + // 3.4.2 if (value == null) { - result.remove("@base"); + result.remove(JsonLdConsts.BASE); } else if (value instanceof String) { + // 3.4.3 if (JsonLdUtils.isAbsoluteIri((String) value)) { - result.put("@base", value); + result.put(JsonLdConsts.BASE, value); } else { - final String baseUri = (String) result.get("@base"); + // 3.4.4 + final String baseUri = (String) result.get(JsonLdConsts.BASE); if (!JsonLdUtils.isAbsoluteIri(baseUri)) { throw new JsonLdError(Error.INVALID_BASE_IRI, baseUri); } - result.put("@base", JsonLdUrl.resolve(baseUri, (String) value)); + result.put(JsonLdConsts.BASE, JsonLdUrl.resolve(baseUri, (String) value)); } } else { + // 3.4.5 throw new JsonLdError(JsonLdError.Error.INVALID_BASE_IRI, "@base must be a string"); } } // 3.5 - if (((Map) context).containsKey("@vocab")) { - final Object value = ((Map) context).get("@vocab"); + if (((Map) context).containsKey(JsonLdConsts.VOCAB)) { + final Object value = ((Map) context).get(JsonLdConsts.VOCAB); if (value == null) { - result.remove("@vocab"); + result.remove(JsonLdConsts.VOCAB); } else if (value instanceof String) { if (JsonLdUtils.isAbsoluteIri((String) value)) { - result.put("@vocab", value); + result.put(JsonLdConsts.VOCAB, value); } else { throw new JsonLdError(Error.INVALID_VOCAB_MAPPING, "@value must be an absolute IRI"); @@ -225,12 +268,12 @@ else if (context instanceof String) { } // 3.6 - if (((Map) context).containsKey("@language")) { - final Object value = ((Map) context).get("@language"); + if (((Map) context).containsKey(JsonLdConsts.LANGUAGE)) { + final Object value = ((Map) context).get(JsonLdConsts.LANGUAGE); if (value == null) { - result.remove("@language"); + result.remove(JsonLdConsts.LANGUAGE); } else if (value instanceof String) { - result.put("@language", ((String) value).toLowerCase()); + result.put(JsonLdConsts.LANGUAGE, ((String) value).toLowerCase()); } else { throw new JsonLdError(Error.INVALID_DEFAULT_LANGUAGE, value); } @@ -239,7 +282,8 @@ else if (context instanceof String) { // 3.7 final Map defined = new LinkedHashMap(); for (final String key : ((Map) context).keySet()) { - if ("@base".equals(key) || "@vocab".equals(key) || "@language".equals(key)) { + if (JsonLdConsts.BASE.equals(key) || JsonLdConsts.VOCAB.equals(key) + || JsonLdConsts.LANGUAGE.equals(key)) { continue; } result.createTermDefinition((Map) context, key, defined); @@ -248,6 +292,15 @@ else if (context instanceof String) { return result; } + private void checkEmptyKey(final Map map) { + if (map.containsKey("")) { + // the term MUST NOT be an empty string ("") + // https://www.w3.org/TR/json-ld/#h3_terms + throw new JsonLdError(Error.INVALID_TERM_DEFINITION, + String.format("empty key for value '%s'", map.get(""))); + } + } + public Context parse(Object localContext) throws JsonLdError { return this.parse(localContext, new ArrayList()); } @@ -257,9 +310,7 @@ public Context parse(Object localContext) throws JsonLdError { * * http://json-ld.org/spec/latest/json-ld-api/#create-term-definition * - * @param result * @param context - * @param key * @param defined * @throws JsonLdError */ @@ -274,22 +325,24 @@ private void createTermDefinition(Map context, String term, defined.put(term, false); - if (JsonLdUtils.isKeyword(term)) { + if (JsonLdUtils.isKeyword(term) + && !(options.getAllowContainerSetOnType() && JsonLdConsts.TYPE.equals(term) + && !(context.get(term)).toString().contains(JsonLdConsts.ID))) { throw new JsonLdError(Error.KEYWORD_REDEFINITION, term); } this.termDefinitions.remove(term); Object value = context.get(term); - if (value == null - || (value instanceof Map && ((Map) value).containsKey("@id") && ((Map) value) - .get("@id") == null)) { + if (value == null || (value instanceof Map + && ((Map) value).containsKey(JsonLdConsts.ID) + && ((Map) value).get(JsonLdConsts.ID) == null)) { this.termDefinitions.put(term, null); defined.put(term, true); return; } if (value instanceof String) { - value = newMap("@id", value); + value = newMap(JsonLdConsts.ID, value); } if (!(value instanceof Map)) { @@ -303,79 +356,82 @@ private void createTermDefinition(Map context, String term, final Map definition = newMap(); // 10) - if (val.containsKey("@type")) { - if (!(val.get("@type") instanceof String)) { - throw new JsonLdError(Error.INVALID_TYPE_MAPPING, val.get("@type")); + if (val.containsKey(JsonLdConsts.TYPE)) { + if (!(val.get(JsonLdConsts.TYPE) instanceof String)) { + throw new JsonLdError(Error.INVALID_TYPE_MAPPING, val.get(JsonLdConsts.TYPE)); } - String type = (String) val.get("@type"); + String type = (String) val.get(JsonLdConsts.TYPE); try { - type = this.expandIri((String) val.get("@type"), false, true, context, defined); + type = this.expandIri((String) val.get(JsonLdConsts.TYPE), false, true, context, + defined); } catch (final JsonLdError error) { if (error.getType() != Error.INVALID_IRI_MAPPING) { throw error; } - throw new JsonLdError(Error.INVALID_TYPE_MAPPING, type); + throw new JsonLdError(Error.INVALID_TYPE_MAPPING, type, error); } // TODO: fix check for absoluteIri (blank nodes shouldn't count, at // least not here!) - if ("@id".equals(type) || "@vocab".equals(type) - || (!type.startsWith("_:") && JsonLdUtils.isAbsoluteIri(type))) { - definition.put("@type", type); + if (JsonLdConsts.ID.equals(type) || JsonLdConsts.VOCAB.equals(type) + || (!type.startsWith(JsonLdConsts.BLANK_NODE_PREFIX) + && JsonLdUtils.isAbsoluteIri(type))) { + definition.put(JsonLdConsts.TYPE, type); } else { throw new JsonLdError(Error.INVALID_TYPE_MAPPING, type); } } // 11) - if (val.containsKey("@reverse")) { - if (val.containsKey("@id")) { + if (val.containsKey(JsonLdConsts.REVERSE)) { + if (val.containsKey(JsonLdConsts.ID)) { throw new JsonLdError(Error.INVALID_REVERSE_PROPERTY, val); } - if (!(val.get("@reverse") instanceof String)) { + if (!(val.get(JsonLdConsts.REVERSE) instanceof String)) { throw new JsonLdError(Error.INVALID_IRI_MAPPING, "Expected String for @reverse value. got " - + (val.get("@reverse") == null ? "null" : val.get("@reverse") - .getClass())); + + (val.get(JsonLdConsts.REVERSE) == null ? "null" + : val.get(JsonLdConsts.REVERSE).getClass())); } - final String reverse = this.expandIri((String) val.get("@reverse"), false, true, - context, defined); + final String reverse = this.expandIri((String) val.get(JsonLdConsts.REVERSE), false, + true, context, defined); if (!JsonLdUtils.isAbsoluteIri(reverse)) { - throw new JsonLdError(Error.INVALID_IRI_MAPPING, "Non-absolute @reverse IRI: " - + reverse); + throw new JsonLdError(Error.INVALID_IRI_MAPPING, + "Non-absolute @reverse IRI: " + reverse); } - definition.put("@id", reverse); - if (val.containsKey("@container")) { - final String container = (String) val.get("@container"); - if (container == null || "@set".equals(container) || "@index".equals(container)) { - definition.put("@container", container); + definition.put(JsonLdConsts.ID, reverse); + if (val.containsKey(JsonLdConsts.CONTAINER)) { + final String container = (String) val.get(JsonLdConsts.CONTAINER); + if (container == null || JsonLdConsts.SET.equals(container) + || JsonLdConsts.INDEX.equals(container)) { + definition.put(JsonLdConsts.CONTAINER, container); } else { throw new JsonLdError(Error.INVALID_REVERSE_PROPERTY, "reverse properties only support set- and index-containers"); } } - definition.put("@reverse", true); + definition.put(JsonLdConsts.REVERSE, true); this.termDefinitions.put(term, definition); defined.put(term, true); return; } // 12) - definition.put("@reverse", false); + definition.put(JsonLdConsts.REVERSE, false); // 13) - if (val.get("@id") != null && !term.equals(val.get("@id"))) { - if (!(val.get("@id") instanceof String)) { + if (val.get(JsonLdConsts.ID) != null && !term.equals(val.get(JsonLdConsts.ID))) { + if (!(val.get(JsonLdConsts.ID) instanceof String)) { throw new JsonLdError(Error.INVALID_IRI_MAPPING, "expected value of @id to be a string"); } - final String res = this.expandIri((String) val.get("@id"), false, true, context, - defined); + final String res = this.expandIri((String) val.get(JsonLdConsts.ID), false, true, + context, defined); if (JsonLdUtils.isKeyword(res) || JsonLdUtils.isAbsoluteIri(res)) { - if ("@context".equals(res)) { + if (JsonLdConsts.CONTEXT.equals(res)) { throw new JsonLdError(Error.INVALID_KEYWORD_ALIAS, "cannot alias @context"); } - definition.put("@id", res); + definition.put(JsonLdConsts.ID, res); } else { throw new JsonLdError(Error.INVALID_IRI_MAPPING, "resulting IRI mapping should be a keyword, absolute IRI or blank node"); @@ -391,35 +447,42 @@ else if (term.indexOf(":") >= 0) { this.createTermDefinition(context, prefix, defined); } if (termDefinitions.containsKey(prefix)) { - definition.put("@id", - ((Map) termDefinitions.get(prefix)).get("@id") + suffix); + definition.put(JsonLdConsts.ID, + ((Map) termDefinitions.get(prefix)).get(JsonLdConsts.ID) + + suffix); } else { - definition.put("@id", term); + definition.put(JsonLdConsts.ID, term); } // 15) - } else if (this.containsKey("@vocab")) { - definition.put("@id", this.get("@vocab") + term); - } else { + } else if (this.containsKey(JsonLdConsts.VOCAB)) { + definition.put(JsonLdConsts.ID, this.get(JsonLdConsts.VOCAB) + term); + } else if (!JsonLdConsts.TYPE.equals(term)) { throw new JsonLdError(Error.INVALID_IRI_MAPPING, "relative term definition without vocab mapping"); } // 16) - if (val.containsKey("@container")) { - final String container = (String) val.get("@container"); - if (!"@list".equals(container) && !"@set".equals(container) - && !"@index".equals(container) && !"@language".equals(container)) { + if (val.containsKey(JsonLdConsts.CONTAINER)) { + final String container = (String) val.get(JsonLdConsts.CONTAINER); + if (!JsonLdConsts.LIST.equals(container) && !JsonLdConsts.SET.equals(container) + && !JsonLdConsts.INDEX.equals(container) + && !JsonLdConsts.LANGUAGE.equals(container)) { throw new JsonLdError(Error.INVALID_CONTAINER_MAPPING, "@container must be either @list, @set, @index, or @language"); } - definition.put("@container", container); + definition.put(JsonLdConsts.CONTAINER, container); + if (JsonLdConsts.TYPE.equals(term)) { + definition.put(JsonLdConsts.ID, "type"); + } } // 17) - if (val.containsKey("@language") && !val.containsKey("@type")) { - if (val.get("@language") == null || val.get("@language") instanceof String) { - final String language = (String) val.get("@language"); - definition.put("@language", language != null ? language.toLowerCase() : null); + if (val.containsKey(JsonLdConsts.LANGUAGE) && !val.containsKey(JsonLdConsts.TYPE)) { + if (val.get(JsonLdConsts.LANGUAGE) == null + || val.get(JsonLdConsts.LANGUAGE) instanceof String) { + final String language = (String) val.get(JsonLdConsts.LANGUAGE); + definition.put(JsonLdConsts.LANGUAGE, + language != null ? language.toLowerCase() : null); } else { throw new JsonLdError(Error.INVALID_LANGUAGE_MAPPING, "@language must be a string or null"); @@ -457,10 +520,9 @@ String expandIri(String value, boolean relative, boolean vocab, Map td = (LinkedHashMap) this.termDefinitions - .get(value); + final Map td = (Map) this.termDefinitions.get(value); if (td != null) { - return (String) td.get("@id"); + return (String) td.get(JsonLdConsts.ID); } else { return null; } @@ -482,19 +544,19 @@ String expandIri(String value, boolean relative, boolean vocab, Map) this.termDefinitions.get(prefix)) - .get("@id") + suffix; + return (String) ((Map) this.termDefinitions.get(prefix)) + .get(JsonLdConsts.ID) + suffix; } // 4.5) return value; } // 5) - if (vocab && this.containsKey("@vocab")) { - return this.get("@vocab") + value; + if (vocab && this.containsKey(JsonLdConsts.VOCAB)) { + return this.get(JsonLdConsts.VOCAB) + value; } // 6) else if (relative) { - return JsonLdUrl.resolve((String) this.get("@base"), value); + return JsonLdUrl.resolve((String) this.get(JsonLdConsts.BASE), value); } else if (context != null && JsonLdUtils.isRelativeIri(value)) { throw new JsonLdError(Error.INVALID_IRI_MAPPING, "not an absolute IRI: " + value); } @@ -514,9 +576,9 @@ else if (relative) { * the IRI to compact. * @param value * the value to check or null. - * @param relativeTo - * options for how to compact IRIs: vocab: true to split after - * @vocab, false not to. + * @param relativeToVocab + * options for how to compact IRIs: vocab: true to split + * after @vocab, false not to. * @param reverse * true if a reverse property is being compacted, false if not. * @@ -531,62 +593,66 @@ String compactIri(String iri, Object value, boolean relativeToVocab, boolean rev // 2) if (relativeToVocab && getInverse().containsKey(iri)) { // 2.1) - String defaultLanguage = (String) this.get("@language"); + String defaultLanguage = (String) this.get(JsonLdConsts.LANGUAGE); if (defaultLanguage == null) { - defaultLanguage = "@none"; + defaultLanguage = JsonLdConsts.NONE; } // 2.2) final List containers = new ArrayList(); // 2.3) - String typeLanguage = "@language"; - String typeLanguageValue = "@null"; + String typeLanguage = JsonLdConsts.LANGUAGE; + String typeLanguageValue = JsonLdConsts.NULL; // 2.4) - if (value instanceof Map && ((Map) value).containsKey("@index")) { - containers.add("@index"); + if (value instanceof Map + && ((Map) value).containsKey(JsonLdConsts.INDEX)) { + containers.add(JsonLdConsts.INDEX); } // 2.5) if (reverse) { - typeLanguage = "@type"; - typeLanguageValue = "@reverse"; - containers.add("@set"); + typeLanguage = JsonLdConsts.TYPE; + typeLanguageValue = JsonLdConsts.REVERSE; + containers.add(JsonLdConsts.SET); } // 2.6) - else if (value instanceof Map && ((Map) value).containsKey("@list")) { + else if (value instanceof Map + && ((Map) value).containsKey(JsonLdConsts.LIST)) { // 2.6.1) - if (!((Map) value).containsKey("@index")) { - containers.add("@list"); + if (!((Map) value).containsKey(JsonLdConsts.INDEX)) { + containers.add(JsonLdConsts.LIST); } // 2.6.2) - final List list = (List) ((Map) value).get("@list"); + final List list = (List) ((Map) value) + .get(JsonLdConsts.LIST); // 2.6.3) String commonLanguage = (list.size() == 0) ? defaultLanguage : null; String commonType = null; // 2.6.4) for (final Object item : list) { // 2.6.4.1) - String itemLanguage = "@none"; - String itemType = "@none"; + String itemLanguage = JsonLdConsts.NONE; + String itemType = JsonLdConsts.NONE; // 2.6.4.2) if (JsonLdUtils.isValue(item)) { // 2.6.4.2.1) - if (((Map) item).containsKey("@language")) { - itemLanguage = (String) ((Map) item).get("@language"); + if (((Map) item).containsKey(JsonLdConsts.LANGUAGE)) { + itemLanguage = (String) ((Map) item) + .get(JsonLdConsts.LANGUAGE); } // 2.6.4.2.2) - else if (((Map) item).containsKey("@type")) { - itemType = (String) ((Map) item).get("@type"); + else if (((Map) item).containsKey(JsonLdConsts.TYPE)) { + itemType = (String) ((Map) item).get(JsonLdConsts.TYPE); } // 2.6.4.2.3) else { - itemLanguage = "@null"; + itemLanguage = JsonLdConsts.NULL; } } // 2.6.4.3) else { - itemType = "@id"; + itemType = JsonLdConsts.ID; } // 2.6.4.4) if (commonLanguage == null) { @@ -594,7 +660,7 @@ else if (((Map) item).containsKey("@type")) { } // 2.6.4.5) else if (!commonLanguage.equals(itemLanguage) && JsonLdUtils.isValue(item)) { - commonLanguage = "@none"; + commonLanguage = JsonLdConsts.NONE; } // 2.6.4.6) if (commonType == null) { @@ -602,20 +668,21 @@ else if (!commonLanguage.equals(itemLanguage) && JsonLdUtils.isValue(item)) { } // 2.6.4.7) else if (!commonType.equals(itemType)) { - commonType = "@none"; + commonType = JsonLdConsts.NONE; } // 2.6.4.8) - if ("@none".equals(commonLanguage) && "@none".equals(commonType)) { + if (JsonLdConsts.NONE.equals(commonLanguage) + && JsonLdConsts.NONE.equals(commonType)) { break; } } // 2.6.5) - commonLanguage = (commonLanguage != null) ? commonLanguage : "@none"; + commonLanguage = (commonLanguage != null) ? commonLanguage : JsonLdConsts.NONE; // 2.6.6) - commonType = (commonType != null) ? commonType : "@none"; + commonType = (commonType != null) ? commonType : JsonLdConsts.NONE; // 2.6.7) - if (!"@none".equals(commonType)) { - typeLanguage = "@type"; + if (!JsonLdConsts.NONE.equals(commonType)) { + typeLanguage = JsonLdConsts.TYPE; typeLanguageValue = commonType; } // 2.6.8) @@ -626,64 +693,71 @@ else if (!commonType.equals(itemType)) { // 2.7) else { // 2.7.1) - if (value instanceof Map && ((Map) value).containsKey("@value")) { + if (value instanceof Map + && ((Map) value).containsKey(JsonLdConsts.VALUE)) { // 2.7.1.1) - if (((Map) value).containsKey("@language") - && !((Map) value).containsKey("@index")) { - containers.add("@language"); - typeLanguageValue = (String) ((Map) value).get("@language"); + if (((Map) value).containsKey(JsonLdConsts.LANGUAGE) + && !((Map) value).containsKey(JsonLdConsts.INDEX)) { + containers.add(JsonLdConsts.LANGUAGE); + typeLanguageValue = (String) ((Map) value) + .get(JsonLdConsts.LANGUAGE); } // 2.7.1.2) - else if (((Map) value).containsKey("@type")) { - typeLanguage = "@type"; - typeLanguageValue = (String) ((Map) value).get("@type"); + else if (((Map) value).containsKey(JsonLdConsts.TYPE)) { + typeLanguage = JsonLdConsts.TYPE; + typeLanguageValue = (String) ((Map) value) + .get(JsonLdConsts.TYPE); } } // 2.7.2) else { - typeLanguage = "@type"; - typeLanguageValue = "@id"; + typeLanguage = JsonLdConsts.TYPE; + typeLanguageValue = JsonLdConsts.ID; } // 2.7.3) - containers.add("@set"); + containers.add(JsonLdConsts.SET); } // 2.8) - containers.add("@none"); + containers.add(JsonLdConsts.NONE); // 2.9) if (typeLanguageValue == null) { - typeLanguageValue = "@null"; + typeLanguageValue = JsonLdConsts.NULL; } // 2.10) final List preferredValues = new ArrayList(); // 2.11) - if ("@reverse".equals(typeLanguageValue)) { - preferredValues.add("@reverse"); + if (JsonLdConsts.REVERSE.equals(typeLanguageValue)) { + preferredValues.add(JsonLdConsts.REVERSE); } // 2.12) - if (("@reverse".equals(typeLanguageValue) || "@id".equals(typeLanguageValue)) - && (value instanceof Map) && ((Map) value).containsKey("@id")) { + if ((JsonLdConsts.REVERSE.equals(typeLanguageValue) + || JsonLdConsts.ID.equals(typeLanguageValue)) && (value instanceof Map) + && ((Map) value).containsKey(JsonLdConsts.ID)) { // 2.12.1) final String result = this.compactIri( - (String) ((Map) value).get("@id"), null, true, true); + (String) ((Map) value).get(JsonLdConsts.ID), null, true, + true); if (termDefinitions.containsKey(result) - && ((Map) termDefinitions.get(result)).containsKey("@id") - && ((Map) value).get("@id").equals( - ((Map) termDefinitions.get(result)).get("@id"))) { - preferredValues.add("@vocab"); - preferredValues.add("@id"); + && ((Map) termDefinitions.get(result)) + .containsKey(JsonLdConsts.ID) + && ((Map) value).get(JsonLdConsts.ID) + .equals(((Map) termDefinitions.get(result)) + .get(JsonLdConsts.ID))) { + preferredValues.add(JsonLdConsts.VOCAB); + preferredValues.add(JsonLdConsts.ID); } // 2.12.2) else { - preferredValues.add("@id"); - preferredValues.add("@vocab"); + preferredValues.add(JsonLdConsts.ID); + preferredValues.add(JsonLdConsts.VOCAB); } } // 2.13) else { preferredValues.add(typeLanguageValue); } - preferredValues.add("@none"); + preferredValues.add(JsonLdConsts.NONE); // 2.14) final String term = selectTerm(iri, containers, typeLanguage, preferredValues); @@ -694,9 +768,9 @@ else if (((Map) value).containsKey("@type")) { } // 3) - if (relativeToVocab && this.containsKey("@vocab")) { + if (relativeToVocab && this.containsKey(JsonLdConsts.VOCAB)) { // determine if vocab is a prefix of the iri - final String vocab = (String) this.get("@vocab"); + final String vocab = (String) this.get(JsonLdConsts.VOCAB); // 3.1) if (iri.indexOf(vocab) == 0 && !iri.equals(vocab)) { // use suffix as relative iri if it is not a term in the @@ -719,16 +793,17 @@ else if (((Map) value).containsKey("@type")) { continue; } // 5.2) - if (termDefinition == null || iri.equals(termDefinition.get("@id")) - || !iri.startsWith((String) termDefinition.get("@id"))) { + if (termDefinition == null || iri.equals(termDefinition.get(JsonLdConsts.ID)) + || !iri.startsWith((String) termDefinition.get(JsonLdConsts.ID))) { continue; } // 5.3) final String candidate = term + ":" - + iri.substring(((String) termDefinition.get("@id")).length()); + + iri.substring(((String) termDefinition.get(JsonLdConsts.ID)).length()); // 5.4) - compactIRI = _iriCompactionStep5point4(iri, value, compactIRI, candidate, termDefinitions); + compactIRI = _iriCompactionStep5point4(iri, value, compactIRI, candidate, + termDefinitions); } // 6) @@ -738,25 +813,26 @@ else if (((Map) value).containsKey("@type")) { // 7) if (!relativeToVocab) { - return JsonLdUrl.removeBase(this.get("@base"), iri); + return JsonLdUrl.removeBase(this.get(JsonLdConsts.BASE), iri); } // 8) return iri; } - /** + /* * This method is only visible for testing. */ public static String _iriCompactionStep5point4(String iri, Object value, String compactIRI, final String candidate, Map termDefinitions) { - - boolean condition1 = (compactIRI == null || compareShortestLeast(candidate, compactIRI) < 0); - - boolean condition2 = (!termDefinitions.containsKey(candidate) || (iri - .equals(((Map) termDefinitions.get(candidate)) - .get("@id")) && value == null)); - + + final boolean condition1 = (compactIRI == null + || compareShortestLeast(candidate, compactIRI) < 0); + + final boolean condition2 = (!termDefinitions.containsKey(candidate) || (iri + .equals(((Map) termDefinitions.get(candidate)).get(JsonLdConsts.ID)) + && value == null)); + if (condition1 && condition2) { compactIRI = candidate; } @@ -771,11 +847,10 @@ public static String _iriCompactionStep5point4(String iri, Object value, String * ":". * * @param onlyCommonPrefixes - * If true, the result will not include - * "not so useful" prefixes, such as "term1": - * "http://example.com/term1", e.g. all IRIs will end with "/" or - * "#". If false, all potential prefixes are - * returned. + * If true, the result will not include "not so + * useful" prefixes, such as "term1": "http://example.com/term1", + * e.g. all IRIs will end with "/" or "#". If false, + * all potential prefixes are returned. * * @return A map from prefix string to IRI string */ @@ -790,7 +865,7 @@ public Map getPrefixes(boolean onlyCommonPrefixes) { if (termDefinition == null) { continue; } - final String id = (String) termDefinition.get("@id"); + final String id = (String) termDefinition.get(JsonLdConsts.ID); if (id == null) { continue; } @@ -842,9 +917,9 @@ public Map getInverse() { inverse = newMap(); // 2) - String defaultLanguage = (String) this.get("@language"); + String defaultLanguage = (String) this.get(JsonLdConsts.LANGUAGE); if (defaultLanguage == null) { - defaultLanguage = "@none"; + defaultLanguage = JsonLdConsts.NONE; } // create term selections for each mapping in the context, ordererd by @@ -865,13 +940,13 @@ public int compare(String a, String b) { } // 3.2) - String container = (String) definition.get("@container"); + String container = (String) definition.get(JsonLdConsts.CONTAINER); if (container == null) { - container = "@none"; + container = JsonLdConsts.NONE; } // 3.3) - final String iri = (String) definition.get("@id"); + final String iri = (String) definition.get(JsonLdConsts.ID); // 3.4 + 3.5) Map containerMap = (Map) inverse.get(iri); @@ -884,32 +959,32 @@ public int compare(String a, String b) { Map typeLanguageMap = (Map) containerMap.get(container); if (typeLanguageMap == null) { typeLanguageMap = newMap(); - typeLanguageMap.put("@language", newMap()); - typeLanguageMap.put("@type", newMap()); + typeLanguageMap.put(JsonLdConsts.LANGUAGE, newMap()); + typeLanguageMap.put(JsonLdConsts.TYPE, newMap()); containerMap.put(container, typeLanguageMap); } // 3.8) - if (Boolean.TRUE.equals(definition.get("@reverse"))) { + if (Boolean.TRUE.equals(definition.get(JsonLdConsts.REVERSE))) { final Map typeMap = (Map) typeLanguageMap - .get("@type"); - if (!typeMap.containsKey("@reverse")) { - typeMap.put("@reverse", term); + .get(JsonLdConsts.TYPE); + if (!typeMap.containsKey(JsonLdConsts.REVERSE)) { + typeMap.put(JsonLdConsts.REVERSE, term); } // 3.9) - } else if (definition.containsKey("@type")) { + } else if (definition.containsKey(JsonLdConsts.TYPE)) { final Map typeMap = (Map) typeLanguageMap - .get("@type"); - if (!typeMap.containsKey(definition.get("@type"))) { - typeMap.put((String) definition.get("@type"), term); + .get(JsonLdConsts.TYPE); + if (!typeMap.containsKey(definition.get(JsonLdConsts.TYPE))) { + typeMap.put((String) definition.get(JsonLdConsts.TYPE), term); } // 3.10) - } else if (definition.containsKey("@language")) { + } else if (definition.containsKey(JsonLdConsts.LANGUAGE)) { final Map languageMap = (Map) typeLanguageMap - .get("@language"); - String language = (String) definition.get("@language"); + .get(JsonLdConsts.LANGUAGE); + String language = (String) definition.get(JsonLdConsts.LANGUAGE); if (language == null) { - language = "@null"; + language = JsonLdConsts.NULL; } if (!languageMap.containsKey(language)) { languageMap.put(language, term); @@ -918,21 +993,21 @@ public int compare(String a, String b) { } else { // 3.11.1) final Map languageMap = (Map) typeLanguageMap - .get("@language"); + .get(JsonLdConsts.LANGUAGE); // 3.11.2) - if (!languageMap.containsKey("@language")) { - languageMap.put("@language", term); + if (!languageMap.containsKey(JsonLdConsts.LANGUAGE)) { + languageMap.put(JsonLdConsts.LANGUAGE, term); } // 3.11.3) - if (!languageMap.containsKey("@none")) { - languageMap.put("@none", term); + if (!languageMap.containsKey(JsonLdConsts.NONE)) { + languageMap.put(JsonLdConsts.NONE, term); } // 3.11.4) final Map typeMap = (Map) typeLanguageMap - .get("@type"); + .get(JsonLdConsts.TYPE); // 3.11.5) - if (!typeMap.containsKey("@none")) { - typeMap.put("@none", term); + if (!typeMap.containsKey(JsonLdConsts.NONE)) { + typeMap.put(JsonLdConsts.NONE, term); } } } @@ -989,20 +1064,23 @@ private String selectTerm(String iri, List containers, String typeLangua * * @param property * The Property to get a container mapping for. - * @return The container mapping + * @return The container mapping if any, else null */ public String getContainer(String property) { - if ("@graph".equals(property)) { - return "@set"; + if (property == null) { + return null; } - if (JsonLdUtils.isKeyword(property)) { + if (JsonLdConsts.GRAPH.equals(property)) { + return JsonLdConsts.SET; + } + if (!property.equals(JsonLdConsts.TYPE) && JsonLdUtils.isKeyword(property)) { return property; } final Map td = (Map) termDefinitions.get(property); if (td == null) { return null; } - return (String) td.get("@container"); + return (String) td.get(JsonLdConsts.CONTAINER); } public Boolean isReverseProperty(String property) { @@ -1010,24 +1088,24 @@ public Boolean isReverseProperty(String property) { if (td == null) { return false; } - final Object reverse = td.get("@reverse"); + final Object reverse = td.get(JsonLdConsts.REVERSE); return reverse != null && (Boolean) reverse; } - private String getTypeMapping(String property) { + public String getTypeMapping(String property) { final Map td = (Map) termDefinitions.get(property); if (td == null) { return null; } - return (String) td.get("@type"); + return (String) td.get(JsonLdConsts.TYPE); } - private String getLanguageMapping(String property) { + public String getLanguageMapping(String property) { final Map td = (Map) termDefinitions.get(property); if (td == null) { return null; } - return (String) td.get("@language"); + return (String) td.get(JsonLdConsts.LANGUAGE); } Map getTermDefinition(String key) { @@ -1038,84 +1116,82 @@ public Object expandValue(String activeProperty, Object value) throws JsonLdErro final Map rval = newMap(); final Map td = getTermDefinition(activeProperty); // 1) - if (td != null && "@id".equals(td.get("@type"))) { + if (td != null && JsonLdConsts.ID.equals(td.get(JsonLdConsts.TYPE))) { // TODO: i'm pretty sure value should be a string if the @type is // @id - rval.put("@id", expandIri(value.toString(), true, false, null, null)); + rval.put(JsonLdConsts.ID, expandIri(value.toString(), true, false, null, null)); return rval; } // 2) - if (td != null && "@vocab".equals(td.get("@type"))) { + if (td != null && JsonLdConsts.VOCAB.equals(td.get(JsonLdConsts.TYPE))) { // TODO: same as above - rval.put("@id", expandIri(value.toString(), true, true, null, null)); + rval.put(JsonLdConsts.ID, expandIri(value.toString(), true, true, null, null)); return rval; } // 3) - rval.put("@value", value); + rval.put(JsonLdConsts.VALUE, value); // 4) - if (td != null && td.containsKey("@type")) { - rval.put("@type", td.get("@type")); + if (td != null && td.containsKey(JsonLdConsts.TYPE)) { + rval.put(JsonLdConsts.TYPE, td.get(JsonLdConsts.TYPE)); } // 5) else if (value instanceof String) { // 5.1) - if (td != null && td.containsKey("@language")) { - final String lang = (String) td.get("@language"); + if (td != null && td.containsKey(JsonLdConsts.LANGUAGE)) { + final String lang = (String) td.get(JsonLdConsts.LANGUAGE); if (lang != null) { - rval.put("@language", lang); + rval.put(JsonLdConsts.LANGUAGE, lang); } } // 5.2) - else if (this.get("@language") != null) { - rval.put("@language", this.get("@language")); + else if (this.get(JsonLdConsts.LANGUAGE) != null) { + rval.put(JsonLdConsts.LANGUAGE, this.get(JsonLdConsts.LANGUAGE)); } } return rval; } - public Object getContextValue(String activeProperty, String string) throws JsonLdError { - throw new JsonLdError(Error.NOT_IMPLEMENTED, - "getContextValue is only used by old code so far and thus isn't implemented"); - } - + @Deprecated public Map serialize() { final Map ctx = newMap(); - if (this.get("@base") != null && !this.get("@base").equals(options.getBase())) { - ctx.put("@base", this.get("@base")); + if (this.get(JsonLdConsts.BASE) != null + && !this.get(JsonLdConsts.BASE).equals(options.getBase())) { + ctx.put(JsonLdConsts.BASE, this.get(JsonLdConsts.BASE)); } - if (this.get("@language") != null) { - ctx.put("@language", this.get("@language")); + if (this.get(JsonLdConsts.LANGUAGE) != null) { + ctx.put(JsonLdConsts.LANGUAGE, this.get(JsonLdConsts.LANGUAGE)); } - if (this.get("@vocab") != null) { - ctx.put("@vocab", this.get("@vocab")); + if (this.get(JsonLdConsts.VOCAB) != null) { + ctx.put(JsonLdConsts.VOCAB, this.get(JsonLdConsts.VOCAB)); } for (final String term : termDefinitions.keySet()) { final Map definition = (Map) termDefinitions.get(term); - if (definition.get("@language") == null - && definition.get("@container") == null - && definition.get("@type") == null - && (definition.get("@reverse") == null || Boolean.FALSE.equals(definition - .get("@reverse")))) { - final String cid = this.compactIri((String) definition.get("@id")); - ctx.put(term, term.equals(cid) ? definition.get("@id") : cid); + if (definition.get(JsonLdConsts.LANGUAGE) == null + && definition.get(JsonLdConsts.CONTAINER) == null + && definition.get(JsonLdConsts.TYPE) == null + && (definition.get(JsonLdConsts.REVERSE) == null + || Boolean.FALSE.equals(definition.get(JsonLdConsts.REVERSE)))) { + final String cid = this.compactIri((String) definition.get(JsonLdConsts.ID)); + ctx.put(term, term.equals(cid) ? definition.get(JsonLdConsts.ID) : cid); } else { final Map defn = newMap(); - final String cid = this.compactIri((String) definition.get("@id")); - final Boolean reverseProperty = Boolean.TRUE.equals(definition.get("@reverse")); + final String cid = this.compactIri((String) definition.get(JsonLdConsts.ID)); + final Boolean reverseProperty = Boolean.TRUE + .equals(definition.get(JsonLdConsts.REVERSE)); if (!(term.equals(cid) && !reverseProperty)) { - defn.put(reverseProperty ? "@reverse" : "@id", cid); + defn.put(reverseProperty ? JsonLdConsts.REVERSE : JsonLdConsts.ID, cid); } - final String typeMapping = (String) definition.get("@type"); + final String typeMapping = (String) definition.get(JsonLdConsts.TYPE); if (typeMapping != null) { - defn.put("@type", JsonLdUtils.isKeyword(typeMapping) ? typeMapping + defn.put(JsonLdConsts.TYPE, JsonLdUtils.isKeyword(typeMapping) ? typeMapping : compactIri(typeMapping, true)); } - if (definition.get("@container") != null) { - defn.put("@container", definition.get("@container")); + if (definition.get(JsonLdConsts.CONTAINER) != null) { + defn.put(JsonLdConsts.CONTAINER, definition.get(JsonLdConsts.CONTAINER)); } - final Object lang = definition.get("@language"); - if (definition.get("@language") != null) { - defn.put("@language", Boolean.FALSE.equals(lang) ? null : lang); + final Object lang = definition.get(JsonLdConsts.LANGUAGE); + if (definition.get(JsonLdConsts.LANGUAGE) != null) { + defn.put(JsonLdConsts.LANGUAGE, Boolean.FALSE.equals(lang) ? null : lang); } ctx.put(term, defn); } @@ -1123,9 +1199,9 @@ public Map serialize() { final Map rval = newMap(); if (!(ctx == null || ctx.isEmpty())) { - rval.put("@context", ctx); + rval.put(JsonLdConsts.CONTEXT, ctx); } return rval; } -} \ No newline at end of file +} diff --git a/core/src/main/java/com/github/jsonldjava/core/DocumentLoader.java b/core/src/main/java/com/github/jsonldjava/core/DocumentLoader.java index de9f4199..55faaac7 100644 --- a/core/src/main/java/com/github/jsonldjava/core/DocumentLoader.java +++ b/core/src/main/java/com/github/jsonldjava/core/DocumentLoader.java @@ -1,86 +1,95 @@ package com.github.jsonldjava.core; -import java.io.IOException; -import java.io.InputStream; import java.net.URL; +import java.util.HashMap; +import java.util.Map; import org.apache.http.impl.client.CloseableHttpClient; -import com.fasterxml.jackson.core.JsonParseException; import com.github.jsonldjava.utils.JsonUtils; +/** + * Resolves URLs to {@link RemoteDocument}s. Subclass this class to change the + * behaviour of loadDocument to suit your purposes. + */ public class DocumentLoader { + private final Map m_injectedDocs = new HashMap<>(); + /** * Identifies a system property that can be set to "true" in order to * disallow remote context loading. */ public static final String DISALLOW_REMOTE_CONTEXT_LOADING = "com.github.jsonldjava.disallowRemoteContextLoading"; - public RemoteDocument loadDocument(String url) throws JsonLdError { - String disallowRemote = System.getProperty(DocumentLoader.DISALLOW_REMOTE_CONTEXT_LOADING); - - if ("true".equalsIgnoreCase(disallowRemote)) { - throw new JsonLdError(JsonLdError.Error.LOADING_REMOTE_CONTEXT_FAILED, url); - } - - final RemoteDocument doc = new RemoteDocument(url, null); + /** + * Avoid resolving a document by instead using the given serialised + * representation. + * + * @param url + * The URL this document represents. + * @param doc + * The serialised document as a String + * @return This object for fluent addition of other injected documents. + * @throws JsonLdError + * If loading of the document failed for any reason. + */ + public DocumentLoader addInjectedDoc(String url, String doc) throws JsonLdError { try { - doc.setDocument(fromURL(new URL(url))); + m_injectedDocs.put(url, JsonUtils.fromString(doc)); + return this; } catch (final Exception e) { - throw new JsonLdError(JsonLdError.Error.LOADING_REMOTE_CONTEXT_FAILED, url); + throw new JsonLdError(JsonLdError.Error.LOADING_INJECTED_CONTEXT_FAILED, url, e); } - return doc; } /** - * An HTTP Accept header that prefers JSONLD. - * @deprecated Use {@link JsonUtils#ACCEPT_HEADER} instead. - */ - @Deprecated - public static final String ACCEPT_HEADER = JsonUtils.ACCEPT_HEADER; - - private volatile CloseableHttpClient httpClient; - - /** - * Returns a Map, List, or String containing the contents of the JSON - * resource resolved from the JsonLdUrl. + * Loads the URL if possible, returning it as a RemoteDocument. * * @param url - * The JsonLdUrl to resolve - * @return The Map, List, or String that represent the JSON resource - * resolved from the JsonLdUrl - * @throws JsonParseException - * If the JSON was not valid. - * @throws IOException - * If there was an error resolving the resource. + * The URL to load + * @return The resolved URL as a RemoteDocument + * @throws JsonLdError + * If there are errors loading or remote context loading has + * been disallowed. */ - public Object fromURL(java.net.URL url) throws JsonParseException, IOException { - return JsonUtils.fromURL(url, getHttpClient()); + public RemoteDocument loadDocument(String url) throws JsonLdError { + if (m_injectedDocs.containsKey(url)) { + try { + return new RemoteDocument(url, m_injectedDocs.get(url)); + } catch (final Exception e) { + throw new JsonLdError(JsonLdError.Error.LOADING_INJECTED_CONTEXT_FAILED, url, e); + } + } else { + final String disallowRemote = System + .getProperty(DocumentLoader.DISALLOW_REMOTE_CONTEXT_LOADING); + if ("true".equalsIgnoreCase(disallowRemote)) { + throw new JsonLdError(JsonLdError.Error.LOADING_REMOTE_CONTEXT_FAILED, + "Remote context loading has been disallowed (url was " + url + ")"); + } + + try { + return new RemoteDocument(url, JsonUtils.fromURL(new URL(url), getHttpClient())); + } catch (final Exception e) { + throw new JsonLdError(JsonLdError.Error.LOADING_REMOTE_CONTEXT_FAILED, url, e); + } + } } - + + private volatile CloseableHttpClient httpClient; + /** - * Opens an {@link InputStream} for the given {@link java.net.URL}, - * including support for http and https URLs that are requested using - * Content Negotiation with application/ld+json as the preferred content - * type. + * Get the {@link CloseableHttpClient} which will be used by this + * DocumentLoader to resolve HTTP and HTTPS resources. * - * @param url - * The {@link java.net.URL} identifying the source. - * @return An InputStream containing the contents of the source. - * @throws IOException - * If there was an error resolving the {@link java.net.URL}. + * @return The {@link CloseableHttpClient} which this DocumentLoader uses. */ - public InputStream openStreamFromURL(java.net.URL url) throws IOException { - return JsonUtils.openStreamForURL(url, getHttpClient()); - } - public CloseableHttpClient getHttpClient() { CloseableHttpClient result = httpClient; if (result == null) { - synchronized(DocumentLoader.class) { + synchronized (DocumentLoader.class) { result = httpClient; - if(result == null) { + if (result == null) { result = httpClient = JsonUtils.getDefaultHttpClient(); } } @@ -88,6 +97,13 @@ public CloseableHttpClient getHttpClient() { return result; } + /** + * Call this method to override the default CloseableHttpClient provided by + * JsonUtils.getDefaultHttpClient. + * + * @param nextHttpClient + * The {@link CloseableHttpClient} to replace the default with. + */ public void setHttpClient(CloseableHttpClient nextHttpClient) { httpClient = nextHttpClient; } diff --git a/core/src/main/java/com/github/jsonldjava/core/JsonLdApi.java b/core/src/main/java/com/github/jsonldjava/core/JsonLdApi.java index 6911ca37..2195d09a 100644 --- a/core/src/main/java/com/github/jsonldjava/core/JsonLdApi.java +++ b/core/src/main/java/com/github/jsonldjava/core/JsonLdApi.java @@ -11,8 +11,10 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -21,6 +23,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.github.jsonldjava.core.JsonLdConsts.Embed; import com.github.jsonldjava.core.JsonLdError.Error; import com.github.jsonldjava.utils.Obj; @@ -161,7 +164,8 @@ public Object compact(Context activeCtx, String activeProperty, Object element, // 2.2) for (final Object item : (List) element) { // 2.2.1) - final Object compactedItem = compact(activeCtx, activeProperty, item, compactArrays); + final Object compactedItem = compact(activeCtx, activeProperty, item, + compactArrays); // 2.2.2) if (compactedItem != null) { result.add(compactedItem); @@ -182,14 +186,14 @@ public Object compact(Context activeCtx, String activeProperty, Object element, final Map elem = (Map) element; // 4 - if (elem.containsKey("@value") || elem.containsKey("@id")) { + if (elem.containsKey(JsonLdConsts.VALUE) || elem.containsKey(JsonLdConsts.ID)) { final Object compactedValue = activeCtx.compactValue(activeProperty, elem); if (!(compactedValue instanceof Map || compactedValue instanceof List)) { return compactedValue; } } // 5) - final boolean insideReverse = ("@reverse".equals(activeProperty)); + final boolean insideReverse = (JsonLdConsts.REVERSE.equals(activeProperty)); // 6) final Map result = newMap(); @@ -198,15 +202,18 @@ public Object compact(Context activeCtx, String activeProperty, Object element, Collections.sort(keys); for (final String expandedProperty : keys) { final Object expandedValue = elem.get(expandedProperty); - // 7.1) - if ("@id".equals(expandedProperty) || "@type".equals(expandedProperty)) { + if (JsonLdConsts.ID.equals(expandedProperty) + || JsonLdConsts.TYPE.equals(expandedProperty)) { + // TODO: Relabel these step numbers when spec changes + // 7.1.3) + final String alias = activeCtx.compactIri(expandedProperty, true); Object compactedValue; // 7.1.1) if (expandedValue instanceof String) { compactedValue = activeCtx.compactIri((String) expandedValue, - "@type".equals(expandedProperty)); + JsonLdConsts.TYPE.equals(expandedProperty)); } // 7.1.2) else { @@ -216,15 +223,16 @@ public Object compact(Context activeCtx, String activeProperty, Object element, types.add(activeCtx.compactIri(expandedType, true)); } // 7.1.2.3) - if (types.size() == 1) { + if (types.size() == 1// + // see w3c/json-ld-syntax#74 + && (!opts.getAllowContainerSetOnType() + || !(activeCtx.getContainer(alias) != null && activeCtx + .getContainer(alias).equals(JsonLdConsts.SET)))) { compactedValue = types.get(0); } else { compactedValue = types; } } - - // 7.1.3) - final String alias = activeCtx.compactIri(expandedProperty, true); // 7.1.4) result.put(alias, compactedValue); continue; @@ -235,10 +243,10 @@ public Object compact(Context activeCtx, String activeProperty, Object element, } // 7.2) - if ("@reverse".equals(expandedProperty)) { + if (JsonLdConsts.REVERSE.equals(expandedProperty)) { // 7.2.1) final Map compactedValue = (Map) compact( - activeCtx, "@reverse", expandedValue, compactArrays); + activeCtx, JsonLdConsts.REVERSE, expandedValue, compactArrays); // 7.2.2) // Note: Must create a new set to avoid modifying the set we @@ -248,8 +256,8 @@ public Object compact(Context activeCtx, String activeProperty, Object element, // 7.2.2.1) if (activeCtx.isReverseProperty(property)) { // 7.2.2.1.1) - if (("@set".equals(activeCtx.getContainer(property)) || !compactArrays) - && !(value instanceof List)) { + if ((JsonLdConsts.SET.equals(activeCtx.getContainer(property)) + || !compactArrays) && !(value instanceof List)) { final List tmp = new ArrayList(); tmp.add(value); result.put(property, tmp); @@ -278,7 +286,7 @@ public Object compact(Context activeCtx, String activeProperty, Object element, // 7.2.3) if (!compactedValue.isEmpty()) { // 7.2.3.1) - final String alias = activeCtx.compactIri("@reverse", true); + final String alias = activeCtx.compactIri(JsonLdConsts.REVERSE, true); // 7.2.3.2) result.put(alias, compactedValue); } @@ -287,13 +295,14 @@ public Object compact(Context activeCtx, String activeProperty, Object element, } // 7.3) - if ("@index".equals(expandedProperty) - && "@index".equals(activeCtx.getContainer(activeProperty))) { + if (JsonLdConsts.INDEX.equals(expandedProperty) + && JsonLdConsts.INDEX.equals(activeCtx.getContainer(activeProperty))) { continue; } // 7.4) - else if ("@index".equals(expandedProperty) || "@value".equals(expandedProperty) - || "@language".equals(expandedProperty)) { + else if (JsonLdConsts.INDEX.equals(expandedProperty) + || JsonLdConsts.VALUE.equals(expandedProperty) + || JsonLdConsts.LANGUAGE.equals(expandedProperty)) { // 7.4.1) final String alias = activeCtx.compactIri(expandedProperty, true); // 7.4.2) @@ -331,16 +340,16 @@ else if ("@index".equals(expandedProperty) || "@value".equals(expandedProperty) final String container = activeCtx.getContainer(itemActiveProperty); // get @list value if appropriate - final boolean isList = (expandedItem instanceof Map && ((Map) expandedItem) - .containsKey("@list")); + final boolean isList = (expandedItem instanceof Map + && ((Map) expandedItem).containsKey(JsonLdConsts.LIST)); Object list = null; if (isList) { - list = ((Map) expandedItem).get("@list"); + list = ((Map) expandedItem).get(JsonLdConsts.LIST); } // 7.6.3) - Object compactedItem = compact(activeCtx, itemActiveProperty, isList ? list - : expandedItem, compactArrays); + Object compactedItem = compact(activeCtx, itemActiveProperty, + isList ? list : expandedItem, compactArrays); // 7.6.4) if (isList) { @@ -351,20 +360,23 @@ else if ("@index".equals(expandedProperty) || "@value".equals(expandedProperty) compactedItem = tmp; } // 7.6.4.2) - if (!"@list".equals(container)) { + if (!JsonLdConsts.LIST.equals(container)) { // 7.6.4.2.1) final Map wrapper = newMap(); // TODO: SPEC: no mention of vocab = true - wrapper.put(activeCtx.compactIri("@list", true), compactedItem); + wrapper.put(activeCtx.compactIri(JsonLdConsts.LIST, true), + compactedItem); compactedItem = wrapper; // 7.6.4.2.2) - if (((Map) expandedItem).containsKey("@index")) { + if (((Map) expandedItem) + .containsKey(JsonLdConsts.INDEX)) { ((Map) compactedItem).put( // TODO: SPEC: no mention of vocab = // true - activeCtx.compactIri("@index", true), - ((Map) expandedItem).get("@index")); + activeCtx.compactIri(JsonLdConsts.INDEX, true), + ((Map) expandedItem) + .get(JsonLdConsts.INDEX)); } } // 7.6.4.3) @@ -375,7 +387,8 @@ else if (result.containsKey(itemActiveProperty)) { } // 7.6.5) - if ("@language".equals(container) || "@index".equals(container)) { + if (JsonLdConsts.LANGUAGE.equals(container) + || JsonLdConsts.INDEX.equals(container)) { // 7.6.5.1) Map mapObject; if (result.containsKey(itemActiveProperty)) { @@ -386,10 +399,11 @@ else if (result.containsKey(itemActiveProperty)) { } // 7.6.5.2) - if ("@language".equals(container) - && (compactedItem instanceof Map && ((Map) compactedItem) - .containsKey("@value"))) { - compactedItem = ((Map) compactedItem).get("@value"); + if (JsonLdConsts.LANGUAGE.equals(container) && (compactedItem instanceof Map + && ((Map) compactedItem) + .containsKey(JsonLdConsts.VALUE))) { + compactedItem = ((Map) compactedItem) + .get(JsonLdConsts.VALUE); } // 7.6.5.3) @@ -412,9 +426,10 @@ else if (result.containsKey(itemActiveProperty)) { // 7.6.6) else { // 7.6.6.1) - final Boolean check = (!compactArrays || "@set".equals(container) - || "@list".equals(container) || "@list".equals(expandedProperty) || "@graph" - .equals(expandedProperty)) + final Boolean check = (!compactArrays || JsonLdConsts.SET.equals(container) + || JsonLdConsts.LIST.equals(container) + || JsonLdConsts.LIST.equals(expandedProperty) + || JsonLdConsts.GRAPH.equals(expandedProperty)) && (!(compactedItem instanceof List)); if (check) { final List tmp = new ArrayList(); @@ -465,7 +480,7 @@ else if (result.containsKey(itemActiveProperty)) { */ public Object compact(Context activeCtx, String activeProperty, Object element) throws JsonLdError { - return compact(activeCtx, activeProperty, element, true); + return compact(activeCtx, activeProperty, element, JsonLdOptions.DEFAULT_COMPACT_ARRAYS); } /*** @@ -493,6 +508,7 @@ public Object compact(Context activeCtx, String activeProperty, Object element) */ public Object expand(Context activeCtx, String activeProperty, Object element) throws JsonLdError { + final boolean frameExpansion = this.opts.getFrameExpansion(); // 1) if (element == null) { return null; @@ -507,10 +523,10 @@ public Object expand(Context activeCtx, String activeProperty, Object element) // 3.2.1) final Object v = expand(activeCtx, activeProperty, item); // 3.2.2) - if (("@list".equals(activeProperty) || "@list".equals(activeCtx - .getContainer(activeProperty))) - && (v instanceof List || (v instanceof Map && ((Map) v) - .containsKey("@list")))) { + if ((JsonLdConsts.LIST.equals(activeProperty) + || JsonLdConsts.LIST.equals(activeCtx.getContainer(activeProperty))) + && (v instanceof List || (v instanceof Map + && ((Map) v).containsKey(JsonLdConsts.LIST)))) { throw new JsonLdError(Error.LIST_OF_LISTS, "lists of lists are not permitted."); } // 3.2.3) @@ -530,8 +546,8 @@ else if (element instanceof Map) { // access helper final Map elem = (Map) element; // 5) - if (elem.containsKey("@context")) { - activeCtx = activeCtx.parse(elem.get("@context")); + if (elem.containsKey(JsonLdConsts.CONTEXT)) { + activeCtx = activeCtx.parse(elem.get(JsonLdConsts.CONTEXT)); } // 6) Map result = newMap(); @@ -541,7 +557,7 @@ else if (element instanceof Map) { for (final String key : keys) { final Object value = elem.get(key); // 7.1) - if (key.equals("@context")) { + if (key.equals(JsonLdConsts.CONTEXT)) { continue; } // 7.2) @@ -555,26 +571,48 @@ else if (element instanceof Map) { // 7.4) if (isKeyword(expandedProperty)) { // 7.4.1) - if ("@reverse".equals(activeProperty)) { + if (JsonLdConsts.REVERSE.equals(activeProperty)) { throw new JsonLdError(Error.INVALID_REVERSE_PROPERTY_MAP, "a keyword cannot be used as a @reverse propery"); } // 7.4.2) if (result.containsKey(expandedProperty)) { - throw new JsonLdError(Error.COLLIDING_KEYWORDS, expandedProperty - + " already exists in result"); + throw new JsonLdError(Error.COLLIDING_KEYWORDS, + expandedProperty + " already exists in result"); } // 7.4.3) - if ("@id".equals(expandedProperty)) { - if (!(value instanceof String)) { + if (JsonLdConsts.ID.equals(expandedProperty)) { + if (value instanceof String) { + expandedValue = activeCtx.expandIri((String) value, true, false, null, + null); + } else if (frameExpansion) { + if (value instanceof Map) { + if (((Map) value).size() != 0) { + throw new JsonLdError(Error.INVALID_ID_VALUE, + "@id value must be a an empty object for framing"); + } + expandedValue = value; + } else if (value instanceof List) { + expandedValue = new ArrayList(); + for (final Object v : (List) value) { + if (!(v instanceof String)) { + throw new JsonLdError(Error.INVALID_ID_VALUE, + "@id value must be a string, an array of strings or an empty dictionary"); + } + ((List) expandedValue).add(activeCtx + .expandIri((String) v, true, true, null, null)); + } + } else { + throw new JsonLdError(Error.INVALID_ID_VALUE, + "value of @id must be a string, an array of strings or an empty dictionary"); + } + } else { throw new JsonLdError(Error.INVALID_ID_VALUE, "value of @id must be a string"); } - expandedValue = activeCtx - .expandIri((String) value, true, false, null, null); } // 7.4.4) - else if ("@type".equals(expandedProperty)) { + else if (JsonLdConsts.TYPE.equals(expandedProperty)) { if (value instanceof List) { expandedValue = new ArrayList(); for (final Object v : (List) value) { @@ -582,16 +620,16 @@ else if ("@type".equals(expandedProperty)) { throw new JsonLdError(Error.INVALID_TYPE_VALUE, "@type value must be a string or array of strings"); } - ((List) expandedValue).add(activeCtx.expandIri((String) v, - true, true, null, null)); + ((List) expandedValue).add( + activeCtx.expandIri((String) v, true, true, null, null)); } } else if (value instanceof String) { expandedValue = activeCtx.expandIri((String) value, true, true, null, null); } // TODO: SPEC: no mention of empty map check - else if (value instanceof Map) { - if (((Map) value).size() != 0) { + else if (frameExpansion && value instanceof Map) { + if (!((Map) value).isEmpty()) { throw new JsonLdError(Error.INVALID_TYPE_VALUE, "@type value must be a an empty object for framing"); } @@ -602,41 +640,41 @@ else if (value instanceof Map) { } } // 7.4.5) - else if ("@graph".equals(expandedProperty)) { - expandedValue = expand(activeCtx, "@graph", value); + else if (JsonLdConsts.GRAPH.equals(expandedProperty)) { + expandedValue = expand(activeCtx, JsonLdConsts.GRAPH, value); } // 7.4.6) - else if ("@value".equals(expandedProperty)) { + else if (JsonLdConsts.VALUE.equals(expandedProperty)) { if (value != null && (value instanceof Map || value instanceof List)) { - throw new JsonLdError(Error.INVALID_VALUE_OBJECT_VALUE, "value of " - + expandedProperty + " must be a scalar or null"); + throw new JsonLdError(Error.INVALID_VALUE_OBJECT_VALUE, + "value of " + expandedProperty + " must be a scalar or null"); } expandedValue = value; if (expandedValue == null) { - result.put("@value", null); + result.put(JsonLdConsts.VALUE, null); continue; } } // 7.4.7) - else if ("@language".equals(expandedProperty)) { + else if (JsonLdConsts.LANGUAGE.equals(expandedProperty)) { if (!(value instanceof String)) { - throw new JsonLdError(Error.INVALID_LANGUAGE_TAGGED_STRING, "Value of " - + expandedProperty + " must be a string"); + throw new JsonLdError(Error.INVALID_LANGUAGE_TAGGED_STRING, + "Value of " + expandedProperty + " must be a string"); } expandedValue = ((String) value).toLowerCase(); } // 7.4.8) - else if ("@index".equals(expandedProperty)) { + else if (JsonLdConsts.INDEX.equals(expandedProperty)) { if (!(value instanceof String)) { - throw new JsonLdError(Error.INVALID_INDEX_VALUE, "Value of " - + expandedProperty + " must be a string"); + throw new JsonLdError(Error.INVALID_INDEX_VALUE, + "Value of " + expandedProperty + " must be a string"); } expandedValue = value; } // 7.4.9) - else if ("@list".equals(expandedProperty)) { + else if (JsonLdConsts.LIST.equals(expandedProperty)) { // 7.4.9.1) - if (activeProperty == null || "@graph".equals(activeProperty)) { + if (activeProperty == null || JsonLdConsts.GRAPH.equals(activeProperty)) { continue; } // 7.4.9.2) @@ -651,29 +689,31 @@ else if ("@list".equals(expandedProperty)) { // 7.4.9.3) for (final Object o : (List) expandedValue) { - if (o instanceof Map && ((Map) o).containsKey("@list")) { + if (o instanceof Map + && ((Map) o).containsKey(JsonLdConsts.LIST)) { throw new JsonLdError(Error.LIST_OF_LISTS, "A list may not contain another list"); } } } // 7.4.10) - else if ("@set".equals(expandedProperty)) { + else if (JsonLdConsts.SET.equals(expandedProperty)) { expandedValue = expand(activeCtx, activeProperty, value); } // 7.4.11) - else if ("@reverse".equals(expandedProperty)) { + else if (JsonLdConsts.REVERSE.equals(expandedProperty)) { if (!(value instanceof Map)) { throw new JsonLdError(Error.INVALID_REVERSE_VALUE, "@reverse value must be an object"); } // 7.4.11.1) - expandedValue = expand(activeCtx, "@reverse", value); + expandedValue = expand(activeCtx, JsonLdConsts.REVERSE, value); // NOTE: algorithm assumes the result is a map // 7.4.11.2) - if (((Map) expandedValue).containsKey("@reverse")) { + if (((Map) expandedValue) + .containsKey(JsonLdConsts.REVERSE)) { final Map reverse = (Map) ((Map) expandedValue) - .get("@reverse"); + .get(JsonLdConsts.REVERSE); for (final String property : reverse.keySet()) { final Object item = reverse.get(property); // 7.4.11.2.1) @@ -690,19 +730,20 @@ else if ("@reverse".equals(expandedProperty)) { } } // 7.4.11.3) - if (((Map) expandedValue).size() > (((Map) expandedValue) - .containsKey("@reverse") ? 1 : 0)) { + if (((Map) expandedValue) + .size() > (((Map) expandedValue) + .containsKey(JsonLdConsts.REVERSE) ? 1 : 0)) { // 7.4.11.3.1) - if (!result.containsKey("@reverse")) { - result.put("@reverse", newMap()); + if (!result.containsKey(JsonLdConsts.REVERSE)) { + result.put(JsonLdConsts.REVERSE, newMap()); } // 7.4.11.3.2) final Map reverseMap = (Map) result - .get("@reverse"); + .get(JsonLdConsts.REVERSE); // 7.4.11.3.3) for (final String property : ((Map) expandedValue) .keySet()) { - if ("@reverse".equals(property)) { + if (JsonLdConsts.REVERSE.equals(property)) { continue; } // 7.4.11.3.3.1) @@ -710,9 +751,10 @@ else if ("@reverse".equals(expandedProperty)) { .get(property); for (final Object item : items) { // 7.4.11.3.3.1.1) - if (item instanceof Map - && (((Map) item).containsKey("@value") || ((Map) item) - .containsKey("@list"))) { + if (item instanceof Map && (((Map) item) + .containsKey(JsonLdConsts.VALUE) + || ((Map) item) + .containsKey(JsonLdConsts.LIST))) { throw new JsonLdError(Error.INVALID_REVERSE_PROPERTY_VALUE); } // 7.4.11.3.3.1.2) @@ -728,11 +770,12 @@ else if ("@reverse".equals(expandedProperty)) { continue; } // TODO: SPEC no mention of @explicit etc in spec - else if ("@explicit".equals(expandedProperty) - || "@default".equals(expandedProperty) - || "@embed".equals(expandedProperty) - || "@embedChildren".equals(expandedProperty) - || "@omitDefault".equals(expandedProperty)) { + else if (frameExpansion && (JsonLdConsts.EXPLICIT.equals(expandedProperty) + || JsonLdConsts.DEFAULT.equals(expandedProperty) + || JsonLdConsts.EMBED.equals(expandedProperty) + || JsonLdConsts.REQUIRE_ALL.equals(expandedProperty) + || JsonLdConsts.EMBED_CHILDREN.equals(expandedProperty) + || JsonLdConsts.OMIT_DEFAULT.equals(expandedProperty))) { expandedValue = expand(activeCtx, expandedProperty, value); } // 7.4.12) @@ -743,7 +786,8 @@ else if ("@explicit".equals(expandedProperty) continue; } // 7.5 - else if ("@language".equals(activeCtx.getContainer(key)) && value instanceof Map) { + else if (JsonLdConsts.LANGUAGE.equals(activeCtx.getContainer(key)) + && value instanceof Map) { // 7.5.1) expandedValue = new ArrayList(); // 7.5.2) @@ -759,19 +803,20 @@ else if ("@language".equals(activeCtx.getContainer(key)) && value instanceof Map for (final Object item : (List) languageValue) { // 7.5.2.2.1) if (!(item instanceof String)) { - throw new JsonLdError(Error.INVALID_LANGUAGE_MAP_VALUE, "Expected " - + item.toString() + " to be a string"); + throw new JsonLdError(Error.INVALID_LANGUAGE_MAP_VALUE, + "Expected " + item.toString() + " to be a string"); } // 7.5.2.2.2) final Map tmp = newMap(); - tmp.put("@value", item); - tmp.put("@language", language.toLowerCase()); + tmp.put(JsonLdConsts.VALUE, item); + tmp.put(JsonLdConsts.LANGUAGE, language.toLowerCase()); ((List) expandedValue).add(tmp); } } } // 7.6) - else if ("@index".equals(activeCtx.getContainer(key)) && value instanceof Map) { + else if (JsonLdConsts.INDEX.equals(activeCtx.getContainer(key)) + && value instanceof Map) { // 7.6.1) expandedValue = new ArrayList(); // 7.6.2) @@ -791,8 +836,8 @@ else if ("@index".equals(activeCtx.getContainer(key)) && value instanceof Map) { // 7.6.2.3) for (final Map item : (List>) indexValue) { // 7.6.2.3.1) - if (!item.containsKey("@index")) { - item.put("@index", index); + if (!item.containsKey(JsonLdConsts.INDEX)) { + item.put(JsonLdConsts.INDEX, index); } // 7.6.2.3.2) ((List) expandedValue).add(item); @@ -808,27 +853,27 @@ else if ("@index".equals(activeCtx.getContainer(key)) && value instanceof Map) { continue; } // 7.9) - if ("@list".equals(activeCtx.getContainer(key))) { - if (!(expandedValue instanceof Map) - || !((Map) expandedValue).containsKey("@list")) { + if (JsonLdConsts.LIST.equals(activeCtx.getContainer(key))) { + if (!(expandedValue instanceof Map) || !((Map) expandedValue) + .containsKey(JsonLdConsts.LIST)) { Object tmp = expandedValue; if (!(tmp instanceof List)) { tmp = new ArrayList(); ((List) tmp).add(expandedValue); } expandedValue = newMap(); - ((Map) expandedValue).put("@list", tmp); + ((Map) expandedValue).put(JsonLdConsts.LIST, tmp); } } // 7.10) if (activeCtx.isReverseProperty(key)) { // 7.10.1) - if (!result.containsKey("@reverse")) { - result.put("@reverse", newMap()); + if (!result.containsKey(JsonLdConsts.REVERSE)) { + result.put(JsonLdConsts.REVERSE, newMap()); } // 7.10.2) final Map reverseMap = (Map) result - .get("@reverse"); + .get(JsonLdConsts.REVERSE); // 7.10.3) if (!(expandedValue instanceof List)) { final Object tmp = expandedValue; @@ -838,9 +883,9 @@ else if ("@index".equals(activeCtx.getContainer(key)) && value instanceof Map) { // 7.10.4) for (final Object item : (List) expandedValue) { // 7.10.4.1) - if (item instanceof Map - && (((Map) item).containsKey("@value") || ((Map) item) - .containsKey("@list"))) { + if (item instanceof Map && (((Map) item) + .containsKey(JsonLdConsts.VALUE) + || ((Map) item).containsKey(JsonLdConsts.LIST))) { throw new JsonLdError(Error.INVALID_REVERSE_PROPERTY_VALUE); } // 7.10.4.2) @@ -872,82 +917,83 @@ else if ("@index".equals(activeCtx.getContainer(key)) && value instanceof Map) { } } // 8) - if (result.containsKey("@value")) { + if (result.containsKey(JsonLdConsts.VALUE)) { // 8.1) // TODO: is this method faster than just using containsKey for // each? - final Set keySet = new HashSet(result.keySet()); - keySet.remove("@value"); - keySet.remove("@index"); - final boolean langremoved = keySet.remove("@language"); - final boolean typeremoved = keySet.remove("@type"); + final Set keySet = new HashSet<>(result.keySet()); + keySet.remove(JsonLdConsts.VALUE); + keySet.remove(JsonLdConsts.INDEX); + final boolean langremoved = keySet.remove(JsonLdConsts.LANGUAGE); + final boolean typeremoved = keySet.remove(JsonLdConsts.TYPE); if ((langremoved && typeremoved) || !keySet.isEmpty()) { throw new JsonLdError(Error.INVALID_VALUE_OBJECT, "value object has unknown keys"); } // 8.2) - final Object rval = result.get("@value"); + final Object rval = result.get(JsonLdConsts.VALUE); if (rval == null) { // nothing else is possible with result if we set it to // null, so simply return it return null; } // 8.3) - if (!(rval instanceof String) && result.containsKey("@language")) { + if (!(rval instanceof String) && result.containsKey(JsonLdConsts.LANGUAGE)) { throw new JsonLdError(Error.INVALID_LANGUAGE_TAGGED_VALUE, "when @language is used, @value must be a string"); } // 8.4) - else if (result.containsKey("@type")) { + else if (result.containsKey(JsonLdConsts.TYPE)) { // TODO: is this enough for "is an IRI" - if (!(result.get("@type") instanceof String) - || ((String) result.get("@type")).startsWith("_:") - || !((String) result.get("@type")).contains(":")) { + if (!(result.get(JsonLdConsts.TYPE) instanceof String) + || ((String) result.get(JsonLdConsts.TYPE)).startsWith("_:") + || !((String) result.get(JsonLdConsts.TYPE)).contains(":")) { throw new JsonLdError(Error.INVALID_TYPED_VALUE, "value of @type must be an IRI"); } } } // 9) - else if (result.containsKey("@type")) { - final Object rtype = result.get("@type"); + else if (result.containsKey(JsonLdConsts.TYPE)) { + final Object rtype = result.get(JsonLdConsts.TYPE); if (!(rtype instanceof List)) { final List tmp = new ArrayList(); tmp.add(rtype); - result.put("@type", tmp); + result.put(JsonLdConsts.TYPE, tmp); } } // 10) - else if (result.containsKey("@set") || result.containsKey("@list")) { + else if (result.containsKey(JsonLdConsts.SET) + || result.containsKey(JsonLdConsts.LIST)) { // 10.1) - if (result.size() > (result.containsKey("@index") ? 2 : 1)) { + if (result.size() > (result.containsKey(JsonLdConsts.INDEX) ? 2 : 1)) { throw new JsonLdError(Error.INVALID_SET_OR_LIST_OBJECT, "@set or @list may only contain @index"); } // 10.2) - if (result.containsKey("@set")) { + if (result.containsKey(JsonLdConsts.SET)) { // result becomes an array here, thus the remaining checks // will never be true from here on // so simply return the value rather than have to make // result an object and cast it with every // other use in the function. - return result.get("@set"); + return result.get(JsonLdConsts.SET); } } // 11) - if (result.containsKey("@language") && result.size() == 1) { + if (result.containsKey(JsonLdConsts.LANGUAGE) && result.size() == 1) { result = null; } // 12) - if (activeProperty == null || "@graph".equals(activeProperty)) { + if (activeProperty == null || JsonLdConsts.GRAPH.equals(activeProperty)) { // 12.1) - if (result != null - && (result.size() == 0 || result.containsKey("@value") || result - .containsKey("@list"))) { + if (result != null && (result.size() == 0 || result.containsKey(JsonLdConsts.VALUE) + || result.containsKey(JsonLdConsts.LIST))) { result = null; } // 12.2) - else if (result != null && result.containsKey("@id") && result.size() == 1) { + else if (result != null && !frameExpansion && result.containsKey(JsonLdConsts.ID) + && result.size() == 1) { result = null; } } @@ -957,7 +1003,7 @@ else if (result != null && result.containsKey("@id") && result.size() == 1) { // 2) If element is a scalar else { // 2.1) - if (activeProperty == null || "@graph".equals(activeProperty)) { + if (activeProperty == null || JsonLdConsts.GRAPH.equals(activeProperty)) { return null; } return activeCtx.expandValue(activeProperty, element); @@ -990,7 +1036,7 @@ public Object expand(Context activeCtx, Object element) throws JsonLdError { */ void generateNodeMap(Object element, Map nodeMap) throws JsonLdError { - generateNodeMap(element, nodeMap, "@default", null, null, null); + generateNodeMap(element, nodeMap, JsonLdConsts.DEFAULT, null, null, null); } void generateNodeMap(Object element, Map nodeMap, String activeGraph) @@ -1018,19 +1064,19 @@ void generateNodeMap(Object element, Map nodeMap, String activeG nodeMap.put(activeGraph, newMap()); } final Map graph = (Map) nodeMap.get(activeGraph); - Map node = (Map) (activeSubject == null ? null : graph - .get(activeSubject)); + Map node = (Map) (activeSubject == null ? null + : graph.get(activeSubject)); // 3) - if (elem.containsKey("@type")) { + if (elem.containsKey(JsonLdConsts.TYPE)) { // 3.1) List oldTypes; final List newTypes = new ArrayList(); - if (elem.get("@type") instanceof List) { - oldTypes = (List) elem.get("@type"); + if (elem.get(JsonLdConsts.TYPE) instanceof List) { + oldTypes = (List) elem.get(JsonLdConsts.TYPE); } else { - oldTypes = new ArrayList(); - oldTypes.add((String) elem.get("@type")); + oldTypes = new ArrayList(4); + oldTypes.add((String) elem.get(JsonLdConsts.TYPE)); } for (final String item : oldTypes) { if (item.startsWith("_:")) { @@ -1039,36 +1085,36 @@ void generateNodeMap(Object element, Map nodeMap, String activeG newTypes.add(item); } } - if (elem.get("@type") instanceof List) { - elem.put("@type", newTypes); + if (elem.get(JsonLdConsts.TYPE) instanceof List) { + elem.put(JsonLdConsts.TYPE, newTypes); } else { - elem.put("@type", newTypes.get(0)); + elem.put(JsonLdConsts.TYPE, newTypes.get(0)); } } // 4) - if (elem.containsKey("@value")) { + if (elem.containsKey(JsonLdConsts.VALUE)) { // 4.1) if (list == null) { JsonLdUtils.mergeValue(node, activeProperty, elem); } // 4.2) else { - JsonLdUtils.mergeValue(list, "@list", elem); + JsonLdUtils.mergeValue(list, JsonLdConsts.LIST, elem); } } // 5) - else if (elem.containsKey("@list")) { + else if (elem.containsKey(JsonLdConsts.LIST)) { // 5.1) - final Map result = newMap("@list", new ArrayList()); + final Map result = newMap(JsonLdConsts.LIST, new ArrayList(4)); // 5.2) // for (final Object item : (List) elem.get("@list")) { // generateNodeMap(item, nodeMap, activeGraph, activeSubject, // activeProperty, result); // } - generateNodeMap(elem.get("@list"), nodeMap, activeGraph, activeSubject, activeProperty, - result); + generateNodeMap(elem.get(JsonLdConsts.LIST), nodeMap, activeGraph, activeSubject, + activeProperty, result); // 5.3) JsonLdUtils.mergeValue(node, activeProperty, result); } @@ -1076,7 +1122,7 @@ else if (elem.containsKey("@list")) { // 6) else { // 6.1) - String id = (String) elem.remove("@id"); + String id = (String) elem.remove(JsonLdConsts.ID); if (id != null) { if (id.startsWith("_:")) { id = generateBlankNodeIdentifier(id); @@ -1088,7 +1134,7 @@ else if (elem.containsKey("@list")) { } // 6.3) if (!graph.containsKey(id)) { - final Map tmp = newMap("@id", id); + final Map tmp = newMap(JsonLdConsts.ID, id); graph.put(id, tmp); } // 6.4) TODO: SPEC this line is asked for by the spec, but it breaks @@ -1102,7 +1148,7 @@ else if (elem.containsKey("@list")) { } // 6.6) else if (activeProperty != null) { - final Map reference = newMap("@id", id); + final Map reference = newMap(JsonLdConsts.ID, id); // 6.6.2) if (list == null) { // 6.6.2.1+2) @@ -1111,49 +1157,50 @@ else if (activeProperty != null) { // 6.6.3) TODO: SPEC says to add ELEMENT to @list member, should // be REFERENCE else { - JsonLdUtils.mergeValue(list, "@list", reference); + JsonLdUtils.mergeValue(list, JsonLdConsts.LIST, reference); } } // TODO: SPEC this is removed in the spec now, but it's still needed // (see 6.4) node = (Map) graph.get(id); // 6.7) - if (elem.containsKey("@type")) { - for (final Object type : (List) elem.remove("@type")) { - JsonLdUtils.mergeValue(node, "@type", type); + if (elem.containsKey(JsonLdConsts.TYPE)) { + for (final Object type : (List) elem.remove(JsonLdConsts.TYPE)) { + JsonLdUtils.mergeValue(node, JsonLdConsts.TYPE, type); } } // 6.8) - if (elem.containsKey("@index")) { - final Object elemIndex = elem.remove("@index"); - if (node.containsKey("@index")) { - if (!JsonLdUtils.deepCompare(node.get("@index"), elemIndex)) { + if (elem.containsKey(JsonLdConsts.INDEX)) { + final Object elemIndex = elem.remove(JsonLdConsts.INDEX); + if (node.containsKey(JsonLdConsts.INDEX)) { + if (!JsonLdUtils.deepCompare(node.get(JsonLdConsts.INDEX), elemIndex)) { throw new JsonLdError(Error.CONFLICTING_INDEXES); } } else { - node.put("@index", elemIndex); + node.put(JsonLdConsts.INDEX, elemIndex); } } // 6.9) - if (elem.containsKey("@reverse")) { + if (elem.containsKey(JsonLdConsts.REVERSE)) { // 6.9.1) - final Map referencedNode = newMap("@id", id); + final Map referencedNode = newMap(JsonLdConsts.ID, id); // 6.9.2+6.9.4) final Map reverseMap = (Map) elem - .remove("@reverse"); + .remove(JsonLdConsts.REVERSE); // 6.9.3) for (final String property : reverseMap.keySet()) { final List values = (List) reverseMap.get(property); // 6.9.3.1) for (final Object value : values) { // 6.9.3.1.1) - generateNodeMap(value, nodeMap, activeGraph, referencedNode, property, null); + generateNodeMap(value, nodeMap, activeGraph, referencedNode, property, + null); } } } // 6.10) - if (elem.containsKey("@graph")) { - generateNodeMap(elem.remove("@graph"), nodeMap, id, null, null, null); + if (elem.containsKey(JsonLdConsts.GRAPH)) { + generateNodeMap(elem.remove(JsonLdConsts.GRAPH), nodeMap, id, null, null, null); } // 6.11) final List keys = new ArrayList(elem.keySet()); @@ -1166,7 +1213,7 @@ else if (activeProperty != null) { } // 6.11.2) if (!node.containsKey(property)) { - node.put(property, new ArrayList()); + node.put(property, new ArrayList(4)); } // 6.11.3) generateNodeMap(value, nodeMap, activeGraph, id, property, null); @@ -1233,21 +1280,26 @@ String generateBlankNodeIdentifier() { */ private class FramingContext { - public boolean embed; + public Embed embed; public boolean explicit; public boolean omitDefault; + public Map uniqueEmbeds; + public LinkedList subjectStack; + public boolean requireAll; public FramingContext() { - embed = true; + embed = Embed.LAST; explicit = false; omitDefault = false; - embeds = null; + requireAll = false; + uniqueEmbeds = new HashMap<>(); + subjectStack = new LinkedList<>(); } public FramingContext(JsonLdOptions opts) { this(); if (opts.getEmbed() != null) { - this.embed = opts.getEmbed(); + this.embed = opts.getEmbedVal(); } if (opts.getExplicit() != null) { this.explicit = opts.getExplicit(); @@ -1255,21 +1307,27 @@ public FramingContext(JsonLdOptions opts) { if (opts.getOmitDefault() != null) { this.omitDefault = opts.getOmitDefault(); } + if (opts.getRequireAll() != null) { + this.requireAll = opts.getRequireAll(); + } } - - public Map embeds = null; } private class EmbedNode { public Object parent = null; public String property = null; + + public EmbedNode(Object parent, String property) { + this.parent = parent; + this.property = property; + } } private Map nodeMap; /** - * Performs JSON-LD framing. + * Performs JSON-LD + * framing. * * @param input * the expanded JSON-LD to frame. @@ -1283,29 +1341,33 @@ public List frame(Object input, List frame) throws JsonLdError { // create framing state final FramingContext state = new FramingContext(this.opts); - // use tree map so keys are sotred by default + // use tree map so keys are sorted by default final Map nodes = new TreeMap(); generateNodeMap(input, nodes); - this.nodeMap = (Map) nodes.get("@default"); + this.nodeMap = (Map) nodes.get(JsonLdConsts.DEFAULT); final List framed = new ArrayList(); // NOTE: frame validation is done by the function not allowing anything // other than list to me passed - frame(state, - this.nodeMap, + // 1. + // If frame is an array, set frame to the first member of the array, + // which MUST be a valid frame. + frame(state, this.nodeMap, (frame != null && frame.size() > 0 ? (Map) frame.get(0) : newMap()), framed, null); return framed; } + private boolean createsCircularReference(String id, FramingContext state) { + return state.subjectStack.contains(id); + } + /** * Frames subjects according to the given frame. * * @param state * the current framing state. - * @param subjects - * the subjects to filter. * @param frame * the frame. * @param parent @@ -1318,200 +1380,266 @@ public List frame(Object input, List frame) throws JsonLdError { private void frame(FramingContext state, Map nodes, Map frame, Object parent, String property) throws JsonLdError { - // filter out subjects that match the frame - final Map matches = filterNodes(state, nodes, frame); - - // get flags for current frame - Boolean embedOn = getFrameFlag(frame, "@embed", state.embed); - final Boolean explicicOn = getFrameFlag(frame, "@explicit", state.explicit); - - // add matches to output + // https://json-ld.org/spec/latest/json-ld-framing/#framing-algorithm + + // 2. + // Initialize flags embed, explicit, and requireAll from object embed + // flag, + // explicit inclusion flag, and require all flag in state overriding + // from + // any property values for @embed, @explicit, and @requireAll in frame. + // TODO: handle @requireAll + final Embed embed = getFrameEmbed(frame, state.embed); + final Boolean explicitOn = getFrameFlag(frame, JsonLdConsts.EXPLICIT, state.explicit); + final Boolean requireAll = getFrameFlag(frame, JsonLdConsts.REQUIRE_ALL, state.requireAll); + final Map flags = newMap(); + flags.put(JsonLdConsts.EXPLICIT, explicitOn); + flags.put(JsonLdConsts.EMBED, embed); + flags.put(JsonLdConsts.REQUIRE_ALL, requireAll); + + // 3. + // Create a list of matched subjects by filtering subjects against frame + // using the Frame Matching algorithm with state, subjects, frame, and + // requireAll. + final Map matches = filterNodes(state, nodes, frame, requireAll); final List ids = new ArrayList(matches.keySet()); Collections.sort(ids); + + // 4. + // Set link the the value of link in state associated with graph name in + // state, + // creating a new empty dictionary, if necessary. + final Map link = state.uniqueEmbeds; + + // 5. + // For each id and associated node object node from the set of matched + // subjects, ordered by id: for (final String id : ids) { + final Map subject = (Map) matches.get(id); + + // 5.1 + // Initialize output to a new dictionary with @id and id and add + // output to link associated with id. + final Map output = newMap(); + output.put(JsonLdConsts.ID, id); + + // 5.2 + // If embed is @link and id is in link, node already exists in + // results. + // Add the associated node object from link to parent and do not + // perform + // additional processing for this node. + if (embed == Embed.LINK && state.uniqueEmbeds.containsKey(id)) { + addFrameOutput(state, parent, property, state.uniqueEmbeds.get(id)); + continue; + } + + // Occurs only at top level, compartmentalize each top-level match if (property == null) { - state.embeds = new LinkedHashMap(); + state.uniqueEmbeds = new HashMap<>(); } - // start output - final Map output = newMap(); - output.put("@id", id); - - // prepare embed meta info - final EmbedNode embeddedNode = new EmbedNode(); - embeddedNode.parent = parent; - embeddedNode.property = property; - - // if embed is on and there is an existing embed - if (embedOn && state.embeds.containsKey(id)) { - final EmbedNode existing = state.embeds.get(id); - embedOn = false; - - if (existing.parent instanceof List) { - for (final Object p : (List) existing.parent) { - if (JsonLdUtils.compareValues(output, p)) { - embedOn = true; - break; - } - } - } - // existing embed's parent is an object - else { - if (((Map) existing.parent).containsKey(existing.property)) { - for (final Object v : (List) ((Map) existing.parent) - .get(existing.property)) { - if (v instanceof Map - && Obj.equals(id, ((Map) v).get("@id"))) { - embedOn = true; - break; - } - } - } - } + // 5.3 + // Otherwise, if embed is @never or if a circular reference would be + // created by an embed, + // add output to parent and do not perform additional processing for + // this node. + if (embed == Embed.NEVER || createsCircularReference(id, state)) { + addFrameOutput(state, parent, property, output); + continue; + } - // existing embed has already been added, so allow an overwrite - if (embedOn) { + // 5.4 + // Otherwise, if embed is @last, remove any existing embedded node + // from parent associated + // with graph name in state. Requires sorting of subjects. + if (embed == Embed.LAST) { + if (state.uniqueEmbeds.containsKey(id)) { removeEmbed(state, id); } + state.uniqueEmbeds.put(id, new EmbedNode(parent, property)); } - // not embedding, add output without any other properties - if (!embedOn) { - addFrameOutput(state, parent, property, output); - } else { - // add embed meta info - state.embeds.put(id, embeddedNode); - - // iterate over subject properties - final Map element = (Map) matches.get(id); - List props = new ArrayList(element.keySet()); - Collections.sort(props); - for (final String prop : props) { - - // copy keywords to output - if (isKeyword(prop)) { - output.put(prop, JsonLdUtils.clone(element.get(prop))); - continue; - } + state.subjectStack.push(id); - // if property isn't in the frame - if (!frame.containsKey(prop)) { - // if explicit is off, embed values - if (!explicicOn) { - embedValues(state, element, prop, output); - } - continue; - } + // 5.5 If embed is @last or @always + + // Skip 5.5.1 + + // 5.5.2 For each property and objects in node, ordered by property: + final Map element = (Map) matches.get(id); + List props = new ArrayList(element.keySet()); + Collections.sort(props); + for (final String prop : props) { + + // 5.5.2.1 If property is a keyword, add property and objects to + // output. + if (isKeyword(prop)) { + output.put(prop, JsonLdUtils.clone(element.get(prop))); + continue; + } - // add objects - final List value = (List) element.get(prop); - - for (final Object item : value) { - - // recurse into list - if ((item instanceof Map) - && ((Map) item).containsKey("@list")) { - // add empty list - final Map list = newMap(); - list.put("@list", new ArrayList()); - addFrameOutput(state, output, prop, list); - - // add list objects - for (final Object listitem : (List) ((Map) item) - .get("@list")) { - // recurse into subject reference - if (JsonLdUtils.isNodeReference(listitem)) { - final Map tmp = newMap(); - final String itemid = (String) ((Map) listitem) - .get("@id"); - // TODO: nodes may need to be node_map, - // which is global - tmp.put(itemid, this.nodeMap.get(itemid)); - frame(state, tmp, - (Map) ((List) frame.get(prop)) - .get(0), list, "@list"); + // 5.5.2.2 Otherwise, if property is not in frame, and explicit + // is true, processors + // MUST NOT add any values for property to output, and the + // following steps are skipped. + if (explicitOn && !frame.containsKey(prop)) { + continue; + } + + // add objects + final List value = (List) element.get(prop); + + // 5.5.2.3 For each item in objects: + for (final Object item : value) { + if ((item instanceof Map) + && ((Map) item).containsKey(JsonLdConsts.LIST)) { + // add empty list + final Map list = newMap(); + list.put(JsonLdConsts.LIST, new ArrayList()); + addFrameOutput(state, output, prop, list); + + // add list objects + for (final Object listitem : (List) ((Map) item) + .get(JsonLdConsts.LIST)) { + // 5.5.2.3.1.1 recurse into subject reference + if (JsonLdUtils.isNodeReference(listitem)) { + final Map tmp = newMap(); + final String itemid = (String) ((Map) listitem) + .get(JsonLdConsts.ID); + // TODO: nodes may need to be node_map, + // which is global + tmp.put(itemid, this.nodeMap.get(itemid)); + Map subframe; + if (frame.containsKey(prop)) { + subframe = (Map) ((List) frame + .get(prop)).get(0); } else { - // include other values automatcially (TODO: - // may need JsonLdUtils.clone(n)) - addFrameOutput(state, list, "@list", listitem); + subframe = flags; } + frame(state, tmp, subframe, list, JsonLdConsts.LIST); + } else { + + // include other values automatcially (TODO: + // may need JsonLdUtils.clone(n)) + addFrameOutput(state, list, JsonLdConsts.LIST, listitem); } } - - // recurse into subject reference - else if (JsonLdUtils.isNodeReference(item)) { - final Map tmp = newMap(); - final String itemid = (String) ((Map) item).get("@id"); - // TODO: nodes may need to be node_map, which is - // global - tmp.put(itemid, this.nodeMap.get(itemid)); - frame(state, tmp, - (Map) ((List) frame.get(prop)).get(0), - output, prop); + } + // recurse into subject reference + else if (JsonLdUtils.isNodeReference(item)) { + final Map tmp = newMap(); + final String itemid = (String) ((Map) item) + .get(JsonLdConsts.ID); + // TODO: nodes may need to be node_map, which is + // global + tmp.put(itemid, this.nodeMap.get(itemid)); + Map subframe; + if (frame.containsKey(prop)) { + subframe = (Map) ((List) frame.get(prop)) + .get(0); } else { - // include other values automatically (TODO: may - // need JsonLdUtils.clone(o)) - addFrameOutput(state, output, prop, item); + subframe = flags; } + frame(state, tmp, subframe, output, prop); + } else { + // include other values automatically (TODO: may + // need JsonLdUtils.clone(o)) + addFrameOutput(state, output, prop, item); } } + } - // handle defaults - props = new ArrayList(frame.keySet()); - Collections.sort(props); - for (final String prop : props) { - // skip keywords - if (isKeyword(prop)) { - continue; - } + // handle defaults + props = new ArrayList(frame.keySet()); + Collections.sort(props); + for (final String prop : props) { + // skip keywords + if (isKeyword(prop)) { + continue; + } - final List pf = (List) frame.get(prop); - Map propertyFrame = pf.size() > 0 ? (Map) pf - .get(0) : null; - if (propertyFrame == null) { - propertyFrame = newMap(); + final List pf = (List) frame.get(prop); + Map propertyFrame = pf.size() > 0 ? (Map) pf.get(0) + : null; + if (propertyFrame == null) { + propertyFrame = newMap(); + } + final boolean omitDefaultOn = getFrameFlag(propertyFrame, JsonLdConsts.OMIT_DEFAULT, + state.omitDefault); + if (!omitDefaultOn && !output.containsKey(prop)) { + Object def = "@null"; + if (propertyFrame.containsKey(JsonLdConsts.DEFAULT)) { + def = JsonLdUtils.clone(propertyFrame.get(JsonLdConsts.DEFAULT)); } - final boolean omitDefaultOn = getFrameFlag(propertyFrame, "@omitDefault", - state.omitDefault); - if (!omitDefaultOn && !output.containsKey(prop)) { - Object def = "@null"; - if (propertyFrame.containsKey("@default")) { - def = JsonLdUtils.clone(propertyFrame.get("@default")); - } - if (!(def instanceof List)) { - final List tmp = new ArrayList(); - tmp.add(def); - def = tmp; - } - final Map tmp1 = newMap("@preserve", def); - final List tmp2 = new ArrayList(); - tmp2.add(tmp1); - output.put(prop, tmp2); + if (!(def instanceof List)) { + final List tmp = new ArrayList(); + tmp.add(def); + def = tmp; } + final Map tmp1 = newMap(JsonLdConsts.PRESERVE, def); + final List tmp2 = new ArrayList(); + tmp2.add(tmp1); + output.put(prop, tmp2); } - - // add output to parent - addFrameOutput(state, parent, property, output); } + + // add output to parent + addFrameOutput(state, parent, property, output); + + state.subjectStack.pop(); } } - private Boolean getFrameFlag(Map frame, String name, boolean thedefault) { + private Object getFrameValue(Map frame, String name) { Object value = frame.get(name); if (value instanceof List) { if (((List) value).size() > 0) { value = ((List) value).get(0); } } - if (value instanceof Map && ((Map) value).containsKey("@value")) { - value = ((Map) value).get("@value"); + if (value instanceof Map && ((Map) value).containsKey(JsonLdConsts.VALUE)) { + value = ((Map) value).get(JsonLdConsts.VALUE); } + return value; + } + + private Boolean getFrameFlag(Map frame, String name, boolean thedefault) { + final Object value = getFrameValue(frame, name); if (value instanceof Boolean) { return (Boolean) value; } return thedefault; } + private Embed getFrameEmbed(Map frame, Embed thedefault) throws JsonLdError { + final Object value = getFrameValue(frame, JsonLdConsts.EMBED); + if (value == null) { + return thedefault; + } + if (value instanceof Boolean) { + return (Boolean) value ? Embed.LAST : Embed.NEVER; + } + if (value instanceof Embed) { + return (Embed) value; + } + if (value instanceof String) { + switch ((String) value) { + case "@always": + return Embed.ALWAYS; + case "@never": + return Embed.NEVER; + case "@last": + return Embed.LAST; + case "@link": + return Embed.LINK; + default: + throw new JsonLdError(JsonLdError.Error.INVALID_EMBED_VALUE); + } + } + throw new JsonLdError(JsonLdError.Error.INVALID_EMBED_VALUE); + } + /** * Removes an existing embed. * @@ -1522,13 +1650,13 @@ private Boolean getFrameFlag(Map frame, String name, boolean the */ private static void removeEmbed(FramingContext state, String id) { // get existing embed - final Map embeds = state.embeds; - final EmbedNode embed = embeds.get(id); + final Map links = state.uniqueEmbeds; + final EmbedNode embed = links.get(id); final Object parent = embed.parent; final String property = embed.property; // create reference to replace embed - final Map node = newMap("@id", id); + final Map node = newMap(JsonLdConsts.ID, id); // remove existing embed if (JsonLdUtils.isNode(parent)) { @@ -1537,7 +1665,8 @@ private static void removeEmbed(FramingContext state, String id) { final List oldvals = (List) ((Map) parent) .get(property); for (final Object v : oldvals) { - if (v instanceof Map && Obj.equals(((Map) v).get("@id"), id)) { + if (v instanceof Map + && Obj.equals(((Map) v).get(JsonLdConsts.ID), id)) { newvals.add(node); } else { newvals.add(v); @@ -1546,18 +1675,17 @@ private static void removeEmbed(FramingContext state, String id) { ((Map) parent).put(property, newvals); } // recursively remove dependent dangling embeds - removeDependents(embeds, id); + removeDependents(links, id); } private static void removeDependents(Map embeds, String id) { // get embed keys as a separate array to enable deleting keys in map for (final String id_dep : new HashSet(embeds.keySet())) { final EmbedNode e = embeds.get(id_dep); - final Object p = e.parent != null ? e.parent : newMap(); - if (!(p instanceof Map)) { + if (e == null || e.parent == null || !(e.parent instanceof Map)) { continue; } - final String pid = (String) ((Map) p).get("@id"); + final String pid = (String) ((Map) e.parent).get(JsonLdConsts.ID); if (Obj.equals(id, pid)) { embeds.remove(id_dep); removeDependents(embeds, id_dep); @@ -1566,11 +1694,11 @@ private static void removeDependents(Map embeds, String id) { } private Map filterNodes(FramingContext state, Map nodes, - Map frame) throws JsonLdError { + Map frame, boolean requireAll) throws JsonLdError { final Map rval = newMap(); for (final String id : nodes.keySet()) { final Map element = (Map) nodes.get(id); - if (element != null && filterNode(state, element, frame)) { + if (element != null && filterNode(state, element, frame, requireAll)) { rval.put(id, element); } } @@ -1578,56 +1706,105 @@ private Map filterNodes(FramingContext state, Map node, - Map frame) throws JsonLdError { - final Object types = frame.get("@type"); + Map frame, boolean requireAll) throws JsonLdError { + final Object types = frame.get(JsonLdConsts.TYPE); + final Object frameIds = frame.get(JsonLdConsts.ID); + // https://json-ld.org/spec/latest/json-ld-framing/#frame-matching + // + // 1. Node matches if it has an @id property including any IRI or + // blank node in the @id property in frame. + if (frameIds != null) { + if (frameIds instanceof String) { + final Object nodeId = node.get(JsonLdConsts.ID); + if (nodeId == null) { + return false; + } + if (JsonLdUtils.deepCompare(nodeId, frameIds)) { + return true; + } + } else if (frameIds instanceof LinkedHashMap + && ((LinkedHashMap) frameIds).size() == 0) { + if (node.containsKey(JsonLdConsts.ID)) { + return true; + } + return false; + } else if (!(frameIds instanceof List)) { + throw new JsonLdError(Error.SYNTAX_ERROR, "frame @id must be an array"); + } else { + final Object nodeId = node.get(JsonLdConsts.ID); + if (nodeId == null) { + return false; + } + for (final Object j : (List) frameIds) { + if (JsonLdUtils.deepCompare(nodeId, j)) { + return true; + } + } + } + return false; + } + // 2. Node matches if frame has no non-keyword properties.TODO + // 3. If requireAll is true, node matches if all non-keyword properties + // (property) in frame match any of the following conditions. Or, if + // requireAll is false, if any of the non-keyword properties (property) + // in frame match any of the following conditions. For the values of + // each + // property from frame in node: + // 3.1 If property is @type: if (types != null) { if (!(types instanceof List)) { throw new JsonLdError(Error.SYNTAX_ERROR, "frame @type must be an array"); } - Object nodeTypes = node.get("@type"); + Object nodeTypes = node.get(JsonLdConsts.TYPE); if (nodeTypes == null) { nodeTypes = new ArrayList(); } else if (!(nodeTypes instanceof List)) { throw new JsonLdError(Error.SYNTAX_ERROR, "node @type must be an array"); } + // 3.1.1 Property matches if the @type property in frame includes + // any IRI in values. + for (final Object i : (List) nodeTypes) { + for (final Object j : (List) types) { + if (JsonLdUtils.deepCompare(i, j)) { + return true; + } + } + } + // TODO: 3.1.2 + // 3.1.3 Otherwise, property matches if values is empty and the + // @type property in frame is match none. if (((List) types).size() == 1 && ((List) types).get(0) instanceof Map && ((Map) ((List) types).get(0)).size() == 0) { return !((List) nodeTypes).isEmpty(); - } else { - for (final Object i : (List) nodeTypes) { - for (final Object j : (List) types) { - if (JsonLdUtils.deepCompare(i, j)) { - return true; - } - } - } - return false; } - } else { - for (final String key : frame.keySet()) { - if ("@id".equals(key) || !isKeyword(key) && !(node.containsKey(key))) { - - Object frameObject = frame.get(key); - if (frameObject instanceof ArrayList) { - ArrayList o = (ArrayList) frame.get(key); - - boolean _default = false; - for (Object oo : o) { - if (oo instanceof Map) { - if (((Map) oo).containsKey("@default")) { - _default = true; - } + // 3.1.4 Otherwise, property does not match. + return false; + } + // 3.2 + for (final String key : frame.keySet()) { + if (!isKeyword(key) && !(node.containsKey(key))) { + + final Object frameObject = frame.get(key); + if (frameObject instanceof ArrayList) { + final ArrayList o = (ArrayList) frame.get(key); + + boolean _default = false; + for (final Object oo : o) { + if (oo instanceof Map) { + if (((Map) oo).containsKey(JsonLdConsts.DEFAULT)) { + _default = true; } } - if (_default) - continue; } - - return false; + if (_default) { + continue; + } } + + return false; } - return true; } + return true; } /** @@ -1656,60 +1833,6 @@ private static void addFrameOutput(FramingContext state, Object parent, String p } } - /** - * Embeds values for the given subject and property into the given output - * during the framing algorithm. - * - * @param state - * the current framing state. - * @param element - * the subject. - * @param property - * the property. - * @param output - * the output. - */ - private void embedValues(FramingContext state, Map element, String property, - Object output) { - // embed subject properties in output - final List objects = (List) element.get(property); - for (Object o : objects) { - // handle subject reference - if (JsonLdUtils.isNodeReference(o)) { - final String sid = (String) ((Map) o).get("@id"); - - // embed full subject if isn't already embedded - if (!state.embeds.containsKey(sid)) { - // add embed - final EmbedNode embed = new EmbedNode(); - embed.parent = output; - embed.property = property; - state.embeds.put(sid, embed); - - // recurse into subject - o = newMap(); - Map s = (Map) this.nodeMap.get(sid); - if (s == null) { - s = newMap("@id", sid); - } - for (final String prop : s.keySet()) { - // copy keywords - if (isKeyword(prop)) { - ((Map) o).put(prop, JsonLdUtils.clone(s.get(prop))); - continue; - } - embedValues(state, s, prop, o); - } - } - addFrameOutput(state, output, property, o); - } - // copy non-subject value - else { - addFrameOutput(state, output, property, JsonLdUtils.clone(o)); - } - } - } - /*** * ____ _ __ ____ ____ _____ _ _ _ _ _ / ___|___ _ ____ _____ _ __| |_ / _|_ * __ ___ _ __ ___ | _ \| _ \| ___| / \ | | __ _ ___ _ __(_) |_| |__ _ __ @@ -1738,12 +1861,22 @@ public UsagesNode(NodeMapNode node, String property, Map value) public Map value = null; } + private class Node { + private final String predicate; + private final RDFDataset.Node object; + + public Node(String predicate, RDFDataset.Node object) { + this.predicate = predicate; + this.object = object; + } + } + private class NodeMapNode extends LinkedHashMap { - public List usages = new ArrayList(); + public List usages = new ArrayList(4); public NodeMapNode(String id) { super(); - this.put("@id", id); + this.put(JsonLdConsts.ID, id); } // helper fucntion for 4.3.3 @@ -1754,25 +1887,28 @@ public boolean isWellFormedListNode() { int keys = 0; if (containsKey(RDF_FIRST)) { keys++; - if (!(get(RDF_FIRST) instanceof List && ((List) get(RDF_FIRST)).size() == 1)) { + if (!(get(RDF_FIRST) instanceof List + && ((List) get(RDF_FIRST)).size() == 1)) { return false; } } if (containsKey(RDF_REST)) { keys++; - if (!(get(RDF_REST) instanceof List && ((List) get(RDF_REST)).size() == 1)) { + if (!(get(RDF_REST) instanceof List + && ((List) get(RDF_REST)).size() == 1)) { return false; } } - if (containsKey("@type")) { + if (containsKey(JsonLdConsts.TYPE)) { keys++; - if (!(get("@type") instanceof List && ((List) get("@type")).size() == 1) - && RDF_LIST.equals(((List) get("@type")).get(0))) { + if (!(get(JsonLdConsts.TYPE) instanceof List + && ((List) get(JsonLdConsts.TYPE)).size() == 1) + && RDF_LIST.equals(((List) get(JsonLdConsts.TYPE)).get(0))) { return false; } } // TODO: SPEC: 4.3.3 has no mention of @id - if (containsKey("@id")) { + if (containsKey(JsonLdConsts.ID)) { keys++; } if (keys < size()) { @@ -1797,11 +1933,30 @@ public Map serialize() { * If there was an error during conversion from RDF to JSON-LD. */ public List fromRDF(final RDFDataset dataset) throws JsonLdError { + return fromRDF(dataset, false); + } + + /** + * Converts RDF statements into JSON-LD, presuming that there are no + * duplicates in the dataset. + * + * @param dataset + * the RDF statements. + * @param noDuplicatesInDataset + * True if there are no duplicates in the dataset and false + * otherwise. + * @return A list of JSON-LD objects found in the given dataset. + * @throws JsonLdError + * If there was an error during conversion from RDF to JSON-LD. + */ + public List fromRDF(final RDFDataset dataset, boolean noDuplicatesInDataset) + throws JsonLdError { // 1) - final Map defaultGraph = new LinkedHashMap(); + final Map defaultGraph = new LinkedHashMap(4); // 2) - final Map> graphMap = new LinkedHashMap>(); - graphMap.put("@default", defaultGraph); + final Map> graphMap = new LinkedHashMap>( + 4); + graphMap.put(JsonLdConsts.DEFAULT, defaultGraph); // 3/3.1) for (final String name : dataset.graphNames()) { @@ -1809,58 +1964,65 @@ public List fromRDF(final RDFDataset dataset) throws JsonLdError { final List graph = dataset.getQuads(name); // 3.2+3.4) - Map nodeMap; - if (!graphMap.containsKey(name)) { - nodeMap = new LinkedHashMap(); - graphMap.put(name, nodeMap); - } else { - nodeMap = graphMap.get(name); - } + final Map nodeMap = graphMap.computeIfAbsent(name, + k -> new LinkedHashMap()); // 3.3) - if (!"@default".equals(name) && !Obj.contains(defaultGraph, name)) { - defaultGraph.put(name, new NodeMapNode(name)); + if (!JsonLdConsts.DEFAULT.equals(name)) { + // Existing entries in the default graph are not overwritten + defaultGraph.computeIfAbsent(name, k -> new NodeMapNode(k)); } // 3.5) + final Map> nodes = new HashMap<>(); + for (final RDFDataset.Quad triple : graph) { final String subject = triple.getSubject().getValue(); final String predicate = triple.getPredicate().getValue(); final RDFDataset.Node object = triple.getObject(); + nodes.computeIfAbsent(subject, k -> new ArrayList<>()) + .add(new Node(predicate, object)); + } + for (final Map.Entry> nodeEntry : nodes.entrySet()) { + final String subject = nodeEntry.getKey(); - // 3.5.1+3.5.2) - NodeMapNode node; - if (!nodeMap.containsKey(subject)) { - node = new NodeMapNode(subject); - nodeMap.put(subject, node); - } else { - node = nodeMap.get(subject); - } + for (final Node n : nodeEntry.getValue()) { + final String predicate = n.predicate; + final RDFDataset.Node object = n.object; - // 3.5.3) - if ((object.isIRI() || object.isBlankNode()) - && !nodeMap.containsKey(object.getValue())) { - nodeMap.put(object.getValue(), new NodeMapNode(object.getValue())); - } + // 3.5.1+3.5.2) + final NodeMapNode node = nodeMap.computeIfAbsent(subject, + k -> new NodeMapNode(k)); - // 3.5.4) - if (RDF_TYPE.equals(predicate) && (object.isIRI() || object.isBlankNode()) - && !opts.getUseRdfType()) { - JsonLdUtils.mergeValue(node, "@type", object.getValue()); - continue; - } + // 3.5.3) + if ((object.isIRI() || object.isBlankNode())) { + nodeMap.computeIfAbsent(object.getValue(), k -> new NodeMapNode(k)); + } + + // 3.5.4) + if (RDF_TYPE.equals(predicate) && (object.isIRI() || object.isBlankNode()) + && !opts.getUseRdfType() && + (!nodes.containsKey(object.getValue()) || subject.equals(object.getValue()))) { + JsonLdUtils.mergeValue(node, JsonLdConsts.TYPE, object.getValue()); + continue; + } - // 3.5.5) - final Map value = object.toObject(opts.getUseNativeTypes()); + // 3.5.5) + final Map value = object.toObject(opts.getUseNativeTypes()); - // 3.5.6+7) - JsonLdUtils.mergeValue(node, predicate, value); + // 3.5.6+7) + if (noDuplicatesInDataset) { + JsonLdUtils.laxMergeValue(node, predicate, value); + } else { + JsonLdUtils.mergeValue(node, predicate, value); + } - // 3.5.8) - if (object.isBlankNode() || object.isIRI()) { - // 3.5.8.1-3) - nodeMap.get(object.getValue()).usages - .add(new UsagesNode(node, predicate, value)); + // 3.5.8) + if (object.isBlankNode() || object.isIRI()) { + // 3.5.8.1-3) + nodeMap.get(object.getValue()).usages + .add(new UsagesNode(node, predicate, value)); + } } } } @@ -1883,14 +2045,14 @@ public List fromRDF(final RDFDataset dataset) throws JsonLdError { String property = usage.property; Map head = usage.value; // 4.3.2) - final List list = new ArrayList(); - final List listNodes = new ArrayList(); + final List list = new ArrayList(4); + final List listNodes = new ArrayList(4); // 4.3.3) while (RDF_REST.equals(property) && node.isWellFormedListNode()) { // 4.3.3.1) list.add(((List) node.get(RDF_FIRST)).get(0)); // 4.3.3.2) - listNodes.add((String) node.get("@id")); + listNodes.add((String) node.get(JsonLdConsts.ID)); // 4.3.3.3) final UsagesNode nodeUsage = node.usages.get(0); // 4.3.3.4) @@ -1905,11 +2067,11 @@ public List fromRDF(final RDFDataset dataset) throws JsonLdError { // 4.3.4) if (RDF_FIRST.equals(property)) { // 4.3.4.1) - if (RDF_NIL.equals(node.get("@id"))) { + if (RDF_NIL.equals(node.get(JsonLdConsts.ID))) { continue; } // 4.3.4.3) - final String headId = (String) head.get("@id"); + final String headId = (String) head.get(JsonLdConsts.ID); // 4.3.4.4-5) head = (Map) ((List) graph.get(headId).get(RDF_REST)) .get(0); @@ -1918,11 +2080,11 @@ public List fromRDF(final RDFDataset dataset) throws JsonLdError { listNodes.remove(listNodes.size() - 1); } // 4.3.5) - head.remove("@id"); + head.remove(JsonLdConsts.ID); // 4.3.6) Collections.reverse(list); // 4.3.7) - head.put("@list", list); + head.put(JsonLdConsts.LIST, list); // 4.3.8) for (final String nodeId : listNodes) { graph.remove(nodeId); @@ -1931,7 +2093,7 @@ public List fromRDF(final RDFDataset dataset) throws JsonLdError { } // 5) - final List result = new ArrayList(); + final List result = new ArrayList(4); // 6) final List ids = new ArrayList(defaultGraph.keySet()); Collections.sort(ids); @@ -1940,20 +2102,22 @@ public List fromRDF(final RDFDataset dataset) throws JsonLdError { // 6.1) if (graphMap.containsKey(subject)) { // 6.1.1) - node.put("@graph", new ArrayList()); + final List nextGraph = new ArrayList(4); + node.put(JsonLdConsts.GRAPH, nextGraph); // 6.1.2) - final List keys = new ArrayList(graphMap.get(subject).keySet()); + final Map nextSubjectMap = graphMap.get(subject); + final List keys = new ArrayList(nextSubjectMap.keySet()); Collections.sort(keys); for (final String s : keys) { - final NodeMapNode n = graphMap.get(subject).get(s); - if (n.size() == 1 && n.containsKey("@id")) { + final NodeMapNode n = nextSubjectMap.get(s); + if (n.size() == 1 && n.containsKey(JsonLdConsts.ID)) { continue; } - ((List) node.get("@graph")).add(n.serialize()); + nextGraph.add(n.serialize()); } } // 6.2) - if (node.size() == 1 && node.containsKey("@id")) { + if (node.size() == 1 && node.containsKey(JsonLdConsts.ID)) { continue; } result.add(node.serialize()); @@ -1984,7 +2148,7 @@ public RDFDataset toRDF() throws JsonLdError { // TODO: make the default generateNodeMap call (i.e. without a // graphName) create and return the nodeMap final Map nodeMap = newMap(); - nodeMap.put("@default", newMap()); + nodeMap.put(JsonLdConsts.DEFAULT, newMap()); generateNodeMap(this.value, nodeMap); final RDFDataset dataset = new RDFDataset(this); @@ -2027,7 +2191,7 @@ public Object normalize(Map dataset) throws JsonLdError { for (String graphName : dataset.keySet()) { final List> triples = (List>) dataset .get(graphName); - if ("@default".equals(graphName)) { + if (JsonLdConsts.DEFAULT.equals(graphName)) { graphName = null; } for (final Map quad : triples) { @@ -2048,9 +2212,8 @@ public Object normalize(Map dataset) throws JsonLdError { final String[] attrs = new String[] { "subject", "object", "name" }; for (final String attr : attrs) { - if (quad.containsKey(attr) - && "blank node".equals(((Map) quad.get(attr)) - .get("type"))) { + if (quad.containsKey(attr) && "blank node" + .equals(((Map) quad.get(attr)).get("type"))) { final String id = (String) ((Map) quad.get(attr)) .get("value"); if (!bnodes.containsKey(id)) { @@ -2068,8 +2231,8 @@ public Object normalize(Map dataset) throws JsonLdError { } // mapping complete, start canonical naming - final NormalizeUtils normalizeUtils = new NormalizeUtils(quads, bnodes, new UniqueNamer( - "_:c14n"), opts); + final NormalizeUtils normalizeUtils = new NormalizeUtils(quads, bnodes, + new UniqueNamer("_:c14n"), opts); return normalizeUtils.hashBlankNodes(bnodes.keySet()); } diff --git a/core/src/main/java/com/github/jsonldjava/core/JsonLdConsts.java b/core/src/main/java/com/github/jsonldjava/core/JsonLdConsts.java index b68c4cd4..9de5b76b 100644 --- a/core/src/main/java/com/github/jsonldjava/core/JsonLdConsts.java +++ b/core/src/main/java/com/github/jsonldjava/core/JsonLdConsts.java @@ -27,4 +27,39 @@ public final class JsonLdConsts { public static final String RDF_OBJECT = RDF_SYNTAX_NS + "object"; public static final String RDF_LANGSTRING = RDF_SYNTAX_NS + "langString"; public static final String RDF_LIST = RDF_SYNTAX_NS + "List"; -} + + public static final String TEXT_TURTLE = "text/turtle"; + public static final String APPLICATION_NQUADS = "application/n-quads"; // https://www.w3.org/TR/n-quads/#sec-mediatype + + public static final String FLATTENED = "flattened"; + public static final String COMPACTED = "compacted"; + public static final String EXPANDED = "expanded"; + + public static final String ID = "@id"; + public static final String DEFAULT = "@default"; + public static final String GRAPH = "@graph"; + public static final String CONTEXT = "@context"; + public static final String PRESERVE = "@preserve"; + public static final String EXPLICIT = "@explicit"; + public static final String OMIT_DEFAULT = "@omitDefault"; + public static final String EMBED_CHILDREN = "@embedChildren"; + public static final String EMBED = "@embed"; + public static final String LIST = "@list"; + public static final String LANGUAGE = "@language"; + public static final String INDEX = "@index"; + public static final String SET = "@set"; + public static final String TYPE = "@type"; + public static final String REVERSE = "@reverse"; + public static final String VALUE = "@value"; + public static final String NULL = "@null"; + public static final String NONE = "@none"; + public static final String CONTAINER = "@container"; + public static final String BLANK_NODE_PREFIX = "_:"; + public static final String VOCAB = "@vocab"; + public static final String BASE = "@base"; + public static final String REQUIRE_ALL = "@requireAll"; + + public enum Embed { + ALWAYS, NEVER, LAST, LINK; + } +} \ No newline at end of file diff --git a/core/src/main/java/com/github/jsonldjava/core/JsonLdError.java b/core/src/main/java/com/github/jsonldjava/core/JsonLdError.java index 9bdccf41..21ab5772 100644 --- a/core/src/main/java/com/github/jsonldjava/core/JsonLdError.java +++ b/core/src/main/java/com/github/jsonldjava/core/JsonLdError.java @@ -1,11 +1,9 @@ package com.github.jsonldjava.core; -import java.util.Map; +public class JsonLdError extends RuntimeException { -public class JsonLdError extends Exception { - - Map details; - private Error type; + private static final long serialVersionUID = -8685402790466459014L; + private final Error type; public JsonLdError(Error type, Object detail) { // TODO: pretty toString (e.g. print whole json objects) @@ -18,6 +16,17 @@ public JsonLdError(Error type) { this.type = type; } + public JsonLdError(Error type, Object detail, Throwable cause) { + // TODO: pretty toString (e.g. print whole json objects) + super(detail == null ? "" : detail.toString(), cause); + this.type = type; + } + + public JsonLdError(Error type, Throwable cause) { + super(cause); + this.type = type; + } + public enum Error { LOADING_DOCUMENT_FAILED("loading document failed"), @@ -35,6 +44,8 @@ public enum Error { LOADING_REMOTE_CONTEXT_FAILED("loading remote context failed"), + LOADING_INJECTED_CONTEXT_FAILED("loading injected context failed"), + INVALID_REMOTE_CONTEXT("invalid remote context"), RECURSIVE_CONTEXT_INCLUSION("recursive context inclusion"), @@ -89,6 +100,8 @@ public enum Error { INVALID_REVERSE_PROPERTY_VALUE("invalid reverse property value"), + INVALID_EMBED_VALUE("invalid @embed value"), + // non spec related errors SYNTAX_ERROR("syntax error"), @@ -114,19 +127,10 @@ public String toString() { } } - public JsonLdError setType(Error error) { - this.type = error; - return this; - }; - public Error getType() { return type; } - public Map getDetails() { - return details; - } - @Override public String getMessage() { final String msg = super.getMessage(); diff --git a/core/src/main/java/com/github/jsonldjava/core/JsonLdOptions.java b/core/src/main/java/com/github/jsonldjava/core/JsonLdOptions.java index 51f3145d..ff0038ee 100644 --- a/core/src/main/java/com/github/jsonldjava/core/JsonLdOptions.java +++ b/core/src/main/java/com/github/jsonldjava/core/JsonLdOptions.java @@ -1,160 +1,303 @@ -package com.github.jsonldjava.core; - -/** - * The JsonLdOptions type as specified in the JSON-LD-API - * specification. - * - * @author tristan - * - */ -public class JsonLdOptions { - - /** - * Constructs an instance of JsonLdOptions using an empty base. - */ - public JsonLdOptions() { - this(""); - } - - /** - * Constructs an instance of JsonLdOptions using the given base. - * - * @param base - * The base IRI for the document. - */ - public JsonLdOptions(String base) { - this.setBase(base); - } - - // Base options : http://www.w3.org/TR/json-ld-api/#idl-def-JsonLdOptions - - /** - * http://www.w3.org/TR/json-ld-api/#widl-JsonLdOptions-base - */ - private String base = null; - - /** - * http://www.w3.org/TR/json-ld-api/#widl-JsonLdOptions-compactArrays - */ - private Boolean compactArrays = true; - /** - * http://www.w3.org/TR/json-ld-api/#widl-JsonLdOptions-expandContext - */ - private Object expandContext = null; - /** - * http://www.w3.org/TR/json-ld-api/#widl-JsonLdOptions-processingMode - */ - private String processingMode = "json-ld-1.0"; - /** - * http://www.w3.org/TR/json-ld-api/#widl-JsonLdOptions-documentLoader - */ - private DocumentLoader documentLoader = new DocumentLoader(); - - // Frame options : http://json-ld.org/spec/latest/json-ld-framing/ - - private Boolean embed = null; - private Boolean explicit = null; - private Boolean omitDefault = null; - - // RDF conversion options : - // http://www.w3.org/TR/json-ld-api/#serialize-rdf-as-json-ld-algorithm - - Boolean useRdfType = false; - Boolean useNativeTypes = false; - private boolean produceGeneralizedRdf = false; - - public Boolean getEmbed() { - return embed; - } - - public void setEmbed(Boolean embed) { - this.embed = embed; - } - - public Boolean getExplicit() { - return explicit; - } - - public void setExplicit(Boolean explicit) { - this.explicit = explicit; - } - - public Boolean getOmitDefault() { - return omitDefault; - } - - public void setOmitDefault(Boolean omitDefault) { - this.omitDefault = omitDefault; - } - - public Boolean getCompactArrays() { - return compactArrays; - } - - public void setCompactArrays(Boolean compactArrays) { - this.compactArrays = compactArrays; - } - - public Object getExpandContext() { - return expandContext; - } - - public void setExpandContext(Object expandContext) { - this.expandContext = expandContext; - } - - public String getProcessingMode() { - return processingMode; - } - - public void setProcessingMode(String processingMode) { - this.processingMode = processingMode; - } - - public String getBase() { - return base; - } - - public void setBase(String base) { - this.base = base; - } - - public Boolean getUseRdfType() { - return useRdfType; - } - - public void setUseRdfType(Boolean useRdfType) { - this.useRdfType = useRdfType; - } - - public Boolean getUseNativeTypes() { - return useNativeTypes; - } - - public void setUseNativeTypes(Boolean useNativeTypes) { - this.useNativeTypes = useNativeTypes; - } - - public boolean getProduceGeneralizedRdf() { - return this.produceGeneralizedRdf; - } - - public void setProduceGeneralizedRdf(Boolean produceGeneralizedRdf) { - this.produceGeneralizedRdf = produceGeneralizedRdf; - } - - public DocumentLoader getDocumentLoader() { - return documentLoader; - } - - public void setDocumentLoader(DocumentLoader documentLoader) { - this.documentLoader = documentLoader; - } - - // TODO: THE FOLLOWING ONLY EXIST SO I DON'T HAVE TO DELETE A LOT OF CODE, - // REMOVE IT WHEN DONE - public String format = null; - public Boolean useNamespaces = false; - public String outputForm = null; -} +package com.github.jsonldjava.core; + +import com.github.jsonldjava.core.JsonLdConsts.Embed; + +/** + * The JsonLdOptions type as specified in the + * JSON-LD- + * API specification. + * + * @author tristan + * + */ +public class JsonLdOptions { + + public static final String JSON_LD_1_0 = "json-ld-1.0"; + + public static final String JSON_LD_1_1 = "json-ld-1.1"; + + public static final boolean DEFAULT_COMPACT_ARRAYS = true; + + /** + * Constructs an instance of JsonLdOptions using an empty base. + */ + public JsonLdOptions() { + this(""); + } + + /** + * Constructs an instance of JsonLdOptions using the given base. + * + * @param base + * The base IRI for the document. + */ + public JsonLdOptions(String base) { + this.setBase(base); + } + + /** + * Creates a shallow copy of this JsonLdOptions object. + * + * It will share the same DocumentLoader unless that is overridden, and + * other mutable objects, so it isn't immutable. + * + * @return A copy of this JsonLdOptions object. + */ + public JsonLdOptions copy() { + final JsonLdOptions copy = new JsonLdOptions(base); + + copy.setCompactArrays(compactArrays); + copy.setExpandContext(expandContext); + copy.setProcessingMode(processingMode); + copy.setDocumentLoader(documentLoader); + copy.setEmbed(embed); + copy.setExplicit(explicit); + copy.setOmitDefault(omitDefault); + copy.setOmitGraph(omitGraph); + copy.setFrameExpansion(frameExpansion); + copy.setPruneBlankNodeIdentifiers(pruneBlankNodeIdentifiers); + copy.setRequireAll(requireAll); + copy.setAllowContainerSetOnType(allowContainerSetOnType); + copy.setUseRdfType(useRdfType); + copy.setUseNativeTypes(useNativeTypes); + copy.setProduceGeneralizedRdf(produceGeneralizedRdf); + copy.format = format; + copy.useNamespaces = useNamespaces; + copy.outputForm = outputForm; + + return copy; + } + + // Base options : http://www.w3.org/TR/json-ld-api/#idl-def-JsonLdOptions + + /** + * http://www.w3.org/TR/json-ld-api/#widl-JsonLdOptions-base + */ + private String base = null; + + /** + * http://www.w3.org/TR/json-ld-api/#widl-JsonLdOptions-compactArrays + */ + private Boolean compactArrays = DEFAULT_COMPACT_ARRAYS; + /** + * http://www.w3.org/TR/json-ld-api/#widl-JsonLdOptions-expandContext + */ + private Object expandContext = null; + /** + * http://www.w3.org/TR/json-ld-api/#widl-JsonLdOptions-processingMode + */ + private String processingMode = JSON_LD_1_0; + /** + * http://www.w3.org/TR/json-ld-api/#widl-JsonLdOptions-documentLoader + */ + private DocumentLoader documentLoader = new DocumentLoader(); + + // Frame options : http://json-ld.org/spec/latest/json-ld-framing/ + + private Embed embed = Embed.LAST; + private Boolean explicit = null; + private Boolean omitDefault = null; + private Boolean omitGraph = false; + private Boolean frameExpansion = false; + private Boolean pruneBlankNodeIdentifiers = false; + private Boolean requireAll = false; + private Boolean allowContainerSetOnType = false; + + // RDF conversion options : + // http://www.w3.org/TR/json-ld-api/#serialize-rdf-as-json-ld-algorithm + + Boolean useRdfType = false; + Boolean useNativeTypes = false; + private boolean produceGeneralizedRdf = false; + + public String getEmbed() { + switch (this.embed) { + case ALWAYS: + return "@always"; + case NEVER: + return "@never"; + case LINK: + return "@link"; + default: + return "@last"; + } + } + + Embed getEmbedVal() { + return this.embed; + } + + public void setEmbed(Boolean embed) { + this.embed = embed ? Embed.LAST : Embed.NEVER; + } + + public void setEmbed(String embed) throws JsonLdError { + switch (embed) { + case "@always": + this.embed = Embed.ALWAYS; + break; + case "@never": + this.embed = Embed.NEVER; + break; + case "@last": + this.embed = Embed.LAST; + break; + case "@link": + this.embed = Embed.LINK; + break; + default: + throw new JsonLdError(JsonLdError.Error.INVALID_EMBED_VALUE); + } + } + + public void setEmbed(Embed embed) throws JsonLdError { + switch (embed) { + case ALWAYS: + this.embed = Embed.ALWAYS; + break; + case NEVER: + this.embed = Embed.NEVER; + break; + case LAST: + this.embed = Embed.LAST; + break; + case LINK: + this.embed = Embed.LINK; + break; + default: + throw new JsonLdError(JsonLdError.Error.INVALID_EMBED_VALUE); + } + } + + public Boolean getExplicit() { + return explicit; + } + + public void setExplicit(Boolean explicit) { + this.explicit = explicit; + } + + public Boolean getOmitDefault() { + return omitDefault; + } + + public void setOmitDefault(Boolean omitDefault) { + this.omitDefault = omitDefault; + } + + public Boolean getFrameExpansion() { + return frameExpansion; + } + + public void setFrameExpansion(Boolean frameExpansion) { + this.frameExpansion = frameExpansion; + } + + public Boolean getOmitGraph() { + return omitGraph; + } + + public void setOmitGraph(Boolean omitGraph) { + this.omitGraph = omitGraph; + } + + public Boolean getPruneBlankNodeIdentifiers() { + return pruneBlankNodeIdentifiers; + } + + public void setPruneBlankNodeIdentifiers(Boolean pruneBlankNodeIdentifiers) { + this.pruneBlankNodeIdentifiers = pruneBlankNodeIdentifiers; + } + + public Boolean getRequireAll() { + return this.requireAll; + } + + public void setRequireAll(Boolean requireAll) { + this.requireAll = requireAll; + } + + public Boolean getAllowContainerSetOnType() { + return allowContainerSetOnType; + } + + public void setAllowContainerSetOnType(Boolean allowContainerSetOnType) { + this.allowContainerSetOnType = allowContainerSetOnType; + } + + public Boolean getCompactArrays() { + return compactArrays; + } + + public void setCompactArrays(Boolean compactArrays) { + this.compactArrays = compactArrays; + } + + public Object getExpandContext() { + return expandContext; + } + + public void setExpandContext(Object expandContext) { + this.expandContext = expandContext; + } + + public String getProcessingMode() { + return processingMode; + } + + public void setProcessingMode(String processingMode) { + this.processingMode = processingMode; + if (processingMode.equals(JSON_LD_1_1)) { + this.omitGraph = true; + this.pruneBlankNodeIdentifiers = true; + this.allowContainerSetOnType = true; + } + } + + public String getBase() { + return base; + } + + public void setBase(String base) { + this.base = base; + } + + public Boolean getUseRdfType() { + return useRdfType; + } + + public void setUseRdfType(Boolean useRdfType) { + this.useRdfType = useRdfType; + } + + public Boolean getUseNativeTypes() { + return useNativeTypes; + } + + public void setUseNativeTypes(Boolean useNativeTypes) { + this.useNativeTypes = useNativeTypes; + } + + public boolean getProduceGeneralizedRdf() { + return this.produceGeneralizedRdf; + } + + public void setProduceGeneralizedRdf(Boolean produceGeneralizedRdf) { + this.produceGeneralizedRdf = produceGeneralizedRdf; + } + + public DocumentLoader getDocumentLoader() { + return documentLoader; + } + + public void setDocumentLoader(DocumentLoader documentLoader) { + this.documentLoader = documentLoader; + } + + // TODO: THE FOLLOWING ONLY EXIST SO I DON'T HAVE TO DELETE A LOT OF CODE, + // REMOVE IT WHEN DONE + public String format = null; + public Boolean useNamespaces = false; + public String outputForm = null; + +} diff --git a/core/src/main/java/com/github/jsonldjava/core/JsonLdProcessor.java b/core/src/main/java/com/github/jsonldjava/core/JsonLdProcessor.java index 8ea35f59..fedff3d2 100644 --- a/core/src/main/java/com/github/jsonldjava/core/JsonLdProcessor.java +++ b/core/src/main/java/com/github/jsonldjava/core/JsonLdProcessor.java @@ -11,13 +11,11 @@ import com.github.jsonldjava.core.JsonLdError.Error; import com.github.jsonldjava.impl.NQuadRDFParser; import com.github.jsonldjava.impl.NQuadTripleCallback; -import com.github.jsonldjava.impl.TurtleRDFParser; -import com.github.jsonldjava.impl.TurtleTripleCallback; /** * This class implements the JsonLdProcessor interface, except that it does not currently support + * "http://json-ld.org/spec/latest/json-ld-api/#the-jsonldprocessor-interface" > + * JsonLdProcessor interface, except that it does not currently support * asynchronous processing, and hence does not return Promises, instead directly * returning the results. * @@ -50,8 +48,9 @@ public static Map compact(Object input, Object context, JsonLdOp // 2-6) NOTE: these are all the same steps as in expand final Object expanded = expand(input, opts); // 7) - if (context instanceof Map && ((Map) context).containsKey("@context")) { - context = ((Map) context).get("@context"); + if (context instanceof Map + && ((Map) context).containsKey(JsonLdConsts.CONTEXT)) { + context = ((Map) context).get(JsonLdConsts.CONTEXT); } Context activeCtx = new Context(opts); activeCtx = activeCtx.parse(context); @@ -67,23 +66,16 @@ public static Map compact(Object input, Object context, JsonLdOp } else { final Map tmp = newMap(); // TODO: SPEC: doesn't specify to use vocab = true here - tmp.put(activeCtx.compactIri("@graph", true), compacted); + tmp.put(activeCtx.compactIri(JsonLdConsts.GRAPH, true), compacted); compacted = tmp; } } - if (compacted != null && context != null) { - // TODO: figure out if we can make "@context" appear at the start of - // the keySet - if ((context instanceof Map && !((Map) context).isEmpty()) - || (context instanceof List && !((List) context).isEmpty())) { - - if (context instanceof List && ((List) context).size() == 1 - && opts.getCompactArrays()) { - ((Map) compacted).put("@context", - ((List) context).get(0)); - } else { - ((Map) compacted).put("@context", context); - } + if (compacted != null) { + final Object returnedContext = returnedContext(context, opts); + if(returnedContext != null) { + // TODO: figure out if we can make "@context" appear at the start of + // the keySet + ((Map) compacted).put(JsonLdConsts.CONTEXT, returnedContext); } } @@ -92,8 +84,8 @@ public static Map compact(Object input, Object context, JsonLdOp } /** - * Expands the given input according to the steps in the Expansion + * Expands the given input according to the steps in the + * Expansion * algorithm. * * @param input @@ -113,10 +105,10 @@ public static List expand(Object input, JsonLdOptions opts) throws JsonL if (input instanceof String && ((String) input).contains(":")) { try { final RemoteDocument tmp = opts.getDocumentLoader().loadDocument((String) input); - input = tmp.document; + input = tmp.getDocument(); // TODO: figure out how to deal with remote context } catch (final Exception e) { - throw new JsonLdError(Error.LOADING_DOCUMENT_FAILED, e.getMessage()); + throw new JsonLdError(Error.LOADING_DOCUMENT_FAILED, e); } // if set the base in options should override the base iri in the // active context @@ -132,8 +124,9 @@ public static List expand(Object input, JsonLdOptions opts) throws JsonL // 4) if (opts.getExpandContext() != null) { Object exCtx = opts.getExpandContext(); - if (exCtx instanceof Map && ((Map) exCtx).containsKey("@context")) { - exCtx = ((Map) exCtx).get("@context"); + if (exCtx instanceof Map + && ((Map) exCtx).containsKey(JsonLdConsts.CONTEXT)) { + exCtx = ((Map) exCtx).get(JsonLdConsts.CONTEXT); } activeCtx = activeCtx.parse(exCtx); } @@ -146,9 +139,9 @@ public static List expand(Object input, JsonLdOptions opts) throws JsonL Object expanded = new JsonLdApi(opts).expand(activeCtx, input); // final step of Expansion Algorithm - if (expanded instanceof Map && ((Map) expanded).containsKey("@graph") + if (expanded instanceof Map && ((Map) expanded).containsKey(JsonLdConsts.GRAPH) && ((Map) expanded).size() == 1) { - expanded = ((Map) expanded).get("@graph"); + expanded = ((Map) expanded).get(JsonLdConsts.GRAPH); } else if (expanded == null) { expanded = new ArrayList(); } @@ -163,8 +156,8 @@ public static List expand(Object input, JsonLdOptions opts) throws JsonL } /** - * Expands the given input according to the steps in the Expansion + * Expands the given input according to the steps in the + * Expansion * algorithm, using the default {@link JsonLdOptions}. * * @param input @@ -182,8 +175,9 @@ public static Object flatten(Object input, Object context, JsonLdOptions opts) // 2-6) NOTE: these are all the same steps as in expand final Object expanded = expand(input, opts); // 7) - if (context instanceof Map && ((Map) context).containsKey("@context")) { - context = ((Map) context).get("@context"); + if (context instanceof Map + && ((Map) context).containsKey(JsonLdConsts.CONTEXT)) { + context = ((Map) context).get(JsonLdConsts.CONTEXT); } // 8) NOTE: blank node generation variables are members of JsonLdApi // 9) NOTE: the next block is the Flattening Algorithm described in @@ -191,11 +185,12 @@ public static Object flatten(Object input, Object context, JsonLdOptions opts) // 1) final Map nodeMap = newMap(); - nodeMap.put("@default", newMap()); + nodeMap.put(JsonLdConsts.DEFAULT, newMap()); // 2) new JsonLdApi(opts).generateNodeMap(expanded, nodeMap); // 3) - final Map defaultGraph = (Map) nodeMap.remove("@default"); + final Map defaultGraph = (Map) nodeMap + .remove(JsonLdConsts.DEFAULT); // 4) for (final String graphName : nodeMap.keySet()) { final Map graph = (Map) nodeMap.get(graphName); @@ -203,7 +198,7 @@ public static Object flatten(Object input, Object context, JsonLdOptions opts) Map entry; if (!defaultGraph.containsKey(graphName)) { entry = newMap(); - entry.put("@id", graphName); + entry.put(JsonLdConsts.ID, graphName); defaultGraph.put(graphName, entry); } else { entry = (Map) defaultGraph.get(graphName); @@ -211,15 +206,15 @@ public static Object flatten(Object input, Object context, JsonLdOptions opts) // 4.3) // TODO: SPEC doesn't specify that this should only be added if it // doesn't exists - if (!entry.containsKey("@graph")) { - entry.put("@graph", new ArrayList()); + if (!entry.containsKey(JsonLdConsts.GRAPH)) { + entry.put(JsonLdConsts.GRAPH, new ArrayList()); } final List keys = new ArrayList(graph.keySet()); Collections.sort(keys); for (final String id : keys) { final Map node = (Map) graph.get(id); - if (!(node.containsKey("@id") && node.size() == 1)) { - ((List) entry.get("@graph")).add(node); + if (!(node.containsKey(JsonLdConsts.ID) && node.size() == 1)) { + ((List) entry.get(JsonLdConsts.GRAPH)).add(node); } } @@ -231,7 +226,7 @@ public static Object flatten(Object input, Object context, JsonLdOptions opts) Collections.sort(keys); for (final String id : keys) { final Map node = (Map) defaultGraph.get(id); - if (!(node.containsKey("@id") && node.size() == 1)) { + if (!(node.containsKey(JsonLdConsts.ID) && node.size() == 1)) { flattened.add(node); } } @@ -247,8 +242,12 @@ public static Object flatten(Object input, Object context, JsonLdOptions opts) tmp.add(compacted); compacted = tmp; } - final String alias = activeCtx.compactIri("@graph"); - final Map rval = activeCtx.serialize(); + final String alias = activeCtx.compactIri(JsonLdConsts.GRAPH); + final Map rval = newMap(); + final Object returnedContext = returnedContext(context, opts); + if(returnedContext != null) { + rval.put(JsonLdConsts.CONTEXT, returnedContext); + } rval.put(alias, compacted); return rval; } @@ -257,9 +256,9 @@ public static Object flatten(Object input, Object context, JsonLdOptions opts) /** * Flattens the given input and compacts it using the passed context - * according to the steps in the Flattening - * algorithm: + * according to the steps in the + * + * Flattening algorithm: * * @param input * The input JSON-LD object. @@ -275,8 +274,9 @@ public static Object flatten(Object input, JsonLdOptions opts) throws JsonLdErro } /** - * Frames the given input using the frame according to the steps in the + * Frames the given input using the frame according to the steps in the + * * Framing Algorithm. * * @param input @@ -299,26 +299,73 @@ public static Map frame(Object input, Object frame, JsonLdOption } // TODO string/IO input + // 2. Set expanded input to the result of using the expand method using + // input and options. final Object expandedInput = expand(input, opts); + + // 3. Set expanded frame to the result of using the expand method using + // frame and options with expandContext set to null and the + // frameExpansion option set to true. + final Object savedExpandedContext = opts.getExpandContext(); + opts.setExpandContext(null); + opts.setFrameExpansion(true); final List expandedFrame = expand(frame, opts); + opts.setExpandContext(savedExpandedContext); + // 4. Set context to the value of @context from frame, if it exists, or + // to a new empty + // context, otherwise. final JsonLdApi api = new JsonLdApi(expandedInput, opts); + final Object context = ((Map) frame).get(JsonLdConsts.CONTEXT); + final Context activeCtx = api.context.parse(context); final List framed = api.frame(expandedInput, expandedFrame); - final Context activeCtx = api.context.parse(((Map) frame).get("@context")); - - Object compacted = api.compact(activeCtx, null, framed); - if (!(compacted instanceof List)) { + if (opts.getPruneBlankNodeIdentifiers()) { + JsonLdUtils.pruneBlankNodes(framed); + } + Object compacted = api.compact(activeCtx, null, framed, opts.getCompactArrays()); + final Map rval = newMap(); + final Object returnedContext = returnedContext(context, opts); + if(returnedContext != null) { + rval.put(JsonLdConsts.CONTEXT, returnedContext); + } + final boolean addGraph = ((!(compacted instanceof List)) && !opts.getOmitGraph()); + if (addGraph && !(compacted instanceof List)) { final List tmp = new ArrayList(); tmp.add(compacted); compacted = tmp; } - final String alias = activeCtx.compactIri("@graph"); - final Map rval = activeCtx.serialize(); - rval.put(alias, compacted); + if (addGraph || (compacted instanceof List)) { + final String alias = activeCtx.compactIri(JsonLdConsts.GRAPH); + rval.put(alias, compacted); + } else if (!addGraph && (compacted instanceof Map)) { + rval.putAll((Map) compacted); + } JsonLdUtils.removePreserve(activeCtx, rval, opts); return rval; } + /** + * Builds the context to be returned in framing, flattening and compaction algorithms. + * In cases where the context is empty or from an unexpected type, it returns null. + * When JsonLdOptions compactArrays is set to true and the context contains a List with a single element, + * the element is returned instead of the list + */ + private static Object returnedContext(Object context, JsonLdOptions opts) { + if (context != null && + ((context instanceof Map && !((Map) context).isEmpty()) + || (context instanceof List && !((List) context).isEmpty()) + || (context instanceof String && !((String) context).isEmpty()))) { + + if (context instanceof List && ((List) context).size() == 1 + && opts.getCompactArrays()) { + return ((List) context).get(0); + } + return context; + } else { + return null; + } + } + /** * A registry for RDF Parsers (in this case, JSONLDSerializers) used by * fromRDF if no specific serializer is specified and options.format is set. @@ -328,8 +375,7 @@ public static Map frame(Object input, Object frame, JsonLdOption private static Map rdfParsers = new LinkedHashMap() { { // automatically register nquad serializer - put("application/nquads", new NQuadRDFParser()); - put("text/turtle", new TurtleRDFParser()); + put(JsonLdConsts.APPLICATION_NQUADS, new NQuadRDFParser()); } }; @@ -365,7 +411,7 @@ public static Object fromRDF(Object dataset, JsonLdOptions options) throws JsonL if (options.format == null && dataset instanceof String) { // attempt to parse the input as nquads - options.format = "application/nquads"; + options.format = JsonLdConsts.APPLICATION_NQUADS; } if (rdfParsers.containsKey(options.format)) { @@ -424,15 +470,15 @@ public static Object fromRDF(Object input, JsonLdOptions options, RDFParser pars // re-process using the generated context if outputForm is set if (options.outputForm != null) { - if ("expanded".equals(options.outputForm)) { + if (JsonLdConsts.EXPANDED.equals(options.outputForm)) { return rval; - } else if ("compacted".equals(options.outputForm)) { + } else if (JsonLdConsts.COMPACTED.equals(options.outputForm)) { return compact(rval, dataset.getContext(), options); - } else if ("flattened".equals(options.outputForm)) { + } else if (JsonLdConsts.FLATTENED.equals(options.outputForm)) { return flatten(rval, dataset.getContext(), options); } else { - throw new JsonLdError(JsonLdError.Error.UNKNOWN_ERROR, "Output form was unknown: " - + options.outputForm); + throw new JsonLdError(JsonLdError.Error.UNKNOWN_ERROR, + "Output form was unknown: " + options.outputForm); } } return rval; @@ -494,8 +540,8 @@ public static Object toRDF(Object input, JsonLdTripleCallback callback, JsonLdOp _input.add((Map) input); } for (final Map e : _input) { - if (e.containsKey("@context")) { - dataset.parseContext(e.get("@context")); + if (e.containsKey(JsonLdConsts.CONTEXT)) { + dataset.parseContext(e.get(JsonLdConsts.CONTEXT)); } } } @@ -505,10 +551,8 @@ public static Object toRDF(Object input, JsonLdTripleCallback callback, JsonLdOp } if (options.format != null) { - if ("application/nquads".equals(options.format)) { + if (JsonLdConsts.APPLICATION_NQUADS.equals(options.format)) { return new NQuadTripleCallback().call(dataset); - } else if ("text/turtle".equals(options.format)) { - return new TurtleTripleCallback().call(dataset); } else { throw new JsonLdError(JsonLdError.Error.UNKNOWN_FORMAT, options.format); } @@ -582,7 +626,7 @@ public static Object toRDF(Object input) throws JsonLdError { */ public static Object normalize(Object input, JsonLdOptions options) throws JsonLdError { - final JsonLdOptions opts = new JsonLdOptions(options.getBase()); + final JsonLdOptions opts = options.copy(); opts.format = null; final RDFDataset dataset = (RDFDataset) toRDF(input, opts); diff --git a/core/src/main/java/com/github/jsonldjava/core/JsonLdUtils.java b/core/src/main/java/com/github/jsonldjava/core/JsonLdUtils.java index 890f8751..e0accff7 100644 --- a/core/src/main/java/com/github/jsonldjava/core/JsonLdUtils.java +++ b/core/src/main/java/com/github/jsonldjava/core/JsonLdUtils.java @@ -1,15 +1,11 @@ package com.github.jsonldjava.core; -import static com.github.jsonldjava.utils.Obj.newMap; - import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; -import com.github.jsonldjava.utils.JsonLdUrl; import com.github.jsonldjava.utils.Obj; public class JsonLdUtils { @@ -21,7 +17,8 @@ public class JsonLdUtils { * * @param v * the value to check. - * @param [ctx] the active context to check against. + * @param [ctx] + * the active context to check against. * * @return true if the value is a keyword, false if not. */ @@ -34,7 +31,8 @@ static boolean isKeyword(Object key) { || "@graph".equals(key) || "@id".equals(key) || "@index".equals(key) || "@language".equals(key) || "@list".equals(key) || "@omitDefault".equals(key) || "@reverse".equals(key) || "@preserve".equals(key) || "@set".equals(key) - || "@type".equals(key) || "@value".equals(key) || "@vocab".equals(key); + || "@type".equals(key) || "@value".equals(key) || "@vocab".equals(key) + || "@requireAll".equals(key); } public static Boolean deepCompare(Object v1, Object v2, Boolean listOrderMatters) { @@ -118,24 +116,22 @@ static void mergeValue(Map obj, String key, Object value) { } } - static void mergeCompactedValue(Map obj, String key, Object value) { + static void laxMergeValue(Map obj, String key, Object value) { if (obj == null) { return; } - final Object prop = obj.get(key); - if (prop == null) { - obj.put(key, value); - return; - } - if (!(prop instanceof List)) { - final List tmp = new ArrayList(); - tmp.add(prop); - } - if (value instanceof List) { - ((List) prop).addAll((List) value); - } else { - ((List) prop).add(value); + List values = (List) obj.get(key); + if (values == null) { + values = new ArrayList(); + obj.put(key, values); } + // if ("@list".equals(key) + // || (value instanceof Map && ((Map) + // value).containsKey("@list")) + // || !deepContains(values, value) + // ) { + values.add(value); + // } } public static boolean isAbsoluteIri(String value) { @@ -156,9 +152,8 @@ static boolean isNode(Object v) { // 1. It is an Object. // 2. It is not a @value, @set, or @list. // 3. It has more than 1 key OR any existing key is not @id. - if (v instanceof Map - && !(((Map) v).containsKey("@value") || ((Map) v).containsKey("@set") || ((Map) v) - .containsKey("@list"))) { + if (v instanceof Map && !(((Map) v).containsKey("@value") || ((Map) v).containsKey("@set") + || ((Map) v).containsKey("@list"))) { return ((Map) v).size() > 1 || !((Map) v).containsKey("@id"); } return false; @@ -176,8 +171,8 @@ static boolean isNodeReference(Object v) { // Note: A value is a subject reference if all of these hold true: // 1. It is an Object. // 2. It has a single key: @id. - return (v instanceof Map && ((Map) v).size() == 1 && ((Map) v) - .containsKey("@id")); + return (v instanceof Map && ((Map) v).size() == 1 + && ((Map) v).containsKey("@id")); } // TODO: fix this test @@ -188,309 +183,6 @@ public static boolean isRelativeIri(String value) { return false; } - // //////////////////////////////////////////////////// OLD CODE BELOW - - /** - * Adds a value to a subject. If the value is an array, all values in the - * array will be added. - * - * Note: If the value is a subject that already exists as a property of the - * given subject, this method makes no attempt to deeply merge properties. - * Instead, the value will not be added. - * - * @param subject - * the subject to add the value to. - * @param property - * the property that relates the value to the subject. - * @param value - * the value to add. - * @param [propertyIsArray] true if the property is always an array, false - * if not (default: false). - * @param [allowDuplicate] true if the property is a @list, false if not - * (default: false). - */ - static void addValue(Map subject, String property, Object value, - boolean propertyIsArray, boolean allowDuplicate) { - - if (isArray(value)) { - if (((List) value).size() == 0 && propertyIsArray && !subject.containsKey(property)) { - subject.put(property, new ArrayList()); - } - for (final Object val : (List) value) { - addValue(subject, property, val, propertyIsArray, allowDuplicate); - } - } else if (subject.containsKey(property)) { - // check if subject already has the value if duplicates not allowed - final boolean hasValue = !allowDuplicate && hasValue(subject, property, value); - - // make property an array if value not present or always an array - if (!isArray(subject.get(property)) && (!hasValue || propertyIsArray)) { - final List tmp = new ArrayList(); - tmp.add(subject.get(property)); - subject.put(property, tmp); - } - - // add new value - if (!hasValue) { - ((List) subject.get(property)).add(value); - } - } else { - // add new value as a set or single value - Object tmp; - if (propertyIsArray) { - tmp = new ArrayList(); - ((List) tmp).add(value); - } else { - tmp = value; - } - subject.put(property, tmp); - } - } - - static void addValue(Map subject, String property, Object value, - boolean propertyIsArray) { - addValue(subject, property, value, propertyIsArray, true); - } - - static void addValue(Map subject, String property, Object value) { - addValue(subject, property, value, false, true); - } - - /** - * Prepends a base IRI to the given relative IRI. - * - * @param base - * the base IRI. - * @param iri - * the relative IRI. - * - * @return the absolute IRI. - * - * TODO: the JsonLdUrl class isn't as forgiving as the Node.js url - * parser, we may need to re-implement the parser here to support - * the flexibility required - */ - private static String prependBase(Object baseobj, String iri) { - // already an absolute IRI - if (iri.indexOf(":") != -1) { - return iri; - } - - // parse base if it is a string - JsonLdUrl base; - if (isString(baseobj)) { - base = JsonLdUrl.parse((String) baseobj); - } else { - // assume base is already a JsonLdUrl - base = (JsonLdUrl) baseobj; - } - - final JsonLdUrl rel = JsonLdUrl.parse(iri); - - // start hierarchical part - String hierPart = base.protocol; - if (!"".equals(rel.authority)) { - hierPart += "//" + rel.authority; - } else if (!"".equals(base.href)) { - hierPart += "//" + base.authority; - } - - // per RFC3986 normalize - String path; - - // IRI represents an absolute path - if (rel.pathname.indexOf("/") == 0) { - path = rel.pathname; - } else { - path = base.pathname; - - // append relative path to the end of the last directory from base - if (!"".equals(rel.pathname)) { - path = path.substring(0, path.lastIndexOf("/") + 1); - if (path.length() > 0 && !path.endsWith("/")) { - path += "/"; - } - path += rel.pathname; - } - } - - // remove slashes anddots in path - path = JsonLdUrl.removeDotSegments(path, !"".equals(hierPart)); - - // add query and hash - if (!"".equals(rel.query)) { - path += "?" + rel.query; - } - - if (!"".equals(rel.hash)) { - path += rel.hash; - } - - final String rval = hierPart + path; - - if ("".equals(rval)) { - return "./"; - } - return rval; - } - - /** - * Expands a language map. - * - * @param languageMap - * the language map to expand. - * - * @return the expanded language map. - * @throws JsonLdError - */ - static List expandLanguageMap(Map languageMap) throws JsonLdError { - final List rval = new ArrayList(); - final List keys = new ArrayList(languageMap.keySet()); - Collections.sort(keys); // lexicographically sort languages - for (final String key : keys) { - List val; - if (!isArray(languageMap.get(key))) { - val = new ArrayList(); - val.add(languageMap.get(key)); - } else { - val = (List) languageMap.get(key); - } - for (final Object item : val) { - if (!isString(item)) { - throw new JsonLdError(JsonLdError.Error.SYNTAX_ERROR); - } - final Map tmp = newMap(); - tmp.put("@value", item); - tmp.put("@language", key.toLowerCase()); - rval.add(tmp); - } - } - - return rval; - } - - /** - * Throws an exception if the given value is not a valid @type value. - * - * @param v - * the value to check. - * @throws JsonLdError - */ - static boolean validateTypeValue(Object v) throws JsonLdError { - if (v == null) { - throw new NullPointerException("\"@type\" value cannot be null"); - } - - // must be a string, subject reference, or empty object - if (v instanceof String - || (v instanceof Map && (((Map) v).containsKey("@id") || ((Map) v) - .size() == 0))) { - return true; - } - - // must be an array - boolean isValid = false; - if (v instanceof List) { - isValid = true; - for (final Object i : (List) v) { - if (!(i instanceof String || i instanceof Map - && ((Map) i).containsKey("@id"))) { - isValid = false; - break; - } - } - } - - if (!isValid) { - throw new JsonLdError(JsonLdError.Error.SYNTAX_ERROR); - } - return true; - } - - /** - * Removes a base IRI from the given absolute IRI. - * - * @param base - * the base IRI. - * @param iri - * the absolute IRI. - * - * @return the relative IRI if relative to base, otherwise the absolute IRI. - */ - private static String removeBase(Object baseobj, String iri) { - JsonLdUrl base; - if (isString(baseobj)) { - base = JsonLdUrl.parse((String) baseobj); - } else { - base = (JsonLdUrl) baseobj; - } - - // establish base root - String root = ""; - if (!"".equals(base.href)) { - root += (base.protocol) + "//" + base.authority; - } - // support network-path reference with empty base - else if (iri.indexOf("//") != 0) { - root += "//"; - } - - // IRI not relative to base - if (iri.indexOf(root) != 0) { - return iri; - } - - // remove root from IRI and parse remainder - final JsonLdUrl rel = JsonLdUrl.parse(iri.substring(root.length())); - - // remove path segments that match - final List baseSegments = _split(base.normalizedPath, "/"); - final List iriSegments = _split(rel.normalizedPath, "/"); - - while (baseSegments.size() > 0 && iriSegments.size() > 0) { - if (!baseSegments.get(0).equals(iriSegments.get(0))) { - break; - } - if (baseSegments.size() > 0) { - baseSegments.remove(0); - } - if (iriSegments.size() > 0) { - iriSegments.remove(0); - } - } - - // use '../' for each non-matching base segment - String rval = ""; - if (baseSegments.size() > 0) { - // don't count the last segment if it isn't a path (doesn't end in - // '/') - // don't count empty first segment, it means base began with '/' - if (!base.normalizedPath.endsWith("/") || "".equals(baseSegments.get(0))) { - baseSegments.remove(baseSegments.size() - 1); - } - for (int i = 0; i < baseSegments.size(); ++i) { - rval += "../"; - } - } - - // prepend remaining segments - rval += _join(iriSegments, "/"); - - // add query and hash - if (!"".equals(rel.query)) { - rval += "?" + rel.query; - } - if (!"".equals(rel.hash)) { - rval += rel.hash; - } - - if ("".equals(rval)) { - rval = "./"; - } - - return rval; - } - /** * Removes the @preserve keywords as the last step of the framing algorithm. * @@ -552,39 +244,81 @@ static Object removePreserve(Context ctx, Object input, JsonLdOptions opts) thro } /** - * replicate javascript .join because i'm too lazy to keep doing it manually + * Removes the @id member of each node object where the member value is a + * blank node identifier which appears only once in any property value + * within input. * - * @param iriSegments - * @param string - * @return + * @param input + * the framed output before compaction */ - private static String _join(List list, String joiner) { - String rval = ""; - if (list.size() > 0) { - rval += list.get(0); - } - for (int i = 1; i < list.size(); i++) { - rval += joiner + list.get(i); + + static void pruneBlankNodes(final Object input) { + final Map toPrune = new HashMap<>(); + fillNodesToPrune(input, toPrune); + for (final String id : toPrune.keySet()) { + final Object node = toPrune.get(id); + if (node == null) { + continue; + } + ((Map) node).remove(JsonLdConsts.ID); } - return rval; } /** - * replicates the functionality of javascript .split, which has different - * results to java's String.split if there is a trailing / + * Gets the objects on which we'll prune the blank node ID * - * @param string - * @param delim - * @return + * @param input + * the framed output before compaction + * @param toPrune + * the resulting object. */ - private static List _split(String string, String delim) { - final List rval = new ArrayList(Arrays.asList(string.split(delim))); - if (string.endsWith("/")) { - // javascript .split includes a blank entry if the string ends with - // the delimiter, java .split does not so we need to add it manually - rval.add(""); + static void fillNodesToPrune(Object input, final Map toPrune) { + // recurse through arrays + if (isArray(input)) { + for (final Object i : (List) input) { + fillNodesToPrune(i, toPrune); + } + } else if (isObject(input)) { + // skip @values + if (isValue(input)) { + return; + } + // recurse through @lists + if (isList(input)) { + fillNodesToPrune(((Map) input).get("@list"), toPrune); + return; + } + // recurse through properties + for (final String prop : new LinkedHashSet<>(((Map) input).keySet())) { + if (prop.equals(JsonLdConsts.ID)) { + final String id = (String) ((Map) input).get(JsonLdConsts.ID); + if (id.startsWith("_:")) { + // if toPrune contains the id already, it was already + // present somewhere else, + // so we just null the value + if (toPrune.containsKey(id)) { + toPrune.put(id, null); + } else { + // else we add the object as the value + toPrune.put(id, input); + } + } + } else { + fillNodesToPrune(((Map) input).get(prop), toPrune); + } + } + } else if (input instanceof String) { + // this is an id, as non-id values will have been discarded by the + // isValue() above + final String p = (String) input; + if (p.startsWith("_:")) { + // the id is outside of the context of an @id property, if we're + // in that case, + // then we're referencing a blank node id so this id should not + // be removed + toPrune.put(p, null); + } } - return rval; } /** @@ -606,56 +340,13 @@ static int compareShortestLeast(String a, String b) { return Integer.signum(a.compareTo(b)); } - /** - * Determines if the given value is a property of the given subject. - * - * @param subject - * the subject to check. - * @param property - * the property to check. - * @param value - * the value to check. - * - * @return true if the value exists, false if not. - */ - static boolean hasValue(Map subject, String property, Object value) { - boolean rval = false; - if (hasProperty(subject, property)) { - Object val = subject.get(property); - final boolean isList = isList(val); - if (isList || val instanceof List) { - if (isList) { - val = ((Map) val).get("@list"); - } - for (final Object i : (List) val) { - if (compareValues(value, i)) { - rval = true; - break; - } - } - } else if (!(value instanceof List)) { - rval = compareValues(value, val); - } - } - return rval; - } - - private static boolean hasProperty(Map subject, String property) { - boolean rval = false; - if (subject.containsKey(property)) { - final Object value = subject.get(property); - rval = (!(value instanceof List) || ((List) value).size() > 0); - } - return rval; - } - /** * Compares two JSON-LD values for equality. Two JSON-LD values will be * considered equal if: * - * 1. They are both primitives of the same type and value. 2. They are both @values - * with the same @value, @type, and @language, OR 3. They both have @ids - * they are the same. + * 1. They are both primitives of the same type and value. 2. They are + * both @values with the same @value, @type, and @language, OR 3. They both + * have @ids they are the same. * * @param v1 * the first value. @@ -669,70 +360,28 @@ static boolean compareValues(Object v1, Object v2) { return true; } - if (isValue(v1) - && isValue(v2) + if (isValue(v1) && isValue(v2) && Obj.equals(((Map) v1).get("@value"), ((Map) v2).get("@value")) - && Obj.equals(((Map) v1).get("@type"), - ((Map) v2).get("@type")) - && Obj.equals(((Map) v1).get("@language"), - ((Map) v2).get("@language")) - && Obj.equals(((Map) v1).get("@index"), - ((Map) v2).get("@index"))) { + && Obj.equals(((Map) v1).get("@type"), + ((Map) v2).get("@type")) + && Obj.equals(((Map) v1).get("@language"), + ((Map) v2).get("@language")) + && Obj.equals(((Map) v1).get("@index"), + ((Map) v2).get("@index"))) { return true; } if ((v1 instanceof Map && ((Map) v1).containsKey("@id")) && (v2 instanceof Map && ((Map) v2).containsKey("@id")) - && ((Map) v1).get("@id").equals( - ((Map) v2).get("@id"))) { + && ((Map) v1).get("@id") + .equals(((Map) v2).get("@id"))) { return true; } return false; } - /** - * Removes a value from a subject. - * - * @param subject - * the subject. - * @param property - * the property that relates the value to the subject. - * @param value - * the value to remove. - * @param [options] the options to use: [propertyIsArray] true if the - * property is always an array, false if not (default: false). - */ - static void removeValue(Map subject, String property, Map value) { - removeValue(subject, property, value, false); - } - - static void removeValue(Map subject, String property, - Map value, boolean propertyIsArray) { - // filter out value - final List values = new ArrayList(); - if (subject.get(property) instanceof List) { - for (final Object e : ((List) subject.get(property))) { - if (!(value.equals(e))) { - values.add(value); - } - } - } else { - if (!value.equals(subject.get(property))) { - values.add(subject.get(property)); - } - } - - if (values.size() == 0) { - subject.remove(property); - } else if (values.size() == 1 && !propertyIsArray) { - subject.put(property, values.get(0)); - } else { - subject.put(property, values); - } - } - /** * Returns true if the given value is a blank node. * @@ -747,86 +396,13 @@ static boolean isBlankNode(Object v) { // 2. If it has an @id key its value begins with '_:'. // 3. It has no keys OR is not a @value, @set, or @list. if (v instanceof Map) { - if (((Map) v).containsKey("@id")) { - return ((String) ((Map) v).get("@id")).startsWith("_:"); + final Map map = (Map) v; + if (map.containsKey("@id")) { + return ((String) map.get("@id")).startsWith("_:"); } else { - return ((Map) v).size() == 0 - || !(((Map) v).containsKey("@value") || ((Map) v).containsKey("@set") || ((Map) v) - .containsKey("@list")); - } - } - return false; - } - - /** - * Finds all @context URLs in the given JSON-LD input. - * - * @param input - * the JSON-LD input. - * @param urls - * a map of URLs (url => false/@contexts). - * @param replace - * true to replace the URLs in the given input with the - * @contexts from the urls map, false not to. - * - * @return true if new URLs to resolve were found, false if not. - */ - private static boolean findContextUrls(Object input, Map urls, Boolean replace) { - final int count = urls.size(); - if (input instanceof List) { - for (final Object i : (List) input) { - findContextUrls(i, urls, replace); - } - return count < urls.size(); - } else if (input instanceof Map) { - for (final String key : ((Map) input).keySet()) { - if (!"@context".equals(key)) { - findContextUrls(((Map) input).get(key), urls, replace); - continue; - } - - // get @context - final Object ctx = ((Map) input).get(key); - - // array @context - if (ctx instanceof List) { - int length = ((List) ctx).size(); - for (int i = 0; i < length; i++) { - Object _ctx = ((List) ctx).get(i); - if (_ctx instanceof String) { - // replace w/@context if requested - if (replace) { - _ctx = urls.get(_ctx); - if (_ctx instanceof List) { - // add flattened context - ((List) ctx).remove(i); - ((List) ctx).addAll((Collection) _ctx); - i += ((List) _ctx).size(); - length += ((List) _ctx).size(); - } else { - ((List) ctx).set(i, _ctx); - } - } - // @context JsonLdUrl found - else if (!urls.containsKey(_ctx)) { - urls.put((String) _ctx, Boolean.FALSE); - } - } - } - } - // string @context - else if (ctx instanceof String) { - // replace w/@context if requested - if (replace) { - ((Map) input).put(key, urls.get(ctx)); - } - // @context JsonLdUrl found - else if (!urls.containsKey(ctx)) { - urls.put((String) ctx, Boolean.FALSE); - } - } + return map.isEmpty() || !map.containsKey("@value") || map.containsKey("@set") + || map.containsKey("@list"); } - return (count < urls.size()); } return false; } diff --git a/core/src/main/java/com/github/jsonldjava/core/NormalizeUtils.java b/core/src/main/java/com/github/jsonldjava/core/NormalizeUtils.java index 6aab6e46..7b1bd5e7 100644 --- a/core/src/main/java/com/github/jsonldjava/core/NormalizeUtils.java +++ b/core/src/main/java/com/github/jsonldjava/core/NormalizeUtils.java @@ -31,7 +31,7 @@ public NormalizeUtils(List quads, Map bnodes, UniqueName this.namer = namer; } - // generates unique and duplicate hashes for bnodes + // generates unique and duplicate hashes for bnodes public Object hashBlankNodes(Collection unnamed_) throws JsonLdError { List unnamed = new ArrayList(unnamed_); List nextUnnamed = new ArrayList(); @@ -93,24 +93,24 @@ public Object hashBlankNodes(Collection unnamed_) throws JsonLdError { for (int cai = 0; cai < quads.size(); ++cai) { final Map quad = (Map) quads .get(cai); - for (final String attr : new String[] { "subject", "object", "name" }) { + for (final String attr : new String[] { "subject", "object", + "name" }) { if (quad.containsKey(attr)) { final Map qa = (Map) quad .get(attr); - if (qa != null - && "blank node".equals(qa.get("type")) - && ((String) qa.get("value")).indexOf("_:c14n") != 0) { + if (qa != null && "blank node".equals(qa.get("type")) + && ((String) qa.get("value")) + .indexOf("_:c14n") != 0) { qa.put("value", namer.getName((String) qa.get(("value")))); } } } - normalized - .add(toNQuad( - (RDFDataset.Quad) quad, - quad.containsKey("name") - && quad.get("name") != null ? (String) ((Map) quad - .get("name")).get("value") : null)); + normalized.add(toNQuad((RDFDataset.Quad) quad, + quad.containsKey("name") && quad.get("name") != null + ? (String) ((Map) quad.get("name")) + .get("value") + : null)); } // sort normalized output @@ -118,8 +118,8 @@ public Object hashBlankNodes(Collection unnamed_) throws JsonLdError { // handle output format if (options.format != null) { - if ("application/nquads".equals(options.format)) { - StringBuilder rval = new StringBuilder(); + if (JsonLdConsts.APPLICATION_NQUADS.equals(options.format)) { + final StringBuilder rval = new StringBuilder(); for (final String n : normalized) { rval.append(n); } @@ -129,7 +129,7 @@ public Object hashBlankNodes(Collection unnamed_) throws JsonLdError { options.format); } } - StringBuilder rval = new StringBuilder(); + final StringBuilder rval = new StringBuilder(); for (final String n : normalized) { rval.append(n); } @@ -171,7 +171,8 @@ public int compare(HashResult a, HashResult b) { final UniqueNamer pathNamer = new UniqueNamer("_:b"); pathNamer.getName(bnode); - final HashResult result = hashPaths(bnode, bnodes, namer, pathNamer); + final HashResult result = hashPaths(bnode, bnodes, namer, + pathNamer); results.add(result); } } @@ -435,8 +436,10 @@ private static String hashQuads(String id, Map bnodes, UniqueNam final List nquads = new ArrayList(); for (int i = 0; i < quads.size(); ++i) { nquads.add(toNQuad((RDFDataset.Quad) quads.get(i), - quads.get(i).get("name") != null ? (String) ((Map) quads.get(i) - .get("name")).get("value") : null, id)); + quads.get(i).get("name") != null + ? (String) ((Map) quads.get(i).get("name")).get("value") + : null, + id)); } // sort serialized quads Collections.sort(nquads); @@ -490,8 +493,9 @@ private static String encodeHex(final byte[] data) { */ private static String getAdjacentBlankNodeName(Map node, String id) { return "blank node".equals(node.get("type")) - && (!node.containsKey("value") || !Obj.equals(node.get("value"), id)) ? (String) node - .get("value") : null; + && (!node.containsKey("value") || !Obj.equals(node.get("value"), id)) + ? (String) node.get("value") + : null; } private static class Permutator { @@ -540,8 +544,9 @@ public List next() { final String element = this.list.get(i); final Boolean left = this.left.get(element); if ((k == null || element.compareTo(k) > 0) - && ((left && i > 0 && element.compareTo(this.list.get(i - 1)) > 0) || (!left - && i < (length - 1) && element.compareTo(this.list.get(i + 1)) > 0))) { + && ((left && i > 0 && element.compareTo(this.list.get(i - 1)) > 0) + || (!left && i < (length - 1) + && element.compareTo(this.list.get(i + 1)) > 0))) { k = element; pos = i; } diff --git a/core/src/main/java/com/github/jsonldjava/core/RDFDataset.java b/core/src/main/java/com/github/jsonldjava/core/RDFDataset.java index e1bf55c1..8f8e18c2 100644 --- a/core/src/main/java/com/github/jsonldjava/core/RDFDataset.java +++ b/core/src/main/java/com/github/jsonldjava/core/RDFDataset.java @@ -6,6 +6,7 @@ import static com.github.jsonldjava.core.JsonLdConsts.RDF_REST; import static com.github.jsonldjava.core.JsonLdConsts.RDF_TYPE; import static com.github.jsonldjava.core.JsonLdConsts.XSD_BOOLEAN; +import static com.github.jsonldjava.core.JsonLdConsts.XSD_DECIMAL; import static com.github.jsonldjava.core.JsonLdConsts.XSD_DOUBLE; import static com.github.jsonldjava.core.JsonLdConsts.XSD_INTEGER; import static com.github.jsonldjava.core.JsonLdConsts.XSD_STRING; @@ -48,8 +49,8 @@ public static class Quad extends LinkedHashMap implements Compar public Quad(final String subject, final String predicate, final String object, final String graph) { - this(subject, predicate, object.startsWith("_:") ? new BlankNode(object) : new IRI( - object), graph); + this(subject, predicate, + object.startsWith("_:") ? new BlankNode(object) : new IRI(object), graph); }; public Quad(final String subject, final String predicate, final String value, @@ -59,11 +60,12 @@ public Quad(final String subject, final String predicate, final String value, private Quad(final String subject, final String predicate, final Node object, final String graph) { - this(subject.startsWith("_:") ? new BlankNode(subject) : new IRI(subject), new IRI( - predicate), object, graph); + this(subject.startsWith("_:") ? new BlankNode(subject) : new IRI(subject), + new IRI(predicate), object, graph); }; - public Quad(final Node subject, final Node predicate, final Node object, final String graph) { + public Quad(final Node subject, final Node predicate, final Node object, + final String graph) { super(); put("subject", subject); put("predicate", predicate); @@ -112,8 +114,8 @@ public int compareTo(Quad o) { } } - public static abstract class Node extends LinkedHashMap implements - Comparable { + public static abstract class Node extends LinkedHashMap + implements Comparable { private static final long serialVersionUID = 1460990331795672793L; public abstract boolean isLiteral(); @@ -136,6 +138,10 @@ public String getLanguage() { @Override public int compareTo(Node o) { + if (o == null) { + // valid nodes are > null nodes + return 1; + } if (this.isIRI()) { if (!o.isIRI()) { // IRIs > everything @@ -149,7 +155,13 @@ public int compareTo(Node o) { // blank node > literal return 1; } + } else if (this.isLiteral()) { + if (o.isIRI() || o.isBlankNode()) { + return -1; // literals < blanknode < IRI + } } + // NOTE: Literal will also need to compare + // language and datatype return this.getValue().compareTo(o.getValue()); } @@ -198,10 +210,11 @@ Map toObject(Boolean useNativeTypes) throws JsonLdError { rval.put("@type", type); } } else if ( - // http://www.w3.org/TR/xmlschema11-2/#integer - (XSD_INTEGER.equals(type) && PATTERN_INTEGER.matcher(value).matches()) + // http://www.w3.org/TR/xmlschema11-2/#integer + (XSD_INTEGER.equals(type) && PATTERN_INTEGER.matcher(value).matches()) // http://www.w3.org/TR/xmlschema11-2/#nt-doubleRep - || (XSD_DOUBLE.equals(type) && PATTERN_DOUBLE.matcher(value).matches())) { + || (XSD_DOUBLE.equals(type) + && PATTERN_DOUBLE.matcher(value).matches())) { try { final Double d = Double.parseDouble(value); if (!Double.isNaN(d) && !Double.isInfinite(d)) { @@ -263,32 +276,36 @@ public boolean isBlankNode() { return false; } - @Override - public int compareTo(Node o) { - if (o == null) { - // valid nodes are > null nodes - return 1; - } - if (o.isIRI()) { - // literals < iri - return -1; + private static int nullSafeCompare(String a, String b) { + if (a == null && b == null) { + return 0; } - if (o.isBlankNode()) { - // blank node < iri - return -1; + if (a == null) { + return 1; } - if (this.getLanguage() == null && ((Literal) o).getLanguage() != null) { + if (b == null) { return -1; - } else if (this.getLanguage() != null && ((Literal) o).getLanguage() == null) { - return 1; } + return a.compareTo(b); + } - if (this.getDatatype() != null) { - return this.getDatatype().compareTo(((Literal) o).getDatatype()); - } else if (((Literal) o).getDatatype() != null) { - return -1; + @Override + public int compareTo(Node o) { + // NOTE: this will also compare getValue() early! + final int nodeCompare = super.compareTo(o); + if (nodeCompare != 0) { + // null, different type or different value + return nodeCompare; + } + if (this.getLanguage() != null || o.getLanguage() != null) { + // We'll ignore type-checking if either has language tag + // as language tagged literals should always have the type + // rdf:langString in RDF 1.1 + return nullSafeCompare(this.getLanguage(), o.getLanguage()); + } else { + return nullSafeCompare(this.getDatatype(), o.getDatatype()); } - return 0; + // NOTE: getValue() already compared by super.compareTo() } } @@ -638,26 +655,38 @@ private Node objectToRDF(Object item) { if (value instanceof Boolean || value instanceof Number) { // convert to XSD datatype if (value instanceof Boolean) { - return new Literal(value.toString(), datatype == null ? XSD_BOOLEAN - : (String) datatype, null); + return new Literal(value.toString(), + datatype == null ? XSD_BOOLEAN : (String) datatype, null); } else if (value instanceof Double || value instanceof Float || XSD_DOUBLE.equals(datatype)) { - // canonical double representation - final DecimalFormat df = new DecimalFormat("0.0###############E0"); - df.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Locale.US)); - return new Literal(df.format(value), datatype == null ? XSD_DOUBLE - : (String) datatype, null); + if (value instanceof Double && !Double.isFinite((double) value)) { + return new Literal(Double.toString((double) value), + datatype == null ? XSD_DOUBLE : (String) datatype, null); + } else if (value instanceof Float && !Float.isFinite((float) value)) { + return new Literal(Float.toString((float) value), + datatype == null ? XSD_DOUBLE : (String) datatype, null); + } else { + // Only canonicalize representation if datatype is not XSD_DECIMAL + if (XSD_DECIMAL.equals(datatype)) { + return new Literal(value.toString(), XSD_DECIMAL, null); + } + final DecimalFormat df = new DecimalFormat("0.0###############E0"); + df.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Locale.US)); + return new Literal(df.format(value), + datatype == null ? XSD_DOUBLE : (String) datatype, null); + } } else { final DecimalFormat df = new DecimalFormat("0"); - return new Literal(df.format(value), datatype == null ? XSD_INTEGER - : (String) datatype, null); + return new Literal(df.format(value), + datatype == null ? XSD_INTEGER : (String) datatype, null); } } else if (((Map) item).containsKey("@language")) { - return new Literal((String) value, datatype == null ? RDF_LANGSTRING - : (String) datatype, (String) ((Map) item).get("@language")); + return new Literal((String) value, + datatype == null ? RDF_LANGSTRING : (String) datatype, + (String) ((Map) item).get("@language")); } else { - return new Literal((String) value, datatype == null ? XSD_STRING - : (String) datatype, null); + return new Literal((String) value, + datatype == null ? XSD_STRING : (String) datatype, null); } } // convert string/node object to RDF diff --git a/core/src/main/java/com/github/jsonldjava/core/RDFDatasetUtils.java b/core/src/main/java/com/github/jsonldjava/core/RDFDatasetUtils.java index 4961752a..a2739e64 100644 --- a/core/src/main/java/com/github/jsonldjava/core/RDFDatasetUtils.java +++ b/core/src/main/java/com/github/jsonldjava/core/RDFDatasetUtils.java @@ -1,224 +1,23 @@ package com.github.jsonldjava.core; -import static com.github.jsonldjava.core.JsonLdConsts.RDF_FIRST; import static com.github.jsonldjava.core.JsonLdConsts.RDF_LANGSTRING; -import static com.github.jsonldjava.core.JsonLdConsts.RDF_NIL; -import static com.github.jsonldjava.core.JsonLdConsts.RDF_REST; -import static com.github.jsonldjava.core.JsonLdConsts.RDF_TYPE; -import static com.github.jsonldjava.core.JsonLdConsts.XSD_BOOLEAN; -import static com.github.jsonldjava.core.JsonLdConsts.XSD_DOUBLE; -import static com.github.jsonldjava.core.JsonLdConsts.XSD_INTEGER; import static com.github.jsonldjava.core.JsonLdConsts.XSD_STRING; -import static com.github.jsonldjava.core.JsonLdUtils.isKeyword; -import static com.github.jsonldjava.core.JsonLdUtils.isList; -import static com.github.jsonldjava.core.JsonLdUtils.isObject; -import static com.github.jsonldjava.core.JsonLdUtils.isValue; import static com.github.jsonldjava.core.Regex.HEX; -import static com.github.jsonldjava.utils.Obj.newMap; -import java.text.DecimalFormat; -import java.text.DecimalFormatSymbols; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Locale; -import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; public class RDFDatasetUtils { - /** - * Creates an array of RDF triples for the given graph. - * - * @param graph - * the graph to create RDF triples for. - * @param namer - * a UniqueNamer for assigning blank node names. - * - * @return the array of RDF triples for the given graph. - * @deprecated Use {@link RDFDataset#graphToRDF(String, Map)} instead - */ - @Deprecated - static List graphToRDF(Map graph, UniqueNamer namer) { - final List rval = new ArrayList(); - for (final String id : graph.keySet()) { - final Map node = (Map) graph.get(id); - final List properties = new ArrayList(node.keySet()); - Collections.sort(properties); - for (String property : properties) { - final Object items = node.get(property); - if ("@type".equals(property)) { - property = RDF_TYPE; - } else if (isKeyword(property)) { - continue; - } - - for (final Object item : (List) items) { - // RDF subjects - final Map subject = newMap(); - if (id.indexOf("_:") == 0) { - subject.put("type", "blank node"); - subject.put("value", namer.getName(id)); - } else { - subject.put("type", "IRI"); - subject.put("value", id); - } - - // RDF predicates - final Map predicate = newMap(); - predicate.put("type", "IRI"); - predicate.put("value", property); - - // convert @list to triples - if (isList(item)) { - listToRDF((List) ((Map) item).get("@list"), namer, - subject, predicate, rval); - } - // convert value or node object to triple - else { - final Object object = objectToRDF(item, namer); - final Map tmp = newMap(); - tmp.put("subject", subject); - tmp.put("predicate", predicate); - tmp.put("object", object); - rval.add(tmp); - } - } - } - } - - return rval; - } - - /** - * Converts a @list value into linked list of blank node RDF triples (an RDF - * collection). - * - * @param list - * the @list value. - * @param namer - * a UniqueNamer for assigning blank node names. - * @param subject - * the subject for the head of the list. - * @param predicate - * the predicate for the head of the list. - * @param triples - * the array of triples to append to. - */ - private static void listToRDF(List list, UniqueNamer namer, - Map subject, Map predicate, List triples) { - final Map first = newMap(); - first.put("type", "IRI"); - first.put("value", RDF_FIRST); - final Map rest = newMap(); - rest.put("type", "IRI"); - rest.put("value", RDF_REST); - final Map nil = newMap(); - nil.put("type", "IRI"); - nil.put("value", RDF_NIL); - - for (final Object item : list) { - final Map blankNode = newMap(); - blankNode.put("type", "blank node"); - blankNode.put("value", namer.getName()); - - { - final Map tmp = newMap(); - tmp.put("subject", subject); - tmp.put("predicate", predicate); - tmp.put("object", blankNode); - triples.add(tmp); - } - - subject = blankNode; - predicate = first; - final Object object = objectToRDF(item, namer); - - { - final Map tmp = newMap(); - tmp.put("subject", subject); - tmp.put("predicate", predicate); - tmp.put("object", object); - triples.add(tmp); - } - - predicate = rest; - } - final Map tmp = newMap(); - tmp.put("subject", subject); - tmp.put("predicate", predicate); - tmp.put("object", nil); - triples.add(tmp); - } - - /** - * Converts a JSON-LD value object to an RDF literal or a JSON-LD string or - * node object to an RDF resource. - * - * @param item - * the JSON-LD value or node object. - * @param namer - * the UniqueNamer to use to assign blank node names. - * - * @return the RDF literal or RDF resource. - */ - private static Object objectToRDF(Object item, UniqueNamer namer) { - final Map object = newMap(); - - // convert value object to RDF - if (isValue(item)) { - object.put("type", "literal"); - final Object value = ((Map) item).get("@value"); - final Object datatype = ((Map) item).get("@type"); - - // convert to XSD datatypes as appropriate - if (value instanceof Boolean || value instanceof Number) { - // convert to XSD datatype - if (value instanceof Boolean) { - object.put("value", value.toString()); - object.put("datatype", datatype == null ? XSD_BOOLEAN : datatype); - } else if (value instanceof Double || value instanceof Float) { - // canonical double representation - final DecimalFormat df = new DecimalFormat("0.0###############E0"); - df.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Locale.US)); - object.put("value", df.format(value)); - object.put("datatype", datatype == null ? XSD_DOUBLE : datatype); - } else { - final DecimalFormat df = new DecimalFormat("0"); - object.put("value", df.format(value)); - object.put("datatype", datatype == null ? XSD_INTEGER : datatype); - } - } else if (((Map) item).containsKey("@language")) { - object.put("value", value); - object.put("datatype", datatype == null ? RDF_LANGSTRING : datatype); - object.put("language", ((Map) item).get("@language")); - } else { - object.put("value", value); - object.put("datatype", datatype == null ? XSD_STRING : datatype); - } - } - // convert string/node object to RDF - else { - final String id = isObject(item) ? (String) ((Map) item).get("@id") - : (String) item; - if (id.indexOf("_:") == 0) { - object.put("type", "blank node"); - object.put("value", namer.getName(id)); - } else { - object.put("type", "IRI"); - object.put("value", id); - } - } - - return object; - } - public static String toNQuads(RDFDataset dataset) { - StringBuilder output = new StringBuilder(256); + final StringBuilder output = new StringBuilder(256); toNQuads(dataset, output); return output.toString(); } + public static void toNQuads(RDFDataset dataset, StringBuilder output) { final List quads = new ArrayList(); for (String graphName : dataset.graphNames()) { @@ -237,11 +36,13 @@ public static void toNQuads(RDFDataset dataset, StringBuilder output) { } static String toNQuad(RDFDataset.Quad triple, String graphName, String bnode) { - StringBuilder output = new StringBuilder(256); + final StringBuilder output = new StringBuilder(256); toNQuad(triple, graphName, bnode, output); return output.toString(); } - static void toNQuad(RDFDataset.Quad triple, String graphName, String bnode, StringBuilder output) { + + static void toNQuad(RDFDataset.Quad triple, String graphName, String bnode, + StringBuilder output) { final RDFDataset.Node s = triple.getSubject(); final RDFDataset.Node p = triple.getPredicate(); final RDFDataset.Node o = triple.getObject(); @@ -252,7 +53,7 @@ static void toNQuad(RDFDataset.Quad triple, String graphName, String bnode, Stri escape(s.getValue(), output); output.append(">"); } - // normalization mode + // normalization mode else if (bnode != null) { output.append(bnode.equals(s.getValue()) ? "_:a" : "_:z"); } @@ -321,8 +122,8 @@ static String toNQuad(RDFDataset.Quad triple, String graphName) { return toNQuad(triple, graphName, null); } - final private static Pattern UCHAR_MATCHED = Pattern.compile("\\u005C(?:([tbnrf\\\"'])|(?:u(" - + HEX + "{4}))|(?:U(" + HEX + "{8})))"); + final private static Pattern UCHAR_MATCHED = Pattern + .compile("\\u005C(?:([tbnrf\\\"'])|(?:u(" + HEX + "{4}))|(?:U(" + HEX + "{8})))"); public static String unescape(String str) { String rval = str; @@ -344,7 +145,7 @@ public static String unescape(String str) { final int w1 = 0xD800 + vh; final int w2 = 0xDC00 + v1; - final StringBuffer b = new StringBuffer(); + final StringBuilder b = new StringBuilder(); b.appendCodePoint(w1); b.appendCodePoint(w2); uni = b.toString(); @@ -384,7 +185,7 @@ public static String unescape(String str) { } } final String pat = Pattern.quote(m.group(0)); - final String x = Integer.toHexString(uni.charAt(0)); + // final String x = Integer.toHexString(uni.charAt(0)); rval = rval.replaceAll(pat, uni); } } @@ -393,20 +194,11 @@ public static String unescape(String str) { /** * Escapes the given string according to the N-Quads escape rules - * @param str The string to escape - * @return The escaped string - * @deprecated Use {@link #escape(String, StringBuilder)} instead. - */ - public static String escape(String str) { - StringBuilder rval = new StringBuilder(); - escape(str, rval); - return rval.toString(); - } - - /** - * Escapes the given string according to the N-Quads escape rules - * @param str The string to escape - * @param rval The {@link StringBuilder} to append to. + * + * @param str + * The string to escape + * @param rval + * The {@link StringBuilder} to append to. */ public static void escape(String str, StringBuilder rval) { for (int i = 0; i < str.length(); i++) { @@ -417,12 +209,12 @@ public static void escape(String str, StringBuilder rval) { // supplement // characters ((hi >= 0x24F // 0x24F is the end of latin extensions - && !Character.isHighSurrogate(hi)) + && !Character.isHighSurrogate(hi)) // TODO: there's probably a lot of other characters that // shouldn't be escaped that // fall outside these ranges, this is one example from the // json-ld tests - )) { + )) { rval.append(String.format("\\u%04x", (int) hi)); } else if (Character.isHighSurrogate(hi)) { final char lo = str.charAt(++i); @@ -445,9 +237,9 @@ public static void escape(String str, StringBuilder rval) { case '\r': rval.append("\\r"); break; - // case '\'': - // rval += "\\'"; - // break; + // case '\'': + // rval += "\\'"; + // break; case '\"': rval.append("\\\""); // rval += "\\u0022"; @@ -462,7 +254,7 @@ public static void escape(String str, StringBuilder rval) { } } } - //return rval; + // return rval; } private static class Regex { @@ -474,8 +266,8 @@ private static class Regex { final public static Pattern PLAIN = Pattern.compile("\"([^\"\\\\]*(?:\\\\.[^\"\\\\]*)*)\""); final public static Pattern DATATYPE = Pattern.compile("(?:\\^\\^" + IRI + ")"); final public static Pattern LANGUAGE = Pattern.compile("(?:@([a-z]+(?:-[a-zA-Z0-9]+)*))"); - final public static Pattern LITERAL = Pattern.compile("(?:" + PLAIN + "(?:" + DATATYPE - + "|" + LANGUAGE + ")?)"); + final public static Pattern LITERAL = Pattern + .compile("(?:" + PLAIN + "(?:" + DATATYPE + "|" + LANGUAGE + ")?)"); final public static Pattern WS = Pattern.compile("[ \\t]+"); final public static Pattern WSO = Pattern.compile("[ \\t]*"); final public static Pattern EOLN = Pattern.compile("(?:\r\n)|(?:\n)|(?:\r)"); @@ -484,14 +276,14 @@ private static class Regex { // define quad part regexes final public static Pattern SUBJECT = Pattern.compile("(?:" + IRI + "|" + BNODE + ")" + WS); final public static Pattern PROPERTY = Pattern.compile(IRI.pattern() + WS.pattern()); - final public static Pattern OBJECT = Pattern.compile("(?:" + IRI + "|" + BNODE + "|" - + LITERAL + ")" + WSO); - final public static Pattern GRAPH = Pattern.compile("(?:\\.|(?:(?:" + IRI + "|" + BNODE - + ")" + WSO + "\\.))"); + final public static Pattern OBJECT = Pattern + .compile("(?:" + IRI + "|" + BNODE + "|" + LITERAL + ")" + WSO); + final public static Pattern GRAPH = Pattern + .compile("(?:\\.|(?:(?:" + IRI + "|" + BNODE + ")" + WSO + "\\.))"); // full quad regex - final public static Pattern QUAD = Pattern.compile("^" + WSO + SUBJECT + PROPERTY + OBJECT - + GRAPH + WSO + "$"); + final public static Pattern QUAD = Pattern + .compile("^" + WSO + SUBJECT + PROPERTY + OBJECT + GRAPH + WSO + "$"); } /** @@ -545,8 +337,8 @@ public static RDFDataset parseNQuads(String input) throws JsonLdError { object = new RDFDataset.BlankNode(unescape(match.group(5))); } else { final String language = unescape(match.group(8)); - final String datatype = match.group(7) != null ? unescape(match.group(7)) : match - .group(8) != null ? RDF_LANGSTRING : XSD_STRING; + final String datatype = match.group(7) != null ? unescape(match.group(7)) + : match.group(8) != null ? RDF_LANGSTRING : XSD_STRING; final String unescaped = unescape(match.group(6)); object = new RDFDataset.Literal(unescaped, datatype, language); } diff --git a/core/src/main/java/com/github/jsonldjava/core/RDFParser.java b/core/src/main/java/com/github/jsonldjava/core/RDFParser.java index 4b6fed61..db4ec692 100644 --- a/core/src/main/java/com/github/jsonldjava/core/RDFParser.java +++ b/core/src/main/java/com/github/jsonldjava/core/RDFParser.java @@ -1,47 +1,47 @@ -package com.github.jsonldjava.core; - -/** - * Interface for parsing RDF into the RDF Dataset objects to be used by - * JSONLD.fromRDF - * - * @author Tristan - * - */ -public interface RDFParser { - - /** - * Parse the input into the internal RDF Dataset format The format is a Map - * with the following structure: { GRAPH_1: [ TRIPLE_1, TRIPLE_2, ..., - * TRIPLE_N ], GRAPH_2: [ TRIPLE_1, TRIPLE_2, ..., TRIPLE_N ], ... GRAPH_N: - * [ TRIPLE_1, TRIPLE_2, ..., TRIPLE_N ] } - * - * GRAPH: Must be the graph name/IRI. if no graph is present for a triple, - * add it to the "@default" graph TRIPLE: Must be a map with the following - * structure: { "subject" : SUBJECT "predicate" : PREDICATE "object" : - * OBJECT } - * - * Each of the values in the triple map must also be a map with the - * following key-value pairs: "value" : The value of the node. "subject" can - * be an IRI or blank node id. "predicate" should only ever be an IRI - * "object" can be and IRI or blank node id, or a literal value (represented - * as a string) "type" : "IRI" if the value is an IRI or "blank node" if the - * value is a blank node. "object" can also be "literal" in the case of - * literals. The value of "object" can also contain the following optional - * key-value pairs: "language" : the language value of a string literal - * "datatype" : the datatype of the literal. (if not set will default to - * XSD:string, if set to null, null will be used). - * - * The RDFDatasetUtils class has the following helper methods to make - * generating this format easier: result = getInitialRDFDatasetResult(); - * triple = generateTriple(s,p,o); triple = - * generateTriple(s,p,value,datatype,language); - * addTripleToRDFDatasetResult(result, graphName, triple); - * - * @param input - * The RDF library specific input to parse - * @return The input parsed using the internal RDF Dataset format - * @throws JsonLdError - * If there was an error parsing the input - */ - public RDFDataset parse(Object input) throws JsonLdError; -} +package com.github.jsonldjava.core; + +/** + * Interface for parsing RDF into the RDF Dataset objects to be used by + * JSONLD.fromRDF + * + * @author Tristan + * + */ +public interface RDFParser { + + /** + * Parse the input into the internal RDF Dataset format The format is a Map + * with the following structure: { GRAPH_1: [ TRIPLE_1, TRIPLE_2, ..., + * TRIPLE_N ], GRAPH_2: [ TRIPLE_1, TRIPLE_2, ..., TRIPLE_N ], ... GRAPH_N: + * [ TRIPLE_1, TRIPLE_2, ..., TRIPLE_N ] } + * + * GRAPH: Must be the graph name/IRI. if no graph is present for a triple, + * add it to the "@default" graph TRIPLE: Must be a map with the following + * structure: { "subject" : SUBJECT "predicate" : PREDICATE "object" : + * OBJECT } + * + * Each of the values in the triple map must also be a map with the + * following key-value pairs: "value" : The value of the node. "subject" can + * be an IRI or blank node id. "predicate" should only ever be an IRI + * "object" can be and IRI or blank node id, or a literal value (represented + * as a string) "type" : "IRI" if the value is an IRI or "blank node" if the + * value is a blank node. "object" can also be "literal" in the case of + * literals. The value of "object" can also contain the following optional + * key-value pairs: "language" : the language value of a string literal + * "datatype" : the datatype of the literal. (if not set will default to + * XSD:string, if set to null, null will be used). + * + * The RDFDatasetUtils class has the following helper methods to make + * generating this format easier: result = getInitialRDFDatasetResult(); + * triple = generateTriple(s,p,o); triple = + * generateTriple(s,p,value,datatype,language); + * addTripleToRDFDatasetResult(result, graphName, triple); + * + * @param input + * The RDF library specific input to parse + * @return The input parsed using the internal RDF Dataset format + * @throws JsonLdError + * If there was an error parsing the input + */ + public RDFDataset parse(Object input) throws JsonLdError; +} diff --git a/core/src/main/java/com/github/jsonldjava/core/Regex.java b/core/src/main/java/com/github/jsonldjava/core/Regex.java index a34a1ee7..33f43759 100644 --- a/core/src/main/java/com/github/jsonldjava/core/Regex.java +++ b/core/src/main/java/com/github/jsonldjava/core/Regex.java @@ -2,45 +2,45 @@ import java.util.regex.Pattern; -public class Regex { +class Regex { final public static Pattern TRICKY_UTF_CHARS = Pattern.compile( // ("1.7".equals(System.getProperty("java.specification.version")) ? // "[\\x{10000}-\\x{EFFFF}]" : "[\uD800\uDC00-\uDB7F\uDFFF]" // this seems to work with jdk1.6 - ); + ); // for ttl - final public static Pattern PN_CHARS_BASE = Pattern - .compile("[a-zA-Z]|[\\u00C0-\\u00D6]|[\\u00D8-\\u00F6]|[\\u00F8-\\u02FF]|[\\u0370-\\u037D]|[\\u037F-\\u1FFF]|" + final public static Pattern PN_CHARS_BASE = Pattern.compile( + "[a-zA-Z]|[\\u00C0-\\u00D6]|[\\u00D8-\\u00F6]|[\\u00F8-\\u02FF]|[\\u0370-\\u037D]|[\\u037F-\\u1FFF]|" + "[\\u200C-\\u200D]|[\\u2070-\\u218F]|[\\u2C00-\\u2FEF]|[\\u3001-\\uD7FF]|[\\uF900-\\uFDCF]|[\\uFDF0-\\uFFFD]|" + TRICKY_UTF_CHARS); final public static Pattern PN_CHARS_U = Pattern.compile(PN_CHARS_BASE + "|[_]"); - final public static Pattern PN_CHARS = Pattern.compile(PN_CHARS_U - + "|[-0-9]|[\\u00B7]|[\\u0300-\\u036F]|[\\u203F-\\u2040]"); - final public static Pattern PN_PREFIX = Pattern.compile("(?:(?:" + PN_CHARS_BASE + ")(?:(?:" - + PN_CHARS + "|[\\.])*(?:" + PN_CHARS + "))?)"); + final public static Pattern PN_CHARS = Pattern + .compile(PN_CHARS_U + "|[-0-9]|[\\u00B7]|[\\u0300-\\u036F]|[\\u203F-\\u2040]"); + final public static Pattern PN_PREFIX = Pattern.compile( + "(?:(?:" + PN_CHARS_BASE + ")(?:(?:" + PN_CHARS + "|[\\.])*(?:" + PN_CHARS + "))?)"); final public static Pattern HEX = Pattern.compile("[0-9A-Fa-f]"); final public static Pattern PN_LOCAL_ESC = Pattern .compile("[\\\\][_~\\.\\-!$&'\\(\\)*+,;=/?#@%]"); final public static Pattern PERCENT = Pattern.compile("%" + HEX + HEX); final public static Pattern PLX = Pattern.compile(PERCENT + "|" + PN_LOCAL_ESC); - final public static Pattern PN_LOCAL = Pattern.compile("((?:" + PN_CHARS_U + "|[:]|[0-9]|" - + PLX + ")(?:(?:" + PN_CHARS + "|[.]|[:]|" + PLX + ")*(?:" + PN_CHARS + "|[:]|" + PLX - + "))?)"); + final public static Pattern PN_LOCAL = Pattern + .compile("((?:" + PN_CHARS_U + "|[:]|[0-9]|" + PLX + ")(?:(?:" + PN_CHARS + "|[.]|[:]|" + + PLX + ")*(?:" + PN_CHARS + "|[:]|" + PLX + "))?)"); final public static Pattern PNAME_NS = Pattern.compile("((?:" + PN_PREFIX + ")?):"); final public static Pattern PNAME_LN = Pattern.compile("" + PNAME_NS + PN_LOCAL); final public static Pattern UCHAR = Pattern.compile("\\u005Cu" + HEX + HEX + HEX + HEX + "|\\u005CU" + HEX + HEX + HEX + HEX + HEX + HEX + HEX + HEX); final public static Pattern ECHAR = Pattern.compile("\\u005C[tbnrf\\u005C\"']"); - final public static Pattern IRIREF = Pattern.compile("(?:<((?:[^\\x00-\\x20<>\"{}|\\^`\\\\]|" - + UCHAR + ")*)>)"); + final public static Pattern IRIREF = Pattern + .compile("(?:<((?:[^\\x00-\\x20<>\"{}|\\^`\\\\]|" + UCHAR + ")*)>)"); final public static Pattern BLANK_NODE_LABEL = Pattern.compile("(?:_:((?:" + PN_CHARS_U + "|[0-9])(?:(?:" + PN_CHARS + "|[\\.])*(?:" + PN_CHARS + "))?))"); final public static Pattern WS = Pattern.compile("[ \t\r\n]"); final public static Pattern WS_0_N = Pattern.compile(WS + "*"); final public static Pattern WS_0_1 = Pattern.compile(WS + "?"); final public static Pattern WS_1_N = Pattern.compile(WS + "+"); - final public static Pattern STRING_LITERAL_QUOTE = Pattern - .compile("\"(?:[^\\u0022\\u005C\\u000A\\u000D]|(?:" + ECHAR + ")|(?:" + UCHAR + "))*\""); + final public static Pattern STRING_LITERAL_QUOTE = Pattern.compile( + "\"(?:[^\\u0022\\u005C\\u000A\\u000D]|(?:" + ECHAR + ")|(?:" + UCHAR + "))*\""); final public static Pattern STRING_LITERAL_SINGLE_QUOTE = Pattern .compile("'(?:[^\\u0027\\u005C\\u000A\\u000D]|(?:" + ECHAR + ")|(?:" + UCHAR + "))*'"); final public static Pattern STRING_LITERAL_LONG_SINGLE_QUOTE = Pattern diff --git a/core/src/main/java/com/github/jsonldjava/core/RemoteDocument.java b/core/src/main/java/com/github/jsonldjava/core/RemoteDocument.java index 66fb9aad..0af7a5cd 100644 --- a/core/src/main/java/com/github/jsonldjava/core/RemoteDocument.java +++ b/core/src/main/java/com/github/jsonldjava/core/RemoteDocument.java @@ -1,41 +1,43 @@ package com.github.jsonldjava.core; +/** + * Encapsulates a URL along with the parsed resource matching the URL. + * + * @author Tristan King + */ public class RemoteDocument { - public String getDocumentUrl() { - return documentUrl; + private final String documentUrl; + private final Object document; + + /** + * Create a new RemoteDocument with the URL and the parsed resource for the + * document. + * + * @param url + * The URL + * @param document + * The parsed resource for the document + */ + public RemoteDocument(String url, Object document) { + this.documentUrl = url; + this.document = document; } - public void setDocumentUrl(String documentUrl) { - this.documentUrl = documentUrl; + /** + * Get the URL for this document. + * + * @return The URL for this document, as a String + */ + public String getDocumentUrl() { + return documentUrl; } + /** + * Get the parsed resource for this document. + * + * @return The parsed resource for this document + */ public Object getDocument() { return document; } - - public void setDocument(Object document) { - this.document = document; - } - - public String getContextUrl() { - return contextUrl; - } - - public void setContextUrl(String contextUrl) { - this.contextUrl = contextUrl; - } - - String documentUrl; - Object document; - String contextUrl; - - public RemoteDocument(String url, Object document) { - this(url, document, null); - } - - public RemoteDocument(String url, Object document, String context) { - this.documentUrl = url; - this.document = document; - this.contextUrl = context; - } } diff --git a/core/src/main/java/com/github/jsonldjava/core/UniqueNamer.java b/core/src/main/java/com/github/jsonldjava/core/UniqueNamer.java index 98e2fb45..0d0aa760 100644 --- a/core/src/main/java/com/github/jsonldjava/core/UniqueNamer.java +++ b/core/src/main/java/com/github/jsonldjava/core/UniqueNamer.java @@ -1,72 +1,72 @@ -package com.github.jsonldjava.core; - -import java.util.LinkedHashMap; -import java.util.Map; - -public class UniqueNamer { - private final String prefix; - private int counter; - private Map existing; - - /** - * Creates a new UniqueNamer. A UniqueNamer issues unique names, keeping - * track of any previously issued names. - * - * @param prefix - * the prefix to use ('<prefix><counter>'). - */ - public UniqueNamer(String prefix) { - this.prefix = prefix; - this.counter = 0; - this.existing = new LinkedHashMap(); - } - - /** - * Copies this UniqueNamer. - * - * @return a copy of this UniqueNamer. - */ - @Override - public UniqueNamer clone() { - final UniqueNamer copy = new UniqueNamer(this.prefix); - copy.counter = this.counter; - copy.existing = (Map) JsonLdUtils.clone(this.existing); - return copy; - } - - /** - * Gets the new name for the given old name, where if no old name is given a - * new name will be generated. - * - * @param oldName - * the old name to get the new name for. - * - * @return the new name. - */ - public String getName(String oldName) { - if (oldName != null && this.existing.containsKey(oldName)) { - return this.existing.get(oldName); - } - - final String name = this.prefix + this.counter; - this.counter++; - - if (oldName != null) { - this.existing.put(oldName, name); - } - - return name; - } - - public String getName() { - return getName(null); - } - - public Boolean isNamed(String oldName) { - return this.existing.containsKey(oldName); - } - - public Map existing() { - return existing; - } +package com.github.jsonldjava.core; + +import java.util.LinkedHashMap; +import java.util.Map; + +class UniqueNamer { + private final String prefix; + private int counter; + private Map existing; + + /** + * Creates a new UniqueNamer. A UniqueNamer issues unique names, keeping + * track of any previously issued names. + * + * @param prefix + * the prefix to use ('<prefix><counter>'). + */ + public UniqueNamer(String prefix) { + this.prefix = prefix; + this.counter = 0; + this.existing = new LinkedHashMap(); + } + + /** + * Copies this UniqueNamer. + * + * @return a copy of this UniqueNamer. + */ + @Override + public UniqueNamer clone() { + final UniqueNamer copy = new UniqueNamer(this.prefix); + copy.counter = this.counter; + copy.existing = (Map) JsonLdUtils.clone(this.existing); + return copy; + } + + /** + * Gets the new name for the given old name, where if no old name is given a + * new name will be generated. + * + * @param oldName + * the old name to get the new name for. + * + * @return the new name. + */ + public String getName(String oldName) { + if (oldName != null && this.existing.containsKey(oldName)) { + return this.existing.get(oldName); + } + + final String name = this.prefix + this.counter; + this.counter++; + + if (oldName != null) { + this.existing.put(oldName, name); + } + + return name; + } + + public String getName() { + return getName(null); + } + + public Boolean isNamed(String oldName) { + return this.existing.containsKey(oldName); + } + + public Map existing() { + return existing; + } } \ No newline at end of file diff --git a/core/src/main/java/com/github/jsonldjava/impl/NQuadRDFParser.java b/core/src/main/java/com/github/jsonldjava/impl/NQuadRDFParser.java index 3f361dc2..5209cac3 100644 --- a/core/src/main/java/com/github/jsonldjava/impl/NQuadRDFParser.java +++ b/core/src/main/java/com/github/jsonldjava/impl/NQuadRDFParser.java @@ -1,16 +1,15 @@ package com.github.jsonldjava.impl; -import static com.github.jsonldjava.core.RDFDatasetUtils.parseNQuads; - import com.github.jsonldjava.core.JsonLdError; import com.github.jsonldjava.core.RDFDataset; +import com.github.jsonldjava.core.RDFDatasetUtils; import com.github.jsonldjava.core.RDFParser; public class NQuadRDFParser implements RDFParser { @Override public RDFDataset parse(Object input) throws JsonLdError { if (input instanceof String) { - return parseNQuads((String) input); + return RDFDatasetUtils.parseNQuads((String) input); } else { throw new JsonLdError(JsonLdError.Error.INVALID_INPUT, "NQuad Parser expected string input."); diff --git a/core/src/main/java/com/github/jsonldjava/impl/TurtleRDFParser.java b/core/src/main/java/com/github/jsonldjava/impl/TurtleRDFParser.java deleted file mode 100644 index ab2959e6..00000000 --- a/core/src/main/java/com/github/jsonldjava/impl/TurtleRDFParser.java +++ /dev/null @@ -1,565 +0,0 @@ -package com.github.jsonldjava.impl; - -import static com.github.jsonldjava.core.JsonLdConsts.RDF_FIRST; -import static com.github.jsonldjava.core.JsonLdConsts.RDF_LANGSTRING; -import static com.github.jsonldjava.core.JsonLdConsts.RDF_NIL; -import static com.github.jsonldjava.core.JsonLdConsts.RDF_REST; -import static com.github.jsonldjava.core.JsonLdConsts.RDF_TYPE; -import static com.github.jsonldjava.core.JsonLdConsts.XSD_BOOLEAN; -import static com.github.jsonldjava.core.JsonLdConsts.XSD_DECIMAL; -import static com.github.jsonldjava.core.JsonLdConsts.XSD_DOUBLE; -import static com.github.jsonldjava.core.JsonLdConsts.XSD_INTEGER; -import static com.github.jsonldjava.core.RDFDatasetUtils.unescape; -import static com.github.jsonldjava.core.Regex.BLANK_NODE_LABEL; -import static com.github.jsonldjava.core.Regex.DECIMAL; -import static com.github.jsonldjava.core.Regex.DOUBLE; -import static com.github.jsonldjava.core.Regex.INTEGER; -import static com.github.jsonldjava.core.Regex.IRIREF; -import static com.github.jsonldjava.core.Regex.LANGTAG; -import static com.github.jsonldjava.core.Regex.PNAME_LN; -import static com.github.jsonldjava.core.Regex.PNAME_NS; -import static com.github.jsonldjava.core.Regex.STRING_LITERAL_LONG_QUOTE; -import static com.github.jsonldjava.core.Regex.STRING_LITERAL_LONG_SINGLE_QUOTE; -import static com.github.jsonldjava.core.Regex.STRING_LITERAL_QUOTE; -import static com.github.jsonldjava.core.Regex.STRING_LITERAL_SINGLE_QUOTE; -import static com.github.jsonldjava.core.Regex.UCHAR; -import static com.github.jsonldjava.core.Regex.WS; -import static com.github.jsonldjava.core.Regex.WS_0_N; -import static com.github.jsonldjava.core.Regex.WS_1_N; - -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Stack; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import com.github.jsonldjava.core.JsonLdError; -import com.github.jsonldjava.core.RDFDataset; -import com.github.jsonldjava.core.RDFParser; -import com.github.jsonldjava.core.UniqueNamer; - -/** - * A (probably terribly slow) Parser for turtle. Turtle is the internal - * RDFDataset used by JSOND-Java - * - * TODO: this probably needs to be changed to use a proper parser/lexer - * - * @author Tristan - * - */ -public class TurtleRDFParser implements RDFParser { - - static class Regex { - final public static Pattern PREFIX_ID = Pattern.compile("@prefix" + WS_1_N + PNAME_NS - + WS_1_N + IRIREF + WS_0_N + "\\." + WS_0_N); - final public static Pattern BASE = Pattern.compile("@base" + WS_1_N + IRIREF + WS_0_N - + "\\." + WS_0_N); - final public static Pattern SPARQL_PREFIX = Pattern.compile("[Pp][Rr][Ee][Ff][Ii][Xx]" + WS - + PNAME_NS + WS + IRIREF + WS_0_N); - final public static Pattern SPARQL_BASE = Pattern.compile("[Bb][Aa][Ss][Ee]" + WS + IRIREF - + WS_0_N); - - final public static Pattern PREFIXED_NAME = Pattern.compile("(?:" + PNAME_LN + "|" - + PNAME_NS + ")"); - final public static Pattern IRI = Pattern.compile("(?:" + IRIREF + "|" + PREFIXED_NAME - + ")"); - final public static Pattern ANON = Pattern.compile("(?:\\[" + WS + "*\\])"); - final public static Pattern BLANK_NODE = Pattern.compile(BLANK_NODE_LABEL + "|" + ANON); - final public static Pattern STRING = Pattern.compile("(" + STRING_LITERAL_LONG_SINGLE_QUOTE - + "|" + STRING_LITERAL_LONG_QUOTE + "|" + STRING_LITERAL_QUOTE + "|" - + STRING_LITERAL_SINGLE_QUOTE + ")"); - final public static Pattern BOOLEAN_LITERAL = Pattern.compile("(true|false)"); - final public static Pattern RDF_LITERAL = Pattern.compile(STRING + "(?:" + LANGTAG - + "|\\^\\^" + IRI + ")?"); - final public static Pattern NUMERIC_LITERAL = Pattern.compile("(" + DOUBLE + ")|(" - + DECIMAL + ")|(" + INTEGER + ")"); - final public static Pattern LITERAL = Pattern.compile(RDF_LITERAL + "|" + NUMERIC_LITERAL - + "|" + BOOLEAN_LITERAL); - - final public static Pattern DIRECTIVE = Pattern.compile("^(?:" + PREFIX_ID + "|" + BASE - + "|" + SPARQL_PREFIX + "|" + SPARQL_BASE + ")"); - final public static Pattern SUBJECT = Pattern.compile("^" + IRI + "|" + BLANK_NODE); - final public static Pattern PREDICATE = Pattern.compile("^" + IRI + "|a" + WS_1_N); - final public static Pattern OBJECT = Pattern.compile("^" + IRI + "|" + BLANK_NODE + "|" - + LITERAL); - - // others - // final public static Pattern WS_AT_LINE_START = Pattern.compile("^" + - // WS_1_N); - final public static Pattern EOLN = Pattern.compile("(?:\r\n)|(?:\n)|(?:\r)"); - final public static Pattern NEXT_EOLN = Pattern.compile("^.*(?:" + EOLN + ")" + WS_0_N); - // final public static Pattern EMPTY_LINE = Pattern.compile("^" + WS + - // "*$"); - - final public static Pattern COMMENT_OR_WS = Pattern.compile("^(?:(?:[#].*(?:" + EOLN + ")" - + WS_0_N + ")|(?:" + WS_1_N + "))"); - } - - private class State { - String baseIri = ""; - Map namespaces = new LinkedHashMap(); - String curSubject = null; - String curPredicate = null; - - String line = null; - - int lineNumber = 0; - int linePosition = 0; - - // int bnodes = 0; - UniqueNamer namer = new UniqueNamer("_:b");// {{ getName(); }}; // call - // getName() after - // construction to make - // first active bnode _:b1 - - private final Stack> stack = new Stack>(); - public boolean expectingBnodeClose = false; - - public State(String input) throws JsonLdError { - line = input; - lineNumber = 1; - advanceLinePosition(0); - } - - public void push() { - stack.push(new LinkedHashMap() { - { - put(curSubject, curPredicate); - } - }); - expectingBnodeClose = true; - curSubject = null; - curPredicate = null; - } - - public void pop() { - if (stack.size() > 0) { - for (final Entry x : stack.pop().entrySet()) { - curSubject = x.getKey(); - curPredicate = x.getValue(); - } - } - if (stack.size() == 0) { - expectingBnodeClose = false; - } - } - - private void advanceLineNumber() throws JsonLdError { - final Matcher match = Regex.NEXT_EOLN.matcher(line); - if (match.find()) { - final String[] split = match.group(0).split("" + Regex.EOLN); - lineNumber += (split.length - 1); - linePosition += split[split.length - 1].length(); - line = line.substring(match.group(0).length()); - } - } - - public void advanceLinePosition(int len) throws JsonLdError { - if (len > 0) { - linePosition += len; - line = line.substring(len); - } - - while (!"".equals(line)) { - // clear any whitespace - final Matcher match = Regex.COMMENT_OR_WS.matcher(line); - if (match.find() && match.group(0).length() > 0) { - final Matcher eoln = Regex.EOLN.matcher(match.group(0)); - int end = 0; - while (eoln.find()) { - lineNumber += 1; - end = eoln.end(); - } - linePosition = match.group(0).length() - end; - line = line.substring(match.group(0).length()); - } else { - break; - } - } - if ("".equals(line) && !endIsOK()) { - throw new JsonLdError(JsonLdError.Error.PARSE_ERROR, - "Error while parsing Turtle; unexpected end of input. {line: " + lineNumber - + ", position:" + linePosition + "}"); - } - } - - private boolean endIsOK() { - return curSubject == null && stack.size() == 0; - } - - public String expandIRI(String ns, String name) throws JsonLdError { - if (namespaces.containsKey(ns)) { - return namespaces.get(ns) + name; - } else { - throw new JsonLdError(JsonLdError.Error.PARSE_ERROR, "No prefix found for: " + ns - + " {line: " + lineNumber + ", position:" + linePosition + "}"); - } - } - } - - @Override - public RDFDataset parse(Object input) throws JsonLdError { - if (!(input instanceof String)) { - throw new JsonLdError(JsonLdError.Error.INVALID_INPUT, - "Invalid input; Triple RDF Parser requires a string input"); - } - final RDFDataset result = new RDFDataset(); - final State state = new State((String) input); - - while (!"".equals(state.line)) { - // check if line is a directive - Matcher match = Regex.DIRECTIVE.matcher(state.line); - if (match.find()) { - if (match.group(1) != null || match.group(4) != null) { - final String ns = match.group(1) != null ? match.group(1) : match.group(4); - String iri = match.group(1) != null ? match.group(2) : match.group(5); - if (!iri.contains(":")) { - iri = state.baseIri + iri; - } - iri = unescape(iri); - validateIRI(state, iri); - state.namespaces.put(ns, iri); - result.setNamespace(ns, iri); - } else { - String base = match.group(3) != null ? match.group(3) : match.group(6); - base = unescape(base); - validateIRI(state, base); - if (!base.contains(":")) { - state.baseIri = state.baseIri + base; - } else { - state.baseIri = base; - } - } - state.advanceLinePosition(match.group(0).length()); - continue; - } - - if (state.curSubject == null) { - // we need to match a subject - match = Regex.SUBJECT.matcher(state.line); - if (match.find()) { - String iri; - if (match.group(1) != null) { - // matched IRI - iri = unescape(match.group(1)); - if (!iri.contains(":")) { - iri = state.baseIri + iri; - } - } else if (match.group(2) != null) { - // matched NS:NAME - final String ns = match.group(2); - final String name = unescapeReserved(match.group(3)); - iri = state.expandIRI(ns, name); - } else if (match.group(4) != null) { - // match ns: only - iri = state.expandIRI(match.group(4), ""); - } else if (match.group(5) != null) { - // matched BNODE - iri = state.namer.getName(match.group(0).trim()); - } else { - // matched anon node - iri = state.namer.getName(); - } - // make sure IRI still matches an IRI after escaping - validateIRI(state, iri); - state.curSubject = iri; - state.advanceLinePosition(match.group(0).length()); - } - // handle blank nodes - else if (state.line.startsWith("[")) { - final String bnode = state.namer.getName(); - state.advanceLinePosition(1); - state.push(); - state.curSubject = bnode; - } - // handle collections - else if (state.line.startsWith("(")) { - final String bnode = state.namer.getName(); - // so we know we want a predicate if the collection close - // isn't followed by a subject end - state.curSubject = bnode; - state.advanceLinePosition(1); - state.push(); - state.curSubject = bnode; - state.curPredicate = RDF_FIRST; - } - // make sure we have a subject already - else { - throw new JsonLdError(JsonLdError.Error.PARSE_ERROR, - "Error while parsing Turtle; missing expected subject. {line: " - + state.lineNumber + "position: " + state.linePosition + "}"); - } - } - - if (state.curPredicate == null) { - // match predicate - match = Regex.PREDICATE.matcher(state.line); - if (match.find()) { - String iri = ""; - if (match.group(1) != null) { - // matched IRI - iri = unescape(match.group(1)); - if (!iri.contains(":")) { - iri = state.baseIri + iri; - } - } else if (match.group(2) != null) { - // matched NS:NAME - final String ns = match.group(2); - final String name = unescapeReserved(match.group(3)); - iri = state.expandIRI(ns, name); - } else if (match.group(4) != null) { - // matched ns: - iri = state.expandIRI(match.group(4), ""); - } else { - // matched "a" - iri = RDF_TYPE; - } - validateIRI(state, iri); - state.curPredicate = iri; - state.advanceLinePosition(match.group(0).length()); - } else { - throw new JsonLdError(JsonLdError.Error.PARSE_ERROR, - "Error while parsing Turtle; missing expected predicate. {line: " - + state.lineNumber + "position: " + state.linePosition + "}"); - } - } - - // expecting bnode or object - - // match BNODE values - if (state.line.startsWith("[")) { - final String bnode = state.namer.getName(); - result.addTriple(state.curSubject, state.curPredicate, bnode); - state.advanceLinePosition(1); - // check for anonymous objects - if (state.line.startsWith("]")) { - state.advanceLinePosition(1); - // next we expect a statement or object separator - } - // otherwise we're inside the blank node - else { - state.push(); - state.curSubject = bnode; - // next we expect a predicate - continue; - } - } - // match collections - else if (state.line.startsWith("(")) { - state.advanceLinePosition(1); - // check for empty collection - if (state.line.startsWith(")")) { - state.advanceLinePosition(1); - result.addTriple(state.curSubject, state.curPredicate, RDF_NIL); - // next we expect a statement or object separator - } - // otherwise we're inside the collection - else { - final String bnode = state.namer.getName(); - result.addTriple(state.curSubject, state.curPredicate, bnode); - state.push(); - state.curSubject = bnode; - state.curPredicate = RDF_FIRST; - continue; - } - } else { - // match object - match = Regex.OBJECT.matcher(state.line); - if (match.find()) { - String iri = null; - if (match.group(1) != null) { - // matched IRI - iri = unescape(match.group(1)); - if (!iri.contains(":")) { - iri = state.baseIri + iri; - } - } else if (match.group(2) != null) { - // matched NS:NAME - final String ns = match.group(2); - final String name = unescapeReserved(match.group(3)); - iri = state.expandIRI(ns, name); - } else if (match.group(4) != null) { - // matched ns: - iri = state.expandIRI(match.group(4), ""); - } else if (match.group(5) != null) { - // matched BNODE - iri = state.namer.getName(match.group(0).trim()); - } - if (iri != null) { - validateIRI(state, iri); - // we have a object - result.addTriple(state.curSubject, state.curPredicate, iri); - } else { - // we have a literal - String value = match.group(6); - String lang = null; - String datatype = null; - if (value != null) { - // we have a string literal - value = unquoteString(value); - value = unescape(value); - lang = match.group(7); - if (lang == null) { - if (match.group(8) != null) { - datatype = unescape(match.group(8)); - if (!datatype.contains(":")) { - datatype = state.baseIri + datatype; - } - validateIRI(state, datatype); - } else if (match.group(9) != null) { - datatype = state.expandIRI(match.group(9), - unescapeReserved(match.group(10))); - } else if (match.group(11) != null) { - datatype = state.expandIRI(match.group(11), ""); - } - } else { - datatype = RDF_LANGSTRING; - } - } else if (match.group(12) != null) { - // integer literal - value = match.group(12); - datatype = XSD_DOUBLE; - } else if (match.group(13) != null) { - // decimal literal - value = match.group(13); - datatype = XSD_DECIMAL; - } else if (match.group(14) != null) { - // double literal - value = match.group(14); - datatype = XSD_INTEGER; - } else if (match.group(15) != null) { - // boolean literal - value = match.group(15); - datatype = XSD_BOOLEAN; - } - result.addTriple(state.curSubject, state.curPredicate, value, datatype, - lang); - } - state.advanceLinePosition(match.group(0).length()); - } else { - throw new JsonLdError(JsonLdError.Error.PARSE_ERROR, - "Error while parsing Turtle; missing expected object or blank node. {line: " - + state.lineNumber + "position: " + state.linePosition + "}"); - } - } - - // close collection - boolean collectionClosed = false; - while (state.line.startsWith(")")) { - if (!RDF_FIRST.equals(state.curPredicate)) { - throw new JsonLdError(JsonLdError.Error.PARSE_ERROR, - "Error while parsing Turtle; unexpected ). {line: " + state.lineNumber - + "position: " + state.linePosition + "}"); - } - result.addTriple(state.curSubject, RDF_REST, RDF_NIL); - state.pop(); - state.advanceLinePosition(1); - collectionClosed = true; - } - - boolean expectDotOrPred = false; - - // match end of bnode - if (state.line.startsWith("]")) { - final String bnode = state.curSubject; - state.pop(); - state.advanceLinePosition(1); - if (state.curSubject == null) { - // this is a bnode as a subject and we - // expect either a . or a predicate - state.curSubject = bnode; - expectDotOrPred = true; - } - } - - // match list separator - if (!expectDotOrPred && state.line.startsWith(",")) { - state.advanceLinePosition(1); - // now we expect another object/bnode - continue; - } - - // match predicate end - if (!expectDotOrPred) { - while (state.line.startsWith(";")) { - state.curPredicate = null; - state.advanceLinePosition(1); - // now we expect another predicate, or a dot - expectDotOrPred = true; - } - } - - if (state.line.startsWith(".")) { - if (state.expectingBnodeClose) { - throw new JsonLdError(JsonLdError.Error.PARSE_ERROR, - "Error while parsing Turtle; missing expected )\"]\". {line: " - + state.lineNumber + "position: " + state.linePosition + "}"); - } - state.curSubject = null; - state.curPredicate = null; - state.advanceLinePosition(1); - // this can now be the end of the document. - continue; - } else if (expectDotOrPred) { - // we're expecting another predicate since we didn't find a dot - continue; - } - - // if we're in a collection - if (RDF_FIRST.equals(state.curPredicate)) { - final String bnode = state.namer.getName(); - result.addTriple(state.curSubject, RDF_REST, bnode); - state.curSubject = bnode; - continue; - } - - if (collectionClosed) { - // we expect another object - // TODO: it's not clear yet if this is valid - continue; - } - - // if we get here, we're missing a close statement - throw new JsonLdError(JsonLdError.Error.PARSE_ERROR, - "Error while parsing Turtle; missing expected \"]\" \",\" \";\" or \".\". {line: " - + state.lineNumber + "position: " + state.linePosition + "}"); - } - - return result; - } - - final public static Pattern IRIREF_MINUS_CONTAINER = Pattern - .compile("(?:(?:[^\\x00-\\x20<>\"{}|\\^`\\\\]|" + UCHAR + ")*)|" + Regex.PREFIXED_NAME); - - private void validateIRI(State state, String iri) throws JsonLdError { - if (!IRIREF_MINUS_CONTAINER.matcher(iri).matches()) { - throw new JsonLdError(JsonLdError.Error.PARSE_ERROR, - "Error while parsing Turtle; invalid IRI after escaping. {line: " - + state.lineNumber + "position: " + state.linePosition + "}"); - } - } - - final private static Pattern PN_LOCAL_ESC_MATCHED = Pattern - .compile("[\\\\]([_~\\.\\-!$&'\\(\\)*+,;=/?#@%])"); - - static String unescapeReserved(String str) { - if (str != null) { - final Matcher m = PN_LOCAL_ESC_MATCHED.matcher(str); - if (m.find()) { - return m.replaceAll("$1"); - } - } - return str; - } - - private String unquoteString(String value) { - if (value.startsWith("\"\"\"") || value.startsWith("'''")) { - return value.substring(3, value.length() - 3); - } else if (value.startsWith("\"") || value.startsWith("'")) { - return value.substring(1, value.length() - 1); - } - return value; - } - -} diff --git a/core/src/main/java/com/github/jsonldjava/impl/TurtleTripleCallback.java b/core/src/main/java/com/github/jsonldjava/impl/TurtleTripleCallback.java deleted file mode 100644 index 92e4aad7..00000000 --- a/core/src/main/java/com/github/jsonldjava/impl/TurtleTripleCallback.java +++ /dev/null @@ -1,375 +0,0 @@ -package com.github.jsonldjava.impl; - -import static com.github.jsonldjava.core.JsonLdConsts.RDF_FIRST; -import static com.github.jsonldjava.core.JsonLdConsts.RDF_NIL; -import static com.github.jsonldjava.core.JsonLdConsts.RDF_REST; -import static com.github.jsonldjava.core.JsonLdConsts.XSD_BOOLEAN; -import static com.github.jsonldjava.core.JsonLdConsts.XSD_DOUBLE; -import static com.github.jsonldjava.core.JsonLdConsts.XSD_FLOAT; -import static com.github.jsonldjava.core.JsonLdConsts.XSD_INTEGER; -import static com.github.jsonldjava.core.JsonLdConsts.XSD_STRING; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; - -import com.github.jsonldjava.core.JsonLdTripleCallback; -import com.github.jsonldjava.core.RDFDataset; - -public class TurtleTripleCallback implements JsonLdTripleCallback { - - private static final int MAX_LINE_LENGTH = 160; - private static final int TAB_SPACES = 4; - private static final String COLS_KEY = "..cols.."; // this shouldn't be a - // valid iri/bnode i - // hope! - final Map availableNamespaces = new LinkedHashMap() { - { - // TODO: fill with default namespaces - } - }; - Set usedNamespaces; - - public TurtleTripleCallback() { - } - - @Override - public Object call(RDFDataset dataset) { - for (final Entry e : dataset.getNamespaces().entrySet()) { - availableNamespaces.put(e.getValue(), e.getKey()); - } - usedNamespaces = new LinkedHashSet(); - - final int tabs = 0; - - final Map> refs = new LinkedHashMap>(); - final Map>> ttl = new LinkedHashMap>>(); - - for (String graphName : dataset.keySet()) { - final List triples = dataset.getQuads(graphName); - if ("@default".equals(graphName)) { - graphName = null; - } - - // http://www.w3.org/TR/turtle/#unlabeled-bnodes - // TODO: implement nesting for unlabled nodes - - // map of what the output should look like - // subj (or [ if bnode) > pred > obj - // > obj (set ref if IRI) - // > pred > obj (set ref if bnode) - // subj > etc etc etc - - // subjid -> [ ref, ref, ref ] - - String prevSubject = ""; - String prevPredicate = ""; - - Map> thisSubject = null; - List thisPredicate = null; - - for (final RDFDataset.Quad triple : triples) { - final String subject = triple.getSubject().getValue(); - final String predicate = triple.getPredicate().getValue(); - - if (prevSubject.equals(subject)) { - if (prevPredicate.equals(predicate)) { - // nothing to do - } else { - // new predicate - if (thisSubject.containsKey(predicate)) { - thisPredicate = thisSubject.get(predicate); - } else { - thisPredicate = new ArrayList(); - thisSubject.put(predicate, thisPredicate); - } - prevPredicate = predicate; - } - } else { - // new subject - if (ttl.containsKey(subject)) { - thisSubject = ttl.get(subject); - } else { - thisSubject = new LinkedHashMap>(); - ttl.put(subject, thisSubject); - } - if (thisSubject.containsKey(predicate)) { - thisPredicate = thisSubject.get(predicate); - } else { - thisPredicate = new ArrayList(); - thisSubject.put(predicate, thisPredicate); - } - - prevSubject = subject; - prevPredicate = predicate; - } - - if (triple.getObject().isLiteral()) { - thisPredicate.add(triple.getObject()); - } else { - final String o = triple.getObject().getValue(); - if (o.startsWith("_:")) { - // add ref to o - if (!refs.containsKey(o)) { - refs.put(o, new ArrayList()); - } - refs.get(o).add(thisPredicate); - } - thisPredicate.add(o); - } - } - } - - final Map> collections = new LinkedHashMap>(); - - final List subjects = new ArrayList(ttl.keySet()); - // find collections - for (final String subj : subjects) { - Map> preds = ttl.get(subj); - if (preds != null && preds.containsKey(RDF_FIRST)) { - final List col = new ArrayList(); - collections.put(subj, col); - while (true) { - final List first = preds.remove(RDF_FIRST); - final Object o = first.get(0); - col.add(o); - // refs - if (refs.containsKey(o)) { - refs.get(o).remove(first); - refs.get(o).add(col); - } - final String next = (String) preds.remove(RDF_REST).get(0); - if (RDF_NIL.equals(next)) { - // end of this list - break; - } - // if collections already contains a value for "next", add - // it to this col and break out - if (collections.containsKey(next)) { - col.addAll(collections.remove(next)); - break; - } - preds = ttl.remove(next); - refs.remove(next); - } - } - } - - // process refs (nesting referenced bnodes if only one reference to them - // in the whole graph) - for (final String id : refs.keySet()) { - // skip items if there is more than one reference to them in the - // graph - if (refs.get(id).size() > 1) { - continue; - } - - // otherwise embed them into the referenced location - Object object = ttl.remove(id); - if (collections.containsKey(id)) { - object = new LinkedHashMap>(); - final List tmp = new ArrayList(); - tmp.add(collections.remove(id)); - ((HashMap) object).put(COLS_KEY, tmp); - } - final List predicate = (List) refs.get(id).get(0); - // replace the one bnode ref with the object - predicate.set(predicate.lastIndexOf(id), object); - } - - // replace the rest of the collections - for (final String id : collections.keySet()) { - final Map> subj = ttl.get(id); - if (!subj.containsKey(COLS_KEY)) { - subj.put(COLS_KEY, new ArrayList()); - } - subj.get(COLS_KEY).add(collections.get(id)); - } - - // build turtle output - final String output = generateTurtle(ttl, 0, 0, false); - - String prefixes = ""; - for (final String prefix : usedNamespaces) { - final String name = availableNamespaces.get(prefix); - prefixes += "@prefix " + name + ": <" + prefix + "> .\n"; - } - - return ("".equals(prefixes) ? "" : prefixes + "\n") + output; - } - - private String generateObject(Object object, String sep, boolean hasNext, int indentation, - int lineLength) { - String rval = ""; - String obj; - if (object instanceof String) { - obj = getURI((String) object); - } else if (object instanceof RDFDataset.Literal) { - obj = ((RDFDataset.Literal) object).getValue(); - final String lang = ((RDFDataset.Literal) object).getLanguage(); - final String dt = ((RDFDataset.Literal) object).getDatatype(); - if (lang != null) { - obj = "\"" + obj + "\""; - obj += "@" + lang; - } else if (dt != null) { - // TODO: this probably isn't an exclusive list of all the - // datatype literals that can be represented as native types - if (!(XSD_DOUBLE.equals(dt) || XSD_INTEGER.equals(dt) || XSD_FLOAT.equals(dt) || XSD_BOOLEAN - .equals(dt))) { - obj = "\"" + obj + "\""; - if (!XSD_STRING.equals(dt)) { - obj += "^^" + getURI(dt); - } - } - } else { - obj = "\"" + obj + "\""; - } - } else { - // must be an object - final Map>> tmp = new LinkedHashMap>>(); - tmp.put("_:x", (Map>) object); - obj = generateTurtle(tmp, indentation + 1, lineLength, true); - } - - final int idxofcr = obj.indexOf("\n"); - // check if output will fix in the max line length (factor in comma if - // not the last item, current line length and length to the next CR) - if ((hasNext ? 1 : 0) + lineLength + (idxofcr != -1 ? idxofcr : obj.length()) > MAX_LINE_LENGTH) { - rval += "\n" + tabs(indentation + 1); - lineLength = (indentation + 1) * TAB_SPACES; - } - rval += obj; - if (idxofcr != -1) { - lineLength += (obj.length() - obj.lastIndexOf("\n")); - } else { - lineLength += obj.length(); - } - if (hasNext) { - rval += sep; - lineLength += sep.length(); - if (lineLength < MAX_LINE_LENGTH) { - rval += " "; - lineLength++; - } else { - rval += "\n"; - } - } - return rval; - } - - private String generateTurtle(Map>> ttl, int indentation, - int lineLength, boolean isObject) { - String rval = ""; - final Iterator subjIter = ttl.keySet().iterator(); - while (subjIter.hasNext()) { - final String subject = subjIter.next(); - final Map> subjval = ttl.get(subject); - // boolean isBlankNode = subject.startsWith("_:"); - boolean hasOpenBnodeBracket = false; - if (subject.startsWith("_:")) { - // only open blank node bracket the node doesn't contain any - // collections - if (!subjval.containsKey(COLS_KEY)) { - rval += "[ "; - lineLength += 2; - hasOpenBnodeBracket = true; - } - - // TODO: according to http://www.rdfabout.com/demo/validator/ - // 1) collections as objects cannot contain any predicates other - // than rdf:first and rdf:rest - // 2) collections cannot be surrounded with [ ] - - // check for collection - if (subjval.containsKey(COLS_KEY)) { - final List collections = subjval.remove(COLS_KEY); - for (final Object collection : collections) { - rval += "( "; - lineLength += 2; - final Iterator objIter = ((List) collection).iterator(); - while (objIter.hasNext()) { - final Object object = objIter.next(); - rval += generateObject(object, "", objIter.hasNext(), indentation, - lineLength); - lineLength = rval.length() - rval.lastIndexOf("\n"); - } - rval += " ) "; - lineLength += 3; - } - } - // check for blank node - } else { - rval += getURI(subject) + " "; - lineLength += subject.length() + 1; - } - final Iterator predIter = ttl.get(subject).keySet().iterator(); - while (predIter.hasNext()) { - final String predicate = predIter.next(); - rval += getURI(predicate) + " "; - lineLength += predicate.length() + 1; - final Iterator objIter = ttl.get(subject).get(predicate).iterator(); - while (objIter.hasNext()) { - final Object object = objIter.next(); - rval += generateObject(object, ",", objIter.hasNext(), indentation, lineLength); - lineLength = rval.length() - rval.lastIndexOf("\n"); - } - if (predIter.hasNext()) { - rval += " ;\n" + tabs(indentation + 1); - lineLength = (indentation + 1) * TAB_SPACES; - } - } - if (hasOpenBnodeBracket) { - rval += " ]"; - } - if (!isObject) { - rval += " .\n"; - if (subjIter.hasNext()) { // add blank space if we have another - // object below this - rval += "\n"; - } - } - } - return rval; - } - - // TODO: Assert (TAB_SPACES == 4) otherwise this needs to be edited, and - // should fail to compile - private String tabs(int tabs) { - String rval = ""; - for (int i = 0; i < tabs; i++) { - rval += " "; // using spaces for tabs - } - return rval; - } - - /** - * checks the URI for a prefix, and if one is found, set used prefixes to - * true - * - * @param predicate - * @return - */ - private String getURI(String uri) { - // check for bnode - if (uri.startsWith("_:")) { - // return the bnode id - return uri; - } - for (final String prefix : availableNamespaces.keySet()) { - if (uri.startsWith(prefix)) { - usedNamespaces.add(prefix); - // return the prefixed URI - return availableNamespaces.get(prefix) + ":" + uri.substring(prefix.length()); - } - } - // return the full URI - return "<" + uri + ">"; - } - -} diff --git a/core/src/main/java/com/github/jsonldjava/utils/JarCacheResource.java b/core/src/main/java/com/github/jsonldjava/utils/JarCacheResource.java index 216a942c..5c76f8d4 100644 --- a/core/src/main/java/com/github/jsonldjava/utils/JarCacheResource.java +++ b/core/src/main/java/com/github/jsonldjava/utils/JarCacheResource.java @@ -23,9 +23,7 @@ public JarCacheResource(URL classpath) throws IOException { @Override public long length() { - // TODO should be getContentLengthLong() but this is not available in - // Java 6. - return connection.getContentLength(); + return connection.getContentLengthLong(); } @Override diff --git a/core/src/main/java/com/github/jsonldjava/utils/JarCacheStorage.java b/core/src/main/java/com/github/jsonldjava/utils/JarCacheStorage.java index 35ecce90..137daa74 100644 --- a/core/src/main/java/com/github/jsonldjava/utils/JarCacheStorage.java +++ b/core/src/main/java/com/github/jsonldjava/utils/JarCacheStorage.java @@ -1,18 +1,19 @@ package com.github.jsonldjava.utils; import java.io.IOException; -import java.lang.ref.SoftReference; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; +import java.util.Collections; import java.util.Date; -import java.util.Enumeration; import java.util.Iterator; import java.util.List; -import java.util.concurrent.ConcurrentHashMap; +import java.util.Objects; +import java.util.Optional; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutionException; import org.apache.http.Header; import org.apache.http.HttpVersion; @@ -22,79 +23,90 @@ import org.apache.http.client.cache.HttpCacheUpdateCallback; import org.apache.http.client.cache.HttpCacheUpdateException; import org.apache.http.client.cache.Resource; +import org.apache.http.client.utils.DateUtils; import org.apache.http.impl.client.cache.BasicHttpCacheStorage; import org.apache.http.impl.client.cache.CacheConfig; -import org.apache.http.impl.cookie.DateUtils; import org.apache.http.message.BasicHeader; import org.apache.http.message.BasicStatusLine; import org.apache.http.protocol.HTTP; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; - +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.MapMaker; + +/** + * Implementation of the Apache HttpClient {@link HttpCacheStorage} interface + * using {@code jarcache.json} files on the classpath to identify static JSON-LD + * resources on the classpath, to avoid retrieving them. + * + * @author Stian Soiland-Reyes + * @author Peter Ansell p_ansell@yahoo.com + */ public class JarCacheStorage implements HttpCacheStorage { + /** + * The classpath location that is searched inside of the classloader set for + * this cache. Note this search is also done on the Thread + * contextClassLoader if none is explicitly set, and the System classloader + * if there is no contextClassLoader. + */ private static final String JARCACHE_JSON = "jarcache.json"; private final Logger log = LoggerFactory.getLogger(getClass()); private final CacheConfig cacheConfig; - // private final CacheConfig cacheConfig = new CacheConfig(); - private ClassLoader classLoader; /** - * All live caching that is not found locally is delegated to this - * implementation. + * The classloader to use, defaults to null which will use the thread + * context classloader. */ - private HttpCacheStorage delegate; - - ObjectMapper mapper = new ObjectMapper(); + private ClassLoader classLoader = null; /** - * Map from uri of jarcache.json (e.g. jar://blab.jar!jarcache.json) to a - * SoftReference to its content as JsonNode. + * A holder for the case where the System class loader needs to be used, but + * cannot be directly identified in another way. * - * @see #getJarCache(URL) + * Used as a key in cachedResourceList. */ - protected ConcurrentMap> jarCaches = new ConcurrentHashMap>(); + private static final Object NULL_CLASS_LOADER = new Object(); - public ClassLoader getClassLoader() { - if (classLoader != null) { - return classLoader; - } - return Thread.currentThread().getContextClassLoader(); - } + /** + * All live caching that is not found locally is delegated to this + * implementation. + */ + private final HttpCacheStorage delegate; - public void setClassLoader(ClassLoader classLoader) { - this.classLoader = classLoader; - } + private final ObjectMapper mapper = new ObjectMapper(); /** - * @deprecated Use - * {@link JarCacheStorage#JarCacheStorage(ClassLoader, CacheConfig)} - * instead. + * Map from uri of jarcache.json (e.g. jar://blab.jar!jarcache.json) to a + * SoftReference to its parsed content as JsonNode. + * + * @see #getJarCache(URL) */ - @Deprecated - public JarCacheStorage() { - this(null, CacheConfig.DEFAULT); - } + private final LoadingCache jarCaches = CacheBuilder.newBuilder() + .concurrencyLevel(4).maximumSize(100).softValues() + .build(new CacheLoader() { + @Override + public JsonNode load(URL url) throws IOException { + return mapper.readTree(url); + } + }); /** - * - * @param classLoader - * The ClassLoader to use to locate JAR files and resources, or - * null to use the Thread context class loader in each case. - * @deprecated Use - * {@link JarCacheStorage#JarCacheStorage(ClassLoader, CacheConfig)} - * instead. + * Cached URLs from the given ClassLoader to identified locations of + * jarcache.json resources on the classpath + * + * Uses a Guava concurrent weak reference key map to avoid holding onto + * ClassLoader instances after they are otherwise unavailable. */ - @Deprecated - public JarCacheStorage(ClassLoader classLoader) { - this(classLoader, CacheConfig.DEFAULT); - } + private static final ConcurrentMap> cachedResourceList = new MapMaker() + .concurrencyLevel(4).weakKeys().makeMap(); public JarCacheStorage(ClassLoader classLoader, CacheConfig cacheConfig) { this(classLoader, cacheConfig, new BasicHttpCacheStorage(cacheConfig)); @@ -103,8 +115,29 @@ public JarCacheStorage(ClassLoader classLoader, CacheConfig cacheConfig) { public JarCacheStorage(ClassLoader classLoader, CacheConfig cacheConfig, HttpCacheStorage delegate) { setClassLoader(classLoader); - this.cacheConfig = cacheConfig; - this.delegate = delegate; + this.cacheConfig = Objects.requireNonNull(cacheConfig, "Cache config cannot be null"); + this.delegate = Objects.requireNonNull(delegate, "Delegate cannot be null"); + } + + public ClassLoader getClassLoader() { + final ClassLoader nextClassLoader = classLoader; + if (nextClassLoader != null) { + return nextClassLoader; + } + return Thread.currentThread().getContextClassLoader(); + } + + /** + * Sets the ClassLoader used internally to a new value, or null to use + * {@link Thread#currentThread()} and {@link Thread#getContextClassLoader()} + * for each access. + * + * @param classLoader + * The classloader to use, or null to use the thread context + * classloader + */ + public void setClassLoader(ClassLoader classLoader) { + this.classLoader = classLoader; } @Override @@ -114,34 +147,48 @@ public void putEntry(String key, HttpCacheEntry entry) throws IOException { @Override public HttpCacheEntry getEntry(String key) throws IOException { - log.trace("Requesting " + key); - URI requestedUri; + log.trace("Requesting {}", key); + Optional parsedUri = Optional.empty(); try { - requestedUri = new URI(key); + parsedUri = Optional.of(new URI(key)); } catch (final URISyntaxException e) { - return null; + // Ignore, will delegate this request } - if ((requestedUri.getScheme().equals("http") && requestedUri.getPort() == 80) - || (requestedUri.getScheme().equals("https") && requestedUri.getPort() == 443)) { - // Strip away default http ports - try { - requestedUri = new URI(requestedUri.getScheme(), requestedUri.getHost(), - requestedUri.getPath(), requestedUri.getFragment()); - } catch (final URISyntaxException e) { + if (parsedUri.isPresent()) { + URI requestedUri = parsedUri.get(); + if ((requestedUri.getScheme().equals("http") && requestedUri.getPort() == 80) + || (requestedUri.getScheme().equals("https") + && requestedUri.getPort() == 443)) { + // Strip away default http ports + try { + requestedUri = new URI(requestedUri.getScheme(), requestedUri.getHost(), + requestedUri.getPath(), requestedUri.getFragment()); + } catch (final URISyntaxException e) { + if (log.isTraceEnabled()) { + log.trace("Failed to normalise URI port before looking in cache: " + + requestedUri, e); + } + // Ignore syntax error and use the original URI directly + // instead + // This shouldn't happen as we already attempted to parse + // the URI earlier and + // would not come here if that failed + } } - } - - final Enumeration jarcaches = getResources(); - while (jarcaches.hasMoreElements()) { - final URL url = jarcaches.nextElement(); - - final JsonNode tree = getJarCache(url); - // TODO: Cache tree per URL - for (final JsonNode node : tree) { - final URI uri = URI.create(node.get("Content-Location").asText()); - if (uri.equals(requestedUri)) { - return cacheEntry(requestedUri, url, node); + // getResources uses a cache to avoid scanning the classpath again + // for the + // current classloader + for (final URL url : getResources()) { + // getJarCache attempts to use already parsed in-memory + // locations to avoid + // retrieving and parsing again + final JsonNode tree = getJarCache(url); + for (final JsonNode node : tree) { + final URI uri = URI.create(node.get("Content-Location").asText()); + if (uri.equals(requestedUri)) { + return cacheEntry(requestedUri, url, node); + } } } } @@ -150,60 +197,58 @@ public HttpCacheEntry getEntry(String key) throws IOException { return delegate.getEntry(key); } - private Enumeration getResources() throws IOException { + /** + * Get all of the {@code jarcache.json} resources that exist on the + * classpath + * + * @return A cached list of jarcache.json classpath resources as + * {@link URL}s + * @throws IOException + * If there was an IO error while scanning the classpath + */ + private List getResources() throws IOException { final ClassLoader cl = getClassLoader(); - if (cl != null) { - return cl.getResources(JARCACHE_JSON); - } else { - return ClassLoader.getSystemResources(JARCACHE_JSON); - } - } - protected JsonNode getJarCache(URL url) throws IOException, JsonProcessingException { + // ConcurrentHashMap doesn't support null keys, so substitute a pseudo + // key + final Object key = cl == null ? NULL_CLASS_LOADER : cl; - URI uri; - try { - uri = url.toURI(); - } catch (final URISyntaxException e) { - throw new IllegalArgumentException("Invalid jarCache URI " + url, e); + // computeIfAbsent requires unchecked exceptions for the creation + // process, so we + // cannot easily use it directly, instead using get and putIfAbsent + List newValue = cachedResourceList.get(key); + if (newValue != null) { + return newValue; } - // Check if we have one from before - we'll use SoftReference so that - // the maps reference is not counted for garbage collection purposes - final SoftReference jarCacheRef = jarCaches.get(uri); - if (jarCacheRef != null) { - final JsonNode jarCache = jarCacheRef.get(); - if (jarCache != null) { - return jarCache; - } else { - jarCaches.remove(uri); - } + if (cl != null) { + newValue = Collections + .unmodifiableList(Collections.list(cl.getResources(JARCACHE_JSON))); + } else { + newValue = Collections.unmodifiableList( + Collections.list(ClassLoader.getSystemResources(JARCACHE_JSON))); } - // Only parse again if the optimistic get failed - final JsonNode tree = mapper.readTree(url); - // Use putIfAbsent to ensure concurrent reads do not return different - // JsonNode objects, for memory management purposes - final SoftReference putIfAbsent = jarCaches.putIfAbsent(uri, - new SoftReference(tree)); - if (putIfAbsent != null) { - final JsonNode returnValue = putIfAbsent.get(); - if (returnValue != null) { - return returnValue; - } else { - // Force update the reference if the existing reference had - // been garbage collected - jarCaches.put(uri, new SoftReference(tree)); - } + final List oldValue = cachedResourceList.putIfAbsent(key, newValue); + // We are not synchronising access to the ConcurrentMap, so if there + // were + // multiple classpath scans, we always choose the first one + return oldValue != null ? oldValue : newValue; + } + + protected JsonNode getJarCache(URL url) throws IOException { + try { + return jarCaches.get(url); + } catch (final ExecutionException e) { + throw new IOException("Failed to retrieve jar cache for URL: " + url, e); } - return tree; } protected HttpCacheEntry cacheEntry(URI requestedUri, URL baseURL, JsonNode cacheNode) throws MalformedURLException, IOException { final URL classpath = new URL(baseURL, cacheNode.get("X-Classpath").asText()); - log.debug("Cache hit for " + requestedUri); - log.trace("{}", cacheNode); + log.debug("Cache hit for: {}", requestedUri); + log.trace("Parsed cache entry: {}", cacheNode); final List
responseHeaders = new ArrayList
(); if (!cacheNode.has(HTTP.DATE_HEADER)) { @@ -219,12 +264,14 @@ protected HttpCacheEntry cacheEntry(URI requestedUri, URL baseURL, JsonNode cach while (fieldNames.hasNext()) { final String headerName = fieldNames.next(); final JsonNode header = cacheNode.get(headerName); - // TODO: Support multiple headers with [] - responseHeaders.add(new BasicHeader(headerName, header.asText())); + if (header != null) { + responseHeaders.add(new BasicHeader(headerName, header.asText())); + } } - return new HttpCacheEntry(new Date(), new Date(), new BasicStatusLine(HttpVersion.HTTP_1_1, - 200, "OK"), responseHeaders.toArray(new Header[0]), resource); + return new HttpCacheEntry(new Date(), new Date(), + new BasicStatusLine(HttpVersion.HTTP_1_1, 200, "OK"), + responseHeaders.toArray(new Header[0]), resource); } @Override @@ -233,8 +280,8 @@ public void removeEntry(String key) throws IOException { } @Override - public void updateEntry(String key, HttpCacheUpdateCallback callback) throws IOException, - HttpCacheUpdateException { + public void updateEntry(String key, HttpCacheUpdateCallback callback) + throws IOException, HttpCacheUpdateException { delegate.updateEntry(key, callback); } diff --git a/core/src/main/java/com/github/jsonldjava/utils/JsonLdUrl.java b/core/src/main/java/com/github/jsonldjava/utils/JsonLdUrl.java index b7a316c4..af5da444 100755 --- a/core/src/main/java/com/github/jsonldjava/utils/JsonLdUrl.java +++ b/core/src/main/java/com/github/jsonldjava/utils/JsonLdUrl.java @@ -1,315 +1,328 @@ -package com.github.jsonldjava.utils; - -import java.net.URI; -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class JsonLdUrl { - - public String href = ""; - public String protocol = ""; - public String host = ""; - public String auth = ""; - public String user = ""; - public String password = ""; - public String hostname = ""; - public String port = ""; - public String relative = ""; - public String path = ""; - public String directory = ""; - public String file = ""; - public String query = ""; - public String hash = ""; - - // things not populated by the regex (NOTE: i don't think it matters if - // these are null or "" to start with) - public String pathname = null; - public String normalizedPath = null; - public String authority = null; - - private static Pattern parser = Pattern - .compile("^(?:([^:\\/?#]+):)?(?:\\/\\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\\/?#]*)(?::(\\d*))?))?((((?:[^?#\\/]*\\/)*)([^?#]*))(?:\\?([^#]*))?(?:#(.*))?)"); - - public static JsonLdUrl parse(String url) { - final JsonLdUrl rval = new JsonLdUrl(); - rval.href = url; - - final Matcher matcher = parser.matcher(url); - if (matcher.matches()) { - if (matcher.group(1) != null) { - rval.protocol = matcher.group(1); - } - if (matcher.group(2) != null) { - rval.host = matcher.group(2); - } - if (matcher.group(3) != null) { - rval.auth = matcher.group(3); - } - if (matcher.group(4) != null) { - rval.user = matcher.group(4); - } - if (matcher.group(5) != null) { - rval.password = matcher.group(5); - } - if (matcher.group(6) != null) { - rval.hostname = matcher.group(6); - } - if (matcher.group(7) != null) { - rval.port = matcher.group(7); - } - if (matcher.group(8) != null) { - rval.relative = matcher.group(8); - } - if (matcher.group(9) != null) { - rval.path = matcher.group(9); - } - if (matcher.group(10) != null) { - rval.directory = matcher.group(10); - } - if (matcher.group(11) != null) { - rval.file = matcher.group(11); - } - if (matcher.group(12) != null) { - rval.query = matcher.group(12); - } - if (matcher.group(13) != null) { - rval.hash = matcher.group(13); - } - - // normalize to node.js API - if (!"".equals(rval.host) && "".equals(rval.path)) { - rval.path = "/"; - } - rval.pathname = rval.path; - parseAuthority(rval); - rval.normalizedPath = removeDotSegments(rval.pathname, !"".equals(rval.authority)); - if (!"".equals(rval.query)) { - rval.path += "?" + rval.query; - } - if (!"".equals(rval.protocol)) { - rval.protocol += ":"; - } - if (!"".equals(rval.hash)) { - rval.hash = "#" + rval.hash; - } - return rval; - } - - return rval; - } - - /** - * Removes dot segments from a JsonLdUrl path. - * - * @param path - * the path to remove dot segments from. - * @param hasAuthority - * true if the JsonLdUrl has an authority, false if not. - * @return The URL without the dot segments - */ - public static String removeDotSegments(String path, boolean hasAuthority) { - String rval = ""; - - if (path.indexOf("/") == 0) { - rval = "/"; - } - - // RFC 3986 5.2.4 (reworked) - final List input = new ArrayList(Arrays.asList(path.split("/"))); - if (path.endsWith("/")) { - // javascript .split includes a blank entry if the string ends with - // the delimiter, java .split does not so we need to add it manually - input.add(""); - } - final List output = new ArrayList(); - for (int i = 0; i < input.size(); i++) { - if (".".equals(input.get(i)) || ("".equals(input.get(i)) && input.size() - i > 1)) { - // input.remove(0); - continue; - } - if ("..".equals(input.get(i))) { - // input.remove(0); - if (hasAuthority - || (output.size() > 0 && !"..".equals(output.get(output.size() - 1)))) { - // [].pop() doesn't fail, to replicate this we need to check - // that there is something to remove - if (output.size() > 0) { - output.remove(output.size() - 1); - } - } else { - output.add(".."); - } - continue; - } - output.add(input.get(i)); - // input.remove(0); - } - - if (output.size() > 0) { - rval += output.get(0); - for (int i = 1; i < output.size(); i++) { - rval += "/" + output.get(i); - } - } - return rval; - } - - public static String removeBase(Object baseobj, String iri) { - if (baseobj == null) { - return iri; - } - - JsonLdUrl base; - if (baseobj instanceof String) { - base = JsonLdUrl.parse((String) baseobj); - } else { - base = (JsonLdUrl) baseobj; - } - - // establish base root - String root = ""; - if (!"".equals(base.href)) { - root += (base.protocol) + "//" + base.authority; - } - // support network-path reference with empty base - else if (iri.indexOf("//") != 0) { - root += "//"; - } - - // IRI not relative to base - if (iri.indexOf(root) != 0) { - return iri; - } - - // remove root from IRI and parse remainder - final JsonLdUrl rel = JsonLdUrl.parse(iri.substring(root.length())); - - // remove path segments that match - final List baseSegments = new ArrayList(Arrays.asList(base.normalizedPath - .split("/"))); - if (base.normalizedPath.endsWith("/")) { - baseSegments.add(""); - } - final List iriSegments = new ArrayList(Arrays.asList(rel.normalizedPath - .split("/"))); - if (rel.normalizedPath.endsWith("/")) { - iriSegments.add(""); - } - - while (baseSegments.size() > 0 && iriSegments.size() > 0) { - if (!baseSegments.get(0).equals(iriSegments.get(0))) { - break; - } - if (baseSegments.size() > 0) { - baseSegments.remove(0); - } - if (iriSegments.size() > 0) { - iriSegments.remove(0); - } - } - - // use '../' for each non-matching base segment - String rval = ""; - if (baseSegments.size() > 0) { - // don't count the last segment if it isn't a path (doesn't end in - // '/') - // don't count empty first segment, it means base began with '/' - if (!base.normalizedPath.endsWith("/") || "".equals(baseSegments.get(0))) { - baseSegments.remove(baseSegments.size() - 1); - } - for (int i = 0; i < baseSegments.size(); ++i) { - rval += "../"; - } - } - - // prepend remaining segments - if (iriSegments.size() > 0) { - rval += iriSegments.get(0); - } - for (int i = 1; i < iriSegments.size(); i++) { - rval += "/" + iriSegments.get(i); - } - - // add query and hash - if (!"".equals(rel.query)) { - rval += "?" + rel.query; - } - if (!"".equals(rel.hash)) { - rval += rel.hash; - } - - if ("".equals(rval)) { - rval = "./"; - } - - return rval; - } - - public static String resolve(String baseUri, String pathToResolve) { - // TODO: some input will need to be normalized to perform the expected - // result with java - // TODO: we can do this without using java URI! - if (baseUri == null) { - return pathToResolve; - } - if (pathToResolve == null || "".equals(pathToResolve.trim())) { - return baseUri; - } - try { - URI uri = new URI(baseUri); - // query string parsing - if (pathToResolve.startsWith("?")) { - // drop fragment from uri if it has one - if (uri.getFragment() != null) { - uri = new URI(uri.getScheme(), uri.getAuthority(), uri.getPath(), null, null); - } - // add query to the end manually (as URI.resolve does it wrong) - return uri.toString() + pathToResolve; - } - - uri = uri.resolve(pathToResolve); - // java doesn't discard unnecessary dot segments - String path = uri.getPath(); - if (path != null) { - path = JsonLdUrl.removeDotSegments(uri.getPath(), true); - } - return new URI(uri.getScheme(), uri.getAuthority(), path, uri.getQuery(), - uri.getFragment()).toString(); - } catch (final URISyntaxException e) { - return null; - } - } - - /** - * Parses the authority for the pre-parsed given JsonLdUrl. - * - * @param parsed - * the pre-parsed JsonLdUrl. - */ - private static void parseAuthority(JsonLdUrl parsed) { - // parse authority for unparsed relative network-path reference - if (parsed.href.indexOf(":") == -1 && parsed.href.indexOf("//") == 0 - && "".equals(parsed.host)) { - // must parse authority from pathname - parsed.pathname = parsed.pathname.substring(2); - final int idx = parsed.pathname.indexOf("/"); - if (idx == -1) { - parsed.authority = parsed.pathname; - parsed.pathname = ""; - } else { - parsed.authority = parsed.pathname.substring(0, idx); - parsed.pathname = parsed.pathname.substring(idx); - } - } else { - // construct authority - parsed.authority = parsed.host; - if (!"".equals(parsed.auth)) { - parsed.authority = parsed.auth + "@" + parsed.authority; - } - } - } -} +package com.github.jsonldjava.utils; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class JsonLdUrl { + + public String href = ""; + public String protocol = ""; + public String host = ""; + public String auth = ""; + public String user = ""; + public String password = ""; + public String hostname = ""; + public String port = ""; + public String relative = ""; + public String path = ""; + public String directory = ""; + public String file = ""; + public String query = ""; + public String hash = ""; + + // things not populated by the regex (NOTE: i don't think it matters if + // these are null or "" to start with) + public String pathname = null; + public String normalizedPath = null; + public String authority = null; + + private static Pattern parser = Pattern.compile( + "^(?:([^:\\/?#]+):)?(?:\\/\\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\\/?#]*)(?::(\\d*))?))?((((?:[^?#\\/]*\\/)*)([^?#]*))(?:\\?([^#]*))?(?:#(.*))?)"); + + public static JsonLdUrl parse(String url) { + final JsonLdUrl rval = new JsonLdUrl(); + rval.href = url; + + final Matcher matcher = parser.matcher(url); + if (matcher.matches()) { + if (matcher.group(1) != null) { + rval.protocol = matcher.group(1); + } + if (matcher.group(2) != null) { + rval.host = matcher.group(2); + } + if (matcher.group(3) != null) { + rval.auth = matcher.group(3); + } + if (matcher.group(4) != null) { + rval.user = matcher.group(4); + } + if (matcher.group(5) != null) { + rval.password = matcher.group(5); + } + if (matcher.group(6) != null) { + rval.hostname = matcher.group(6); + } + if (matcher.group(7) != null) { + rval.port = matcher.group(7); + } + if (matcher.group(8) != null) { + rval.relative = matcher.group(8); + } + if (matcher.group(9) != null) { + rval.path = matcher.group(9); + } + if (matcher.group(10) != null) { + rval.directory = matcher.group(10); + } + if (matcher.group(11) != null) { + rval.file = matcher.group(11); + } + if (matcher.group(12) != null) { + rval.query = matcher.group(12); + } + if (matcher.group(13) != null) { + rval.hash = matcher.group(13); + } + + // normalize to node.js API + if (!"".equals(rval.host) && "".equals(rval.path)) { + rval.path = "/"; + } + rval.pathname = rval.path; + parseAuthority(rval); + rval.normalizedPath = removeDotSegments(rval.pathname, !"".equals(rval.authority)); + if (!"".equals(rval.query)) { + rval.path += "?" + rval.query; + } + if (!"".equals(rval.protocol)) { + rval.protocol += ":"; + } + if (!"".equals(rval.hash)) { + rval.hash = "#" + rval.hash; + } + return rval; + } + + return rval; + } + + /** + * Removes dot segments from a JsonLdUrl path. + * + * @param path + * the path to remove dot segments from. + * @param hasAuthority + * true if the JsonLdUrl has an authority, false if not. + * @return The URL without the dot segments + */ + public static String removeDotSegments(String path, boolean hasAuthority) { + String rval = ""; + + if (path.indexOf("/") == 0) { + rval = "/"; + } + + // RFC 3986 5.2.4 (reworked) + final List input = new ArrayList(Arrays.asList(path.split("/"))); + if (path.endsWith("/")) { + // javascript .split includes a blank entry if the string ends with + // the delimiter, java .split does not so we need to add it manually + input.add(""); + } + final List output = new ArrayList(); + for (int i = 0; i < input.size(); i++) { + if (".".equals(input.get(i)) || ("".equals(input.get(i)) && input.size() - i > 1)) { + // input.remove(0); + continue; + } + if ("..".equals(input.get(i))) { + // input.remove(0); + if (hasAuthority + || (output.size() > 0 && !"..".equals(output.get(output.size() - 1)))) { + // [].pop() doesn't fail, to replicate this we need to check + // that there is something to remove + if (output.size() > 0) { + output.remove(output.size() - 1); + } + } else { + output.add(".."); + } + continue; + } + output.add(input.get(i)); + // input.remove(0); + } + + if (output.size() > 0) { + rval += output.get(0); + for (int i = 1; i < output.size(); i++) { + rval += "/" + output.get(i); + } + } + return rval; + } + + public static String removeBase(Object baseobj, String iri) { + if (baseobj == null) { + return iri; + } + + JsonLdUrl base; + if (baseobj instanceof String) { + base = JsonLdUrl.parse((String) baseobj); + } else { + base = (JsonLdUrl) baseobj; + } + + // establish base root + String root = ""; + if (!"".equals(base.href)) { + root += (base.protocol) + "//" + base.authority; + } + // support network-path reference with empty base + else if (iri.indexOf("//") != 0) { + root += "//"; + } + + // IRI not relative to base + if (iri.indexOf(root) != 0) { + return iri; + } + + // remove root from IRI and parse remainder + final JsonLdUrl rel = JsonLdUrl.parse(iri.substring(root.length())); + + // remove path segments that match + final List baseSegments = new ArrayList( + Arrays.asList(base.normalizedPath.split("/"))); + if (base.normalizedPath.endsWith("/")) { + baseSegments.add(""); + } + final List iriSegments = new ArrayList( + Arrays.asList(rel.normalizedPath.split("/"))); + if (rel.normalizedPath.endsWith("/")) { + iriSegments.add(""); + } + + while (baseSegments.size() > 0 && iriSegments.size() > 0) { + if (!baseSegments.get(0).equals(iriSegments.get(0))) { + break; + } + if (baseSegments.size() > 0) { + baseSegments.remove(0); + } + if (iriSegments.size() > 0) { + iriSegments.remove(0); + } + } + + // use '../' for each non-matching base segment + String rval = ""; + if (baseSegments.size() > 0) { + // don't count the last segment if it isn't a path (doesn't end in + // '/') + // don't count empty first segment, it means base began with '/' + if (!base.normalizedPath.endsWith("/") || "".equals(baseSegments.get(0))) { + baseSegments.remove(baseSegments.size() - 1); + } + for (int i = 0; i < baseSegments.size(); ++i) { + rval += "../"; + } + } + + // prepend remaining segments + if (iriSegments.size() > 0) { + rval += iriSegments.get(0); + } + for (int i = 1; i < iriSegments.size(); i++) { + rval += "/" + iriSegments.get(i); + } + + // add query and hash + if (!"".equals(rel.query)) { + rval += "?" + rel.query; + } + if (!"".equals(rel.hash)) { + rval += rel.hash; + } + + if ("".equals(rval)) { + rval = "./"; + } + + return rval; + } + + public static String resolve(String baseUri, String pathToResolve) { + // TODO: some input will need to be normalized to perform the expected + // result with java + // TODO: we can do this without using java URI! + if (baseUri == null) { + return pathToResolve; + } + if (pathToResolve == null || "".equals(pathToResolve.trim())) { + return baseUri; + } + try { + URI uri = new URI(baseUri); + // URI#resolve drops base scheme for opaque URIs, https://github.com/jsonld-java/jsonld-java/issues/232 + if (uri.isOpaque()) { + String basePath = uri.getPath() != null ? uri.getPath() : uri.getSchemeSpecificPart(); + // Drop the last segment, see https://tools.ietf.org/html/rfc3986#section-5.2.3 (2nd bullet point) + basePath = basePath.contains("/") ? basePath.substring(0, basePath.lastIndexOf('/') + 1) : ""; + return new URI(uri.getScheme(), basePath + pathToResolve, null).toString(); + } + // "a base URI [...] does not allow a fragment" (https://tools.ietf.org/html/rfc3986#section-4.3) + uri = new URI(uri.getScheme(), uri.getAuthority(), uri.getPath(), uri.getQuery(), null); + // query string parsing + if (pathToResolve.startsWith("?")) { + // drop query, https://tools.ietf.org/html/rfc3986#section-5.2.2: T.query = R.query; + uri = new URI(uri.getScheme(), uri.getAuthority(), uri.getPath(), null, null); + // add query to the end manually (as URI#resolve does it wrong) + return uri.toString() + pathToResolve; + } else if (pathToResolve.startsWith("#")) { + // add fragment to the end manually (as URI#resolve does it wrong) + return uri.toString() + pathToResolve; + } + // ensure a slash between the authority and the path of a URL + if (uri.getSchemeSpecificPart().startsWith("//") && !uri.getSchemeSpecificPart().matches("//.*/.*")) { + uri = new URI(uri + "/"); + } + uri = uri.resolve(pathToResolve); + // java doesn't discard unnecessary dot segments + String path = uri.getPath(); + if (path != null) { + path = JsonLdUrl.removeDotSegments(path, true); + } + return new URI(uri.getScheme(), uri.getAuthority(), path, uri.getQuery(), + uri.getFragment()).toString(); + } catch (final URISyntaxException e) { + return null; + } + } + + /** + * Parses the authority for the pre-parsed given JsonLdUrl. + * + * @param parsed + * the pre-parsed JsonLdUrl. + */ + private static void parseAuthority(JsonLdUrl parsed) { + // parse authority for unparsed relative network-path reference + if (parsed.href.indexOf(":") == -1 && parsed.href.indexOf("//") == 0 + && "".equals(parsed.host)) { + // must parse authority from pathname + parsed.pathname = parsed.pathname.substring(2); + final int idx = parsed.pathname.indexOf("/"); + if (idx == -1) { + parsed.authority = parsed.pathname; + parsed.pathname = ""; + } else { + parsed.authority = parsed.pathname.substring(0, idx); + parsed.pathname = parsed.pathname.substring(idx); + } + } else { + // construct authority + parsed.authority = parsed.host; + if (!"".equals(parsed.auth)) { + parsed.authority = parsed.auth + "@" + parsed.authority; + } + } + } +} diff --git a/core/src/main/java/com/github/jsonldjava/utils/JsonUtils.java b/core/src/main/java/com/github/jsonldjava/utils/JsonUtils.java index 3fa8021a..c24c8467 100644 --- a/core/src/main/java/com/github/jsonldjava/utils/JsonUtils.java +++ b/core/src/main/java/com/github/jsonldjava/utils/JsonUtils.java @@ -8,19 +8,14 @@ import java.io.StringReader; import java.io.StringWriter; import java.io.Writer; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpUriRequest; -import org.apache.http.client.protocol.RequestAcceptEncoding; -import org.apache.http.client.protocol.ResponseContentEncoding; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.cache.BasicHttpCacheStorage; -import org.apache.http.impl.client.cache.CacheConfig; -import org.apache.http.impl.client.cache.CachingHttpClientBuilder; - import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerationException; import com.fasterxml.jackson.core.JsonGenerator; @@ -28,9 +23,28 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.jsonldjava.core.DocumentLoader; import com.github.jsonldjava.core.JsonLdApi; import com.github.jsonldjava.core.JsonLdProcessor; +import org.apache.commons.io.ByteOrderMark; +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.input.BOMInputStream; +import org.apache.http.Header; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.client.protocol.RequestAcceptEncoding; +import org.apache.http.client.protocol.ResponseContentEncoding; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.DefaultRedirectStrategy; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.client.cache.BasicHttpCacheStorage; +import org.apache.http.impl.client.cache.CacheConfig; +import org.apache.http.impl.client.cache.CachingHttpClientBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * Functions used to make loading, parsing, and serializing JSON easy using * Jackson. @@ -43,9 +57,22 @@ public class JsonUtils { * An HTTP Accept header that prefers JSONLD. */ public static final String ACCEPT_HEADER = "application/ld+json, application/json;q=0.9, application/javascript;q=0.5, text/javascript;q=0.5, text/plain;q=0.2, */*;q=0.1"; + + /** + * The user agent used by the default {@link CloseableHttpClient}. + * + * This will not be used if + * {@link DocumentLoader#setHttpClient(CloseableHttpClient)} is called with + * a custom client. + */ + public static final String JSONLD_JAVA_USER_AGENT = "JSONLD-Java"; + private static final ObjectMapper JSON_MAPPER = new ObjectMapper(); private static final JsonFactory JSON_FACTORY = new JsonFactory(JSON_MAPPER); + private static volatile CloseableHttpClient DEFAULT_HTTP_CLIENT; + // Avoid possible endless loop when following alternate locations + private static final int MAX_LINKS_FOLLOW = 20; static { // Disable default Jackson behaviour to close @@ -73,8 +100,27 @@ public class JsonUtils { * If there was an IO error during parsing. */ public static Object fromInputStream(InputStream input) throws IOException { - // no readers from inputstreams w.o. encoding!! - return fromInputStream(input, "UTF-8"); + // filter BOMs from InputStream + try (final BOMInputStream bOMInputStream = new BOMInputStream(input, false, + ByteOrderMark.UTF_8, ByteOrderMark.UTF_16BE, ByteOrderMark.UTF_16LE, + ByteOrderMark.UTF_32BE, ByteOrderMark.UTF_32LE);) { + Charset charset = StandardCharsets.UTF_8; + // Attempt to use the BOM if it exists + if (bOMInputStream.hasBOM()) { + try { + charset = Charset.forName(bOMInputStream.getBOMCharsetName()); + } catch (final IllegalArgumentException e) { + // If there are any issues with the BOM charset, attempt to + // parse with UTF_8 + charset = StandardCharsets.UTF_8; + } + } + return fromInputStream(bOMInputStream, charset); + } finally { + if (input != null) { + input.close(); + } + } } /** @@ -94,7 +140,30 @@ public static Object fromInputStream(InputStream input) throws IOException { * If there was an IO error during parsing. */ public static Object fromInputStream(InputStream input, String enc) throws IOException { - return fromReader(new BufferedReader(new InputStreamReader(input, enc))); + return fromInputStream(input, Charset.forName(enc)); + } + + /** + * Parses a JSON-LD document from the given {@link InputStream} to an object + * that can be used as input for the {@link JsonLdApi} and + * {@link JsonLdProcessor} methods. + * + * @param input + * The JSON-LD document in an InputStream. + * @param enc + * The character encoding to use when interpreting the characters + * in the InputStream. + * @return A JSON Object. + * @throws JsonParseException + * If there was a JSON related error during parsing. + * @throws IOException + * If there was an IO error during parsing. + */ + public static Object fromInputStream(InputStream input, Charset enc) throws IOException { + try (InputStreamReader in = new InputStreamReader(input, enc); + BufferedReader reader = new BufferedReader(in);) { + return fromReader(reader); + } } /** @@ -112,7 +181,24 @@ public static Object fromInputStream(InputStream input, String enc) throws IOExc */ public static Object fromReader(Reader reader) throws IOException { final JsonParser jp = JSON_FACTORY.createParser(reader); - Object rval ; + return fromJsonParser(jp); + } + + /** + * Parses a JSON-LD document from the given {@link JsonParser} to an object + * that can be used as input for the {@link JsonLdApi} and + * {@link JsonLdProcessor} methods. + * + * @param jp + * The JSON-LD document in a {@link JsonParser}. + * @return A JSON Object. + * @throws JsonParseException + * If there was a JSON related error during parsing. + * @throws IOException + * If there was an IO error during parsing. + */ + public static Object fromJsonParser(JsonParser jp) throws IOException { + Object rval; final JsonToken initialToken = jp.nextToken(); if (initialToken == JsonToken.START_ARRAY) { @@ -129,19 +215,24 @@ public static Object fromReader(Reader reader) throws IOException { } else if (initialToken == JsonToken.VALUE_NULL) { rval = null; } else { - throw new JsonParseException("document doesn't start with a valid json element : " - + initialToken, jp.getCurrentLocation()); + throw new JsonParseException(jp, + "document doesn't start with a valid json element : " + initialToken, + jp.getCurrentLocation()); } - - JsonToken t ; - try { t = jp.nextToken(); } - catch (JsonParseException ex) { - throw new JsonParseException("Document contains more content after json-ld element - (possible mismatched {}?)", - jp.getCurrentLocation()); + + JsonToken t; + try { + t = jp.nextToken(); + } catch (final JsonParseException ex) { + throw new JsonParseException(jp, + "Document contains more content after json-ld element - (possible mismatched {}?)", + jp.getCurrentLocation()); + } + if (t != null) { + throw new JsonParseException(jp, + "Document contains possible json content after the json-ld element - (possible mismatched {}?)", + jp.getCurrentLocation()); } - if ( t != null ) - throw new JsonParseException("Document contains possible json content after the json-ld element - (possible mismatched {}?)", - jp.getCurrentLocation()); return rval; } @@ -161,25 +252,6 @@ public static Object fromString(String jsonString) throws JsonParseException, IO return fromReader(new StringReader(jsonString)); } - /** - * Parses a JSON-LD document, from the contents of the JSON resource - * resolved from the JsonLdUrl, to an object that can be used as input for - * the {@link JsonLdApi} and {@link JsonLdProcessor} methods. - * - * @param url - * The JsonLdUrl to resolve - * @return A JSON Object. - * @throws JsonParseException - * If there was a JSON related error during parsing. - * @throws IOException - * If there was an IO error during parsing. - * @deprecated Use {@link #fromURL(java.net.URL, CloseableHttpClient)} instead. - */ - @Deprecated - public static Object fromURL(java.net.URL url) throws JsonParseException, IOException { - return fromURL(url, getDefaultHttpClient()); - } - /** * Writes the given JSON-LD Object out to a String, using indentation and * new lines to improve readability. @@ -192,8 +264,8 @@ public static Object fromURL(java.net.URL url) throws JsonParseException, IOExce * @throws IOException * If there is an IO error during serialization. */ - public static String toPrettyString(Object jsonObject) throws JsonGenerationException, - IOException { + public static String toPrettyString(Object jsonObject) + throws JsonGenerationException, IOException { final StringWriter sw = new StringWriter(); writePrettyPrint(sw, jsonObject); return sw.toString(); @@ -228,8 +300,8 @@ public static String toString(Object jsonObject) throws JsonGenerationException, * @throws IOException * If there is an IO error during serialization. */ - public static void write(Writer writer, Object jsonObject) throws JsonGenerationException, - IOException { + public static void write(Writer writer, Object jsonObject) + throws JsonGenerationException, IOException { final JsonGenerator jw = JSON_FACTORY.createGenerator(writer); jw.writeObject(jsonObject); } @@ -255,63 +327,115 @@ public static void writePrettyPrint(Writer writer, Object jsonObject) } /** - * Attempts to open an {@link InputStream} that will contain the content of the URL, as resolved by the given HTTP Client. - * - * If the URL is not an HTTP or HTTPS URL it is resolved using the default {@link java.net.URL#openStream()} method. - * @param url The URL to resolve. - * @param httpClient The CloseableHttpClient to use to resolve the URL. - * @return An InputStream containing the contents of the resolved URL. - * @throws IOException If there are any IO exceptions while resolving the URL. + * Parses a JSON-LD document, from the contents of the JSON resource + * resolved from the JsonLdUrl, to an object that can be used as input for + * the {@link JsonLdApi} and {@link JsonLdProcessor} methods. + * + * @param url + * The JsonLdUrl to resolve + * @param httpClient + * The {@link CloseableHttpClient} to use to resolve the URL. + * @return A JSON Object. + * @throws JsonParseException + * If there was a JSON related error during parsing. + * @throws IOException + * If there was an IO error during parsing. */ - public static InputStream openStreamForURL(java.net.URL url, CloseableHttpClient httpClient) throws IOException { + public static Object fromURL(java.net.URL url, CloseableHttpClient httpClient) + throws JsonParseException, IOException { final String protocol = url.getProtocol(); + // We can only use the Apache HTTPClient for HTTP/HTTPS, so use the + // native java client for the others if (!protocol.equalsIgnoreCase("http") && !protocol.equalsIgnoreCase("https")) { // Can't use the HTTP client for those! // Fallback to Java's built-in JsonLdUrl handler. No need for // Accept headers as it's likely to be file: or jar: - return url.openStream(); + return fromInputStream(url.openStream()); + } else { + return fromJsonLdViaHttpUri(url, httpClient, 0); } + } + + private static Object fromJsonLdViaHttpUri(final URL url, final CloseableHttpClient httpClient, int linksFollowed) + throws IOException { final HttpUriRequest request = new HttpGet(url.toExternalForm()); // We prefer application/ld+json, but fallback to application/json // or whatever is available request.addHeader("Accept", ACCEPT_HEADER); - - final CloseableHttpResponse response = httpClient.execute(request); - try { + try (CloseableHttpResponse response = httpClient.execute(request)) { final int status = response.getStatusLine().getStatusCode(); if (status != 200 && status != 203) { throw new IOException("Can't retrieve " + url + ", status code: " + status); } - return response.getEntity().getContent(); - } finally { - if (response != null) { - response.close(); + // follow alternate document location + // https://www.w3.org/TR/json-ld11/#alternate-document-location + URL alternateLink = alternateLink(url, response); + if (alternateLink != null) { + linksFollowed++; + if (linksFollowed > MAX_LINKS_FOLLOW) { + throw new IOException("Too many alternate links followed. This may indicate a cycle. Aborting."); + } + return fromJsonLdViaHttpUri(alternateLink, httpClient, linksFollowed); + } + return fromInputStream(response.getEntity().getContent()); + } + } + + private static URL alternateLink(URL url, CloseableHttpResponse response) + throws MalformedURLException { + if (response.getEntity().getContentType() != null + && !response.getEntity().getContentType().getValue().equals("application/ld+json")) { + for (Header header : response.getAllHeaders()) { + if (header.getName().equalsIgnoreCase("link")) { + String alternateLink = ""; + boolean relAlternate = false; + boolean jsonld = false; + for (String value : header.getValue().split(";")) { + value=value.trim(); + if (value.startsWith("<") && value.endsWith(">")) { + alternateLink = value.substring(1, value.length() - 1); + } + if (value.startsWith("type=\"application/ld+json\"")) { + jsonld = true; + } + if (value.startsWith("rel=\"alternate\"")) { + relAlternate = true; + } + } + if (jsonld && relAlternate && !alternateLink.isEmpty()) { + return new URL(url.getProtocol() + "://" + url.getAuthority() + alternateLink); + } + } } } + return null; } /** - * Parses a JSON-LD document, from the contents of the JSON resource - * resolved from the JsonLdUrl, to an object that can be used as input for - * the {@link JsonLdApi} and {@link JsonLdProcessor} methods. + * Fallback method directly using the {@link java.net.HttpURLConnection} + * class for cases where servers do not interoperate correctly with Apache + * HTTPClient. * * @param url - * The JsonLdUrl to resolve - * @param httpClient - * The {@link CloseableHttpClient} to use to resolve the URL. - * @return A JSON Object. + * The URL to access. + * @return The result, after conversion from JSON to a Java Object. * @throws JsonParseException * If there was a JSON related error during parsing. * @throws IOException * If there was an IO error during parsing. */ - public static Object fromURL(java.net.URL url, CloseableHttpClient httpClient) throws JsonParseException, IOException { - final InputStream in = openStreamForURL(url, httpClient); - try { - return fromInputStream(in); + public static Object fromURLJavaNet(URL url) throws JsonParseException, IOException { + final HttpURLConnection urlConn = (HttpURLConnection) url.openConnection(); + urlConn.addRequestProperty("Accept", ACCEPT_HEADER); + + final StringWriter output = new StringWriter(); + try (final InputStream directStream = urlConn.getInputStream();) { + IOUtils.copy(directStream, output, StandardCharsets.UTF_8); } finally { - in.close(); + output.flush(); } + final Object context = JsonUtils.fromReader(new StringReader(output.toString())); + return context; } public static CloseableHttpClient getDefaultHttpClient() { @@ -327,27 +451,45 @@ public static CloseableHttpClient getDefaultHttpClient() { return result; } - private static CloseableHttpClient createDefaultHttpClient() { + public static CloseableHttpClient createDefaultHttpClient() { + final CacheConfig cacheConfig = createDefaultCacheConfig(); + + final CloseableHttpClient result = createDefaultHttpClient(cacheConfig); + + return result; + } + + public static CacheConfig createDefaultCacheConfig() { + return CacheConfig.custom().setMaxCacheEntries(500).setMaxObjectSize(1024 * 256) + .setSharedCache(false).setHeuristicCachingEnabled(true) + .setHeuristicDefaultLifetime(86400).build(); + } + + public static CloseableHttpClient createDefaultHttpClient(final CacheConfig cacheConfig) { + return createDefaultHttpClientBuilder(cacheConfig).build(); + } + + public static HttpClientBuilder createDefaultHttpClientBuilder(final CacheConfig cacheConfig) { // Common CacheConfig for both the JarCacheStorage and the underlying // BasicHttpCacheStorage - final CacheConfig cacheConfig = CacheConfig.custom().setMaxCacheEntries(1000) - .setMaxObjectSize(1024 * 128).build(); - - CloseableHttpClient result = CachingHttpClientBuilder - .create() + return CachingHttpClientBuilder.create() // allow caching .setCacheConfig(cacheConfig) // Wrap the local JarCacheStorage around a BasicHttpCacheStorage - .setHttpCacheStorage( - new JarCacheStorage(null, cacheConfig, new BasicHttpCacheStorage( - cacheConfig))) + .setHttpCacheStorage(new JarCacheStorage(null, cacheConfig, + new BasicHttpCacheStorage(cacheConfig))) // Support compressed data - // http://hc.apache.org/httpcomponents-client-ga/tutorial/html/httpagent.html#d5e1238 + // https://wayback.archive.org/web/20130901115452/http://hc.apache.org:80/httpcomponents-client-ga/tutorial/html/httpagent.html#d5e1238 .addInterceptorFirst(new RequestAcceptEncoding()) .addInterceptorFirst(new ResponseContentEncoding()) + .setRedirectStrategy(DefaultRedirectStrategy.INSTANCE) + // User agent customisation + .setUserAgent(JSONLD_JAVA_USER_AGENT) // use system defaults for proxy etc. - .useSystemProperties().build(); - - return result; + .useSystemProperties(); + } + + private JsonUtils() { + // Static class, no access to constructor } } diff --git a/core/src/main/java/com/github/jsonldjava/utils/Obj.java b/core/src/main/java/com/github/jsonldjava/utils/Obj.java index a7f371de..4e7e36bb 100644 --- a/core/src/main/java/com/github/jsonldjava/utils/Obj.java +++ b/core/src/main/java/com/github/jsonldjava/utils/Obj.java @@ -11,7 +11,7 @@ public class Obj { * @return A new {@link Map} instance. */ public static Map newMap() { - return new LinkedHashMap(2, 0.75f); + return new LinkedHashMap(4, 0.75f); } /** diff --git a/core/src/test/java/com/github/jsonldjava/core/ArrayContextToRDFTest.java b/core/src/test/java/com/github/jsonldjava/core/ArrayContextToRDFTest.java index 2283d5cb..28aa9d35 100644 --- a/core/src/test/java/com/github/jsonldjava/core/ArrayContextToRDFTest.java +++ b/core/src/test/java/com/github/jsonldjava/core/ArrayContextToRDFTest.java @@ -21,7 +21,8 @@ public void toRdfWithNamespace() throws Exception { final URL arrayContextUrl = getClass().getResource("/custom/array-context.jsonld"); assertNotNull(arrayContextUrl); - final Object arrayContext = JsonUtils.fromURL(arrayContextUrl, JsonUtils.getDefaultHttpClient()); + final Object arrayContext = JsonUtils.fromURL(arrayContextUrl, + JsonUtils.getDefaultHttpClient()); assertNotNull(arrayContext); final JsonLdOptions options = new JsonLdOptions(); options.useNamespaces = true; @@ -35,7 +36,7 @@ public RemoteDocument loadDocument(String url) throws JsonLdError { }; options.setDocumentLoader(documentLoader); final RDFDataset rdf = (RDFDataset) JsonLdProcessor.toRDF(arrayContext, options); - System.out.println(rdf.getNamespaces()); + // System.out.println(rdf.getNamespaces()); assertEquals("http://example.org/", rdf.getNamespace("ex")); assertEquals("http://example.com/2/", rdf.getNamespace("ex2")); // Only 'proper' prefixes returned diff --git a/core/src/test/java/com/github/jsonldjava/core/ContextCompactionTest.java b/core/src/test/java/com/github/jsonldjava/core/ContextCompactionTest.java index fb08bee7..31bfc3ef 100644 --- a/core/src/test/java/com/github/jsonldjava/core/ContextCompactionTest.java +++ b/core/src/test/java/com/github/jsonldjava/core/ContextCompactionTest.java @@ -1,24 +1,21 @@ package com.github.jsonldjava.core; -import static org.junit.Assert.*; - -import java.io.IOException; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; -import org.junit.After; -import org.junit.Before; +import com.github.jsonldjava.utils.JsonUtils; import org.junit.Test; -import com.fasterxml.jackson.core.JsonGenerationException; -import com.github.jsonldjava.utils.JsonUtils; public class ContextCompactionTest { @Test - public void testCompaction() throws Exception { + public void testCompaction() { final Map contextAbbrevs = new HashMap(); contextAbbrevs.put("so", "http://schema.org/"); @@ -38,19 +35,30 @@ public void testCompaction() throws Exception { options.setBase("http://schema.org/"); options.setCompactArrays(true); - System.out.println("Before compact"); - System.out.println(JsonUtils.toPrettyString(json)); - final List newContexts = new LinkedList(); newContexts.add("http://schema.org/"); final Map compacted = JsonLdProcessor.compact(json, newContexts, options); - System.out.println("\n\nAfter compact:"); - System.out.println(JsonUtils.toPrettyString(compacted)); - assertTrue("Compaction removed the context", compacted.containsKey("@context")); assertFalse("Compaction of context should be a string, not a list", compacted.get("@context") instanceof List); } + @Test + public void testCompactionSingleRemoteContext() throws Exception { + final String jsonString = "[{\"@type\": [\"http://schema.org/Person\"] } ]"; + final String ctxStr = "{\"@context\": \"http://schema.org/\"}"; + + final Object json = JsonUtils.fromString(jsonString); + final Object ctx = JsonUtils.fromString(ctxStr); + + final JsonLdOptions options = new JsonLdOptions(); + + final Map compacted = JsonLdProcessor.compact(json, ctx, options); + + assertEquals("Wrong returned context", "http://schema.org/", compacted.get("@context")); + assertEquals("Wrong type", "Person", compacted.get("type")); + assertEquals("Wrong number of Json entries",2, compacted.size()); + } + } diff --git a/core/src/test/java/com/github/jsonldjava/core/ContextFlatteningTest.java b/core/src/test/java/com/github/jsonldjava/core/ContextFlatteningTest.java new file mode 100644 index 00000000..8e3a1710 --- /dev/null +++ b/core/src/test/java/com/github/jsonldjava/core/ContextFlatteningTest.java @@ -0,0 +1,66 @@ +package com.github.jsonldjava.core; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import com.github.jsonldjava.utils.JsonUtils; +import org.junit.Test; + +public class ContextFlatteningTest { + + @Test + public void testFlatenning() throws Exception { + + final Map contextAbbrevs = new HashMap<>(); + contextAbbrevs.put("so", "http://schema.org/"); + + final Map json = new HashMap<>(); + json.put("@context", contextAbbrevs); + json.put("@id", "http://example.org/my_work"); + + final List types = new LinkedList<>(); + types.add("so:CreativeWork"); + + json.put("@type", types); + json.put("so:name", "My Work"); + json.put("so:url", "http://example.org/my_work"); + + final JsonLdOptions options = new JsonLdOptions(); + options.setBase("http://schema.org/"); + options.setCompactArrays(true); + options.setOmitGraph(true); + + final String flattenStr = "{\"@id\": \"http://schema.org/myid\", \"@context\": \"http://schema.org/\"}"; + final Object flatten = JsonUtils.fromString(flattenStr); + + final Map flattened = ((Map)JsonLdProcessor.flatten(json, flatten, options)); + + assertTrue("Flattening removed the context", flattened.containsKey("@context")); + assertFalse("Flattening of context should be a string, not a list", + flattened.get("@context") instanceof List); + } + + @Test + public void testFlatteningRemoteContext() throws Exception { + final String jsonString = + "{\"@context\": {\"@vocab\": \"http://schema.org/\"}, \"knows\": [{\"name\": \"a\"}, {\"name\": \"b\"}] }"; + final String flattenStr = "{\"@context\": \"http://schema.org/\"}"; + + final Object json = JsonUtils.fromString(jsonString); + final Object flatten = JsonUtils.fromString(flattenStr); + + final JsonLdOptions options = new JsonLdOptions(); + options.setOmitGraph(true); + + final Map flattened = ((Map)JsonLdProcessor.flatten(json, flatten, options)); + + assertEquals("Wrong returned context", "http://schema.org/", flattened.get("@context")); + assertEquals("Wrong number of Json entries",2, flattened.size()); + } + +} diff --git a/core/src/test/java/com/github/jsonldjava/core/ContextFramingTest.java b/core/src/test/java/com/github/jsonldjava/core/ContextFramingTest.java new file mode 100644 index 00000000..73116205 --- /dev/null +++ b/core/src/test/java/com/github/jsonldjava/core/ContextFramingTest.java @@ -0,0 +1,67 @@ +package com.github.jsonldjava.core; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import com.github.jsonldjava.utils.JsonUtils; +import org.junit.Test; + +public class ContextFramingTest { + + @Test + public void testFraming() throws Exception { + + final Map contextAbbrevs = new HashMap<>(); + contextAbbrevs.put("so", "http://schema.org/"); + + final Map json = new HashMap<>(); + json.put("@context", contextAbbrevs); + json.put("@id", "http://example.org/my_work"); + + final List types = new LinkedList<>(); + types.add("so:CreativeWork"); + + json.put("@type", types); + json.put("so:name", "My Work"); + json.put("so:url", "http://example.org/my_work"); + + final JsonLdOptions options = new JsonLdOptions(); + options.setBase("http://schema.org/"); + options.setCompactArrays(true); + options.setOmitGraph(true); + + final String frameStr = "{\"@id\": \"http://schema.org/myid\", \"@context\": \"http://schema.org/\"}"; + final Object frame = JsonUtils.fromString(frameStr); + + final Map framed = JsonLdProcessor.frame(json, frame, options); + + assertTrue("Framing removed the context", framed.containsKey("@context")); + assertFalse("Framing of context should be a string, not a list", + framed.get("@context") instanceof List); + } + + @Test + public void testFramingRemoteContext() throws Exception { + final String jsonString = "{\"@id\": \"http://schema.org/myid\", \"@type\": [\"http://schema.org/Person\"]}"; + final String frameStr = "{\"@id\": \"http://schema.org/myid\", \"@context\": \"http://schema.org/\"}"; + + final Object json = JsonUtils.fromString(jsonString); + final Object frame = JsonUtils.fromString(frameStr); + + final JsonLdOptions options = new JsonLdOptions(); + options.setOmitGraph(true); + + final Map framed = JsonLdProcessor.frame(json, frame, options); + + assertEquals("Wrong returned context", "http://schema.org/", framed.get("@context")); + assertEquals("Wrong id", "schema:myid", framed.get("id")); + assertEquals("Wrong type", "Person", framed.get("type")); + assertEquals("Wrong number of Json entries",3, framed.size()); + } + +} diff --git a/core/src/test/java/com/github/jsonldjava/core/ContextRecursionTest.java b/core/src/test/java/com/github/jsonldjava/core/ContextRecursionTest.java new file mode 100644 index 00000000..d6610121 --- /dev/null +++ b/core/src/test/java/com/github/jsonldjava/core/ContextRecursionTest.java @@ -0,0 +1,75 @@ +package com.github.jsonldjava.core; + +import com.github.jsonldjava.utils.JsonUtils; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + + +public class ContextRecursionTest { + + @BeforeClass + public static void setup() { + System.setProperty(DocumentLoader.DISALLOW_REMOTE_CONTEXT_LOADING, "true"); + } + + @AfterClass + public static void tearDown() { + System.setProperty(DocumentLoader.DISALLOW_REMOTE_CONTEXT_LOADING, "false"); + } + + @Test + public void testIssue302_allowedRecursion() throws IOException { + + final String contextB = "{\"@context\": [\"http://localhost/d\", {\"b\": \"http://localhost/b\"} ] }"; + final String contextC = "{\"@context\": [\"http://localhost/d\", {\"c\": \"http://localhost/c\"} ] }"; + final String contextD = "{\"@context\": [\"http://localhost/e\", {\"d\": \"http://localhost/d\"} ] }"; + final String contextE = "{\"@context\": {\"e\": \"http://localhost/e\"} }"; + + final DocumentLoader dl = new DocumentLoader(); + dl.addInjectedDoc("http://localhost/b", contextB); + dl.addInjectedDoc("http://localhost/c", contextC); + dl.addInjectedDoc("http://localhost/d", contextD); + dl.addInjectedDoc("http://localhost/e", contextE); + final JsonLdOptions options = new JsonLdOptions(); + options.setDocumentLoader(dl); + + final String jsonString = "{\"@context\": [\"http://localhost/d\", \"http://localhost/b\", \"http://localhost/c\", {\"a\": \"http://localhost/a\"} ], \"a\": \"A\", \"b\": \"B\", \"c\": \"C\", \"d\": \"D\"}"; + final Object json = JsonUtils.fromString(jsonString); + final Object expanded = JsonLdProcessor.expand(json, options); + assertEquals( + "[{http://localhost/a=[{@value=A}], http://localhost/b=[{@value=B}], http://localhost/c=[{@value=C}], http://localhost/d=[{@value=D}]}]", + expanded.toString()); + } + + @Test + public void testCyclicRecursion() throws IOException { + + final String contextC = "{\"@context\": [\"http://localhost/d\", {\"c\": \"http://localhost/c\"} ] }"; + final String contextD = "{\"@context\": [\"http://localhost/e\", {\"d\": \"http://localhost/d\"} ] }"; + final String contextE = "{\"@context\": [\"http://localhost/c\", {\"e\": \"http://localhost/e\"} ] }"; + + final DocumentLoader dl = new DocumentLoader(); + dl.addInjectedDoc("http://localhost/c", contextC); + dl.addInjectedDoc("http://localhost/d", contextD); + dl.addInjectedDoc("http://localhost/e", contextE); + final JsonLdOptions options = new JsonLdOptions(); + options.setDocumentLoader(dl); + + final String jsonString = "{\"@context\": [\"http://localhost/c\", {\"a\": \"http://localhost/a\"} ]}"; + final Object json = JsonUtils.fromString(jsonString); + try { + JsonLdProcessor.expand(json, options); + fail("it should throw"); + } catch(JsonLdError err) { + assertEquals(JsonLdError.Error.RECURSIVE_CONTEXT_INCLUSION, err.getType()); + assertEquals("recursive context inclusion: http://localhost/c", err.getMessage()); + } + } + +} diff --git a/core/src/test/java/com/github/jsonldjava/core/ContextSerializationTest.java b/core/src/test/java/com/github/jsonldjava/core/ContextSerializationTest.java new file mode 100644 index 00000000..7077f228 --- /dev/null +++ b/core/src/test/java/com/github/jsonldjava/core/ContextSerializationTest.java @@ -0,0 +1,24 @@ +package com.github.jsonldjava.core; + +import com.github.jsonldjava.utils.JsonUtils; +import org.junit.Test; + +import java.io.IOException; +import java.util.Map; + +import static org.junit.Assert.assertEquals; + +public class ContextSerializationTest { + + @Test + // Added in order to have some coverage on the serialize method since is not used anywhere. + public void serializeTest() throws IOException { + final Map json = (Map)JsonUtils + .fromInputStream(getClass().getResourceAsStream("/custom/contexttest-0005.jsonld")); + + final Map contextValue = (Map)json.get(JsonLdConsts.CONTEXT); + final Map serializedContext = new Context().parse(contextValue).serialize(); + + assertEquals("Wrong serialized context", json, serializedContext); + } +} diff --git a/core/src/test/java/com/github/jsonldjava/core/ContextTest.java b/core/src/test/java/com/github/jsonldjava/core/ContextTest.java index 6efe820b..2fab52c1 100644 --- a/core/src/test/java/com/github/jsonldjava/core/ContextTest.java +++ b/core/src/test/java/com/github/jsonldjava/core/ContextTest.java @@ -1,11 +1,81 @@ package com.github.jsonldjava.core; +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + import org.junit.Test; +import com.google.common.collect.ImmutableMap; + public class ContextTest { @Test public void testRemoveBase() { // TODO: test if Context.removeBase actually works } + + // See https://github.com/jsonld-java/jsonld-java/issues/141 + + @Test(expected = JsonLdError.class) + public void testIssue141_errorOnEmptyKey_compact() { + JsonLdProcessor.compact(ImmutableMap.of(), ImmutableMap.of("", "http://example.com"), + new JsonLdOptions()); + } + + @Test(expected = JsonLdError.class) + public void testIssue141_errorOnEmptyKey_expand() { + JsonLdProcessor.expand( + ImmutableMap.of("@context", ImmutableMap.of("", "http://example.com")), + new JsonLdOptions()); + } + + @Test(expected = JsonLdError.class) + public void testIssue141_errorOnEmptyKey_newContext1() { + new Context(ImmutableMap.of("", "http://example.com")); + } + + @Test(expected = JsonLdError.class) + public void testIssue141_errorOnEmptyKey_newContext2() { + new Context(ImmutableMap.of("", "http://example.com"), new JsonLdOptions()); + } + + /* + * schema.org documentation says some properties can be either Text or URL, + * but sets `@type : @id` in the context, e.g. for + * https://schema.org/roleName: + */ + Map schemaOrg = ImmutableMap.of("roleName", + ImmutableMap.of("@id", "http://schema.org/roleName", "@type", "@id")); + + // See https://github.com/jsonld-java/jsonld-java/issues/248 + + @Test(expected = IllegalArgumentException.class) + public void testIssue248_uriExpected() { + JsonLdProcessor + .expand(ImmutableMap.of("roleName", "Production Company", "@context", schemaOrg)); + } + + @Test + public void testIssue248_forceValue() { + final List value = Arrays.asList(ImmutableMap.of("@value", "Production Company")); + final Map input = ImmutableMap.of("roleName", value, "@context", schemaOrg); + final Object output = JsonLdProcessor.expand(input); + assertEquals("[{http://schema.org/roleName=[{@value=Production Company}]}]", + output.toString()); + } + + @Test + public void testIssue248_overrideContext() { + final List context = Arrays.asList(schemaOrg, + ImmutableMap.of("roleName", ImmutableMap.of("@id", "http://schema.org/roleName"))); + final Map input = ImmutableMap.of("roleName", "Production Company", + "@context", context); + final Object output = JsonLdProcessor.expand(input); + assertEquals("[{http://schema.org/roleName=[{@value=Production Company}]}]", + output.toString()); + } + } diff --git a/core/src/test/java/com/github/jsonldjava/core/DecimalLiteralCanonicalTest.java b/core/src/test/java/com/github/jsonldjava/core/DecimalLiteralCanonicalTest.java new file mode 100644 index 00000000..0e05921a --- /dev/null +++ b/core/src/test/java/com/github/jsonldjava/core/DecimalLiteralCanonicalTest.java @@ -0,0 +1,34 @@ +package com.github.jsonldjava.core; + +import org.junit.Test; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertEquals; + +public class DecimalLiteralCanonicalTest { + + @Test + public void testDecimalIsNotCanonicalized() { + double value = 6.5; + + Map innerMap = new HashMap<>(); + innerMap.put("@value", value); + innerMap.put("@type", "http://www.w3.org/2001/XMLSchema#decimal"); + + Map jsonMap = new HashMap<>(); + jsonMap.put("ex:id", innerMap); + + JsonLdApi api = new JsonLdApi(jsonMap, new JsonLdOptions("")); + RDFDataset dataset = api.toRDF(); + + List defaultList = (List) dataset.get("@default"); + Map tripleMap = (Map) defaultList.get(0); + Map objectMap = (Map) tripleMap.get("object"); + + assertEquals("http://www.w3.org/2001/XMLSchema#decimal", objectMap.get("datatype")); + assertEquals(Double.toString(value), objectMap.get("value")); + } +} diff --git a/core/src/test/java/com/github/jsonldjava/core/DocumentLoaderTest.java b/core/src/test/java/com/github/jsonldjava/core/DocumentLoaderTest.java index d3ef225f..57b8f5ce 100644 --- a/core/src/test/java/com/github/jsonldjava/core/DocumentLoaderTest.java +++ b/core/src/test/java/com/github/jsonldjava/core/DocumentLoaderTest.java @@ -13,14 +13,19 @@ import java.io.IOException; import java.io.InputStream; +import java.io.StringReader; +import java.io.StringWriter; +import java.net.HttpURLConnection; import java.net.URL; import java.net.URLClassLoader; import java.net.URLConnection; import java.net.URLStreamHandler; +import java.nio.charset.Charset; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; +import org.apache.commons.io.IOUtils; import org.apache.http.Header; import org.apache.http.HeaderElement; import org.apache.http.HttpEntity; @@ -33,22 +38,29 @@ import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.SystemDefaultHttpClient; import org.apache.http.util.EntityUtils; import org.junit.After; +import org.junit.Ignore; import org.junit.Test; import org.mockito.ArgumentCaptor; +import com.github.jsonldjava.utils.JsonUtils; + @SuppressWarnings("unchecked") public class DocumentLoaderTest { - DocumentLoader documentLoader = new DocumentLoader(); + private final DocumentLoader documentLoader = new DocumentLoader(); + + @After + public void setContextClassLoader() { + Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); + } @Test public void fromURLTest0001() throws Exception { final URL contexttest = getClass().getResource("/custom/contexttest-0001.jsonld"); assertNotNull(contexttest); - final Object context = documentLoader.fromURL(contexttest); + final Object context = JsonUtils.fromURL(contexttest, documentLoader.getHttpClient()); assertTrue(context instanceof Map); final Map contextMap = (Map) context; assertEquals(1, contextMap.size()); @@ -63,7 +75,7 @@ public void fromURLTest0001() throws Exception { public void fromURLTest0002() throws Exception { final URL contexttest = getClass().getResource("/custom/contexttest-0002.jsonld"); assertNotNull(contexttest); - final Object context = documentLoader.fromURL(contexttest); + final Object context = JsonUtils.fromURL(contexttest, documentLoader.getHttpClient()); assertTrue(context instanceof List); final List> contextList = (List>) context; @@ -83,11 +95,20 @@ public void fromURLTest0002() throws Exception { assertEquals("ex:term2", term2.get("@id")); } + @Test + public void fromURLBomTest0004() throws Exception { + final URL contexttest = getClass().getResource("/custom/contexttest-0004.jsonld"); + assertNotNull(contexttest); + final Object context = JsonUtils.fromURL(contexttest, documentLoader.getHttpClient()); + assertTrue(context instanceof Map); + assertFalse(((Map) context).isEmpty()); + } + // @Ignore("Integration test") @Test public void fromURLredirectHTTPSToHTTP() throws Exception { final URL url = new URL("https://w3id.org/bundle/context"); - final Object context = documentLoader.fromURL(url); + final Object context = JsonUtils.fromURL(url, documentLoader.getHttpClient()); // Should not fail because of // http://stackoverflow.com/questions/1884230/java-doesnt-follow-redirect-in-urlconnection // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4620571 @@ -99,21 +120,66 @@ public void fromURLredirectHTTPSToHTTP() throws Exception { @Test public void fromURLredirect() throws Exception { final URL url = new URL("http://purl.org/wf4ever/ro-bundle/context.json"); - final Object context = documentLoader.fromURL(url); + final Object context = JsonUtils.fromURL(url, documentLoader.getHttpClient()); assertTrue(context instanceof Map); assertFalse(((Map) context).isEmpty()); } + // @Ignore("Integration test") + @Test + public void loadDocumentWf4ever() throws Exception { + final RemoteDocument document = documentLoader + .loadDocument("http://purl.org/wf4ever/ro-bundle/context.json"); + final Object context = document.getDocument(); + assertTrue(context instanceof Map); + assertFalse(((Map) context).isEmpty()); + } + + @Ignore("Schema.org started to redirect from HTTP to HTTPS which breaks the Java HttpURLConnection API") + @Test + public void fromURLSchemaOrgNoApacheHttpClient() throws Exception { + final URL url = new URL("http://schema.org/"); + + final HttpURLConnection urlConn = (HttpURLConnection) url.openConnection(); + urlConn.addRequestProperty("Accept", "application/ld+json"); + + final StringWriter output = new StringWriter(); + try (final InputStream directStream = urlConn.getInputStream();) { + IOUtils.copy(directStream, output, Charset.forName("UTF-8")); + } + final Object context = JsonUtils.fromReader(new StringReader(output.toString())); + assertTrue(context instanceof Map); + assertFalse(((Map) context).isEmpty()); + } + + @Test + public void loadDocumentSchemaOrg() throws Exception { + final RemoteDocument document = documentLoader.loadDocument("http://schema.org/"); + final Object context = document.getDocument(); + assertTrue(context instanceof Map); + assertFalse(((Map) context).isEmpty()); + } + + @Test + public void loadDocumentSchemaOrgDirect() throws Exception { + final RemoteDocument document = documentLoader + .loadDocument("http://schema.org/docs/jsonldcontext.json"); + final Object context = document.getDocument(); + assertTrue(context instanceof Map); + assertFalse(((Map) context).isEmpty()); + } + + @Ignore("Caching failed without any apparent cause on the client side") @Test public void fromURLCache() throws Exception { - final URL url = new URL("http://json-ld.org/contexts/person.jsonld"); - documentLoader.fromURL(url); + final URL url = new URL("https://json-ld.org/contexts/person.jsonld"); + JsonUtils.fromURL(url, documentLoader.getHttpClient()); // Now try to get it again and ensure it is // cached final HttpClient clientCached = documentLoader.getHttpClient(); final HttpUriRequest getCached = new HttpGet(url.toURI()); - getCached.setHeader("Accept", DocumentLoader.ACCEPT_HEADER); + getCached.setHeader("Accept", JsonUtils.ACCEPT_HEADER); final HttpCacheContext localContextCached = HttpCacheContext.create(); final HttpResponse respoCached = clientCached.execute(getCached, localContextCached); EntityUtils.consume(respoCached.getEntity()); @@ -147,7 +213,7 @@ public InputStream getInputStream() throws IOException { }; final URL url = new URL(null, "jsonldtest:context", handler); assertEquals(0, requests.get()); - final Object context = documentLoader.fromURL(url); + final Object context = JsonUtils.fromURL(url, documentLoader.getHttpClient()); assertEquals(1, requests.get()); assertTrue(context instanceof Map); assertFalse(((Map) context).isEmpty()); @@ -176,10 +242,11 @@ public void fromURLAcceptHeaders() throws Exception { .forClass(HttpUriRequest.class); documentLoader.setHttpClient(fakeHttpClient(httpRequest)); try { - final Object context = documentLoader.fromURL(url); + final Object context = JsonUtils.fromURL(url, documentLoader.getHttpClient()); assertTrue(context instanceof Map); } finally { documentLoader.setHttpClient(null); + assertSame(documentLoader.getHttpClient(), new DocumentLoader().getHttpClient()); } assertEquals(1, httpRequest.getAllValues().size()); final HttpUriRequest req = httpRequest.getValue(); @@ -187,7 +254,7 @@ public void fromURLAcceptHeaders() throws Exception { final Header[] accept = req.getHeaders("Accept"); assertEquals(1, accept.length); - assertEquals(DocumentLoader.ACCEPT_HEADER, accept[0].getValue()); + assertEquals(JsonUtils.ACCEPT_HEADER, accept[0].getValue()); // Test that this header parses correctly final HeaderElement[] elems = accept[0].getElements(); assertEquals("application/ld+json", elems[0].getName()); @@ -220,8 +287,8 @@ public void fromURLAcceptHeaders() throws Exception { public void jarCacheHit() throws Exception { // If no cache, should fail-fast as nonexisting.example.com is not in // DNS - final Object context = documentLoader.fromURL(new URL( - "http://nonexisting.example.com/context")); + final Object context = JsonUtils.fromURL(new URL("http://nonexisting.example.com/context"), + documentLoader.getHttpClient()); assertTrue(context instanceof Map); assertTrue(((Map) context).containsKey("@context")); } @@ -229,21 +296,16 @@ public void jarCacheHit() throws Exception { @Test(expected = IOException.class) public void jarCacheMiss404() throws Exception { // Should fail-fast as nonexisting.example.com is not in DNS - final Object context = documentLoader - .fromURL(new URL("http://nonexisting.example.com/miss")); - } - - @After - public void setContextClassLoader() { - Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); + JsonUtils.fromURL(new URL("http://nonexisting.example.com/miss"), + documentLoader.getHttpClient()); } @Test(expected = IOException.class) public void jarCacheMissThreadCtx() throws Exception { final URLClassLoader findNothingCL = new URLClassLoader(new URL[] {}, null); Thread.currentThread().setContextClassLoader(findNothingCL); - final Object context = documentLoader.fromURL(new URL( - "http://nonexisting.example.com/context")); + JsonUtils.fromURL(new URL("http://nonexisting.example.com/context"), + documentLoader.getHttpClient()); } @Test @@ -251,7 +313,15 @@ public void jarCacheHitThreadCtx() throws Exception { final URL url = new URL("http://nonexisting.example.com/nested/hello"); final URL nestedJar = getClass().getResource("/nested.jar"); try { - final Object hello = documentLoader.fromURL(url); + JsonUtils.fromURL(url, documentLoader.getHttpClient()); + fail("Should not be able to find nested/hello yet"); + } catch (final IOException ex) { + // expected + } + + Thread.currentThread().setContextClassLoader(null); + try { + JsonUtils.fromURL(url, documentLoader.getHttpClient()); fail("Should not be able to find nested/hello yet"); } catch (final IOException ex) { // expected @@ -259,7 +329,7 @@ public void jarCacheHitThreadCtx() throws Exception { final ClassLoader cl = new URLClassLoader(new URL[] { nestedJar }); Thread.currentThread().setContextClassLoader(cl); - final Object hello = documentLoader.fromURL(url); + final Object hello = JsonUtils.fromURL(url, documentLoader.getHttpClient()); assertTrue(hello instanceof Map); assertEquals("World!", ((Map) hello).get("Hello")); } @@ -273,36 +343,95 @@ public void sharedHttpClient() throws Exception { @Test public void differentHttpClient() throws Exception { // Custom http client - documentLoader.setHttpClient(new SystemDefaultHttpClient()); - assertNotSame(documentLoader.getHttpClient(), new DocumentLoader().getHttpClient()); - - // Use default again - documentLoader.setHttpClient(null); - assertSame(documentLoader.getHttpClient(), new DocumentLoader().getHttpClient()); + try { + documentLoader.setHttpClient(JsonUtils.createDefaultHttpClient()); + assertNotSame(documentLoader.getHttpClient(), new DocumentLoader().getHttpClient()); + } finally { + // Use default again + documentLoader.setHttpClient(null); + assertSame(documentLoader.getHttpClient(), new DocumentLoader().getHttpClient()); + } } @Test public void testDisallowRemoteContexts() throws Exception { - String testUrl = "http://json-ld.org/contexts/person.jsonld"; - Object test = documentLoader.loadDocument(testUrl); + final String testUrl = "http://json-ld.org/contexts/person.jsonld"; + final Object test = documentLoader.loadDocument(testUrl); assertNotNull( "Was not able to fetch from URL before testing disallow remote contexts loading", test); - String disallowProperty = System + final String disallowProperty = System .getProperty(DocumentLoader.DISALLOW_REMOTE_CONTEXT_LOADING); try { System.setProperty(DocumentLoader.DISALLOW_REMOTE_CONTEXT_LOADING, "true"); documentLoader.loadDocument(testUrl); fail("Expected exception to occur"); - } catch (JsonLdError e) { + } catch (final JsonLdError e) { assertEquals(JsonLdError.Error.LOADING_REMOTE_CONTEXT_FAILED, e.getType()); } finally { if (disallowProperty == null) { System.clearProperty(DocumentLoader.DISALLOW_REMOTE_CONTEXT_LOADING); } else { - System.setProperty(DocumentLoader.DISALLOW_REMOTE_CONTEXT_LOADING, disallowProperty); + System.setProperty(DocumentLoader.DISALLOW_REMOTE_CONTEXT_LOADING, + disallowProperty); + } + } + } + + @Test + public void testInjectContext() throws Exception { + injectContext(new JsonLdOptions()); + } + + @Test + public void testIssue304_remoteContextAndBaseIri() throws Exception { + injectContext(new JsonLdOptions("testing:baseIri")); + } + + private void injectContext(final JsonLdOptions options) throws Exception { + + final Object jsonObject = JsonUtils.fromString( + "{ \"@context\":\"http://nonexisting.example.com/thing\", \"pony\":5 }"); + + // Verify fails to find context by default + try { + JsonLdProcessor.expand(jsonObject, options); + fail("Expected exception to occur"); + } catch (final JsonLdError e) { + // Success + } + + // Inject context + final DocumentLoader dl = new DocumentLoader(); + dl.addInjectedDoc("http://nonexisting.example.com/thing", + "{ \"@context\": { \"pony\":\"http://nonexisting.example.com/thing/pony\" } }"); + options.setDocumentLoader(dl); + + // Execute + final List expand = JsonLdProcessor.expand(jsonObject, options); + + // Verify result + final Object v = ((Map) ((List) ((Map) expand + .get(0)).get("http://nonexisting.example.com/thing/pony")).get(0)).get("@value"); + assertEquals(5, v); + } + + @Test + public void testRemoteContextCaching() throws Exception { + final String[] urls = { "http://schema.org/", "http://schema.org/docs/jsonldcontext.json" }; + for (final String url : urls) { + final long start = System.currentTimeMillis(); + for (int i = 1; i <= 1000; i++) { + documentLoader.loadDocument(url); + + final long seconds = (System.currentTimeMillis() - start) / 1000; + + if (seconds > 60) { + fail(String.format("Took %s seconds to access %s %s times", seconds, url, i)); + break; + } } } } diff --git a/core/src/test/java/com/github/jsonldjava/core/JsonLdFramingTest.java b/core/src/test/java/com/github/jsonldjava/core/JsonLdFramingTest.java index 882b5d5e..bacec894 100644 --- a/core/src/test/java/com/github/jsonldjava/core/JsonLdFramingTest.java +++ b/core/src/test/java/com/github/jsonldjava/core/JsonLdFramingTest.java @@ -1,24 +1,198 @@ package com.github.jsonldjava.core; -import com.github.jsonldjava.utils.JsonUtils; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + import java.io.IOException; import java.util.Map; import org.junit.Test; -import static org.junit.Assert.*; + +import com.github.jsonldjava.utils.JsonUtils; public class JsonLdFramingTest { @Test public void testFrame0001() throws IOException, JsonLdError { - Object frame = JsonUtils.fromInputStream( - getClass().getResourceAsStream("/custom/frame-0001-frame.jsonld")); - Object in = JsonUtils.fromInputStream( - getClass().getResourceAsStream("/custom/frame-0001-in.jsonld")); + final Object frame = JsonUtils + .fromInputStream(getClass().getResourceAsStream("/custom/frame-0001-frame.jsonld")); + final Object in = JsonUtils + .fromInputStream(getClass().getResourceAsStream("/custom/frame-0001-in.jsonld")); + + final Map frame2 = JsonLdProcessor.frame(in, frame, new JsonLdOptions()); - Map frame2 = JsonLdProcessor.frame(in, frame, new JsonLdOptions()); - assertEquals(2, frame2.size()); } + @Test + public void testFrame0002() throws IOException, JsonLdError { + final Object frame = JsonUtils + .fromInputStream(getClass().getResourceAsStream("/custom/frame-0002-frame.jsonld")); + final Object in = JsonUtils + .fromInputStream(getClass().getResourceAsStream("/custom/frame-0002-in.jsonld")); + + final JsonLdOptions opts = new JsonLdOptions(); + opts.setCompactArrays(false); + final Map frame2 = JsonLdProcessor.frame(in, frame, opts); + + final Object out = JsonUtils + .fromInputStream(getClass().getResourceAsStream("/custom/frame-0002-out.jsonld")); + assertEquals(out, frame2); + } + + @Test + public void testFrame0003() throws IOException, JsonLdError { + final Object frame = JsonUtils + .fromInputStream(getClass().getResourceAsStream("/custom/frame-0002-frame.jsonld")); + final Object in = JsonUtils + .fromInputStream(getClass().getResourceAsStream("/custom/frame-0002-in.jsonld")); + + final JsonLdOptions opts = new JsonLdOptions(); + opts.setCompactArrays(false); + opts.setProcessingMode("json-ld-1.1"); + final Map frame2 = JsonLdProcessor.frame(in, frame, opts); + assertFalse("Result should contain no blank nodes", frame2.toString().contains("_:")); + + final Object out = JsonUtils + .fromInputStream(getClass().getResourceAsStream("/custom/frame-0003-out.jsonld")); + assertEquals(out, frame2); + } + + @Test + public void testFrame0004() throws IOException, JsonLdError { + final Object frame = JsonUtils + .fromInputStream(getClass().getResourceAsStream("/custom/frame-0004-frame.jsonld")); + final Object in = JsonUtils + .fromInputStream(getClass().getResourceAsStream("/custom/frame-0004-in.jsonld")); + + final JsonLdOptions opts = new JsonLdOptions(); + opts.setCompactArrays(true); + final Map frame2 = JsonLdProcessor.frame(in, frame, opts); + + final Object out = JsonUtils + .fromInputStream(getClass().getResourceAsStream("/custom/frame-0004-out.jsonld")); + assertEquals(out, frame2); + } + + @Test + public void testFrame0005() throws IOException, JsonLdError { + final Object frame = JsonUtils + .fromInputStream(getClass().getResourceAsStream("/custom/frame-0005-frame.jsonld")); + final Object in = JsonUtils + .fromInputStream(getClass().getResourceAsStream("/custom/frame-0005-in.jsonld")); + + final JsonLdOptions opts = new JsonLdOptions(); + opts.setCompactArrays(true); + final Map frame2 = JsonLdProcessor.frame(in, frame, opts); + + final Object out = JsonUtils + .fromInputStream(getClass().getResourceAsStream("/custom/frame-0005-out.jsonld")); + assertEquals(out, frame2); + } + + @Test + public void testFrame0006() throws IOException, JsonLdError { + final Object frame = JsonUtils + .fromInputStream(getClass().getResourceAsStream("/custom/frame-0006-frame.jsonld")); + final Object in = JsonUtils + .fromInputStream(getClass().getResourceAsStream("/custom/frame-0006-in.jsonld")); + + final JsonLdOptions opts = new JsonLdOptions(); + opts.setCompactArrays(true); + final Map frame2 = JsonLdProcessor.frame(in, frame, opts); + + final Object out = JsonUtils + .fromInputStream(getClass().getResourceAsStream("/custom/frame-0006-out.jsonld")); + assertEquals(out, frame2); + } + + @Test + public void testFrame0007() throws IOException, JsonLdError { + final Object frame = JsonUtils + .fromInputStream(getClass().getResourceAsStream("/custom/frame-0007-frame.jsonld")); + final Object in = JsonUtils + .fromInputStream(getClass().getResourceAsStream("/custom/frame-0007-in.jsonld")); + + final JsonLdOptions opts = new JsonLdOptions(); + opts.setCompactArrays(true); + final Map frame2 = JsonLdProcessor.frame(in, frame, opts); + + final Object out = JsonUtils + .fromInputStream(getClass().getResourceAsStream("/custom/frame-0007-out.jsonld")); + assertEquals(out, frame2); + } + + @Test + public void testFrame0008() throws IOException, JsonLdError { + final Object frame = JsonUtils + .fromInputStream(getClass().getResourceAsStream("/custom/frame-0008-frame.jsonld")); + final Object in = JsonUtils + .fromInputStream(getClass().getResourceAsStream("/custom/frame-0008-in.jsonld")); + + final JsonLdOptions opts = new JsonLdOptions(); + opts.setEmbed("@always"); + final Map frame2 = JsonLdProcessor.frame(in, frame, opts); + + final Object out = JsonUtils + .fromInputStream(getClass().getResourceAsStream("/custom/frame-0008-out.jsonld")); + assertEquals(out, frame2); + } + + @Test + public void testFrame0009() throws IOException, JsonLdError { + final Object frame = JsonUtils + .fromInputStream(getClass().getResourceAsStream("/custom/frame-0009-frame.jsonld")); + final Object in = JsonUtils + .fromInputStream(getClass().getResourceAsStream("/custom/frame-0009-in.jsonld")); + + final JsonLdOptions opts = new JsonLdOptions(); + opts.setProcessingMode("json-ld-1.1"); + final Map frame2 = JsonLdProcessor.frame(in, frame, opts); + final Object out = JsonUtils + .fromInputStream(getClass().getResourceAsStream("/custom/frame-0009-out.jsonld")); + assertEquals(out, frame2); + } + + @Test + public void testFrame0010() throws IOException, JsonLdError { + final Object frame = JsonUtils + .fromInputStream(getClass().getResourceAsStream("/custom/frame-0010-frame.jsonld")); + // { + // "@id": "http://example.com/main/id", + // "http://www.w3.org/1999/02/22-rdf-syntax-ns#type": { + // "@id": "http://example.com/rdf/id", + // "http://www.w3.org/1999/02/22-rdf-syntax-ns#label": "someLabel" + // } + // } + final RDFDataset ds = new RDFDataset(); + ds.addTriple("http://example.com/main/id", + "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", "http://example.com/rdf/id"); + ds.addTriple("http://example.com/rdf/id", + "http://www.w3.org/1999/02/22-rdf-syntax-ns#label", "someLabel", null, null); + final JsonLdOptions opts = new JsonLdOptions(); + opts.setProcessingMode(JsonLdOptions.JSON_LD_1_0); + + final Object in = new JsonLdApi(opts).fromRDF(ds, true); + + final Map frame2 = JsonLdProcessor.frame(in, frame, opts); + final Object out = JsonUtils + .fromInputStream(getClass().getResourceAsStream("/custom/frame-0010-out.jsonld")); + assertEquals(out, frame2); + } + + @Test + public void testFrame0011() throws IOException, JsonLdError { + final Object frame = JsonUtils + .fromInputStream(getClass().getResourceAsStream("/custom/frame-0011-frame.jsonld")); + final Object in = JsonUtils + .fromInputStream(getClass().getResourceAsStream("/custom/frame-0011-in.jsonld")); + + final JsonLdOptions opts = new JsonLdOptions(); + final Map frame2 = JsonLdProcessor.frame(in, frame, opts); + + final Object out = JsonUtils + .fromInputStream(getClass().getResourceAsStream("/custom/frame-0011-out.jsonld")); + assertEquals(out, frame2); + } + } diff --git a/core/src/test/java/com/github/jsonldjava/core/JsonLdPerformanceTest.java b/core/src/test/java/com/github/jsonldjava/core/JsonLdPerformanceTest.java index baa72e88..5fce5a48 100644 --- a/core/src/test/java/com/github/jsonldjava/core/JsonLdPerformanceTest.java +++ b/core/src/test/java/com/github/jsonldjava/core/JsonLdPerformanceTest.java @@ -3,13 +3,28 @@ */ package com.github.jsonldjava.core; +import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.LongSummaryStatistics; +import java.util.Random; +import java.util.function.Function; import java.util.zip.GZIPInputStream; +import org.apache.commons.io.FileUtils; +import org.junit.Before; import org.junit.Ignore; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import com.github.jsonldjava.core.RDFDataset.Quad; import com.github.jsonldjava.utils.JsonUtils; /** @@ -18,6 +33,16 @@ */ public class JsonLdPerformanceTest { + @Rule + public TemporaryFolder tempDir = new TemporaryFolder(); + + private File testDir; + + @Before + public void setUp() throws Exception { + testDir = tempDir.newFolder("jsonld-perf-tests-"); + } + /** * Test performance parsing using test data from: * @@ -27,18 +52,590 @@ public class JsonLdPerformanceTest { */ @Ignore("Enable as necessary for manual testing, particularly to test that it fails due to irregular URIs") @Test - public final void test() throws Exception { - final long parseStart = System.currentTimeMillis(); - final Object inputObject = JsonUtils.fromInputStream(new GZIPInputStream( - new FileInputStream(new File("/home/ans025/Downloads/2000007922.jsonld.gz")))); - final long parseEnd = System.currentTimeMillis(); - System.out.printf("Parse time: %d", (parseEnd - parseStart)); - final JsonLdOptions opts = new JsonLdOptions("urn:test:"); + public final void testPerformance1() throws Exception { + testCompaction("Long", new GZIPInputStream( + new FileInputStream(new File("/home/peter/Downloads/2000007922.jsonld.gz")))); + } + + /** + * Test performance parsing using test data from: + * + * https://github.com/jsonld-java/jsonld-java/files/245372/jsonldperfs.zip + * + * @throws Exception + */ + @Ignore("Enable as necessary to test performance") + @Test + public final void testLaxMergeValuesPerfFast() throws Exception { + testCompaction("Fast", + new FileInputStream(new File("/home/peter/Downloads/jsonldperfs/fast.jsonld"))); + } + + /** + * Test performance parsing using test data from: + * + * https://github.com/jsonld-java/jsonld-java/files/245372/jsonldperfs.zip + * + * @throws Exception + */ + @Ignore("Enable as necessary to test performance") + @Test + public final void testLaxMergeValuesPerfSlow() throws Exception { + testCompaction("Slow", + new FileInputStream(new File("/home/peter/Downloads/jsonldperfs/slow.jsonld"))); + } + + private void testCompaction(String label, InputStream nextInputStream) + throws IOException, FileNotFoundException, JsonLdError { + final File testFile = File.createTempFile("jsonld-perf-source-", ".jsonld", testDir); + FileUtils.copyInputStreamToFile(nextInputStream, testFile); + + final LongSummaryStatistics parseStats = new LongSummaryStatistics(); + final LongSummaryStatistics compactStats = new LongSummaryStatistics(); + + for (int i = 0; i < 1000; i++) { + final InputStream testInput = new BufferedInputStream(new FileInputStream(testFile)); + try { + final long parseStart = System.currentTimeMillis(); + final Object inputObject = JsonUtils.fromInputStream(testInput); + parseStats.accept(System.currentTimeMillis() - parseStart); + final JsonLdOptions opts = new JsonLdOptions("urn:test:"); + + final long compactStart = System.currentTimeMillis(); + JsonLdProcessor.compact(inputObject, null, opts); + compactStats.accept(System.currentTimeMillis() - compactStart); + } finally { + testInput.close(); + } + } + + System.out.println("(" + label + ") Parse average : " + parseStats.getAverage()); + System.out.println("(" + label + ") Compact average : " + compactStats.getAverage()); + } + + @Ignore("Disable performance tests by default") + @Test + public final void testPerformanceRandom() throws Exception { + final Random prng = new Random(); + final int rounds = 10000; + + final String exNs = "http://example.org/"; + + final String bnode = "_:anon"; + final String uri1 = exNs + "a1"; + final String uri2 = exNs + "b2"; + final String uri3 = exNs + "c3"; + final List potentialSubjects = new ArrayList(); + potentialSubjects.add(bnode); + potentialSubjects.add(uri1); + potentialSubjects.add(uri2); + potentialSubjects.add(uri3); + for (int i = 0; i < 50; i++) { + potentialSubjects.add("_:" + i); + } + for (int i = 1; i < 50; i++) { + potentialSubjects.add("_:a" + Integer.toHexString(i).toUpperCase()); + } + for (int i = 0; i < 200; i++) { + potentialSubjects + .add(exNs + Integer.toHexString(i) + "/z" + Integer.toOctalString(i % 20)); + } + Collections.shuffle(potentialSubjects, prng); + + final List potentialObjects = new ArrayList(); + potentialObjects.addAll(potentialSubjects); + Collections.shuffle(potentialObjects, prng); + + final List potentialPredicates = new ArrayList(); + potentialPredicates.add(JsonLdConsts.RDF_TYPE); + potentialPredicates.add(JsonLdConsts.RDF_LIST); + potentialPredicates.add(JsonLdConsts.RDF_NIL); + potentialPredicates.add(JsonLdConsts.RDF_FIRST); + potentialPredicates.add(JsonLdConsts.RDF_OBJECT); + potentialPredicates.add(JsonLdConsts.XSD_STRING); + Collections.shuffle(potentialPredicates, prng); + + final RDFDataset testData = new RDFDataset(); + + for (int i = 0; i < 2000; i++) { + final String nextObject = potentialObjects.get(prng.nextInt(potentialObjects.size())); + boolean isLiteral = true; + if (nextObject.startsWith("_:") || nextObject.startsWith("http://")) { + isLiteral = false; + } + if (isLiteral) { + if (i % 2 == 0) { + testData.addQuad(potentialSubjects.get(prng.nextInt(potentialSubjects.size())), + potentialPredicates.get(prng.nextInt(potentialPredicates.size())), + nextObject, JsonLdConsts.XSD_STRING, + potentialSubjects.get(prng.nextInt(potentialSubjects.size())), null); + } else if (i % 5 == 0) { + testData.addTriple( + potentialSubjects.get(prng.nextInt(potentialSubjects.size())), + potentialPredicates.get(prng.nextInt(potentialPredicates.size())), + nextObject, JsonLdConsts.RDF_LANGSTRING, "en"); + } + } else { + if (i % 2 == 0) { + testData.addQuad(potentialSubjects.get(prng.nextInt(potentialSubjects.size())), + potentialPredicates.get(prng.nextInt(potentialPredicates.size())), + nextObject, + potentialSubjects.get(prng.nextInt(potentialSubjects.size()))); + } else if (i % 5 == 0) { + testData.addTriple( + potentialSubjects.get(prng.nextInt(potentialSubjects.size())), + potentialPredicates.get(prng.nextInt(potentialPredicates.size())), + nextObject); + } + } + } + + System.out.println( + "RDF triples to JSON-LD (internal objects, not parsed from a document)..."); + final JsonLdOptions options = new JsonLdOptions(); + final JsonLdApi jsonLdApi = new JsonLdApi(options); + final int[] hashCodes = new int[rounds]; + final LongSummaryStatistics statsFirst5000 = new LongSummaryStatistics(); + final LongSummaryStatistics stats = new LongSummaryStatistics(); + for (int i = 0; i < rounds; i++) { + final long start = System.nanoTime(); + Object fromRDF = jsonLdApi.fromRDF(testData); + if (i < 5000) { + statsFirst5000.accept(System.nanoTime() - start); + } else { + stats.accept(System.nanoTime() - start); + } + hashCodes[i] = fromRDF.hashCode(); + fromRDF = null; + } + System.out.println("First 5000 out of " + rounds); + System.out.println("Average: " + statsFirst5000.getAverage() / 100000); + System.out.println("Sum: " + statsFirst5000.getSum() / 100000); + System.out.println("Maximum: " + statsFirst5000.getMax() / 100000); + System.out.println("Minimum: " + statsFirst5000.getMin() / 100000); + System.out.println("Count: " + statsFirst5000.getCount()); + + System.out.println("Post 5000 out of " + rounds); + System.out.println("Average: " + stats.getAverage() / 100000); + System.out.println("Sum: " + stats.getSum() / 100000); + System.out.println("Maximum: " + stats.getMax() / 100000); + System.out.println("Minimum: " + stats.getMin() / 100000); + System.out.println("Count: " + stats.getCount()); + + System.out.println( + "RDF triples to JSON-LD (internal objects, not parsed from a document), using laxMergeValue..."); + final JsonLdOptions optionsLax = new JsonLdOptions(); + final JsonLdApi jsonLdApiLax = new JsonLdApi(optionsLax); + final int[] hashCodesLax = new int[rounds]; + final LongSummaryStatistics statsLaxFirst5000 = new LongSummaryStatistics(); + final LongSummaryStatistics statsLax = new LongSummaryStatistics(); + for (int i = 0; i < rounds; i++) { + final long start = System.nanoTime(); + Object fromRDF = jsonLdApiLax.fromRDF(testData, true); + if (i < 5000) { + statsLaxFirst5000.accept(System.nanoTime() - start); + } else { + statsLax.accept(System.nanoTime() - start); + } + hashCodesLax[i] = fromRDF.hashCode(); + fromRDF = null; + } + System.out.println("First 5000 out of " + rounds); + System.out.println("Average: " + statsLaxFirst5000.getAverage() / 100000); + System.out.println("Sum: " + statsLaxFirst5000.getSum() / 100000); + System.out.println("Maximum: " + statsLaxFirst5000.getMax() / 100000); + System.out.println("Minimum: " + statsLaxFirst5000.getMin() / 100000); + System.out.println("Count: " + statsLaxFirst5000.getCount()); + + System.out.println("Post 5000 out of " + rounds); + System.out.println("Average: " + statsLax.getAverage() / 100000); + System.out.println("Sum: " + statsLax.getSum() / 100000); + System.out.println("Maximum: " + statsLax.getMax() / 100000); + System.out.println("Minimum: " + statsLax.getMin() / 100000); + System.out.println("Count: " + statsLax.getCount()); + + System.out.println("Non-pretty print benchmarking..."); + final JsonLdOptions options2 = new JsonLdOptions(); + final JsonLdApi jsonLdApi2 = new JsonLdApi(options2); + final LongSummaryStatistics statsFirst5000Part2 = new LongSummaryStatistics(); + final LongSummaryStatistics statsPart2 = new LongSummaryStatistics(); + final Object fromRDF2 = jsonLdApi2.fromRDF(testData); + for (int i = 0; i < rounds; i++) { + final long start = System.nanoTime(); + JsonUtils.toString(fromRDF2); + if (i < 5000) { + statsFirst5000Part2.accept(System.nanoTime() - start); + } else { + statsPart2.accept(System.nanoTime() - start); + } + } + System.out.println("First 5000 out of " + rounds); + System.out.println("Average: " + statsFirst5000Part2.getAverage() / 100000); + System.out.println("Sum: " + statsFirst5000Part2.getSum() / 100000); + System.out.println("Maximum: " + statsFirst5000Part2.getMax() / 100000); + System.out.println("Minimum: " + statsFirst5000Part2.getMin() / 100000); + System.out.println("Count: " + statsFirst5000Part2.getCount()); + + System.out.println("Post 5000 out of " + rounds); + System.out.println("Average: " + statsPart2.getAverage() / 100000); + System.out.println("Sum: " + statsPart2.getSum() / 100000); + System.out.println("Maximum: " + statsPart2.getMax() / 100000); + System.out.println("Minimum: " + statsPart2.getMin() / 100000); + System.out.println("Count: " + statsPart2.getCount()); + + System.out.println("Pretty print benchmarking..."); + final JsonLdOptions options3 = new JsonLdOptions(); + final JsonLdApi jsonLdApi3 = new JsonLdApi(options3); + final LongSummaryStatistics statsFirst5000Part3 = new LongSummaryStatistics(); + final LongSummaryStatistics statsPart3 = new LongSummaryStatistics(); + final Object fromRDF3 = jsonLdApi3.fromRDF(testData); + for (int i = 0; i < rounds; i++) { + final long start = System.nanoTime(); + JsonUtils.toPrettyString(fromRDF3); + if (i < 5000) { + statsFirst5000Part3.accept(System.nanoTime() - start); + } else { + statsPart3.accept(System.nanoTime() - start); + } + } + System.out.println("First 5000 out of " + rounds); + System.out.println("Average: " + statsFirst5000Part3.getAverage() / 100000); + System.out.println("Sum: " + statsFirst5000Part3.getSum() / 100000); + System.out.println("Maximum: " + statsFirst5000Part3.getMax() / 100000); + System.out.println("Minimum: " + statsFirst5000Part3.getMin() / 100000); + System.out.println("Count: " + statsFirst5000Part3.getCount()); + + System.out.println("Post 5000 out of " + rounds); + System.out.println("Average: " + statsPart3.getAverage() / 100000); + System.out.println("Sum: " + statsPart3.getSum() / 100000); + System.out.println("Maximum: " + statsPart3.getMax() / 100000); + System.out.println("Minimum: " + statsPart3.getMin() / 100000); + System.out.println("Count: " + statsPart3.getCount()); + + System.out.println("Expansion benchmarking..."); + final JsonLdOptions options4 = new JsonLdOptions(); + final JsonLdApi jsonLdApi4 = new JsonLdApi(options4); + final LongSummaryStatistics statsFirst5000Part4 = new LongSummaryStatistics(); + final LongSummaryStatistics statsPart4 = new LongSummaryStatistics(); + final Object fromRDF4 = jsonLdApi4.fromRDF(testData); + for (int i = 0; i < rounds; i++) { + final long start = System.nanoTime(); + JsonLdProcessor.expand(fromRDF4, options4); + if (i < 5000) { + statsFirst5000Part4.accept(System.nanoTime() - start); + } else { + statsPart4.accept(System.nanoTime() - start); + } + } + System.out.println("First 5000 out of " + rounds); + System.out.println("Average: " + statsFirst5000Part4.getAverage() / 100000); + System.out.println("Sum: " + statsFirst5000Part4.getSum() / 100000); + System.out.println("Maximum: " + statsFirst5000Part4.getMax() / 100000); + System.out.println("Minimum: " + statsFirst5000Part4.getMin() / 100000); + System.out.println("Count: " + statsFirst5000Part4.getCount()); + + System.out.println("Post 5000 out of " + rounds); + System.out.println("Average: " + statsPart4.getAverage() / 100000); + System.out.println("Sum: " + statsPart4.getSum() / 100000); + System.out.println("Maximum: " + statsPart4.getMax() / 100000); + System.out.println("Minimum: " + statsPart4.getMin() / 100000); + System.out.println("Count: " + statsPart4.getCount()); + + } + + /** + * many triples with same subject and prop: current implementation is slow + * + * @author fpservant + */ + @Ignore("Disable performance tests by default") + @Test + public final void slowVsFast5Predicates() throws Exception { + + final String ns = "http://www.example.com/foo/"; + + final Function subjectGenerator = new Function() { + @Override + public String apply(Integer index) { + return ns + "s"; + } + }; + final Function predicateGenerator = new Function() { + @Override + public String apply(Integer index) { + return ns + "p" + Integer.toString(index % 5); + } + }; + final Function objectGenerator = new Function() { + @Override + public String apply(Integer index) { + return ns + "o" + Integer.toString(index); + } + }; + final int tripleCount = 2000; + final int warmingRounds = 200; + final int rounds = 1000; + + runLaxVersusSlowToRDFTest("5 predicates", ns, subjectGenerator, predicateGenerator, + objectGenerator, tripleCount, warmingRounds, rounds); + + } + + /** + * many triples with same subject and prop: current implementation is slow + * + * @author fpservant + */ + @Ignore("Disable performance tests by default") + @Test + public final void slowVsFast2Predicates() throws Exception { + + final String ns = "http://www.example.com/foo/"; + + final Function subjectGenerator = new Function() { + @Override + public String apply(Integer index) { + return ns + "s"; + } + }; + final Function predicateGenerator = new Function() { + @Override + public String apply(Integer index) { + return ns + "p" + Integer.toString(index % 2); + } + }; + final Function objectGenerator = new Function() { + @Override + public String apply(Integer index) { + return ns + "o" + Integer.toString(index); + } + }; + final int tripleCount = 2000; + final int warmingRounds = 200; + final int rounds = 1000; + + runLaxVersusSlowToRDFTest("2 predicates", ns, subjectGenerator, predicateGenerator, + objectGenerator, tripleCount, warmingRounds, rounds); + + } + + /** + * many triples with same subject and prop: current implementation is slow + * + * @author fpservant + */ + @Ignore("Disable performance tests by default") + @Test + public final void slowVsFast1Predicate() throws Exception { + + final String ns = "http://www.example.com/foo/"; + + final Function subjectGenerator = new Function() { + @Override + public String apply(Integer index) { + return ns + "s"; + } + }; + final Function predicateGenerator = new Function() { + @Override + public String apply(Integer index) { + return ns + "p"; + } + }; + final Function objectGenerator = new Function() { + @Override + public String apply(Integer index) { + return ns + "o" + Integer.toString(index); + } + }; + final int tripleCount = 2000; + final int warmingRounds = 200; + final int rounds = 1000; + + runLaxVersusSlowToRDFTest("1 predicate", ns, subjectGenerator, predicateGenerator, + objectGenerator, tripleCount, warmingRounds, rounds); + + } + + /** + * many triples with same subject and prop: current implementation is slow + * + * @author fpservant + */ + @Ignore("Disable performance tests by default") + @Test + public final void slowVsFastMultipleSubjects1Predicate() throws Exception { + + final String ns = "http://www.example.com/foo/"; + + final Function subjectGenerator = new Function() { + @Override + public String apply(Integer index) { + return ns + "s" + Integer.toString(index % 100); + } + }; + final Function predicateGenerator = new Function() { + @Override + public String apply(Integer index) { + return ns + "p"; + } + }; + final Function objectGenerator = new Function() { + @Override + public String apply(Integer index) { + return ns + "o" + Integer.toString(index); + } + }; + final int tripleCount = 2000; + final int warmingRounds = 200; + final int rounds = 1000; + + runLaxVersusSlowToRDFTest("100 subjects and 1 predicate", ns, subjectGenerator, + predicateGenerator, objectGenerator, tripleCount, warmingRounds, rounds); + + } + + /** + * many triples with same subject and prop: current implementation is slow + * + * @author fpservant + */ + @Ignore("Disable performance tests by default") + @Test + public final void slowVsFastMultipleSubjects5Predicates() throws Exception { + + final String ns = "http://www.example.com/foo/"; + + final Function subjectGenerator = new Function() { + @Override + public String apply(Integer index) { + return ns + "s" + Integer.toString(index % 1000); + } + }; + final Function predicateGenerator = new Function() { + @Override + public String apply(Integer index) { + return ns + "p" + Integer.toString(index % 5); + } + }; + final Function objectGenerator = new Function() { + @Override + public String apply(Integer index) { + return ns + "o" + Integer.toString(index); + } + }; + final int tripleCount = 2000; + final int warmingRounds = 200; + final int rounds = 1000; + + runLaxVersusSlowToRDFTest("1000 subjects and 5 predicates", ns, subjectGenerator, + predicateGenerator, objectGenerator, tripleCount, warmingRounds, rounds); + + } + + /** + * Run a test on lax versus slow methods for toRDF. + * + * @param ns + * The namespace to assign + * @param subjectGenerator + * A {@link Function} used to generate the subject IRIs + * @param predicateGenerator + * A {@link Function} used to generate the predicate IRIs + * @param objectGenerator + * A {@link Function} used to generate the object IRIs + * @param tripleCount + * The number of triples to create for the dataset + * @param warmingRounds + * The number of warming rounds to use + * @param rounds + * The number of test rounds to use + * @throws JsonLdError + * If there is an error with the JSONLD processing. + */ + private void runLaxVersusSlowToRDFTest(final String label, final String ns, + Function subjectGenerator, + Function predicateGenerator, Function objectGenerator, + int tripleCount, int warmingRounds, int rounds) throws JsonLdError { + + System.out.println("Running test for lax versus slow for " + label); + + final RDFDataset inputRdf = new RDFDataset(); + inputRdf.setNamespace("ex", ns); - final long compactStart = System.currentTimeMillis(); - JsonLdProcessor.compact(inputObject, null, opts); - final long compactEnd = System.currentTimeMillis(); - System.out.printf("Compaction time: %d", (compactEnd - compactStart)); + for (int i = 0; i < tripleCount; i++) { + inputRdf.addTriple(subjectGenerator.apply(i), predicateGenerator.apply(i), + objectGenerator.apply(i)); + } + + final JsonLdOptions options = new JsonLdOptions(); + options.useNamespaces = true; + + // warming + for (int i = 0; i < warmingRounds; i++) { + new JsonLdApi(options).fromRDF(inputRdf); + // JsonLdProcessor.expand(new JsonLdApi(options).fromRDF(inputRdf)); + } + + for (int i = 0; i < warmingRounds; i++) { + new JsonLdApi(options).fromRDF(inputRdf, true); + // JsonLdProcessor.expand(new JsonLdApi(options).fromRDF(inputRdf, + // true)); + } + + System.out.println("Average time to parse a dataset containing " + tripleCount + + " different triples:"); + final long startLax = System.currentTimeMillis(); + for (int i = 0; i < rounds; i++) { + new JsonLdApi(options).fromRDF(inputRdf, true); + // JsonLdProcessor.expand(new JsonLdApi(options).fromRDF(inputRdf, + // true)); + } + System.out.println("\t- Assuming no duplicates: " + + (((System.currentTimeMillis() - startLax)) / rounds)); + + final long start = System.currentTimeMillis(); + for (int i = 0; i < rounds; i++) { + new JsonLdApi(options).fromRDF(inputRdf); + // JsonLdProcessor.expand(new JsonLdApi(options).fromRDF(inputRdf)); + } + System.out.println( + "\t- Assuming duplicates: " + (((System.currentTimeMillis() - start)) / rounds)); } + /** + * @author fpservant + */ + @Test + public final void duplicatedTriplesInAnRDFDataset() throws Exception { + final RDFDataset inputRdf = new RDFDataset(); + final String ns = "http://www.example.com/foo/"; + inputRdf.setNamespace("ex", ns); + inputRdf.addTriple(ns + "s", ns + "p", ns + "o"); + inputRdf.addTriple(ns + "s", ns + "p", ns + "o"); + + // System.out.println("Twice the same triple in RDFDataset:/n"); + for (final Quad quad : inputRdf.getQuads("@default")) { + // System.out.println(quad); + } + + final JsonLdOptions options = new JsonLdOptions(); + options.useNamespaces = true; + + // System.out.println("\nJSON-LD output is OK:\n"); + final Object fromRDF1 = JsonLdProcessor.compact(new JsonLdApi(options).fromRDF(inputRdf), + inputRdf.getContext(), options); + + final String jsonld1 = JsonUtils.toPrettyString(fromRDF1); + // System.out.println(jsonld1); + + // System.out.println( + // "\nWouldn't be the case assuming there is no duplicated triple in + // RDFDataset:\n"); + final Object fromRDF2 = JsonLdProcessor.compact( + new JsonLdApi(options).fromRDF(inputRdf, true), inputRdf.getContext(), options); + final String jsonld2 = JsonUtils.toPrettyString(fromRDF2); + // System.out.println(jsonld2); + + } } diff --git a/core/src/test/java/com/github/jsonldjava/core/JsonLdProcessorTest.java b/core/src/test/java/com/github/jsonldjava/core/JsonLdProcessorTest.java index d1562ec4..b3074db5 100644 --- a/core/src/test/java/com/github/jsonldjava/core/JsonLdProcessorTest.java +++ b/core/src/test/java/com/github/jsonldjava/core/JsonLdProcessorTest.java @@ -15,6 +15,7 @@ import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; @@ -37,7 +38,6 @@ import com.fasterxml.jackson.core.JsonGenerationException; import com.fasterxml.jackson.databind.JsonMappingException; -import com.github.jsonldjava.impl.TurtleTripleCallback; import com.github.jsonldjava.utils.JsonUtils; import com.github.jsonldjava.utils.Obj; import com.github.jsonldjava.utils.TestUtils; @@ -155,55 +155,21 @@ public static void prepareReportFrame() { private static final String reportOutputFile = "reports/report"; @AfterClass - public static void writeReport() throws JsonGenerationException, JsonMappingException, - IOException, JsonLdError { + public static void writeReport() + throws JsonGenerationException, JsonMappingException, IOException, JsonLdError { // Only write reports if "-Dreport.format=..." is set String reportFormat = System.getProperty("report.format"); if (reportFormat != null) { reportFormat = reportFormat.toLowerCase(); - } else { - return; // nothing to do - } - - if ("application/ld+json".equals(reportFormat) || "jsonld".equals(reportFormat) - || "*".equals(reportFormat)) { - System.out.println("Generating JSON-LD Report"); - JsonUtils.writePrettyPrint(new OutputStreamWriter(new FileOutputStream(reportOutputFile - + ".jsonld")), REPORT); - } - - if ("text/plain".equals(reportFormat) || "nquads".equals(reportFormat) - || "nq".equals(reportFormat) || "nt".equals(reportFormat) - || "ntriples".equals(reportFormat) || "*".equals(reportFormat)) { - System.out.println("Generating Nquads Report"); - final JsonLdOptions options = new JsonLdOptions("") { - { - this.format = "application/nquads"; - } - }; - final String rdf = (String) JsonLdProcessor.toRDF(REPORT, options); - final OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream( - reportOutputFile + ".nq")); - writer.write(rdf); - writer.close(); - } - if ("text/turtle".equals(reportFormat) || "turtle".equals(reportFormat) - || "ttl".equals(reportFormat) || "*".equals(reportFormat)) { // write - // turtle - System.out.println("Generating Turtle Report"); - final JsonLdOptions options = new JsonLdOptions("") { - { - format = "text/turtle"; - useNamespaces = true; - } - }; - final String rdf = (String) JsonLdProcessor.toRDF(REPORT, new TurtleTripleCallback(), - options); - final OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream( - reportOutputFile + ".ttl")); - writer.write(rdf); - writer.close(); + if ("application/ld+json".equals(reportFormat) || "jsonld".equals(reportFormat) + || "*".equals(reportFormat)) { + System.out.println("Generating JSON-LD Report"); + JsonUtils.writePrettyPrint( + new OutputStreamWriter(new FileOutputStream(reportOutputFile + ".jsonld"), + StandardCharsets.UTF_8), + REPORT); + } } } @@ -236,7 +202,6 @@ public boolean accept(File dir, String name) { })); final Collection rdata = new ArrayList(); - final int count = 0; for (final File in : manifestfiles) { // System.out.println("Reading: " + in.getCanonicalPath()); final FileInputStream manifestfile = new FileInputStream(in); @@ -276,24 +241,26 @@ public TestDocumentLoader(String base) { @Override public RemoteDocument loadDocument(String url) throws JsonLdError { if (url == null) { - throw new JsonLdError(JsonLdError.Error.LOADING_REMOTE_CONTEXT_FAILED); + throw new JsonLdError(JsonLdError.Error.LOADING_REMOTE_CONTEXT_FAILED, + "URL was null"); } if (url.contains(":")) { // check if the url is relative to the test base if (url.startsWith(this.base)) { final String classpath = url.substring(this.base.length()); final ClassLoader cl = Thread.currentThread().getContextClassLoader(); - final InputStream inputStream = cl.getResourceAsStream(TEST_DIR + "/" - + classpath); + final InputStream inputStream = cl + .getResourceAsStream(TEST_DIR + "/" + classpath); try { return new RemoteDocument(url, JsonUtils.fromInputStream(inputStream)); } catch (final IOException e) { - throw new JsonLdError(JsonLdError.Error.LOADING_DOCUMENT_FAILED); + throw new JsonLdError(JsonLdError.Error.LOADING_DOCUMENT_FAILED, e); } } } // we can't load this remote document from the test suite - throw new JsonLdError(JsonLdError.Error.NOT_IMPLEMENTED); + throw new JsonLdError(JsonLdError.Error.NOT_IMPLEMENTED, + "URL scheme was not recognised: " + url); } public void setRedirectTo(String string) { @@ -328,7 +295,8 @@ public void addHttpLink(String nextLink) { private final String group; private final Map test; - public JsonLdProcessorTest(final String group, final String id, final Map test) { + public JsonLdProcessorTest(final String group, final String id, + final Map test) { this.group = group; this.test = test; } @@ -356,8 +324,8 @@ public void runTest() throws URISyntaxException, IOException, JsonLdError { input = JsonUtils.fromInputStream(inputStream); } else if (inputType.equals("nt") || inputType.equals("nq")) { final List inputLines = new ArrayList(); - final BufferedReader buf = new BufferedReader(new InputStreamReader(inputStream, - "UTF-8")); + final BufferedReader buf = new BufferedReader( + new InputStreamReader(inputStream, "UTF-8")); String line; while ((line = buf.readLine()) != null) { line = line.trim(); @@ -389,8 +357,8 @@ public void runTest() throws URISyntaxException, IOException, JsonLdError { expect = JsonUtils.fromInputStream(expectStream); } else if (expectType.equals("nt") || expectType.equals("nq")) { final List expectLines = new ArrayList(); - final BufferedReader buf = new BufferedReader(new InputStreamReader( - expectStream, "UTF-8")); + final BufferedReader buf = new BufferedReader( + new InputStreamReader(expectStream, "UTF-8")); String line; while ((line = buf.readLine()) != null) { line = line.trim(); @@ -409,8 +377,8 @@ public void runTest() throws URISyntaxException, IOException, JsonLdError { } else if (sparqlFile != null) { final InputStream sparqlStream = cl.getResourceAsStream(TEST_DIR + "/" + sparqlFile); assertNotNull("unable to find expect file: " + sparqlFile, sparqlStream); - final BufferedReader buf = new BufferedReader(new InputStreamReader(sparqlStream, - "UTF-8")); + final BufferedReader buf = new BufferedReader( + new InputStreamReader(sparqlStream, "UTF-8")); String buffer = null; while ((buffer = buf.readLine()) != null) { sparql += buffer + "\n"; @@ -424,8 +392,8 @@ public void runTest() throws URISyntaxException, IOException, JsonLdError { Object result = null; // OPTIONS SETUP - final JsonLdOptions options = new JsonLdOptions("http://json-ld.org/test-suite/tests/" - + test.get("input")); + final JsonLdOptions options = new JsonLdOptions( + "http://json-ld.org/test-suite/tests/" + test.get("input")); final TestDocumentLoader testLoader = new TestDocumentLoader( "http://json-ld.org/test-suite/tests/"); options.setDocumentLoader(testLoader); @@ -435,8 +403,8 @@ public void runTest() throws URISyntaxException, IOException, JsonLdError { options.setBase((String) test_opts.get("base")); } if (test_opts.containsKey("expandContext")) { - final InputStream contextStream = cl.getResourceAsStream(TEST_DIR + "/" - + test_opts.get("expandContext")); + final InputStream contextStream = cl + .getResourceAsStream(TEST_DIR + "/" + test_opts.get("expandContext")); options.setExpandContext(JsonUtils.fromInputStream(contextStream)); } if (test_opts.containsKey("compactArrays")) { @@ -448,6 +416,12 @@ public void runTest() throws URISyntaxException, IOException, JsonLdError { if (test_opts.containsKey("useRdfType")) { options.setUseRdfType((Boolean) test_opts.get("useRdfType")); } + if (test_opts.containsKey("processingMode")) { + options.setProcessingMode((String) test_opts.get("processingMode")); + } + if (test_opts.containsKey("omitGraph")) { + options.setOmitGraph((Boolean) test_opts.get("omitGraph")); + } if (test_opts.containsKey("produceGeneralizedRdf")) { options.setProduceGeneralizedRdf((Boolean) test_opts.get("produceGeneralizedRdf")); } @@ -476,33 +450,33 @@ public void runTest() throws URISyntaxException, IOException, JsonLdError { if (testType.contains("jld:ExpandTest")) { result = JsonLdProcessor.expand(input, options); } else if (testType.contains("jld:CompactTest")) { - final InputStream contextStream = cl.getResourceAsStream(TEST_DIR + "/" - + test.get("context")); + final InputStream contextStream = cl + .getResourceAsStream(TEST_DIR + "/" + test.get("context")); final Object contextJson = JsonUtils.fromInputStream(contextStream); result = JsonLdProcessor.compact(input, contextJson, options); } else if (testType.contains("jld:FlattenTest")) { if (test.containsKey("context")) { - final InputStream contextStream = cl.getResourceAsStream(TEST_DIR + "/" - + test.get("context")); + final InputStream contextStream = cl + .getResourceAsStream(TEST_DIR + "/" + test.get("context")); final Object contextJson = JsonUtils.fromInputStream(contextStream); result = JsonLdProcessor.flatten(input, contextJson, options); } else { result = JsonLdProcessor.flatten(input, options); } } else if (testType.contains("jld:FrameTest")) { - final InputStream frameStream = cl.getResourceAsStream(TEST_DIR + "/" - + test.get("frame")); + final InputStream frameStream = cl + .getResourceAsStream(TEST_DIR + "/" + test.get("frame")); final Map frameJson = (Map) JsonUtils .fromInputStream(frameStream); result = JsonLdProcessor.frame(input, frameJson, options); } else if (testType.contains("jld:FromRDFTest")) { result = JsonLdProcessor.fromRDF(input, options); } else if (testType.contains("jld:ToRDFTest")) { - options.format = "application/nquads"; + options.format = JsonLdConsts.APPLICATION_NQUADS; result = JsonLdProcessor.toRDF(input, options); result = ((String) result).trim(); } else if (testType.contains("jld:NormalizeTest")) { - options.format = "application/nquads"; + options.format = JsonLdConsts.APPLICATION_NQUADS; result = JsonLdProcessor.normalize(input, options); result = ((String) result).trim(); } else { @@ -581,28 +555,18 @@ public void runTest() throws URISyntaxException, IOException, JsonLdError { { put("@id", "http://json-ld.org/test-suite/tests/error-expand-manifest.jsonld" - .equals(manifest) ? "earl:semiAuto" : "earl:automatic"); + .equals(manifest) ? "earl:semiAuto" : "earl:automatic"); } }); } }); - assertTrue( - "\nFailed test: " - + group - + test.get("@id") - + " " - + test.get("name") - + " (" - + test.get("input") - + "," - + test.get("expect") - + ")\n" - + "expected: " - + JsonUtils.toPrettyString(expect) - + "\nresult: " - + (result instanceof JsonLdError ? ((JsonLdError) result).toString() - : JsonUtils.toPrettyString(result)), testpassed); + assertTrue("\nFailed test: " + group + test.get("@id") + " " + test.get("name") + " (" + + test.get("input") + "," + test.get("expect") + ")\n" + "expected: " + + JsonUtils.toPrettyString(expect) + "\nresult: " + + (result instanceof JsonLdError ? ((JsonLdError) result).toString() + : JsonUtils.toPrettyString(result)), + testpassed); } } diff --git a/core/src/test/java/com/github/jsonldjava/core/JsonLdToRdfTest.java b/core/src/test/java/com/github/jsonldjava/core/JsonLdToRdfTest.java new file mode 100644 index 00000000..7586c1c6 --- /dev/null +++ b/core/src/test/java/com/github/jsonldjava/core/JsonLdToRdfTest.java @@ -0,0 +1,31 @@ +package com.github.jsonldjava.core; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class JsonLdToRdfTest { + + @Test + public void testIssue301() throws JsonLdError { + final RDFDataset rdf = new RDFDataset(); + rdf.addTriple( + "http://www.w3.org/2002/07/owl#Class", + "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", + "http://www.w3.org/2002/07/owl#Class"); + final JsonLdOptions opts = new JsonLdOptions(); + opts.setUseRdfType(Boolean.FALSE); + opts.setProcessingMode(JsonLdOptions.JSON_LD_1_0); + + final Object out = new JsonLdApi(opts).fromRDF(rdf, true); + assertEquals("[{@id=http://www.w3.org/2002/07/owl#Class, @type=[http://www.w3.org/2002/07/owl#Class]}]", + out.toString()); + + opts.setUseRdfType(Boolean.TRUE); + + final Object out2 = new JsonLdApi(opts).fromRDF(rdf, true); + assertEquals("[{@id=http://www.w3.org/2002/07/owl#Class, http://www.w3.org/1999/02/22-rdf-syntax-ns#type=[{@id=http://www.w3.org/2002/07/owl#Class}]}]", + out2.toString()); + } + +} diff --git a/core/src/test/java/com/github/jsonldjava/core/LocalBaseTest.java b/core/src/test/java/com/github/jsonldjava/core/LocalBaseTest.java new file mode 100644 index 00000000..22093345 --- /dev/null +++ b/core/src/test/java/com/github/jsonldjava/core/LocalBaseTest.java @@ -0,0 +1,81 @@ +package com.github.jsonldjava.core; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.Charset; +import java.util.List; + +import org.junit.Test; + +import com.github.jsonldjava.utils.JsonUtils; + +public class LocalBaseTest { + @Test + public void testMixedLocalRemoteBaseRemoteContextFirst() throws Exception { + + final Reader reader = new BufferedReader(new InputStreamReader( + this.getClass().getResourceAsStream("/custom/base-0001-in.jsonld"), + Charset.forName("UTF-8"))); + final Object context = JsonUtils.fromReader(reader); + assertNotNull(context); + + final JsonLdOptions options = new JsonLdOptions(); + final Object expanded = JsonLdProcessor.expand(context, options); + // System.out.println(JsonUtils.toPrettyString(expanded)); + + final Reader outReader = new BufferedReader(new InputStreamReader( + this.getClass().getResourceAsStream("/custom/base-0001-out.jsonld"), + Charset.forName("UTF-8"))); + final Object output = JsonUtils.fromReader(outReader); + assertNotNull(output); + assertEquals(expanded, output); + } + + @Test + public void testMixedLocalRemoteBaseLocalContextFirst() throws Exception { + + final Reader reader = new BufferedReader(new InputStreamReader( + this.getClass().getResourceAsStream("/custom/base-0002-in.jsonld"), + Charset.forName("UTF-8"))); + final Object context = JsonUtils.fromReader(reader); + assertNotNull(context); + + final JsonLdOptions options = new JsonLdOptions(); + final Object expanded = JsonLdProcessor.expand(context, options); + // System.out.println(JsonUtils.toPrettyString(expanded)); + + final Reader outReader = new BufferedReader(new InputStreamReader( + this.getClass().getResourceAsStream("/custom/base-0002-out.jsonld"), + Charset.forName("UTF-8"))); + final Object output = JsonUtils.fromReader(outReader); + assertNotNull(output); + assertEquals(expanded, output); + } + + @Test + public void testUriResolveWhenExpandingBase() throws Exception { + + final Reader reader = new BufferedReader(new InputStreamReader( + this.getClass().getResourceAsStream("/custom/base-0003-in.jsonld"), + Charset.forName("UTF-8"))); + final Object input = JsonUtils.fromReader(reader); + assertNotNull(input); + + final JsonLdOptions options = new JsonLdOptions(); + final List expanded = JsonLdProcessor.expand(input, options); + assertFalse("expanded form must not be empty", expanded.isEmpty()); + + final Reader outReader = new BufferedReader(new InputStreamReader( + this.getClass().getResourceAsStream("/custom/base-0003-out.jsonld"), + Charset.forName("UTF-8"))); + final Object expected = JsonLdProcessor.expand(JsonUtils.fromReader(outReader), options); + assertNotNull(expected); + assertEquals(expected, expanded); + } + +} diff --git a/core/src/test/java/com/github/jsonldjava/core/LongestPrefixTest.java b/core/src/test/java/com/github/jsonldjava/core/LongestPrefixTest.java index 1471feaf..69757bb5 100644 --- a/core/src/test/java/com/github/jsonldjava/core/LongestPrefixTest.java +++ b/core/src/test/java/com/github/jsonldjava/core/LongestPrefixTest.java @@ -23,7 +23,7 @@ public void toRdfWithNamespace() throws Exception { final JsonLdOptions options = new JsonLdOptions(); options.useNamespaces = true; final RDFDataset rdf = (RDFDataset) JsonLdProcessor.toRDF(context, options); - System.out.println(rdf.getNamespaces()); + // System.out.println(rdf.getNamespaces()); assertEquals("http://vocab.getty.edu/aat/", rdf.getNamespace("aat")); assertEquals("http://vocab.getty.edu/aat/rev/", rdf.getNamespace("aat_rev")); } @@ -31,7 +31,7 @@ public void toRdfWithNamespace() throws Exception { @Test public void fromRdfWithNamespaceLexicographicallyShortestChosen() throws Exception { - RDFDataset inputRdf = new RDFDataset(); + final RDFDataset inputRdf = new RDFDataset(); inputRdf.setNamespace("aat", "http://vocab.getty.edu/aat/"); inputRdf.setNamespace("aat_rev", "http://vocab.getty.edu/aat/rev/"); @@ -41,16 +41,16 @@ public void fromRdfWithNamespaceLexicographicallyShortestChosen() throws Excepti final JsonLdOptions options = new JsonLdOptions(); options.useNamespaces = true; - Object fromRDF = JsonLdProcessor.compact(new JsonLdApi(options).fromRDF(inputRdf), + final Object fromRDF = JsonLdProcessor.compact(new JsonLdApi(options).fromRDF(inputRdf), inputRdf.getContext(), options); final RDFDataset rdf = (RDFDataset) JsonLdProcessor.toRDF(fromRDF, options); - System.out.println(rdf.getNamespaces()); + // System.out.println(rdf.getNamespaces()); assertEquals("http://vocab.getty.edu/aat/", rdf.getNamespace("aat")); assertEquals("http://vocab.getty.edu/aat/rev/", rdf.getNamespace("aat_rev")); - String toJSONLD = JsonUtils.toPrettyString(fromRDF); - System.out.println(toJSONLD); + final String toJSONLD = JsonUtils.toPrettyString(fromRDF); + // System.out.println(toJSONLD); assertTrue("The lexicographically shortest URI was not chosen", toJSONLD.contains("aat:rev/")); @@ -59,7 +59,7 @@ public void fromRdfWithNamespaceLexicographicallyShortestChosen() throws Excepti @Test public void fromRdfWithNamespaceLexicographicallyShortestChosen2() throws Exception { - RDFDataset inputRdf = new RDFDataset(); + final RDFDataset inputRdf = new RDFDataset(); inputRdf.setNamespace("aat", "http://vocab.getty.edu/aat/"); inputRdf.setNamespace("aatrev", "http://vocab.getty.edu/aat/rev/"); @@ -69,16 +69,16 @@ public void fromRdfWithNamespaceLexicographicallyShortestChosen2() throws Except final JsonLdOptions options = new JsonLdOptions(); options.useNamespaces = true; - Object fromRDF = JsonLdProcessor.compact(new JsonLdApi(options).fromRDF(inputRdf), + final Object fromRDF = JsonLdProcessor.compact(new JsonLdApi(options).fromRDF(inputRdf), inputRdf.getContext(), options); final RDFDataset rdf = (RDFDataset) JsonLdProcessor.toRDF(fromRDF, options); - System.out.println(rdf.getNamespaces()); + // System.out.println(rdf.getNamespaces()); assertEquals("http://vocab.getty.edu/aat/", rdf.getNamespace("aat")); assertEquals("http://vocab.getty.edu/aat/rev/", rdf.getNamespace("aatrev")); - String toJSONLD = JsonUtils.toPrettyString(fromRDF); - System.out.println(toJSONLD); + final String toJSONLD = JsonUtils.toPrettyString(fromRDF); + // System.out.println(toJSONLD); assertFalse("The lexicographically shortest URI was not chosen", toJSONLD.contains("aat:rev/")); @@ -95,10 +95,10 @@ public void prefixUsedToShortenPredicate() throws Exception { final JsonLdOptions options = new JsonLdOptions(); options.useNamespaces = true; - Object fromRDF = JsonLdProcessor.compact(new JsonLdApi(options).fromRDF(inputRdf), + final Object fromRDF = JsonLdProcessor.compact(new JsonLdApi(options).fromRDF(inputRdf), inputRdf.getContext(), options); - String toJSONLD = JsonUtils.toPrettyString(fromRDF); - System.out.println(toJSONLD); + final String toJSONLD = JsonUtils.toPrettyString(fromRDF); + // System.out.println(toJSONLD); assertFalse("The lexicographically shortest URI was not chosen", toJSONLD.contains("http://www.a.com/foo/p")); diff --git a/core/src/test/java/com/github/jsonldjava/core/MinimalSchemaOrgRegressionTest.java b/core/src/test/java/com/github/jsonldjava/core/MinimalSchemaOrgRegressionTest.java new file mode 100644 index 00000000..4694f897 --- /dev/null +++ b/core/src/test/java/com/github/jsonldjava/core/MinimalSchemaOrgRegressionTest.java @@ -0,0 +1,61 @@ +package com.github.jsonldjava.core; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.net.URL; + +import com.github.jsonldjava.utils.JarCacheStorage; +import com.github.jsonldjava.utils.JsonUtils; + +import org.apache.http.client.protocol.RequestAcceptEncoding; +import org.apache.http.client.protocol.ResponseContentEncoding; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.DefaultRedirectStrategy; +import org.apache.http.impl.client.cache.BasicHttpCacheStorage; +import org.apache.http.impl.client.cache.CacheConfig; +import org.apache.http.impl.client.cache.CachingHttpClientBuilder; +import org.junit.Test; + +public class MinimalSchemaOrgRegressionTest { + + /** + * Tests getting JSON from schema.org with the HTTP Accept header set to + * {@value com.github.jsonldjava.utils.JsonUtils#ACCEPT_HEADER}? . + */ + @Test + public void testApacheHttpClient() throws Exception { + final URL url = new URL("http://schema.org/"); + // Common CacheConfig for both the JarCacheStorage and the underlying + // BasicHttpCacheStorage + final CacheConfig cacheConfig = CacheConfig.custom().setMaxCacheEntries(1000) + .setMaxObjectSize(1024 * 128).build(); + + final CloseableHttpClient httpClient = CachingHttpClientBuilder.create() + // allow caching + .setCacheConfig(cacheConfig) + // Wrap the local JarCacheStorage around a BasicHttpCacheStorage + .setHttpCacheStorage(new JarCacheStorage(null, cacheConfig, + new BasicHttpCacheStorage(cacheConfig))) + // Support compressed data + // http://hc.apache.org/httpcomponents-client-ga/tutorial/html/httpagent.html#d5e1238 + .addInterceptorFirst(new RequestAcceptEncoding()) + .addInterceptorFirst(new ResponseContentEncoding()) + .setRedirectStrategy(DefaultRedirectStrategy.INSTANCE) + // use system defaults for proxy etc. + .useSystemProperties().build(); + + Object content = JsonUtils.fromURL(url, httpClient); + checkBasicConditions(content.toString()); + } + + private void checkBasicConditions(final String outputString) { + // Test for some basic conditions without including the JSON/JSON-LD + // parsing code here + assertTrue(outputString, outputString.endsWith("}")); + assertFalse("Output string should not be empty: " + outputString.length(), + outputString.isEmpty()); + assertTrue("Unexpected length: " + outputString.length(), outputString.length() > 100000); + } + +} diff --git a/core/src/test/java/com/github/jsonldjava/core/NodeCompareTest.java b/core/src/test/java/com/github/jsonldjava/core/NodeCompareTest.java new file mode 100644 index 00000000..6b7e5b55 --- /dev/null +++ b/core/src/test/java/com/github/jsonldjava/core/NodeCompareTest.java @@ -0,0 +1,223 @@ +package com.github.jsonldjava.core; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Random; + +import org.junit.Test; + +import com.github.jsonldjava.core.RDFDataset.BlankNode; +import com.github.jsonldjava.core.RDFDataset.IRI; +import com.github.jsonldjava.core.RDFDataset.Literal; +import com.github.jsonldjava.core.RDFDataset.Node; +import com.github.jsonldjava.core.RDFDataset.Quad; + +public class NodeCompareTest { + + /** + * While this order might not particularly make sense (RDF is unordered), + * this is at least documented. Feel free to move things around below if the + * underlying .compareTo() changes. + */ + @Test + public void ordered() throws Exception { + final List expected = Arrays.asList( + + new Literal("1", JsonLdConsts.XSD_INTEGER, null), + new Literal("10", JsonLdConsts.XSD_INTEGER, null), + new Literal("2", JsonLdConsts.XSD_INTEGER, null), // still + // ordered by + // string + // value + + new Literal("a", JsonLdConsts.RDF_LANGSTRING, "en"), + new Literal("a", JsonLdConsts.RDF_LANGSTRING, "fr"), new Literal("a", null, null), // equivalent + // to + // xsd:string + new Literal("b", JsonLdConsts.XSD_STRING, null), + new Literal("false", JsonLdConsts.XSD_BOOLEAN, null), + new Literal("true", JsonLdConsts.XSD_BOOLEAN, null), + + new Literal("x", JsonLdConsts.XSD_STRING, null), + + new Literal("z", JsonLdConsts.RDF_LANGSTRING, "en"), + new Literal("z", JsonLdConsts.RDF_LANGSTRING, "fr"), new Literal("z", null, null), + + new BlankNode("a"), new BlankNode("f"), new BlankNode("z"), + + new IRI("http://example.com/ex1"), new IRI("http://example.com/ex2"), + new IRI("http://example.org/ex"), new IRI("https://example.net/")); + + final List shuffled = new ArrayList<>(expected); + final Random rand = new Random(1337); // fixed seed + Collections.shuffle(shuffled, rand); + // System.out.println("Shuffled:"); + // shuffled.stream().forEach(System.out::println); + assertNotEquals(expected, shuffled); + + Collections.sort(shuffled); + final List sorted = shuffled; + // System.out.println("Now sorted:"); + // sorted.stream().forEach(System.out::println); + // Not so useful output from this + // assertEquals(expected, sorted); + // so we'll instead do: + for (int i = 0; i < expected.size(); i++) { + assertEquals("Wrong sort order at position " + i, expected.get(i), sorted.get(i)); + } + } + + @Test + public void literalSameValue() throws Exception { + final Literal l1 = new Literal("Same", null, null); + final Literal l2 = new Literal("Same", null, null); + assertEquals(l1, l2); + assertEquals(0, l1.compareTo(l2)); + } + + @Test + public void literalDifferentValue() throws Exception { + final Literal l1 = new Literal("Same", null, null); + final Literal l2 = new Literal("Different", null, null); + assertNotEquals(l1, l2); + assertNotEquals(0, l1.compareTo(l2)); + } + + @Test + public void literalSameValueSameLang() throws Exception { + final Literal l1 = new Literal("Same", JsonLdConsts.RDF_LANGSTRING, "en"); + final Literal l2 = new Literal("Same", JsonLdConsts.RDF_LANGSTRING, "en"); + assertEquals(l1, l2); + assertEquals(0, l1.compareTo(l2)); + } + + @Test + public void literalDifferentValueSameLang() throws Exception { + final Literal l1 = new Literal("Same", JsonLdConsts.RDF_LANGSTRING, "en"); + final Literal l2 = new Literal("Different", JsonLdConsts.RDF_LANGSTRING, "en"); + assertNotEquals(l1, l2); + assertNotEquals(0, l1.compareTo(l2)); + } + + @Test + public void literalSameValueDifferentLang() throws Exception { + final Literal l1 = new Literal("Same", JsonLdConsts.RDF_LANGSTRING, "en"); + final Literal l2 = new Literal("Same", JsonLdConsts.RDF_LANGSTRING, "no"); + assertNotEquals(l1, l2); + assertNotEquals(0, l1.compareTo(l2)); + } + + @Test + public void literalSameValueLangNull() throws Exception { + final Literal l1 = new Literal("Same", JsonLdConsts.RDF_LANGSTRING, "en"); + final Literal l2 = new Literal("Same", JsonLdConsts.RDF_LANGSTRING, null); + assertNotEquals(l1, l2); + assertNotEquals(0, l1.compareTo(l2)); + assertNotEquals(0, l2.compareTo(l1)); + } + + @Test + public void literalSameValueSameType() throws Exception { + final Literal l1 = new Literal("1", JsonLdConsts.XSD_INTEGER, null); + final Literal l2 = new Literal("1", JsonLdConsts.XSD_INTEGER, null); + assertEquals(l1, l2); + assertEquals(0, l1.compareTo(l2)); + } + + @Test + public void literalSameValueSameTypeNull() throws Exception { + final Literal l1 = new Literal("1", JsonLdConsts.XSD_STRING, null); + final Literal l2 = new Literal("1", null, null); + assertEquals(l1, l2); + assertEquals(0, l1.compareTo(l2)); + } + + @Test + public void literalSameValueDifferentType() throws Exception { + final Literal l1 = new Literal("1", JsonLdConsts.XSD_INTEGER, null); + final Literal l2 = new Literal("1", JsonLdConsts.XSD_STRING, null); + assertNotEquals(l1, l2); + assertNotEquals(0, l1.compareTo(l2)); + } + + @Test + public void literalsInDataset() throws Exception { + final RDFDataset dataset = new RDFDataset(); + dataset.addQuad("http://example.com/p", "http://example.com/p", "Same", null, null, + "http://example.com/g1"); + dataset.addQuad("http://example.com/p", "http://example.com/p", "Different", null, null, + "http://example.com/g1"); + final List quads = dataset.getQuads("http://example.com/g1"); + final Quad q1 = quads.get(0); + final Quad q2 = quads.get(1); + assertNotEquals(q1, q2); + assertNotEquals(0, q1.compareTo(q2)); + assertNotEquals(0, q1.getObject().compareTo(q2.getObject())); + } + + @Test + public void iriDifferentLiteral() throws Exception { + final Node iri = new IRI("http://example.com/"); + final Node literal = new Literal("http://example.com/", null, null); + assertNotEquals(iri, literal); + assertNotEquals(0, iri.compareTo(literal)); + assertNotEquals(0, literal.compareTo(iri)); + } + + @Test + public void iriDifferentNull() throws Exception { + final Node iri = new IRI("http://example.com/"); + assertNotEquals(0, iri.compareTo(null)); + } + + @Test + public void literalDifferentNull() throws Exception { + final Node literal = new Literal("hello", null, null); + assertNotEquals(0, literal.compareTo(null)); + } + + @Test + public void iriDifferentIri() throws Exception { + final Node iri = new IRI("http://example.com/"); + final Node other = new IRI("http://example.com/other"); + assertNotEquals(iri, other); + assertNotEquals(0, iri.compareTo(other)); + } + + @Test + public void iriSameIri() throws Exception { + final Node iri = new IRI("http://example.com/same"); + final Node same = new IRI("http://example.com/same"); + assertEquals(iri, same); + assertEquals(0, iri.compareTo(same)); + } + + @Test + public void iriDifferentBlankNode() throws Exception { + // We'll use a relative IRI to avoid :-issues + final Node iri = new IRI("b1"); + final Node bnode = new BlankNode("b1"); + assertNotEquals(iri, bnode); + assertNotEquals(bnode, iri); + assertNotEquals(0, iri.compareTo(bnode)); + assertNotEquals(0, bnode.compareTo(iri)); + } + + @Test + public void literalDifferentBlankNode() throws Exception { + // We'll use a relative IRI to avoid :-issues + final Node literal = new Literal("b1", null, null); + final Node bnode = new BlankNode("b1"); + assertNotEquals(literal, bnode); + assertNotEquals(bnode, literal); + assertNotEquals(0, literal.compareTo(bnode)); + assertNotEquals(0, bnode.compareTo(literal)); + + } + +} diff --git a/core/src/test/java/com/github/jsonldjava/core/QuadCompareTest.java b/core/src/test/java/com/github/jsonldjava/core/QuadCompareTest.java new file mode 100644 index 00000000..8d3a569c --- /dev/null +++ b/core/src/test/java/com/github/jsonldjava/core/QuadCompareTest.java @@ -0,0 +1,66 @@ +package com.github.jsonldjava.core; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import org.junit.Test; + +import com.github.jsonldjava.core.RDFDataset.Quad; + +public class QuadCompareTest { + + Quad q = new Quad("http://example.com/s1", "http://example.com/p1", "http://example.com/o1", + "http://example.com/g1"); + + @Test + public void compareToNull() throws Exception { + assertNotEquals(0, q.compareTo(null)); + } + + @Test + public void compareToSame() throws Exception { + final Quad q2 = new Quad("http://example.com/s1", "http://example.com/p1", + "http://example.com/o1", "http://example.com/g1"); + assertEquals(0, q.compareTo(q2)); + // Should still compare equal, even if extra attributes are added + q2.put("example", "value"); + assertEquals(0, q.compareTo(q2)); + } + + @Test + public void compareToDifferentGraph() throws Exception { + final Quad q2 = new Quad("http://example.com/s1", "http://example.com/p1", + "http://example.com/o1", "http://example.com/other"); + assertNotEquals(0, q.compareTo(q2)); + } + + @Test + public void compareToDifferentSubject() throws Exception { + final Quad q2 = new Quad("http://example.com/other", "http://example.com/p1", + "http://example.com/o1", "http://example.com/g1"); + assertNotEquals(0, q.compareTo(q2)); + } + + @Test + public void compareToDifferentPredicate() throws Exception { + final Quad q2 = new Quad("http://example.com/s1", "http://example.com/other", + "http://example.com/o1", "http://example.com/g1"); + assertNotEquals(0, q.compareTo(q2)); + } + + @Test + public void compareToDifferentObject() throws Exception { + final Quad q2 = new Quad("http://example.com/s1", "http://example.com/p1", + "http://example.com/other", "http://example.com/g1"); + assertNotEquals(0, q.compareTo(q2)); + } + + @Test + public void compareToDifferentObjectType() throws Exception { + final Quad q2 = new Quad("http://example.com/s1", "http://example.com/p1", + "http://example.com/other", null, null, // literal + "http://example.com/g1"); + assertNotEquals(0, q.compareTo(q2)); + } + +} diff --git a/core/src/test/java/com/github/jsonldjava/core/RegexTest.java b/core/src/test/java/com/github/jsonldjava/core/RegexTest.java index 89854f85..fa833f39 100644 --- a/core/src/test/java/com/github/jsonldjava/core/RegexTest.java +++ b/core/src/test/java/com/github/jsonldjava/core/RegexTest.java @@ -101,8 +101,8 @@ public void test_PNAME_NS() { public void test_PNAME_LN() { assertTrue(":p".matches("^" + Regex.PNAME_LN + "$")); assertTrue("abc:def".matches("^" + Regex.PNAME_LN + "$")); - assertTrue("\u00F8\u02FF\u0370\u037D:\u00F8\u02FF\u0370\u037D".matches("^" + Regex.PNAME_LN - + "$")); + assertTrue("\u00F8\u02FF\u0370\u037D:\u00F8\u02FF\u0370\u037D" + .matches("^" + Regex.PNAME_LN + "$")); } @Test @@ -147,16 +147,16 @@ public void test_STRING_LITERAL_QUOTE() { .matcher("\"IRI with four digit numeric escape (\\\\u)\" ;"); assertTrue(matcher.find()); - assertTrue("\"dffhjkasdhfskldhfoiw'eu\\\"fhowleifh \u00F8\u02FF\u0370\u037D\"".matches("^" - + Regex.STRING_LITERAL_QUOTE + "$")); + assertTrue("\"dffhjkasdhfskldhfoiw'eu\\\"fhowleifh \u00F8\u02FF\u0370\u037D\"" + .matches("^" + Regex.STRING_LITERAL_QUOTE + "$")); assertFalse("\"dffhjkasdhfs\nkldhfoiw\\\"'eufhowleifh \u00F8\u02FF\u0370\u037D\"" .matches("^" + Regex.STRING_LITERAL_QUOTE + "$")); } @Test public void test_STRING_LITERAL_SINGLE_QUOTE() { - assertTrue("'dffhjkasdhfskldhf\\'oiweu\"fhowleifh \u00F8\u02FF\u0370\u037D'".matches("^" - + Regex.STRING_LITERAL_SINGLE_QUOTE + "$")); + assertTrue("'dffhjkasdhfskldhf\\'oiweu\"fhowleifh \u00F8\u02FF\u0370\u037D'" + .matches("^" + Regex.STRING_LITERAL_SINGLE_QUOTE + "$")); assertFalse("\"dffhjkasdhfs\nkldhfoiw\\\"'eufhowleifh \u00F8\u02FF\u0370\u037D\"" .matches("^" + Regex.STRING_LITERAL_SINGLE_QUOTE + "$")); } @@ -199,15 +199,17 @@ public void test_unescape() { r = RDFDatasetUtils.unescape("\\t\\u007A\\U000F0000\\U00010000\\n"); assertTrue("\t\u007A\uDB80\uDC00\uD800\uDC00\n".equals(r)); - r = RDFDatasetUtils - .unescape("http://a.example/AZaz\u00c0\u00d6\u00d8\u00f6\u00f8\u02ff\u0370\u037d\u0384\u1ffe\u200c\u200d\u2070\u2189\u2c00\u2fd5\u3001\ud7fb\ufa0e\ufdc7\ufdf0\uffef"); - assertTrue("http://a.example/AZaz\u00c0\u00d6\u00d8\u00f6\u00f8\u02ff\u0370\u037d\u0384\u1ffe\u200c\u200d\u2070\u2189\u2c00\u2fd5\u3001\ud7fb\ufa0e\ufdc7\ufdf0\uffef" - .equals(r)); - - r = RDFDatasetUtils - .unescape("http://a.example/AZaz\\u00c0\\u00d6\\u00d8\\u00f6\\u00f8\\u02ff\\u0370\\u037d\\u0384\\u1ffe\\u200c\\u200d\\u2070\\u2189\\u2c00\\u2fd5\\u3001\\ud7fb\\ufa0e\\ufdc7\\ufdf0\\uffef\\U00010000\\U000e01ef"); - assertTrue("http://a.example/AZaz\u00c0\u00d6\u00d8\u00f6\u00f8\u02ff\u0370\u037d\u0384\u1ffe\u200c\u200d\u2070\u2189\u2c00\u2fd5\u3001\ud7fb\ufa0e\ufdc7\ufdf0\uffef\uD800\uDC00\uDB40\uDDEF" - .equals(r)); + r = RDFDatasetUtils.unescape( + "http://a.example/AZaz\u00c0\u00d6\u00d8\u00f6\u00f8\u02ff\u0370\u037d\u0384\u1ffe\u200c\u200d\u2070\u2189\u2c00\u2fd5\u3001\ud7fb\ufa0e\ufdc7\ufdf0\uffef"); + assertTrue( + "http://a.example/AZaz\u00c0\u00d6\u00d8\u00f6\u00f8\u02ff\u0370\u037d\u0384\u1ffe\u200c\u200d\u2070\u2189\u2c00\u2fd5\u3001\ud7fb\ufa0e\ufdc7\ufdf0\uffef" + .equals(r)); + + r = RDFDatasetUtils.unescape( + "http://a.example/AZaz\\u00c0\\u00d6\\u00d8\\u00f6\\u00f8\\u02ff\\u0370\\u037d\\u0384\\u1ffe\\u200c\\u200d\\u2070\\u2189\\u2c00\\u2fd5\\u3001\\ud7fb\\ufa0e\\ufdc7\\ufdf0\\uffef\\U00010000\\U000e01ef"); + assertTrue( + "http://a.example/AZaz\u00c0\u00d6\u00d8\u00f6\u00f8\u02ff\u0370\u037d\u0384\u1ffe\u200c\u200d\u2070\u2189\u2c00\u2fd5\u3001\ud7fb\ufa0e\ufdc7\ufdf0\uffef\uD800\uDC00\uDB40\uDDEF" + .equals(r)); } @Test diff --git a/core/src/test/java/com/github/jsonldjava/impl/TurtleRDFParserTest.java b/core/src/test/java/com/github/jsonldjava/impl/TurtleRDFParserTest.java deleted file mode 100644 index 7c16d507..00000000 --- a/core/src/test/java/com/github/jsonldjava/impl/TurtleRDFParserTest.java +++ /dev/null @@ -1,448 +0,0 @@ -package com.github.jsonldjava.impl; - -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import java.io.IOException; -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; - -import com.github.jsonldjava.core.JsonLdError; -import com.github.jsonldjava.core.RDFDataset; -import com.github.jsonldjava.core.RDFDataset.Quad; -import com.github.jsonldjava.core.RDFDatasetUtils; -import com.github.jsonldjava.utils.EarlTestSuite; -import com.github.jsonldjava.utils.Obj; - -@Ignore -@RunWith(Parameterized.class) -public class TurtleRDFParserTest { - - // @Test - public void simpleTest() throws JsonLdError { - - final String input = "@prefix ericFoaf: .\n" - + "@prefix : .\n" - + "ericFoaf:ericP :givenName \"Eric\" ;\n" - + "\t:knows ,\n" - + "\t\t[ :mbox ] ,\n" + "\t\t ."; - - final List> expected = new ArrayList>() { - { - add(new LinkedHashMap() { - { - put("@id", "_:b1"); - put("http://xmlns.com/foaf/0.1/mbox", new ArrayList() { - { - add(new LinkedHashMap() { - { - put("@id", "mailto:timbl@w3.org"); - } - }); - } - }); - } - }); - add(new LinkedHashMap() { - { - put("@id", "http://getopenid.com/amyvdh"); - } - }); - add(new LinkedHashMap() { - { - put("@id", "http://norman.walsh.name/knows/who/dan-brickley"); - } - }); - add(new LinkedHashMap() { - { - put("@id", "http://www.w3.org/People/Eric/ericP-foaf.rdf#ericP"); - put("http://xmlns.com/foaf/0.1/givenName", new ArrayList() { - { - add(new LinkedHashMap() { - { - put("@value", "Eric"); - } - }); - } - }); - put("http://xmlns.com/foaf/0.1/knows", new ArrayList() { - { - add(new LinkedHashMap() { - { - put("@id", - "http://norman.walsh.name/knows/who/dan-brickley"); - } - }); - add(new LinkedHashMap() { - { - put("@id", "_:b1"); - } - }); - add(new LinkedHashMap() { - { - put("@id", "http://getopenid.com/amyvdh"); - } - }); - } - }); - } - }); - add(new LinkedHashMap() { - { - put("@id", "mailto:timbl@w3.org"); - } - }); - } - }; - - final Object json = null; /* - * JsonLdProcessor.fromRDF(input, new - * JsonLdOptions() { { format = "text/turtle"; - * } }, new TurtleRDFParser()); - */ - assertTrue(Obj.equals(expected, json)); - } - - @BeforeClass - public static void before() { - if (CACHE_DIR == null) { - System.out.println("Using temp dir: " + System.getProperty("java.io.tmpdir")); - } - } - - private static String TURTLE_TEST_MANIFEST = "https://dvcs.w3.org/hg/rdf/raw-file/default/rdf-turtle/tests-ttl/manifest.ttl"; - private static final String LAST_ETAG = null; // "1369157887.0"; - private static final String CACHE_DIR = null; - - @Parameters(name = "{0}{1}") - public static Collection data() throws URISyntaxException, IOException { - - final EarlTestSuite testSuite = new EarlTestSuite(TURTLE_TEST_MANIFEST, CACHE_DIR, - LAST_ETAG); - - final Collection rdata = new ArrayList(); - - for (final Map test : testSuite.getTests()) { - rdata.add(new Object[] { testSuite, test.get("@id"), test }); - } - - return rdata; - } - - private final Map test; - private final EarlTestSuite testSuite; - - public TurtleRDFParserTest(final EarlTestSuite testSuite, final String id, - final Map test) { - this.test = test; - this.testSuite = testSuite; - } - - @Test - public void runTest() throws IOException, JsonLdError { - final String inputfn = (String) Obj.get(test, "mf:action", "@id"); - final String outputfn = (String) Obj.get(test, "mf:result", "@id"); - final String type = (String) Obj.get(test, "@type"); - final String input = testSuite.getFile(inputfn); - - Boolean passed = false; - String failmsg = ""; - if ("rdft:TestTurtleEval".equals(type)) { - final RDFDataset result = new TurtleRDFParser().parse(input); - final RDFDataset expected = RDFDatasetUtils.parseNQuads(testSuite.getFile(outputfn)); - passed = compareDatasets("http://example/base/" + inputfn, result, expected); - if (!passed) { - failmsg = "\n" + "Expected: " + RDFDatasetUtils.toNQuads(expected) + "\n" - + "Result : " + RDFDatasetUtils.toNQuads(result); - } - } else if ("rdft:TestTurtlePositiveSyntax".equals(type)) { - /* - * JsonLdProcessor.fromRDF(input, new - * JsonLdOptions("http://example/base/") { { format = "text/turtle"; - * } }); passed = true; // otherwise an exception would have been - * thrown - */ - // TODO: temporary until new code is done - throw new JsonLdError(JsonLdError.Error.NOT_IMPLEMENTED, ""); - } else if ("rdft:TestTurtleNegativeSyntax".equals(type) - || "rdft:TestTurtleNegativeEval".equals(type)) { - // TODO: need to figure out how to properly deal with negative tests - try { - /* - * JsonLdProcessor.fromRDF(input, new - * JsonLdOptions("http://example/base/") { { format = - * "text/turtle"; } }); - */ - failmsg = "Expected parse error, but no problems detected"; - throw new JsonLdError(JsonLdError.Error.NOT_IMPLEMENTED, ""); - } catch (final JsonLdError e) { - if (e.getType() == JsonLdError.Error.PARSE_ERROR) { - passed = true; - } else { - failmsg = "Expected parse error, got: " + e.getMessage(); - } - } - } else { - failmsg = "DON'T KNOW HOW TO HANDLE: " + type; - } - assertTrue(failmsg, passed); - } - - /** - * Compare datasets, normalizing the blank nodes and adding baseIRI to - * relative IRIs - * - * @param result - * @param expected - * @return - */ - private Boolean compareDatasets(final String baseIRI, final RDFDataset result, - final RDFDataset expected) { - final String baseIRIpath = baseIRI.substring(0, baseIRI.lastIndexOf("/") + 1); - final List res = new ArrayList() { - { - for (final RDFDataset.Quad q : result.getQuads("@default")) { - final RDFDataset.Node s = q.getSubject(); - final RDFDataset.Node p = q.getPredicate(); - final RDFDataset.Node o = q.getObject(); - if (s.isIRI() && !s.getValue().contains(":")) { - final String v = s.getValue(); - if (v.startsWith("#") || v.startsWith("?")) { - s.put("value", baseIRI + s.getValue()); - } else { - s.put("value", baseIRIpath + s.getValue()); - } - } - if (p.isIRI() && !p.getValue().contains(":")) { - final String v = p.getValue(); - if (v.startsWith("#") || v.startsWith("?")) { - p.put("value", baseIRI + p.getValue()); - } else { - p.put("value", baseIRIpath + p.getValue()); - } - } - if (o.isIRI() && !o.getValue().contains(":")) { - final String v = o.getValue(); - if (v.startsWith("#") || v.startsWith("?")) { - o.put("value", baseIRI + o.getValue()); - } else { - o.put("value", baseIRIpath + o.getValue()); - } - } - add(q); - } - } - }; - final List exp = new ArrayList() { - { - addAll(expected.getQuads("@default")); - } - }; - final List unmatched = new ArrayList(); - final BnodeMappings bnodeMaps = new BnodeMappings(); - boolean finalpass = false; - while (!exp.isEmpty() && !res.isEmpty()) { - final Quad eq = exp.remove(0); - int matches = 0; - RDFDataset.Quad last_match = null; - for (final RDFDataset.Quad rq : res) { - // if predicates are not equal there cannot be a match - if (!eq.getPredicate().equals(rq.getPredicate())) { - continue; - } - if (eq.getSubject().isBlankNode() && rq.getSubject().isBlankNode()) { - // check for locking - boolean subjectLocked = false; - if (bnodeMaps.isLocked(eq.getSubject().getValue())) { - // if this mapping doesn't match the locked mapping, we - // don't have a match - if (!rq.getSubject().getValue() - .equals(bnodeMaps.getMapping(eq.getSubject().getValue()))) { - continue; - } - subjectLocked = true; - } - // if the objects are also both blank nodes - if (eq.getObject().isBlankNode() && rq.getObject().isBlankNode()) { - // check for locking - if (bnodeMaps.isLocked(eq.getObject().getValue())) { - // if this mapping doesn't match the locked mapping, - // we don't have a match - if (!rq.getObject().getValue() - .equals(bnodeMaps.getMapping(eq.getObject().getValue()))) { - continue; - } - } else { - // add possible mappings for the objects - bnodeMaps.addPossibleMapping(eq.getObject().getValue(), rq.getObject() - .getValue()); - } - } - // otherwise, if the objects aren't equal we can't have a - // match - else if (!eq.getObject().equals(rq.getObject())) { - continue; - } - // objects are equal or both blank nodes so we have a match - matches++; - last_match = rq; - // if subject is not locked add a possible mapping between - // subjects - if (!subjectLocked) { - bnodeMaps.addPossibleMapping(eq.getSubject().getValue(), rq.getSubject() - .getValue()); - } - } - // otherwise check if the subjects are equal - else if (eq.getSubject().equals(rq.getSubject())) { - // if both objects are blank nodes, add possible mappings - // for them - if (eq.getObject().isBlankNode() && rq.getObject().isBlankNode()) { - // check for locking - if (bnodeMaps.isLocked(eq.getObject().getValue())) { - // if this mapping doesn't match the locked mapping, - // we don't have a match - if (!rq.getObject().getValue() - .equals(bnodeMaps.getMapping(eq.getObject().getValue()))) { - continue; - } - } else { - // add possible mappings for the objects - bnodeMaps.addPossibleMapping(eq.getObject().getValue(), rq.getObject() - .getValue()); - } - // if we get here we have a match - matches++; - last_match = rq; - } - // otherwise, if the objects are equal we we have an exact - // match - else if (eq.getObject().equals(rq.getObject())) { - matches = 1; - last_match = rq; - break; - } - } - } - - if (matches == 0) { - // if we didn't find any matches, we're done and things didn't - // match! - return false; - } else if (matches == 1) { - // we have one match - if (eq.getSubject().isBlankNode()) { - // lock this mapping - bnodeMaps.lockMapping(eq.getSubject().getValue(), last_match.getSubject() - .getValue()); - } - if (eq.getObject().isBlankNode()) { - // lock this mapping - bnodeMaps.lockMapping(eq.getObject().getValue(), last_match.getObject() - .getValue()); - } - res.remove(last_match); - } else { - // we got multiple matches, we need to figure this stuff out - // later! - unmatched.add(eq); - } - - // TODO: no tests so far test this out, make one! - if (exp.isEmpty() && !finalpass) { - // if we are at the end and we have unmatched triples - if (!unmatched.isEmpty()) { - // lock the remaining bnodes, and test again - bnodeMaps.lockRemaining(); - exp.addAll(unmatched); - unmatched.clear(); - } - // we also only want to do this once, if we get here again - // without matching everything - // we're not going to match everything - finalpass = true; - } - } - - // they both matched if we have nothing left over - return res.isEmpty() && exp.isEmpty() && unmatched.isEmpty(); - } - - private class BnodeMappings { - Map> possiblebnodemappings = new LinkedHashMap>(); - Map lockedbnodemappings = new LinkedHashMap(); - - public void lockMapping(final String bn1, final String bn2) { - lockedbnodemappings.put(bn1, bn2); - possiblebnodemappings.remove(bn1); - for (final String i : possiblebnodemappings.keySet()) { - // remove bn2 as a possible mapping for any other bnodes - possiblebnodemappings.get(i).remove(bn2); - } - } - - public void lockRemaining() { - final List unlocked = new ArrayList(possiblebnodemappings.keySet()); - for (final String bn1 : unlocked) { - final String bn2 = getMapping(bn1); - assertNotNull("Unable to find mapping for blank node " + bn1 - + ". Possible error in mapping code", bn2); - lockMapping(bn1, bn2); - } - } - - public boolean isLocked(final String b) { - return lockedbnodemappings.containsKey(b); - } - - /** - * return either the locked mapping, or the highest matching - * - * @param b - * @return - */ - public String getMapping(final String b) { - if (isLocked(b)) { - return lockedbnodemappings.get(b); - } else { - int max = -1; - String rval = null; - for (final Entry map : possiblebnodemappings.get(b).entrySet()) { - if (map.getValue() > max) { - max = map.getValue(); - rval = map.getKey(); - } - } - return rval; - } - } - - public void addPossibleMapping(final String bn1, final String bn2) { - Map bn1m; - if (possiblebnodemappings.containsKey(bn1)) { - bn1m = possiblebnodemappings.get(bn1); - } else { - bn1m = new LinkedHashMap(); - possiblebnodemappings.put(bn1, bn1m); - } - Integer mappingcount = 0; - if (bn1m.containsKey(bn2)) { - mappingcount = bn1m.get(bn2); - } - bn1m.put(bn2, mappingcount + 1); - } - } - -} diff --git a/core/src/test/java/com/github/jsonldjava/impl/TurtleRegexTests.java b/core/src/test/java/com/github/jsonldjava/impl/TurtleRegexTests.java deleted file mode 100644 index a5403f2b..00000000 --- a/core/src/test/java/com/github/jsonldjava/impl/TurtleRegexTests.java +++ /dev/null @@ -1,639 +0,0 @@ -package com.github.jsonldjava.impl; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -import java.util.regex.Matcher; - -import org.junit.Test; - -import com.github.jsonldjava.impl.TurtleRDFParser.Regex; - -public class TurtleRegexTests { - - private void printMatcher(Matcher matcher) { - if (matcher.matches()) { - for (int i = 1; i <= matcher.groupCount(); i++) { - System.out.println(matcher.group(i)); - } - } - } - - @Test - public void test_PREFIX_ID() { - Matcher matcher = Regex.PREFIX_ID.matcher("@prefix : ."); - assertTrue(matcher.matches()); - assertEquals(2, matcher.groupCount()); - assertNotNull(matcher.group(1)); - assertNotNull(matcher.group(2)); - assertEquals("", matcher.group(1)); - assertEquals("http://www.google.com/test#", matcher.group(2)); - - matcher = Regex.PREFIX_ID.matcher("@prefix abcdef: ."); - assertTrue(matcher.matches()); - assertEquals(2, matcher.groupCount()); - assertNotNull(matcher.group(1)); - assertNotNull(matcher.group(2)); - assertEquals("abcdef", matcher.group(1)); - assertEquals("http://www.google.com/test#", matcher.group(2)); - } - - @Test - public void test_BASE() { - final Matcher matcher = Regex.BASE.matcher("@base ."); - assertTrue(matcher.matches()); - assertEquals(1, matcher.groupCount()); - assertNotNull(matcher.group(1)); - assertEquals("http://www.google.com/test#", matcher.group(1)); - } - - @Test - public void test_SPARQL_PREFIX() { - Matcher matcher = Regex.SPARQL_PREFIX.matcher("PREFix : "); - assertTrue(matcher.matches()); - assertEquals(2, matcher.groupCount()); - assertNotNull(matcher.group(1)); - assertNotNull(matcher.group(2)); - assertEquals("", matcher.group(1)); - assertEquals("http://www.google.com/test#", matcher.group(2)); - - matcher = Regex.SPARQL_PREFIX.matcher("prefIX abcdef: "); - assertTrue(matcher.matches()); - assertEquals(2, matcher.groupCount()); - assertNotNull(matcher.group(1)); - assertNotNull(matcher.group(2)); - assertEquals("abcdef", matcher.group(1)); - assertEquals("http://www.google.com/test#", matcher.group(2)); - } - - @Test - public void test_SPARQL_BASE() { - final Matcher matcher = Regex.SPARQL_BASE.matcher("BaSe "); - assertTrue(matcher.matches()); - assertEquals(1, matcher.groupCount()); - assertNotNull(matcher.group(1)); - assertEquals("http://www.google.com/test#", matcher.group(1)); - } - - @Test - public void test_DIRECTIVE() { - Matcher matcher = Regex.DIRECTIVE.matcher("@prefix : ."); - assertTrue(matcher.matches()); - assertEquals(6, matcher.groupCount()); - assertEquals("", matcher.group(1)); - assertEquals("http://www.google.com/test#", matcher.group(2)); - - matcher = Regex.DIRECTIVE.matcher("@prefix abc: ."); - assertTrue(matcher.matches()); - assertEquals(6, matcher.groupCount()); - assertEquals("abc", matcher.group(1)); - assertEquals("http://www.google.com/test#", matcher.group(2)); - - matcher = Regex.DIRECTIVE.matcher("@base ."); - assertTrue(matcher.matches()); - assertNull(matcher.group(1)); - assertNull(matcher.group(2)); - assertEquals("http://www.google.com/test#", matcher.group(3)); - - matcher = Regex.DIRECTIVE.matcher("PREFix : "); - assertTrue(matcher.matches()); - assertNull(matcher.group(1)); - assertNull(matcher.group(2)); - assertNull(matcher.group(3)); - assertEquals("", matcher.group(4)); - assertEquals("http://www.google.com/test#", matcher.group(5)); - - matcher = Regex.DIRECTIVE.matcher("PREFix abc: "); - assertTrue(matcher.matches()); - assertNull(matcher.group(1)); - assertNull(matcher.group(2)); - assertNull(matcher.group(3)); - assertEquals("abc", matcher.group(4)); - assertEquals("http://www.google.com/test#", matcher.group(5)); - - matcher = Regex.DIRECTIVE.matcher("BASE "); - assertTrue(matcher.matches()); - assertNull(matcher.group(1)); - assertNull(matcher.group(2)); - assertNull(matcher.group(3)); - assertNull(matcher.group(4)); - assertNull(matcher.group(5)); - assertEquals("http://www.google.com/test#", matcher.group(6)); - } - - @Test - public void test_PREFIXED_NAME() { - assertTrue("abc:def".matches("" + Regex.PREFIXED_NAME)); - assertTrue(":def".matches("" + Regex.PREFIXED_NAME)); - } - - @Test - public void test_IRI() { - Matcher matcher = Regex.IRI.matcher(""); - assertTrue(matcher.matches()); - assertEquals(4, matcher.groupCount()); - assertEquals("http://www.google.com/test#hello", matcher.group(1)); - assertNull(matcher.group(2)); - assertNull(matcher.group(3)); - assertNull(matcher.group(4)); - - matcher = Regex.IRI.matcher("abc:def"); - assertTrue(matcher.matches()); - assertEquals(4, matcher.groupCount()); - assertNull(matcher.group(1)); - assertEquals("abc", matcher.group(2)); - assertEquals("def", matcher.group(3)); - assertNull(matcher.group(4)); - - matcher = Regex.IRI.matcher("hij:"); - assertTrue(matcher.matches()); - assertEquals(4, matcher.groupCount()); - assertNull(matcher.group(1)); - assertNull(matcher.group(2)); - assertNull(matcher.group(3)); - assertEquals("hij", matcher.group(4)); - } - - @Test - public void test_ANON() { - assertTrue("[ ]".matches("^" + Regex.ANON + "$")); - } - - @Test - public void test_BLANK_NODE() { - Matcher matcher = Regex.BLANK_NODE.matcher("_:b0"); - assertTrue(matcher.matches()); - assertEquals(1, matcher.groupCount()); - assertEquals("b0", matcher.group(1)); - - matcher = Regex.BLANK_NODE.matcher("[ ]"); - assertTrue(matcher.matches()); - assertEquals(1, matcher.groupCount()); - assertEquals(null, matcher.group(1)); - } - - @Test - public void test_STRING() { - assertTrue("\"dffhjkasdhfskldhfoiw'eu\\\"fhowleifh\u00F8\u02FF\u0370\u037D\"".matches("^" - + Regex.STRING + "$")); - assertFalse("\"dffhjkasdhfs\nkldhfoiw\\\"'eufhowleifh \u00F8\u02FF\u0370\u037D\"" - .matches("^" + Regex.STRING + "$")); - assertTrue("'dffhjkasdhfskldh\\'foiweu\"fhowleifh \u00F8\u02FF\u0370\u037D'".matches("^" - + Regex.STRING + "$")); - assertFalse("\"dffhjkasdhfs\nkldhfoiw\\\"'eufhowleifh \u00F8\u02FF\u0370\u037D\"" - .matches("^" + Regex.STRING + "$")); - assertTrue("'''dffhjkasdhfsk\nldhfoiw\"'eufhowleifh \u00F8\u02FF\u0370\u037D'''" - .matches("^" + Regex.STRING + "$")); - assertTrue("\"\"\"dffhjkasdhfsk\nldhfoiw\"'eufhowleifh \u00F8\u02FF\u0370\u037D\"\"\"" - .matches("^" + Regex.STRING + "$")); - - Matcher matcher = Regex.STRING.matcher("'''x''y'''"); - assertTrue(matcher.find()); - assertEquals("'''x''y'''", matcher.group(1)); - - matcher = Regex.STRING.matcher("'''" + (char) 0xA + "''' ."); - assertTrue(matcher.find()); - assertEquals("'''" + (char) 0xA + "'''", matcher.group(1)); - - matcher = Regex.STRING.matcher("'''\f'''"); - assertTrue(matcher.find()); - assertEquals("'''\f'''", matcher.group(1)); - - assertFalse("'''x'''y'''".matches("^" + Regex.STRING + "$")); - assertFalse("\"\"\"x\"\"\"y\"\"\"".matches("^" + Regex.STRING + "$")); - } - - @Test - public void test_BOOLEAN_LITERAL() { - assertTrue("true".matches("^" + Regex.BOOLEAN_LITERAL + "$")); - assertTrue("false".matches("^" + Regex.BOOLEAN_LITERAL + "$")); - } - - @Test - public void test_RDF_LITERAL() { - Matcher matcher = Regex.RDF_LITERAL.matcher("\"hello\"@en"); - assertTrue(matcher.matches()); - assertEquals(6, matcher.groupCount()); - assertEquals("\"hello\"", matcher.group(1)); - assertEquals("en", matcher.group(2)); - assertNull(matcher.group(3)); - assertNull(matcher.group(4)); - assertNull(matcher.group(5)); - assertNull(matcher.group(6)); - - matcher = Regex.RDF_LITERAL.matcher("\"123\"^^xsd:integer"); - assertTrue(matcher.matches()); - assertEquals(6, matcher.groupCount()); - assertEquals("\"123\"", matcher.group(1)); - assertNull(matcher.group(2)); - assertNull(matcher.group(3)); - assertEquals("xsd", matcher.group(4)); - assertEquals("integer", matcher.group(5)); - assertNull(matcher.group(6)); - - matcher = Regex.RDF_LITERAL.matcher("\"123\"^^"); - assertTrue(matcher.matches()); - assertEquals(6, matcher.groupCount()); - assertEquals("\"123\"", matcher.group(1)); - assertNull(matcher.group(2)); - assertEquals("http://fake/type", matcher.group(3)); - assertNull(matcher.group(4)); - assertNull(matcher.group(5)); - assertNull(matcher.group(6)); - - matcher = Regex.RDF_LITERAL.matcher("\"123\"^^def:"); - assertTrue(matcher.matches()); - assertEquals(6, matcher.groupCount()); - assertEquals("\"123\"", matcher.group(1)); - assertNull(matcher.group(2)); - assertNull(matcher.group(3)); - assertNull(matcher.group(4)); - assertNull(matcher.group(5)); - assertEquals("def", matcher.group(6)); - } - - @Test - public void test_NUMERIC_LITERAL() { - Matcher matcher = Regex.NUMERIC_LITERAL.matcher("3E1"); - assertTrue(matcher.matches()); - assertEquals(3, matcher.groupCount()); - assertEquals("3E1", matcher.group(1)); - - matcher = Regex.NUMERIC_LITERAL.matcher("-5.1E-10000"); - assertTrue(matcher.matches()); - assertEquals("-5.1E-10000", matcher.group(1)); - - matcher = Regex.NUMERIC_LITERAL.matcher("2.01"); - assertTrue(matcher.matches()); - assertNull(matcher.group(1)); - assertEquals("2.01", matcher.group(2)); - - matcher = Regex.NUMERIC_LITERAL.matcher("123"); - assertTrue(matcher.matches()); - assertNull(matcher.group(1)); - assertNull(matcher.group(2)); - assertEquals("123", matcher.group(3)); - - matcher = Regex.NUMERIC_LITERAL.matcher("-1"); - assertTrue(matcher.matches()); - assertNull(matcher.group(1)); - assertNull(matcher.group(2)); - assertEquals("-1", matcher.group(3)); - } - - @Test - public void test_SUBJECT() { - Matcher matcher = Regex.SUBJECT.matcher(""); - assertTrue(matcher.matches()); - assertEquals(5, matcher.groupCount()); - assertEquals("http://www.google.com/test#hello", matcher.group(1)); - - matcher = Regex.SUBJECT.matcher("abc:def"); - assertTrue(matcher.matches()); - assertNull(matcher.group(1)); - assertEquals("abc", matcher.group(2)); - assertEquals("def", matcher.group(3)); - - matcher = Regex.SUBJECT.matcher("hij:"); - assertTrue(matcher.matches()); - assertNull(matcher.group(1)); - assertNull(matcher.group(2)); - assertNull(matcher.group(3)); - assertEquals("hij", matcher.group(4)); - - matcher = Regex.SUBJECT.matcher("_:b0"); - assertTrue(matcher.matches()); - assertNull(matcher.group(1)); - assertNull(matcher.group(2)); - assertNull(matcher.group(3)); - assertNull(matcher.group(4)); - assertEquals("b0", matcher.group(5)); - - // a subject match without any matching groups should == an anonymous - // subject - matcher = Regex.SUBJECT.matcher("[ ]"); - assertTrue(matcher.matches()); - } - - @Test - public void test_PREDICATE() { - Matcher matcher = Regex.PREDICATE.matcher(""); - assertTrue(matcher.matches()); - assertEquals(4, matcher.groupCount()); - assertEquals("http://www.google.com/test#hello", matcher.group(1)); - - matcher = Regex.PREDICATE.matcher("abc:def"); - assertTrue(matcher.matches()); - assertNull(matcher.group(1)); - assertEquals("abc", matcher.group(2)); - assertEquals("def", matcher.group(3)); - - matcher = Regex.PREDICATE.matcher("hij:"); - assertTrue(matcher.matches()); - assertNull(matcher.group(1)); - assertNull(matcher.group(2)); - assertNull(matcher.group(3)); - assertEquals("hij", matcher.group(4)); - - // match rdf:type shorthand - matcher = Regex.PREDICATE.matcher("a "); - assertTrue(matcher.matches()); - assertEquals("a ", matcher.group(0)); - assertNull(matcher.group(1)); - assertNull(matcher.group(2)); - assertNull(matcher.group(3)); - assertNull(matcher.group(4)); - } - - @Test - public void test_LITERAL() { - Matcher matcher = Regex.LITERAL.matcher("\"hello\"@en"); - assertTrue(matcher.matches()); - assertEquals(10, matcher.groupCount()); - assertEquals("\"hello\"", matcher.group(1)); - assertEquals("en", matcher.group(2)); - - matcher = Regex.LITERAL.matcher("\"123\"^^xsd:integer"); - assertTrue(matcher.matches()); - assertEquals("\"123\"", matcher.group(1)); - assertNull(matcher.group(2)); - assertNull(matcher.group(3)); - assertEquals("xsd", matcher.group(4)); - assertEquals("integer", matcher.group(5)); - - matcher = Regex.LITERAL.matcher("\"123\"^^"); - assertTrue(matcher.matches()); - assertEquals("\"123\"", matcher.group(1)); - assertNull(matcher.group(2)); - assertEquals("http://fake/type", matcher.group(3)); - - matcher = Regex.LITERAL.matcher("\"123\"^^def:"); - assertTrue(matcher.matches()); - assertEquals("\"123\"", matcher.group(1)); - assertNull(matcher.group(2)); - assertNull(matcher.group(3)); - assertNull(matcher.group(4)); - assertNull(matcher.group(5)); - assertEquals("def", matcher.group(6)); - - matcher = Regex.LITERAL.matcher("123"); - assertTrue(matcher.matches()); - assertNull(matcher.group(1)); - assertNull(matcher.group(2)); - assertNull(matcher.group(3)); - assertNull(matcher.group(4)); - assertNull(matcher.group(5)); - assertNull(matcher.group(6)); - assertNull(matcher.group(7)); - assertNull(matcher.group(8)); - assertEquals("123", matcher.group(9)); - - matcher = Regex.LITERAL.matcher("-1"); - assertTrue(matcher.matches()); - assertNull(matcher.group(1)); - assertNull(matcher.group(2)); - assertNull(matcher.group(3)); - assertNull(matcher.group(4)); - assertNull(matcher.group(5)); - assertNull(matcher.group(6)); - assertNull(matcher.group(7)); - assertNull(matcher.group(8)); - assertEquals("-1", matcher.group(9)); - - matcher = Regex.LITERAL.matcher("2.01"); - assertTrue(matcher.matches()); - assertNull(matcher.group(1)); - assertNull(matcher.group(2)); - assertNull(matcher.group(3)); - assertNull(matcher.group(4)); - assertNull(matcher.group(5)); - assertNull(matcher.group(6)); - assertNull(matcher.group(7)); - assertEquals("2.01", matcher.group(8)); - assertNull(matcher.group(9)); - - matcher = Regex.LITERAL.matcher("3E1"); - assertTrue(matcher.matches()); - assertTrue(matcher.matches()); - assertNull(matcher.group(1)); - assertNull(matcher.group(2)); - assertNull(matcher.group(3)); - assertNull(matcher.group(4)); - assertNull(matcher.group(5)); - assertNull(matcher.group(6)); - assertEquals("3E1", matcher.group(7)); - - matcher = Regex.LITERAL.matcher("-5.1E-10000"); - assertTrue(matcher.matches()); - assertTrue(matcher.matches()); - assertTrue(matcher.matches()); - assertNull(matcher.group(1)); - assertNull(matcher.group(2)); - assertNull(matcher.group(3)); - assertNull(matcher.group(4)); - assertNull(matcher.group(5)); - assertNull(matcher.group(6)); - assertEquals("-5.1E-10000", matcher.group(7)); - - matcher = Regex.LITERAL.matcher("true"); - assertTrue(matcher.matches()); - assertTrue(matcher.matches()); - assertTrue(matcher.matches()); - assertNull(matcher.group(1)); - assertNull(matcher.group(2)); - assertNull(matcher.group(3)); - assertNull(matcher.group(4)); - assertNull(matcher.group(5)); - assertNull(matcher.group(6)); - assertNull(matcher.group(7)); - assertNull(matcher.group(8)); - assertNull(matcher.group(9)); - assertEquals("true", matcher.group(10)); - - } - - @Test - public void test_OBJECT() { - // IRIs should be at position 1 - Matcher matcher = Regex.OBJECT.matcher("<>"); - assertTrue(matcher.matches()); - assertEquals(15, matcher.groupCount()); - assertEquals("", matcher.group(1)); - - matcher = Regex.OBJECT.matcher(""); - assertTrue(matcher.matches()); - assertEquals("http://test", matcher.group(1)); - - // prefixed names should be at pos 2 + 3 - matcher = Regex.OBJECT.matcher("abc:def"); - assertTrue(matcher.matches()); - assertNull(matcher.group(1)); - assertEquals("abc", matcher.group(2)); - assertEquals("def", matcher.group(3)); - - matcher = Regex.OBJECT.matcher(":def"); - assertTrue(matcher.matches()); - assertNull(matcher.group(1)); - assertEquals("", matcher.group(2)); - assertEquals("def", matcher.group(3)); - - // prefixes only should be at pos 4 - matcher = Regex.OBJECT.matcher("hij:"); - assertTrue(matcher.matches()); - assertNull(matcher.group(1)); - assertNull(matcher.group(2)); - assertNull(matcher.group(3)); - assertEquals("hij", matcher.group(4)); - - // blank node ids should be at pos 5 - matcher = Regex.OBJECT.matcher("_:b0"); - assertTrue(matcher.matches()); - assertNull(matcher.group(1)); - assertNull(matcher.group(2)); - assertNull(matcher.group(3)); - assertNull(matcher.group(4)); - assertEquals("b0", matcher.group(5)); - - // strings should be in position 6 - matcher = Regex.OBJECT.matcher("\"hello world\""); - assertTrue(matcher.matches()); - assertNull(matcher.group(1)); - assertNull(matcher.group(2)); - assertNull(matcher.group(3)); - assertNull(matcher.group(4)); - assertNull(matcher.group(5)); - assertEquals("\"hello world\"", matcher.group(6)); - assertNull(matcher.group(7)); - assertNull(matcher.group(8)); - assertNull(matcher.group(9)); - assertNull(matcher.group(11)); - - // language taged strings should be at position 6 + 7 for langtag - matcher = Regex.OBJECT.matcher("\"hello\"@en"); - assertTrue(matcher.matches()); - assertNull(matcher.group(1)); - assertNull(matcher.group(2)); - assertNull(matcher.group(3)); - assertNull(matcher.group(4)); - assertNull(matcher.group(5)); - assertEquals("\"hello\"", matcher.group(6)); - assertEquals("en", matcher.group(7)); - - // literals with IRI datatype should be at pos 6 + 8 - matcher = Regex.OBJECT.matcher("\"123\"^^"); - assertTrue(matcher.matches()); - assertNull(matcher.group(1)); - assertNull(matcher.group(2)); - assertNull(matcher.group(3)); - assertNull(matcher.group(4)); - assertNull(matcher.group(5)); - assertEquals("\"123\"", matcher.group(6)); - assertNull(matcher.group(7)); - assertEquals("http://test/type", matcher.group(8)); - - // literals with ns:name datatype should be at pos 6 + 9 + 10 - matcher = Regex.OBJECT.matcher("\"123\"^^xsd:integer"); - assertTrue(matcher.matches()); - assertNull(matcher.group(1)); - assertNull(matcher.group(2)); - assertNull(matcher.group(3)); - assertNull(matcher.group(4)); - assertNull(matcher.group(5)); - assertEquals("\"123\"", matcher.group(6)); - assertNull(matcher.group(7)); - assertNull(matcher.group(8)); - assertEquals("xsd", matcher.group(9)); - assertEquals("integer", matcher.group(10)); - - // literals with ns: datatype should be at pos 6 + 11 - matcher = Regex.OBJECT.matcher("\"123\"^^def:"); - assertTrue(matcher.matches()); - assertNull(matcher.group(1)); - assertNull(matcher.group(2)); - assertNull(matcher.group(3)); - assertNull(matcher.group(4)); - assertNull(matcher.group(5)); - assertEquals("\"123\"", matcher.group(6)); - assertNull(matcher.group(7)); - assertNull(matcher.group(8)); - assertNull(matcher.group(9)); - assertNull(matcher.group(10)); - assertEquals("def", matcher.group(11)); - - // double literals should be at pos 12 - matcher = Regex.OBJECT.matcher("1.234E-10"); - assertTrue(matcher.matches()); - assertNull(matcher.group(1)); - assertNull(matcher.group(2)); - assertNull(matcher.group(3)); - assertNull(matcher.group(4)); - assertNull(matcher.group(5)); - assertNull(matcher.group(6)); - assertNull(matcher.group(7)); - assertNull(matcher.group(8)); - assertNull(matcher.group(9)); - assertNull(matcher.group(10)); - assertNull(matcher.group(11)); - assertEquals("1.234E-10", matcher.group(12)); - - // decimal literals should be at pos 13 - matcher = Regex.OBJECT.matcher("12.34"); - assertTrue(matcher.matches()); - assertNull(matcher.group(1)); - assertNull(matcher.group(2)); - assertNull(matcher.group(3)); - assertNull(matcher.group(4)); - assertNull(matcher.group(5)); - assertNull(matcher.group(6)); - assertNull(matcher.group(7)); - assertNull(matcher.group(8)); - assertNull(matcher.group(9)); - assertNull(matcher.group(10)); - assertNull(matcher.group(11)); - assertNull(matcher.group(12)); - assertEquals("12.34", matcher.group(13)); - - // integer literals should be at pos 14 - matcher = Regex.OBJECT.matcher("1234"); - assertTrue(matcher.matches()); - assertNull(matcher.group(1)); - assertNull(matcher.group(2)); - assertNull(matcher.group(3)); - assertNull(matcher.group(4)); - assertNull(matcher.group(5)); - assertNull(matcher.group(6)); - assertNull(matcher.group(7)); - assertNull(matcher.group(8)); - assertNull(matcher.group(9)); - assertNull(matcher.group(10)); - assertNull(matcher.group(11)); - assertNull(matcher.group(12)); - assertNull(matcher.group(13)); - assertEquals("1234", matcher.group(14)); - - // boolean literals should be at pos 15 - matcher = Regex.OBJECT.matcher("false"); - assertTrue(matcher.matches()); - assertNull(matcher.group(1)); - assertNull(matcher.group(2)); - assertNull(matcher.group(3)); - assertNull(matcher.group(4)); - assertNull(matcher.group(5)); - assertNull(matcher.group(6)); - assertNull(matcher.group(7)); - assertNull(matcher.group(8)); - assertNull(matcher.group(9)); - assertNull(matcher.group(10)); - assertNull(matcher.group(11)); - assertNull(matcher.group(12)); - assertNull(matcher.group(13)); - assertNull(matcher.group(14)); - assertEquals("false", matcher.group(15)); - - matcher = Regex.OBJECT.matcher("\"IRI with four digit numeric escape (\\\\u)\" ;"); - assertTrue(matcher.find()); - } -} diff --git a/core/src/test/java/com/github/jsonldjava/utils/EarlTestSuite.java b/core/src/test/java/com/github/jsonldjava/utils/EarlTestSuite.java index 23569186..cfd20c90 100644 --- a/core/src/test/java/com/github/jsonldjava/utils/EarlTestSuite.java +++ b/core/src/test/java/com/github/jsonldjava/utils/EarlTestSuite.java @@ -62,8 +62,8 @@ public EarlTestSuite(String manifestURL, String cacheDir, String etag) throws IO if (manifestURL.endsWith(".ttl") || manifestURL.endsWith("nq") || manifestURL.endsWith("nt")) { try { - Map rval = (Map) JsonLdProcessor.fromRDF( - manifestFile, new JsonLdOptions(manifestURL) { + Map rval = (Map) JsonLdProcessor + .fromRDF(manifestFile, new JsonLdOptions(manifestURL) { { this.format = "text/turtle"; this.useNamespaces = true; diff --git a/core/src/test/java/com/github/jsonldjava/utils/JarCacheTest.java b/core/src/test/java/com/github/jsonldjava/utils/JarCacheTest.java index 4dca5951..259ca515 100644 --- a/core/src/test/java/com/github/jsonldjava/utils/JarCacheTest.java +++ b/core/src/test/java/com/github/jsonldjava/utils/JarCacheTest.java @@ -12,8 +12,12 @@ import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; -import org.apache.http.impl.client.SystemDefaultHttpClient; -import org.apache.http.impl.client.cache.CachingHttpClient; +import org.apache.http.client.protocol.RequestAcceptEncoding; +import org.apache.http.client.protocol.ResponseContentEncoding; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.DefaultRedirectStrategy; +import org.apache.http.impl.client.cache.CacheConfig; +import org.apache.http.impl.client.cache.CachingHttpClientBuilder; import org.junit.After; import org.junit.Test; @@ -21,9 +25,10 @@ public class JarCacheTest { @Test public void cacheHit() throws Exception { - final JarCacheStorage storage = new JarCacheStorage(); - final HttpClient httpClient = new CachingHttpClient(new SystemDefaultHttpClient(), storage, - storage.getCacheConfig()); + final CacheConfig cacheConfig = CacheConfig.custom().setMaxCacheEntries(1000) + .setMaxObjectSize(1024 * 128).build(); + final JarCacheStorage storage = new JarCacheStorage(null, cacheConfig); + final HttpClient httpClient = createTestHttpClient(cacheConfig, storage); final HttpGet get = new HttpGet("http://nonexisting.example.com/context"); final HttpResponse resp = httpClient.execute(get); @@ -34,20 +39,22 @@ public void cacheHit() throws Exception { @Test(expected = IOException.class) public void cacheMiss() throws Exception { - final JarCacheStorage storage = new JarCacheStorage(); - final HttpClient httpClient = new CachingHttpClient(new SystemDefaultHttpClient(), storage, - storage.getCacheConfig()); + final CacheConfig cacheConfig = CacheConfig.custom().setMaxCacheEntries(1000) + .setMaxObjectSize(1024 * 128).build(); + final JarCacheStorage storage = new JarCacheStorage(null, cacheConfig); + final HttpClient httpClient = createTestHttpClient(cacheConfig, storage); final HttpGet get = new HttpGet("http://nonexisting.example.com/notfound"); // Should throw an IOException as the DNS name // nonexisting.example.com does not exist - final HttpResponse resp = httpClient.execute(get); + httpClient.execute(get); } @Test public void doubleLoad() throws Exception { - final JarCacheStorage storage = new JarCacheStorage(); - final HttpClient httpClient = new CachingHttpClient(new SystemDefaultHttpClient(), storage, - storage.getCacheConfig()); + final CacheConfig cacheConfig = CacheConfig.custom().setMaxCacheEntries(1000) + .setMaxObjectSize(1024 * 128).build(); + final JarCacheStorage storage = new JarCacheStorage(null, cacheConfig); + final HttpClient httpClient = createTestHttpClient(cacheConfig, storage); final HttpGet get = new HttpGet("http://nonexisting.example.com/context"); HttpResponse resp = httpClient.execute(get); resp = httpClient.execute(get); @@ -59,10 +66,10 @@ public void doubleLoad() throws Exception { public void customClassPath() throws Exception { final URL nestedJar = getClass().getResource("/nested.jar"); final ClassLoader cl = new URLClassLoader(new URL[] { nestedJar }); - final JarCacheStorage storage = new JarCacheStorage(cl); - - final HttpClient httpClient = new CachingHttpClient(new SystemDefaultHttpClient(), storage, - storage.getCacheConfig()); + final CacheConfig cacheConfig = CacheConfig.custom().setMaxCacheEntries(1000) + .setMaxObjectSize(1024 * 128).build(); + final JarCacheStorage storage = new JarCacheStorage(cl, cacheConfig); + final HttpClient httpClient = createTestHttpClient(cacheConfig, storage); final HttpGet get = new HttpGet("http://nonexisting.example.com/nested/hello"); final HttpResponse resp = httpClient.execute(get); @@ -77,11 +84,12 @@ public void contextClassLoader() throws Exception { assertNotNull(nestedJar); final ClassLoader cl = new URLClassLoader(new URL[] { nestedJar }); - final JarCacheStorage storage = new JarCacheStorage(); + final CacheConfig cacheConfig = CacheConfig.custom().setMaxCacheEntries(1000) + .setMaxObjectSize(1024 * 128).build(); + final JarCacheStorage storage = new JarCacheStorage(cl, cacheConfig); Thread.currentThread().setContextClassLoader(cl); - final HttpClient httpClient = new CachingHttpClient(new SystemDefaultHttpClient(), storage, - storage.getCacheConfig()); + final HttpClient httpClient = createTestHttpClient(cacheConfig, storage); final HttpGet get = new HttpGet("http://nonexisting.example.com/nested/hello"); final HttpResponse resp = httpClient.execute(get); @@ -99,13 +107,31 @@ public void setContextClassLoader() { public void systemClassLoader() throws Exception { final URL nestedJar = getClass().getResource("/nested.jar"); assertNotNull(nestedJar); - final JarCacheStorage storage = new JarCacheStorage(null); + final CacheConfig cacheConfig = CacheConfig.custom().setMaxCacheEntries(1000) + .setMaxObjectSize(1024 * 128).build(); + final JarCacheStorage storage = new JarCacheStorage(null, cacheConfig); - final HttpClient httpClient = new CachingHttpClient(new SystemDefaultHttpClient(), storage, - storage.getCacheConfig()); + final HttpClient httpClient = createTestHttpClient(cacheConfig, storage); final HttpGet get = new HttpGet("http://nonexisting.example.com/context"); final HttpResponse resp = httpClient.execute(get); assertEquals("application/ld+json", resp.getEntity().getContentType().getValue()); } + private static CloseableHttpClient createTestHttpClient(CacheConfig cacheConfig, + JarCacheStorage jarCacheConfig) { + final CloseableHttpClient result = CachingHttpClientBuilder.create() + // allow caching + .setCacheConfig(cacheConfig) + // Set the JarCacheStorage instance as the HttpCache + .setHttpCacheStorage(jarCacheConfig) + // Support compressed data + // http://hc.apache.org/httpcomponents-client-ga/tutorial/html/httpagent.html#d5e1238 + .addInterceptorFirst(new RequestAcceptEncoding()) + .addInterceptorFirst(new ResponseContentEncoding()) + .setRedirectStrategy(DefaultRedirectStrategy.INSTANCE) + // use system defaults for proxy etc. + .useSystemProperties().build(); + + return result; + } } diff --git a/core/src/test/java/com/github/jsonldjava/utils/JsonUtilsTest.java b/core/src/test/java/com/github/jsonldjava/utils/JsonUtilsTest.java index b87182d7..d0387b49 100644 --- a/core/src/test/java/com/github/jsonldjava/utils/JsonUtilsTest.java +++ b/core/src/test/java/com/github/jsonldjava/utils/JsonUtilsTest.java @@ -1,15 +1,35 @@ package com.github.jsonldjava.utils; + +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; import java.util.Map; -import com.fasterxml.jackson.core.JsonParseException; - import org.junit.Test; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.ObjectMapper; + public class JsonUtilsTest { + @Test + public void resolveTest() { + final String baseUri = "http://mysite.net"; + final String pathToResolve = "picture.jpg"; + String resolve = ""; + + try { + resolve = JsonLdUrl.resolve(baseUri, pathToResolve); + assertEquals(baseUri + "/" + pathToResolve, resolve); + } catch (final Exception e) { + assertTrue(false); + } + } @SuppressWarnings("unchecked") @Test @@ -20,13 +40,12 @@ public void fromStringTest() { try { obj = JsonUtils.fromString(testString); + assertTrue(((Map) obj).containsKey("seq")); + assertTrue(((Map) obj).get("seq") instanceof Number); } catch (final Exception e) { assertTrue(false); } - assertTrue(((Map) obj).containsKey("seq")); - assertTrue(((Map) obj).get("seq") instanceof Number); - try { obj = JsonUtils.fromString(testFailure); assertTrue(false); @@ -35,6 +54,15 @@ public void fromStringTest() { } } + @Test + public void testFromJsonParser() throws Exception { + final ObjectMapper jsonMapper = new ObjectMapper(); + final JsonFactory jsonFactory = new JsonFactory(jsonMapper); + final Reader testInputString = new StringReader("{}"); + final JsonParser jp = jsonFactory.createParser(testInputString); + JsonUtils.fromJsonParser(jp); + } + @Test public void trailingContent_1() throws JsonParseException, IOException { trailingContent("{}"); diff --git a/core/src/test/resources/custom/base-0001-in.jsonld b/core/src/test/resources/custom/base-0001-in.jsonld new file mode 100644 index 00000000..12a082af --- /dev/null +++ b/core/src/test/resources/custom/base-0001-in.jsonld @@ -0,0 +1,17 @@ +{ + "@context": [ + "https://raw.githubusercontent.com/jsonld-java/jsonld-java/master/core/src/test/resources/custom/monarch-context.jsonld", + { + "@base": "http://example.org/base/", + "ex": "http://example.org/", + "ex:friendOf": { + "@type": "@id" + } + } + ], + "@id": "3456", + "ex:name": "Jim", + "ex:friendOf": "1234", + "@type": "Person" +} + diff --git a/core/src/test/resources/custom/base-0001-out.jsonld b/core/src/test/resources/custom/base-0001-out.jsonld new file mode 100644 index 00000000..5d522f30 --- /dev/null +++ b/core/src/test/resources/custom/base-0001-out.jsonld @@ -0,0 +1,10 @@ +[ { + "@id" : "http://example.org/base/3456", + "@type" : [ "http://example.org/base/Person" ], + "http://example.org/friendOf" : [ { + "@id" : "http://example.org/base/1234" + } ], + "http://example.org/name" : [ { + "@value" : "Jim" + } ] +} ] \ No newline at end of file diff --git a/core/src/test/resources/custom/base-0002-in.jsonld b/core/src/test/resources/custom/base-0002-in.jsonld new file mode 100644 index 00000000..d533f432 --- /dev/null +++ b/core/src/test/resources/custom/base-0002-in.jsonld @@ -0,0 +1,17 @@ +{ + "@context": [ + { + "@base": "http://example.org/base/", + "ex": "http://example.org/", + "ex:friendOf": { + "@type": "@id" + } + }, + "https://raw.githubusercontent.com/jsonld-java/jsonld-java/master/core/src/test/resources/custom/monarch-context.jsonld" + ], + "@id": "3456", + "ex:name": "Jim", + "ex:friendOf": "1234", + "@type": "Person" +} + diff --git a/core/src/test/resources/custom/base-0002-out.jsonld b/core/src/test/resources/custom/base-0002-out.jsonld new file mode 100644 index 00000000..5d522f30 --- /dev/null +++ b/core/src/test/resources/custom/base-0002-out.jsonld @@ -0,0 +1,10 @@ +[ { + "@id" : "http://example.org/base/3456", + "@type" : [ "http://example.org/base/Person" ], + "http://example.org/friendOf" : [ { + "@id" : "http://example.org/base/1234" + } ], + "http://example.org/name" : [ { + "@value" : "Jim" + } ] +} ] \ No newline at end of file diff --git a/core/src/test/resources/custom/base-0003-in.jsonld b/core/src/test/resources/custom/base-0003-in.jsonld new file mode 100644 index 00000000..da995fd0 --- /dev/null +++ b/core/src/test/resources/custom/base-0003-in.jsonld @@ -0,0 +1,17 @@ +{ + "@context": { + "@base": "http://mysite.net", + "DataSet": "http://schema.org/DataSet", + "CreativeWork": "http://schema.org/CreativeWork" + }, + "@graph": [ + { + "@type": "CreativeWork", + "@id": "picture.jpg" + }, + { + "@id": "./", + "@type": "DataSet" + } + ] +} diff --git a/core/src/test/resources/custom/base-0003-out.jsonld b/core/src/test/resources/custom/base-0003-out.jsonld new file mode 100644 index 00000000..d925a8dc --- /dev/null +++ b/core/src/test/resources/custom/base-0003-out.jsonld @@ -0,0 +1,7 @@ +[ { + "@id" : "http://mysite.net/picture.jpg", + "@type" : [ "http://schema.org/CreativeWork" ] +}, { + "@id" : "http://mysite.net/", + "@type" : [ "http://schema.org/DataSet" ] +} ] diff --git a/core/src/test/resources/custom/contexttest-0004.jsonld b/core/src/test/resources/custom/contexttest-0004.jsonld new file mode 100644 index 00000000..74d28f6d --- /dev/null +++ b/core/src/test/resources/custom/contexttest-0004.jsonld @@ -0,0 +1,28 @@ +{ + "@context": [ + { + "wdl": "http://wellcomelibrary.org/iiif-ext/0#", + "rdfs": "http://www.w3.org/2000/01/rdf-schema#", + + "accessHint": { + "@id": "wdl:accessHint", + "@type": "@vocab" + }, + "open": { + "@id": "wdl:openAccess", + "@type": "wdl:accessHint" + }, + "clickthrough": { + "@id": "wdl:clickthrough", + "@type": "wdl:accessHint" + }, + "credentials": { + "@id": "wdl:credentials", + "@type": "wdl:accessHint" + }, + "authService": { + "@id": "wdl:suggestedAuthService" + } + } + ] +} \ No newline at end of file diff --git a/core/src/test/resources/custom/contexttest-0005.jsonld b/core/src/test/resources/custom/contexttest-0005.jsonld new file mode 100644 index 00000000..f4baa06e --- /dev/null +++ b/core/src/test/resources/custom/contexttest-0005.jsonld @@ -0,0 +1,12 @@ +{ + "@context": { + "@base": "http://ex.com/base/", + "@vocab": "http://ex.com/vocab/", + "xsd": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + "integer": { + "@id": "http://example.com/vocab/integer", + "@type": "xsd:integer" + }, + "@language": "en" + } +} \ No newline at end of file diff --git a/core/src/test/resources/custom/frame-0002-frame.jsonld b/core/src/test/resources/custom/frame-0002-frame.jsonld new file mode 100644 index 00000000..43e9937f --- /dev/null +++ b/core/src/test/resources/custom/frame-0002-frame.jsonld @@ -0,0 +1,6 @@ +{ + "@context": { + "@vocab": "http://xmlns.com/foaf/0.1/" + }, + "@type": "Person" +} diff --git a/core/src/test/resources/custom/frame-0002-in.jsonld b/core/src/test/resources/custom/frame-0002-in.jsonld new file mode 100644 index 00000000..ae706c5c --- /dev/null +++ b/core/src/test/resources/custom/frame-0002-in.jsonld @@ -0,0 +1,13 @@ +{ + "@context": { + "@vocab": "http://xmlns.com/foaf/0.1/", + "member": {"@type": "@id"} + }, + "@graph": [{ + "@type": "Person", + "member": "_:b1" + }, { + "@id": "_:b1", + "@type": "Group" + }] +} diff --git a/core/src/test/resources/custom/frame-0002-out.jsonld b/core/src/test/resources/custom/frame-0002-out.jsonld new file mode 100644 index 00000000..c795cb8e --- /dev/null +++ b/core/src/test/resources/custom/frame-0002-out.jsonld @@ -0,0 +1,13 @@ +{ + "@context" : { + "@vocab" : "http://xmlns.com/foaf/0.1/" + }, + "@graph" : [ { + "@id" : "_:b0", + "@type" : "Person", + "member" : [{ + "@id" : "_:b1", + "@type" : "Group" + }] + } ] +} diff --git a/core/src/test/resources/custom/frame-0003-out.jsonld b/core/src/test/resources/custom/frame-0003-out.jsonld new file mode 100644 index 00000000..b87d5710 --- /dev/null +++ b/core/src/test/resources/custom/frame-0003-out.jsonld @@ -0,0 +1,11 @@ +{ + "@context" : { + "@vocab" : "http://xmlns.com/foaf/0.1/" + }, + "@graph" : [ { + "@type" : "Person", + "member" : [{ + "@type" : "Group" + }] + } ] +} diff --git a/core/src/test/resources/custom/frame-0004-frame.jsonld b/core/src/test/resources/custom/frame-0004-frame.jsonld new file mode 100644 index 00000000..661045f2 --- /dev/null +++ b/core/src/test/resources/custom/frame-0004-frame.jsonld @@ -0,0 +1,72 @@ +{ + "@context": { + "sc": "http://iiif.io/api/presentation/2#", + "oa": "http://www.w3.org/ns/oa#", + "manifests": { + "@type": "@id", + "@id": "sc:hasManifests", + "@container": "@list" + }, + "sequences": { + "@type": "@id", + "@id": "sc:hasSequences", + "@container": "@list" + }, + "canvases": { + "@type": "@id", + "@id": "sc:hasCanvases", + "@container": "@list" + }, + "resources": { + "@type": "@id", + "@id": "sc:hasAnnotations", + "@container": "@set" + }, + "images": { + "@type": "@id", + "@id": "sc:hasImageAnnotations", + "@container": "@list" + }, + "otherContent": { + "@type": "@id", + "@id": "sc:hasLists", + "@container": "@list" + }, + "resource" : { + "@id" : "oa:hasBody", + "@type" : "@id" + }, + "on" : { + "@id" : "oa:hasTarget", + "@type" : "@id" + } + }, + "@type": "sc:Manifest", + "sequences": [ + { + "@type": "sc:Sequence", + "startCanvas": { + "@type": "sc:Canvas", + "@omitDefault": true, + "@embed": false + }, + "canvases": [ + { + "@type": "sc:Canvas", + "images": [ + { + "@type": "oa:Annotation", + "@embed": true + } + ], + "otherContent": [ + { + "@type": "sc:AnnotationList", + "@embed": true + } + ] + } + ] + } + ] +} diff --git a/core/src/test/resources/custom/frame-0004-in.jsonld b/core/src/test/resources/custom/frame-0004-in.jsonld new file mode 100644 index 00000000..d65c4cb2 --- /dev/null +++ b/core/src/test/resources/custom/frame-0004-in.jsonld @@ -0,0 +1,117 @@ +{ + "@graph": [ + { + "@id": "_:b1", + "@type": "oa:Annotation", + "resource": "http://localhost/r1.jp2", + "on": "http://localhost/c1" + }, + { + "@id": "_:b2", + "@type": "oa:Annotation", + "resource": "http://localhost/r0.jp2", + "on": "http://localhost/c0" + }, + { + "@id": "http://localhost:5004/r0.jp2", + "@type": "http://iiif.io/api/image/2/context.json" + }, + { + "@id": "http://localhost:5004/r1.jp2", + "@type": "http://iiif.io/api/image/2/context.json" + }, + { + "@id": "http://localhost/c0", + "@type": "sc:Canvas", + "images": [ + "_:b2" + ], + "otherContent": [ + "http://localhost/l0" + ] + }, + { + "@id": "http://localhost/c1", + "@type": "sc:Canvas", + "images": [ + "_:b1" + ], + "otherContent": [ + "http://localhost/l1" + ] + }, + { + "@id": "http://localhost/l0", + "@type": "sc:AnnotationList" + }, + { + "@id": "http://localhost/l1", + "@type": "sc:AnnotationList" + }, + { + "@id": "http://localhost/manifest", + "@type": "sc:Manifest", + "sequences": [ + "http://localhost/sequence/normal" + ] + }, + { + "@id": "http://localhost/r0.jp2", + "@type": "dctypes:Image" + }, + { + "@id": "http://localhost/r1.jp2", + "@type": "dctypes:Image" + }, + { + "@id": "http://localhost/sequence/normal", + "@type": "sc:Sequence", + "canvases": [ + "http://localhost/c0", + "http://localhost/c1" + ] + } + ], + "@context": { + "sc": "http://iiif.io/api/presentation/2#", + "oa": "http://www.w3.org/ns/oa#", + "manifests": { + "@type": "@id", + "@id": "sc:hasManifests", + "@container": "@list" + }, + "sequences": { + "@type": "@id", + "@id": "sc:hasSequences", + "@container": "@list" + }, + "canvases": { + "@type": "@id", + "@id": "sc:hasCanvases", + "@container": "@list" + }, + "resources": { + "@type": "@id", + "@id": "sc:hasAnnotations", + "@container": "@set" + }, + "images": { + "@type": "@id", + "@id": "sc:hasImageAnnotations", + "@container": "@list" + }, + "otherContent": { + "@type": "@id", + "@id": "sc:hasLists", + "@container": "@list" + }, + "resource": { + "@id": "oa:hasBody", + "@type": "@id" + }, + "on": { + "@id": "oa:hasTarget", + "@type": "@id" + } + } +} diff --git a/core/src/test/resources/custom/frame-0004-out.jsonld b/core/src/test/resources/custom/frame-0004-out.jsonld new file mode 100644 index 00000000..f0e2ee45 --- /dev/null +++ b/core/src/test/resources/custom/frame-0004-out.jsonld @@ -0,0 +1,100 @@ +{ + "@context": { + "sc": "http://iiif.io/api/presentation/2#", + "oa": "http://www.w3.org/ns/oa#", + "manifests": { + "@id": "sc:hasManifests", + "@type": "@id", + "@container": "@list" + }, + "sequences": { + "@id": "sc:hasSequences", + "@type": "@id", + "@container": "@list" + }, + "canvases": { + "@id": "sc:hasCanvases", + "@type": "@id", + "@container": "@list" + }, + "resources": { + "@id": "sc:hasAnnotations", + "@type": "@id", + "@container": "@set" + }, + "images": { + "@id": "sc:hasImageAnnotations", + "@type": "@id", + "@container": "@list" + }, + "otherContent": { + "@id": "sc:hasLists", + "@type": "@id", + "@container": "@list" + }, + "resource": { + "@id": "oa:hasBody", + "@type": "@id" + }, + "on": { + "@id": "oa:hasTarget", + "@type": "@id" + } + }, + "@graph": [ + { + "@id": "http://localhost/manifest", + "@type": "sc:Manifest", + "sequences": [ + { + "@id": "http://localhost/sequence/normal", + "@type": "sc:Sequence", + "canvases": [ + { + "@id": "http://localhost/c0", + "@type": "sc:Canvas", + "images": [ + { + "@id": "_:b1", + "@type": "oa:Annotation", + "resource": { + "@id": "http://localhost/r0.jp2", + "@type": "dctypes:Image" + }, + "on": "http://localhost/c0" + } + ], + "otherContent": [ + { + "@id": "http://localhost/l0", + "@type": "sc:AnnotationList" + } + ] + }, + { + "@id": "http://localhost/c1", + "@type": "sc:Canvas", + "images": [ + { + "@id": "_:b0", + "@type": "oa:Annotation", + "resource": { + "@id": "http://localhost/r1.jp2", + "@type": "dctypes:Image" + }, + "on": "http://localhost/c1" + } + ], + "otherContent": [ + { + "@id": "http://localhost/l1", + "@type": "sc:AnnotationList" + } + ] + } + ] + } + ] + } + ] +} diff --git a/core/src/test/resources/custom/frame-0005-frame.jsonld b/core/src/test/resources/custom/frame-0005-frame.jsonld new file mode 100644 index 00000000..d7ec9aa6 --- /dev/null +++ b/core/src/test/resources/custom/frame-0005-frame.jsonld @@ -0,0 +1,4 @@ +{ + "@context": {}, + "@type": "http://www.myresource/uuidtype" +} diff --git a/core/src/test/resources/custom/frame-0005-in.jsonld b/core/src/test/resources/custom/frame-0005-in.jsonld new file mode 100644 index 00000000..14701a93 --- /dev/null +++ b/core/src/test/resources/custom/frame-0005-in.jsonld @@ -0,0 +1,19 @@ +{ + "@context": { + "rdfs": "http://www.w3.org/2000/01/rdf-schema#" + }, + "@id": "http://www.myresource/uuid", + "@type": "http://www.myresource/uuidtype", + "http://www.myresource.com/ontology/1.0#talksAbout": { + "@list": [ + { + "@id": "http://rdf.freebase.com/ns/m.018w8", + "rdfs:label": [ + { + "@value": "Basketball", + "@language": "en" + } + ] + } + ] } +} diff --git a/core/src/test/resources/custom/frame-0005-out.jsonld b/core/src/test/resources/custom/frame-0005-out.jsonld new file mode 100644 index 00000000..394cffe6 --- /dev/null +++ b/core/src/test/resources/custom/frame-0005-out.jsonld @@ -0,0 +1,15 @@ +{ + "@graph" : [ { + "@id" : "http://www.myresource/uuid", + "@type" : "http://www.myresource/uuidtype", + "http://www.myresource.com/ontology/1.0#talksAbout" : { + "@list" : [ { + "@id" : "http://rdf.freebase.com/ns/m.018w8", + "http://www.w3.org/2000/01/rdf-schema#label" : { + "@language" : "en", + "@value" : "Basketball" + } + } ] + } + } ] +} diff --git a/core/src/test/resources/custom/frame-0006-frame.jsonld b/core/src/test/resources/custom/frame-0006-frame.jsonld new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/core/src/test/resources/custom/frame-0006-frame.jsonld @@ -0,0 +1 @@ +{} diff --git a/core/src/test/resources/custom/frame-0006-in.jsonld b/core/src/test/resources/custom/frame-0006-in.jsonld new file mode 100644 index 00000000..570de35e --- /dev/null +++ b/core/src/test/resources/custom/frame-0006-in.jsonld @@ -0,0 +1,11 @@ +[ { + "@id" : "http://example.com/canvas-1", + "@type" : "http://example.com" +}, { + "@id" : "http://example.com/element", + "http://example.com" : { + "@list" : [ { + "@id" : "http://example.com/canvas-1" + } ] + } +} ] diff --git a/core/src/test/resources/custom/frame-0006-out.jsonld b/core/src/test/resources/custom/frame-0006-out.jsonld new file mode 100644 index 00000000..63a390ee --- /dev/null +++ b/core/src/test/resources/custom/frame-0006-out.jsonld @@ -0,0 +1,14 @@ +{ + "@graph" : [ { + "@id" : "http://example.com/canvas-1", + "@type" : "http://example.com" + }, { + "@id" : "http://example.com/element", + "http://example.com" : { + "@list" : [ { + "@id" : "http://example.com/canvas-1", + "@type" : "http://example.com" + } ] + } + } ] +} diff --git a/core/src/test/resources/custom/frame-0007-frame.jsonld b/core/src/test/resources/custom/frame-0007-frame.jsonld new file mode 100644 index 00000000..1f6d39e7 --- /dev/null +++ b/core/src/test/resources/custom/frame-0007-frame.jsonld @@ -0,0 +1,12 @@ +{ + "@context": { + "dc": "http://purl.org/dc/elements/1.1/", + "ex": "http://example.org/vocab#" + }, + "@type": "ex:Library", + "ex:contains": { + "@explicit":true, + "dc:title":{"@default":"Title missing"}, + "dc:creator":{} + } +} \ No newline at end of file diff --git a/core/src/test/resources/custom/frame-0007-in.jsonld b/core/src/test/resources/custom/frame-0007-in.jsonld new file mode 100644 index 00000000..c10df848 --- /dev/null +++ b/core/src/test/resources/custom/frame-0007-in.jsonld @@ -0,0 +1,24 @@ +{ + "@context": { + "dc": "http://purl.org/dc/elements/1.1/", + "ex": "http://example.org/vocab#" + }, + "@graph": [ + { + "@id": "http://example.org/library", + "@type": "ex:Library", + "ex:contains": [{"@id":"http://example.org/library/the-republic#introduction"},{"@id":"http://example.org/library/the-republic"}] + }, + { + "@id": "http://example.org/library/the-republic", + "@type": "ex:Book", + "dc:creator": "Plato", + "dc:title": "The Republic" + }, + { + "@id": "http://example.org/library/the-republic#introduction", + "@type": "ex:Book", + "dc:creator": "Plato" + } + ] +} diff --git a/core/src/test/resources/custom/frame-0007-out.jsonld b/core/src/test/resources/custom/frame-0007-out.jsonld new file mode 100644 index 00000000..42836207 --- /dev/null +++ b/core/src/test/resources/custom/frame-0007-out.jsonld @@ -0,0 +1,26 @@ +{ + "@context": { + "dc": "http://purl.org/dc/elements/1.1/", + "ex": "http://example.org/vocab#" + }, + "@graph": [ + { + "@id": "http://example.org/library", + "@type": "ex:Library", + "ex:contains": [ + { + "@id": "http://example.org/library/the-republic#introduction", + "@type": "ex:Book", + "dc:creator": "Plato", + "dc:title": "Title missing" + }, + { + "@id": "http://example.org/library/the-republic", + "@type": "ex:Book", + "dc:creator": "Plato", + "dc:title": "The Republic" + } + ] + } + ] +} \ No newline at end of file diff --git a/core/src/test/resources/custom/frame-0008-frame.jsonld b/core/src/test/resources/custom/frame-0008-frame.jsonld new file mode 100644 index 00000000..16d05498 --- /dev/null +++ b/core/src/test/resources/custom/frame-0008-frame.jsonld @@ -0,0 +1,7 @@ +{ + "@context": { + "dct": "http://purl.org/dc/terms/", + "ex": "http://example.org/vocab#" + }, + "@type": "ex:Biography" +} diff --git a/core/src/test/resources/custom/frame-0008-in.jsonld b/core/src/test/resources/custom/frame-0008-in.jsonld new file mode 100644 index 00000000..75eef110 --- /dev/null +++ b/core/src/test/resources/custom/frame-0008-in.jsonld @@ -0,0 +1,20 @@ +{ + "@context": { + "dct": "http://purl.org/dc/terms/", + "ex": "http://example.org/vocab#" + }, + "@graph": [ + { + "@id": "http://lobid.org/resources/HT019277879", + "@type": "ex:Biography", + "dct:creator": { + "@id" : "https://www.wikidata.org/entity/Q115211", + "ex:name": "Harry Rowohlt" + }, + "dct:subject": { + "@id" : "https://www.wikidata.org/entity/Q115211", + "ex:name": "Harry Rowohlt" + } + } + ] +} diff --git a/core/src/test/resources/custom/frame-0008-out.jsonld b/core/src/test/resources/custom/frame-0008-out.jsonld new file mode 100644 index 00000000..75eef110 --- /dev/null +++ b/core/src/test/resources/custom/frame-0008-out.jsonld @@ -0,0 +1,20 @@ +{ + "@context": { + "dct": "http://purl.org/dc/terms/", + "ex": "http://example.org/vocab#" + }, + "@graph": [ + { + "@id": "http://lobid.org/resources/HT019277879", + "@type": "ex:Biography", + "dct:creator": { + "@id" : "https://www.wikidata.org/entity/Q115211", + "ex:name": "Harry Rowohlt" + }, + "dct:subject": { + "@id" : "https://www.wikidata.org/entity/Q115211", + "ex:name": "Harry Rowohlt" + } + } + ] +} diff --git a/core/src/test/resources/custom/frame-0009-frame.jsonld b/core/src/test/resources/custom/frame-0009-frame.jsonld new file mode 100644 index 00000000..8947878f --- /dev/null +++ b/core/src/test/resources/custom/frame-0009-frame.jsonld @@ -0,0 +1,7 @@ +{ + "@context": { + "@vocab": "http://example/", + "id": "@id" + }, + "id": {} +} \ No newline at end of file diff --git a/core/src/test/resources/custom/frame-0009-in.jsonld b/core/src/test/resources/custom/frame-0009-in.jsonld new file mode 100644 index 00000000..073345ec --- /dev/null +++ b/core/src/test/resources/custom/frame-0009-in.jsonld @@ -0,0 +1,10 @@ +{ + "@context": { + "@vocab": "http://example/", + "id": "@id" + }, + "id": "_:bnode0", + "name": "bar", + "prop1": { "name": "foo", "id": "_:bnode1" }, + "prop2": { "name": "foo", "id": "_:bnode1" } +} \ No newline at end of file diff --git a/core/src/test/resources/custom/frame-0009-out.jsonld b/core/src/test/resources/custom/frame-0009-out.jsonld new file mode 100644 index 00000000..01ef962d --- /dev/null +++ b/core/src/test/resources/custom/frame-0009-out.jsonld @@ -0,0 +1,22 @@ +{ + "@context": { + "@vocab": "http:\/\/example\/", + "id": "@id" + }, + "@graph": [ + { + "name": "bar", + "prop1": { + "id": "_:b1" + }, + "prop2": { + "id": "_:b1", + "name": "foo" + } + }, + { + "id": "_:b1", + "name": "foo" + } + ] +} \ No newline at end of file diff --git a/core/src/test/resources/custom/frame-0010-frame.jsonld b/core/src/test/resources/custom/frame-0010-frame.jsonld new file mode 100644 index 00000000..91a3ac26 --- /dev/null +++ b/core/src/test/resources/custom/frame-0010-frame.jsonld @@ -0,0 +1,6 @@ +{ + "@context" : { + "rdf" : "http://www.w3.org/1999/02/22-rdf-syntax-ns#" + }, + "@id" : "http://example.com/main/id" +} \ No newline at end of file diff --git a/core/src/test/resources/custom/frame-0010-out.jsonld b/core/src/test/resources/custom/frame-0010-out.jsonld new file mode 100644 index 00000000..491fa921 --- /dev/null +++ b/core/src/test/resources/custom/frame-0010-out.jsonld @@ -0,0 +1,13 @@ +{ + "@context" : { + "rdf" : "http://www.w3.org/1999/02/22-rdf-syntax-ns#" + }, + "@graph" : [ { + "@id" : "http://example.com/main/id", + "rdf:type" : { + "@id" : "http://example.com/rdf/id", + "rdf:label" : "someLabel" + } + } + ] +} \ No newline at end of file diff --git a/core/src/test/resources/custom/frame-0011-frame.jsonld b/core/src/test/resources/custom/frame-0011-frame.jsonld new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/core/src/test/resources/custom/frame-0011-frame.jsonld @@ -0,0 +1 @@ +{} diff --git a/core/src/test/resources/custom/frame-0011-in.jsonld b/core/src/test/resources/custom/frame-0011-in.jsonld new file mode 100644 index 00000000..570de35e --- /dev/null +++ b/core/src/test/resources/custom/frame-0011-in.jsonld @@ -0,0 +1,11 @@ +[ { + "@id" : "http://example.com/canvas-1", + "@type" : "http://example.com" +}, { + "@id" : "http://example.com/element", + "http://example.com" : { + "@list" : [ { + "@id" : "http://example.com/canvas-1" + } ] + } +} ] diff --git a/core/src/test/resources/custom/frame-0011-out.jsonld b/core/src/test/resources/custom/frame-0011-out.jsonld new file mode 100644 index 00000000..3ceeada9 --- /dev/null +++ b/core/src/test/resources/custom/frame-0011-out.jsonld @@ -0,0 +1,14 @@ +{ + "@graph" : [ { + "@id" : "http://example.com/canvas-1", + "@type" : "http://example.com" + }, { + "@id" : "http://example.com/element", + "http://example.com" : { + "@list" : [ { + "@id" : "http://example.com/canvas-1", + "@type" : "http://example.com" + } ] + } + } ] +} \ No newline at end of file diff --git a/core/src/test/resources/custom/monarch-context.jsonld b/core/src/test/resources/custom/monarch-context.jsonld new file mode 100644 index 00000000..472f4153 --- /dev/null +++ b/core/src/test/resources/custom/monarch-context.jsonld @@ -0,0 +1,149 @@ +{ + "@context" : { + "EFO" : "http://purl.obolibrary.org/obo/EFO_", + "obo" : "http://purl.obolibrary.org/obo/", + "inheritance" : "monarch:mode_of_inheritance", + "@base" : "http://monarch-initiative.org/", + "email" : "foaf:mbox", + "BIND" : "http://identifiers.org/bind/bind:", + "morpholino" : "GENO:0000417", + "GENO" : "http://purl.obolibrary.org/obo/GENO_", + "UMLS" : "http://purl.obolibrary.org/obo/UMLS_", + "dcat" : "http://www.w3.org/ns/dcat#", + "PMID" : "http://www.ncbi.nlm.nih.gov/pubmed/", + "MP" : "http://purl.obolibrary.org/obo/MP_", + "ISBN-10" : "http://monarch-initiative.org/publications/ISBN:", + "rdf" : "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + "title" : "dc:title", + "Class" : "owl:Class", + "FBbt" : "http://purl.obolibrary.org/obo/FBbt_", + "pathway_associations" : "rdfs:seeAlso", + "FBInternalGT" : "http://monarchinitiative.org/genotype/", + "has_genotype" : "GENO:0000222", + "genotype" : "GENO:0000000", + "void" : "http://rdfs.org/ns/void#", + "has_phenotype" : "RO:0002200", + "reference_locus" : "GENO:0000036", + "dbVar" : "http://identifiers.org/dbVar_", + "AQTLTrait" : "http://purl.obolibrary.org/obo/AQTLTrait_", + "phenotype_associations" : "rdfs:seeAlso", + "ECO" : "http://purl.obolibrary.org/obo/ECO_", + "WB" : "http://identifiers.org/WormBase:", + "rdfs" : "http://www.w3.org/2000/01/rdf-schema#", + "variant_loci" : "GENO:0000027", + "MedGen" : "http://purl.obolibrary.org/obo/MedGen_", + "creator" : { + "@id" : "dc:creator", + "@type" : "@id" + }, + "SIO" : "http://semanticscience.org/resource/SIO_", + "DOID" : "http://purl.obolibrary.org/obo/DOID_", + "Ensembl" : "http://identifiers.org/ensembl:", + "id" : "@id", + "publisher" : "dc:publisher", + "depiction" : { + "@id" : "foaf:depiction", + "@type" : "@id" + }, + "ENSEMBL" : "http://identifiers.org/ensembl:", + "monarch" : "http://monarchinitiative.org/", + "ORPHANET" : "http://purl.obolibrary.org/obo/ORPHANET_", + "owl" : "http://www.w3.org/2002/07/owl#", + "GeneReviews" : "http://www.ncbi.nlm.nih.gov/books/", + "SNOMED_CT" : "http://purl.obolibrary.org/obo/SNOMED_", + "chromosomal_region" : "GENO:0000390", + "EOM" : "http://purl.obolibrary.org/obo/EOM_", + "type" : { + "@id" : "rdf:type", + "@type" : "@id" + }, + "FlyBase" : "http://identifiers.org/flybase:", + "DECIPHER" : "http://purl.obolibrary.org/obo/DECIPHER_", + "faldo" : "http://biohackathon.org/resource/faldo#", + "prov" : "http://www.w3.org/ns/prov#", + "MIM" : "http://purl.obolibrary.org/obo/OMIM_", + "genomic_variation_complement" : "GENO:0000009", + "evidence" : "monarch:evidence", + "BioGRID" : "http://purl.obolibrary.org/BioGRID_", + "dcterms" : "http://purl.org/dc/terms/", + "sequence_alteration" : "SO:0001059", + "RO" : "http://purl.obolibrary.org/obo/RO_", + "created" : { + "@id" : "dc:created", + "@type" : "xsd:dateTime" + }, + "Orphanet" : "http://purl.obolibrary.org/obo/ORPHANET_", + "oa" : "http://www.w3.org/ns/oa#", + "SGD" : "http://identifiers.org/mgd/sgd:", + "Gene" : "http://purl.obolibrary.org/obo/NCBIGene_", + "PomBase" : "http://identifiers.org/PomBase:", + "genotype_associations" : "rdfs:seeAlso", + "xsd" : "http://www.w3.org/2001/XMLSchema#", + "OMIABreed" : "http://purl.obolibrary.org/obo/OMIA_", + "TAIR" : "http://identifiers.org/mgd/tair:", + "oboInOwl" : "http://www.geneontology.org/formats/oboInOwl#", + "description" : "dc:description", + "disease" : "monarch:disease", + "OMIAPub" : "http://purl.obolibrary.org/obo/OMIAPub_", + "foaf" : "http://xmlns.com/foaf/0.1/", + "idot" : "http://identifiers.org/", + "subClassOf" : "owl:subClassOf", + "source" : "dc:source", + "keyword" : "dcat:keyword", + "onset" : "monarch:age_of_onset", + "genomic_background" : "GENO:0000010", + "dictyBase" : "http://identifiers.org/dictyBase:", + "OMIA" : "http://purl.obolibrary.org/obo/OMIA_", + "has_part" : "BFO:0000051", + "ClinVarVariant" : "http://identifiers.org/ClinVarVariant_", + "gene_locus" : "GENO:0000014", + "effective_genotype" : "GENO:0000525", + "VT" : "http://purl.obolibrary.org/obo/VT_", + "Association" : "SIO:000897", + "resource" : "monarch:nif-resource", + "KEGG" : "http://identifiers.org/kegg:", + "Annotation" : "oa:Annotation", + "FBdv" : "http://purl.obolibrary.org/obo/FBdv_", + "GeneID" : "http://purl.obolibrary.org/obo/NCBIGene_", + "has_background" : "GENO:0000010", + "BFO" : "http://purl.obolibrary.org/obo/BFO_", + "FB" : "http://identifiers.org/flybase:", + "frequency" : "monarch:frequency", + "ZP" : "http://purl.obolibrary.org/obo/ZP_", + "OMIM" : "http://purl.obolibrary.org/obo/OMIM_", + "MGI" : "http://identifiers.org/mgd/MGI:", + "dc" : "http://purl.org/dc/terms/", + "MONARCH" : "http://monarchinitiative.org/MONARCH_", + "ClinVarHaplotype" : "http://identifiers.org/ClinVarHaplotype_", + "homepage" : { + "@id" : "foaf:homepage", + "@type" : "@id" + }, + "RGD" : "http://identifiers.org/mgd/rgd:", + "CORIELL" : "http://purl.obolibrary.org/obo/CORIELL_", + "label" : "rdfs:label", + "NCBIGene" : "http://purl.obolibrary.org/obo/NCBIGene_", + "intrinsic_genotype" : "GENO:0000000", + "FBcv" : "http://purl.obolibrary.org/obo/FBcv_", + "WBStrain" : "http://identifiers.org/WormBase:", + "sequence_alteration_collection" : "GENO:0000025", + "reference" : "dc:publication", + "zygosity" : "GENO:0000133", + "chromosome" : "GENO:0000323", + "WormBase" : "http://identifiers.org/WormBase:", + "HPRD" : "http://identifiers.org/hprd/hprd:", + "ClinVar" : "http://purl.obolibrary.org/obo/ClinVar_", + "ISBN-13" : "http://monarch-initiative.org/publications/ISBN:", + "extrinsic_genotype" : "GENO:0000524", + "NCBITaxon" : "http://purl.obolibrary.org/obo/NCBITaxon_", + "environment" : "GENO:0000099", + "variant_locus" : "GENO:0000481", + "comment" : "rdfs:comment", + "SO" : "http://purl.obolibrary.org/obo/SO_", + "phenotype" : "monarch:phenotype", + "ZFIN" : "http://identifiers.org/zfin:", + "HP" : "http://purl.obolibrary.org/obo/HP_", + "MESH" : "http://purl.obolibrary.org/obo/MESH_", + "dbSNP" : "http://identifiers.org/dbSNP_" + } +} diff --git a/core/src/test/resources/custom/toRdf-0001-in.jsonld b/core/src/test/resources/custom/toRdf-0001-in.jsonld new file mode 100644 index 00000000..92ec4b55 --- /dev/null +++ b/core/src/test/resources/custom/toRdf-0001-in.jsonld @@ -0,0 +1 @@ +{"@id":"relativeURIWithNoBase","@type":"http://example.org/SomeRDFSClass"} \ No newline at end of file diff --git a/core/src/test/resources/custom/toRdf-0001-out.nq b/core/src/test/resources/custom/toRdf-0001-out.nq new file mode 100644 index 00000000..d3e0b99a --- /dev/null +++ b/core/src/test/resources/custom/toRdf-0001-out.nq @@ -0,0 +1 @@ + . diff --git a/core/src/test/resources/custom/toRdf-0002-out.nq b/core/src/test/resources/custom/toRdf-0002-out.nq new file mode 100644 index 00000000..4d011703 --- /dev/null +++ b/core/src/test/resources/custom/toRdf-0002-out.nq @@ -0,0 +1 @@ + . diff --git a/core/src/test/resources/custom/toRdf-0003-out.nq b/core/src/test/resources/custom/toRdf-0003-out.nq new file mode 100644 index 00000000..aee420ed --- /dev/null +++ b/core/src/test/resources/custom/toRdf-0003-out.nq @@ -0,0 +1 @@ + . diff --git a/core/src/test/resources/json-ld.org/compact-0104-context.jsonld b/core/src/test/resources/json-ld.org/compact-0104-context.jsonld new file mode 100644 index 00000000..dd085528 --- /dev/null +++ b/core/src/test/resources/json-ld.org/compact-0104-context.jsonld @@ -0,0 +1,5 @@ +{ + "@context": { + "@type": {"@container": "@set"} + } +} diff --git a/core/src/test/resources/json-ld.org/compact-0104-in.jsonld b/core/src/test/resources/json-ld.org/compact-0104-in.jsonld new file mode 100644 index 00000000..9bcd4848 --- /dev/null +++ b/core/src/test/resources/json-ld.org/compact-0104-in.jsonld @@ -0,0 +1,3 @@ +{ + "@type": "http://example.org/type" +} diff --git a/core/src/test/resources/json-ld.org/compact-0104-out.jsonld b/core/src/test/resources/json-ld.org/compact-0104-out.jsonld new file mode 100644 index 00000000..6ac5afcc --- /dev/null +++ b/core/src/test/resources/json-ld.org/compact-0104-out.jsonld @@ -0,0 +1,6 @@ +{ + "@context": { + "@type": {"@container": "@set"} + }, + "@type": ["http://example.org/type"] +} diff --git a/core/src/test/resources/json-ld.org/compact-0105-context.jsonld b/core/src/test/resources/json-ld.org/compact-0105-context.jsonld new file mode 100644 index 00000000..bc961d55 --- /dev/null +++ b/core/src/test/resources/json-ld.org/compact-0105-context.jsonld @@ -0,0 +1,5 @@ +{ + "@context": { + "type": {"@id": "@type", "@container": "@set"} + } +} diff --git a/core/src/test/resources/json-ld.org/compact-0105-in.jsonld b/core/src/test/resources/json-ld.org/compact-0105-in.jsonld new file mode 100644 index 00000000..9bcd4848 --- /dev/null +++ b/core/src/test/resources/json-ld.org/compact-0105-in.jsonld @@ -0,0 +1,3 @@ +{ + "@type": "http://example.org/type" +} diff --git a/core/src/test/resources/json-ld.org/compact-0105-out.jsonld b/core/src/test/resources/json-ld.org/compact-0105-out.jsonld new file mode 100644 index 00000000..6ce29444 --- /dev/null +++ b/core/src/test/resources/json-ld.org/compact-0105-out.jsonld @@ -0,0 +1,6 @@ +{ + "@context": { + "type": {"@id": "@type", "@container": "@set"} + }, + "type": ["http://example.org/type"] +} diff --git a/core/src/test/resources/json-ld.org/compact-0106-context.jsonld b/core/src/test/resources/json-ld.org/compact-0106-context.jsonld new file mode 100644 index 00000000..bc961d55 --- /dev/null +++ b/core/src/test/resources/json-ld.org/compact-0106-context.jsonld @@ -0,0 +1,5 @@ +{ + "@context": { + "type": {"@id": "@type", "@container": "@set"} + } +} diff --git a/core/src/test/resources/json-ld.org/compact-0106-in.jsonld b/core/src/test/resources/json-ld.org/compact-0106-in.jsonld new file mode 100644 index 00000000..9bcd4848 --- /dev/null +++ b/core/src/test/resources/json-ld.org/compact-0106-in.jsonld @@ -0,0 +1,3 @@ +{ + "@type": "http://example.org/type" +} diff --git a/core/src/test/resources/json-ld.org/compact-0106-out.jsonld b/core/src/test/resources/json-ld.org/compact-0106-out.jsonld new file mode 100644 index 00000000..349e0fb4 --- /dev/null +++ b/core/src/test/resources/json-ld.org/compact-0106-out.jsonld @@ -0,0 +1,6 @@ +{ + "@context": { + "type": {"@id": "@type", "@container": "@set"} + }, + "type": "http://example.org/type" +} diff --git a/core/src/test/resources/json-ld.org/compact-manifest.jsonld b/core/src/test/resources/json-ld.org/compact-manifest.jsonld index cddd412e..14e06675 100644 --- a/core/src/test/resources/json-ld.org/compact-manifest.jsonld +++ b/core/src/test/resources/json-ld.org/compact-manifest.jsonld @@ -585,6 +585,36 @@ "input": "compact-0072-in.jsonld", "context": "compact-0072-context.jsonld", "expect": "compact-0072-out.jsonld" + }, + { + "@id": "#t0104", + "@type": ["jld:PositiveEvaluationTest", "jld:CompactTest"], + "name": "Compact @type with @container: @set", + "purpose": "Ensures that a single @type value is represented as an array", + "input": "compact-0104-in.jsonld", + "context": "compact-0104-context.jsonld", + "expect": "compact-0104-out.jsonld", + "option": {"processingMode": "json-ld-1.1", "specVersion": "json-ld-1.1"} + }, + { + "@id": "#t0105", + "@type": ["jld:PositiveEvaluationTest", "jld:CompactTest"], + "name": "Compact @type with @container: @set using an alias of @type", + "purpose": "Ensures that a single @type value is represented as an array", + "input": "compact-0105-in.jsonld", + "context": "compact-0105-context.jsonld", + "expect": "compact-0105-out.jsonld", + "option": {"processingMode": "json-ld-1.1", "specVersion": "json-ld-1.1"} + }, + { + "@id": "#t0106", + "@type": ["jld:PositiveEvaluationTest", "jld:CompactTest"], + "name": "Do not compact @type with @container: @set to an array using an alias of @type", + "purpose": "Ensures that a single @type value is not represented as an array in 1.0", + "input": "compact-0106-in.jsonld", + "context": "compact-0106-context.jsonld", + "expect": "compact-0106-out.jsonld", + "option": {"processingMode": "json-ld-1.0", "specVersion": "json-ld-1.1"} } ] } diff --git a/core/src/test/resources/json-ld.org/error-manifest.jsonld b/core/src/test/resources/json-ld.org/error-manifest.jsonld index afc16304..4b9b5c56 100644 --- a/core/src/test/resources/json-ld.org/error-manifest.jsonld +++ b/core/src/test/resources/json-ld.org/error-manifest.jsonld @@ -308,6 +308,14 @@ "purpose": "Verifies that an exception is raised in Flattening when conflicting indexes are found", "input": "error-0043-in.jsonld", "expect": "conflicting indexes" + }, + { + "@id": "#te042", + "@type": [ "jld:NegativeEvaluationTest", "jld:ExpandTest" ], + "name": "Keywords may not be redefined", + "purpose": "Verifies that an exception is raised on expansion when processing an invalid context attempting to define @container on a keyword", + "input": "expand-e042-in.jsonld", + "expect": "keyword redefinition" } ] } diff --git a/core/src/test/resources/json-ld.org/expand-e042-in.jsonld b/core/src/test/resources/json-ld.org/expand-e042-in.jsonld new file mode 100644 index 00000000..41360255 --- /dev/null +++ b/core/src/test/resources/json-ld.org/expand-e042-in.jsonld @@ -0,0 +1,6 @@ +{ + "@context": { + "@type": {"@container": "@set"} + }, + "@type": "http://example.org/type" +} diff --git a/core/src/test/resources/json-ld.org/frame-0021-frame.jsonld b/core/src/test/resources/json-ld.org/frame-0021-frame.jsonld index da74d30b..32bfc6a6 100644 --- a/core/src/test/resources/json-ld.org/frame-0021-frame.jsonld +++ b/core/src/test/resources/json-ld.org/frame-0021-frame.jsonld @@ -2,6 +2,6 @@ "@context": { "dc": "http://purl.org/dc/elements/1.1/", "ex": "http://example.org/vocab#", - "dc:list": {"@container": "@list"} + "ex:list": {"@container": "@list"} } } \ No newline at end of file diff --git a/core/src/test/resources/json-ld.org/frame-0021-in.jsonld b/core/src/test/resources/json-ld.org/frame-0021-in.jsonld index d645c097..ccb878c3 100644 --- a/core/src/test/resources/json-ld.org/frame-0021-in.jsonld +++ b/core/src/test/resources/json-ld.org/frame-0021-in.jsonld @@ -6,12 +6,12 @@ "ex:contains": { "@type": "@id" }, - "dc:list": {"@container": "@list"} + "ex:list": {"@container": "@list"} }, "@graph": [ { "@id": "_:Book", - "dc:label": "Book type" + "dc:title": "Book type" }, { "@id": "http://example.org/library", "@type": "ex:Library", @@ -27,6 +27,6 @@ "@type": "ex:Chapter", "dc:description": "An introductory chapter on The Republic.", "dc:title": "The Introduction", - "dc:list": [1, 2, 3, 4, 4, 4, 5] + "ex:list": [1, 2, 3, 4, 4, 4, 5] }] } \ No newline at end of file diff --git a/core/src/test/resources/json-ld.org/frame-0021-out.jsonld b/core/src/test/resources/json-ld.org/frame-0021-out.jsonld index c1bd1c0b..a5e67d43 100644 --- a/core/src/test/resources/json-ld.org/frame-0021-out.jsonld +++ b/core/src/test/resources/json-ld.org/frame-0021-out.jsonld @@ -2,12 +2,12 @@ "@context": { "dc": "http://purl.org/dc/elements/1.1/", "ex": "http://example.org/vocab#", - "dc:list": {"@container": "@list"} + "ex:list": {"@container": "@list"} }, "@graph": [ { "@id": "_:b0", - "dc:label": "Book type" + "dc:title": "Book type" }, { "@id": "http://example.org/library", "@type": "ex:Library", @@ -21,7 +21,7 @@ "@type": "ex:Chapter", "dc:description": "An introductory chapter on The Republic.", "dc:title": "The Introduction", - "dc:list": [1, 2, 3, 4, 4, 4, 5] + "ex:list": [1, 2, 3, 4, 4, 4, 5] } } }, { @@ -32,7 +32,7 @@ "@type": "ex:Chapter", "dc:description": "An introductory chapter on The Republic.", "dc:title": "The Introduction", - "dc:list": [1, 2, 3, 4, 4, 4, 5] + "ex:list": [1, 2, 3, 4, 4, 4, 5] }, "dc:creator": "Plato", "dc:title": "The Republic" @@ -40,7 +40,7 @@ "@id": "http://example.org/library/the-republic#introduction", "@type": "ex:Chapter", "dc:description": "An introductory chapter on The Republic.", - "dc:list": [1, 2, 3, 4, 4, 4, 5], + "ex:list": [1, 2, 3, 4, 4, 4, 5], "dc:title": "The Introduction" }] } diff --git a/core/src/test/resources/json-ld.org/frame-0022-frame.jsonld b/core/src/test/resources/json-ld.org/frame-0022-frame.jsonld index 1f6d39e7..dc15b5fd 100644 --- a/core/src/test/resources/json-ld.org/frame-0022-frame.jsonld +++ b/core/src/test/resources/json-ld.org/frame-0022-frame.jsonld @@ -1,12 +1,4 @@ { - "@context": { - "dc": "http://purl.org/dc/elements/1.1/", - "ex": "http://example.org/vocab#" - }, - "@type": "ex:Library", - "ex:contains": { - "@explicit":true, - "dc:title":{"@default":"Title missing"}, - "dc:creator":{} - } -} \ No newline at end of file + "@context": {"ex": "http://example.org/"}, + "@id": "ex:Sub1" +} diff --git a/core/src/test/resources/json-ld.org/frame-0022-in.jsonld b/core/src/test/resources/json-ld.org/frame-0022-in.jsonld index 6c0feddb..3e9969a6 100644 --- a/core/src/test/resources/json-ld.org/frame-0022-in.jsonld +++ b/core/src/test/resources/json-ld.org/frame-0022-in.jsonld @@ -1,24 +1,10 @@ { - "@context": { - "dc": "http://purl.org/dc/elements/1.1/", - "ex": "http://example.org/vocab#" - }, - "@graph": [ - { - "@id": "http://example.org/library", - "@type": "ex:Library", - "ex:contains": [{"@id":"http://example.org/library/the-republic#introduction"},{"@id":"http://example.org/library/the-republic"}] - }, - { - "@id": "http://example.org/library/the-republic", - "@type": "ex:Book", - "dc:creator": "Plato", - "dc:title": "The Republic" - }, - { - "@id": "http://example.org/library/the-republic#introduction", - "@type": "ex:Book", - "dc:creator": "Plato" - } - ] -} \ No newline at end of file + "@context": {"ex": "http://example.org/"}, + "@graph": [{ + "@id": "ex:Sub1", + "@type": "ex:Type1" + }, { + "@id": "ex:Sub2", + "@type": "ex:Type2" + }] +} diff --git a/core/src/test/resources/json-ld.org/frame-0022-out.jsonld b/core/src/test/resources/json-ld.org/frame-0022-out.jsonld index 42836207..ef560bfc 100644 --- a/core/src/test/resources/json-ld.org/frame-0022-out.jsonld +++ b/core/src/test/resources/json-ld.org/frame-0022-out.jsonld @@ -1,26 +1,7 @@ { - "@context": { - "dc": "http://purl.org/dc/elements/1.1/", - "ex": "http://example.org/vocab#" - }, - "@graph": [ - { - "@id": "http://example.org/library", - "@type": "ex:Library", - "ex:contains": [ - { - "@id": "http://example.org/library/the-republic#introduction", - "@type": "ex:Book", - "dc:creator": "Plato", - "dc:title": "Title missing" - }, - { - "@id": "http://example.org/library/the-republic", - "@type": "ex:Book", - "dc:creator": "Plato", - "dc:title": "The Republic" - } - ] - } - ] -} \ No newline at end of file + "@context": {"ex": "http://example.org/"}, + "@graph": [{ + "@id": "ex:Sub1", + "@type": "ex:Type1" + }] +} diff --git a/core/src/test/resources/json-ld.org/frame-0030-frame.jsonld b/core/src/test/resources/json-ld.org/frame-0030-frame.jsonld new file mode 100644 index 00000000..415e2b8f --- /dev/null +++ b/core/src/test/resources/json-ld.org/frame-0030-frame.jsonld @@ -0,0 +1,12 @@ +{ + "@context": { + "ex": "http://www.example.com/#" + }, + "@type": "ex:Thing", + "ex:embed": { + "@embed": "@always" + }, + "ex:noembed": { + "@embed": "@never" + } +} \ No newline at end of file diff --git a/core/src/test/resources/json-ld.org/frame-0030-in.jsonld b/core/src/test/resources/json-ld.org/frame-0030-in.jsonld new file mode 100644 index 00000000..d5df9e32 --- /dev/null +++ b/core/src/test/resources/json-ld.org/frame-0030-in.jsonld @@ -0,0 +1,15 @@ +{ + "@context": { + "ex": "http://www.example.com/#" + }, + "@id": "ex:subject", + "@type": "ex:Thing", + "ex:embed": { + "@id": "ex:embedded", + "ex:title": "Embedded" + }, + "ex:noembed": { + "@id": "ex:notembedded", + "ex:title": "Not Embedded" + } +} \ No newline at end of file diff --git a/core/src/test/resources/json-ld.org/frame-0030-out.jsonld b/core/src/test/resources/json-ld.org/frame-0030-out.jsonld new file mode 100644 index 00000000..358ab54c --- /dev/null +++ b/core/src/test/resources/json-ld.org/frame-0030-out.jsonld @@ -0,0 +1,16 @@ +{ + "@context": { + "ex": "http://www.example.com/#" + }, + "@graph": [{ + "@id": "ex:subject", + "@type": "ex:Thing", + "ex:embed": { + "@id": "ex:embedded", + "ex:title": "Embedded" + }, + "ex:noembed": { + "@id": "ex:notembedded" + } + }] +} \ No newline at end of file diff --git a/core/src/test/resources/json-ld.org/frame-0046-frame.jsonld b/core/src/test/resources/json-ld.org/frame-0046-frame.jsonld new file mode 100644 index 00000000..edd59d96 --- /dev/null +++ b/core/src/test/resources/json-ld.org/frame-0046-frame.jsonld @@ -0,0 +1,4 @@ +{ + "@context": {"@vocab": "urn:"}, + "@type": "Class" +} \ No newline at end of file diff --git a/core/src/test/resources/json-ld.org/frame-0046-in.jsonld b/core/src/test/resources/json-ld.org/frame-0046-in.jsonld new file mode 100644 index 00000000..a092c9da --- /dev/null +++ b/core/src/test/resources/json-ld.org/frame-0046-in.jsonld @@ -0,0 +1,11 @@ +{ + "@context": {"@vocab": "urn:"}, + "@id": "urn:id-1", + "@type": "Class", + "preserve": { + "@graph": { + "@id": "urn:id-2", + "term": "data" + } + } +} \ No newline at end of file diff --git a/core/src/test/resources/json-ld.org/frame-g001-frame.jsonld b/core/src/test/resources/json-ld.org/frame-g001-frame.jsonld new file mode 100644 index 00000000..16faf5bb --- /dev/null +++ b/core/src/test/resources/json-ld.org/frame-g001-frame.jsonld @@ -0,0 +1,13 @@ +{ + "@context": { + "dc": "http://purl.org/dc/elements/1.1/", + "ex": "http://example.org/vocab#" + }, + "@type": "ex:Library", + "ex:contains": { + "@type": "ex:Book", + "ex:contains": { + "@type": "ex:Chapter" + } + } +} \ No newline at end of file diff --git a/core/src/test/resources/json-ld.org/frame-g001-in.jsonld b/core/src/test/resources/json-ld.org/frame-g001-in.jsonld new file mode 100644 index 00000000..dcc2dfab --- /dev/null +++ b/core/src/test/resources/json-ld.org/frame-g001-in.jsonld @@ -0,0 +1,27 @@ +{ + "@context": { + "dc": "http://purl.org/dc/elements/1.1/", + "ex": "http://example.org/vocab#", + "ex:contains": {"@type": "@id"} + }, + "@graph": [ + { + "@id": "http://example.org/test/#library", + "@type": "ex:Library", + "ex:contains": "http://example.org/test#book" + }, + { + "@id": "http://example.org/test#book", + "@type": "ex:Book", + "dc:contributor": "Writer", + "dc:title": "My Book", + "ex:contains": "http://example.org/test#chapter" + }, + { + "@id": "http://example.org/test#chapter", + "@type": "ex:Chapter", + "dc:description": "Fun", + "dc:title": "Chapter One" + } + ] +} \ No newline at end of file diff --git a/core/src/test/resources/json-ld.org/frame-g001-out.jsonld b/core/src/test/resources/json-ld.org/frame-g001-out.jsonld new file mode 100644 index 00000000..54356959 --- /dev/null +++ b/core/src/test/resources/json-ld.org/frame-g001-out.jsonld @@ -0,0 +1,20 @@ +{ + "@context": { + "dc": "http://purl.org/dc/elements/1.1/", + "ex": "http://example.org/vocab#" + }, + "@id": "http://example.org/test/#library", + "@type": "ex:Library", + "ex:contains": { + "@id": "http://example.org/test#book", + "@type": "ex:Book", + "dc:contributor": "Writer", + "dc:title": "My Book", + "ex:contains": { + "@id": "http://example.org/test#chapter", + "@type": "ex:Chapter", + "dc:description": "Fun", + "dc:title": "Chapter One" + } + } +} \ No newline at end of file diff --git a/core/src/test/resources/json-ld.org/frame-manifest.jsonld b/core/src/test/resources/json-ld.org/frame-manifest.jsonld index d476dbac..03df4284 100644 --- a/core/src/test/resources/json-ld.org/frame-manifest.jsonld +++ b/core/src/test/resources/json-ld.org/frame-manifest.jsonld @@ -151,14 +151,72 @@ "name": "Blank nodes in @type", "input": "frame-0021-in.jsonld", "frame": "frame-0021-frame.jsonld", - "expect": "frame-0021-out.jsonld" - } - , { - "@id": "#t0022", - "@type": ["jld:PositiveEvaluationTest", "jld:FrameTest"], - "name": "Default inside sets", - "input": "frame-0022-in.jsonld", - "frame": "frame-0022-frame.jsonld", - "expect": "frame-0022-out.jsonld" - }] + "expect": "frame-0021-out.jsonld", + "option": {"specVersion": "json-ld-1.1", "processingMode": "json-ld-1.0"} + } , { + "@id": "#t0022", + "@type": ["jld:PositiveEvaluationTest", "jld:FrameTest"], + "name": "Default inside sets", + "input": "frame-0022-in.jsonld", + "frame": "frame-0022-frame.jsonld", + "expect": "frame-0022-out.jsonld" + } , { + "@id": "#t0030", + "@type": ["jld:PositiveEvaluationTest", "jld:FrameTest"], + "name": "@embed", + "input": "frame-0030-in.jsonld", + "frame": "frame-0030-frame.jsonld", + "expect": "frame-0030-out.jsonld" + }, { + "@id": "#tg001", + "@type": ["jld:PositiveEvaluationTest", "jld:FrameTest"], + "name": "Library framing example with @graph and omitGraph is true.", + "purpose": "Basic example used in playground and spec examples.", + "input": "frame-g001-in.jsonld", + "frame": "frame-g001-frame.jsonld", + "expect": "frame-g001-out.jsonld", + "option": {"specVersion": "json-ld-1.1", "omitGraph": true} + }, { + "@id": "#tp010", + "@type": ["jld:PositiveEvaluationTest", "jld:FrameTest"], + "name": "Property CURIE conflict (prune bnodes)", + "purpose": "(Not really framing) A term looking like a CURIE becomes a CURIE when framing/compacting if defined as such in frame/context.", + "option": {"processingMode": "json-ld-1.1", "specVersion": "json-ld-1.1"}, + "input": "frame-0010-in.jsonld", + "frame": "frame-0010-frame.jsonld", + "expect": "frame-p010-out.jsonld" + }, { + "@id": "#tp020", + "@type": ["jld:PositiveEvaluationTest", "jld:FrameTest"], + "name": "Blank nodes in an array (prune bnodes)", + "option": {"processingMode": "json-ld-1.1", "specVersion": "json-ld-1.1"}, + "input": "frame-0020-in.jsonld", + "frame": "frame-0020-frame.jsonld", + "expect": "frame-p020-out.jsonld" + }, { + "@id": "#tp021", + "@type": ["jld:PositiveEvaluationTest", "jld:FrameTest"], + "name": "Blank nodes in @type (prune bnodes)", + "option": {"processingMode": "json-ld-1.1", "specVersion": "json-ld-1.1"}, + "input": "frame-0021-in.jsonld", + "frame": "frame-0021-frame.jsonld", + "expect": "frame-p021-out.jsonld" + }, { + "@id": "#tp046", + "@type": ["jld:PositiveEvaluationTest", "jld:FrameTest"], + "name": "Merge graphs if no outer @graph is used (prune bnodes)", + "option": {"processingMode": "json-ld-1.1", "specVersion": "json-ld-1.1"}, + "input": "frame-0046-in.jsonld", + "frame": "frame-0046-frame.jsonld", + "expect": "frame-p046-out.jsonld" + }, { + "@id": "#tp050", + "@type": ["jld:PositiveEvaluationTest", "jld:FrameTest"], + "name": "Prune blank nodes with alias of @id", + "purpose": "If @id is aliased in a frame, an unreferenced blank node is still pruned.", + "input": "frame-p050-in.jsonld", + "frame": "frame-p050-frame.jsonld", + "expect": "frame-p050-out.jsonld", + "option": {"processingMode": "json-ld-1.1", "specVersion": "json-ld-1.1"} + }] } diff --git a/core/src/test/resources/json-ld.org/frame-p010-out.jsonld b/core/src/test/resources/json-ld.org/frame-p010-out.jsonld new file mode 100644 index 00000000..38077e48 --- /dev/null +++ b/core/src/test/resources/json-ld.org/frame-p010-out.jsonld @@ -0,0 +1,15 @@ +{ + "@context": { + "dc": "http://purl.org/dc/terms/", + "dc:creator": { + "@type": "@id" + }, + "foaf": "http://xmlns.com/foaf/0.1/", + "ps": "http://purl.org/payswarm#" + }, + "@id": "http://example.com/asset", + "@type": "ps:Asset", + "dc:creator": { + "foaf:name": "John Doe" + } +} \ No newline at end of file diff --git a/core/src/test/resources/json-ld.org/frame-p020-out.jsonld b/core/src/test/resources/json-ld.org/frame-p020-out.jsonld new file mode 100644 index 00000000..e9d42b94 --- /dev/null +++ b/core/src/test/resources/json-ld.org/frame-p020-out.jsonld @@ -0,0 +1,79 @@ +{ + "@graph": [ + { + "http://rdf.data-vocabulary.org/#ingredients": ["12 fresh mint leaves", "1/2 lime, juiced with pulp", "1 tablespoons white sugar", "1 cup ice cubes", "2 fluid ounces white rum", "1/2 cup club soda"], + "http://rdf.data-vocabulary.org/#instructions": [{ + "@id": "_:b1", + "http://rdf.data-vocabulary.org/#description": "Crush lime juice, mint and sugar together in glass.", + "http://rdf.data-vocabulary.org/#step": { + "@type": "http://www.w3.org/2001/XMLSchema#integer", + "@value": 1 + } + }, { + "@id": "_:b2", + "http://rdf.data-vocabulary.org/#description": "Fill glass to top with ice cubes.", + "http://rdf.data-vocabulary.org/#step": { + "@type": "http://www.w3.org/2001/XMLSchema#integer", + "@value": 2 + } + }, { + "@id": "_:b3", + "http://rdf.data-vocabulary.org/#description": "Pour white rum over ice.", + "http://rdf.data-vocabulary.org/#step": { + "@type": "http://www.w3.org/2001/XMLSchema#integer", + "@value": 3 + } + }, { + "@id": "_:b4", + "http://rdf.data-vocabulary.org/#description": "Fill the rest of glass with club soda, stir.", + "http://rdf.data-vocabulary.org/#step": { + "@type": "http://www.w3.org/2001/XMLSchema#integer", + "@value": 4 + } + }, { + "@id": "_:b5", + "http://rdf.data-vocabulary.org/#description": "Garnish with a lime wedge.", + "http://rdf.data-vocabulary.org/#step": { + "@type": "http://www.w3.org/2001/XMLSchema#integer", + "@value": 5 + } + }], + "http://rdf.data-vocabulary.org/#name": "Mojito", + "http://rdf.data-vocabulary.org/#yield": "1 cocktail" + }, { + "@id": "_:b1", + "http://rdf.data-vocabulary.org/#description": "Crush lime juice, mint and sugar together in glass.", + "http://rdf.data-vocabulary.org/#step": { + "@type": "http://www.w3.org/2001/XMLSchema#integer", + "@value": 1 + } + }, { + "@id": "_:b2", + "http://rdf.data-vocabulary.org/#description": "Fill glass to top with ice cubes.", + "http://rdf.data-vocabulary.org/#step": { + "@type": "http://www.w3.org/2001/XMLSchema#integer", + "@value": 2 + } + }, { + "@id": "_:b3", + "http://rdf.data-vocabulary.org/#description": "Pour white rum over ice.", + "http://rdf.data-vocabulary.org/#step": { + "@type": "http://www.w3.org/2001/XMLSchema#integer", + "@value": 3 + } + }, { + "@id": "_:b4", + "http://rdf.data-vocabulary.org/#description": "Fill the rest of glass with club soda, stir.", + "http://rdf.data-vocabulary.org/#step": { + "@type": "http://www.w3.org/2001/XMLSchema#integer", + "@value": 4 + } + }, { + "@id": "_:b5", + "http://rdf.data-vocabulary.org/#description": "Garnish with a lime wedge.", + "http://rdf.data-vocabulary.org/#step": { + "@type": "http://www.w3.org/2001/XMLSchema#integer", + "@value": 5 + } + }] +} diff --git a/core/src/test/resources/json-ld.org/frame-p021-out.jsonld b/core/src/test/resources/json-ld.org/frame-p021-out.jsonld new file mode 100644 index 00000000..a5e67d43 --- /dev/null +++ b/core/src/test/resources/json-ld.org/frame-p021-out.jsonld @@ -0,0 +1,46 @@ +{ + "@context": { + "dc": "http://purl.org/dc/elements/1.1/", + "ex": "http://example.org/vocab#", + "ex:list": {"@container": "@list"} + }, + "@graph": [ + { + "@id": "_:b0", + "dc:title": "Book type" + }, { + "@id": "http://example.org/library", + "@type": "ex:Library", + "ex:contains": { + "@id": "http://example.org/library/the-republic", + "@type": "_:b0", + "dc:creator": "Plato", + "dc:title": "The Republic", + "ex:contains": { + "@id": "http://example.org/library/the-republic#introduction", + "@type": "ex:Chapter", + "dc:description": "An introductory chapter on The Republic.", + "dc:title": "The Introduction", + "ex:list": [1, 2, 3, 4, 4, 4, 5] + } + } + }, { + "@id": "http://example.org/library/the-republic", + "@type": "_:b0", + "ex:contains": { + "@id": "http://example.org/library/the-republic#introduction", + "@type": "ex:Chapter", + "dc:description": "An introductory chapter on The Republic.", + "dc:title": "The Introduction", + "ex:list": [1, 2, 3, 4, 4, 4, 5] + }, + "dc:creator": "Plato", + "dc:title": "The Republic" + }, { + "@id": "http://example.org/library/the-republic#introduction", + "@type": "ex:Chapter", + "dc:description": "An introductory chapter on The Republic.", + "ex:list": [1, 2, 3, 4, 4, 4, 5], + "dc:title": "The Introduction" + }] +} diff --git a/core/src/test/resources/json-ld.org/frame-p046-out.jsonld b/core/src/test/resources/json-ld.org/frame-p046-out.jsonld new file mode 100644 index 00000000..6d127277 --- /dev/null +++ b/core/src/test/resources/json-ld.org/frame-p046-out.jsonld @@ -0,0 +1,6 @@ +{ + "@context": {"@vocab": "urn:"}, + "@id": "urn:id-1", + "@type": "Class", + "preserve": {} +} \ No newline at end of file diff --git a/core/src/test/resources/json-ld.org/frame-p050-frame.jsonld b/core/src/test/resources/json-ld.org/frame-p050-frame.jsonld new file mode 100644 index 00000000..77dc5555 --- /dev/null +++ b/core/src/test/resources/json-ld.org/frame-p050-frame.jsonld @@ -0,0 +1,8 @@ +{ + "@context": { + "@vocab": "http://example/", + "id": "@id" + }, + "id": {}, + "name": {} +} \ No newline at end of file diff --git a/core/src/test/resources/json-ld.org/frame-p050-in.jsonld b/core/src/test/resources/json-ld.org/frame-p050-in.jsonld new file mode 100644 index 00000000..fc31face --- /dev/null +++ b/core/src/test/resources/json-ld.org/frame-p050-in.jsonld @@ -0,0 +1,8 @@ +{ + "@context": { + "@vocab": "http://example/", + "id": "@id" + }, + "id": "_:bnode0", + "name": "foo" +} \ No newline at end of file diff --git a/core/src/test/resources/json-ld.org/frame-p050-out.jsonld b/core/src/test/resources/json-ld.org/frame-p050-out.jsonld new file mode 100644 index 00000000..75e0a23e --- /dev/null +++ b/core/src/test/resources/json-ld.org/frame-p050-out.jsonld @@ -0,0 +1,7 @@ +{ + "@context": { + "@vocab": "http://example/", + "id": "@id" + }, + "name": "foo" +} \ No newline at end of file diff --git a/core/src/test/resources/json-ld.org/fromRdf-0020-in.nq b/core/src/test/resources/json-ld.org/fromRdf-0020-in.nq new file mode 100644 index 00000000..ce811f51 --- /dev/null +++ b/core/src/test/resources/json-ld.org/fromRdf-0020-in.nq @@ -0,0 +1,7 @@ + . + "myLabel" . + "2012-05-12"^^ . + . + "Plain" . + "2012-05-12"^^ . + "English"@en . diff --git a/core/src/test/resources/json-ld.org/fromRdf-0020-out.jsonld b/core/src/test/resources/json-ld.org/fromRdf-0020-out.jsonld new file mode 100644 index 00000000..6f9d169f --- /dev/null +++ b/core/src/test/resources/json-ld.org/fromRdf-0020-out.jsonld @@ -0,0 +1,19 @@ +[ + { + "@id": "http://example.com/Subj1", + "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" : [{ + "@id": "http://example.com/Type" + }], + "http://example.com/prop1": [{"@id": "http://example.com/Obj1"}], + "http://example.com/prop2": [ + {"@value": "Plain"}, + {"@value": "2012-05-12", "@type": "http://www.w3.org/2001/XMLSchema#date"}, + {"@value": "English", "@language": "en"} + ] + }, + { + "@id": "http://example.com/Type", + "http://www.w3.org/1999/02/22-rdf-syntax-ns#label": [{"@value": "myLabel"}], + "http://example.com/prop2": [{"@value": "2012-05-12", "@type": "http://www.w3.org/2001/XMLSchema#date"}] + } +] diff --git a/core/src/test/resources/json-ld.org/fromRdf-manifest.jsonld b/core/src/test/resources/json-ld.org/fromRdf-manifest.jsonld index 451791ab..6d9451bb 100644 --- a/core/src/test/resources/json-ld.org/fromRdf-manifest.jsonld +++ b/core/src/test/resources/json-ld.org/fromRdf-manifest.jsonld @@ -145,6 +145,13 @@ }, "input": "fromRdf-0019-in.nq", "expect": "fromRdf-0019-out.jsonld" + }, { + "@id": "#t0020", + "@type": ["jld:PositiveEvaluationTest", "jld:FromRDFTest"], + "name": "rdf:type as an @id with values", + "purpose": "Tests the proper formatting of @type (even with useRdfType to false) into rdf:type when the object contains more triples.", + "input": "fromRdf-0020-in.nq", + "expect": "fromRdf-0020-out.jsonld" } ] } diff --git a/core/src/test/resources/json-ld.org/toRdf-0120-in.jsonld b/core/src/test/resources/json-ld.org/toRdf-0120-in.jsonld new file mode 100644 index 00000000..ad2884b9 --- /dev/null +++ b/core/src/test/resources/json-ld.org/toRdf-0120-in.jsonld @@ -0,0 +1,47 @@ +{ + "@context": {"@base": "http://a/bb/ccc/d;p?q", "urn:ex:p": {"@type": "@id"}}, + "@graph": [ + {"@id": "urn:ex:s001", "urn:ex:p": "g:h"}, + {"@id": "urn:ex:s002", "urn:ex:p": "g"}, + {"@id": "urn:ex:s003", "urn:ex:p": "./g"}, + {"@id": "urn:ex:s004", "urn:ex:p": "g/"}, + {"@id": "urn:ex:s005", "urn:ex:p": "/g"}, + {"@id": "urn:ex:s006", "urn:ex:p": "//g"}, + {"@id": "urn:ex:s007", "urn:ex:p": "?y"}, + {"@id": "urn:ex:s008", "urn:ex:p": "g?y"}, + {"@id": "urn:ex:s009", "urn:ex:p": "#s"}, + {"@id": "urn:ex:s010", "urn:ex:p": "g#s"}, + {"@id": "urn:ex:s011", "urn:ex:p": "g?y#s"}, + {"@id": "urn:ex:s012", "urn:ex:p": ";x"}, + {"@id": "urn:ex:s013", "urn:ex:p": "g;x"}, + {"@id": "urn:ex:s014", "urn:ex:p": "g;x?y#s"}, + {"@id": "urn:ex:s015", "urn:ex:p": ""}, + {"@id": "urn:ex:s016", "urn:ex:p": "."}, + {"@id": "urn:ex:s017", "urn:ex:p": "./"}, + {"@id": "urn:ex:s018", "urn:ex:p": ".."}, + {"@id": "urn:ex:s019", "urn:ex:p": "../"}, + {"@id": "urn:ex:s020", "urn:ex:p": "../g"}, + {"@id": "urn:ex:s021", "urn:ex:p": "../.."}, + {"@id": "urn:ex:s022", "urn:ex:p": "../../"}, + {"@id": "urn:ex:s023", "urn:ex:p": "../../g"}, + {"@id": "urn:ex:s024", "urn:ex:p": "../../../g"}, + {"@id": "urn:ex:s025", "urn:ex:p": "../../../../g"}, + {"@id": "urn:ex:s026", "urn:ex:p": "/./g"}, + {"@id": "urn:ex:s027", "urn:ex:p": "/../g"}, + {"@id": "urn:ex:s028", "urn:ex:p": "g."}, + {"@id": "urn:ex:s029", "urn:ex:p": ".g"}, + {"@id": "urn:ex:s030", "urn:ex:p": "g.."}, + {"@id": "urn:ex:s031", "urn:ex:p": "..g"}, + {"@id": "urn:ex:s032", "urn:ex:p": "./../g"}, + {"@id": "urn:ex:s033", "urn:ex:p": "./g/."}, + {"@id": "urn:ex:s034", "urn:ex:p": "g/./h"}, + {"@id": "urn:ex:s035", "urn:ex:p": "g/../h"}, + {"@id": "urn:ex:s036", "urn:ex:p": "g;x=1/./y"}, + {"@id": "urn:ex:s037", "urn:ex:p": "g;x=1/../y"}, + {"@id": "urn:ex:s038", "urn:ex:p": "g?y/./x"}, + {"@id": "urn:ex:s039", "urn:ex:p": "g?y/../x"}, + {"@id": "urn:ex:s040", "urn:ex:p": "g#s/./x"}, + {"@id": "urn:ex:s041", "urn:ex:p": "g#s/../x"}, + {"@id": "urn:ex:s042", "urn:ex:p": "http:g"} + ] +} \ No newline at end of file diff --git a/core/src/test/resources/json-ld.org/toRdf-0120-out.nq b/core/src/test/resources/json-ld.org/toRdf-0120-out.nq new file mode 100644 index 00000000..8503e524 --- /dev/null +++ b/core/src/test/resources/json-ld.org/toRdf-0120-out.nq @@ -0,0 +1,42 @@ + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . diff --git a/core/src/test/resources/json-ld.org/toRdf-0121-in.jsonld b/core/src/test/resources/json-ld.org/toRdf-0121-in.jsonld new file mode 100644 index 00000000..86a197dc --- /dev/null +++ b/core/src/test/resources/json-ld.org/toRdf-0121-in.jsonld @@ -0,0 +1,47 @@ +{ + "@context": {"@base": "http://a/bb/ccc/d/", "urn:ex:p": {"@type": "@id"}}, + "@graph": [ + {"@id": "urn:ex:s043", "urn:ex:p": "g:h"}, + {"@id": "urn:ex:s044", "urn:ex:p": "g"}, + {"@id": "urn:ex:s045", "urn:ex:p": "./g"}, + {"@id": "urn:ex:s046", "urn:ex:p": "g/"}, + {"@id": "urn:ex:s047", "urn:ex:p": "/g"}, + {"@id": "urn:ex:s048", "urn:ex:p": "//g"}, + {"@id": "urn:ex:s049", "urn:ex:p": "?y"}, + {"@id": "urn:ex:s050", "urn:ex:p": "g?y"}, + {"@id": "urn:ex:s051", "urn:ex:p": "#s"}, + {"@id": "urn:ex:s052", "urn:ex:p": "g#s"}, + {"@id": "urn:ex:s053", "urn:ex:p": "g?y#s"}, + {"@id": "urn:ex:s054", "urn:ex:p": ";x"}, + {"@id": "urn:ex:s055", "urn:ex:p": "g;x"}, + {"@id": "urn:ex:s056", "urn:ex:p": "g;x?y#s"}, + {"@id": "urn:ex:s057", "urn:ex:p": ""}, + {"@id": "urn:ex:s058", "urn:ex:p": "."}, + {"@id": "urn:ex:s059", "urn:ex:p": "./"}, + {"@id": "urn:ex:s060", "urn:ex:p": ".."}, + {"@id": "urn:ex:s061", "urn:ex:p": "../"}, + {"@id": "urn:ex:s062", "urn:ex:p": "../g"}, + {"@id": "urn:ex:s063", "urn:ex:p": "../.."}, + {"@id": "urn:ex:s064", "urn:ex:p": "../../"}, + {"@id": "urn:ex:s065", "urn:ex:p": "../../g"}, + {"@id": "urn:ex:s066", "urn:ex:p": "../../../g"}, + {"@id": "urn:ex:s067", "urn:ex:p": "../../../../g"}, + {"@id": "urn:ex:s068", "urn:ex:p": "/./g"}, + {"@id": "urn:ex:s069", "urn:ex:p": "/../g"}, + {"@id": "urn:ex:s070", "urn:ex:p": "g."}, + {"@id": "urn:ex:s071", "urn:ex:p": ".g"}, + {"@id": "urn:ex:s072", "urn:ex:p": "g.."}, + {"@id": "urn:ex:s073", "urn:ex:p": "..g"}, + {"@id": "urn:ex:s074", "urn:ex:p": "./../g"}, + {"@id": "urn:ex:s075", "urn:ex:p": "./g/."}, + {"@id": "urn:ex:s076", "urn:ex:p": "g/./h"}, + {"@id": "urn:ex:s077", "urn:ex:p": "g/../h"}, + {"@id": "urn:ex:s078", "urn:ex:p": "g;x=1/./y"}, + {"@id": "urn:ex:s079", "urn:ex:p": "g;x=1/../y"}, + {"@id": "urn:ex:s080", "urn:ex:p": "g?y/./x"}, + {"@id": "urn:ex:s081", "urn:ex:p": "g?y/../x"}, + {"@id": "urn:ex:s082", "urn:ex:p": "g#s/./x"}, + {"@id": "urn:ex:s083", "urn:ex:p": "g#s/../x"}, + {"@id": "urn:ex:s084", "urn:ex:p": "http:g"} + ] +} \ No newline at end of file diff --git a/core/src/test/resources/json-ld.org/toRdf-0121-out.nq b/core/src/test/resources/json-ld.org/toRdf-0121-out.nq new file mode 100644 index 00000000..b0a0231a --- /dev/null +++ b/core/src/test/resources/json-ld.org/toRdf-0121-out.nq @@ -0,0 +1,42 @@ + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . diff --git a/core/src/test/resources/json-ld.org/toRdf-0122-in.jsonld b/core/src/test/resources/json-ld.org/toRdf-0122-in.jsonld new file mode 100644 index 00000000..f6c240c0 --- /dev/null +++ b/core/src/test/resources/json-ld.org/toRdf-0122-in.jsonld @@ -0,0 +1,47 @@ +{ + "@context": {"@base": "http://a/bb/ccc/./d;p?q", "urn:ex:p": {"@type": "@id"}}, + "@graph": [ + {"@id": "urn:ex:s085", "urn:ex:p": "g:h"}, + {"@id": "urn:ex:s086", "urn:ex:p": "g"}, + {"@id": "urn:ex:s087", "urn:ex:p": "./g"}, + {"@id": "urn:ex:s088", "urn:ex:p": "g/"}, + {"@id": "urn:ex:s089", "urn:ex:p": "/g"}, + {"@id": "urn:ex:s090", "urn:ex:p": "//g"}, + {"@id": "urn:ex:s091", "urn:ex:p": "?y"}, + {"@id": "urn:ex:s092", "urn:ex:p": "g?y"}, + {"@id": "urn:ex:s093", "urn:ex:p": "#s"}, + {"@id": "urn:ex:s094", "urn:ex:p": "g#s"}, + {"@id": "urn:ex:s095", "urn:ex:p": "g?y#s"}, + {"@id": "urn:ex:s096", "urn:ex:p": ";x"}, + {"@id": "urn:ex:s097", "urn:ex:p": "g;x"}, + {"@id": "urn:ex:s098", "urn:ex:p": "g;x?y#s"}, + {"@id": "urn:ex:s099", "urn:ex:p": ""}, + {"@id": "urn:ex:s100", "urn:ex:p": "."}, + {"@id": "urn:ex:s101", "urn:ex:p": "./"}, + {"@id": "urn:ex:s102", "urn:ex:p": ".."}, + {"@id": "urn:ex:s103", "urn:ex:p": "../"}, + {"@id": "urn:ex:s104", "urn:ex:p": "../g"}, + {"@id": "urn:ex:s105", "urn:ex:p": "../.."}, + {"@id": "urn:ex:s106", "urn:ex:p": "../../"}, + {"@id": "urn:ex:s107", "urn:ex:p": "../../g"}, + {"@id": "urn:ex:s108", "urn:ex:p": "../../../g"}, + {"@id": "urn:ex:s109", "urn:ex:p": "../../../../g"}, + {"@id": "urn:ex:s110", "urn:ex:p": "/./g"}, + {"@id": "urn:ex:s111", "urn:ex:p": "/../g"}, + {"@id": "urn:ex:s112", "urn:ex:p": "g."}, + {"@id": "urn:ex:s113", "urn:ex:p": ".g"}, + {"@id": "urn:ex:s114", "urn:ex:p": "g.."}, + {"@id": "urn:ex:s115", "urn:ex:p": "..g"}, + {"@id": "urn:ex:s116", "urn:ex:p": "./../g"}, + {"@id": "urn:ex:s117", "urn:ex:p": "./g/."}, + {"@id": "urn:ex:s118", "urn:ex:p": "g/./h"}, + {"@id": "urn:ex:s119", "urn:ex:p": "g/../h"}, + {"@id": "urn:ex:s120", "urn:ex:p": "g;x=1/./y"}, + {"@id": "urn:ex:s121", "urn:ex:p": "g;x=1/../y"}, + {"@id": "urn:ex:s122", "urn:ex:p": "g?y/./x"}, + {"@id": "urn:ex:s123", "urn:ex:p": "g?y/../x"}, + {"@id": "urn:ex:s124", "urn:ex:p": "g#s/./x"}, + {"@id": "urn:ex:s125", "urn:ex:p": "g#s/../x"}, + {"@id": "urn:ex:s126", "urn:ex:p": "http:g"} + ] +} \ No newline at end of file diff --git a/core/src/test/resources/json-ld.org/toRdf-0122-out.nq b/core/src/test/resources/json-ld.org/toRdf-0122-out.nq new file mode 100644 index 00000000..fd518304 --- /dev/null +++ b/core/src/test/resources/json-ld.org/toRdf-0122-out.nq @@ -0,0 +1,42 @@ + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . diff --git a/core/src/test/resources/json-ld.org/toRdf-0123-in.jsonld b/core/src/test/resources/json-ld.org/toRdf-0123-in.jsonld new file mode 100644 index 00000000..006fa689 --- /dev/null +++ b/core/src/test/resources/json-ld.org/toRdf-0123-in.jsonld @@ -0,0 +1,47 @@ +{ + "@context": {"@base": "http://a/bb/ccc/../d;p?q", "urn:ex:p": {"@type": "@id"}}, + "@graph": [ + {"@id": "urn:ex:s127", "urn:ex:p": "g:h"}, + {"@id": "urn:ex:s128", "urn:ex:p": "g"}, + {"@id": "urn:ex:s129", "urn:ex:p": "./g"}, + {"@id": "urn:ex:s130", "urn:ex:p": "g/"}, + {"@id": "urn:ex:s131", "urn:ex:p": "/g"}, + {"@id": "urn:ex:s132", "urn:ex:p": "//g"}, + {"@id": "urn:ex:s133", "urn:ex:p": "?y"}, + {"@id": "urn:ex:s134", "urn:ex:p": "g?y"}, + {"@id": "urn:ex:s135", "urn:ex:p": "#s"}, + {"@id": "urn:ex:s136", "urn:ex:p": "g#s"}, + {"@id": "urn:ex:s137", "urn:ex:p": "g?y#s"}, + {"@id": "urn:ex:s138", "urn:ex:p": ";x"}, + {"@id": "urn:ex:s139", "urn:ex:p": "g;x"}, + {"@id": "urn:ex:s140", "urn:ex:p": "g;x?y#s"}, + {"@id": "urn:ex:s141", "urn:ex:p": ""}, + {"@id": "urn:ex:s142", "urn:ex:p": "."}, + {"@id": "urn:ex:s143", "urn:ex:p": "./"}, + {"@id": "urn:ex:s144", "urn:ex:p": ".."}, + {"@id": "urn:ex:s145", "urn:ex:p": "../"}, + {"@id": "urn:ex:s146", "urn:ex:p": "../g"}, + {"@id": "urn:ex:s147", "urn:ex:p": "../.."}, + {"@id": "urn:ex:s148", "urn:ex:p": "../../"}, + {"@id": "urn:ex:s149", "urn:ex:p": "../../g"}, + {"@id": "urn:ex:s150", "urn:ex:p": "../../../g"}, + {"@id": "urn:ex:s151", "urn:ex:p": "../../../../g"}, + {"@id": "urn:ex:s152", "urn:ex:p": "/./g"}, + {"@id": "urn:ex:s153", "urn:ex:p": "/../g"}, + {"@id": "urn:ex:s154", "urn:ex:p": "g."}, + {"@id": "urn:ex:s155", "urn:ex:p": ".g"}, + {"@id": "urn:ex:s156", "urn:ex:p": "g.."}, + {"@id": "urn:ex:s157", "urn:ex:p": "..g"}, + {"@id": "urn:ex:s158", "urn:ex:p": "./../g"}, + {"@id": "urn:ex:s159", "urn:ex:p": "./g/."}, + {"@id": "urn:ex:s160", "urn:ex:p": "g/./h"}, + {"@id": "urn:ex:s161", "urn:ex:p": "g/../h"}, + {"@id": "urn:ex:s162", "urn:ex:p": "g;x=1/./y"}, + {"@id": "urn:ex:s163", "urn:ex:p": "g;x=1/../y"}, + {"@id": "urn:ex:s164", "urn:ex:p": "g?y/./x"}, + {"@id": "urn:ex:s165", "urn:ex:p": "g?y/../x"}, + {"@id": "urn:ex:s166", "urn:ex:p": "g#s/./x"}, + {"@id": "urn:ex:s167", "urn:ex:p": "g#s/../x"}, + {"@id": "urn:ex:s168", "urn:ex:p": "http:g"} + ] +} \ No newline at end of file diff --git a/core/src/test/resources/json-ld.org/toRdf-0123-out.nq b/core/src/test/resources/json-ld.org/toRdf-0123-out.nq new file mode 100644 index 00000000..59af1ece --- /dev/null +++ b/core/src/test/resources/json-ld.org/toRdf-0123-out.nq @@ -0,0 +1,42 @@ + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . diff --git a/core/src/test/resources/json-ld.org/toRdf-0124-in.jsonld b/core/src/test/resources/json-ld.org/toRdf-0124-in.jsonld new file mode 100644 index 00000000..d75b3d8c --- /dev/null +++ b/core/src/test/resources/json-ld.org/toRdf-0124-in.jsonld @@ -0,0 +1,47 @@ +{ + "@context": {"@base": "http://a/bb/ccc/.", "urn:ex:p": {"@type": "@id"}}, + "@graph": [ + {"@id": "urn:ex:s169", "urn:ex:p": "g:h"}, + {"@id": "urn:ex:s170", "urn:ex:p": "g"}, + {"@id": "urn:ex:s171", "urn:ex:p": "./g"}, + {"@id": "urn:ex:s172", "urn:ex:p": "g/"}, + {"@id": "urn:ex:s173", "urn:ex:p": "/g"}, + {"@id": "urn:ex:s174", "urn:ex:p": "//g"}, + {"@id": "urn:ex:s175", "urn:ex:p": "?y"}, + {"@id": "urn:ex:s176", "urn:ex:p": "g?y"}, + {"@id": "urn:ex:s177", "urn:ex:p": "#s"}, + {"@id": "urn:ex:s178", "urn:ex:p": "g#s"}, + {"@id": "urn:ex:s179", "urn:ex:p": "g?y#s"}, + {"@id": "urn:ex:s180", "urn:ex:p": ";x"}, + {"@id": "urn:ex:s181", "urn:ex:p": "g;x"}, + {"@id": "urn:ex:s182", "urn:ex:p": "g;x?y#s"}, + {"@id": "urn:ex:s183", "urn:ex:p": ""}, + {"@id": "urn:ex:s184", "urn:ex:p": "."}, + {"@id": "urn:ex:s185", "urn:ex:p": "./"}, + {"@id": "urn:ex:s186", "urn:ex:p": ".."}, + {"@id": "urn:ex:s187", "urn:ex:p": "../"}, + {"@id": "urn:ex:s188", "urn:ex:p": "../g"}, + {"@id": "urn:ex:s189", "urn:ex:p": "../.."}, + {"@id": "urn:ex:s190", "urn:ex:p": "../../"}, + {"@id": "urn:ex:s191", "urn:ex:p": "../../g"}, + {"@id": "urn:ex:s192", "urn:ex:p": "../../../g"}, + {"@id": "urn:ex:s193", "urn:ex:p": "../../../../g"}, + {"@id": "urn:ex:s194", "urn:ex:p": "/./g"}, + {"@id": "urn:ex:s195", "urn:ex:p": "/../g"}, + {"@id": "urn:ex:s196", "urn:ex:p": "g."}, + {"@id": "urn:ex:s197", "urn:ex:p": ".g"}, + {"@id": "urn:ex:s198", "urn:ex:p": "g.."}, + {"@id": "urn:ex:s199", "urn:ex:p": "..g"}, + {"@id": "urn:ex:s200", "urn:ex:p": "./../g"}, + {"@id": "urn:ex:s201", "urn:ex:p": "./g/."}, + {"@id": "urn:ex:s202", "urn:ex:p": "g/./h"}, + {"@id": "urn:ex:s203", "urn:ex:p": "g/../h"}, + {"@id": "urn:ex:s204", "urn:ex:p": "g;x=1/./y"}, + {"@id": "urn:ex:s205", "urn:ex:p": "g;x=1/../y"}, + {"@id": "urn:ex:s206", "urn:ex:p": "g?y/./x"}, + {"@id": "urn:ex:s207", "urn:ex:p": "g?y/../x"}, + {"@id": "urn:ex:s208", "urn:ex:p": "g#s/./x"}, + {"@id": "urn:ex:s209", "urn:ex:p": "g#s/../x"}, + {"@id": "urn:ex:s210", "urn:ex:p": "http:g"} + ] +} diff --git a/core/src/test/resources/json-ld.org/toRdf-0124-out.nq b/core/src/test/resources/json-ld.org/toRdf-0124-out.nq new file mode 100644 index 00000000..7a57e0e6 --- /dev/null +++ b/core/src/test/resources/json-ld.org/toRdf-0124-out.nq @@ -0,0 +1,42 @@ + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . diff --git a/core/src/test/resources/json-ld.org/toRdf-0125-in.jsonld b/core/src/test/resources/json-ld.org/toRdf-0125-in.jsonld new file mode 100644 index 00000000..2e1adc8b --- /dev/null +++ b/core/src/test/resources/json-ld.org/toRdf-0125-in.jsonld @@ -0,0 +1,47 @@ +{ + "@context": {"@base": "http://a/bb/ccc/..", "urn:ex:p": {"@type": "@id"}}, + "@graph": [ + {"@id": "urn:ex:s211", "urn:ex:p": "g:h"}, + {"@id": "urn:ex:s212", "urn:ex:p": "g"}, + {"@id": "urn:ex:s213", "urn:ex:p": "./g"}, + {"@id": "urn:ex:s214", "urn:ex:p": "g/"}, + {"@id": "urn:ex:s215", "urn:ex:p": "/g"}, + {"@id": "urn:ex:s216", "urn:ex:p": "//g"}, + {"@id": "urn:ex:s217", "urn:ex:p": "?y"}, + {"@id": "urn:ex:s218", "urn:ex:p": "g?y"}, + {"@id": "urn:ex:s219", "urn:ex:p": "#s"}, + {"@id": "urn:ex:s220", "urn:ex:p": "g#s"}, + {"@id": "urn:ex:s221", "urn:ex:p": "g?y#s"}, + {"@id": "urn:ex:s222", "urn:ex:p": ";x"}, + {"@id": "urn:ex:s223", "urn:ex:p": "g;x"}, + {"@id": "urn:ex:s224", "urn:ex:p": "g;x?y#s"}, + {"@id": "urn:ex:s225", "urn:ex:p": ""}, + {"@id": "urn:ex:s226", "urn:ex:p": "."}, + {"@id": "urn:ex:s227", "urn:ex:p": "./"}, + {"@id": "urn:ex:s228", "urn:ex:p": ".."}, + {"@id": "urn:ex:s229", "urn:ex:p": "../"}, + {"@id": "urn:ex:s230", "urn:ex:p": "../g"}, + {"@id": "urn:ex:s231", "urn:ex:p": "../.."}, + {"@id": "urn:ex:s232", "urn:ex:p": "../../"}, + {"@id": "urn:ex:s233", "urn:ex:p": "../../g"}, + {"@id": "urn:ex:s234", "urn:ex:p": "../../../g"}, + {"@id": "urn:ex:s235", "urn:ex:p": "../../../../g"}, + {"@id": "urn:ex:s236", "urn:ex:p": "/./g"}, + {"@id": "urn:ex:s237", "urn:ex:p": "/../g"}, + {"@id": "urn:ex:s238", "urn:ex:p": "g."}, + {"@id": "urn:ex:s239", "urn:ex:p": ".g"}, + {"@id": "urn:ex:s240", "urn:ex:p": "g.."}, + {"@id": "urn:ex:s241", "urn:ex:p": "..g"}, + {"@id": "urn:ex:s242", "urn:ex:p": "./../g"}, + {"@id": "urn:ex:s243", "urn:ex:p": "./g/."}, + {"@id": "urn:ex:s244", "urn:ex:p": "g/./h"}, + {"@id": "urn:ex:s245", "urn:ex:p": "g/../h"}, + {"@id": "urn:ex:s246", "urn:ex:p": "g;x=1/./y"}, + {"@id": "urn:ex:s247", "urn:ex:p": "g;x=1/../y"}, + {"@id": "urn:ex:s248", "urn:ex:p": "g?y/./x"}, + {"@id": "urn:ex:s249", "urn:ex:p": "g?y/../x"}, + {"@id": "urn:ex:s250", "urn:ex:p": "g#s/./x"}, + {"@id": "urn:ex:s251", "urn:ex:p": "g#s/../x"}, + {"@id": "urn:ex:s252", "urn:ex:p": "http:g"} + ] +} diff --git a/core/src/test/resources/json-ld.org/toRdf-0125-out.nq b/core/src/test/resources/json-ld.org/toRdf-0125-out.nq new file mode 100644 index 00000000..89a3f659 --- /dev/null +++ b/core/src/test/resources/json-ld.org/toRdf-0125-out.nq @@ -0,0 +1,42 @@ + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . diff --git a/core/src/test/resources/json-ld.org/toRdf-0127-in.jsonld b/core/src/test/resources/json-ld.org/toRdf-0127-in.jsonld new file mode 100644 index 00000000..eec91f99 --- /dev/null +++ b/core/src/test/resources/json-ld.org/toRdf-0127-in.jsonld @@ -0,0 +1,11 @@ +{ + "@context": {"@base": "http://abc/def/ghi", "urn:ex:p": {"@type": "@id"}}, + "@graph": [ + {"@id": "urn:ex:s295", "urn:ex:p": "."}, + {"@id": "urn:ex:s296", "urn:ex:p": ".?a=b"}, + {"@id": "urn:ex:s297", "urn:ex:p": ".#a=b"}, + {"@id": "urn:ex:s298", "urn:ex:p": ".."}, + {"@id": "urn:ex:s299", "urn:ex:p": "..?a=b"}, + {"@id": "urn:ex:s300", "urn:ex:p": "..#a=b"} + ] +} diff --git a/core/src/test/resources/json-ld.org/toRdf-0127-out.nq b/core/src/test/resources/json-ld.org/toRdf-0127-out.nq new file mode 100644 index 00000000..65e26022 --- /dev/null +++ b/core/src/test/resources/json-ld.org/toRdf-0127-out.nq @@ -0,0 +1,6 @@ + . + . + . + . + . + . diff --git a/core/src/test/resources/json-ld.org/toRdf-0129-in.jsonld b/core/src/test/resources/json-ld.org/toRdf-0129-in.jsonld new file mode 100644 index 00000000..a199895e --- /dev/null +++ b/core/src/test/resources/json-ld.org/toRdf-0129-in.jsonld @@ -0,0 +1,8 @@ +{ + "@context": {"@base": "http://abc/d:f/ghi", "urn:ex:p": {"@type": "@id"}}, + "@graph": [ + {"@id": "urn:ex:s304", "urn:ex:p": "xyz"}, + {"@id": "urn:ex:s305", "urn:ex:p": "./xyz"}, + {"@id": "urn:ex:s306", "urn:ex:p": "../xyz"} + ] +} diff --git a/core/src/test/resources/json-ld.org/toRdf-0129-out.nq b/core/src/test/resources/json-ld.org/toRdf-0129-out.nq new file mode 100644 index 00000000..31bce616 --- /dev/null +++ b/core/src/test/resources/json-ld.org/toRdf-0129-out.nq @@ -0,0 +1,3 @@ + . + . + . diff --git a/core/src/test/resources/json-ld.org/toRdf-0130-in.jsonld b/core/src/test/resources/json-ld.org/toRdf-0130-in.jsonld new file mode 100644 index 00000000..bb11d1fe --- /dev/null +++ b/core/src/test/resources/json-ld.org/toRdf-0130-in.jsonld @@ -0,0 +1,6 @@ +{ + "@context": {"@base": "tag:example", "urn:ex:p": {"@type": "@id"}}, + "@graph": [ + {"@id": "urn:ex:s307", "urn:ex:p": "a"} + ] +} diff --git a/core/src/test/resources/json-ld.org/toRdf-0130-out.nq b/core/src/test/resources/json-ld.org/toRdf-0130-out.nq new file mode 100644 index 00000000..48c95173 --- /dev/null +++ b/core/src/test/resources/json-ld.org/toRdf-0130-out.nq @@ -0,0 +1 @@ + . diff --git a/core/src/test/resources/json-ld.org/toRdf-0131-in.jsonld b/core/src/test/resources/json-ld.org/toRdf-0131-in.jsonld new file mode 100644 index 00000000..86954242 --- /dev/null +++ b/core/src/test/resources/json-ld.org/toRdf-0131-in.jsonld @@ -0,0 +1,6 @@ +{ + "@context": {"@base": "tag:example/foo", "urn:ex:p": {"@type": "@id"}}, + "@graph": [ + {"@id": "urn:ex:s308", "urn:ex:p": "a"} + ] +} diff --git a/core/src/test/resources/json-ld.org/toRdf-0131-out.nq b/core/src/test/resources/json-ld.org/toRdf-0131-out.nq new file mode 100644 index 00000000..4c420b35 --- /dev/null +++ b/core/src/test/resources/json-ld.org/toRdf-0131-out.nq @@ -0,0 +1 @@ + . diff --git a/core/src/test/resources/json-ld.org/toRdf-0132-in.jsonld b/core/src/test/resources/json-ld.org/toRdf-0132-in.jsonld new file mode 100644 index 00000000..d26b45b6 --- /dev/null +++ b/core/src/test/resources/json-ld.org/toRdf-0132-in.jsonld @@ -0,0 +1,6 @@ +{ + "@context": {"@base": "tag:example/foo/", "urn:ex:p": {"@type": "@id"}}, + "@graph": [ + {"@id": "urn:ex:s309", "urn:ex:p": "a"} + ] +} diff --git a/core/src/test/resources/json-ld.org/toRdf-0132-out.nq b/core/src/test/resources/json-ld.org/toRdf-0132-out.nq new file mode 100644 index 00000000..7215f758 --- /dev/null +++ b/core/src/test/resources/json-ld.org/toRdf-0132-out.nq @@ -0,0 +1 @@ + . diff --git a/core/src/test/resources/json-ld.org/toRdf-manifest.jsonld b/core/src/test/resources/json-ld.org/toRdf-manifest.jsonld index 8abb3e57..a8d2fdf2 100644 --- a/core/src/test/resources/json-ld.org/toRdf-manifest.jsonld +++ b/core/src/test/resources/json-ld.org/toRdf-manifest.jsonld @@ -807,6 +807,83 @@ "purpose": "Proper (re-)labeling of blank nodes if used with reverse properties.", "input": "toRdf-0119-in.jsonld", "expect": "toRdf-0119-out.nq" + }, { + "@id": "#t0120", + "@type": ["jld:PositiveEvaluationTest", "jld:ToRDFTest"], + "name": "IRI Resolution (0)", + "purpose": "IRI resolution according to RFC3986.", + "input": "toRdf-0120-in.jsonld", + "expect": "toRdf-0120-out.nq" + }, { + "@id": "#t0121", + "@type": ["jld:PositiveEvaluationTest", "jld:ToRDFTest"], + "name": "IRI Resolution (1)", + "purpose": "IRI resolution according to RFC3986.", + "input": "toRdf-0121-in.jsonld", + "expect": "toRdf-0121-out.nq" + }, { + "@id": "#t0122", + "@type": ["jld:PositiveEvaluationTest", "jld:ToRDFTest"], + "name": "IRI Resolution (2)", + "purpose": "IRI resolution according to RFC3986.", + "input": "toRdf-0122-in.jsonld", + "expect": "toRdf-0122-out.nq" + }, { + "@id": "#t0123", + "@type": ["jld:PositiveEvaluationTest", "jld:ToRDFTest"], + "name": "IRI Resolution (3)", + "purpose": "IRI resolution according to RFC3986.", + "input": "toRdf-0123-in.jsonld", + "expect": "toRdf-0123-out.nq" + }, { + "@id": "#t0124", + "@type": ["jld:PositiveEvaluationTest", "jld:ToRDFTest"], + "name": "IRI Resolution (4)", + "purpose": "IRI resolution according to RFC3986.", + "input": "toRdf-0124-in.jsonld", + "expect": "toRdf-0124-out.nq" + }, { + "@id": "#t0125", + "@type": ["jld:PositiveEvaluationTest", "jld:ToRDFTest"], + "name": "IRI Resolution (5)", + "purpose": "IRI resolution according to RFC3986.", + "input": "toRdf-0125-in.jsonld", + "expect": "toRdf-0125-out.nq" + }, { + "@id": "#t0127", + "@type": ["jld:PositiveEvaluationTest", "jld:ToRDFTest"], + "name": "IRI Resolution (7)", + "purpose": "IRI resolution according to RFC3986.", + "input": "toRdf-0127-in.jsonld", + "expect": "toRdf-0127-out.nq" + }, { + "@id": "#t0129", + "@type": ["jld:PositiveEvaluationTest", "jld:ToRDFTest"], + "name": "IRI Resolution (9)", + "purpose": "IRI resolution according to RFC3986.", + "input": "toRdf-0129-in.jsonld", + "expect": "toRdf-0129-out.nq" + }, { + "@id": "#t0130", + "@type": ["jld:PositiveEvaluationTest", "jld:ToRDFTest"], + "name": "IRI Resolution (10)", + "purpose": "IRI resolution according to RFC3986.", + "input": "toRdf-0130-in.jsonld", + "expect": "toRdf-0130-out.nq" + }, { + "@id": "#t0131", + "@type": ["jld:PositiveEvaluationTest", "jld:ToRDFTest"], + "name": "IRI Resolution (11)", + "purpose": "IRI resolution according to RFC3986.", + "input": "toRdf-0131-in.jsonld", + "expect": "toRdf-0131-out.nq" + }, { + "@id": "#t0132", + "@type": ["jld:PositiveEvaluationTest", "jld:ToRDFTest"], + "name": "IRI Resolution (12)", + "purpose": "IRI resolution according to RFC3986.", + "input": "toRdf-0132-in.jsonld", + "expect": "toRdf-0132-out.nq" } ] } diff --git a/core/src/test/resources/log4j.properties b/core/src/test/resources/log4j.properties deleted file mode 100644 index 136eba0c..00000000 --- a/core/src/test/resources/log4j.properties +++ /dev/null @@ -1,5 +0,0 @@ -log4j.rootLogger=INFO, R - -log4j.appender.R=org.apache.log4j.ConsoleAppender -log4j.appender.R.layout=org.apache.log4j.PatternLayout -log4j.appender.R.layout.ConversionPattern=[%d] %-5p (%c:%L) %m%n diff --git a/pom.xml b/pom.xml index 0be7af1c..de5316fa 100755 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.github.jsonld-java jsonld-java-parent - 0.8.3-SNAPSHOT + 0.13.6 JSONLD Java :: Parent Json-LD Java Parent POM pom @@ -39,17 +39,26 @@ UTF-8 UTF-8 - 4.5.1 - 4.4.4 - 2.6.3 - 4.12 - 1.7.13 + 4.5.13 + 4.4.14 + 2.12.7 + 2.12.7.1 + 4.13.2 + 1.7.32 + 1.2.7 + + 0.11.0 - - 3.0.5 - + + + com.fasterxml.jackson + jackson-bom + ${jackson.version} + pom + import + com.fasterxml.jackson.core jackson-core @@ -58,7 +67,7 @@ com.fasterxml.jackson.core jackson-databind - ${jackson.version} + ${jackson-databind.version} com.fasterxml.jackson.core @@ -89,9 +98,9 @@ runtime - org.slf4j - slf4j-log4j12 - ${slf4j.version} + ch.qos.logback + logback-classic + ${logback.version} test @@ -185,37 +194,119 @@ commons-codec commons-codec - 1.10 + 1.15 org.mockito mockito-core - 1.10.19 + 2.28.2 + test commons-io commons-io - 2.4 + 2.8.0 + + + + com.google.guava + guava + 32.1.3-jre + + + org.apache.maven.plugins + maven-enforcer-plugin + + + org.codehaus.mojo + animal-sniffer-maven-plugin + + + + org.apache.maven.plugins + maven-enforcer-plugin + 3.0.0-M3 + + + enforce-maven-3 + + enforce + + + + + [3.0.5,) + + + [1.8,) + + + + + + enforce-bytecode-version + + enforce + + + + + 1.8 + + + true + + + + + + org.codehaus.mojo + extra-enforcer-rules + 1.3 + + + org.apache.maven.plugins maven-compiler-plugin - 3.5 + 3.8.1 - 1.6 - 1.6 + 1.8 + 1.8 + + org.apache.maven.plugins + maven-assembly-plugin + 3.2.0 + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.1 + + + org.apache.maven.plugins + maven-dependency-plugin + 3.1.1 + + + org.apache.maven.plugins + maven-antrun-plugin + 1.8 + org.apache.maven.plugins maven-javadoc-plugin - 2.10.3 + 3.1.1 org.apache.maven.plugins @@ -225,7 +316,12 @@ org.apache.maven.plugins maven-resources-plugin - 2.7 + 3.1.0 + + + org.apache.maven.plugins + maven-release-plugin + 2.5.3 org.apache.maven.plugins @@ -235,7 +331,7 @@ org.apache.maven.plugins maven-clean-plugin - 2.6.1 + 3.1.0 org.apache.maven.plugins @@ -245,7 +341,7 @@ org.apache.maven.plugins maven-jar-plugin - 2.5 + 3.2.0 @@ -257,7 +353,7 @@ org.apache.maven.plugins maven-source-plugin - 2.4 + 3.2.1 attach-source @@ -276,14 +372,20 @@ org.apache.maven.plugins maven-surefire-plugin - 2.19.1 + 2.22.2 + + + org.apache.maven.plugins + maven-site-plugin + 3.8.2 org.codehaus.mojo animal-sniffer-maven-plugin - 1.14 + 1.18 + check-jdk-compliance test check @@ -293,31 +395,62 @@ org.codehaus.mojo.signature - java16 + java18 1.0 + + com.github.siom79.japicmp + japicmp-maven-plugin + 0.14.4 + + + + ${project.groupId} + ${project.artifactId} + ${last-compare-version} + jar + + + + + ${project.build.directory}/${project.artifactId}-${project.version}.jar + + + + true + + + + + verify + + cmp + + + + org.codehaus.mojo appassembler-maven-plugin - 1.10 + 2.1.0 org.apache.felix maven-bundle-plugin - 2.5.4 + 3.5.1 org.eluder.coveralls coveralls-maven-plugin - 3.0.1 + 4.3.0 org.jacoco jacoco-maven-plugin - 0.7.4.201502262128 + 0.8.6 prepare-agent @@ -327,35 +460,10 @@ - - org.eclipse.m2e - lifecycle-mapping - 1.0.0 - - - - - - org.jacoco - - jacoco-maven-plugin - - - [0.7.2.201409121644,) - - - prepare-agent - - - - - - - - - + org.codehaus.mojo + versions-maven-plugin + 2.7 @@ -397,7 +505,6 @@ org.apache.maven.plugins maven-source-plugin - 2.4 attach-sources @@ -410,7 +517,6 @@ org.apache.maven.plugins maven-javadoc-plugin - 2.10.3 attach-javadocs @@ -423,7 +529,6 @@ org.apache.maven.plugins maven-gpg-plugin - 1.6 sign-artifacts