Skip to content

Commit

Permalink
Merge branch 'development'
Browse files Browse the repository at this point in the history
  • Loading branch information
ledsoft committed Jan 17, 2021
2 parents 7dbf5d6 + 6074bf3 commit 092f1a0
Show file tree
Hide file tree
Showing 18 changed files with 365 additions and 62 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>cz.cvut.kbss.jsonld</groupId>
<artifactId>jb4jsonld</artifactId>
<version>0.8.3</version>
<version>0.8.4</version>
<name>JB4JSON-LD</name>
<description>Java Binding for JSON-LD allows serialization and deserialization of Java POJOs to/from JSON-LD.
This is the core implementation, which has to be integrated with Jackson, Jersey etc.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
/**
* Copyright (C) 2020 Czech Technical University in Prague
*
* <p>
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* <p>
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
Expand All @@ -20,6 +20,7 @@
import cz.cvut.kbss.jsonld.annotation.JsonLdAttributeOrder;
import cz.cvut.kbss.jsonld.exception.JsonLdSerializationException;

import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;
Expand Down Expand Up @@ -70,7 +71,72 @@ public static String getOwlClass(Class<?> cls) {
if (owlClass == null) {
throw new IllegalArgumentException(cls + " is not an OWL class entity.");
}
return owlClass.iri();
return expandIriIfNecessary(owlClass.iri(), cls);
}

/**
* Attempts to expand the specified IRI in case it is compacted (see {@link IdentifierUtil#isCompactIri(String)}) using JOPA namespace declarations.
* <p>
* If the IRI is not compact or no matching namespace is found, the original IRI is returned.
*
* @param iri IRI to expand (if necessary and possible)
* @param declaringClass Class in/on which the IRI is declared. It is used as base for namespace search
* @return Expanded IRI if it was possible to expand it, original argument if not
* @see IdentifierUtil#isCompactIri(String)
* @see Namespaces
* @see Namespace
*/
public static String expandIriIfNecessary(String iri, Class<?> declaringClass) {
Objects.requireNonNull(declaringClass);
return IdentifierUtil.isCompactIri(iri) ? expandIri(iri, declaringClass).orElse(iri) : iri;
}

/**
* Attempts to expand the specified compact IRI by finding a corresponding {@link Namespace} annotation in the specified class's ancestor hierarchy.
* <p>
* That is, it tries to find a {@link Namespace} annotation with matching prefix on the specified class or any of its ancestors. If such an annotation
* is found, its namespace is concatenated with the suffix from the specified {@code iri} to produce the expanded version of the IRI.
* <p>
* If no matching {@link Namespace} annotation is found, an empty {@link Optional} is returned.
*
* @param iri Compact IRI to expand
* @param declaringClass Class in which the IRI was declared. Used to start search for namespace declaration
* @return Expanded IRI if a matching namespace declaration is found, empty {@code Optional} if not
*/
private static Optional<String> expandIri(String iri, Class<?> declaringClass) {
assert IdentifierUtil.isCompactIri(iri);

final int colonIndex = iri.indexOf(':');
final String prefix = iri.substring(0, colonIndex);
final String suffix = iri.substring(colonIndex + 1);
Optional<String> ns = resolveNamespace(declaringClass, prefix);
if (ns.isPresent()) {
return ns.map(v -> v + suffix);
}
if (declaringClass.getPackage() != null) {
ns = resolveNamespace(declaringClass.getPackage(), prefix);
if (ns.isPresent()) {
return ns.map(v -> v + suffix);
}
}
return declaringClass.getSuperclass() != null ? expandIri(iri, declaringClass.getSuperclass()) :
Optional.empty();
}

private static Optional<String> resolveNamespace(AnnotatedElement annotated, String prefix) {
Namespace ns = annotated.getDeclaredAnnotation(Namespace.class);
if (ns != null && ns.prefix().equals(prefix)) {
return Optional.of(ns.namespace());
}
Namespaces namespaces = annotated.getDeclaredAnnotation(Namespaces.class);
if (namespaces != null) {
final Optional<Namespace> namespace =
Arrays.stream(namespaces.value()).filter(n -> n.prefix().equals(prefix)).findAny();
if (namespace.isPresent()) {
return namespace.map(Namespace::namespace);
}
}
return Optional.empty();
}

/**
Expand Down Expand Up @@ -100,7 +166,7 @@ public static Set<String> getOwlClasses(Class<?> cls) {
getAncestors(cls).forEach(c -> {
final OWLClass owlClass = c.getDeclaredAnnotation(OWLClass.class);
if (owlClass != null) {
classes.add(owlClass.iri());
classes.add(expandIriIfNecessary(owlClass.iri(), c));
}
});
return classes;
Expand Down Expand Up @@ -319,15 +385,15 @@ public static String getAttributeIdentifier(Field field) {
}
final OWLDataProperty dp = field.getDeclaredAnnotation(OWLDataProperty.class);
if (dp != null) {
return dp.iri();
return expandIriIfNecessary(dp.iri(), field.getDeclaringClass());
}
final OWLObjectProperty op = field.getDeclaredAnnotation(OWLObjectProperty.class);
if (op != null) {
return op.iri();
return expandIriIfNecessary(op.iri(), field.getDeclaringClass());
}
final OWLAnnotationProperty ap = field.getDeclaredAnnotation(OWLAnnotationProperty.class);
if (ap != null) {
return ap.iri();
return expandIriIfNecessary(ap.iri(), field.getDeclaringClass());
}
if (field.getDeclaredAnnotation(Types.class) != null) {
return JsonLd.TYPE;
Expand Down
23 changes: 21 additions & 2 deletions src/main/java/cz/cvut/kbss/jsonld/common/IdentifierUtil.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
/**
* Copyright (C) 2020 Czech Technical University in Prague
*
* <p>
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* <p>
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
Expand All @@ -14,6 +14,7 @@
*/
package cz.cvut.kbss.jsonld.common;

import java.util.Objects;
import java.util.Random;

/**
Expand All @@ -40,4 +41,22 @@ public class IdentifierUtil {
public static String generateBlankNodeId() {
return B_NODE_PREFIX + RANDOM.nextInt(Integer.MAX_VALUE);
}

/**
* Checks whether the specified value is a <i>compact IRI</i>, as defined by the JSON-LD specification
* <a href="https://w3c.github.io/json-ld-syntax/#compact-iris">par. 4.1.5</a>.
*
* @param value The value to examine
* @return {@code true} if the specified value is a compact IRI, {@code false} otherwise
*/
public static boolean isCompactIri(String value) {
Objects.requireNonNull(value);
final int colonIndex = value.indexOf(':');
if (colonIndex >= 0) {
final String prefixWithColon = value.substring(0, colonIndex + 1);
final String suffix = value.substring(colonIndex + 1);
return !B_NODE_PREFIX.equals(prefixWithColon) && !suffix.startsWith("//");
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -111,20 +111,19 @@ public <T> void openObject(String id, Class<T> cls) {
} else {
if (knownInstances.containsKey(id)) {
final InstanceContext<T> context = reopenExistingInstance(id, cls);
replaceCurrentContext(context.getInstance(), context);
replaceCurrentContext(context);
} else {
final T instance = BeanClassProcessor.createInstance(cls);
final InstanceContext<T> context = new SingularObjectContext<>(instance,
BeanAnnotationProcessor.mapFieldsForDeserialization(cls), knownInstances);
replaceCurrentContext(instance, context);
replaceCurrentContext(context);
currentInstance.setIdentifierValue(id);
}
}
}

private <T> void replaceCurrentContext(T instance, InstanceContext<?> ctx) {
private void replaceCurrentContext(InstanceContext<?> ctx) {
if (currentInstance != null) {
currentInstance.addItem(instance);
openInstances.push(currentInstance);
}
this.currentInstance = ctx;
Expand All @@ -137,7 +136,10 @@ public void closeObject() {
pendingReferenceRegistry.resolveReferences(currentInstance.getIdentifier(), currentInstance.getInstance());
}
if (!openInstances.isEmpty()) {
final InstanceContext<?> closing = this.currentInstance;
this.currentInstance = openInstances.pop();
// Add the item to the instance after closing it, so that all its fields have been initialized already (if they are needed by equals/hashCode)
currentInstance.addItem(closing.getInstance());
}
}

Expand Down Expand Up @@ -199,7 +201,7 @@ private Object getCollectionForField(Field targetField) {
public void openCollection(CollectionType collectionType) {
final Collection<?> collection = BeanClassProcessor.createCollection(collectionType);
final InstanceContext<?> context = new CollectionInstanceContext<>(collection, knownInstances);
replaceCurrentContext(collection, context);
replaceCurrentContext(context);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ Field getFieldForProperty(String property) {
* @param value The value to set
*/
void setFieldValue(Field field, Object value) {
throw new UnsupportedOperationException("Not supported by this type of instance context.");
// Do nothing
}

/**
Expand All @@ -118,7 +118,7 @@ void setFieldValue(Field field, Object value) {
* @param item Item to add
*/
void addItem(Object item) {
throw new UnsupportedOperationException("Not supported by this type of instance context.");
// Do nothing
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import cz.cvut.kbss.jopa.model.annotations.OWLClass;
import cz.cvut.kbss.jsonld.ConfigParam;
import cz.cvut.kbss.jsonld.Configuration;
import cz.cvut.kbss.jsonld.common.BeanAnnotationProcessor;
import cz.cvut.kbss.jsonld.common.Configurable;
import cz.cvut.kbss.jsonld.deserialization.expanded.ExpandedJsonLdDeserializer;
import cz.cvut.kbss.jsonld.deserialization.util.ClasspathScanner;
Expand Down Expand Up @@ -49,7 +50,7 @@ private TargetClassResolver initializeTargetClassResolver() {
new ClasspathScanner(c -> {
final OWLClass ann = c.getDeclaredAnnotation(OWLClass.class);
if (ann != null) {
typeMap.register(ann.iri(), c);
typeMap.register(BeanAnnotationProcessor.expandIriIfNecessary(ann.iri(), c), c);
}
}).processClasses(scanPath);
return new TargetClassResolver(typeMap,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
/**
* Copyright (C) 2020 Czech Technical University in Prague
*
* <p>
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* <p>
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
Expand All @@ -14,11 +14,11 @@
*/
package cz.cvut.kbss.jsonld.common;

import cz.cvut.kbss.jopa.model.annotations.Id;
import cz.cvut.kbss.jopa.model.annotations.OWLAnnotationProperty;
import cz.cvut.kbss.jopa.model.annotations.OWLClass;
import cz.cvut.kbss.jopa.model.annotations.OWLDataProperty;
import cz.cvut.kbss.jopa.model.annotations.*;
import cz.cvut.kbss.jopa.vocabulary.DC;
import cz.cvut.kbss.jopa.vocabulary.RDF;
import cz.cvut.kbss.jopa.vocabulary.RDFS;
import cz.cvut.kbss.jopa.vocabulary.SKOS;
import cz.cvut.kbss.jsonld.JsonLd;
import cz.cvut.kbss.jsonld.annotation.JsonLdAttributeOrder;
import cz.cvut.kbss.jsonld.annotation.JsonLdProperty;
Expand All @@ -33,6 +33,7 @@
import java.util.*;

import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.hasItems;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.*;

Expand Down Expand Up @@ -218,6 +219,48 @@ void getOwlClassThrowsIllegalArgumentWhenNonOwlClassJavaTypeIsPassedAsArgument()
assertEquals(Integer.class + " is not an OWL class entity.", result.getMessage());
}

@Test
void getOwlClassExpandsIriIfItUsesNamespacePrefix() {
assertEquals(DC.Terms.AGENT, BeanAnnotationProcessor.getOwlClass(ClassWithNamespace.class));
}

@Namespace(prefix = "dc", namespace = DC.Terms.NAMESPACE)
@OWLClass(iri = "dc:Agent")
private static class ClassWithNamespace {
}

@Test
void getOWLClassExpandsIriBasedOnNamespacesDeclarationIfItUsesPrefix() {
assertEquals(SKOS.CONCEPT, BeanAnnotationProcessor.getOwlClass(ClassWithNamespaces.class));
}

@Namespaces({@Namespace(prefix = "rdf", namespace = RDF.NAMESPACE),
@Namespace(prefix = "skos", namespace = SKOS.NAMESPACE)})
@OWLClass(iri = "skos:Concept")
private static class ClassWithNamespaces {

@OWLDataProperty(iri = "skos:prefLabel")
private String prefLabel;

@OWLObjectProperty(iri = "rdfs:range")
private URI range;
}

@Test
void getOWLClassExpandsIriBasedOnNamespaceDeclaredInAncestorIfItUsesPrefix() {
assertEquals(SKOS.CONCEPT_SCHEME, BeanAnnotationProcessor.getOwlClass(ClassWithParentNamespaces.class));
}

@OWLClass(iri = "skos:ConceptScheme")
private static class ClassWithParentNamespaces extends ClassWithNamespaces {
}

@Test
void getOwlClassesExpandsCompactIrisBasedOnNamespaces() {
final Set<String> result = BeanAnnotationProcessor.getOwlClasses(ClassWithParentNamespaces.class);
assertThat(result, hasItems(SKOS.CONCEPT_SCHEME, SKOS.CONCEPT));
}

@Test
void getSerializableFieldsReturnsPropertiesFieldAsWell() throws Exception {
final List<Field> fields = BeanAnnotationProcessor.getSerializableFields(new Person());
Expand Down Expand Up @@ -290,7 +333,20 @@ void hasTypesFieldReturnsFalseWhenClassDoesNotHaveTypesAttribute() {

@Test
void isAnnotationPropertyReturnsTrueForAnnotationPropertyField() throws Exception {
assertTrue(BeanAnnotationProcessor.isAnnotationProperty(ObjectWithAnnotationProperties.class.getDeclaredField("changedValue")));
assertTrue(BeanAnnotationProcessor
.isAnnotationProperty(ObjectWithAnnotationProperties.class.getDeclaredField("changedValue")));
assertFalse(BeanAnnotationProcessor.isAnnotationProperty(Person.class.getDeclaredField("firstName")));
}

@Test
void getAttributeIdentifierExpandsCompactedIriBasedOnNamespaceDeclaration() throws Exception {
assertEquals(SKOS.PREF_LABEL, BeanAnnotationProcessor
.getAttributeIdentifier(ClassWithNamespaces.class.getDeclaredField("prefLabel")));
}

@Test
void getAttributeIdentifierExpandsCompactedIriBasedOnPackageLevelNamespaceDeclaration() throws Exception {
assertEquals(RDFS.RANGE,
BeanAnnotationProcessor.getAttributeIdentifier(ClassWithNamespaces.class.getDeclaredField("range")));
}
}
26 changes: 26 additions & 0 deletions src/test/java/cz/cvut/kbss/jsonld/common/IdentifierUtilTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package cz.cvut.kbss.jsonld.common;

import cz.cvut.kbss.jsonld.environment.Generator;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

class IdentifierUtilTest {

@Test
void isCompactIriReturnsFalseForExpandedAbsoluteIri() {
assertFalse(IdentifierUtil.isCompactIri(Generator.generateUri().toString()));
}

@Test
void isCompactIriReturnsFalseForBlankNodeIdentifier() {
assertFalse(IdentifierUtil.isCompactIri(IdentifierUtil.generateBlankNodeId()));
}

@Test
void isCompactIriReturnsTrueForCompactIri() {
final String compact = "dc:description";
assertTrue(IdentifierUtil.isCompactIri(compact));
}
}
Loading

0 comments on commit 092f1a0

Please sign in to comment.