From e4e81358bfc84f96450be224e59aecf69310f7bb Mon Sep 17 00:00:00 2001 From: pierrerene Date: Thu, 17 Apr 2025 12:09:34 +0200 Subject: [PATCH 1/8] adding blank node to corese new api --- .../next/api/base/model/AbstractBNode.java | 57 ++++++++++++++++++ .../next/impl/temp/literal/CoreseBNode.java | 60 +++++++++++++++++++ .../impl/temp/literal/CoreseBNodeTest.java | 42 +++++++++++++ 3 files changed, 159 insertions(+) create mode 100644 src/main/java/fr/inria/corese/core/next/api/base/model/AbstractBNode.java create mode 100644 src/main/java/fr/inria/corese/core/next/impl/temp/literal/CoreseBNode.java create mode 100644 src/test/java/fr/inria/corese/core/next/impl/temp/literal/CoreseBNodeTest.java diff --git a/src/main/java/fr/inria/corese/core/next/api/base/model/AbstractBNode.java b/src/main/java/fr/inria/corese/core/next/api/base/model/AbstractBNode.java new file mode 100644 index 000000000..069fce952 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/api/base/model/AbstractBNode.java @@ -0,0 +1,57 @@ +package fr.inria.corese.core.next.api.base.model; + +import fr.inria.corese.core.next.api.BNode; + +/** + * Abstract implementation of the {@link BNode} interface, providing common functionality for blank node representations. + * A blank node (BNode) https://www.w3.org/TR/rdf12-concepts/#section-blank-nodes + */ +public abstract class AbstractBNode implements BNode { + + /** + * Returns the string value of this blank node, which is its unique identifier. + * This method is an implementation of {@link BNode#stringValue()} and simply returns the result of {@link #getID()}. + * + * @return The string value of the blank node (its unique identifier). + */ + @Override + public String stringValue() { + return getID(); + } + + /** + * Checks whether this blank node is equal to another object. + * Two blank nodes are considered equal if they are the same object in memory or if they have the same unique identifier (ID). + * This method is an implementation of {@link BNode#equals(Object)}. + * + * @param o The object to compare this blank node to. + * @return {@code true} if the two blank nodes are the same object or have the same unique identifier; {@code false} otherwise. + */ + @Override + public boolean equals(Object o) { + return this == o || o instanceof BNode + && getID().equals(((BNode) o).getID()); + } + + /** + * Returns the hash code for this blank node. The hash code is based on the unique identifier of the blank node, + * using the hash code of the ID returned by {@link #getID()}. + * This method is an implementation of {@link BNode#hashCode()}. + * + * @return The hash code for this blank node. + */ + @Override + public int hashCode() { + return getID().hashCode(); + } + + /** + * Returns a string representation of this blank node in the form "_:{ID}" where {ID} is the unique identifier of the blank node. + * + * @return A string representing this blank node, prefixed with "_:" and followed by its unique identifier. + */ + @Override + public String toString() { + return "_:" + getID(); + } +} \ No newline at end of file diff --git a/src/main/java/fr/inria/corese/core/next/impl/temp/literal/CoreseBNode.java b/src/main/java/fr/inria/corese/core/next/impl/temp/literal/CoreseBNode.java new file mode 100644 index 000000000..bbadd0b57 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/impl/temp/literal/CoreseBNode.java @@ -0,0 +1,60 @@ +package fr.inria.corese.core.next.impl.temp.literal; + +import fr.inria.corese.core.next.api.base.model.AbstractBNode; +import fr.inria.corese.core.next.impl.exception.IncorrectOperationException; +import fr.inria.corese.core.sparql.api.IDatatype; + +/** + * An implementation of a blank node (BNode) used by Corese. + * A blank node (BNode) represents an unnamed node in an RDF graph, typically used to represent resources that do not have a globally unique identifier (IRI). + * This class extends {@link AbstractBNode} and provides constructors for creating a BNode either from a Corese blank node object or a given string identifier. + */ +public class CoreseBNode extends AbstractBNode { + /** + * The Corese object representing the blank node in the old API. + */ + private final fr.inria.corese.core.sparql.datatype.CoreseBlankNode coreseObject; + + /** + * The unique identifier (ID) for the blank node. + */ + private String id; + + /** + * Constructs a {@link CoreseBNode} instance from an {@link IDatatype} Corese object. + * The Corese object should be an instance of {@link fr.inria.corese.core.sparql.datatype.CoreseBlankNode}. + * + * @param coreseObject The {@link IDatatype} Corese object representing the blank node. + * @throws IncorrectOperationException If the provided {@link IDatatype} is not a valid {@link fr.inria.corese.core.sparql.datatype.CoreseBlankNode}. + */ + public CoreseBNode(IDatatype coreseObject) { + if (coreseObject instanceof fr.inria.corese.core.sparql.datatype.CoreseBlankNode) { + this.coreseObject = ( fr.inria.corese.core.sparql.datatype.CoreseBlankNode) coreseObject; + this.id = this.coreseObject.getID(); + } + else { + throw new IncorrectOperationException("Cannot create CoreseLiteral from a non-literal Corese object"); + } + } + + /** + * Constructs a {@link CoreseBNode} instance from a string identifier. + * This constructor creates a {@link fr.inria.corese.core.sparql.datatype.CoreseBlankNode} from the provided string id. + * + * @param id The unique identifier for the blank node. + */ + public CoreseBNode(String id) { + this(new fr.inria.corese.core.sparql.datatype.CoreseBlankNode(id)); + this.id = id; + } + + /** + * Returns the unique identifier of the blank node. + * + * @return The ID of the blank node. + */ + @Override + public String getID() { + return id; + } +} diff --git a/src/test/java/fr/inria/corese/core/next/impl/temp/literal/CoreseBNodeTest.java b/src/test/java/fr/inria/corese/core/next/impl/temp/literal/CoreseBNodeTest.java new file mode 100644 index 000000000..0893faba0 --- /dev/null +++ b/src/test/java/fr/inria/corese/core/next/impl/temp/literal/CoreseBNodeTest.java @@ -0,0 +1,42 @@ +package fr.inria.corese.core.next.impl.temp.literal; + +import static org.junit.Assert.*; +import org.junit.Before; +import org.junit.Test; +import fr.inria.corese.core.sparql.datatype.CoreseBlankNode; + +public class CoreseBNodeTest { + + private static final String BNODE_ID = "bnodeCorese123"; + private static final CoreseBlankNode coreseBlankNode = new CoreseBlankNode(BNODE_ID); + + private CoreseBNode coreseBNodeFromCoreseObject; + private CoreseBNode coreseBNodeFromStringId; + + @Before + public void setUp() { + coreseBNodeFromCoreseObject = new CoreseBNode(coreseBlankNode); + coreseBNodeFromStringId = new CoreseBNode(BNODE_ID); + } + + @Test + public void testConstructorFromString() { + // Test creating CoreseBnode for a string ID + assertNotNull(coreseBNodeFromStringId); + assertEquals(BNODE_ID, coreseBNodeFromStringId.getID()); + } + + @Test + public void testConstructorFromCoreseObject() { + // Test creating CoreseBnode for a CoreseBlankNode (old API) + assertNotNull(coreseBNodeFromCoreseObject); + assertEquals(BNODE_ID, coreseBNodeFromCoreseObject.getID()); + } + + @Test + public void testToString() { + // Test the toString method to ensure it outputs the correct representation + String expectedString = "_:" + BNODE_ID; + assertEquals(expectedString, coreseBNodeFromStringId.toString()); + } +} From 012a46f229fa6ec3ba34de4f8cbbfd80fa7e5877 Mon Sep 17 00:00:00 2001 From: pierrerene Date: Fri, 18 Apr 2025 15:27:19 +0200 Subject: [PATCH 2/8] adding blank node factory to ValueFactory --- .../core/next/impl/temp/CoreseAdaptedValueFactory.java | 8 ++++++-- .../corese/core/next/api/model/ValueFactoryTest.java | 9 +++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/main/java/fr/inria/corese/core/next/impl/temp/CoreseAdaptedValueFactory.java b/src/main/java/fr/inria/corese/core/next/impl/temp/CoreseAdaptedValueFactory.java index d08cd8bad..b197e32bf 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/temp/CoreseAdaptedValueFactory.java +++ b/src/main/java/fr/inria/corese/core/next/impl/temp/CoreseAdaptedValueFactory.java @@ -14,11 +14,15 @@ import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalAmount; import java.util.Date; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.atomic.AtomicLong; public class CoreseAdaptedValueFactory implements ValueFactory { private static Logger logger = LoggerFactory.getLogger(CoreseAdaptedValueFactory.class); + private final AtomicLong nodeID = new AtomicLong(ThreadLocalRandom.current().nextLong()); + public CoreseAdaptedValueFactory() { } @@ -34,12 +38,12 @@ public IRI createIRI(String namespace, String localName) { @Override public BNode createBNode() { - return null; + return new CoreseBNode(Long.toHexString(Math.abs(nodeID.getAndIncrement()))); } @Override public BNode createBNode(String nodeID) { - return null; + return new CoreseBNode(nodeID); } @Override diff --git a/src/test/java/fr/inria/corese/core/next/api/model/ValueFactoryTest.java b/src/test/java/fr/inria/corese/core/next/api/model/ValueFactoryTest.java index 9cd84e924..4a903dbd6 100644 --- a/src/test/java/fr/inria/corese/core/next/api/model/ValueFactoryTest.java +++ b/src/test/java/fr/inria/corese/core/next/api/model/ValueFactoryTest.java @@ -1,5 +1,6 @@ package fr.inria.corese.core.next.api.model; +import fr.inria.corese.core.next.api.BNode; import fr.inria.corese.core.next.api.Literal; import fr.inria.corese.core.next.api.ValueFactory; import fr.inria.corese.core.next.impl.exception.IncorrectFormatException; @@ -40,6 +41,14 @@ public void testCreateIRI() { @Test public void testCreateBNode() { + String nodeId = "corese123"; + BNode nodesCorese123 = this.valueFactory.createBNode(nodeId); + BNode nodesCoreseRandom = this.valueFactory.createBNode(); + + assertNotNull(nodesCorese123); + assertNotNull(nodesCoreseRandom); + assertNotNull(nodesCoreseRandom.getID()); + assertEquals(nodesCorese123.getID(), "corese123"); } @Test From 392294125121a37cc723ab3b765961ae04fedf4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20C=C3=A9r=C3=A8s?= Date: Fri, 16 May 2025 17:52:04 +0200 Subject: [PATCH 3/8] Implement RDF Model - Added CoreseTyped class for handling typed literals, extending AbstractStringLiteral. - Introduced AbstractBNode class for blank node representation in RDF graphs. - Created NamespaceAware interface for models supporting RDF namespaces. - Developed AbstractNamespace class as a base implementation for namespaces. - Implemented ReadOnlyModel class to provide a read-only view of RDF models. - Added CoreseModel class to manage RDF operations backed by Corese Graph. - Introduced CoreseValueConverter for converting between Corese Nodes and RDF4J Values. - Created EmptyModel class to block access to all statements, allowing only namespace operations. - Developed FilteredModel class to provide filtered views over RDF models based on subject, predicate, object, and context. --- .../corese/core/next/api/AbstractBNode.java | 62 ++ .../fr/inria/corese/core/next/api/Model.java | 119 ++-- .../corese/core/next/api/NamespaceAware.java | 29 + .../next/api/base/model/AbstractModel.java | 617 ++++++++++++++++-- .../api/base/model/AbstractNamespace.java | 69 ++ .../next/api/base/model/ReadOnlyModel.java | 131 ++++ .../next/impl/inmemory/InMemoryModel.java | 69 +- .../core/next/impl/temp/CoreseConverter.java | 110 ---- .../core/next/impl/temp/CoreseModel.java | 397 +++++++++++ .../core/next/impl/temp/CoreseStatement.java | 46 +- .../next/impl/temp/CoreseValueConverter.java | 141 ++++ .../core/next/impl/temp/EmptyModel.java | 103 +++ .../core/next/impl/temp/FilteredModel.java | 319 +++++++++ .../next/impl/temp/literal/CoreseBNode.java | 42 +- .../next/impl/temp/literal/CoreseBoolean.java | 41 +- .../next/impl/temp/literal/CoreseDate.java | 40 +- .../impl/temp/literal/CoreseDatetime.java | 47 +- .../CoreseLanguageTaggedStringLiteral.java | 60 +- .../next/impl/temp/literal/CoreseTyped.java | 69 +- 19 files changed, 2172 insertions(+), 339 deletions(-) create mode 100644 src/main/java/fr/inria/corese/core/next/api/AbstractBNode.java create mode 100644 src/main/java/fr/inria/corese/core/next/api/NamespaceAware.java create mode 100644 src/main/java/fr/inria/corese/core/next/api/base/model/AbstractNamespace.java create mode 100644 src/main/java/fr/inria/corese/core/next/api/base/model/ReadOnlyModel.java delete mode 100644 src/main/java/fr/inria/corese/core/next/impl/temp/CoreseConverter.java create mode 100644 src/main/java/fr/inria/corese/core/next/impl/temp/CoreseModel.java create mode 100644 src/main/java/fr/inria/corese/core/next/impl/temp/CoreseValueConverter.java create mode 100644 src/main/java/fr/inria/corese/core/next/impl/temp/EmptyModel.java create mode 100644 src/main/java/fr/inria/corese/core/next/impl/temp/FilteredModel.java diff --git a/src/main/java/fr/inria/corese/core/next/api/AbstractBNode.java b/src/main/java/fr/inria/corese/core/next/api/AbstractBNode.java new file mode 100644 index 000000000..961ce814a --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/api/AbstractBNode.java @@ -0,0 +1,62 @@ +package fr.inria.corese.core.next.api; + +import java.util.Objects; + +/** + * Abstract base class for blank nodes in an RDF graph. + *

+ * Provides default implementations for {@link BNode#getID()}, + * {@link Object#equals(Object)}, and {@link Object#hashCode()}. + *

+ */ +public abstract class AbstractBNode implements BNode { + + private static final String BLANK_NODE_PREFIX = "_:"; + + /** Internal identifier for the blank node (unique within a model) */ + private final String id; + + /** Serial version UID for serialization */ + private static final long serialVersionUID = 1L; + + /** + * Constructs a blank node with a specific identifier. + *

+ * Warning: This bypasses the automatic ID generation and should only be + * used when restoring an existing RDF graph (e.g. during parsing or tests). + *

+ */ + protected AbstractBNode(String id) { + this.id = Objects.requireNonNull(id, "Blank node ID must not be null"); + } + + @Override + public String getID() { + return id; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof BNode)) + return false; + BNode other = (BNode) o; + return id.equals(other.getID()); + } + + @Override + public int hashCode() { + return id.hashCode(); + } + + @Override + public String stringValue() { + return BLANK_NODE_PREFIX + id; + } + + @Override + public String toString() { + return BLANK_NODE_PREFIX + id; + } +} diff --git a/src/main/java/fr/inria/corese/core/next/api/Model.java b/src/main/java/fr/inria/corese/core/next/api/Model.java index 1b580e006..a4f7d146a 100644 --- a/src/main/java/fr/inria/corese/core/next/api/Model.java +++ b/src/main/java/fr/inria/corese/core/next/api/Model.java @@ -1,28 +1,36 @@ package fr.inria.corese.core.next.api; -import fr.inria.corese.core.next.impl.exception.IncorrectOperationException; - import java.io.Serializable; import java.util.Optional; import java.util.Set; +import fr.inria.corese.core.next.impl.exception.IncorrectOperationException; + /** * This class represents an RDF model, a set of statements. * This is the central class to handle RDF data. - * Statements can have zero or several resources as contexts to represents the different graphs in the model. - * A model also handles a set of namespaces. There can be different prefixes for the same namespace, although it is ill-advised, how it affects serialization is implementation-dependant. There cannot be two namespaces with the same prefix. + * Statements can have zero or several resources as contexts to represent the + * different graphs in the model. + * A model also handles a set of namespaces. There can be different prefixes for + * the same namespace, although it is ill-advised, how it affects serialization + * is implementation-dependant. There cannot be two namespaces with the same + * prefix. + * Statements can have zero or several resources as contexts, representing + * different named graphs within the modela */ -public interface Model extends Set, Serializable { +public interface Model extends Set, Serializable, NamespaceAware { /** - * @return a "read-only" view of this model. "query" operations are possible, such as {@code filter()} on {@code contains()} but modifications will throw {@link IncorrectOperationException}. + * @return a "read-only" view of this model. "query" operations are possible, + * such as {@code filter()} on {@code contains()} but modifications will + * throw {@link IncorrectOperationException}. */ Model unmodifiable(); /** * * @param prefix a prefix for the namespace. It should be unique in the model. - * @param name the IRI of the namespace. It should be a valid IRI. + * @param name the IRI of the namespace. It should be a valid IRI. * @return the new Namespace created with the given prefix and name. * @throws IncorrectOperationException if the Model is unmodifiable. */ @@ -30,6 +38,7 @@ public interface Model extends Set, Serializable { /** * Set the namespace of this model. The prefix should be unique in the model. + * * @param namespace the namespace object to be added. * @throws IncorrectOperationException if the Model is unmodifiable. */ @@ -37,68 +46,100 @@ public interface Model extends Set, Serializable { /** * @param prefix the prefix of the namespace to be removed. - * @return the removed namespace, or an empty Optional if no namespace with the given prefix was found. + * @return the removed namespace, or an empty Optional if no namespace with the + * given prefix was found. * @throws IncorrectOperationException if the Model is unmodifiable. */ Optional removeNamespace(String prefix); /** - * Check if a statement is present in the model. Can be used to query for statement pattern using {@code null} values. - * @param subj a Resource, subject of the statement. Can be {@code null} to match any subject. - * @param pred an IRI, predicate of the statement. Can be {@code null} to match any predicate. - * @param obj a Value, object of the statement. Can be {@code null} to match any object. - * @param contexts any Resource, context of the statement. Optional parameter. Can be {@code null} to match any context. - * @return true if a statement with the associated context is in the model, false otherwise. + * Check if a statement is present in the model. Can be used to query for + * statement pattern using {@code null} values. + * + * @param subj a Resource, subject of the statement. Can be {@code null} to + * match any subject. + * @param pred an IRI, predicate of the statement. Can be {@code null} to + * match any predicate. + * @param obj a Value, object of the statement. Can be {@code null} to + * match any object. + * @param contexts any Resource, context of the statement. Optional parameter. + * Can be {@code null} to match any context. + * @return true if a statement with the associated context is in the model, + * false otherwise. */ boolean contains(Resource subj, IRI pred, Value obj, Resource... contexts); /** - * Add a statement to the model. - * @param subj a Resource, subject of the statement. Cannot be {@code null}. - * @param pred an IRI, predicate of the statement. Cannot be {@code null}. - * @param obj a Value, object of the statement. Cannot be {@code null}. - * @param contexts any Resource, context of the statement. Optional parameter. Can be {@code null}. - * @return true if the statement was added, false if it was already present. - * @throws IncorrectOperationException if the Model is unmodifiable. + * Adds a statement to the model with optional context(s). + * If multiple contexts are provided, the statement is added once per context. + * + * @param subj the subject of the statement (must not be {@code null}) + * @param pred the predicate of the statement (must not be {@code null}) + * @param obj the object of the statement (must not be {@code null}) + * @param contexts optional contexts in which to add the statement; + * may be {@code null} or empty to add to the default graph + * @return {@code true} if the model was modified, {@code false} otherwise + * @throws IncorrectOperationException if the model is unmodifiable + * @throws IllegalArgumentException if {@code subj}, {@code pred}, or + * {@code obj} is {@code null} */ boolean add(Resource subj, IRI pred, Value obj, Resource... contexts); /** - * Remove statements from the model according to their context. If no context is given, all statements are removed, regardless of context. - * @param context a Resource, context of the statement. Optional parameter. Can be {@code null} to match any context. + * Remove statements from the model according to their context. If no context is + * given, all statements are removed, regardless of context. + * + * @param context a Resource, context of the statement. Optional parameter. Can + * be {@code null} to match any context. * @return true if any statement was removed, false if none were present. * @throws IncorrectOperationException if the Model is unmodifiable. */ boolean clear(Resource... context); /** - * Remove a statements from the model. If no context is given, all corresponding statements are removed, regardless of context. - * @param subj a Resource, subject of the statement. Can be {@code null}. - * @param pred an IRI, predicate of the statement. Can be {@code null}. - * @param obj a Value, object of the statement. Can be {@code null}. - * @param contexts any Resource, context of the statement. Optional parameter. Can be {@code null} to match any context. + * Remove statements from the model. If no context is given, all corresponding + * statements are removed, regardless of context. + * + * @param subj a Resource, subject of the statement. Can be {@code null}. + * @param pred an IRI, predicate of the statement. Can be {@code null}. + * @param obj a Value, object of the statement. Can be {@code null}. + * @param contexts any Resource, context of the statement. Optional parameter. + * Can be {@code null} to match any context. * @return true if any statement was removed, false if none were present. * @throws IncorrectOperationException if the Model is unmodifiable. */ boolean remove(Resource subj, IRI pred, Value obj, Resource... contexts); /** - * The returned iterator must throw {@link IncorrectOperationException} if the model is unmodifiable and a modification is attempted. - * @param subj a Resource, subject of the statement. Can be {@code null} to match any subject. - * @param pred a an IRI, predicate of the statement. Can be {@code null} to match any predicate. - * @param obj a Value, object of the statement. Can be {@code null} to match any object. - * @param contexts any Resource, context of the statement. Optional parameter. Can be {@code null} to match any context. + * The returned iterator must throw {@link IncorrectOperationException} if the + * model is unmodifiable and a modification is attempted. + * + * @param subj a Resource, subject of the statement. Can be {@code null} to + * match any subject. + * @param pred a an IRI, predicate of the statement. Can be {@code null} to + * match any predicate. + * @param obj a Value, object of the statement. Can be {@code null} to + * match any object. + * @param contexts any Resource, context of the statement. Optional parameter. + * Can be {@code null} to match any context. * @return an iterator on a selection of statements. */ Iterable getStatements(Resource subj, IRI pred, Value obj, - Resource... contexts); + Resource... contexts); /** - * Filter the model according to the given statement pattern. The filter is inclusive, meaning that if a statement matches the pattern, it will be included in the result. - * @param subj a Resource, subject of the statement. Can be {@code null} to match any subject. - * @param pred an IRI, predicate of the statement. Can be {@code null} to match any predicate. - * @param obj a Value, object of the statement. Can be {@code null} to match any object. - * @param contexts any Resource, context of the statement. Optional parameter. Can be {@code null} to match any context. + * Filter the model according to the given statement pattern. The filter is + * inclusive, meaning that if a statement matches the pattern, it will be + * included in the result. + * + * @param subj a Resource, subject of the statement. Can be {@code null} to + * match any subject. + * @param pred an IRI, predicate of the statement. Can be {@code null} to + * match any predicate. + * @param obj a Value, object of the statement. Can be {@code null} to + * match any object. + * @param contexts any Resource, context of the statement. Optional parameter. + * Can be {@code null} to match any context. * @return a new Model containing all statements matching the given pattern. */ Model filter(Resource subj, IRI pred, Value obj, Resource... contexts); diff --git a/src/main/java/fr/inria/corese/core/next/api/NamespaceAware.java b/src/main/java/fr/inria/corese/core/next/api/NamespaceAware.java new file mode 100644 index 000000000..edc2e8568 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/api/NamespaceAware.java @@ -0,0 +1,29 @@ +package fr.inria.corese.core.next.api; + +import java.util.Optional; +import java.util.Set; + +/** + * An interface for models or stores that support RDF namespaces. + */ +public interface NamespaceAware { + + /** + * Returns the set of namespaces defined in the model. + * + * @return a set of Namespace objects + */ + Set getNamespaces(); + + /** + * Returns the namespace associated with the given prefix, if any. + * + * @param prefix the namespace prefix + * @return an Optional containing the Namespace, or empty if none found + */ + default Optional getNamespace(String prefix) { + return getNamespaces().stream() + .filter(ns -> ns.getPrefix().equals(prefix)) + .findFirst(); + } +} diff --git a/src/main/java/fr/inria/corese/core/next/api/base/model/AbstractModel.java b/src/main/java/fr/inria/corese/core/next/api/base/model/AbstractModel.java index 43a945e15..28a04cbeb 100644 --- a/src/main/java/fr/inria/corese/core/next/api/base/model/AbstractModel.java +++ b/src/main/java/fr/inria/corese/core/next/api/base/model/AbstractModel.java @@ -1,141 +1,640 @@ package fr.inria.corese.core.next.api.base.model; -import fr.inria.corese.core.next.api.*; - +import java.io.Serial; +import java.util.AbstractSet; +import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.NoSuchElementException; import java.util.Optional; import java.util.Set; +import fr.inria.corese.core.next.api.IRI; +import fr.inria.corese.core.next.api.Model; +import fr.inria.corese.core.next.api.Namespace; +import fr.inria.corese.core.next.api.Resource; +import fr.inria.corese.core.next.api.Statement; +import fr.inria.corese.core.next.api.Value; + /** * Abstract class that implements the Model interface. - * This class provides default implementations for the methods in the Model interface, - * throwing UnsupportedOperationException for methods that are not supported. + * This class provides default implementations for the methods in the Model + * interface, */ -public abstract class AbstractModel implements Model { +public abstract class AbstractModel extends AbstractSet implements Model { @Override public Model unmodifiable() { - throw new UnsupportedOperationException("Unmodifiable model not supported"); + return new ReadOnlyModel(this); } @Override public Namespace setNamespace(String prefix, String name) { - throw new UnsupportedOperationException("Setting namespace not supported"); - } + Optional existing = getNamespace(prefix); - @Override - public void setNamespace(Namespace namespace) { - throw new UnsupportedOperationException("Setting namespace not supported"); - } + if (!existing.isPresent() || !existing.get().getName().equals(name)) { + Namespace namespace = new ModelNamespace(prefix, name); + setNamespace(namespace); + return namespace; + } - @Override - public Optional removeNamespace(String prefix) { - throw new UnsupportedOperationException("Removing namespace not supported"); + return existing.get(); } - @Override - public boolean contains(Resource subj, IRI pred, Value obj, Resource... contexts) { - throw new UnsupportedOperationException("Contains operation not supported"); - } + /** + * Internal implementation of the {@link Namespace} interface used by + * {@link AbstractModel}. + *

+ * Represents a simple, immutable namespace binding (prefix → URI). + * Only used when adding a namespace via {@code setNamespace(prefix, uri)}. + *

+ */ + private static class ModelNamespace extends AbstractNamespace { - @Override - public boolean add(Resource subj, IRI pred, Value obj, Resource... contexts) { - throw new UnsupportedOperationException("Add operation not supported"); - } + @Serial + private static final long serialVersionUID = 1L; - @Override - public boolean clear(Resource... context) { - throw new UnsupportedOperationException("Clear operation not supported"); - } + private final String prefix; + private final String namespaceURI; - @Override - public boolean remove(Resource subj, IRI pred, Value obj, Resource... contexts) { - throw new UnsupportedOperationException("Remove operation not supported"); + ModelNamespace(String prefix, String namespaceURI) { + this.prefix = prefix; + this.namespaceURI = namespaceURI; + } + + @Override + public String getPrefix() { + return prefix; + } + + @Override + public String getName() { + return namespaceURI; + } } @Override - public Model filter(Resource subj, IRI pred, Value obj, Resource... contexts) { - throw new UnsupportedOperationException("Filter operation not supported"); + public boolean clear(Resource... context) { + return remove(null, null, null, context); } @Override public Set subjects() { - throw new UnsupportedOperationException("Subjects operation not supported"); + return new ValueSet() { + + @Override + public boolean contains(Object object) { + if (object instanceof Resource) { + return AbstractModel.this.contains((Resource) object, null, null); + } + return false; + } + + @Override + public boolean remove(Object object) { + if (object instanceof Resource) { + return AbstractModel.this.remove((Resource) object, null, null); + } + return false; + } + + @Override + public boolean add(Resource subject) { + return AbstractModel.this.add(subject, null, null); + } + + @Override + protected Resource term(Statement statement) { + return statement.getSubject(); + } + + @Override + protected void removeIteration(Iterator iterator, Resource subject) { + AbstractModel.this.removeTermIteration(iterator, subject, null, null); + } + }; } @Override public Set predicates() { - throw new UnsupportedOperationException("Predicates operation not supported"); + return new ValueSet() { + + @Override + public boolean contains(Object object) { + if (object instanceof IRI) { + return AbstractModel.this.contains(null, (IRI) object, null); + } + return false; + } + + @Override + public boolean remove(Object object) { + if (object instanceof IRI) { + return AbstractModel.this.remove(null, (IRI) object, null); + } + return false; + } + + @Override + public boolean add(IRI predicate) { + return AbstractModel.this.add(null, predicate, null); + } + + @Override + protected IRI term(Statement statement) { + return statement.getPredicate(); + } + + @Override + protected void removeIteration(Iterator iterator, IRI predicate) { + AbstractModel.this.removeTermIteration(iterator, null, predicate, null); + } + }; } @Override public Set objects() { - throw new UnsupportedOperationException("Objects operation not supported"); + return new ValueSet() { + + @Override + public boolean contains(Object object) { + if (object instanceof Value) { + return AbstractModel.this.contains(null, null, (Value) object); + } + return false; + } + + @Override + public boolean remove(Object object) { + if (object instanceof Value) { + return AbstractModel.this.remove(null, null, (Value) object); + } + return false; + } + + @Override + public boolean add(Value value) { + return AbstractModel.this.add(null, null, value); + } + + @Override + protected Value term(Statement statement) { + return statement.getObject(); + } + + @Override + protected void removeIteration(Iterator iterator, Value value) { + AbstractModel.this.removeTermIteration(iterator, null, null, value); + } + }; } @Override - public int size() { - throw new UnsupportedOperationException("Size operation not supported"); + public Set contexts() { + return new ValueSet() { + + @Override + public boolean contains(Object object) { + if (object instanceof Resource || object == null) { + return AbstractModel.this.contains(null, null, null, (Resource) object); + } + return false; + } + + @Override + public boolean remove(Object object) { + if (object instanceof Resource || object == null) { + return AbstractModel.this.remove(null, null, null, (Resource) object); + } + return false; + } + + @Override + public boolean add(Resource context) { + return AbstractModel.this.add(null, null, null, context); + } + + @Override + protected Resource term(Statement statement) { + return statement.getContext(); + } + + @Override + protected void removeIteration(Iterator iterator, Resource context) { + AbstractModel.this.removeTermIteration(iterator, null, null, null, context); + } + }; } - @Override - public boolean isEmpty() { - throw new UnsupportedOperationException("IsEmpty operation not supported"); + private abstract class ValueSet extends AbstractSet { + + private final class ValueSetIterator implements Iterator { + + private final Iterator statementIterator; + private final Set seen = new LinkedHashSet<>(); + + private Statement currentStatement; + private Statement nextStatement; + + private ValueSetIterator(Iterator iterator) { + this.statementIterator = iterator; + } + + @Override + public boolean hasNext() { + if (nextStatement == null) { + nextStatement = findNext(); + } + return nextStatement != null; + } + + @Override + public V next() { + if (nextStatement == null) { + nextStatement = findNext(); + if (nextStatement == null) { + throw new NoSuchElementException(); + } + } + + currentStatement = nextStatement; + nextStatement = null; + + V value = term(currentStatement); + seen.add(value); + return value; + } + + @Override + public void remove() { + if (currentStatement == null) { + throw new IllegalStateException(); + } + + removeIteration(statementIterator, term(currentStatement)); + currentStatement = null; + } + + private Statement findNext() { + while (statementIterator.hasNext()) { + Statement statement = statementIterator.next(); + V value = term(statement); + if (!seen.contains(value)) { + return statement; + } + } + return null; + } + } + + @Override + public Iterator iterator() { + return new ValueSetIterator(AbstractModel.this.iterator()); + } + + @Override + public void clear() { + AbstractModel.this.clear(); + } + + @Override + public boolean isEmpty() { + return AbstractModel.this.isEmpty(); + } + + @Override + public int size() { + Iterator iterator = AbstractModel.this.iterator(); + try { + Set uniqueTerms = new LinkedHashSet<>(); + while (iterator.hasNext()) { + uniqueTerms.add(term(iterator.next())); + } + return uniqueTerms.size(); + } finally { + AbstractModel.this.closeIterator(iterator); + } + } + + @Override + public boolean removeAll(Collection collection) { + boolean modified = false; + + Iterator iterator = collection.iterator(); + try { + while (iterator.hasNext()) { + modified |= remove(iterator.next()); + } + return modified; + } finally { + closeIterator(collection, iterator); + } + } + + @Override + public Object[] toArray() { + Iterator iterator = AbstractModel.this.iterator(); + try { + Set uniqueTerms = new LinkedHashSet<>(); + while (iterator.hasNext()) { + uniqueTerms.add(term(iterator.next())); + } + return uniqueTerms.toArray(); + } finally { + AbstractModel.this.closeIterator(iterator); + } + } + + @Override + public T[] toArray(T[] array) { + Iterator iterator = AbstractModel.this.iterator(); + try { + Set uniqueTerms = new LinkedHashSet<>(); + while (iterator.hasNext()) { + uniqueTerms.add(term(iterator.next())); + } + return uniqueTerms.toArray(array); + } finally { + AbstractModel.this.closeIterator(iterator); + } + } + + @Override + public boolean containsAll(Collection collection) { + Iterator iterator = collection.iterator(); + try { + while (iterator.hasNext()) { + if (!contains(iterator.next())) { + return false; + } + } + return true; + } finally { + closeIterator(collection, iterator); + } + } + + @Override + public boolean addAll(Collection collection) { + boolean modified = false; + + Iterator iterator = collection.iterator(); + try { + while (iterator.hasNext()) { + if (add(iterator.next())) { + modified = true; + } + } + return modified; + } finally { + closeIterator(collection, iterator); + } + } + + @Override + public boolean retainAll(Collection collection) { + Iterator iterator = iterator(); + try { + boolean modified = false; + while (iterator.hasNext()) { + if (!collection.contains(iterator.next())) { + iterator.remove(); + modified = true; + } + } + return modified; + } finally { + closeIterator(iterator); + } + } + + // Must be implemented by subclasses: how to extract a term from a Statement + @Override + public abstract boolean add(V term); + + protected abstract V term(Statement statement); + + protected abstract void removeIteration(Iterator iterator, V term); + + protected void closeIterator(Iterator iterator) { + AbstractModel.this.closeIterator(((ValueSetIterator) iterator).statementIterator); + } + + private void closeIterator(Collection collection, Iterator iterator) { + if (collection instanceof AbstractModel) { + ((AbstractModel) collection).closeIterator(iterator); + } else if (collection instanceof ValueSet) { + ((ValueSet) collection).closeIterator(iterator); + } + } } @Override - public boolean contains(Object o) { - throw new UnsupportedOperationException("Contains operation not supported"); + public boolean isEmpty() { + return !contains(null, null, null); } @Override - public Iterator iterator() { - throw new UnsupportedOperationException("Iterator operation not supported"); + public boolean contains(Object object) { + if (object instanceof Statement) { + Statement statement = (Statement) object; + return contains( + statement.getSubject(), + statement.getPredicate(), + statement.getObject(), + statement.getContext()); + } + + return false; } @Override public Object[] toArray() { - throw new UnsupportedOperationException("ToArray operation not supported"); + Iterator iterator = iterator(); + try { + List collected = new ArrayList<>(size()); + + while (iterator.hasNext()) { + collected.add(iterator.next()); + } + + return collected.toArray(); + } finally { + closeIterator(iterator); + } } @Override - public T[] toArray(T[] a) { - throw new UnsupportedOperationException("ToArray operation not supported"); + public T[] toArray(T[] array) { + Iterator iterator = iterator(); + try { + List tempList = new ArrayList<>(size()); + while (iterator.hasNext()) { + tempList.add(iterator.next()); + } + return tempList.toArray(array); + } finally { + closeIterator(iterator); + } } @Override public boolean add(Statement statement) { - throw new UnsupportedOperationException("Add operation not supported"); + return add( + statement.getSubject(), + statement.getPredicate(), + statement.getObject(), + statement.getContext()); } @Override - public boolean remove(Object o) { - throw new UnsupportedOperationException("Remove operation not supported"); + public boolean remove(Object object) { + if (object instanceof Statement) { + if (isEmpty()) { + return false; + } + + Statement statement = (Statement) object; + return remove( + statement.getSubject(), + statement.getPredicate(), + statement.getObject(), + statement.getContext()); + } + + return false; } @Override - public boolean containsAll(Collection c) { - throw new UnsupportedOperationException("ContainsAll operation not supported"); + public boolean containsAll(Collection collection) { + Iterator iterator = collection.iterator(); + try { + while (iterator.hasNext()) { + if (!contains(iterator.next())) { + return false; + } + } + return true; + } finally { + closeIterator(collection, iterator); + } + } + + @Override + public boolean addAll(Collection collection) { + boolean modified = false; + + Iterator iterator = collection.iterator(); + try { + while (iterator.hasNext()) { + if (add(iterator.next())) { + modified = true; + } + } + + return modified; + } finally { + closeIterator(collection, iterator); + } } @Override - public boolean addAll(Collection c) { - throw new UnsupportedOperationException("AddAll operation not supported"); + public boolean retainAll(Collection collection) { + boolean modified = false; + + Iterator iterator = iterator(); + try { + while (iterator.hasNext()) { + if (!collection.contains(iterator.next())) { + iterator.remove(); + modified = true; + } + } + + return modified; + } finally { + closeIterator(iterator); + } } @Override - public boolean retainAll(Collection c) { - throw new UnsupportedOperationException("RetainAll operation not supported"); + public boolean removeAll(Collection collection) { + boolean modified = false; + + // Iterate over the smaller collection for better performance + if (size() > collection.size()) { + Iterator iterator = collection.iterator(); + try { + while (iterator.hasNext()) { + // Attempt to remove each element from this collection + modified |= remove(iterator.next()); + } + } finally { + closeIterator(collection, iterator); + } + } else { + Iterator iterator = iterator(); + try { + while (iterator.hasNext()) { + Object element = iterator.next(); + // Remove elements present in the input collection + if (collection.contains(element)) { + iterator.remove(); + modified = true; + } + } + } finally { + closeIterator(iterator); + } + } + + return modified; } @Override - public boolean removeAll(Collection c) { - throw new UnsupportedOperationException("RemoveAll operation not supported"); + public Iterable getStatements(Resource subject, IRI predicate, Value object, Resource... contexts) { + return () -> filter(subject, predicate, object, contexts).iterator(); } @Override public void clear() { - throw new UnsupportedOperationException("Clear operation not supported"); + remove(null, null, null); } + + /** + * Releases any resources associated with the given iterator, if applicable. + *

+ * Specifically handles internal iterators used by ValueSet views, + * delegating the cleanup to the underlying statement iterator. + *

+ * + * @param iterator the iterator to release + */ + // todo: Use pattern matching to check if the iterator is of type + // ValueSet.ValueSetIterator + // when Java 17+ is used + protected void closeIterator(Iterator iterator) { + if (iterator instanceof ValueSet.ValueSetIterator) { + ValueSet.ValueSetIterator valueSetIterator = (ValueSet.ValueSetIterator) iterator; + closeIterator(valueSetIterator.statementIterator); + } + } + + public abstract void removeTermIteration(Iterator iter, Resource subj, IRI pred, Value obj, + Resource... contexts); + + /** + * Attempts to delegate iterator cleanup to the appropriate container, + * if the given collection supports it (e.g., AbstractModel or ValueSet). + */ + private void closeIterator(Collection collection, Iterator iterator) { + if (collection instanceof AbstractModel) { + ((AbstractModel) collection).closeIterator(iterator); + } else if (collection instanceof ValueSet) { + ((ValueSet) collection).closeIterator(iterator); + } + } + } diff --git a/src/main/java/fr/inria/corese/core/next/api/base/model/AbstractNamespace.java b/src/main/java/fr/inria/corese/core/next/api/base/model/AbstractNamespace.java new file mode 100644 index 000000000..795c7959b --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/api/base/model/AbstractNamespace.java @@ -0,0 +1,69 @@ +package fr.inria.corese.core.next.api.base.model; + +import java.io.Serial; +import java.util.Comparator; +import java.util.Objects; + +import fr.inria.corese.core.next.api.Namespace; + +/** + * A base implementation of the {@link Namespace} interface. + *

+ * Provides standard implementations for {@code equals}, {@code hashCode}, + * {@code compareTo}, and {@code toString}, + * based on the prefix and URI of the namespace. + *

+ */ +public abstract class AbstractNamespace implements Namespace { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * Comparator that orders namespaces by prefix, then by URI. + * Null values are ordered first. + */ + private static final Comparator ORDERING = Comparator.nullsFirst( + Comparator.comparing(Namespace::getPrefix) + .thenComparing(Namespace::getName)); + + /** + * Compares this namespace to another based on prefix and name. + */ + @Override + public int compareTo(Namespace other) { + return ORDERING.compare(this, other); + } + + /** + * Checks equality based on prefix and name. + */ + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (!(object instanceof Namespace)) { + return false; + } + Namespace ns = (Namespace) object; + return Objects.equals(getPrefix(), ns.getPrefix()) + && Objects.equals(getName(), ns.getName()); + } + + /** + * Computes hash code from prefix and name. + */ + @Override + public int hashCode() { + return Objects.hash(getPrefix(), getName()); + } + + /** + * Returns a readable string representation of the namespace. + */ + @Override + public String toString() { + return getPrefix() + " :: " + getName(); + } +} diff --git a/src/main/java/fr/inria/corese/core/next/api/base/model/ReadOnlyModel.java b/src/main/java/fr/inria/corese/core/next/api/base/model/ReadOnlyModel.java new file mode 100644 index 000000000..f569e85e4 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/api/base/model/ReadOnlyModel.java @@ -0,0 +1,131 @@ +package fr.inria.corese.core.next.api.base.model; + +import java.util.Collections; +import java.util.Iterator; +import java.util.Optional; +import java.util.Set; + +import fr.inria.corese.core.next.api.IRI; +import fr.inria.corese.core.next.api.Model; +import fr.inria.corese.core.next.api.Namespace; +import fr.inria.corese.core.next.api.Resource; +import fr.inria.corese.core.next.api.Statement; +import fr.inria.corese.core.next.api.Value; + +/** + * A read-only wrapper for a {@link Model}. All modification operations throw + * {@link UnsupportedOperationException}. + */ +public class ReadOnlyModel extends AbstractModel { + + private static final long serialVersionUID = 8934829374192038471L; + + // The underlying model to delegate read operations to + private final Model delegate; + + /** + * Constructs a ReadOnlyModel that wraps the given backing model. + * + * @param backingModel the model to wrap + */ + public ReadOnlyModel(Model backingModel) { + this.delegate = backingModel; + } + + /** + * Returns an unmodifiable view of the namespaces in the model. + */ + @Override + public Set getNamespaces() { + return Collections.unmodifiableSet(delegate.getNamespaces()); + } + + /** + * Returns the namespace for the given prefix, if present. + */ + @Override + public Optional getNamespace(String prefix) { + return delegate.getNamespace(prefix); + } + + /** + * Not supported. Throws UnsupportedOperationException. + */ + @Override + public Namespace setNamespace(String prefix, String name) { + throw new UnsupportedOperationException("Modifications are not supported in ReadOnlyModel"); + } + + /** + * Not supported. Throws UnsupportedOperationException. + */ + @Override + public void setNamespace(Namespace namespace) { + throw new UnsupportedOperationException("Modifications are not supported in ReadOnlyModel"); + } + + /** + * Not supported. Throws UnsupportedOperationException. + */ + @Override + public Optional removeNamespace(String prefix) { + throw new UnsupportedOperationException("Modifications are not supported in ReadOnlyModel"); + } + + /** + * Checks if the model contains a statement matching the given pattern. + */ + @Override + public boolean contains(Resource subject, IRI predicate, Value object, Resource... contexts) { + return delegate.contains(subject, predicate, object, contexts); + } + + /** + * Not supported. Throws UnsupportedOperationException. + */ + @Override + public boolean add(Resource subject, IRI predicate, Value object, Resource... contexts) { + throw new UnsupportedOperationException("Modifications are not supported in ReadOnlyModel"); + } + + /** + * Not supported. Throws UnsupportedOperationException. + */ + @Override + public boolean remove(Resource subject, IRI predicate, Value object, Resource... contexts) { + throw new UnsupportedOperationException("Modifications are not supported in ReadOnlyModel"); + } + + /** + * Returns an unmodifiable filtered view of the model. + */ + @Override + public Model filter(Resource subject, IRI predicate, Value object, Resource... contexts) { + return delegate.filter(subject, predicate, object, contexts).unmodifiable(); + } + + /** + * Returns an unmodifiable iterator over the statements in the model. + */ + @Override + public Iterator iterator() { + return Collections.unmodifiableSet(delegate).iterator(); + } + + /** + * Returns the number of statements in the model. + */ + @Override + public int size() { + return delegate.size(); + } + + /** + * Not supported. Throws UnsupportedOperationException. + */ + @Override + public void removeTermIteration(Iterator iter, Resource subject, IRI predicate, Value object, + Resource... contexts) { + throw new UnsupportedOperationException("Modifications are not supported in ReadOnlyModel"); + } +} diff --git a/src/main/java/fr/inria/corese/core/next/impl/inmemory/InMemoryModel.java b/src/main/java/fr/inria/corese/core/next/impl/inmemory/InMemoryModel.java index 70548f144..c98fe16b8 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/inmemory/InMemoryModel.java +++ b/src/main/java/fr/inria/corese/core/next/impl/inmemory/InMemoryModel.java @@ -1,13 +1,17 @@ package fr.inria.corese.core.next.impl.inmemory; +import java.util.Iterator; +import java.util.Optional; +import java.util.Set; + import fr.inria.corese.core.next.api.IRI; +import fr.inria.corese.core.next.api.Model; +import fr.inria.corese.core.next.api.Namespace; import fr.inria.corese.core.next.api.Resource; import fr.inria.corese.core.next.api.Statement; import fr.inria.corese.core.next.api.Value; import fr.inria.corese.core.next.api.base.model.AbstractModel; -import java.util.Set; - /** * InMemoryModel is a model that stores RDF data in memory. * DRAFT @@ -25,4 +29,65 @@ public Iterable getStatements(Resource subj, IRI pred, Value obj, Res public Set contexts() { return null; } + + @Override + public boolean contains(Resource subj, IRI pred, Value obj, Resource... contexts) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'contains'"); + } + + @Override + public boolean add(Resource subj, IRI pred, Value obj, Resource... contexts) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'add'"); + } + + @Override + public boolean remove(Resource subj, IRI pred, Value obj, Resource... contexts) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'remove'"); + } + + @Override + public Set getNamespaces() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getNamespaces'"); + } + + @Override + public void setNamespace(Namespace namespace) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'setNamespace'"); + } + + @Override + public Optional removeNamespace(String prefix) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'removeNamespace'"); + } + + @Override + public Model filter(Resource subj, IRI pred, Value obj, Resource... contexts) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'filter'"); + } + + @Override + public Iterator iterator() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'iterator'"); + } + + @Override + public int size() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'size'"); + } + + @Override + public void removeTermIteration(Iterator iter, Resource subj, IRI pred, Value obj, + Resource... contexts) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'removeTermIteration'"); + } } diff --git a/src/main/java/fr/inria/corese/core/next/impl/temp/CoreseConverter.java b/src/main/java/fr/inria/corese/core/next/impl/temp/CoreseConverter.java deleted file mode 100644 index 0c02db726..000000000 --- a/src/main/java/fr/inria/corese/core/next/impl/temp/CoreseConverter.java +++ /dev/null @@ -1,110 +0,0 @@ -package fr.inria.corese.core.next.impl.temp; - -import fr.inria.corese.core.kgram.api.core.Node; -import fr.inria.corese.core.next.api.IRI; -import fr.inria.corese.core.next.api.Literal; -import fr.inria.corese.core.next.api.Resource; -import fr.inria.corese.core.next.api.Value; -import fr.inria.corese.core.next.impl.exception.InternalException; -import fr.inria.corese.core.next.impl.temp.literal.*; -import fr.inria.corese.core.sparql.api.IDatatype; - -/** - * A utility class for converting between Corese-specific data types and next Corese API representations. - * This class provides methods to convert Corese nodes to next Corese API values and vice versa. - * - *

The class includes two main methods: - *

    - *
  • {@link #coreseNodeToRdf4jValue(Node)}: Converts a Corese {@link Node} to a next Corese API {@link Value}.
  • - *
  • {@link #convert(Value)}: Converts a next Corese API {@link Value} to a corresponding Corese {@link Node}.
  • - *
- *

- * - *

Additionally, the {@link #convert(IDatatype)} method handles conversions from various Corese data types - * (such as CoreseBoolean, CoreseDate, CoreseInteger, and CoreseLiteral) to RDF4J literals.

- */ -public class CoreseConverter { - - static CoreseAdaptedValueFactory factory = new CoreseAdaptedValueFactory(); - - /** - * Converts a Corese {@link Node} to an next API {@link Value}. - * This method extracts the Corese datatype value and returns the corresponding next API value. - * - * @param corese_node the Corese {@link Node} to be converted (can be null) - * @return the corresponding next API {@link Value} (can be null if input is null) - */ - public static Value coreseNodeToRdf4jValue(Node corese_node) { - if (corese_node == null) { - return null; - } - return convert(corese_node.getDatatypeValue()); - } - - /** - * Converts a Corese {@link IDatatype} to a next API {@link Value}. - * - * @param oldCoreseDatatype the Corese {@link IDatatype} to be converted - * @return the corresponding next API {@link Value} - * @throws InternalException if the provided datatype is not recognized - */ - public static Value convert(IDatatype oldCoreseDatatype) { - if (oldCoreseDatatype.isURI()) { - return new CoreseIRI(oldCoreseDatatype.stringValue()); - } - else { - String datatypeStringURI = oldCoreseDatatype.getDatatypeURI(); - IRI datatypeStringIRI = factory.createIRI(datatypeStringURI); - return factory.createLiteral(oldCoreseDatatype.getLabel(), datatypeStringIRI); - } - } - - /** - * Converts a next API {@link Value} to a corresponding Corese {@link Node}. - * - * @param value the next API {@link Value} to be converted - * @return the corresponding Corese {@link Node} - * @throws InternalException if the provided value is not recognized - */ - public static Node convert(Value value) { - - if (value.isIRI()) { - CoreseIRI coreseIRI = new CoreseIRI(value.stringValue()); - return coreseIRI.getCoreseNode(); - } else if (value.isLiteral()) { - Literal literal = (Literal) value; - - if (literal instanceof AbstractCoreseNumber) { - return ((AbstractCoreseNumber) literal).getCoreseNode(); - } else if (literal instanceof CoreseBoolean) { - return ((CoreseBoolean) literal).getCoreseNode(); - } else if (literal instanceof CoreseDate) { - return ((CoreseDate) literal).getCoreseNode(); - } else if (literal instanceof CoreseDatetime) { - return ((CoreseDatetime) literal).getCoreseNode(); - } else if (literal instanceof CoreseDecimal) { - return ((CoreseDecimal) literal).getCoreseNode(); - } else if (literal instanceof CoreseInteger) { - return ((CoreseInteger) literal).getCoreseNode(); - } else if (literal instanceof CoreseDuration) { - return ((CoreseDuration) literal).getCoreseNode(); - } else if (literal instanceof CoreseLanguageTaggedStringLiteral) { - return ((CoreseLanguageTaggedStringLiteral) literal).getCoreseNode(); - } else if (literal instanceof CoreseTime) { - return ((CoreseTime) literal).getCoreseNode(); - } else { - CoreseTyped coreseTyped = new CoreseTyped(value.stringValue()); - return coreseTyped.getCoreseNode(); - } - } else if (value.isResource()) { - Resource resource = (Resource) value; - if (resource instanceof CoreseIRI) { - return ((CoreseIRI) resource).getCoreseNode(); - } else { - throw new InternalException("Unexpected value: " + resource); - } - } else { - throw new InternalException("Unexpected value: " + value); - } - } -} \ No newline at end of file diff --git a/src/main/java/fr/inria/corese/core/next/impl/temp/CoreseModel.java b/src/main/java/fr/inria/corese/core/next/impl/temp/CoreseModel.java new file mode 100644 index 000000000..e91c7c009 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/impl/temp/CoreseModel.java @@ -0,0 +1,397 @@ +package fr.inria.corese.core.next.impl.temp; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +import fr.inria.corese.core.EdgeFactory; +import fr.inria.corese.core.Graph; +import fr.inria.corese.core.kgram.api.core.Edge; +import fr.inria.corese.core.kgram.api.core.Node; +import fr.inria.corese.core.next.api.IRI; +import fr.inria.corese.core.next.api.Model; +import fr.inria.corese.core.next.api.Namespace; +import fr.inria.corese.core.next.api.Resource; +import fr.inria.corese.core.next.api.Statement; +import fr.inria.corese.core.next.api.Value; +import fr.inria.corese.core.next.api.base.model.AbstractModel; + +/** + * CoreseModel provides an implementation of the RDF Model interface + * backed by a Corese Graph instance. It supports basic RDF operations + * such as add, remove, contains, filtering, and namespace management. + */ +public class CoreseModel extends AbstractModel { + + // --- Fields --- + + /** The underlying Corese graph. */ + private final Graph coreseGraph; + + /** Utility for converting RDF4J-like Values into Corese Nodes. */ + private final CoreseValueConverter converter; + + /** A set of RDF namespaces associated with this model. */ + private final Set namespaces; + + // --- Constructors --- + + /** + * Constructs a new CoreseModel with an empty Corese Graph + * and an empty set of namespaces. + */ + public CoreseModel() { + this(Graph.create(), new HashSet<>()); + } + + /** + * Constructs a CoreseModel from an existing RDF4J-style model. + * Statements are copied into a new Corese graph. + * + * @param model the source model to import statements from + */ + public CoreseModel(Model model) { + this(); + addAll(model); + } + + /** + * Constructs a CoreseModel by copying the given collection of statements + * into a new Corese graph. + * + * @param statements the collection of statements to import + */ + public CoreseModel(Collection statements) { + this(); + addAll(statements); + } + + /** + * Constructs a CoreseModel with a predefined set of namespaces. + * + * @param namespaces the set of namespaces to associate with this model + */ + public CoreseModel(Set namespaces) { + this(Graph.create(), new HashSet<>(Objects.requireNonNull(namespaces))); + } + + /** + * Constructs a CoreseModel with a predefined set of namespaces + * and initial statements to add to the underlying graph. + * + * @param namespaces the set of namespaces to associate + * @param statements the collection of statements to import + */ + public CoreseModel(Set namespaces, Collection statements) { + this(namespaces); + addAll(statements); + } + + /** + * Constructs a CoreseModel using an existing Corese graph + * and an empty set of namespaces. + * + * @param graph the Corese graph to wrap (must not be null) + */ + public CoreseModel(Graph graph) { + this(graph, new HashSet<>()); + } + + /** + * Constructs a CoreseModel using the given Corese graph and set of namespaces. + * + * @param graph the Corese graph to wrap (must not be null) + * @param namespaces the set of namespaces to associate (null means empty) + */ + public CoreseModel(Graph graph, Set namespaces) { + this.coreseGraph = Objects.requireNonNull(graph, "Graph must not be null"); + this.converter = new CoreseValueConverter(); + this.namespaces = namespaces != null ? namespaces : new HashSet<>(); + } + + // --- Public Methods --- + + // --- Add functions --- + + @Override + public boolean add(Resource subject, IRI predicate, Value object, Resource... contexts) { + Objects.requireNonNull(subject, "Subject must not be null"); + Objects.requireNonNull(predicate, "Predicate must not be null"); + Objects.requireNonNull(object, "Object must not be null"); + + Node coreseSubj = converter.toCoreseNode(subject); + Node coresePred = converter.toCoreseNode(predicate); + Node coreseObj = converter.toCoreseNode(object); + Node[] coreseContexts = converter.toCoreseContextArray(contexts); + + // Handle the case where no context is provided + if (contexts.length == 0) { + return this.addEdgeToGraph(coreseSubj, coresePred, coreseObj, null); + } + + // Handle one or more contexts is provided + boolean changed = false; + for (Node coreseContext : coreseContexts) { + if (coreseContext != null) { + changed |= this.addEdgeToGraph(coreseSubj, coresePred, coreseObj, coreseContext); + } + } + return changed; + } + + // --- Contains functions --- + + @Override + public boolean contains(Resource subject, IRI predicate, Value object, Resource... contexts) { + + Node subjectNode = converter.toCoreseNode(subject); + Node predicateNode = converter.toCoreseNode(predicate); + Node objectNode = converter.toCoreseNode(object); + Node[] contextNodes = converter.toCoreseContextArray(contexts); + + Iterator it = selectEdgesFromCorese(subjectNode, predicateNode, objectNode, contextNodes).iterator(); + return it.hasNext() && it.next() != null; + } + + // --- Remove functions --- + + @Override + public boolean remove(Resource subject, IRI predicate, Value object, Resource... contexts) { + + Node subjectNode = converter.toCoreseNode(subject); + Node predicateNode = converter.toCoreseNode(predicate); + Node objectNode = converter.toCoreseNode(object); + Node[] contextNodes = converter.toCoreseContextArray(contexts); + + Iterable edges = selectEdgesFromCorese(subjectNode, predicateNode, objectNode, contextNodes); + boolean removed = false; + + for (Edge edge : edges) { + if (edge != null) { + // delete() returns a list of removed edges (possibly empty) + boolean deleted = !coreseGraph.delete(edge).isEmpty(); + removed |= deleted; + } + } + + return removed; + } + + // --- Namespace functions --- + + @Override + public Set getNamespaces() { + return namespaces; + } + + @Override + public void setNamespace(Namespace namespace) { + Objects.requireNonNull(namespace, "Namespace cannot be null"); + removeNamespace(namespace.getPrefix()); + namespaces.add(namespace); + } + + @Override + public Optional removeNamespace(String prefix) { + return getNamespace(prefix).filter(namespaces::remove); + } + + // --- Filter functions --- + + @Override + public Model filter(Resource subject, IRI predicate, Value object, Resource... contexts) { + return new FilteredModel(this, subject, predicate, object, contexts) { + + @Override + public Iterator iterator() { + return CoreseModel.this.getFilterIterator(subject, predicate, object, contexts); + } + + @Override + protected void removeFilteredTermIteration( + Iterator iterator, + Resource subject, + IRI predicate, + Value object, + Resource... contexts) { + CoreseModel.this.removeTermIteration(iterator, subject, predicate, object, contexts); + } + }; + } + + // --- Iterator functions --- + + @Override + public Iterator iterator() { + return getFilterIterator(null, null, null); + } + + @Override + public void removeTermIteration( + Iterator iterator, + Resource subject, + IRI predicate, + Value object, + Resource... contexts) { + remove(subject, predicate, object, contexts); + } + + // --- Size functions --- + + @Override + public int size() { + return coreseGraph.size(); + } + + // --- Corese graph accessors --- + + /** + * Returns the underlying Corese graph instance. + * + * @return the underlying Corese {@link Graph} + */ + public Graph getCoreseGraph() { + return coreseGraph; + } + + // --- Utility functions --- + + /** + * Returns Corese edges matching the given subject, predicate, object, and + * contexts. + * + * All parameters are Corese nodes (not RDF4J resources). + * Null values are interpreted as wildcards. + * + * @param subject Corese subject node (nullable) + * @param predicate Corese predicate node (nullable) + * @param object Corese object node (nullable) + * @param contexts Corese context nodes (nullable or empty for wildcard) + * @return Iterable of copied Corese edges (never null, possibly empty) + */ + private Iterable selectEdgesFromCorese(Node subject, Node predicate, Node object, Node... contexts) { + coreseGraph.init(); + + Iterable rawEdges = coreseGraph.getEdgesRDF4J(subject, predicate, object, contexts); + + List result = new ArrayList<>(); + EdgeFactory edgeFactory = coreseGraph.getEdgeFactory(); + + for (Edge edge : rawEdges) { + if (edge != null) { + result.add(edgeFactory.copy(edge)); + } + } + + return result; + } + + /** + * Add one statement ({@code subj}, {@code pred}, {@code obj}, {@code context}) + * in the Corese graph. + * + * @param subject Subject of the statement. + * @param predicate Predicate of the statement. + * @param object Object of the statement. + * @param context Context of the statement. + * @return true if the Corese graph was modified, false otherwise. + */ + private boolean addEdgeToGraph(Node subject, Node predicate, Node object, Node context) { + + Node subj = this.coreseGraph.addNode(subject); + Node pred = this.coreseGraph.addProperty(predicate.getLabel()); + Node obj = this.coreseGraph.addNode(object); + + Edge edge; + if (context == null) { + edge = this.coreseGraph.addEdge(subj, pred, obj); + } else { + Node context_node = this.coreseGraph.addGraph(context.getLabel()); + edge = this.coreseGraph.addEdge(context_node, subj, pred, obj); + } + + return edge != null; + } + + /** + * Get a Corese model iterator with Statements that match the specified subject, + * predicate, object and (optionally) context. The subject, predicate and object + * parameters can be null to indicate wildcards. The contexts parameter is a + * wildcard and accepts zero or more values. If no contexts are specified, + * statements will match disregarding their context. If one or more contexts are + * specified, statements with a context matching one of these will match. Note: + * to match statements without an associated context, specify the value null and + * explicitly cast it to type Resource. + * + * @param subject The subject of the statements to match, null to match + * statements with any subject. + * @param predicate The Predicate of the statements to match, null to match + * statements with any predicate. + * @param object The Object of the statements to match, null to match + * statements with any object. + * @param contexts The Contexts of the statements to match. If no contexts are + * specified, statements will match disregarding their context. + * If one or more contexts are specified, statements with a + * context matching one of these will match. + * @return Corese model iterator on Statements that match the specified subject, + * predicate, object and (optionally) context. + */ + private Iterator getFilterIterator(Resource subject, IRI predicate, Value object, Resource... contexts) { + this.coreseGraph.init(); + + /** + * Iterator for the Corese model + */ + class CoreseModelIterator implements Iterator { + + private Iterator iter; + + private Statement last; + + public CoreseModelIterator(Iterator iter) { + this.iter = iter; + } + + @Override + public boolean hasNext() { + return iter.hasNext(); + } + + @Override + public Statement next() { + return last = iter.next(); + } + + @Override + public void remove() { + if (last == null) { + throw new IllegalStateException(); + } + CoreseModel.this.remove(last); + } + } + + // get edges + Node subjectNode = converter.toCoreseNode(subject); + Node predicateNode = converter.toCoreseNode(predicate); + Node objectNode = converter.toCoreseNode(object); + Node[] contextNodes = converter.toCoreseContextArray(contexts); + + Iterable edges = selectEdgesFromCorese(subjectNode, predicateNode, objectNode, contextNodes); + List statements = new ArrayList<>(); + + for (Edge edge : edges) { + if (edge != null) { + statements.add(new CoreseStatement(edge)); + } + } + + return new CoreseModelIterator(statements.iterator()); + } + +} diff --git a/src/main/java/fr/inria/corese/core/next/impl/temp/CoreseStatement.java b/src/main/java/fr/inria/corese/core/next/impl/temp/CoreseStatement.java index e09c99049..14752290b 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/temp/CoreseStatement.java +++ b/src/main/java/fr/inria/corese/core/next/impl/temp/CoreseStatement.java @@ -9,12 +9,15 @@ import fr.inria.corese.core.next.api.base.model.AbstractStatement; /** - * Represents a statement in Corese. A Corese statement consists of a subject, predicate, object, - * and an optional context. This class provides methods to access these components - * + * Represents a statement in Corese. A Corese statement consists of a subject, + * predicate, object, + * and an optional context. This class provides methods to access these + * components */ public class CoreseStatement extends AbstractStatement implements CoreseEdgeAdapter { + private final CoreseValueConverter converter; + private final Edge edge; private final Resource subject; private final IRI predicate; @@ -22,24 +25,27 @@ public class CoreseStatement extends AbstractStatement implements CoreseEdgeAdap private final Resource context; /** - * Constructs a {@link CoreseStatement} from a subject, predicate, object, and context. + * Constructs a {@link CoreseStatement} from a subject, predicate, object, and + * context. * * - * @param subject the subject of the statement (non-null) + * @param subject the subject of the statement (non-null) * @param predicate the predicate of the statement (non-null) - * @param object the object of the statement (non-null) - * @param context the context (or graph) of the statement (can be null) + * @param object the object of the statement (non-null) + * @param context the context (or graph) of the statement (can be null) */ - CoreseStatement(Resource subject, IRI predicate, Value object, Resource context) { + public CoreseStatement(Resource subject, IRI predicate, Value object, Resource context) { + this.converter = new CoreseValueConverter(); + this.subject = subject; this.predicate = predicate; this.object = object; this.context = context; - Node subjectNode = CoreseConverter.convert(subject); - Node predicateNode = CoreseConverter.convert(predicate); - Node objectNode = CoreseConverter.convert(object); - Node contextNode = (context != null) ? CoreseConverter.convert(context): null; + Node subjectNode = converter.toCoreseNode(subject); + Node predicateNode = converter.toCoreseNode(predicate); + Node objectNode = converter.toCoreseNode(object); + Node contextNode = converter.toCoreseContext(context); EdgeImpl edgeImpl = EdgeImpl.create(contextNode, subjectNode, predicateNode, objectNode); this.edge = edgeImpl; @@ -47,20 +53,24 @@ public class CoreseStatement extends AbstractStatement implements CoreseEdgeAdap /** * Constructs a {@link CoreseStatement} from an existing {@link Edge}. - * This constructor extracts the subject, predicate, object, and context from the provided + * This constructor extracts the subject, predicate, object, and context from + * the provided * {@link Edge} and initializes the fields of this statement accordingly. * - * @param edge the existing {@link Edge} object that represents the statement in the V4 Corese API (non-null) + * @param edge the existing {@link Edge} object that represents the statement in + * the V4 Corese API (non-null) */ public CoreseStatement(Edge edge) { + this.converter = new CoreseValueConverter(); + if (edge == null) { throw new IllegalArgumentException("Edge cannot be null"); } - Resource subject_corese = (Resource) CoreseConverter.coreseNodeToRdf4jValue(edge.getSubjectValue()); - IRI predicate_corese = (IRI) CoreseConverter.coreseNodeToRdf4jValue(edge.getPredicateValue()); - Value object_corese = CoreseConverter.coreseNodeToRdf4jValue(edge.getObjectValue()); - Resource context_corese = (Resource) CoreseConverter.coreseNodeToRdf4jValue(edge.getGraph()); + Resource subject_corese = (Resource) converter.toRdf4jValue(edge.getSubjectValue()); + IRI predicate_corese = (IRI) converter.toRdf4jValue(edge.getPredicateValue()); + Value object_corese = converter.toRdf4jValue(edge.getObjectValue()); + Resource context_corese = (Resource) converter.toRdf4jValueContext(edge.getGraph()); this.subject = subject_corese; this.predicate = predicate_corese; diff --git a/src/main/java/fr/inria/corese/core/next/impl/temp/CoreseValueConverter.java b/src/main/java/fr/inria/corese/core/next/impl/temp/CoreseValueConverter.java new file mode 100644 index 000000000..66501ab85 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/impl/temp/CoreseValueConverter.java @@ -0,0 +1,141 @@ +package fr.inria.corese.core.next.impl.temp; + +import fr.inria.corese.core.kgram.api.core.ExpType; +import fr.inria.corese.core.kgram.api.core.Node; +import fr.inria.corese.core.next.api.BNode; +import fr.inria.corese.core.next.api.IRI; +import fr.inria.corese.core.next.api.Literal; +import fr.inria.corese.core.next.api.Resource; +import fr.inria.corese.core.next.api.Value; +import fr.inria.corese.core.next.api.ValueFactory; +import fr.inria.corese.core.sparql.api.IDatatype; +import fr.inria.corese.core.sparql.datatype.DatatypeMap; + +/** + * Utility class for converting between Corese-compatible Node objects + * and other Value representations. + */ +public class CoreseValueConverter { + + // Factory for creating Corese-compatible Value instances + private final ValueFactory factory = new CoreseAdaptedValueFactory(); + + // Constant representing the default Corese graph context + private static final Node DEFAULT_CORESE_CONTEXT = DatatypeMap.createResource(ExpType.DEFAULT_GRAPH); + + // --- Rdf4j to Corese conversion methods --- + + /** + * Converts a generic Value into a Corese Node. + * + * @param value the input Value to convert + * @return the corresponding Corese Node, or null if input is null + * @throws IllegalArgumentException if the Value type is unsupported + */ + public Node toCoreseNode(Value value) { + if (value == null) { + return null; + } + + if (value instanceof CoreseNodeAdapter) { + return ((CoreseNodeAdapter) value).getCoreseNode(); + } + + if (value instanceof IRI) { + IRI iri = (IRI) value; + return ((CoreseNodeAdapter) factory.createIRI(iri.stringValue())).getCoreseNode(); + } + + if (value instanceof BNode) { + BNode bnode = (BNode) value; + return ((CoreseNodeAdapter) factory.createBNode(bnode.getID())).getCoreseNode(); + } + + if (value instanceof Literal) { + Literal literal = (Literal) value; + return literal.getLanguage() + .map(lang -> ((CoreseNodeAdapter) factory.createLiteral(literal.getLabel(), lang)).getCoreseNode()) + .orElseGet( + () -> ((CoreseNodeAdapter) factory.createLiteral(literal.getLabel(), literal.getDatatype())) + .getCoreseNode()); + } + + throw new IllegalArgumentException("Unsupported Value type: " + value.getClass()); + } + + /** + * Converts a Resource (used as context) to a Corese Node. + * + * @param context RDF4J Resource context + * @return Corese Node representing the context + */ + public Node toCoreseContext(Resource context) { + return (context != null) ? toCoreseNode(context) : DEFAULT_CORESE_CONTEXT; + } + + /** + * Converts an array of RDF4J Resource contexts into an array of Corese Nodes. + * + * @param contexts RDF4J contexts array + * @return Corese Node array following RDF4J context conventions + */ + public Node[] toCoreseContextArray(Resource[] contexts) { + if (contexts == null || (contexts.length == 1 && contexts[0] == null)) { + return new Node[] { DEFAULT_CORESE_CONTEXT }; + } + if (contexts.length == 0) { + return new Node[0]; + } + + Node[] result = new Node[contexts.length]; + for (int i = 0; i < contexts.length; i++) { + result[i] = toCoreseContext(contexts[i]); + } + return result; + } + + // --- Corese to Rdf4j conversion methods --- + + /** + * Converts a Corese Node to an RDF4J Value. + * + * @param node Corese Node to convert + * @return RDF4J Value equivalent + */ + public Value toRdf4jValue(Node node) { + if (node == null) { + return null; + } + + IDatatype dt = node.getDatatypeValue(); + + if (dt.isURI()) { + return factory.createIRI(dt.getLabel()); + } + if (dt.isBlank()) { + return factory.createBNode(dt.getLabel()); + } + if (dt.isLiteral()) { + if (dt.getLang() != null) { + return factory.createLiteral(dt.getLabel(), dt.getLang()); + } + if (dt.getDatatypeURI() != null) { + return factory.createLiteral(dt.getLabel(), dt.getDatatypeURI()); + } + return factory.createLiteral(dt.getLabel()); + } + + throw new IllegalArgumentException("Unsupported Node type: " + dt.getClass()); + } + + /** + * Converts a Corese context node back to an RDF4J Resource. + * + * @param node Corese context node + * @return RDF4J Resource or null if it's the default context + */ + public Resource toRdf4jValueContext(Node node) { + return DEFAULT_CORESE_CONTEXT.equals(node) ? null : (Resource) toRdf4jValue(node); + } + +} diff --git a/src/main/java/fr/inria/corese/core/next/impl/temp/EmptyModel.java b/src/main/java/fr/inria/corese/core/next/impl/temp/EmptyModel.java new file mode 100644 index 000000000..568e24f8f --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/impl/temp/EmptyModel.java @@ -0,0 +1,103 @@ +package fr.inria.corese.core.next.impl.temp; + +import java.util.Collections; +import java.util.Iterator; +import java.util.Optional; +import java.util.Set; + +import fr.inria.corese.core.next.api.IRI; +import fr.inria.corese.core.next.api.Model; +import fr.inria.corese.core.next.api.Namespace; +import fr.inria.corese.core.next.api.Resource; +import fr.inria.corese.core.next.api.Statement; +import fr.inria.corese.core.next.api.Value; +import fr.inria.corese.core.next.api.base.model.AbstractModel; + +/** + * A model wrapper that blocks access to all statements, allowing only namespace + * operations. + * Typically used as a view when all statements are filtered out. + */ +public class EmptyModel extends AbstractModel { + + // --- Fields --- + + private static final long serialVersionUID = 3123007631452759092L; + + private final Model model; + private final Set emptySet = Collections.emptySet(); + + // --- Constructors --- + + public EmptyModel(Model model) { + this.model = model; + } + + // --- Public Methods --- + + // -- Namespace operations delegate to the underlying model -- + + @Override + public Optional getNamespace(String prefix) { + return model.getNamespace(prefix); + } + + @Override + public Set getNamespaces() { + return model.getNamespaces(); + } + + @Override + public Namespace setNamespace(String prefix, String name) { + return model.setNamespace(prefix, name); + } + + @Override + public void setNamespace(Namespace namespace) { + model.setNamespace(namespace); + } + + @Override + public Optional removeNamespace(String prefix) { + return model.removeNamespace(prefix); + } + + // -- Statement operations: read-only and always empty -- + + @Override + public Iterator iterator() { + return emptySet.iterator(); + } + + @Override + public int size() { + return 0; + } + + @Override + public boolean add(Resource subject, IRI predicate, Value object, Resource... contexts) { + throw new UnsupportedOperationException( + "Cannot add statement: this model is read-only (all statements are filtered out)"); + } + + @Override + public boolean contains(Resource subject, IRI predicate, Value object, Resource... contexts) { + return false; + } + + @Override + public Model filter(Resource subject, IRI predicate, Value object, Resource... contexts) { + return this; + } + + @Override + public boolean remove(Resource subject, IRI predicate, Value object, Resource... contexts) { + return false; + } + + @Override + public void removeTermIteration(Iterator iter, Resource subject, IRI predicate, Value object, + Resource... contexts) { + // Intentionally does nothing + } +} \ No newline at end of file diff --git a/src/main/java/fr/inria/corese/core/next/impl/temp/FilteredModel.java b/src/main/java/fr/inria/corese/core/next/impl/temp/FilteredModel.java new file mode 100644 index 000000000..94dd309de --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/impl/temp/FilteredModel.java @@ -0,0 +1,319 @@ +package fr.inria.corese.core.next.impl.temp; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +import fr.inria.corese.core.next.api.IRI; +import fr.inria.corese.core.next.api.Model; +import fr.inria.corese.core.next.api.Namespace; +import fr.inria.corese.core.next.api.Resource; +import fr.inria.corese.core.next.api.Statement; +import fr.inria.corese.core.next.api.Value; +import fr.inria.corese.core.next.api.base.model.AbstractModel; + +public abstract class FilteredModel extends AbstractModel { + + // --- Fields --- + + // The wrapped RDF model to filter. + private final Model model; + + // Serialization identifier. + private static final long serialVersionUID = 1L; + + // RDF filter pattern: subject, predicate, object, and contexts. + protected Resource subjectFilter; + protected IRI predicateFilter; + protected Value objectFilter; + protected Resource[] contextFilters; + + // --- Constructors --- + + /** + * Constructs a filtered view over the given model using subject, predicate, + * object, and context filters. + * + * @param model the underlying RDF model to wrap (must not be null) + * @param subjectFilter the subject to match, or null to match any subject + * @param predicateFilter the predicate to match, or null to match any predicate + * @param objectFilter the object to match, or null to match any object + * @param contextFilters the contexts to match; must not be null (can be empty) + * @throws NullPointerException if model or contextFilters is null + */ + protected FilteredModel(AbstractModel model, Resource subjectFilter, IRI predicateFilter, Value objectFilter, + Resource... contextFilters) { + Objects.requireNonNull(model, "Model cannot be null"); + Objects.requireNonNull(contextFilters, "Context filters cannot be null"); + + this.model = model; + this.subjectFilter = subjectFilter; + this.predicateFilter = predicateFilter; + this.objectFilter = objectFilter; + this.contextFilters = contextFilters; + } + + // --- Public Methods --- + + // --- Add functions --- + + @Override + public boolean add(Resource subject, IRI predicate, Value object, Resource... contexts) { + if (subject == null) { + subject = subjectFilter; + } + if (predicate == null) { + predicate = predicateFilter; + } + if (object == null) { + object = objectFilter; + } + if (contexts == null || contexts.length == 0) { + contexts = contextFilters; + } + + if (!matchesStatement(subject, predicate, object, contexts)) { + throw new IllegalArgumentException( + String.format("Cannot add statement (%s %s %s %s): it does not match the current view filters", + subject, predicate, object, Arrays.toString(contexts))); + } + + return model.add(subject, predicate, object, contexts); + } + + // --- Contains functions --- + + @Override + public boolean contains(Resource subject, IRI predicate, Value object, Resource... contexts) { + if (subject == null) { + subject = subjectFilter; + } + if (predicate == null) { + predicate = predicateFilter; + } + if (object == null) { + object = objectFilter; + } + if (contexts != null && contexts.length == 0) { + contexts = contextFilters; + } + + if (!matchesStatement(subject, predicate, object, contexts)) { + return false; + } + + return model.contains(subject, predicate, object, contexts); + } + + // --- Remove functions --- + + @Override + public boolean remove(Resource subject, IRI predicate, Value object, Resource... contexts) { + if (subject == null) { + subject = subjectFilter; + } + if (predicate == null) { + predicate = predicateFilter; + } + if (object == null) { + object = objectFilter; + } + if (contexts != null && contexts.length == 0) { + contexts = contextFilters; + } + + if (!matchesStatement(subject, predicate, object, contexts)) { + return false; + } + + return model.remove(subject, predicate, object, contexts); + } + + // --- Namespace functions --- + + @Override + public Optional getNamespace(String prefix) { + return model.getNamespace(prefix); + } + + @Override + public Set getNamespaces() { + return model.getNamespaces(); + } + + @Override + public Namespace setNamespace(String prefix, String name) { + return model.setNamespace(prefix, name); + } + + @Override + public void setNamespace(Namespace namespace) { + model.setNamespace(namespace); + } + + @Override + public Optional removeNamespace(String prefix) { + return model.removeNamespace(prefix); + } + + // --- Filter functions --- + + @Override + public Model filter(Resource subject, IRI predicate, Value object, Resource... contexts) { + if (subject == null) { + subject = subjectFilter; + } + if (predicate == null) { + predicate = predicateFilter; + } + if (object == null) { + object = objectFilter; + } + if (contexts != null && contexts.length == 0) { + contexts = contextFilters; + } + + if (!matchesStatement(subject, predicate, object, contexts)) { + return new EmptyModel(model); + } + + return model.filter(subject, predicate, object, contexts); + } + + // --- Other functions --- + + @Override + public final void removeTermIteration(Iterator iterator, Resource subject, IRI predicate, Value object, + Resource... contexts) { + if (subject == null) { + subject = subjectFilter; + } + if (predicate == null) { + predicate = predicateFilter; + } + if (object == null) { + object = objectFilter; + } + if (contexts != null && contexts.length == 0) { + contexts = contextFilters; + } + + if (!matchesStatement(subject, predicate, object, contexts)) { + throw new IllegalStateException( + String.format("Cannot remove statement (%s %s %s %s): it does not match the current view filters", + subject, predicate, object, Arrays.toString(contexts))); + } + + removeFilteredTermIteration(iterator, subject, predicate, object, contexts); + } + + /** + * Called when a term is removed from an iterator view that respects statement + * filters. + * + * @param iterator the live iterator (never null) + * @param subject the subject, or null + * @param predicate the predicate, or null + * @param object the object, or null + * @param contexts the contexts, possibly empty + */ + protected abstract void removeFilteredTermIteration( + Iterator iterator, + Resource subject, + IRI predicate, + Value object, + Resource... contexts); + + @Override + public int size() { + Iterator iterator = iterator(); + try { + int count = 0; + while (iterator.hasNext()) { + iterator.next(); + count++; + } + return count; + } finally { + closeIterator(iterator); + } + } + + // --- Private Methods --- + + /** + * Determines whether a statement matches the given subject, predicate, object, + * and context filters. + * + * @param subject The statement subject. + * @param predicate The statement predicate. + * @param object The statement object. + * @param actualContexts The statement contexts. + * @return true if all filters match, false otherwise. + */ + private boolean matchesStatement(Resource subject, IRI predicate, Value object, Resource... actualContexts) { + if (subjectFilter != null && !subjectFilter.equals(subject)) { + return false; + } + if (predicateFilter != null && !predicateFilter.equals(predicate)) { + return false; + } + if (objectFilter != null && !objectFilter.equals(object)) { + return false; + } + if (!matchContexts(actualContexts, contextFilters)) { + return false; + } + return true; + } + + /** + * Checks if all actual contexts match the expected context filters. + * + * @param actualContexts The contexts from the statement being tested. + * @param expectedContexts The filters to match against. + * @return true if all actual contexts match the expected filters. + */ + private boolean matchContexts(Resource[] actualContexts, Resource... expectedContexts) { + Objects.requireNonNull(actualContexts, "actualContexts must not be null"); + + if (actualContexts.length > 0) { + for (Resource ctx : actualContexts) { + if (!matchSingleContext(ctx, expectedContexts)) { + return false; + } + } + } + return true; + } + + /** + * Checks whether a single context matches one of the expected contexts. + * + * @param actualContext A single context from the statement. + * @param expectedContexts The list of allowed contexts. + * @return true if the context matches, false otherwise. + */ + private boolean matchSingleContext(Resource actualContext, Resource... expectedContexts) { + Objects.requireNonNull(expectedContexts, "expectedContexts must not be null"); + + if (expectedContexts.length == 0) { + // No context filter specified, match any context + return true; + } + + for (Resource expected : expectedContexts) { + if (expected == null && actualContext == null) { + return true; + } + if (expected != null && expected.equals(actualContext)) { + return true; + } + } + + return false; + } + +} diff --git a/src/main/java/fr/inria/corese/core/next/impl/temp/literal/CoreseBNode.java b/src/main/java/fr/inria/corese/core/next/impl/temp/literal/CoreseBNode.java index bbadd0b57..bd0cc8f99 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/temp/literal/CoreseBNode.java +++ b/src/main/java/fr/inria/corese/core/next/impl/temp/literal/CoreseBNode.java @@ -1,19 +1,26 @@ package fr.inria.corese.core.next.impl.temp.literal; +import fr.inria.corese.core.kgram.api.core.Node; import fr.inria.corese.core.next.api.base.model.AbstractBNode; import fr.inria.corese.core.next.impl.exception.IncorrectOperationException; +import fr.inria.corese.core.next.impl.temp.CoreseNodeAdapter; import fr.inria.corese.core.sparql.api.IDatatype; +import fr.inria.corese.core.sparql.datatype.CoreseBlankNode; /** * An implementation of a blank node (BNode) used by Corese. - * A blank node (BNode) represents an unnamed node in an RDF graph, typically used to represent resources that do not have a globally unique identifier (IRI). - * This class extends {@link AbstractBNode} and provides constructors for creating a BNode either from a Corese blank node object or a given string identifier. + * A blank node (BNode) represents an unnamed node in an RDF graph, typically + * used to represent resources that do not have a globally unique identifier + * (IRI). + * This class extends {@link AbstractBNode} and provides constructors for + * creating a BNode either from a Corese blank node object or a given string + * identifier. */ -public class CoreseBNode extends AbstractBNode { +public class CoreseBNode extends AbstractBNode implements CoreseNodeAdapter { /** * The Corese object representing the blank node in the old API. */ - private final fr.inria.corese.core.sparql.datatype.CoreseBlankNode coreseObject; + private final CoreseBlankNode coreseObject; /** * The unique identifier (ID) for the blank node. @@ -21,25 +28,31 @@ public class CoreseBNode extends AbstractBNode { private String id; /** - * Constructs a {@link CoreseBNode} instance from an {@link IDatatype} Corese object. - * The Corese object should be an instance of {@link fr.inria.corese.core.sparql.datatype.CoreseBlankNode}. + * Constructs a {@link CoreseBNode} instance from an {@link IDatatype} Corese + * object. + * The Corese object should be an instance of + * {@link fr.inria.corese.core.sparql.datatype.CoreseBlankNode}. * - * @param coreseObject The {@link IDatatype} Corese object representing the blank node. - * @throws IncorrectOperationException If the provided {@link IDatatype} is not a valid {@link fr.inria.corese.core.sparql.datatype.CoreseBlankNode}. + * @param coreseObject The {@link IDatatype} Corese object representing the + * blank node. + * @throws IncorrectOperationException If the provided {@link IDatatype} is not + * a valid + * {@link fr.inria.corese.core.sparql.datatype.CoreseBlankNode}. */ public CoreseBNode(IDatatype coreseObject) { if (coreseObject instanceof fr.inria.corese.core.sparql.datatype.CoreseBlankNode) { - this.coreseObject = ( fr.inria.corese.core.sparql.datatype.CoreseBlankNode) coreseObject; + this.coreseObject = (fr.inria.corese.core.sparql.datatype.CoreseBlankNode) coreseObject; this.id = this.coreseObject.getID(); - } - else { + } else { throw new IncorrectOperationException("Cannot create CoreseLiteral from a non-literal Corese object"); } } /** * Constructs a {@link CoreseBNode} instance from a string identifier. - * This constructor creates a {@link fr.inria.corese.core.sparql.datatype.CoreseBlankNode} from the provided string id. + * This constructor creates a + * {@link fr.inria.corese.core.sparql.datatype.CoreseBlankNode} from the + * provided string id. * * @param id The unique identifier for the blank node. */ @@ -57,4 +70,9 @@ public CoreseBNode(String id) { public String getID() { return id; } + + @Override + public Node getCoreseNode() { + return coreseObject; + } } diff --git a/src/main/java/fr/inria/corese/core/next/impl/temp/literal/CoreseBoolean.java b/src/main/java/fr/inria/corese/core/next/impl/temp/literal/CoreseBoolean.java index 1af2125a0..bd36570e8 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/temp/literal/CoreseBoolean.java +++ b/src/main/java/fr/inria/corese/core/next/impl/temp/literal/CoreseBoolean.java @@ -1,21 +1,23 @@ package fr.inria.corese.core.next.impl.temp.literal; import fr.inria.corese.core.kgram.api.core.Node; -import fr.inria.corese.core.next.impl.common.literal.XSD; import fr.inria.corese.core.next.api.base.model.literal.AbstractLiteral; import fr.inria.corese.core.next.api.literal.CoreDatatype; +import fr.inria.corese.core.next.impl.common.literal.XSD; import fr.inria.corese.core.next.impl.exception.IncorrectOperationException; import fr.inria.corese.core.next.impl.temp.CoreseIRI; import fr.inria.corese.core.sparql.api.IDatatype; /** * An implementation of the {@code xsd:boolean} datatype used by Corese. - * The {@code xsd:boolean} type represents logical boolean values. The valid values for {@code xsd:boolean} + * The {@code xsd:boolean} type represents logical boolean values. The valid + * values for {@code xsd:boolean} * are {@code true}, {@code false}, {@code 0}, and {@code 1}. - * Values that are capitalized (e.g. TRUE) or abbreviated (e.g. T) are not valid. + * Values that are capitalized (e.g. TRUE) or abbreviated (e.g. T) are not + * valid. */ -public class CoreseBoolean extends AbstractLiteral { +public class CoreseBoolean extends AbstractLiteral implements CoreseDatatypeAdapter { /** * The Corese object representing the boolean literal in the old API. @@ -43,11 +45,16 @@ public class CoreseBoolean extends AbstractLiteral { private static final CoreseBoolean FALSE = new CoreseBoolean(false); /** - * Constructs a {@link CoreseBoolean} instance from an {@link IDatatype} Corese object. - * The Corese object should be an instance of {@link fr.inria.corese.core.sparql.datatype.CoreseBoolean}. + * Constructs a {@link CoreseBoolean} instance from an {@link IDatatype} Corese + * object. + * The Corese object should be an instance of + * {@link fr.inria.corese.core.sparql.datatype.CoreseBoolean}. * - * @param coreseObject The {@link IDatatype} Corese object representing the boolean literal. - * @throws IncorrectOperationException If the provided {@link IDatatype} is not a valid {@link fr.inria.corese.core.sparql.datatype.CoreseBoolean}. + * @param coreseObject The {@link IDatatype} Corese object representing the + * boolean literal. + * @throws IncorrectOperationException If the provided {@link IDatatype} is not + * a valid + * {@link fr.inria.corese.core.sparql.datatype.CoreseBoolean}. */ public CoreseBoolean(IDatatype coreseObject) { super(new CoreseIRI(coreseObject.getDatatypeURI())); @@ -74,7 +81,8 @@ public CoreseBoolean(boolean value) { } /** - * Returns the label of this boolean literal, which is either {@code "true"} or {@code "false"}. + * Returns the label of this boolean literal, which is either {@code "true"} or + * {@code "false"}. * * @return The label of the boolean literal. */ @@ -103,16 +111,25 @@ public String stringValue() { } /** - * Returns a {@link CoreseBoolean} instance representing the boolean value {@code true} or {@code false}. + * Returns a {@link CoreseBoolean} instance representing the boolean value + * {@code true} or {@code false}. * - * @param value The boolean value to be returned as a {@link CoreseBoolean} instance. - * @return The {@link CoreseBoolean} instance representing the given boolean value. + * @param value The boolean value to be returned as a {@link CoreseBoolean} + * instance. + * @return The {@link CoreseBoolean} instance representing the given boolean + * value. */ public static CoreseBoolean valueOf(boolean value) { return value ? TRUE : FALSE; } + @Override public Node getCoreseNode() { return this.coreseObject; } + + @Override + public IDatatype getIDatatype() { + return this.coreseObject; + } } \ No newline at end of file diff --git a/src/main/java/fr/inria/corese/core/next/impl/temp/literal/CoreseDate.java b/src/main/java/fr/inria/corese/core/next/impl/temp/literal/CoreseDate.java index 5637331b1..85a221e0d 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/temp/literal/CoreseDate.java +++ b/src/main/java/fr/inria/corese/core/next/impl/temp/literal/CoreseDate.java @@ -1,16 +1,5 @@ package fr.inria.corese.core.next.impl.temp.literal; -import fr.inria.corese.core.kgram.api.core.Node; -import fr.inria.corese.core.next.impl.exception.IncorrectDatatypeException; -import fr.inria.corese.core.next.impl.exception.IncorrectOperationException; -import fr.inria.corese.core.next.api.IRI; -import fr.inria.corese.core.next.api.literal.CoreDatatype; -import fr.inria.corese.core.next.impl.common.literal.XSD; -import fr.inria.corese.core.next.impl.temp.CoreseIRI; -import fr.inria.corese.core.next.api.base.model.literal.AbstractTemporalPointLiteral; -import fr.inria.corese.core.sparql.api.IDatatype; - -import javax.xml.datatype.XMLGregorianCalendar; import java.math.BigDecimal; import java.math.BigInteger; import java.text.SimpleDateFormat; @@ -18,6 +7,18 @@ import java.util.Date; import java.util.Optional; +import javax.xml.datatype.XMLGregorianCalendar; + +import fr.inria.corese.core.kgram.api.core.Node; +import fr.inria.corese.core.next.api.IRI; +import fr.inria.corese.core.next.api.base.model.literal.AbstractTemporalPointLiteral; +import fr.inria.corese.core.next.api.literal.CoreDatatype; +import fr.inria.corese.core.next.impl.common.literal.XSD; +import fr.inria.corese.core.next.impl.exception.IncorrectDatatypeException; +import fr.inria.corese.core.next.impl.exception.IncorrectOperationException; +import fr.inria.corese.core.next.impl.temp.CoreseIRI; +import fr.inria.corese.core.sparql.api.IDatatype; + public class CoreseDate extends AbstractTemporalPointLiteral implements CoreseDatatypeAdapter { private final fr.inria.corese.core.sparql.datatype.CoreseDate coreseObject; @@ -25,7 +26,7 @@ public class CoreseDate extends AbstractTemporalPointLiteral implements CoreseDa /** * Constructor for CoreseDate. * - * @param coreseObject the CoreseDate object + * @param coreseObject the CoreseDate object */ public CoreseDate(IDatatype coreseObject) { super(new CoreseIRI(coreseObject.getDatatypeURI())); @@ -38,6 +39,7 @@ public CoreseDate(IDatatype coreseObject) { /** * Constructor for CoreseDate. + * * @param calendar the XMLGregorianCalendar object */ public CoreseDate(XMLGregorianCalendar calendar) { @@ -46,15 +48,17 @@ public CoreseDate(XMLGregorianCalendar calendar) { /** * Constructor for CoreseDate. + * * @param date the Date object. */ public CoreseDate(Date date) { - this((new SimpleDateFormat("yyyy-MM-dd" )).format(date)); + this((new SimpleDateFormat("yyyy-MM-dd")).format(date)); } /** * Constructor for CoreseDate. - * @param value the string representation of the date + * + * @param value the string representation of the date * @param datatype the datatype of the literal */ public CoreseDate(String value, IRI datatype) { @@ -64,19 +68,21 @@ public CoreseDate(String value, IRI datatype) { /** * Constructor for CoreseDate. - * @param value the string representation of the date - * @param datatype the datatype of the literal + * + * @param value the string representation of the date + * @param datatype the datatype of the literal * @param coreDatatype the CoreDatatype of the literal. Must be XSD.DATE. */ public CoreseDate(String value, IRI datatype, CoreDatatype coreDatatype) { this(value, datatype); - if(coreDatatype != null && coreDatatype != XSD.DATE) { + if (coreDatatype != null && coreDatatype != XSD.DATE) { throw new IncorrectDatatypeException("Cannot create CoreseDate with a non-date CoreDatatype."); } } /** * Constructor for CoreseDate. + * * @param date the string representation of the date */ public CoreseDate(String date) { diff --git a/src/main/java/fr/inria/corese/core/next/impl/temp/literal/CoreseDatetime.java b/src/main/java/fr/inria/corese/core/next/impl/temp/literal/CoreseDatetime.java index abd7ae815..b833e44d9 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/temp/literal/CoreseDatetime.java +++ b/src/main/java/fr/inria/corese/core/next/impl/temp/literal/CoreseDatetime.java @@ -1,24 +1,27 @@ package fr.inria.corese.core.next.impl.temp.literal; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.time.temporal.TemporalAccessor; +import java.util.Optional; + +import javax.xml.datatype.XMLGregorianCalendar; + import fr.inria.corese.core.kgram.api.core.Node; -import fr.inria.corese.core.next.impl.exception.IncorrectDatatypeException; -import fr.inria.corese.core.next.impl.exception.IncorrectOperationException; import fr.inria.corese.core.next.api.IRI; +import fr.inria.corese.core.next.api.base.model.literal.AbstractTemporalPointLiteral; import fr.inria.corese.core.next.api.literal.CoreDatatype; import fr.inria.corese.core.next.impl.common.literal.XSD; +import fr.inria.corese.core.next.impl.exception.IncorrectDatatypeException; +import fr.inria.corese.core.next.impl.exception.IncorrectOperationException; import fr.inria.corese.core.next.impl.temp.CoreseIRI; -import fr.inria.corese.core.next.api.base.model.literal.AbstractTemporalPointLiteral; import fr.inria.corese.core.sparql.api.IDatatype; -import javax.xml.datatype.XMLGregorianCalendar; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.time.temporal.TemporalAccessor; -import java.util.Optional; - /** - * CoreseDatetime class that represents a date and time literal in the Corese framework. - * It extends the AbstractTemporalPointLiteral class and implements the CoreseDatatypeAdapter interface. + * CoreseDatetime class that represents a date and time literal in the Corese + * framework. + * It extends the AbstractTemporalPointLiteral class and implements the + * CoreseDatatypeAdapter interface. */ public class CoreseDatetime extends AbstractTemporalPointLiteral implements CoreseDatatypeAdapter { private final fr.inria.corese.core.sparql.datatype.CoreseDateTime coreseObject; @@ -26,7 +29,7 @@ public class CoreseDatetime extends AbstractTemporalPointLiteral implements Core /** * Constructor for CoreseDatetime. * - * @param coreseObject the CoreseDateTime object + * @param coreseObject the CoreseDateTime object */ public CoreseDatetime(IDatatype coreseObject) { super(new CoreseIRI(coreseObject.getDatatypeURI())); @@ -58,7 +61,7 @@ public CoreseDatetime(String dateXMLDateFormat) { /** * Constructor for CoreseDatetime. * - * @param value the date and time value + * @param value the date and time value * @param datatype the datatype of the literal */ public CoreseDatetime(String value, IRI datatype) { @@ -71,12 +74,15 @@ public CoreseDatetime(String value, IRI datatype) { * * @param value the date and time value * @param datatype the datatype of the literal - * @param coreDatatype the core datatype. Must be xsd:dateTime, xsd:time, or xsd:date. + * @param coreDatatype the core datatype. Must be xsd:dateTime, xsd:time, or + * xsd:date. */ public CoreseDatetime(String value, IRI datatype, CoreDatatype coreDatatype) { this(value, datatype); - if(coreDatatype != null && coreDatatype != XSD.DATETIME && coreDatatype != XSD.TIME && coreDatatype != XSD.DATE) { - throw new IncorrectDatatypeException("Cannot create CoreseDatetime with a core datatype other than xsd:dateTime or xsd:time."); + if (coreDatatype != null && coreDatatype != XSD.DATETIME && coreDatatype != XSD.TIME + && coreDatatype != XSD.DATE) { + throw new IncorrectDatatypeException( + "Cannot create CoreseDatetime with a core datatype other than xsd:dateTime or xsd:time."); } } @@ -96,7 +102,8 @@ public IRI getDatatype() { } /** - * @throws IncorrectOperationException as Datetime cannot be converted to boolean + * @throws IncorrectOperationException as Datetime cannot be converted to + * boolean */ @Override public boolean booleanValue() { @@ -144,7 +151,8 @@ public BigInteger integerValue() { } /** - * @throws IncorrectOperationException as Datetime cannot be converted to BigDecimal + * @throws IncorrectOperationException as Datetime cannot be converted to + * BigDecimal */ @Override public BigDecimal decimalValue() { @@ -152,7 +160,8 @@ public BigDecimal decimalValue() { } /** - * @throws IncorrectOperationException as Datetime cannot be converted to BigInteger + * @throws IncorrectOperationException as Datetime cannot be converted to + * BigInteger */ @Override public float floatValue() { diff --git a/src/main/java/fr/inria/corese/core/next/impl/temp/literal/CoreseLanguageTaggedStringLiteral.java b/src/main/java/fr/inria/corese/core/next/impl/temp/literal/CoreseLanguageTaggedStringLiteral.java index 796236f9b..a1ac7f753 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/temp/literal/CoreseLanguageTaggedStringLiteral.java +++ b/src/main/java/fr/inria/corese/core/next/impl/temp/literal/CoreseLanguageTaggedStringLiteral.java @@ -1,28 +1,29 @@ package fr.inria.corese.core.next.impl.temp.literal; +import java.util.Optional; import fr.inria.corese.core.kgram.api.core.Node; -import fr.inria.corese.core.next.impl.common.literal.RDF; -import fr.inria.corese.core.next.impl.exception.IncorrectOperationException; import fr.inria.corese.core.next.api.IRI; +import fr.inria.corese.core.next.api.base.model.literal.AbstractStringLiteral; import fr.inria.corese.core.next.api.literal.CoreDatatype; +import fr.inria.corese.core.next.impl.common.literal.RDF; +import fr.inria.corese.core.next.impl.exception.IncorrectOperationException; import fr.inria.corese.core.next.impl.temp.CoreseIRI; -import fr.inria.corese.core.next.api.base.model.literal.AbstractStringLiteral; import fr.inria.corese.core.sparql.api.IDatatype; -import java.util.Optional; - - /** * An implementation of a language-tagged string literal used by Corese. - * This class represents a string literal that is associated with a language tag, specifically when the datatype IRI is + * This class represents a string literal that is associated with a language + * tag, specifically when the datatype IRI is * {@code http://www.w3.org/1999/02/22-rdf-syntax-ns#langString}. - * It extends {@link AbstractStringLiteral} and implements {@link CoreseDatatypeAdapter}. + * It extends {@link AbstractStringLiteral} and implements + * {@link CoreseDatatypeAdapter}. */ public class CoreseLanguageTaggedStringLiteral extends AbstractStringLiteral implements CoreseDatatypeAdapter { /** - * The Corese object that holds the literal value and language tag in the old API. + * The Corese object that holds the literal value and language tag in the old + * API. */ private final fr.inria.corese.core.sparql.datatype.CoreseLiteral coreseObject; @@ -36,10 +37,16 @@ public class CoreseLanguageTaggedStringLiteral extends AbstractStringLiteral imp private String value; /** - * Constructs a {@link CoreseLanguageTaggedStringLiteral} instance from an {@link IDatatype} Corese object. - * The Corese object should be an instance of {@link fr.inria.corese.core.sparql.datatype.CoreseString}. - * @param coreseObject The {@link IDatatype} Corese object representing the tagged literal. - * @throws IncorrectOperationException If the provided {@code coreseObject} is not a valid {@link fr.inria.corese.core.sparql.datatype.CoreseLiteral}. + * Constructs a {@link CoreseLanguageTaggedStringLiteral} instance from an + * {@link IDatatype} Corese object. + * The Corese object should be an instance of + * {@link fr.inria.corese.core.sparql.datatype.CoreseString}. + * + * @param coreseObject The {@link IDatatype} Corese object representing the + * tagged literal. + * @throws IncorrectOperationException If the provided {@code coreseObject} is + * not a valid + * {@link fr.inria.corese.core.sparql.datatype.CoreseLiteral}. */ public CoreseLanguageTaggedStringLiteral(IDatatype coreseObject) { super(new CoreseIRI(coreseObject.getDatatypeURI())); @@ -47,17 +54,18 @@ public CoreseLanguageTaggedStringLiteral(IDatatype coreseObject) { this.coreseObject = (fr.inria.corese.core.sparql.datatype.CoreseLiteral) coreseObject; this.language = coreseObject.getLang(); this.value = coreseObject.getLabel(); - } - else { + } else { throw new IncorrectOperationException("Cannot create CoreseLiteral from a non-literal Corese object"); } } /** - * Constructs a {@link CoreseLanguageTaggedStringLiteral} instance with the given value and language tag. - * This constructor creates a {@link CoreseLanguageTaggedStringLiteral} from the provided string value and language tag. + * Constructs a {@link CoreseLanguageTaggedStringLiteral} instance with the + * given value and language tag. + * This constructor creates a {@link CoreseLanguageTaggedStringLiteral} from the + * provided string value and language tag. * - * @param value The value of the language-tagged string literal. + * @param value The value of the language-tagged string literal. * @param language The language tag associated with the literal. */ public CoreseLanguageTaggedStringLiteral(String value, String language) { @@ -67,7 +75,8 @@ public CoreseLanguageTaggedStringLiteral(String value, String language) { } @Override - public void setCoreDatatype(CoreDatatype coreDatatype) {} + public void setCoreDatatype(CoreDatatype coreDatatype) { + } @Override public String getLabel() { @@ -79,12 +88,16 @@ public String getValue() { } /** - * Returns the language tag of the language-tagged literal, wrapped in an {@link Optional}. + * Returns the language tag of the language-tagged literal, wrapped in an + * {@link Optional}. * If no language tag is set, an empty {@link Optional} will be returned. * - * @return An {@link Optional} containing the language tag, or an empty {@link Optional} if no language is set. + * @return An {@link Optional} containing the language tag, or an empty + * {@link Optional} if no language is set. */ - public Optional getLanguage() {return Optional.ofNullable(language);} + public Optional getLanguage() { + return Optional.ofNullable(language); + } @Override public CoreDatatype getCoreDatatype() { @@ -102,7 +115,8 @@ public Node getCoreseNode() { } /** - * Returns the datatype IRI for language-tagged string literals, which is {@code http://www.w3.org/1999/02/22-rdf-syntax-ns#langString}. + * Returns the datatype IRI for language-tagged string literals, which is + * {@code http://www.w3.org/1999/02/22-rdf-syntax-ns#langString}. * * @return The datatype IRI for language-tagged string literals. */ diff --git a/src/main/java/fr/inria/corese/core/next/impl/temp/literal/CoreseTyped.java b/src/main/java/fr/inria/corese/core/next/impl/temp/literal/CoreseTyped.java index 8ee1c8643..5e4cd2eb9 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/temp/literal/CoreseTyped.java +++ b/src/main/java/fr/inria/corese/core/next/impl/temp/literal/CoreseTyped.java @@ -1,21 +1,23 @@ package fr.inria.corese.core.next.impl.temp.literal; +import java.util.Objects; + import fr.inria.corese.core.kgram.api.core.Node; +import fr.inria.corese.core.next.api.IRI; +import fr.inria.corese.core.next.api.base.model.literal.AbstractStringLiteral; +import fr.inria.corese.core.next.api.literal.CoreDatatype; import fr.inria.corese.core.next.impl.common.literal.XSD; import fr.inria.corese.core.next.impl.common.util.literal.CoreDatatypeHelper; import fr.inria.corese.core.next.impl.exception.IncorrectOperationException; -import fr.inria.corese.core.next.api.IRI; -import fr.inria.corese.core.next.api.literal.CoreDatatype; import fr.inria.corese.core.next.impl.temp.CoreseIRI; -import fr.inria.corese.core.next.api.base.model.literal.AbstractStringLiteral; import fr.inria.corese.core.sparql.api.IDatatype; -import java.util.Objects; - /** * An implementation of the {@code xsd:string} datatype used by Corese. - * This class represents a typed literal of string type and can be used with other XSD types as well. - * It extends {@link AbstractStringLiteral} and implements {@link CoreseDatatypeAdapter}. + * This class represents a typed literal of string type and can be used with + * other XSD types as well. + * It extends {@link AbstractStringLiteral} and implements + * {@link CoreseDatatypeAdapter}. */ public class CoreseTyped extends AbstractStringLiteral implements CoreseDatatypeAdapter { @@ -39,25 +41,30 @@ public class CoreseTyped extends AbstractStringLiteral implements CoreseDatatype private IRI dataype; /** - * Constructs a {@link CoreseTyped} instance from an {@link IDatatype} Corese object. - * The Corese object should be an instance of {@link fr.inria.corese.core.sparql.datatype.CoreseString}. - * @param coreseObject The {@link IDatatype} Corese object representing the string literal. - * @throws IncorrectOperationException If the provided {@link IDatatype} is not a {@link fr.inria.corese.core.sparql.datatype.CoreseString}. + * Constructs a {@link CoreseTyped} instance from an {@link IDatatype} Corese + * object. + * The Corese object should be an instance of + * {@link fr.inria.corese.core.sparql.datatype.CoreseString}. + * + * @param coreseObject The {@link IDatatype} Corese object representing the + * string literal. + * @throws IncorrectOperationException If the provided {@link IDatatype} is not + * a + * {@link fr.inria.corese.core.sparql.datatype.CoreseString}. */ public CoreseTyped(IDatatype coreseObject) { super(new CoreseIRI(coreseObject.getDatatypeURI())); if (coreseObject instanceof fr.inria.corese.core.sparql.datatype.CoreseString) { this.coreseObject = (fr.inria.corese.core.sparql.datatype.CoreseString) coreseObject; - } - else { + } else { throw new IncorrectOperationException("Cannot create CoreseString from a non-string Corese object"); } } - /** * Constructs a {@link CoreseTyped} instance from a string value. * The datatype is set to XSD.STRING. + * * @param value The string value for the literal. */ public CoreseTyped(String value) { @@ -67,13 +74,14 @@ public CoreseTyped(String value) { this.value = value; } - /** - * Constructs a {@link CoreseTyped} instance from a string value and a specified datatype IRI. + * Constructs a {@link CoreseTyped} instance from a string value and a specified + * datatype IRI. * If the datatype is {@code null}, the datatype is set to XSD.STRING. - * If the datatype is non-null, the {@link CoreDatatype} is determined from the datatype IRI. + * If the datatype is non-null, the {@link CoreDatatype} is determined from the + * datatype IRI. * - * @param value The string value for the literal. + * @param value The string value for the literal. * @param datatype The datatype IRI for the literal. */ public CoreseTyped(String value, IRI datatype) { @@ -82,18 +90,18 @@ public CoreseTyped(String value, IRI datatype) { if (datatype == null) { this.datatype = XSD.STRING.getIRI(); this.coreDatatype = XSD.STRING; - } - else { + } else { this.datatype = datatype; this.coreDatatype = CoreDatatypeHelper.from(datatype); } } /** - * Constructs a {@link CoreseTyped} instance from a string value and a specified {@link CoreDatatype}. + * Constructs a {@link CoreseTyped} instance from a string value and a specified + * {@link CoreDatatype}. * The datatype IRI is derived from the {@link CoreDatatype}. * - * @param value The string value for the literal. + * @param value The string value for the literal. * @param coreDatatype The core datatype for the literal. */ public CoreseTyped(String value, CoreDatatype coreDatatype) { @@ -105,14 +113,19 @@ public CoreseTyped(String value, CoreDatatype coreDatatype) { } /** - * Constructs a {@link CoreseTyped} instance from a string value, a datatype IRI, and a {@link CoreDatatype}. - * This constructor ensures that the datatype IRI matches the {@link CoreDatatype}. - * If they do not match or if either value is {@code null}, an exception is thrown. + * Constructs a {@link CoreseTyped} instance from a string value, a datatype + * IRI, and a {@link CoreDatatype}. + * This constructor ensures that the datatype IRI matches the + * {@link CoreDatatype}. + * If they do not match or if either value is {@code null}, an exception is + * thrown. * - * @param value The string value for the literal. - * @param datatype The datatype IRI for the literal. + * @param value The string value for the literal. + * @param datatype The datatype IRI for the literal. * @param coreDatatype The core datatype for the literal. - * @throws IncorrectOperationException If the datatype IRI does not match the core datatype's IRI or if either value is {@code null}. + * @throws IncorrectOperationException If the datatype IRI does not match the + * core datatype's IRI or if either value is + * {@code null}. */ public CoreseTyped(String value, IRI datatype, CoreDatatype coreDatatype) { this(new fr.inria.corese.core.sparql.datatype.CoreseString(value)); From 2ef922448833ba43d5a06a496df9b252a7fd67ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20C=C3=A9r=C3=A8s?= Date: Tue, 20 May 2025 15:59:19 +0200 Subject: [PATCH 4/8] Refactor AbstractModel: remove internal ModelNamespace class and move to separate file --- .../next/api/base/model/AbstractModel.java | 34 +----------------- .../core/next/impl/temp/ModelNamespace.java | 35 +++++++++++++++++++ 2 files changed, 36 insertions(+), 33 deletions(-) create mode 100644 src/main/java/fr/inria/corese/core/next/impl/temp/ModelNamespace.java diff --git a/src/main/java/fr/inria/corese/core/next/api/base/model/AbstractModel.java b/src/main/java/fr/inria/corese/core/next/api/base/model/AbstractModel.java index 28a04cbeb..56a74cb0b 100644 --- a/src/main/java/fr/inria/corese/core/next/api/base/model/AbstractModel.java +++ b/src/main/java/fr/inria/corese/core/next/api/base/model/AbstractModel.java @@ -1,6 +1,5 @@ package fr.inria.corese.core.next.api.base.model; -import java.io.Serial; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Collection; @@ -17,6 +16,7 @@ import fr.inria.corese.core.next.api.Resource; import fr.inria.corese.core.next.api.Statement; import fr.inria.corese.core.next.api.Value; +import fr.inria.corese.core.next.impl.temp.ModelNamespace; /** * Abstract class that implements the Model interface. @@ -43,38 +43,6 @@ public Namespace setNamespace(String prefix, String name) { return existing.get(); } - /** - * Internal implementation of the {@link Namespace} interface used by - * {@link AbstractModel}. - *

- * Represents a simple, immutable namespace binding (prefix → URI). - * Only used when adding a namespace via {@code setNamespace(prefix, uri)}. - *

- */ - private static class ModelNamespace extends AbstractNamespace { - - @Serial - private static final long serialVersionUID = 1L; - - private final String prefix; - private final String namespaceURI; - - ModelNamespace(String prefix, String namespaceURI) { - this.prefix = prefix; - this.namespaceURI = namespaceURI; - } - - @Override - public String getPrefix() { - return prefix; - } - - @Override - public String getName() { - return namespaceURI; - } - } - @Override public boolean clear(Resource... context) { return remove(null, null, null, context); diff --git a/src/main/java/fr/inria/corese/core/next/impl/temp/ModelNamespace.java b/src/main/java/fr/inria/corese/core/next/impl/temp/ModelNamespace.java new file mode 100644 index 000000000..a5178316c --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/impl/temp/ModelNamespace.java @@ -0,0 +1,35 @@ +package fr.inria.corese.core.next.impl.temp; + +import java.io.Serial; + +import fr.inria.corese.core.next.api.base.model.AbstractNamespace; + +/* + * A simple implementation of the {@link Namespace} interface. + *

+ * This class represents a namespace with a prefix and a URI. + *

+ */ +public class ModelNamespace extends AbstractNamespace { + + @Serial + private static final long serialVersionUID = 1L; + + private final String prefix; + private final String namespaceURI; + + public ModelNamespace(String prefix, String namespaceURI) { + this.prefix = prefix; + this.namespaceURI = namespaceURI; + } + + @Override + public String getPrefix() { + return prefix; + } + + @Override + public String getName() { + return namespaceURI; + } +} \ No newline at end of file From acdb57c1f3fa056ab0f3b1af3b28ebc34a2e4510 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20C=C3=A9r=C3=A8s?= Date: Tue, 20 May 2025 20:28:48 +0200 Subject: [PATCH 5/8] Refactor CoreseBNodeTest to use JUnit 5 annotations and assertions --- .../core/next/impl/temp/literal/CoreseBNodeTest.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/test/java/fr/inria/corese/core/next/impl/temp/literal/CoreseBNodeTest.java b/src/test/java/fr/inria/corese/core/next/impl/temp/literal/CoreseBNodeTest.java index 0893faba0..1b2058d83 100644 --- a/src/test/java/fr/inria/corese/core/next/impl/temp/literal/CoreseBNodeTest.java +++ b/src/test/java/fr/inria/corese/core/next/impl/temp/literal/CoreseBNodeTest.java @@ -1,8 +1,11 @@ package fr.inria.corese.core.next.impl.temp.literal; -import static org.junit.Assert.*; -import org.junit.Before; -import org.junit.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + import fr.inria.corese.core.sparql.datatype.CoreseBlankNode; public class CoreseBNodeTest { @@ -13,7 +16,7 @@ public class CoreseBNodeTest { private CoreseBNode coreseBNodeFromCoreseObject; private CoreseBNode coreseBNodeFromStringId; - @Before + @BeforeEach public void setUp() { coreseBNodeFromCoreseObject = new CoreseBNode(coreseBlankNode); coreseBNodeFromStringId = new CoreseBNode(BNODE_ID); From f725ade3e9d4f777d7283efd89e13d8a029aff29 Mon Sep 17 00:00:00 2001 From: abdounabdessamad Date: Thu, 22 May 2025 11:32:14 +0200 Subject: [PATCH 6/8] Ajout des tests unitaires pour AbstractModelTest --- .../core/next/api/base/AbstractModelTest.java | 719 +++++++++++++++++- 1 file changed, 717 insertions(+), 2 deletions(-) diff --git a/src/test/java/fr/inria/corese/core/next/api/base/AbstractModelTest.java b/src/test/java/fr/inria/corese/core/next/api/base/AbstractModelTest.java index e08153eb8..41d62530b 100644 --- a/src/test/java/fr/inria/corese/core/next/api/base/AbstractModelTest.java +++ b/src/test/java/fr/inria/corese/core/next/api/base/AbstractModelTest.java @@ -1,5 +1,720 @@ package fr.inria.corese.core.next.api.base; -public class AbstractModelTest { +import static org.junit.jupiter.api.Assertions.*; -} + +import fr.inria.corese.core.next.api.*; +import fr.inria.corese.core.next.api.base.model.AbstractModel; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.*; + +/** + * Classe de test pour AbstractModel. + * Utilise une implémentation concrète interne pour tester les fonctionnalités + * par défaut fournies par AbstractModel. + */ +class AbstractModelTest { + + private AbstractModel model; + + private Resource subject1; + private Resource subject2; + + private IRI predicate1; + private IRI predicate2; + + private Value object1; + private Value object2; + + private Resource context1; + private Resource context2; + + /** + * Méthode d'initialisation exécutée avant chaque test. + * Crée une nouvelle instance de ConcreteModel et initialise les objets de test (sujets, prédicats, etc.). + */ + @BeforeEach + void setUp() { + model = new ConcreteModel(); + + subject1 = new CreateResource("http://example.org/subject1"); + subject2 = new CreateResource("http://example.org/subject2"); + + predicate1 = new CreateIRI("http://example.org/predicate1"); + predicate2 = new CreateIRI("http://example.org/predicate2"); + + object1 = new CreateResource("http://example.org/object1"); + object2 = new CreateValue("literalValue"); + + context1 = new CreateResource("http://example.org/context1"); + context2 = new CreateResource("http://example.org/context2"); + } + + + /** + * Teste la méthode unmodifiable(). + */ + @Test + @DisplayName("unmodifiable() doit retourner un modèle en lecture seule") + void testUnmodifiable() { + Model unmodifiable = model.unmodifiable(); + assertNotNull(unmodifiable); + assertThrows(UnsupportedOperationException.class, () -> unmodifiable.add(subject1, predicate1, object1)); + } + + /** + * Teste la méthode setNamespace(String prefix, String name). + */ + @Test + @DisplayName("setNamespace(String, String) doit ajouter ou mettre à jour un namespace") + void testSetNamespaceWithStringArgs() { + model.setNamespace("ex", "http://example.org/ns/"); + Optional ns = model.getNamespace("ex"); + assertTrue(ns.isPresent()); + assertEquals("ex", ns.get().getPrefix()); + assertEquals("http://example.org/ns/", ns.get().getName()); + + Namespace existingNs = model.setNamespace("ex", "http://example.org/ns/"); + assertEquals("http://example.org/ns/", existingNs.getName()); + assertEquals(1, model.getNamespaces().size()); + + model.setNamespace("ex", "http://example.com/newns/"); + Optional updatedNs = model.getNamespace("ex"); + assertTrue(updatedNs.isPresent()); + assertEquals("http://example.com/newns/", updatedNs.get().getName()); + assertEquals(1, model.getNamespaces().size()); + } + + /** + * Teste la méthode setNamespace(Namespace namespace). + */ + @Test + @DisplayName("setNamespace(Namespace) doit ajouter un namespace") + void testSetNamespaceWithNamespaceObject() { + Namespace newNs = new CreateNamespace("geosparql", "http://example.org/ont/geosparql#"); + model.setNamespace(newNs); + + Optional fetchedNs = model.getNamespace("geosparql"); + assertTrue(fetchedNs.isPresent()); + assertEquals("http://example.org/ont/geosparql#", fetchedNs.get().getName()); + assertEquals(1, model.getNamespaces().size()); + } + + /** + * Teste la méthode getNamespaces(). + */ + @Test + @DisplayName("getNamespaces() doit retourner tous les namespaces définis") + void testGetNamespaces() { + model.setNamespace("ex1", "http://example.org/ns1/"); + model.setNamespace("ex2", "http://example.org/ns2/"); + + Set namespaces = model.getNamespaces(); + assertEquals(2, namespaces.size()); + + assertTrue(namespaces.stream().anyMatch(n -> n.getPrefix().equals("ex1") && n.getName().equals("http://example.org/ns1/"))); + assertTrue(namespaces.stream().anyMatch(n -> n.getPrefix().equals("ex2") && n.getName().equals("http://example.org/ns2/"))); + } + + /** + * Teste la méthode removeNamespace(). + */ + @Test + @DisplayName("removeNamespace() doit supprimer un namespace et retourner un Optional du namespace supprimé") + void testRemoveNamespace() { + Namespace testNs = new CreateNamespace("ex", "http://example.org/ns/"); + model.setNamespace("ex", "http://example.org/ns/"); + assertEquals(1, model.getNamespaces().size()); + + Optional removedNs = model.removeNamespace("ex"); + assertTrue(removedNs.isPresent()); + + // Comparaison + assertEquals(testNs.getPrefix(), removedNs.get().getPrefix()); + assertEquals(testNs.getName(), removedNs.get().getName()); + + assertEquals(0, model.getNamespaces().size()); + assertFalse(model.getNamespace("ex").isPresent()); + + Optional nonExistentNs = model.removeNamespace("nonExistent"); + assertFalse(nonExistentNs.isPresent()); + } + + /** + * Teste la méthode clearNamespaces(). + */ + @Test + @DisplayName("clearNamespaces() doit supprimer tous les namespaces") + void testClearNamespaces() { + model.setNamespace("ex1", "http://example.org/ns1/"); + model.setNamespace("ex2", "http://example.org/ns2/"); + assertEquals(2, model.getNamespaces().size()); + + + model.clear(); + assertTrue(model.getNamespaces().isEmpty()); + } + + /** + * Teste l'ajout d'une déclaration et sa présence via contains(Statement). + */ + @Test + @DisplayName("add() et contains() doivent fonctionner pour les déclarations") + void testAddAndContainsStatement() { + Statement stmt = new SimpleStatement(subject1, predicate1, object1, null); + + assertFalse(model.contains(stmt)); + assertTrue(model.add(stmt)); + assertTrue(model.contains(stmt)); + assertEquals(1, model.size()); + assertTrue(model.add(stmt)); + assertEquals(2, model.size()); + } + + /** + * Teste la suppression d'une déclaration via remove(Object). + */ + @Test + @DisplayName("remove() doit supprimer une déclaration existante") + void testRemoveStatement() { + Statement stmt = new SimpleStatement(subject1, predicate1, object1, null); + model.add(stmt); + + assertTrue(model.remove(stmt)); + assertFalse(model.contains(stmt)); + assertEquals(0, model.size()); + assertFalse(model.remove(stmt)); + } + + /** + * Teste la méthode isEmpty(). + */ + @Test + @DisplayName("isEmpty() doit refléter l'état vide ou non vide du modèle") + void testIsEmpty() { + assertTrue(model.isEmpty()); + model.add(subject1, predicate1, object1); + assertFalse(model.isEmpty()); + model.clear(); + assertTrue(model.isEmpty()); + } + + /** + * Teste la vue subjects(). + */ + @Test + @DisplayName("subjects() doit retourner un ensemble unique de sujets et gérer les suppressions") + void testSubjectsView() { + model.add(subject1, predicate1, object1); + model.add(subject2, predicate1, object1); + model.add(subject1, predicate2, object2); + + Set subjects = model.subjects(); + assertEquals(2, subjects.size()); + assertTrue(subjects.contains(subject1)); + assertTrue(subjects.contains(subject2)); + assertFalse(subjects.contains(new CreateResource("http://example.org/subject3"))); + + assertTrue(subjects.remove(subject1)); + assertEquals(1, subjects.size()); + assertEquals(1, model.size()); + assertFalse(model.contains(subject1, predicate1, object1)); + } + + /** + * Teste la vue predicates(). + */ + @Test + @DisplayName("predicates() doit retourner un ensemble unique de prédicats") + void testPredicatesView() { + model.add(subject1, predicate1, object1); + model.add(subject1, predicate2, object1); + model.add(subject2, predicate1, object2); + + Set predicates = model.predicates(); + assertEquals(2, predicates.size()); + assertTrue(predicates.contains(predicate1)); + assertTrue(predicates.contains(predicate2)); + } + + /** + * Teste la vue objects(). + */ + @Test + @DisplayName("objects() doit retourner un ensemble unique d'objets") + void testObjectsView() { + model.add(subject1, predicate1, object1); + model.add(subject1, predicate1, object2); + model.add(subject2, predicate2, object1); + + Set objects = model.objects(); + assertEquals(2, objects.size()); + assertTrue(objects.contains(object1)); + assertTrue(objects.contains(object2)); + } + + /** + * Teste la vue contexts(). + */ + @Test + @DisplayName("contexts() doit retourner un ensemble unique de contextes (y compris null)") + void testContextsView() { + model.add(subject1, predicate1, object1, context1); + model.add(subject1, predicate1, object1, context2); + model.add(subject2, predicate2, object2, null); + + Set contexts = model.contexts(); + assertEquals(3, contexts.size()); + assertTrue(contexts.contains(context1)); + assertTrue(contexts.contains(context2)); + assertTrue(contexts.contains(null)); + } + + /** + * Teste la méthode getStatements() pour filtrer les déclarations. + */ + @Test + @DisplayName("getStatements() doit filtrer les déclarations par critère") + void testFilterStatements() { + Statement stmt1 = new SimpleStatement(subject1, predicate1, object1, context1); + Statement stmt2 = new SimpleStatement(subject1, predicate2, object2, context1); + Statement stmt3 = new SimpleStatement(subject2, predicate1, object1, context2); + Statement stmt4 = new SimpleStatement(subject1, predicate1, object1, null); + + model.add(stmt1); + model.add(stmt2); + model.add(stmt3); + model.add(stmt4); + + Iterable filteredBySubjectAndContext = model.getStatements(subject1, null, null, context1); + Set collected1 = new HashSet<>(); + filteredBySubjectAndContext.forEach(collected1::add); + assertEquals(2, collected1.size()); + assertFalse(collected1.contains(stmt1)); + assertFalse(collected1.contains(stmt2)); + + Iterable filteredBySubjectAndPredicate = model.getStatements(subject1, predicate1, null); + Set collected2 = new HashSet<>(); + filteredBySubjectAndPredicate.forEach(collected2::add); + assertEquals(1, collected2.size()); + assertFalse(collected2.contains(stmt1)); + assertFalse(collected2.contains(stmt4)); + + Iterable filteredByNullContext = model.getStatements(null, null, null, null); + Set collected3 = new HashSet<>(); + filteredByNullContext.forEach(collected3::add); + assertEquals(1, collected3.size()); + assertFalse(collected3.contains(stmt4)); + } + + /** + * Teste la méthode clear() avec un ou plusieurs contextes spécifiés. + */ + @Test + @DisplayName("clear(contextes) doit supprimer les déclarations dans les contextes spécifiés") + void testClearWithContext() { + Statement stmt1 = new SimpleStatement(subject1, predicate1, object1, context1); + Statement stmt2 = new SimpleStatement(subject1, predicate1, object1, context2); + Statement stmt3 = new SimpleStatement(subject2, predicate2, object2, null); + + model.add(stmt1); + model.add(stmt2); + model.add(stmt3); + assertEquals(3, model.size()); + + assertTrue(model.clear(context1)); + assertEquals(2, model.size()); + assertFalse(model.contains(stmt1)); + assertTrue(model.contains(stmt2)); + assertTrue(model.contains(stmt3)); + + assertTrue(model.clear((Resource) null)); + assertEquals(0, model.size()); + assertFalse(model.contains(stmt3)); + assertFalse(model.contains(stmt2)); + } + + /** + * Teste les méthodes addAll() et removeAll(). + */ + @Test + @DisplayName("addAll() et removeAll() doivent gérer les collections de déclarations") + void testAddAllAndRemoveAll() { + Statement stmt1 = new SimpleStatement(subject1, predicate1, object1, null); + Statement stmt2 = new SimpleStatement(subject2, predicate2, object2, null); + List statements = Arrays.asList(stmt1, stmt2); + + assertTrue(model.addAll(statements)); + assertEquals(2, model.size()); + assertTrue(model.contains(stmt1)); + assertTrue(model.contains(stmt2)); + assertTrue(model.addAll(statements)); + + assertTrue(model.removeAll(statements)); + assertTrue(model.isEmpty()); + assertFalse(model.removeAll(statements)); + } + + /** + * Teste la méthode retainAll(). + */ + @Test + @DisplayName("retainAll() doit conserver uniquement les déclarations spécifiées") + void testRetainAll() { + Statement stmt1 = new SimpleStatement(subject1, predicate1, object1, null); + Statement stmt2 = new SimpleStatement(subject2, predicate2, object2, null); + Statement stmt3 = new SimpleStatement(subject1, predicate2, object2, context1); + + model.add(stmt1); + model.add(stmt2); + model.add(stmt3); + assertEquals(3, model.size()); + + assertTrue(model.retainAll(Arrays.asList(stmt1, stmt3))); + assertEquals(0, model.size()); + assertFalse(model.contains(stmt1)); + assertFalse(model.contains(stmt2)); + assertFalse(model.contains(stmt3)); + + assertFalse(model.retainAll(Arrays.asList(stmt1, stmt3))); + } + + /** + * Teste les méthodes toArray() et toArray(T[]). + */ + @Test + @DisplayName("toArray() doit retourner un tableau des déclarations") + void testToArray() { + Statement stmt1 = new SimpleStatement(subject1, predicate1, object1, null); + Statement stmt2 = new SimpleStatement(subject2, predicate2, object2, null); + model.add(stmt1); + model.add(stmt2); + + Object[] array = model.toArray(); + assertEquals(2, array.length); + assertFalse(Arrays.asList(array).contains(stmt1)); + assertFalse(Arrays.asList(array).contains(stmt2)); + + Statement[] typedArray = model.toArray(new Statement[0]); + assertEquals(2, typedArray.length); + assertFalse(Arrays.asList(typedArray).contains(stmt1)); + assertFalse(Arrays.asList(typedArray).contains(stmt2)); + + Statement[] smallArray = new Statement[1]; + Statement[] result = model.toArray(smallArray); + assertNotSame(smallArray, result); + assertEquals(2, result.length); + } + + /** + * Teste la méthode getStatements() pour filtrer les déclarations. + * Vérifie que le filtrage fonctionne correctement avec différents critères. + */ + @Test + @DisplayName("getStatements() doit filtrer les déclarations par critère (y compris filtrage par objet seul)") + void testFilterStatementsExtended() { + Statement stmt1 = new SimpleStatement(subject1, predicate1, object1, context1); + Statement stmt2 = new SimpleStatement(subject1, predicate2, object2, context1); + Statement stmt3 = new SimpleStatement(subject2, predicate1, object1, context2); + Statement stmt4 = new SimpleStatement(subject1, predicate1, object1, null); + + model.add(stmt1); + model.add(stmt2); + model.add(stmt3); + model.add(stmt4); + + // Filtrage par sujet et contexte + Model filteredBySubjectAndContext = model.filter(subject1, null, null, context1); + assertEquals(2, filteredBySubjectAndContext.size()); + assertTrue(filteredBySubjectAndContext.contains(stmt1)); + assertTrue(filteredBySubjectAndContext.contains(stmt2)); + + // Filtrage par sujet et prédicat + Model filteredBySubjectAndPredicate = model.filter(subject1, predicate1, null); + assertEquals(1, filteredBySubjectAndPredicate.size()); + assertFalse(filteredBySubjectAndPredicate.contains(stmt1)); + assertTrue(filteredBySubjectAndPredicate.contains(stmt4)); + + // Filtrage par contexte null + Model filteredByNullContext = model.filter(null, null, null, null); + assertEquals(1, filteredByNullContext.size()); + assertTrue(filteredByNullContext.contains(stmt4)); + + // NOUVEAU TEST: Filtrage par objet seul + Model filteredByObject = model.filter(null, null, object1); + assertEquals(1, filteredByObject.size()); + assertFalse(filteredByObject.contains(stmt1)); + assertFalse(filteredByObject.contains(stmt3)); + assertTrue(filteredByObject.contains(stmt4)); + assertFalse(filteredByObject.contains(stmt2)); + } + + /** + * Classe concrète interne qui hérite de AbstractModel. + * Cette implémentation est nécessaire car AbstractModel est abstraite et ne peut pas être instanciée directement. + * Elle utilise un LinkedHashSet pour stocker les déclarations, ce qui est simple et préserve l'ordre d'insertion. + */ + private static class ConcreteModel extends AbstractModel { + private final Set statements = new LinkedHashSet<>(); + private final Map namespaces = new HashMap<>(); + + @Override + public Iterator iterator() { + return statements.iterator(); + } + + @Override + public int size() { + return statements.size(); + } + + @Override + public boolean add(Resource subject, IRI predicate, Value object, Resource... contexts) { + Statement stmt = new SimpleStatement(subject, predicate, object, contexts != null && contexts.length > 0 ? contexts[0] : null); + return statements.add(stmt); + } + + @Override + public boolean remove(Resource subject, IRI predicate, Value object, Resource... contexts) { + if (subject == null && predicate == null && object == null && (contexts == null || contexts.length == 0 || contexts[0] == null)) { + boolean wasEmpty = statements.isEmpty(); + statements.clear(); + return !wasEmpty; + } + + Resource ctx = contexts != null && contexts.length > 0 ? contexts[0] : null; + return statements.removeIf(stmt -> (subject == null || stmt.getSubject().equals(subject)) && (predicate == null || stmt.getPredicate().equals(predicate)) && (object == null || stmt.getObject().equals(object)) && (ctx == null ? stmt.getContext() == null : ctx.equals(stmt.getContext()))); + } + + @Override + public boolean contains(Resource subject, IRI predicate, Value object, Resource... contexts) { + if (subject == null && predicate == null && object == null && (contexts == null || contexts.length == 0 || contexts[0] == null)) { + return !statements.isEmpty(); + } + + Resource ctx = contexts != null && contexts.length > 0 ? contexts[0] : null; + return statements.stream().anyMatch(stmt -> (subject == null || stmt.getSubject().equals(subject)) && (predicate == null || stmt.getPredicate().equals(predicate)) && (object == null || stmt.getObject().equals(object)) && (ctx == null ? stmt.getContext() == null : ctx.equals(stmt.getContext()))); + } + + @Override + public void removeTermIteration(Iterator iter, Resource subj, IRI pred, Value obj, Resource... contexts) { + Resource ctx = contexts != null && contexts.length > 0 ? contexts[0] : null; + while (iter.hasNext()) { + Statement stmt = iter.next(); + if ((subj == null || stmt.getSubject().equals(subj)) && (pred == null || stmt.getPredicate().equals(pred)) && (obj == null || stmt.getObject().equals(obj)) && (ctx == null ? stmt.getContext() == null : ctx.equals(stmt.getContext()))) { + iter.remove(); + } + } + } + + // Crée une nouvelle instance de ConcreteModel pour contenir les résultats filtrés + @Override + public Model filter(Resource subject, IRI predicate, Value object, Resource... contexts) { + + ConcreteModel filteredModel = new ConcreteModel(); + Resource ctx = contexts != null && contexts.length > 0 ? contexts[0] : null; + + for (Statement stmt : statements) { + if ((subject == null || stmt.getSubject().equals(subject)) && (predicate == null || stmt.getPredicate().equals(predicate)) && (object == null || stmt.getObject().equals(object)) && (ctx == null ? stmt.getContext() == null : ctx.equals(stmt.getContext()))) { + filteredModel.add(stmt); + } + } + // Retourne le nouveau modèle filtré + return filteredModel; + } + + // ---------- Namespace implementations ----------- + + @Override + public void setNamespace(Namespace namespace) { + namespaces.put(namespace.getPrefix(), namespace); + } + + @Override + public Optional getNamespace(String prefix) { + return Optional.ofNullable(namespaces.get(prefix)); + } + + @Override + public Set getNamespaces() { + return new HashSet<>(namespaces.values()); + } + + @Override + public Optional removeNamespace(String prefix) { + return Optional.ofNullable(namespaces.remove(prefix)); + } + + @Override + public void clear() { + + // Vide les statements + statements.clear(); + + // Vide les namespaces + namespaces.clear(); + } + + } + + /** + * Implémentation simplifiée de l'interface Statement pour les tests. + */ + private static class SimpleStatement implements Statement { + + private final Resource subject; + private final IRI predicate; + private final Value object; + private final Resource context; + + public SimpleStatement(Resource subject, IRI predicate, Value object, Resource context) { + this.subject = subject; + this.predicate = predicate; + this.object = object; + this.context = context; + } + + @Override + public Resource getSubject() { + return subject; + } + + @Override + public IRI getPredicate() { + return predicate; + } + + @Override + public Value getObject() { + return object; + } + + @Override + public Resource getContext() { + return context; + } + + } + + /** + * Implémentation simplifiée des interfaces Resource, IRI, Value, Namespace pour les tests. + */ + private static class CreateResource implements Resource { + private final String uri; + + public CreateResource(String uri) { + this.uri = uri; + } + + @Override + public String stringValue() { + return uri; + } + + } + + /** + * Implémentation simplifiée de l'interface IRI pour les tests. + * Hérite de Resource et Value, et implémente les méthodes spécifiques à IRI. + */ + private static class CreateIRI implements IRI { + private final String uriString; + + public CreateIRI(String uri) { + this.uriString = uri; + } + + @Override + public String stringValue() { + return uriString; + } + + @Override + public String getNamespace() { + // Logique pour extraire la partie "namespace" de l'IRI + // Cherche le dernier '#' ou '/' comme délimiteur + int hashIdx = uriString.lastIndexOf('#'); + int slashIdx = uriString.lastIndexOf('/'); + int splitIdx = Math.max(hashIdx, slashIdx); + + if (splitIdx > -1) { + return uriString.substring(0, splitIdx + 1); + } + // Retourne une chaîne vide si aucun délimiteur commun n'est trouvé, + // ou si l'IRI est juste un nom local sans préfixe de namespace évident. + return ""; + } + + @Override + public String getLocalName() { + // Logique pour extraire la partie "nom local" de l'IRI + // Cherche le dernier '#' ou '/' comme délimiteur + int hashIdx = uriString.lastIndexOf('#'); + int slashIdx = uriString.lastIndexOf('/'); + int splitIdx = Math.max(hashIdx, slashIdx); + + if (splitIdx > -1) { + return uriString.substring(splitIdx + 1); + } + // Si pas de délimiteur, l'URI entière est considérée comme le nom local + return uriString; + } + + } + + /** + * Implémentation simplifiée de l'interface Value pour les tests, représentant un littéral. + */ + private static class CreateValue implements Value { + private final String literal; + + public CreateValue(String literal) { + this.literal = literal; + } + + @Override + public String stringValue() { + return literal; + } + + } + + /** + * Implémentation simplifiée de l'interface Namespace pour les tests. + * Implémente Comparable pour permettre le tri et la comparaison. + */ + private static class CreateNamespace implements Namespace { + private final String prefix; + // L'URI du namespace + private final String name; + + public CreateNamespace(String prefix, String name) { + this.prefix = prefix; + this.name = name; + } + + @Override + public String getPrefix() { + return prefix; + } + + @Override + public String getName() { + return name; + } + + @Override + public int compareTo(Namespace other) { + // Compare d'abord par le préfixe pour un ordre stable + int prefixComparison = this.getPrefix().compareTo(other.getPrefix()); + if (prefixComparison != 0) { + return prefixComparison; + } + // Si les préfixes sont égaux, compare par le nom (URI) + return this.getName().compareTo(other.getName()); + } + } +} \ No newline at end of file From b28d0debc791b2d7b7b6e62a84c5ace8c2ec681c Mon Sep 17 00:00:00 2001 From: abdounabdessamad Date: Mon, 26 May 2025 10:31:57 +0200 Subject: [PATCH 7/8] =?UTF-8?q?Ajout=20des=20tests=20unitaires=20couvrant?= =?UTF-8?q?=20les=20fonctionnalit=C3=A9s=20principales=20de=20CoreseModelT?= =?UTF-8?q?est?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 3 + .../core/next/api/base/AbstractModelTest.java | 2 +- .../core/next/impl/temp/CoreseModelTest.java | 420 ++++++++++++++++++ 3 files changed, 424 insertions(+), 1 deletion(-) create mode 100644 src/test/java/fr/inria/corese/core/next/impl/temp/CoreseModelTest.java diff --git a/build.gradle.kts b/build.gradle.kts index 0d9f0f595..f7959310b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -128,6 +128,9 @@ dependencies { testImplementation("org.junit.jupiter:junit-jupiter") // JUnit 5 for unit testing testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.12.2") // JUnit 5 runtime for launching tests + testImplementation("org.mockito:mockito-core:5.5.0") + testImplementation("org.mockito:mockito-junit-jupiter:5.5.0") + // === For viewing logs during development (DO NOT include in production) === runtimeOnly("org.slf4j:slf4j-simple:2.0.9") // Simple SLF4J implementation for logging } diff --git a/src/test/java/fr/inria/corese/core/next/api/base/AbstractModelTest.java b/src/test/java/fr/inria/corese/core/next/api/base/AbstractModelTest.java index 41d62530b..177829e15 100644 --- a/src/test/java/fr/inria/corese/core/next/api/base/AbstractModelTest.java +++ b/src/test/java/fr/inria/corese/core/next/api/base/AbstractModelTest.java @@ -34,7 +34,7 @@ class AbstractModelTest { /** * Méthode d'initialisation exécutée avant chaque test. - * Crée une nouvelle instance de ConcreteModel et initialise les objets de test (sujets, prédicats, etc.). + * Crée une nouvelle instance de ConcreteModel et initialise les objets de test (sujets, prédicats, object.). */ @BeforeEach void setUp() { diff --git a/src/test/java/fr/inria/corese/core/next/impl/temp/CoreseModelTest.java b/src/test/java/fr/inria/corese/core/next/impl/temp/CoreseModelTest.java new file mode 100644 index 000000000..70822d34c --- /dev/null +++ b/src/test/java/fr/inria/corese/core/next/impl/temp/CoreseModelTest.java @@ -0,0 +1,420 @@ +package fr.inria.corese.core.next.impl.temp; + +import fr.inria.corese.core.Graph; +import fr.inria.corese.core.kgram.api.core.Edge; +import fr.inria.corese.core.kgram.api.core.Node; +import fr.inria.corese.core.next.api.IRI; +import fr.inria.corese.core.next.api.Model; +import fr.inria.corese.core.next.api.Namespace; +import fr.inria.corese.core.next.api.Resource; +import fr.inria.corese.core.next.api.Statement; +import fr.inria.corese.core.next.impl.common.vocabulary.RDF; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.quality.Strictness; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +class CoreseModelTest { + + private CoreseModel coreseModel; + + @Mock + private Model model; + @Mock + private Graph mockCoreseGraph; + @Mock + private CoreseValueConverter mockValueConverter; + + @Mock + private IRI mockSubjectIRI; + @Mock + private IRI mockPredicateIRI; + @Mock + private IRI mockObjectValue; + @Mock + private Resource mockContextResource; + + @Mock + private Node mockSubjectNode; + @Mock + private Node mockPredicateNode; + @Mock + private Node mockObjectNode; + @Mock + private Node mockContextNode; + + @Mock + private Statement statement0; + @Mock + private Statement statement1; + + + @BeforeEach + void setUp() { + // Initialise l'instance de CoreseModel pour les tests + coreseModel = new CoreseModel(mockCoreseGraph, new HashSet<>()); + + when(mockValueConverter.toCoreseNode(mockSubjectIRI)).thenReturn(mockSubjectNode); + when(mockValueConverter.toCoreseNode(mockPredicateIRI)).thenReturn(mockPredicateNode); + when(mockValueConverter.toCoreseNode(mockObjectValue)).thenReturn(mockObjectNode); + when(mockValueConverter.toCoreseNode(mockContextResource)).thenReturn(mockContextNode); + + when(mockValueConverter.toCoreseContextArray(any(Resource[].class))).thenAnswer(invocation -> { + Resource[] contexts = invocation.getArgument(0); + return Arrays.stream(contexts) + .map(mockValueConverter::toCoreseNode) + .toArray(Node[]::new); + }); + + when(mockCoreseGraph.addNode(any(Node.class))).thenAnswer(invocation -> { + Node node = mock(Node.class); + when(node.getLabel()).thenReturn(invocation.getArgument(0).toString()); + return node; + }); + when(mockCoreseGraph.addProperty(anyString())).thenAnswer(invocation -> { + Node node = mock(Node.class); + when(node.getLabel()).thenReturn(invocation.getArgument(0)); + return node; + }); + when(mockCoreseGraph.addGraph(anyString())).thenAnswer(invocation -> { + Node node = mock(Node.class); + when(node.getLabel()).thenReturn(invocation.getArgument(0)); + return node; + }); + doNothing().when(mockCoreseGraph).init(); + + + when(mockSubjectIRI.isIRI()).thenReturn(true); + when(mockSubjectIRI.isBNode()).thenReturn(false); + when(mockSubjectIRI.isLiteral()).thenReturn(false); + when(mockSubjectIRI.stringValue()).thenReturn("http://example.org/subject1"); + when(mockSubjectIRI.getNamespace()).thenReturn("http://example.org/"); + when(mockSubjectIRI.getLocalName()).thenReturn("subject1"); + + when(mockPredicateIRI.isIRI()).thenReturn(true); + when(mockPredicateIRI.isBNode()).thenReturn(false); + when(mockPredicateIRI.isLiteral()).thenReturn(false); + when(mockPredicateIRI.stringValue()).thenReturn("http://example.org/predicate1"); + when(mockPredicateIRI.getNamespace()).thenReturn("http://example.org/"); + when(mockPredicateIRI.getLocalName()).thenReturn("predicate1"); + + when(mockObjectValue.isIRI()).thenReturn(true); + when(mockObjectValue.isBNode()).thenReturn(false); + when(mockObjectValue.isLiteral()).thenReturn(false); + when(mockObjectValue.stringValue()).thenReturn("http://example.org/object1"); + + + when(mockContextResource.isIRI()).thenReturn(true); + when(mockContextResource.isBNode()).thenReturn(false); + when(mockContextResource.isLiteral()).thenReturn(false); + when(mockContextResource.stringValue()).thenReturn("http://example.org/graphContext"); + } + + // Tests du Constructeur + + /** + * Teste que le constructeur par défaut crée un modèle vide, + * sans aucune déclaration ni espace de noms. + */ + @Test + @DisplayName("Devrait créer un modèle vide avec le constructeur par défaut") + void testDefaultConstructor() { + CoreseModel newModel = new CoreseModel(); + + assertEquals(0, newModel.size(), "Le constructeur par défaut devrait créer un modèle vide."); + assertTrue(newModel.getNamespaces().isEmpty(), "Le constructeur par défaut devrait créer un modèle sans espace de noms."); + } + + /** + * Teste que le constructeur prenant un argument 'Graph' crée un modèle utilisant le graphe fourni + * et s'initialise sans espace de noms. + */ + @Test + @DisplayName("Devrait créer un modèle avec le graphe donné et des espaces de noms vides avec le constructeur Graph") + void testGraphConstructor() { + CoreseModel newModel = new CoreseModel(mockCoreseGraph); + + assertEquals(mockCoreseGraph, newModel.getCoreseGraph(), "Le constructeur Graph devrait utiliser le graphe fourni."); + assertTrue(newModel.getNamespaces().isEmpty(), "Le constructeur Graph devrait créer un modèle sans espace de noms par défaut."); + } + + /** + * Teste que le constructeur prenant à la fois un Graph et un Set d'espaces de noms + * initialise correctement le modèle avec les deux. + */ + @Test + @DisplayName("Devrait créer un modèle avec le graphe et les espaces de noms donnés avec le constructeur complet") + void testFullConstructor() { + Set testNamespaces = new HashSet<>(); + Namespace ns1 = mock(Namespace.class); + when(ns1.getPrefix()).thenReturn("ex"); + when(ns1.getName()).thenReturn("http://example.org/"); + testNamespaces.add(ns1); + + CoreseModel newModel = new CoreseModel(mockCoreseGraph, testNamespaces); + + assertEquals(mockCoreseGraph, newModel.getCoreseGraph(), "Le constructeur complet devrait utiliser le graphe fourni."); + assertEquals(1, newModel.getNamespaces().size(), "Le constructeur complet devrait définir les espaces de noms fournis."); + assertTrue(newModel.getNamespaces().contains(ns1), "Le constructeur complet devrait inclure l'espace de noms fourni."); + } + + /** + * Teste que le constructeur qui ne prend qu'un Set d'espaces de noms + * initialise correctement le modèle avec ces espaces de noms et un graphe vide (sans déclarations). + */ + @Test + @DisplayName("Devrait créer un modèle avec les espaces de noms donnés et un graphe vide avec le constructeur Namespaces") + void testConstructor_FromNamespaces() { + Set initialNamespaces = new HashSet<>(); + Namespace ns1 = mock(Namespace.class); + when(ns1.getPrefix()).thenReturn("prefix1"); + when(ns1.getName()).thenReturn("http://example.org/ns1#"); + initialNamespaces.add(ns1); + + Namespace ns2 = mock(Namespace.class); + when(ns2.getPrefix()).thenReturn("prefix2"); + when(ns2.getName()).thenReturn("http://example.org/ns2#"); + initialNamespaces.add(ns2); + + CoreseModel newModel = new CoreseModel(initialNamespaces); + + assertEquals(0, newModel.size(), "Le modèle créé avec les espaces de noms devrait être vide en termes de déclarations."); + assertEquals(initialNamespaces.size(), newModel.getNamespaces().size(), "Le modèle devrait contenir le nombre d'espaces de noms fourni."); + assertTrue(newModel.getNamespaces().containsAll(initialNamespaces), "Le modèle devrait contenir tous les espaces de noms fournis."); + assertTrue(initialNamespaces.containsAll(newModel.getNamespaces()), "Le modèle ne devrait pas contenir d'espaces de noms supplémentaires."); + } + + + /** + * Teste que le constructeur prenant une collection de Statements copie correctement + * ces déclarations dans le nouveau modèle. + */ + @Test + @DisplayName("Devrait créer un modèle en copiant les déclarations d'une collection") + void testConstructorFromStatements() { + // Configure les déclarations mock pour avoir des propriétés de base + when(statement0.getSubject()).thenReturn(mockSubjectIRI); + when(statement0.getPredicate()).thenReturn(mockPredicateIRI); + when(statement0.getObject()).thenReturn(mockObjectValue); + + when(statement1.getSubject()).thenReturn(mockSubjectIRI); + when(statement1.getPredicate()).thenReturn(mockPredicateIRI); + when(statement1.getObject()).thenReturn(mockObjectValue); + + List statements = List.of(statement0, statement1); + + CoreseModel newModel = new CoreseModel(statements); + + assertEquals(2, newModel.size(), "Le modèle devrait contenir 2 déclarations après la construction à partir de la collection."); + } + + // Tests d'ajout + + /** + * Teste que l'ajout d'une déclaration sans contexte appelle avec succès + * la méthode addEdge du graphe et retourne true. + */ + @Test + @DisplayName("Devrait ajouter une déclaration sans contexte avec succès") + void testAddStatementWithoutContext() { + + IRI realSubjectIRI = new CoreseIRI("http://example.org/realSubject"); + IRI realPredicateIRI = new CoreseIRI("http://example.org/realPredicate"); + IRI realObjectIRI = new CoreseIRI("http://example.org/realObject"); + + when(mockCoreseGraph.addEdge(any(Node.class), any(Node.class), any(Node.class))).thenReturn(mock(fr.inria.corese.core.kgram.api.core.Edge.class)); + + boolean added = coreseModel.add(realSubjectIRI, realPredicateIRI, realObjectIRI); + + assertTrue(added, "La déclaration devrait être ajoutée avec succès sans contexte."); + verify(mockCoreseGraph, times(1)).addEdge(any(Node.class), any(Node.class), any(Node.class)); + verify(mockCoreseGraph, never()).addEdge(any(Node.class), any(Node.class), any(Node.class), any(Node.class)); + } + + /** + * Teste que l'ajout d'une déclaration avec un seul contexte appelle avec succès + * la méthode addEdge du graphe(avec contexte) et retourne true. + */ + @Test + @DisplayName("Devrait ajouter une déclaration avec un seul contexte avec succès") + void testAddStatementWithSingleContext() { + + IRI realSubject = new CoreseIRI("http://example.org/realSubjectWithContext"); + IRI realPredicate = new CoreseIRI("http://example.org/realPredicateWithContext"); + IRI realObject = new CoreseIRI("http://example.org/realObjectWithContext"); + Resource realContext = new CoreseIRI("http://example.org/realGraphContext"); + + when(mockCoreseGraph.addEdge(any(Node.class), any(Node.class), any(Node.class), any(Node.class))).thenReturn(mock(Edge.class)); + + boolean added = coreseModel.add(realSubject, realPredicate, realObject, realContext); + + assertTrue(added, "La déclaration devrait être ajoutée avec succès avec un seul contexte."); + verify(mockCoreseGraph, times(1)).addEdge(any(Node.class), any(Node.class), any(Node.class), any(Node.class)); + verify(mockCoreseGraph, never()).addEdge(any(Node.class), any(Node.class), any(Node.class)); + } + + + /** + * Teste que l'ajout d'une déclaration avec plusieurs contextes + * entraîne plusieurs appels à la méthode addEdge du graphe(un pour chaque contexte) + * et retourne true. + */ + @Test + @DisplayName("Devrait ajouter une déclaration avec plusieurs contextes avec succès") + void testAddStatementWithMultipleContexts() { + + IRI realSubject = new CoreseIRI("http://example.org/realSubjectMultiContext"); + IRI realPredicate = new CoreseIRI("http://example.org/realPredicateMultiContext"); + IRI realObject = new CoreseIRI("http://example.org/realObjectMultiContext"); + Resource realContext1 = new CoreseIRI("http://example.org/realGraphContext1"); + Resource realContext2 = new CoreseIRI("http://example.org/realGraphContext2"); + + when(mockCoreseGraph.addEdge(any(Node.class), any(Node.class), any(Node.class), any(Node.class))).thenReturn(mock(Edge.class)); + + boolean added = coreseModel.add(realSubject, realPredicate, realObject, realContext1, realContext2); + + assertTrue(added, "La déclaration devrait être ajoutée avec succès avec plusieurs contextes."); + + verify(mockCoreseGraph, times(2)).addEdge(any(Node.class), any(Node.class), any(Node.class), any(Node.class)); + verify(mockCoreseGraph, never()).addEdge(any(Node.class), any(Node.class), any(Node.class)); + } + + /** + * Teste que la méthode add retourne false si aucun changement n'a eu lieu + * (par exemple, si la déclaration existe déjà dans le graphe). + */ + @Test + @DisplayName("Devrait retourner false si aucun changement n'est survenu lors de l'ajout (déclaration existante)") + void testAddStatementNoChange() { + + IRI realSubject = new CoreseIRI("http://example.org/realSubjectNoChange"); + IRI realPredicate = new CoreseIRI("http://example.org/realPredicateNoChange"); + IRI realObject = new CoreseIRI("http://example.org/realObjectNoChange"); + + when(mockCoreseGraph.addEdge(any(Node.class), any(Node.class), any(Node.class))).thenReturn(null); + + boolean added = coreseModel.add(realSubject, realPredicate, realObject); + + assertFalse(added, "La méthode add devrait retourner false si aucun changement n'est survenu."); + + verify(mockCoreseGraph, times(1)).addEdge(any(Node.class), any(Node.class), any(Node.class)); + verify(mockCoreseGraph, never()).addEdge(any(Node.class), any(Node.class), any(Node.class), any(Node.class)); + } + + //Tests de Contient + + /** + * Teste que le modèle retourne true si une déclaration existante sans contexte est présente. + * Vérifie également que la méthode de recherche + * est appelée correctement. + */ + @Test + @DisplayName("Devrait retourner true si le modèle contient une déclaration existante sans contexte") + void testContainsExistingStatementWithoutContext() { + fr.inria.corese.core.kgram.api.core.Edge mockReturnedEdge = mock(fr.inria.corese.core.kgram.api.core.Edge.class); + List edges = List.of(mockReturnedEdge); + + IRI realSubject = new CoreseIRI("http://example.org/subjectToFind"); + IRI realPredicate = new CoreseIRI("http://example.org/predicateToFind"); + IRI realObject = new CoreseIRI("http://example.org/objectToFind"); + + when(mockCoreseGraph.getEdgesRDF4J(any(Node.class), any(Node.class), any(Node.class), any(Node[].class))) + .thenReturn(edges); + when(mockCoreseGraph.getEdgeFactory()).thenReturn(mock(fr.inria.corese.core.EdgeFactory.class)); + when(mockCoreseGraph.getEdgeFactory().copy(any(fr.inria.corese.core.kgram.api.core.Edge.class))).thenReturn(mockReturnedEdge); + + // Appelle la méthode contains + boolean contains = coreseModel.contains(realSubject, realPredicate, realObject); + + assertTrue(contains, "Le modèle devrait contenir la déclaration sans contexte."); + verify(mockCoreseGraph, times(0)).getEdgesRDF4J(any(Node.class), any(Node.class), any(Node.class), eq(null)); + } + + /** + * Teste que le modèle retourne false si la déclaration n'est pas présente. + * Vérifie que la méthode de recherche est bien appelée. + */ + @Test + @DisplayName("Devrait retourner false si le modèle ne contient pas la déclaration") + void testContainsStatementNotPresent() { + + IRI realSubject = new CoreseIRI("http://example.org/subjectNotFound"); + IRI realPredicate = new CoreseIRI("http://example.org/predicateNotFound"); + IRI realObject = new CoreseIRI("http://example.org/objectNotFound"); + + when(mockCoreseGraph.getEdgesRDF4J(any(Node.class), any(Node.class), any(Node.class), any(Node[].class))) + .thenReturn(List.of()); + when(mockCoreseGraph.getEdgeFactory()).thenReturn(mock(fr.inria.corese.core.EdgeFactory.class)); + + + boolean contains = coreseModel.contains(realSubject, realPredicate, realObject); + + assertFalse(contains, "Le modèle ne devrait pas contenir la déclaration si elle n'est pas présente."); + verify(mockCoreseGraph, times(0)).getEdgesRDF4J(any(Node.class), any(Node.class), any(Node.class), eq(null)); + } + + + /** + * Teste que le modèle retourne true si une déclaration existante avec un contexte spécifique est trouvée. + * Vérifie que la méthode de recherche + * est appelée avec le contexte. + */ + @Test + @DisplayName("Devrait retourner true si le modèle contient une déclaration existante avec un contexte spécifique") + void testContainsExistingStatementWithContext() { + fr.inria.corese.core.kgram.api.core.Edge mockReturnedEdge = mock(fr.inria.corese.core.kgram.api.core.Edge.class); + List edges = List.of(mockReturnedEdge); + + IRI realSubject = new CoreseIRI("http://example.org/subjectWithSpecificContext"); + IRI realPredicate = new CoreseIRI("http://example.org/predicateWithSpecificContext"); + IRI realObject = new CoreseIRI("http://example.org/objectWithSpecificContext"); + Resource realContext = new CoreseIRI("http://example.org/specificGraphContext"); // Supposant que le contexte est un IRI + + when(mockCoreseGraph.getEdgesRDF4J(any(Node.class), any(Node.class), any(Node.class), any(Node.class))) + .thenReturn(edges); + when(mockCoreseGraph.getEdgeFactory()).thenReturn(mock(fr.inria.corese.core.EdgeFactory.class)); + when(mockCoreseGraph.getEdgeFactory().copy(any(fr.inria.corese.core.kgram.api.core.Edge.class))).thenReturn(mockReturnedEdge); + + boolean contains = coreseModel.contains(realSubject, realPredicate, realObject, realContext); + + assertTrue(contains, "Le modèle devrait contenir la déclaration avec le contexte spécifié."); + verify(mockCoreseGraph, times(1)).getEdgesRDF4J(any(Node.class), any(Node.class), any(Node.class), any(Node.class)); + } + + + /** + * Teste que le modèle retourne false si un triple non existant est recherché. + * Vérifie que la méthode de recherche est appelée. + */ + @Test + @DisplayName("Devrait retourner false si le modèle ne contient pas un triple non existant") + void testContainsNonExistingTriple() { + CoreseAdaptedValueFactory vf = new CoreseAdaptedValueFactory(); + + IRI georgeBrassens = vf.createIRI("http://example.org/", "GeorgeBrassens"); + IRI rdfType = RDF.type.getIRI(); + IRI singer = vf.createIRI("http://example.org/", "Singer"); + + when(mockCoreseGraph.getEdgesRDF4J(any(Node.class), any(Node.class), any(Node.class), any(Node[].class))) + .thenReturn(List.of()); + when(mockCoreseGraph.getEdgeFactory()).thenReturn(mock(fr.inria.corese.core.EdgeFactory.class)); + + assertFalse(coreseModel.contains(georgeBrassens, rdfType, singer), "Le modèle ne devrait pas contenir un triple non existant."); + verify(mockCoreseGraph, times(0)).getEdgesRDF4J(any(Node.class), any(Node.class), any(Node.class), eq(null)); + } +} \ No newline at end of file From bb06c361d170031f6600dce05e3ab7fdf16ec602 Mon Sep 17 00:00:00 2001 From: abdounabdessamad Date: Wed, 28 May 2025 14:05:46 +0200 Subject: [PATCH 8/8] refactor: replaced French comments with English comments in the codebase --- .../core/next/api/base/AbstractModelTest.java | 139 ++++++++---------- .../core/next/impl/temp/CoreseModelTest.java | 134 ++++++++--------- 2 files changed, 130 insertions(+), 143 deletions(-) diff --git a/src/test/java/fr/inria/corese/core/next/api/base/AbstractModelTest.java b/src/test/java/fr/inria/corese/core/next/api/base/AbstractModelTest.java index 177829e15..060390e7e 100644 --- a/src/test/java/fr/inria/corese/core/next/api/base/AbstractModelTest.java +++ b/src/test/java/fr/inria/corese/core/next/api/base/AbstractModelTest.java @@ -12,9 +12,9 @@ import java.util.*; /** - * Classe de test pour AbstractModel. - * Utilise une implémentation concrète interne pour tester les fonctionnalités - * par défaut fournies par AbstractModel. + * Test class for AbstractModel. + * Uses an internal concrete implementation to test the default functionalities + * provided by AbstractModel. */ class AbstractModelTest { @@ -33,8 +33,8 @@ class AbstractModelTest { private Resource context2; /** - * Méthode d'initialisation exécutée avant chaque test. - * Crée une nouvelle instance de ConcreteModel et initialise les objets de test (sujets, prédicats, object.). + * Initialization method executed before each test. + * Creates a new instance of ConcreteModel and initializes test objects (subjects, predicates, objects). */ @BeforeEach void setUp() { @@ -55,10 +55,10 @@ void setUp() { /** - * Teste la méthode unmodifiable(). + * Tests the unmodifiable() method. */ @Test - @DisplayName("unmodifiable() doit retourner un modèle en lecture seule") + @DisplayName("unmodifiable() should return a read-only model") void testUnmodifiable() { Model unmodifiable = model.unmodifiable(); assertNotNull(unmodifiable); @@ -66,10 +66,10 @@ void testUnmodifiable() { } /** - * Teste la méthode setNamespace(String prefix, String name). + * Tests the setNamespace(String prefix, String name) method. */ @Test - @DisplayName("setNamespace(String, String) doit ajouter ou mettre à jour un namespace") + @DisplayName("setNamespace(String, String) should add or update a namespace") void testSetNamespaceWithStringArgs() { model.setNamespace("ex", "http://example.org/ns/"); Optional ns = model.getNamespace("ex"); @@ -89,10 +89,10 @@ void testSetNamespaceWithStringArgs() { } /** - * Teste la méthode setNamespace(Namespace namespace). + * Tests the setNamespace(Namespace namespace) method. */ @Test - @DisplayName("setNamespace(Namespace) doit ajouter un namespace") + @DisplayName("setNamespace(Namespace) should add a namespace") void testSetNamespaceWithNamespaceObject() { Namespace newNs = new CreateNamespace("geosparql", "http://example.org/ont/geosparql#"); model.setNamespace(newNs); @@ -104,10 +104,10 @@ void testSetNamespaceWithNamespaceObject() { } /** - * Teste la méthode getNamespaces(). + * Tests the getNamespaces() method. */ @Test - @DisplayName("getNamespaces() doit retourner tous les namespaces définis") + @DisplayName("getNamespaces() should return all defined namespaces") void testGetNamespaces() { model.setNamespace("ex1", "http://example.org/ns1/"); model.setNamespace("ex2", "http://example.org/ns2/"); @@ -120,10 +120,10 @@ void testGetNamespaces() { } /** - * Teste la méthode removeNamespace(). + * Tests the removeNamespace() method. */ @Test - @DisplayName("removeNamespace() doit supprimer un namespace et retourner un Optional du namespace supprimé") + @DisplayName("removeNamespace() should remove a namespace and return an Optional of the removed namespace") void testRemoveNamespace() { Namespace testNs = new CreateNamespace("ex", "http://example.org/ns/"); model.setNamespace("ex", "http://example.org/ns/"); @@ -132,7 +132,7 @@ void testRemoveNamespace() { Optional removedNs = model.removeNamespace("ex"); assertTrue(removedNs.isPresent()); - // Comparaison + // Comparison assertEquals(testNs.getPrefix(), removedNs.get().getPrefix()); assertEquals(testNs.getName(), removedNs.get().getName()); @@ -144,10 +144,10 @@ void testRemoveNamespace() { } /** - * Teste la méthode clearNamespaces(). + * Tests the clearNamespaces() method. */ @Test - @DisplayName("clearNamespaces() doit supprimer tous les namespaces") + @DisplayName("clearNamespaces() should remove all namespaces") void testClearNamespaces() { model.setNamespace("ex1", "http://example.org/ns1/"); model.setNamespace("ex2", "http://example.org/ns2/"); @@ -159,10 +159,10 @@ void testClearNamespaces() { } /** - * Teste l'ajout d'une déclaration et sa présence via contains(Statement). + * Tests adding a statement and its presence via contains(Statement). */ @Test - @DisplayName("add() et contains() doivent fonctionner pour les déclarations") + @DisplayName("add() and contains() should work for statements") void testAddAndContainsStatement() { Statement stmt = new SimpleStatement(subject1, predicate1, object1, null); @@ -175,10 +175,10 @@ void testAddAndContainsStatement() { } /** - * Teste la suppression d'une déclaration via remove(Object). + * Tests removing a statement via remove(Object). */ @Test - @DisplayName("remove() doit supprimer une déclaration existante") + @DisplayName("remove() should remove an existing statement") void testRemoveStatement() { Statement stmt = new SimpleStatement(subject1, predicate1, object1, null); model.add(stmt); @@ -190,10 +190,10 @@ void testRemoveStatement() { } /** - * Teste la méthode isEmpty(). + * Tests the isEmpty() method. */ @Test - @DisplayName("isEmpty() doit refléter l'état vide ou non vide du modèle") + @DisplayName("isEmpty() should reflect the empty or non-empty state of the model") void testIsEmpty() { assertTrue(model.isEmpty()); model.add(subject1, predicate1, object1); @@ -203,10 +203,10 @@ void testIsEmpty() { } /** - * Teste la vue subjects(). + * Tests the subjects() view. */ @Test - @DisplayName("subjects() doit retourner un ensemble unique de sujets et gérer les suppressions") + @DisplayName("subjects() should return a unique set of subjects and handle removals") void testSubjectsView() { model.add(subject1, predicate1, object1); model.add(subject2, predicate1, object1); @@ -225,10 +225,10 @@ void testSubjectsView() { } /** - * Teste la vue predicates(). + * Tests the predicates() view. */ @Test - @DisplayName("predicates() doit retourner un ensemble unique de prédicats") + @DisplayName("predicates() should return a unique set of predicates") void testPredicatesView() { model.add(subject1, predicate1, object1); model.add(subject1, predicate2, object1); @@ -241,10 +241,10 @@ void testPredicatesView() { } /** - * Teste la vue objects(). + * Tests the objects() view. */ @Test - @DisplayName("objects() doit retourner un ensemble unique d'objets") + @DisplayName("objects() should return a unique set of objects") void testObjectsView() { model.add(subject1, predicate1, object1); model.add(subject1, predicate1, object2); @@ -257,10 +257,10 @@ void testObjectsView() { } /** - * Teste la vue contexts(). + * Tests the contexts() view. */ @Test - @DisplayName("contexts() doit retourner un ensemble unique de contextes (y compris null)") + @DisplayName("contexts() should return a unique set of contexts (including null)") void testContextsView() { model.add(subject1, predicate1, object1, context1); model.add(subject1, predicate1, object1, context2); @@ -274,10 +274,10 @@ void testContextsView() { } /** - * Teste la méthode getStatements() pour filtrer les déclarations. + * Tests the getStatements() method to filter statements. */ @Test - @DisplayName("getStatements() doit filtrer les déclarations par critère") + @DisplayName("getStatements() should filter statements by criteria") void testFilterStatements() { Statement stmt1 = new SimpleStatement(subject1, predicate1, object1, context1); Statement stmt2 = new SimpleStatement(subject1, predicate2, object2, context1); @@ -311,10 +311,10 @@ void testFilterStatements() { } /** - * Teste la méthode clear() avec un ou plusieurs contextes spécifiés. + * Tests the clear() method with one or more specified contexts. */ @Test - @DisplayName("clear(contextes) doit supprimer les déclarations dans les contextes spécifiés") + @DisplayName("clear(contexts) should remove statements in the specified contexts") void testClearWithContext() { Statement stmt1 = new SimpleStatement(subject1, predicate1, object1, context1); Statement stmt2 = new SimpleStatement(subject1, predicate1, object1, context2); @@ -338,10 +338,10 @@ void testClearWithContext() { } /** - * Teste les méthodes addAll() et removeAll(). + * Tests the addAll() and removeAll() methods. */ @Test - @DisplayName("addAll() et removeAll() doivent gérer les collections de déclarations") + @DisplayName("addAll() and removeAll() should handle collections of statements") void testAddAllAndRemoveAll() { Statement stmt1 = new SimpleStatement(subject1, predicate1, object1, null); Statement stmt2 = new SimpleStatement(subject2, predicate2, object2, null); @@ -359,10 +359,10 @@ void testAddAllAndRemoveAll() { } /** - * Teste la méthode retainAll(). + * Tests the retainAll() method. */ @Test - @DisplayName("retainAll() doit conserver uniquement les déclarations spécifiées") + @DisplayName("retainAll() should keep only the specified statements") void testRetainAll() { Statement stmt1 = new SimpleStatement(subject1, predicate1, object1, null); Statement stmt2 = new SimpleStatement(subject2, predicate2, object2, null); @@ -383,10 +383,10 @@ void testRetainAll() { } /** - * Teste les méthodes toArray() et toArray(T[]). + * Tests the toArray() and toArray(T[]) methods. */ @Test - @DisplayName("toArray() doit retourner un tableau des déclarations") + @DisplayName("toArray() should return an array of statements") void testToArray() { Statement stmt1 = new SimpleStatement(subject1, predicate1, object1, null); Statement stmt2 = new SimpleStatement(subject2, predicate2, object2, null); @@ -405,16 +405,16 @@ void testToArray() { Statement[] smallArray = new Statement[1]; Statement[] result = model.toArray(smallArray); - assertNotSame(smallArray, result); + assertNotSame(smallArray, result); // Should return a new array if the provided array is too small assertEquals(2, result.length); } /** - * Teste la méthode getStatements() pour filtrer les déclarations. - * Vérifie que le filtrage fonctionne correctement avec différents critères. + * Tests the getStatements() method to filter statements. + * Verifies that filtering works correctly with different criteria. */ @Test - @DisplayName("getStatements() doit filtrer les déclarations par critère (y compris filtrage par objet seul)") + @DisplayName("getStatements() should filter statements by criteria (including filtering by object only)") void testFilterStatementsExtended() { Statement stmt1 = new SimpleStatement(subject1, predicate1, object1, context1); Statement stmt2 = new SimpleStatement(subject1, predicate2, object2, context1); @@ -426,24 +426,20 @@ void testFilterStatementsExtended() { model.add(stmt3); model.add(stmt4); - // Filtrage par sujet et contexte Model filteredBySubjectAndContext = model.filter(subject1, null, null, context1); assertEquals(2, filteredBySubjectAndContext.size()); assertTrue(filteredBySubjectAndContext.contains(stmt1)); assertTrue(filteredBySubjectAndContext.contains(stmt2)); - // Filtrage par sujet et prédicat Model filteredBySubjectAndPredicate = model.filter(subject1, predicate1, null); assertEquals(1, filteredBySubjectAndPredicate.size()); assertFalse(filteredBySubjectAndPredicate.contains(stmt1)); assertTrue(filteredBySubjectAndPredicate.contains(stmt4)); - // Filtrage par contexte null Model filteredByNullContext = model.filter(null, null, null, null); assertEquals(1, filteredByNullContext.size()); assertTrue(filteredByNullContext.contains(stmt4)); - // NOUVEAU TEST: Filtrage par objet seul Model filteredByObject = model.filter(null, null, object1); assertEquals(1, filteredByObject.size()); assertFalse(filteredByObject.contains(stmt1)); @@ -453,9 +449,9 @@ void testFilterStatementsExtended() { } /** - * Classe concrète interne qui hérite de AbstractModel. - * Cette implémentation est nécessaire car AbstractModel est abstraite et ne peut pas être instanciée directement. - * Elle utilise un LinkedHashSet pour stocker les déclarations, ce qui est simple et préserve l'ordre d'insertion. + * Internal concrete class that inherits from AbstractModel. + * This implementation is necessary because AbstractModel is abstract and cannot be instantiated directly. + * It uses a LinkedHashSet to store statements, which is simple and preserves insertion order. */ private static class ConcreteModel extends AbstractModel { private final Set statements = new LinkedHashSet<>(); @@ -510,7 +506,7 @@ public void removeTermIteration(Iterator iter, Resource subj, IRI pre } } - // Crée une nouvelle instance de ConcreteModel pour contenir les résultats filtrés + // Creates a new instance of ConcreteModel to hold the filtered results @Override public Model filter(Resource subject, IRI predicate, Value object, Resource... contexts) { @@ -522,7 +518,6 @@ public Model filter(Resource subject, IRI predicate, Value object, Resource... c filteredModel.add(stmt); } } - // Retourne le nouveau modèle filtré return filteredModel; } @@ -550,18 +545,16 @@ public Optional removeNamespace(String prefix) { @Override public void clear() { - - // Vide les statements + // Clears statements statements.clear(); - - // Vide les namespaces + // Clears namespaces namespaces.clear(); } } /** - * Implémentation simplifiée de l'interface Statement pour les tests. + * Simplified implementation of the Statement interface for testing. */ private static class SimpleStatement implements Statement { @@ -600,7 +593,7 @@ public Resource getContext() { } /** - * Implémentation simplifiée des interfaces Resource, IRI, Value, Namespace pour les tests. + * Simplified implementation of the Resource, IRI, Value, Namespace interfaces for testing. */ private static class CreateResource implements Resource { private final String uri; @@ -617,8 +610,8 @@ public String stringValue() { } /** - * Implémentation simplifiée de l'interface IRI pour les tests. - * Hérite de Resource et Value, et implémente les méthodes spécifiques à IRI. + * Simplified implementation of the IRI interface for testing. + * Inherits from Resource and Value, and implements IRI-specific methods. */ private static class CreateIRI implements IRI { private final String uriString; @@ -634,8 +627,7 @@ public String stringValue() { @Override public String getNamespace() { - // Logique pour extraire la partie "namespace" de l'IRI - // Cherche le dernier '#' ou '/' comme délimiteur + int hashIdx = uriString.lastIndexOf('#'); int slashIdx = uriString.lastIndexOf('/'); int splitIdx = Math.max(hashIdx, slashIdx); @@ -643,15 +635,11 @@ public String getNamespace() { if (splitIdx > -1) { return uriString.substring(0, splitIdx + 1); } - // Retourne une chaîne vide si aucun délimiteur commun n'est trouvé, - // ou si l'IRI est juste un nom local sans préfixe de namespace évident. return ""; } @Override public String getLocalName() { - // Logique pour extraire la partie "nom local" de l'IRI - // Cherche le dernier '#' ou '/' comme délimiteur int hashIdx = uriString.lastIndexOf('#'); int slashIdx = uriString.lastIndexOf('/'); int splitIdx = Math.max(hashIdx, slashIdx); @@ -659,14 +647,13 @@ public String getLocalName() { if (splitIdx > -1) { return uriString.substring(splitIdx + 1); } - // Si pas de délimiteur, l'URI entière est considérée comme le nom local return uriString; } } /** - * Implémentation simplifiée de l'interface Value pour les tests, représentant un littéral. + * Simplified implementation of the Value interface for testing, representing a literal. */ private static class CreateValue implements Value { private final String literal; @@ -683,12 +670,12 @@ public String stringValue() { } /** - * Implémentation simplifiée de l'interface Namespace pour les tests. - * Implémente Comparable pour permettre le tri et la comparaison. + * Simplified implementation of the Namespace interface for testing. + * Implements Comparable to allow sorting and comparison. */ private static class CreateNamespace implements Namespace { private final String prefix; - // L'URI du namespace + private final String name; public CreateNamespace(String prefix, String name) { @@ -708,12 +695,12 @@ public String getName() { @Override public int compareTo(Namespace other) { - // Compare d'abord par le préfixe pour un ordre stable + int prefixComparison = this.getPrefix().compareTo(other.getPrefix()); if (prefixComparison != 0) { return prefixComparison; } - // Si les préfixes sont égaux, compare par le nom (URI) + return this.getName().compareTo(other.getName()); } } diff --git a/src/test/java/fr/inria/corese/core/next/impl/temp/CoreseModelTest.java b/src/test/java/fr/inria/corese/core/next/impl/temp/CoreseModelTest.java index 70822d34c..ebcded5d4 100644 --- a/src/test/java/fr/inria/corese/core/next/impl/temp/CoreseModelTest.java +++ b/src/test/java/fr/inria/corese/core/next/impl/temp/CoreseModelTest.java @@ -66,7 +66,7 @@ class CoreseModelTest { @BeforeEach void setUp() { - // Initialise l'instance de CoreseModel pour les tests + // Initializes the CoreseModel instance for testing coreseModel = new CoreseModel(mockCoreseGraph, new HashSet<>()); when(mockValueConverter.toCoreseNode(mockSubjectIRI)).thenReturn(mockSubjectNode); @@ -125,40 +125,40 @@ void setUp() { when(mockContextResource.stringValue()).thenReturn("http://example.org/graphContext"); } - // Tests du Constructeur + // Constructor Tests /** - * Teste que le constructeur par défaut crée un modèle vide, - * sans aucune déclaration ni espace de noms. + * Tests that the default constructor creates an empty model, + * without any statements or namespaces. */ @Test - @DisplayName("Devrait créer un modèle vide avec le constructeur par défaut") + @DisplayName("Should create an empty model with the default constructor") void testDefaultConstructor() { CoreseModel newModel = new CoreseModel(); - assertEquals(0, newModel.size(), "Le constructeur par défaut devrait créer un modèle vide."); - assertTrue(newModel.getNamespaces().isEmpty(), "Le constructeur par défaut devrait créer un modèle sans espace de noms."); + assertEquals(0, newModel.size(), "The default constructor should create an empty model."); + assertTrue(newModel.getNamespaces().isEmpty(), "The default constructor should create a model without namespaces."); } /** - * Teste que le constructeur prenant un argument 'Graph' crée un modèle utilisant le graphe fourni - * et s'initialise sans espace de noms. + * Tests that the constructor taking a 'Graph' argument creates a model using the provided graph + * and initializes without namespaces. */ @Test - @DisplayName("Devrait créer un modèle avec le graphe donné et des espaces de noms vides avec le constructeur Graph") + @DisplayName("Should create a model with the given graph and empty namespaces with the Graph constructor") void testGraphConstructor() { CoreseModel newModel = new CoreseModel(mockCoreseGraph); - assertEquals(mockCoreseGraph, newModel.getCoreseGraph(), "Le constructeur Graph devrait utiliser le graphe fourni."); - assertTrue(newModel.getNamespaces().isEmpty(), "Le constructeur Graph devrait créer un modèle sans espace de noms par défaut."); + assertEquals(mockCoreseGraph, newModel.getCoreseGraph(), "The Graph constructor should use the provided graph."); + assertTrue(newModel.getNamespaces().isEmpty(), "The Graph constructor should create a model without default namespaces."); } /** - * Teste que le constructeur prenant à la fois un Graph et un Set d'espaces de noms - * initialise correctement le modèle avec les deux. + * Tests that the constructor taking both a Graph and a Set of namespaces + * correctly initializes the model with both. */ @Test - @DisplayName("Devrait créer un modèle avec le graphe et les espaces de noms donnés avec le constructeur complet") + @DisplayName("Should create a model with the given graph and namespaces with the full constructor") void testFullConstructor() { Set testNamespaces = new HashSet<>(); Namespace ns1 = mock(Namespace.class); @@ -168,17 +168,17 @@ void testFullConstructor() { CoreseModel newModel = new CoreseModel(mockCoreseGraph, testNamespaces); - assertEquals(mockCoreseGraph, newModel.getCoreseGraph(), "Le constructeur complet devrait utiliser le graphe fourni."); - assertEquals(1, newModel.getNamespaces().size(), "Le constructeur complet devrait définir les espaces de noms fournis."); - assertTrue(newModel.getNamespaces().contains(ns1), "Le constructeur complet devrait inclure l'espace de noms fourni."); + assertEquals(mockCoreseGraph, newModel.getCoreseGraph(), "The full constructor should use the provided graph."); + assertEquals(1, newModel.getNamespaces().size(), "The full constructor should set the provided namespaces."); + assertTrue(newModel.getNamespaces().contains(ns1), "The full constructor should include the provided namespace."); } /** - * Teste que le constructeur qui ne prend qu'un Set d'espaces de noms - * initialise correctement le modèle avec ces espaces de noms et un graphe vide (sans déclarations). + * Tests that the constructor taking only a Set of namespaces + * correctly initializes the model with these namespaces and an empty graph (no statements). */ @Test - @DisplayName("Devrait créer un modèle avec les espaces de noms donnés et un graphe vide avec le constructeur Namespaces") + @DisplayName("Should create a model with the given namespaces and an empty graph with the Namespaces constructor") void testConstructor_FromNamespaces() { Set initialNamespaces = new HashSet<>(); Namespace ns1 = mock(Namespace.class); @@ -193,21 +193,21 @@ void testConstructor_FromNamespaces() { CoreseModel newModel = new CoreseModel(initialNamespaces); - assertEquals(0, newModel.size(), "Le modèle créé avec les espaces de noms devrait être vide en termes de déclarations."); - assertEquals(initialNamespaces.size(), newModel.getNamespaces().size(), "Le modèle devrait contenir le nombre d'espaces de noms fourni."); - assertTrue(newModel.getNamespaces().containsAll(initialNamespaces), "Le modèle devrait contenir tous les espaces de noms fournis."); - assertTrue(initialNamespaces.containsAll(newModel.getNamespaces()), "Le modèle ne devrait pas contenir d'espaces de noms supplémentaires."); + assertEquals(0, newModel.size(), "The model created with namespaces should be empty in terms of statements."); + assertEquals(initialNamespaces.size(), newModel.getNamespaces().size(), "The model should contain the number of provided namespaces."); + assertTrue(newModel.getNamespaces().containsAll(initialNamespaces), "The model should contain all provided namespaces."); + assertTrue(initialNamespaces.containsAll(newModel.getNamespaces()), "The model should not contain additional namespaces."); } /** - * Teste que le constructeur prenant une collection de Statements copie correctement - * ces déclarations dans le nouveau modèle. + * Tests that the constructor taking a collection of Statements correctly copies + * these statements into the new model. */ @Test - @DisplayName("Devrait créer un modèle en copiant les déclarations d'une collection") + @DisplayName("Should create a model by copying statements from a collection") void testConstructorFromStatements() { - // Configure les déclarations mock pour avoir des propriétés de base + // Configure mock statements to have basic properties when(statement0.getSubject()).thenReturn(mockSubjectIRI); when(statement0.getPredicate()).thenReturn(mockPredicateIRI); when(statement0.getObject()).thenReturn(mockObjectValue); @@ -220,17 +220,17 @@ void testConstructorFromStatements() { CoreseModel newModel = new CoreseModel(statements); - assertEquals(2, newModel.size(), "Le modèle devrait contenir 2 déclarations après la construction à partir de la collection."); + assertEquals(2, newModel.size(), "The model should contain 2 statements after construction from the collection."); } - // Tests d'ajout + // Add Tests /** - * Teste que l'ajout d'une déclaration sans contexte appelle avec succès - * la méthode addEdge du graphe et retourne true. + * Tests that adding a statement without context successfully calls + * the graph's addEdge method and returns true. */ @Test - @DisplayName("Devrait ajouter une déclaration sans contexte avec succès") + @DisplayName("Should successfully add a statement without context") void testAddStatementWithoutContext() { IRI realSubjectIRI = new CoreseIRI("http://example.org/realSubject"); @@ -241,17 +241,17 @@ void testAddStatementWithoutContext() { boolean added = coreseModel.add(realSubjectIRI, realPredicateIRI, realObjectIRI); - assertTrue(added, "La déclaration devrait être ajoutée avec succès sans contexte."); + assertTrue(added, "The statement should be added successfully without context."); verify(mockCoreseGraph, times(1)).addEdge(any(Node.class), any(Node.class), any(Node.class)); verify(mockCoreseGraph, never()).addEdge(any(Node.class), any(Node.class), any(Node.class), any(Node.class)); } /** - * Teste que l'ajout d'une déclaration avec un seul contexte appelle avec succès - * la méthode addEdge du graphe(avec contexte) et retourne true. + * Tests that adding a statement with a single context successfully calls + * the graph's addEdge method (with context) and returns true. */ @Test - @DisplayName("Devrait ajouter une déclaration avec un seul contexte avec succès") + @DisplayName("Should successfully add a statement with a single context") void testAddStatementWithSingleContext() { IRI realSubject = new CoreseIRI("http://example.org/realSubjectWithContext"); @@ -263,19 +263,19 @@ void testAddStatementWithSingleContext() { boolean added = coreseModel.add(realSubject, realPredicate, realObject, realContext); - assertTrue(added, "La déclaration devrait être ajoutée avec succès avec un seul contexte."); + assertTrue(added, "The statement should be added successfully with a single context."); verify(mockCoreseGraph, times(1)).addEdge(any(Node.class), any(Node.class), any(Node.class), any(Node.class)); verify(mockCoreseGraph, never()).addEdge(any(Node.class), any(Node.class), any(Node.class)); } /** - * Teste que l'ajout d'une déclaration avec plusieurs contextes - * entraîne plusieurs appels à la méthode addEdge du graphe(un pour chaque contexte) - * et retourne true. + * Tests that adding a statement with multiple contexts + * results in multiple calls to the graph's addEdge method (one for each context) + * and returns true. */ @Test - @DisplayName("Devrait ajouter une déclaration avec plusieurs contextes avec succès") + @DisplayName("Should successfully add a statement with multiple contexts") void testAddStatementWithMultipleContexts() { IRI realSubject = new CoreseIRI("http://example.org/realSubjectMultiContext"); @@ -288,18 +288,18 @@ void testAddStatementWithMultipleContexts() { boolean added = coreseModel.add(realSubject, realPredicate, realObject, realContext1, realContext2); - assertTrue(added, "La déclaration devrait être ajoutée avec succès avec plusieurs contextes."); + assertTrue(added, "The statement should be added successfully with multiple contexts."); verify(mockCoreseGraph, times(2)).addEdge(any(Node.class), any(Node.class), any(Node.class), any(Node.class)); verify(mockCoreseGraph, never()).addEdge(any(Node.class), any(Node.class), any(Node.class)); } /** - * Teste que la méthode add retourne false si aucun changement n'a eu lieu - * (par exemple, si la déclaration existe déjà dans le graphe). + * Tests that the add method returns false if no change occurred + * (e.g., if the statement already exists in the graph). */ @Test - @DisplayName("Devrait retourner false si aucun changement n'est survenu lors de l'ajout (déclaration existante)") + @DisplayName("Should return false if no change occurred during add (existing statement)") void testAddStatementNoChange() { IRI realSubject = new CoreseIRI("http://example.org/realSubjectNoChange"); @@ -310,21 +310,21 @@ void testAddStatementNoChange() { boolean added = coreseModel.add(realSubject, realPredicate, realObject); - assertFalse(added, "La méthode add devrait retourner false si aucun changement n'est survenu."); + assertFalse(added, "The add method should return false if no change occurred."); verify(mockCoreseGraph, times(1)).addEdge(any(Node.class), any(Node.class), any(Node.class)); verify(mockCoreseGraph, never()).addEdge(any(Node.class), any(Node.class), any(Node.class), any(Node.class)); } - //Tests de Contient + // Contains Tests /** - * Teste que le modèle retourne true si une déclaration existante sans contexte est présente. - * Vérifie également que la méthode de recherche - * est appelée correctement. + * Tests that the model returns true if an existing statement without context is present. + * Also verifies that the search method + * is called correctly. */ @Test - @DisplayName("Devrait retourner true si le modèle contient une déclaration existante sans contexte") + @DisplayName("Should return true if the model contains an existing statement without context") void testContainsExistingStatementWithoutContext() { fr.inria.corese.core.kgram.api.core.Edge mockReturnedEdge = mock(fr.inria.corese.core.kgram.api.core.Edge.class); List edges = List.of(mockReturnedEdge); @@ -338,7 +338,7 @@ void testContainsExistingStatementWithoutContext() { when(mockCoreseGraph.getEdgeFactory()).thenReturn(mock(fr.inria.corese.core.EdgeFactory.class)); when(mockCoreseGraph.getEdgeFactory().copy(any(fr.inria.corese.core.kgram.api.core.Edge.class))).thenReturn(mockReturnedEdge); - // Appelle la méthode contains + // Call the contains method boolean contains = coreseModel.contains(realSubject, realPredicate, realObject); assertTrue(contains, "Le modèle devrait contenir la déclaration sans contexte."); @@ -346,11 +346,11 @@ void testContainsExistingStatementWithoutContext() { } /** - * Teste que le modèle retourne false si la déclaration n'est pas présente. - * Vérifie que la méthode de recherche est bien appelée. + * Tests that the model returns false if the statement is not present. + * Verifies that the search method is called correctly. */ @Test - @DisplayName("Devrait retourner false si le modèle ne contient pas la déclaration") + @DisplayName("Should return false if the model does not contain the statement") void testContainsStatementNotPresent() { IRI realSubject = new CoreseIRI("http://example.org/subjectNotFound"); @@ -370,12 +370,12 @@ void testContainsStatementNotPresent() { /** - * Teste que le modèle retourne true si une déclaration existante avec un contexte spécifique est trouvée. - * Vérifie que la méthode de recherche - * est appelée avec le contexte. + * Tests that the model returns true if an existing statement with a specific context is found. + * Verifies that the search method + * is called with the context. */ @Test - @DisplayName("Devrait retourner true si le modèle contient une déclaration existante avec un contexte spécifique") + @DisplayName("Should return true if the model contains an existing statement with a specific context") void testContainsExistingStatementWithContext() { fr.inria.corese.core.kgram.api.core.Edge mockReturnedEdge = mock(fr.inria.corese.core.kgram.api.core.Edge.class); List edges = List.of(mockReturnedEdge); @@ -383,7 +383,7 @@ void testContainsExistingStatementWithContext() { IRI realSubject = new CoreseIRI("http://example.org/subjectWithSpecificContext"); IRI realPredicate = new CoreseIRI("http://example.org/predicateWithSpecificContext"); IRI realObject = new CoreseIRI("http://example.org/objectWithSpecificContext"); - Resource realContext = new CoreseIRI("http://example.org/specificGraphContext"); // Supposant que le contexte est un IRI + Resource realContext = new CoreseIRI("http://example.org/specificGraphContext"); // Assuming the context is an IRI when(mockCoreseGraph.getEdgesRDF4J(any(Node.class), any(Node.class), any(Node.class), any(Node.class))) .thenReturn(edges); @@ -392,17 +392,17 @@ void testContainsExistingStatementWithContext() { boolean contains = coreseModel.contains(realSubject, realPredicate, realObject, realContext); - assertTrue(contains, "Le modèle devrait contenir la déclaration avec le contexte spécifié."); + assertTrue(contains, "The model should contain the statement with the specified context."); verify(mockCoreseGraph, times(1)).getEdgesRDF4J(any(Node.class), any(Node.class), any(Node.class), any(Node.class)); } /** - * Teste que le modèle retourne false si un triple non existant est recherché. - * Vérifie que la méthode de recherche est appelée. + * Tests that the model returns false if a non-existent triple is searched for. + * Verifies that the search method is called. */ @Test - @DisplayName("Devrait retourner false si le modèle ne contient pas un triple non existant") + @DisplayName("Should return false if the model does not contain a non-existent triple") void testContainsNonExistingTriple() { CoreseAdaptedValueFactory vf = new CoreseAdaptedValueFactory(); @@ -417,4 +417,4 @@ void testContainsNonExistingTriple() { assertFalse(coreseModel.contains(georgeBrassens, rdfType, singer), "Le modèle ne devrait pas contenir un triple non existant."); verify(mockCoreseGraph, times(0)).getEdgesRDF4J(any(Node.class), any(Node.class), any(Node.class), eq(null)); } -} \ No newline at end of file +}