diff --git a/src/main/java/fr/inria/corese/core/next/api/IPrefixHandler.java b/src/main/java/fr/inria/corese/core/next/api/IPrefixHandler.java index 8a205fba5..e2555c2d5 100644 --- a/src/main/java/fr/inria/corese/core/next/api/IPrefixHandler.java +++ b/src/main/java/fr/inria/corese/core/next/api/IPrefixHandler.java @@ -37,6 +37,14 @@ public interface IPrefixHandler { */ boolean hasPrefix(String prefix); + /** + * Checks if a namespace has a prefix + * + * @param namespace the namespace to check + * @return true if the namespace exists in mappings, false otherwise + */ + boolean hasNamespace(String namespace); + /** * Returns all registered prefixes. * Order of iteration is implementation-dependent but should be consistent @@ -60,6 +68,13 @@ public interface IPrefixHandler { */ Map getPrefixMap(); + /** + * Returns all namespaces mappings as an unmodifiable map. + * + * @return an unmodifiable map where keys are namespaces IRIs and values are prefixes + */ + Map getNamespaceMap(); + /** * Returns all namespace objects as an immutable set. * Each Namespace object contains both prefix and IRI. diff --git a/src/main/java/fr/inria/corese/core/next/api/io/common/BaseIRIOptions.java b/src/main/java/fr/inria/corese/core/next/api/io/common/BaseIRIOptions.java index add748be1..9205cfd14 100644 --- a/src/main/java/fr/inria/corese/core/next/api/io/common/BaseIRIOptions.java +++ b/src/main/java/fr/inria/corese/core/next/api/io/common/BaseIRIOptions.java @@ -1,6 +1,5 @@ package fr.inria.corese.core.next.api.io.common; -import fr.inria.corese.core.next.api.io.IOOptions; /** * Options for RDF parsers and serializers that support a base IRI. diff --git a/src/main/java/fr/inria/corese/core/next/api/io/serializer/BlankNodeIdGenerationOptions.java b/src/main/java/fr/inria/corese/core/next/api/io/serializer/BlankNodeIdGenerationOptions.java new file mode 100644 index 000000000..5fe2d6379 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/api/io/serializer/BlankNodeIdGenerationOptions.java @@ -0,0 +1,14 @@ +package fr.inria.corese.core.next.api.io.serializer; + +/** + * Interface for options that determine the generation of blank node Ids for serializers. + */ +public interface BlankNodeIdGenerationOptions { + + /** + * Checks if deterministic blank node IDs should be generated. + * + * @return {@code true} if stable blank node IDs are enabled, {@code false} otherwise. + */ + boolean stableBlankNodeIds(); +} diff --git a/src/main/java/fr/inria/corese/core/next/api/io/serializer/DatatypePolicyOptions.java b/src/main/java/fr/inria/corese/core/next/api/io/serializer/DatatypePolicyOptions.java new file mode 100644 index 000000000..b21dcaa48 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/api/io/serializer/DatatypePolicyOptions.java @@ -0,0 +1,17 @@ +package fr.inria.corese.core.next.api.io.serializer; + +import fr.inria.corese.core.next.impl.io.serialization.option.LiteralDatatypePolicyEnum; + +/** + * Interface for serializer options to determine the policy for the literal datatypes + */ +public interface DatatypePolicyOptions { + + + /** + * Returns the policy for how literal datatypes are printed. + * + * @return The {@link LiteralDatatypePolicyEnum} indicating the literal datatype serialization policy. + */ + LiteralDatatypePolicyEnum getLiteralDatatypePolicy(); +} diff --git a/src/main/java/fr/inria/corese/core/next/api/io/serializer/LineEndingOptions.java b/src/main/java/fr/inria/corese/core/next/api/io/serializer/LineEndingOptions.java new file mode 100644 index 000000000..2b0478b2a --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/api/io/serializer/LineEndingOptions.java @@ -0,0 +1,13 @@ +package fr.inria.corese.core.next.api.io.serializer; + +/** + * Interface to specify which line ending a serializer must use. + */ +public interface LineEndingOptions { + + /** + * + * @return the end line characters + */ + String getLineEnding(); +} diff --git a/src/main/java/fr/inria/corese/core/next/api/io/serializer/PrettyPrintOptions.java b/src/main/java/fr/inria/corese/core/next/api/io/serializer/PrettyPrintOptions.java new file mode 100644 index 000000000..ad371aae0 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/api/io/serializer/PrettyPrintOptions.java @@ -0,0 +1,51 @@ +package fr.inria.corese.core.next.api.io.serializer; + +import fr.inria.corese.core.next.impl.io.serialization.option.PrefixOrderingEnum; + +/** + * Interface for the options of serializer allowing for pretty printing. + */ +public interface PrettyPrintOptions { + + /** + * Returns the string used for indentation when pretty-printing. + * + * @return The indentation string. + */ + String getIndent(); + + /** + * Checks if human-readable formatting (pretty-printing) is enabled. + * + * @return {@code true} if pretty-printing is enabled, {@code false} otherwise. + */ + boolean prettyPrint(); + + /** + * Returns the maximum desired line length before the serializer attempts to break lines. + * + * @return The maximum line length. + */ + int getMaxLineLength(); + + /** + * Checks if subjects should be sorted alphabetically in the output. + * + * @return {@code true} if subject sorting is enabled, {@code false} otherwise. + */ + boolean sortSubjects(); + + /** + * Checks if predicates should be sorted alphabetically within a subject group. + * + * @return {@code true} if predicate sorting is enabled, {@code false} otherwise. + */ + boolean sortPredicates(); + + /** + * Returns the policy for ordering prefix declarations. + * + * @return The {@link PrefixOrderingEnum} for prefix ordering. + */ + PrefixOrderingEnum getPrefixOrdering(); +} diff --git a/src/main/java/fr/inria/corese/core/next/api/io/serialization/RDFSerializer.java b/src/main/java/fr/inria/corese/core/next/api/io/serializer/RDFSerializer.java similarity index 96% rename from src/main/java/fr/inria/corese/core/next/api/io/serialization/RDFSerializer.java rename to src/main/java/fr/inria/corese/core/next/api/io/serializer/RDFSerializer.java index 1e1501e4f..5fd4a2da4 100644 --- a/src/main/java/fr/inria/corese/core/next/api/io/serialization/RDFSerializer.java +++ b/src/main/java/fr/inria/corese/core/next/api/io/serializer/RDFSerializer.java @@ -1,4 +1,4 @@ -package fr.inria.corese.core.next.api.io.serialization; +package fr.inria.corese.core.next.api.io.serializer; import java.io.Writer; @@ -37,7 +37,7 @@ public interface RDFSerializer { */ default String getFormatName() { return getRDFFormat().getName(); - }; + } /** * Gets the RDF format that this serializer generates. diff --git a/src/main/java/fr/inria/corese/core/next/api/io/serialization/SerializerFactory.java b/src/main/java/fr/inria/corese/core/next/api/io/serializer/SerializerFactory.java similarity index 96% rename from src/main/java/fr/inria/corese/core/next/api/io/serialization/SerializerFactory.java rename to src/main/java/fr/inria/corese/core/next/api/io/serializer/SerializerFactory.java index 2deb675b4..7560ff072 100644 --- a/src/main/java/fr/inria/corese/core/next/api/io/serialization/SerializerFactory.java +++ b/src/main/java/fr/inria/corese/core/next/api/io/serializer/SerializerFactory.java @@ -1,4 +1,4 @@ -package fr.inria.corese.core.next.api.io.serialization; +package fr.inria.corese.core.next.api.io.serializer; import fr.inria.corese.core.next.api.Model; import fr.inria.corese.core.next.api.base.io.RDFFormat; diff --git a/src/main/java/fr/inria/corese/core/next/api/io/serializer/UsesPrefixOptions.java b/src/main/java/fr/inria/corese/core/next/api/io/serializer/UsesPrefixOptions.java new file mode 100644 index 000000000..5059bb46f --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/api/io/serializer/UsesPrefixOptions.java @@ -0,0 +1,35 @@ +package fr.inria.corese.core.next.api.io.serializer; + +import fr.inria.corese.core.next.impl.common.prefix.PrefixHandler; +import fr.inria.corese.core.next.impl.io.serialization.option.PrefixOrderingEnum; + +/** + * Interface for the options of serializer that can declare prefixes + */ +public interface UsesPrefixOptions { + + /** + * Checks if prefix declarations should be used for compact IRIs. + * + * @return {@code true} if prefixes are used, {@code false} otherwise. + */ + boolean usePrefixes(); + /** + * Checks if the serializer should automatically discover and declare prefixes. + * + * @return {@code true} if auto-declaration is enabled, {@code false} otherwise. + */ + boolean autoDeclarePrefixes(); + /** + * Returns the policy for ordering prefix declarations. + * + * @return The {@link PrefixOrderingEnum} for prefix ordering. + */ + PrefixOrderingEnum getPrefixOrdering(); + /** + * Returns an unmodifiable map of custom URI prefixes. + * + * @return The {@link PrefixHandler} managing all prefix mappings. + */ + PrefixHandler getPrefixHandler(); +} diff --git a/src/main/java/fr/inria/corese/core/next/impl/common/prefix/PrefixHandler.java b/src/main/java/fr/inria/corese/core/next/impl/common/prefix/PrefixHandler.java index 321ede195..9e2722fcd 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/common/prefix/PrefixHandler.java +++ b/src/main/java/fr/inria/corese/core/next/impl/common/prefix/PrefixHandler.java @@ -45,6 +45,15 @@ public PrefixHandler(boolean includeStandardVocabularies) { } } + /** + * Copy constructor + */ + public PrefixHandler(PrefixHandler oHandler) { + this.prefixToNamespace = new ConcurrentHashMap<>(oHandler.prefixToNamespace); + this.namespaceToPrefix = new ConcurrentHashMap<>(oHandler.namespaceToPrefix); + this.defaultNamespace = oHandler.defaultNamespace; + } + /** * Initializes the handler with standard W3C vocabulary prefixes by using the * dedicated Vocabulary enum classes. @@ -57,14 +66,11 @@ private void initializeStandardVocabularies() { OWL.class, FOAF.class ); - - for (Class> vocabClass : vocabularyClasses) { - Enum[] constants = vocabClass.getEnumConstants(); - if (constants.length > 0) { - Vocabulary vocabInstance = (Vocabulary) constants[0]; - setPrefix(vocabInstance.getPreferredPrefix(), vocabInstance.getNamespace()); - } - } + setPrefix(RDF.getVocabularyPreferredPrefix(), RDF.getVocabularyNamespace()); + setPrefix(RDFS.getVocabularyPreferredPrefix(), RDFS.getVocabularyNamespace()); + setPrefix(XSD.getVocabularyPreferredPrefix(), XSD.getVocabularyNamespace()); + setPrefix(OWL.getVocabularyPreferredPrefix(), OWL.getVocabularyNamespace()); + setPrefix(FOAF.getVocabularyPreferredPrefix(), FOAF.getVocabularyNamespace()); } /** @@ -87,8 +93,14 @@ public void setPrefix(String prefix, String namespace) { } String oldNamespace = prefixToNamespace.get(prefix); - if (oldNamespace != null && !oldNamespace.equals(namespace)) { + if (oldNamespace != null && ! oldNamespace.equals(namespace)) { namespaceToPrefix.remove(oldNamespace); + prefixToNamespace.remove(prefix); + } + String oldPrefix = namespaceToPrefix.get(namespace); + if(oldPrefix != null && ! oldPrefix.equals(prefix)) { + namespaceToPrefix.remove(namespace); + prefixToNamespace.remove(oldPrefix); } prefixToNamespace.put(prefix, namespace); @@ -142,6 +154,11 @@ public boolean hasPrefix(String prefix) { return prefixToNamespace.containsKey(prefix); } + @Override + public boolean hasNamespace(String namespace) { + return namespaceToPrefix.containsKey(namespace); + } + /** * Gets the default namespace * @@ -218,6 +235,16 @@ public Map getPrefixMap() { return Collections.unmodifiableMap(new HashMap<>(prefixToNamespace)); } + /** + * Returns all namespaces mappings as an unmodifiable map. + * + * @return an unmodifiable map where keys are namespaces IRIs and values are prefixes + */ + @Override + public Map getNamespaceMap() { + return Collections.unmodifiableMap(new HashMap<>(namespaceToPrefix)); + } + /** * Returns all namespace objects as an immutable set. * Each Namespace object contains both prefix and IRI. diff --git a/src/main/java/fr/inria/corese/core/next/impl/common/util/IRIUtils.java b/src/main/java/fr/inria/corese/core/next/impl/common/util/IRIUtils.java index 55a04a6c5..edf0c3c6c 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/common/util/IRIUtils.java +++ b/src/main/java/fr/inria/corese/core/next/impl/common/util/IRIUtils.java @@ -14,14 +14,19 @@ */ public class IRIUtils { - private static final Pattern IRI_PATTERN = Pattern.compile("^(?" + - "(?[\\w\\-]+):(?\\/\\/)?" + - "(?([\\w\\-_:@]+\\.)*[\\w\\-_:]*))" + - "((?\\/([\\w\\-\\._\\:]+\\/)*)" + - "(?[\\w\\-\\._\\:]+)?" + - "(?\\?[\\w\\-_\\:\\?\\=]+)?" + - "(?(\\#))?" + - "(?([\\w\\-_]+))?)?$"); + // Example 1 : http://webisa.webdatacommons.org/data/sparql?query=q#line1 + private static final Pattern IRI_PATTERN = Pattern.compile("^(?" + // http://webisa.webdatacommons.org + "(?[\\w\\-]+):" + // http: + "(?\\/\\/)?" + // // + "(?([\\w\\[\\]\\-_:@]+\\.)*[\\w\\-_:]*)" + // webisa.webdatacommons.org + ")" + + "(?\\/([\\w\\-\\._\\:]+\\/)*)*" + // /data/ + "(?[\\w&<>;\\-\\._\\:\\\"\\\']+)?" + // sparql + "(?\\?[\\w\\-\\\"\\\'_\\:\\?\\=]+)?" + // ?query=q + "(?\\#)?" + // # + "(?[\\w\\-_]+)?" + // line1 + "$" + ); private static final Pattern STANDARD_IRI_PATTERN = Pattern.compile("^(([^:/?#\\s]+):)(\\/\\/([^/?#\\s]*))?([^?#\\s]*)(\\?([^#\\s]*))?(#(.*))?"); private static final int MAX_IRI_LENGTH = 2048; private static final long REGEX_TIMEOUT_MS = 100; @@ -54,20 +59,21 @@ public static String guessNamespace(String iri) { return iri; } } else if (matcher.matches()) { + // This is a blank node if (matcher.group("protocol") != null && matcher.group("protocol").equals("_")) { return ""; } + StringBuilder namespace = new StringBuilder(); - namespace.append(matcher.group("protocol")).append(":"); - if (matcher.group("dblSlashes") != null) { - namespace.append(matcher.group("dblSlashes")); - } - namespace.append(matcher.group("domain")); + namespace.append(matcher.group("rootnamespace")); if (matcher.group("path") != null) { namespace.append(matcher.group("path")); } - if((matcher.group("fragment") != null || matcher.group("anchor") != null) && matcher.group("finalPath") != null) { - namespace.append(matcher.group("finalPath")).append("#"); + if(matcher.group("anchor") != null) { + if(matcher.group("finalPath") != null) { + namespace.append(matcher.group("finalPath")); + } + namespace.append(matcher.group("anchor")); } return namespace.toString(); diff --git a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/SerializerFactory.java b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/SerializerFactory.java index b06573298..8693e1d5e 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/SerializerFactory.java +++ b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/SerializerFactory.java @@ -4,23 +4,17 @@ import fr.inria.corese.core.next.api.ValueFactory; import fr.inria.corese.core.next.api.base.io.RDFFormat; import fr.inria.corese.core.next.api.io.IOOptions; -import fr.inria.corese.core.next.api.io.serialization.RDFSerializer; +import fr.inria.corese.core.next.api.io.serializer.RDFSerializer; import fr.inria.corese.core.next.impl.exception.SerializationException; -import fr.inria.corese.core.next.impl.io.common.JSONLDOptions; import fr.inria.corese.core.next.impl.io.serialization.canonical.RDFC10Canonicalizer; import fr.inria.corese.core.next.impl.io.serialization.canonical.RDFC10Serializer; import fr.inria.corese.core.next.impl.io.serialization.canonical.RDFC10SerializerOptions; import fr.inria.corese.core.next.impl.io.serialization.jsonld.JSONLDSerializer; import fr.inria.corese.core.next.impl.io.serialization.nquads.NQuadsSerializer; -import fr.inria.corese.core.next.impl.io.serialization.nquads.NQuadsSerializerOptions; import fr.inria.corese.core.next.impl.io.serialization.ntriples.NTriplesSerializer; -import fr.inria.corese.core.next.impl.io.serialization.ntriples.NTriplesSerializerOptions; import fr.inria.corese.core.next.impl.io.serialization.rdfxml.RDFXMLSerializer; -import fr.inria.corese.core.next.impl.io.serialization.rdfxml.RDFXMLSerializerOption; import fr.inria.corese.core.next.impl.io.serialization.trig.TriGSerializer; -import fr.inria.corese.core.next.impl.io.serialization.trig.TriGSerializerOptions; import fr.inria.corese.core.next.impl.io.serialization.turtle.TurtleSerializer; -import fr.inria.corese.core.next.impl.io.serialization.turtle.TurtleSerializerOptions; import fr.inria.corese.core.next.impl.temp.CoreseAdaptedValueFactory; import java.util.Collections; @@ -31,7 +25,7 @@ import java.util.function.Function; /** - * Default implementation of {@link fr.inria.corese.core.next.api.io.serialization.SerializerFactory}. + * Default implementation of {@link fr.inria.corese.core.next.api.io.serializer.SerializerFactory}. * This factory is responsible for creating instances of {@link RDFSerializer} * based on the requested {@link RDFFormat}. It uses a registry pattern * to map each format to its corresponding serializer constructor, @@ -44,7 +38,7 @@ * to default configurations if an incompatible type is provided. *

*/ -public class SerializerFactory implements fr.inria.corese.core.next.api.io.serialization.SerializerFactory { +public class SerializerFactory implements fr.inria.corese.core.next.api.io.serializer.SerializerFactory { private final Map> registry; private final Map> defaultRegistry; @@ -65,76 +59,22 @@ public SerializerFactory() { Map> tempRegistry = new HashMap<>(); Map> tempDefaultRegistry = new HashMap<>(); - tempRegistry.put(RDFFormat.TURTLE, (model, genericConfig) -> { - if (genericConfig instanceof TurtleSerializerOptions specificConfig) { - return new TurtleSerializer(model, specificConfig); - } - throw new SerializationException( - "Invalid configuration type. Expected TurtleSerializerOptions, got: " + - genericConfig.getClass().getSimpleName(), - RDFFormat.TURTLE.getName() - ); - }); + tempRegistry.put(RDFFormat.TURTLE, (model, genericConfig) -> new TurtleSerializer(model, genericConfig)); tempDefaultRegistry.put(RDFFormat.TURTLE, TurtleSerializer::new); - tempRegistry.put(RDFFormat.NTRIPLES, (model, genericConfig) -> { - if (genericConfig instanceof NTriplesSerializerOptions specificConfig) { - return new NTriplesSerializer(model, specificConfig); - } - throw new SerializationException( - "Invalid configuration type. Expected NTriplesSerializerOptions, got: " + - genericConfig.getClass().getSimpleName(), - RDFFormat.NTRIPLES.getName() - ); - }); + tempRegistry.put(RDFFormat.NTRIPLES, (model, genericConfig) -> new NTriplesSerializer(model, genericConfig)); tempDefaultRegistry.put(RDFFormat.NTRIPLES, NTriplesSerializer::new); - tempRegistry.put(RDFFormat.NQUADS, (model, genericConfig) -> { - if (genericConfig instanceof NQuadsSerializerOptions specificConfig) { - return new NQuadsSerializer(model, specificConfig); - } - throw new SerializationException( - "Invalid configuration type. Expected NQuadsSerializerOptions, got: " + - genericConfig.getClass().getSimpleName(), - RDFFormat.NQUADS.getName() - ); - }); + tempRegistry.put(RDFFormat.NQUADS, (model, genericConfig) -> new NQuadsSerializer(model, genericConfig)); tempDefaultRegistry.put(RDFFormat.NQUADS, NQuadsSerializer::new); - tempRegistry.put(RDFFormat.TRIG, (model, genericConfig) -> { - if (genericConfig instanceof TriGSerializerOptions specificConfig) { - return new TriGSerializer(model, specificConfig); - } - throw new SerializationException( - "Invalid configuration type. Expected TriGSerializerOptions, got: " + - genericConfig.getClass().getSimpleName(), - RDFFormat.TRIG.getName() - ); - }); + tempRegistry.put(RDFFormat.TRIG, (model, genericConfig) -> new TriGSerializer(model, genericConfig)); tempDefaultRegistry.put(RDFFormat.TRIG, TriGSerializer::new); - tempRegistry.put(RDFFormat.RDFXML, (model, genericConfig) -> { - if (genericConfig instanceof RDFXMLSerializerOption specificConfig) { - return new RDFXMLSerializer(model, specificConfig); - } - throw new SerializationException( - "Invalid configuration type. Expected RDFXMLSerializerOption, got: " + - genericConfig.getClass().getSimpleName(), - RDFFormat.RDFXML.getName() - ); - }); + tempRegistry.put(RDFFormat.RDFXML, (model, genericConfig) -> new RDFXMLSerializer(model, genericConfig)); tempDefaultRegistry.put(RDFFormat.RDFXML, RDFXMLSerializer::new); - tempRegistry.put(RDFFormat.JSONLD, (model, genericConfig) -> { - if (genericConfig instanceof JSONLDOptions specificConfig) { - return new JSONLDSerializer(model, specificConfig); - } - throw new SerializationException( - "Invalid configuration type. Expected JSONLDOptions, got: " + - genericConfig.getClass().getSimpleName(), - RDFFormat.JSONLD.getName() - ); - }); + tempRegistry.put(RDFFormat.JSONLD, (model, genericConfig) -> new JSONLDSerializer(model, genericConfig)); tempDefaultRegistry.put(RDFFormat.JSONLD, JSONLDSerializer::new); tempRegistry.put(RDFFormat.RDFC_1_0, (model, genericConfig) -> { diff --git a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/base/AbstractGraphSerializer.java b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/base/AbstractGraphSerializer.java index 83c88aa40..6e247f9ca 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/base/AbstractGraphSerializer.java +++ b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/base/AbstractGraphSerializer.java @@ -1,8 +1,11 @@ package fr.inria.corese.core.next.impl.io.serialization.base; import fr.inria.corese.core.next.api.*; -import fr.inria.corese.core.next.api.io.serialization.RDFSerializer; +import fr.inria.corese.core.next.api.io.IOOptions; +import fr.inria.corese.core.next.api.io.common.BaseIRIOptions; +import fr.inria.corese.core.next.api.io.serializer.*; import fr.inria.corese.core.next.impl.common.prefix.PrefixHandler; +import fr.inria.corese.core.next.impl.common.util.IRIUtils; import fr.inria.corese.core.next.impl.common.vocabulary.*; import fr.inria.corese.core.next.impl.exception.SerializationException; import fr.inria.corese.core.next.impl.io.serialization.option.*; @@ -28,8 +31,8 @@ * *

Note: Many features related to compact syntax, pretty-printing, and advanced * prefix management are specific to Turtle Trig formats and require the - * provided {@link AbstractSerializerOption} to be an instance of - * {@link AbstractSerializerOption} at runtime. An {@link IllegalStateException} + * provided {@link AbstractSerializerOptions} to be an instance of + * {@link AbstractSerializerOptions} at runtime. An {@link IllegalStateException} * will be thrown if an incompatible configuration is used for such features.

*/ public abstract class AbstractGraphSerializer implements RDFSerializer { @@ -40,9 +43,8 @@ public abstract class AbstractGraphSerializer implements RDFSerializer { protected static final Logger logger = LoggerFactory.getLogger(AbstractGraphSerializer.class); protected final Model model; - protected final AbstractSerializerOption option; - protected final Map iriToPrefixMapping; - protected final Map prefixToIriMapping; + protected IOOptions option; + protected PrefixHandler prefixHandler; protected final Set consumedBlankNodes; protected final Set currentlyWritingBlankNodes; @@ -50,49 +52,20 @@ public abstract class AbstractGraphSerializer implements RDFSerializer { * Constructs a new abstract TriG/Turtle serializer instance. * * @param model the {@link Model} to serialize. Must not be null. - * @param option the {@link AbstractSerializerOption} to use for serialization. Must not be null. + * @param config the {@link AbstractSerializerOptions} to use for serialization. Must not be null. * @throws NullPointerException if the provided model or configuration is null. */ - protected AbstractGraphSerializer(Model model, AbstractSerializerOption option) { + protected AbstractGraphSerializer(Model model, IOOptions config) { this.model = Objects.requireNonNull(model, "The model cannot be null"); - this.option = Objects.requireNonNull(option, "The configuration cannot be null"); - this.iriToPrefixMapping = new HashMap<>(); - this.prefixToIriMapping = new HashMap<>(); + Objects.requireNonNull(config, "The configuration cannot be null"); + this.option = config; + if(config instanceof UsesPrefixOptions usesPrefixOptions) { + this.prefixHandler = usesPrefixOptions.getPrefixHandler(); + } else { + this.prefixHandler = new PrefixHandler(false); + } this.consumedBlankNodes = new HashSet<>(); this.currentlyWritingBlankNodes = new HashSet<>(); - initializePrefixes(); - } - - /** - * Helper method to safely cast the generic config to AbstractTFamilyConfig. - * This should be called before accessing any methods specific to AbstractTFamilyConfig. - * - * @return The config cast to AbstractTFamilyConfig. - * @throws SerializationException if the config is not an instance of AbstractTFamilyConfig. - */ - private AbstractTFamilyOption getTFamilyOption() { - if (!(option instanceof AbstractTFamilyOption)) { - throw new SerializationException("Current serializer configuration is not an instance of AbstractTFamilyOption. " + - "Features like prefixes, compact syntax, and pretty-printing are only available for T-Family formats.", this.getFormatName()); - } - return (AbstractTFamilyOption) option; - } - - /** - * Initializes prefix mappings by adding custom prefixes from the configuration. - */ - private void initializePrefixes() { - if (option instanceof AbstractTFamilyOption && getTFamilyOption().usePrefixes()) { - AbstractTFamilyOption tFamilyOption = getTFamilyOption(); - PrefixHandler prefixHandler = tFamilyOption.getPrefixHandler(); - - for (String prefix : prefixHandler.getPrefixes()) { - String namespace = prefixHandler.getNamespace(prefix); - if (namespace != null) { - addPrefixMapping(namespace, prefix); - } - } - } } /** @@ -136,27 +109,29 @@ public void write(Writer writer) throws SerializationException { * @throws IOException if an I/O error occurs. */ protected void writeHeader(Writer writer) throws IOException { - if (option.getBaseIRI() != null) { + if (option instanceof BaseIRIOptions baseIRIOptions + && option instanceof LineEndingOptions lineEndingOptions + && baseIRIOptions.getBaseIRI() != null) { writer.write(String.format("@base <%s> .%s", - option.getBaseIRI(), - option.getLineEnding())); + baseIRIOptions.getBaseIRI(), + lineEndingOptions.getLineEnding())); } - if (option instanceof AbstractSerializerOption - && getTFamilyOption().usePrefixes() - && getTFamilyOption().autoDeclarePrefixes()) { - collectUsedNamespaces(); + Set actuallyUsedNamespaces = Set.of(); + if (option instanceof UsesPrefixOptions prefixOptions + && prefixOptions.usePrefixes() + && prefixOptions.autoDeclarePrefixes()) { + actuallyUsedNamespaces = collectUsedNamespaces(); } - writePrefixDeclarations(writer); + writePrefixDeclarations(writer, actuallyUsedNamespaces); } /** * Collects all namespaces used in the model and attempts to assign prefixes to them * if auto-declaration is enabled and they are not already mapped. */ - protected void collectUsedNamespaces() { - + protected Set collectUsedNamespaces() { Set namespaces = model.stream() .flatMap(stmt -> { List values = new ArrayList<>(Arrays.asList( @@ -167,21 +142,27 @@ protected void collectUsedNamespaces() { if (stmt.getContext() != null) { values.add(stmt.getContext()); } + if(stmt.getObject().isLiteral() + && ((Literal) stmt.getObject()).getDatatype() != null) { + values.add(((Literal) stmt.getObject()).getDatatype()); + } return values.stream(); }) .filter(Objects::nonNull) .filter(Value::isIRI) - .map(v -> getNamespace(v.stringValue())) + .map(v -> IRIUtils.guessNamespace(v.stringValue())) .collect(Collectors.toSet()); namespaces.forEach(namespace -> { - if (!iriToPrefixMapping.containsKey(namespace)) { + if (!this.prefixHandler.hasNamespace(namespace)) { String prefix = getSuggestedPrefix(namespace); if (prefix != null) { addPrefixMapping(namespace, prefix); } } }); + + return namespaces; } /** @@ -190,25 +171,38 @@ protected void collectUsedNamespaces() { * @param writer the {@link Writer} to which prefixes will be written. * @throws IOException if an I/O error occurs. */ - protected void writePrefixDeclarations(Writer writer) throws IOException { - AbstractTFamilyOption tFamilyConfig = getTFamilyOption(); + protected void writePrefixDeclarations(Writer writer, Set actuallyUsedNamespaces) throws IOException { + if(this.option instanceof UsesPrefixOptions prefixOptions + && this.option instanceof LineEndingOptions lineEndingOptions + && this.option instanceof BaseIRIOptions baseIRIOptions + && prefixOptions.usePrefixes()) { + List prefixes = new ArrayList<>(actuallyUsedNamespaces.stream().map(namespace -> this.prefixHandler.getPrefix(namespace)).toList()); - List prefixes = new ArrayList<>(prefixToIriMapping.keySet()); + if (prefixOptions.getPrefixOrdering() == PrefixOrderingEnum.ALPHABETICAL) { + Collections.sort(prefixes); + } - if (tFamilyConfig.getPrefixOrdering() == PrefixOrderingEnum.ALPHABETICAL) { - Collections.sort(prefixes); - } + for (String prefix : prefixes) { + writer.write(String.format("@prefix %s: <%s> .%s", + prefix, + this.prefixHandler.getNamespace(prefix), + lineEndingOptions.getLineEnding())); + } - for (String prefix : prefixes) { - writer.write(String.format("@prefix %s: <%s> .%s", - prefix, - prefixToIriMapping.get(prefix), - option.getLineEnding())); + if (!prefixes.isEmpty() || baseIRIOptions.getBaseIRI() != null) { + writer.write(lineEndingOptions.getLineEnding()); + } } + } - if (!prefixes.isEmpty() || option.getBaseIRI() != null) { - writer.write(option.getLineEnding()); - } + /** + * Writes prefix declarations to the writer, sorted if configured. + * + * @param writer the {@link Writer} to which prefixes will be written. + * @throws IOException if an I/O error occurs. + */ + protected void writePrefixDeclarations(Writer writer) throws IOException { + writePrefixDeclarations(writer, Set.of()); } /** @@ -222,7 +216,9 @@ protected void writeSimpleStatements(Writer writer) throws IOException { for (Statement stmt : model) { if (!isConsumed(stmt.getSubject())) { writeStatement(writer, stmt); - writer.write(option.getLineEnding()); + if(this.option instanceof LineEndingOptions lineEndingOptions) { + writer.write(lineEndingOptions.getLineEnding()); + } } } } @@ -236,9 +232,10 @@ protected void writeSimpleStatements(Writer writer) throws IOException { * @throws IOException if an I/O error occurs. */ protected void writeStatement(Writer writer, Statement stmt) throws IOException { - AbstractTFamilyOption tFamilyConfig = getTFamilyOption(); - - String indent = tFamilyConfig.prettyPrint() ? tFamilyConfig.getIndent() : SerializationConstants.EMPTY_STRING; + String indent = this.option instanceof PrettyPrintOptions prettyOptions + && prettyOptions.prettyPrint() + ? prettyOptions.getIndent() + : SerializationConstants.EMPTY_STRING; writer.write(indent); // Subject @@ -264,8 +261,9 @@ protected void writeStatement(Writer writer, Statement stmt) throws IOException * @throws IOException if an I/O error occurs. */ protected void writePredicate(Writer writer, Value predicate) throws IOException { - AbstractTFamilyOption tFamilyConfig = getTFamilyOption(); - if (tFamilyConfig.useRdfTypeShortcut() && predicate.equals(RDF.type.getIRI())) { + if (this.option instanceof AbstractTFamilyOptions tFamilyOptions + && tFamilyOptions.useRdfTypeShortcut() + && predicate.equals(RDF.type.getIRI())) { writer.write(SerializationConstants.RDF_TYPE_SHORTCUT); } else { writeValue(writer, predicate); @@ -303,12 +301,12 @@ protected void writeValue(Writer writer, Value value) throws IOException { boolean isSubject = model.stream().anyMatch(stmt -> stmt.getSubject().equals(bNode)); - if (!isSubject && option instanceof AbstractSerializerOption) { - if (getTFamilyOption().useCollections() && bNode.isBNode()) { + if (!isSubject && option instanceof AbstractTFamilyOptions abstractTFOptions) { + if (abstractTFOptions.useCollections() && bNode.isBNode()) { handled = writeRDFList(writer, bNode); } - if (!handled && getTFamilyOption().getBlankNodeStyle() == BlankNodeStyleEnum.ANONYMOUS && bNode.isBNode()) { + if (!handled && abstractTFOptions.getBlankNodeStyle() == BlankNodeStyleEnum.ANONYMOUS && bNode.isBNode()) { List properties = model.stream() .filter(stmt -> stmt.getSubject().equals(bNode)) .toList(); @@ -340,12 +338,12 @@ protected void writeValue(Writer writer, Value value) throws IOException { * @throws IOException if an I/O error occurs. */ protected void writeIRI(Writer writer, IRI iri) throws IOException { - if (option.isStrictMode() && option.validateURIs()) { + if (this.option instanceof AbstractSerializerOptions abstractSerializerOptions && abstractSerializerOptions.isStrictMode() && abstractSerializerOptions.validateURIs()) { validateIRI(iri); } String prefixed = null; - if (option instanceof AbstractSerializerOption && getTFamilyOption().usePrefixes()) { + if (option instanceof UsesPrefixOptions prefixOptions && prefixOptions.usePrefixes()) { prefixed = getPrefixedName(iri.stringValue()); } @@ -369,8 +367,8 @@ protected void writeLiteral(Writer writer, Literal literal) throws IOException { String value = literal.stringValue(); boolean useTripleQuotes = false; - if (option instanceof AbstractTFamilyOption) { - useTripleQuotes = getTFamilyOption().useMultilineLiterals() && + if (option instanceof AbstractTFamilyOptions tFamilyOptions) { + useTripleQuotes = tFamilyOptions.useMultilineLiterals() && (value.contains(SerializationConstants.LINE_FEED) || value.contains(SerializationConstants.CARRIAGE_RETURN) || value.contains("\"\"\"")); } @@ -422,9 +420,10 @@ protected boolean shouldWriteDatatype(Literal literal) { return false; } - return option.getLiteralDatatypePolicy() == LiteralDatatypePolicyEnum.ALWAYS_TYPED || - (!datatype.equals(XSD.xsdString.getIRI()) && - option.getLiteralDatatypePolicy() == LiteralDatatypePolicyEnum.MINIMAL); + return this.option instanceof DatatypePolicyOptions datatypePolicyOptions && + (datatypePolicyOptions.getLiteralDatatypePolicy() == LiteralDatatypePolicyEnum.ALWAYS_TYPED + || (!datatype.equals(XSD.xsdString.getIRI()) && + datatypePolicyOptions.getLiteralDatatypePolicy() == LiteralDatatypePolicyEnum.MINIMAL)); } /** @@ -436,10 +435,8 @@ protected boolean shouldWriteDatatype(Literal literal) { * @throws IOException if an I/O error occurs. */ protected void writeInlineBlankNode(Writer writer, List properties) throws IOException { - AbstractTFamilyOption tFamilyConfig = getTFamilyOption(); - - String currentIndent = tFamilyConfig.prettyPrint() ? tFamilyConfig.getIndent() : SerializationConstants.EMPTY_STRING; - String propIndent = tFamilyConfig.prettyPrint() ? currentIndent + tFamilyConfig.getIndent() : ""; + String currentIndent = this.option instanceof PrettyPrintOptions prettyPrintOptions && prettyPrintOptions.prettyPrint() ? prettyPrintOptions.getIndent() : SerializationConstants.EMPTY_STRING; + String propIndent = this.option instanceof PrettyPrintOptions prettyPrintOptions && prettyPrintOptions.prettyPrint() ? currentIndent + prettyPrintOptions.getIndent() : ""; writer.write(SerializationConstants.BLANK_NODE_START); @@ -455,8 +452,10 @@ protected void writeInlineBlankNode(Writer writer, List properties) t } firstProperty = false; - if (tFamilyConfig.prettyPrint()) { - writer.write(option.getLineEnding() + propIndent); + if (this.option instanceof PrettyPrintOptions prettyPrintOptions + && this.option instanceof LineEndingOptions lineEndingOptions + && prettyPrintOptions.prettyPrint()) { + writer.write(lineEndingOptions.getLineEnding() + propIndent); } else { writer.write(SerializationConstants.SPACE); } @@ -466,8 +465,11 @@ protected void writeInlineBlankNode(Writer writer, List properties) t writeValue(writer, stmt.getObject()); } - if (tFamilyConfig.prettyPrint() && !properties.isEmpty() && !firstProperty) { - writer.write(option.getLineEnding() + currentIndent); + if (this.option instanceof PrettyPrintOptions prettyPrintOptions + && this.option instanceof LineEndingOptions lineEndingOptions + && prettyPrintOptions.prettyPrint() + && !properties.isEmpty() && !firstProperty) { + writer.write(lineEndingOptions.getLineEnding() + currentIndent); } writer.write(SerializationConstants.BLANK_NODE_END); @@ -482,9 +484,7 @@ protected void writeInlineBlankNode(Writer writer, List properties) t * @throws IOException if an I/O error occurs. */ protected void writeOptimizedStatements(Writer writer) throws IOException { - AbstractTFamilyOption tFamilyConfig = getTFamilyOption(); - - Map> bySubject = tFamilyConfig.sortSubjects() ? + Map> bySubject = this.option instanceof PrettyPrintOptions prettyPrintOptions && prettyPrintOptions.sortSubjects() ? new TreeMap<>(Comparator.comparing(Resource::stringValue)) : new HashMap<>(); @@ -493,12 +493,12 @@ protected void writeOptimizedStatements(Writer writer) throws IOException { .forEach(stmt -> bySubject.computeIfAbsent(stmt.getSubject(), k -> new ArrayList<>()).add(stmt)); for (Map.Entry> subjectEntry : bySubject.entrySet()) { - String indent = tFamilyConfig.prettyPrint() ? tFamilyConfig.getIndent() : SerializationConstants.EMPTY_STRING; + String indent = this.option instanceof PrettyPrintOptions prettyPrintOptions && prettyPrintOptions.prettyPrint() ? prettyPrintOptions.getIndent() : SerializationConstants.EMPTY_STRING; writer.write(indent); writeValue(writer, subjectEntry.getKey()); writer.write(SerializationConstants.SPACE); - Map> byPredicate = tFamilyConfig.sortPredicates() ? + Map> byPredicate = this.option instanceof PrettyPrintOptions prettyPrintOptions && prettyPrintOptions.sortPredicates() ? new TreeMap<>(Comparator.comparing(IRI::stringValue)) : new HashMap<>(); @@ -508,8 +508,10 @@ protected void writeOptimizedStatements(Writer writer) throws IOException { for (Map.Entry> predicateEntry : byPredicate.entrySet()) { if (!firstPredicate) { writer.write(SerializationConstants.SEMICOLON); - if (tFamilyConfig.prettyPrint()) { - writer.write(option.getLineEnding() + indent + tFamilyConfig.getIndent()); + if (this.option instanceof PrettyPrintOptions prettyPrintOptions + && this.option instanceof LineEndingOptions lineEndingOptions + && prettyPrintOptions.prettyPrint()) { + writer.write(lineEndingOptions.getLineEnding() + indent + prettyPrintOptions.getIndent()); } else { writer.write(SerializationConstants.SPACE); } @@ -523,8 +525,10 @@ protected void writeOptimizedStatements(Writer writer) throws IOException { for (Statement stmt : predicateEntry.getValue()) { if (!firstObject) { writer.write(SerializationConstants.COMMA); - if (tFamilyConfig.prettyPrint()) { - writer.write(option.getLineEnding() + indent + tFamilyConfig.getIndent() + tFamilyConfig.getIndent()); + if (this.option instanceof PrettyPrintOptions prettyPrintOptions + && this.option instanceof LineEndingOptions lineEndingOptions + && prettyPrintOptions.prettyPrint()) { + writer.write(lineEndingOptions.getLineEnding() + indent + prettyPrintOptions.getIndent() + prettyPrintOptions.getIndent()); } else { writer.write(SerializationConstants.SPACE); } @@ -536,7 +540,9 @@ protected void writeOptimizedStatements(Writer writer) throws IOException { } writer.write(SerializationConstants.SPACE + SerializationConstants.POINT); - writer.write(option.getLineEnding()); + if(this.option instanceof LineEndingOptions lineEndingOptions) { + writer.write(lineEndingOptions.getLineEnding()); + } } } @@ -638,19 +644,17 @@ protected boolean isConsumed(Value value) { * @return A {@link Set} of {@link Resource} representing the blank nodes that will be serialized inline. */ protected Set precomputeInlineBlankNodesAndLists() { - AbstractTFamilyOption tFamilyConfig = getTFamilyOption(); - Set precomputed = new HashSet<>(); for (Statement stmt : model) { if (stmt.getSubject().isBNode()) { Resource bNodeSubject = stmt.getSubject(); - if (tFamilyConfig.useCollections() && isRDFListHead(bNodeSubject)) { + if (this.option instanceof AbstractTFamilyOptions tFamilyOptions && tFamilyOptions.useCollections() && isRDFListHead(bNodeSubject)) { Set listNodes = detectListNodes(bNodeSubject); if (!listNodes.isEmpty()) { precomputed.addAll(listNodes); } } - if (tFamilyConfig.getBlankNodeStyle() == BlankNodeStyleEnum.ANONYMOUS) { + if (this.option instanceof AbstractTFamilyOptions tFamilyOptions && tFamilyOptions.getBlankNodeStyle() == BlankNodeStyleEnum.ANONYMOUS) { List properties = model.stream() .filter(s -> s.getSubject().equals(bNodeSubject)) .toList(); @@ -752,17 +756,17 @@ protected boolean isRDFListHead(Resource bNode) { * @param prefix The associated prefix. */ protected void addPrefixMapping(String namespaceURI, String prefix) { - if (iriToPrefixMapping.containsKey(namespaceURI)) { - if (logger.isWarnEnabled() && !iriToPrefixMapping.get(namespaceURI).equals(prefix)) { + if (this.prefixHandler.hasNamespace(namespaceURI)) { + if (logger.isWarnEnabled() && !this.prefixHandler.getPrefix(namespaceURI).equals(prefix)) { logger.warn("Namespace URI '{}' is already mapped to prefix '{}'. Cannot map to new prefix '{}'.", - namespaceURI, iriToPrefixMapping.get(namespaceURI), prefix); + namespaceURI, this.prefixHandler.getPrefix(namespaceURI), prefix); } return; } - if (prefixToIriMapping.containsKey(prefix)) { - if (logger.isWarnEnabled() && !prefixToIriMapping.get(prefix).equals(namespaceURI)) { - String originalNamespace = prefixToIriMapping.get(prefix); + if (this.prefixHandler.hasPrefix(prefix)) { + if (logger.isWarnEnabled() && !this.prefixHandler.getNamespace(prefix).equals(namespaceURI)) { + String originalNamespace = this.prefixHandler.getNamespace(prefix); logger.warn("Prefix '{}' is already mapped to namespace '{}'. Cannot map to new namespace '{}'. " + "A new unique prefix will be generated for '{}'.", prefix, originalNamespace, namespaceURI, namespaceURI); @@ -770,31 +774,7 @@ protected void addPrefixMapping(String namespaceURI, String prefix) { return; } - iriToPrefixMapping.put(namespaceURI, prefix); - prefixToIriMapping.put(prefix, namespaceURI); - } - - /** - * Extracts the namespace URI part from an IRI string. - * This is a common heuristic for RDF IRIs. - * - * @param iriString The full IRI. - * @return The namespace URI part. - */ - protected String getNamespace(String iriString) { - int hashIdx = iriString.lastIndexOf(SerializationConstants.HASH); - int slashIdx = iriString.lastIndexOf(SerializationConstants.SLASH); - - if (hashIdx > -1) { - return iriString.substring(0, hashIdx + 1); - } else if (slashIdx > -1 && slashIdx < iriString.length() - 1) { - int dotIdx = iriString.lastIndexOf(SerializationConstants.POINT); - if (dotIdx > slashIdx) { - return iriString.substring(0, slashIdx + 1); - } - return iriString.substring(0, slashIdx + 1); - } - return iriString; + this.prefixHandler.setPrefix(prefix, namespaceURI); } /** @@ -804,11 +784,9 @@ protected String getNamespace(String iriString) { * @return The prefixed name (e.g., "ex:someResource") or null if no suitable prefix is found. */ protected String getPrefixedName(String iriString) { - for (Map.Entry entry : iriToPrefixMapping.entrySet()) { - String namespace = entry.getKey(); - String prefix = entry.getValue(); - + for (String namespace : this.prefixHandler.getNamespaces()) { if (iriString.startsWith(namespace)) { + String prefix = this.prefixHandler.getPrefix(namespace); String localName = iriString.substring(namespace.length()); if (localName.isEmpty()) { if (!prefix.isEmpty()) { @@ -831,12 +809,6 @@ protected String getPrefixedName(String iriString) { * @return A suggested prefix, or null if suggestion is not possible. */ protected String getSuggestedPrefix(String namespace) { - if (namespace.equals(RDF.getVocabularyNamespace())) return RDF.getVocabularyPreferredPrefix(); - if (namespace.equals(RDFS.getVocabularyNamespace())) return RDFS.getVocabularyPreferredPrefix(); - if (namespace.equals(XSD.getVocabularyNamespace())) return XSD.getVocabularyPreferredPrefix(); - if (namespace.equals(OWL.getVocabularyNamespace())) return OWL.getVocabularyPreferredPrefix(); - if (namespace.equals(FOAF.getVocabularyNamespace())) return FOAF.getVocabularyPreferredPrefix(); - String base = namespace; if (base.endsWith(SerializationConstants.HASH) || base.endsWith(SerializationConstants.SLASH)) { base = base.substring(0, base.length() - 1); @@ -861,13 +833,13 @@ protected String getSuggestedPrefix(String namespace) { base = base.replaceAll("[^a-zA-Z0-9]", SerializationConstants.EMPTY_STRING).toLowerCase(); if (base.isEmpty()) base = "p"; - String candidate = base; + String candidatePrefix = base; int i = 0; - while (prefixToIriMapping.containsKey(candidate) && !prefixToIriMapping.get(candidate).equals(namespace)) { - candidate = base + (++i); + while (this.prefixHandler.hasPrefix(candidatePrefix) && !this.prefixHandler.getNamespace(candidatePrefix).equals(namespace)) { + candidatePrefix = base + (++i); } - return candidate; + return candidatePrefix; } @@ -908,7 +880,9 @@ protected void validateValue(Value value) { throw new SerializationException("Value cannot be null in {} format when strictMode is enabled.", getFormatName()); } - if (option.isStrictMode() && value.isLiteral()) { + if (this.option instanceof AbstractSerializerOptions abstractSerializerOptions + && abstractSerializerOptions.isStrictMode() + && value.isLiteral()) { validateLiteral((Literal) value); } } diff --git a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/base/AbstractLineBasedSerializer.java b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/base/AbstractLineBasedSerializer.java index 48081413f..6d9298ed9 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/base/AbstractLineBasedSerializer.java +++ b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/base/AbstractLineBasedSerializer.java @@ -2,13 +2,13 @@ import java.io.BufferedWriter; import java.io.IOException; -import java.io.UncheckedIOException; import java.io.Writer; import java.util.Collections; import java.util.Objects; import java.util.Set; import fr.inria.corese.core.next.impl.common.literal.XSD; +import fr.inria.corese.core.next.impl.io.serialization.option.AbstractSerializerOptions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -18,10 +18,9 @@ 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.io.serialization.RDFSerializer; +import fr.inria.corese.core.next.api.io.serializer.RDFSerializer; import fr.inria.corese.core.next.impl.common.literal.RDF; import fr.inria.corese.core.next.impl.exception.SerializationException; -import fr.inria.corese.core.next.impl.io.serialization.option.AbstractSerializerOption; import fr.inria.corese.core.next.impl.io.serialization.option.LiteralDatatypePolicyEnum; import fr.inria.corese.core.next.impl.io.serialization.util.SerializationConstants; @@ -38,16 +37,16 @@ public abstract class AbstractLineBasedSerializer implements RDFSerializer { private static final Logger logger = LoggerFactory.getLogger(AbstractLineBasedSerializer.class); protected final Model model; - protected final AbstractSerializerOption config; + protected AbstractSerializerOptions config; /** * Constructs a new line-based serializer. * * @param model the {@link Model} to be serialized. Must not be null. - * @param config the {@link AbstractSerializerOption} to use for serialization. Must not be null. + * @param config the {@link AbstractSerializerOptions} to use for serialization. Must not be null. * @throws NullPointerException if the provided model or config is null. */ - protected AbstractLineBasedSerializer(Model model, AbstractSerializerOption config) { + protected AbstractLineBasedSerializer(Model model, AbstractSerializerOptions config) { this.model = Objects.requireNonNull(model, "Model cannot be null"); this.config = Objects.requireNonNull(config, "Configuration cannot be null"); } diff --git a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/canonical/RDFC10SerializerOptions.java b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/canonical/RDFC10SerializerOptions.java index afc6ef596..5615d78a4 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/canonical/RDFC10SerializerOptions.java +++ b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/canonical/RDFC10SerializerOptions.java @@ -1,17 +1,17 @@ package fr.inria.corese.core.next.impl.io.serialization.canonical; -import fr.inria.corese.core.next.impl.io.serialization.option.AbstractSerializerOption; +import fr.inria.corese.core.next.impl.io.serialization.option.AbstractSerializerOptions; /** * Configuration for Canonical RDF serialization format (RDFC-1.0). - * This class extends {@link AbstractSerializerOption} and provides specific defaults + * This class extends {@link AbstractSerializerOptions} and provides specific defaults * and options tailored for the RDFC-10 canonicalization algorithm. * It includes options relevant to blank node canonicalization, such as the hashing algorithm * to use, the depth factor for graph isomorphism, and the permutation limit. * Use the {@link Builder} class to create instances of {@code CanonicalOption}. * A predefined default configuration is available via {@link #defaultConfig()}. */ -public class RDFC10SerializerOptions extends AbstractSerializerOption { +public class RDFC10SerializerOptions extends AbstractSerializerOptions { /** * Enumeration for the supported hashing algorithms. @@ -74,7 +74,7 @@ public int getPermutationLimit() { * Provides a fluent API for constructing {@code CanonicalOption} instances with default values * specific to the Canonical RDF format. */ - public static class Builder extends AbstractSerializerOption.AbstractBuilder { + public static class Builder extends AbstractSerializerOptions.AbstractBuilder { private HashAlgorithm hashAlgorithm = HashAlgorithm.SHA_256; private int depthFactor = 5; private int permutationLimit = 50000; diff --git a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/jsonld/JSONLDSerializer.java b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/jsonld/JSONLDSerializer.java index ef4b03477..5f12daec1 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/jsonld/JSONLDSerializer.java +++ b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/jsonld/JSONLDSerializer.java @@ -6,7 +6,7 @@ import com.apicatalog.jsonld.document.RdfDocument; import fr.inria.corese.core.next.api.Model; import fr.inria.corese.core.next.api.base.io.RDFFormat; -import fr.inria.corese.core.next.api.io.serialization.RDFSerializer; +import fr.inria.corese.core.next.api.io.serializer.RDFSerializer; import fr.inria.corese.core.next.api.io.IOOptions; import fr.inria.corese.core.next.impl.exception.SerializationException; import fr.inria.corese.core.next.impl.io.common.JSONLDOptions; diff --git a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/nquads/NQuadsSerializer.java b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/nquads/NQuadsSerializer.java index 5de866837..9c9134460 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/nquads/NQuadsSerializer.java +++ b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/nquads/NQuadsSerializer.java @@ -4,7 +4,13 @@ import fr.inria.corese.core.next.api.Resource; import fr.inria.corese.core.next.api.Statement; import fr.inria.corese.core.next.api.base.io.RDFFormat; +import fr.inria.corese.core.next.api.io.IOOptions; +import fr.inria.corese.core.next.api.io.common.BaseIRIOptions; +import fr.inria.corese.core.next.api.io.serializer.BlankNodeIdGenerationOptions; +import fr.inria.corese.core.next.api.io.serializer.LineEndingOptions; import fr.inria.corese.core.next.impl.io.serialization.base.AbstractLineBasedSerializer; +import fr.inria.corese.core.next.impl.io.serialization.ntriples.NTriplesSerializerOptions; +import fr.inria.corese.core.next.impl.io.serialization.option.AbstractNFamilyOptions; import fr.inria.corese.core.next.impl.io.serialization.util.SerializationConstants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,7 +39,7 @@ public class NQuadsSerializer extends AbstractLineBasedSerializer { * @throws NullPointerException if the provided model is null. */ public NQuadsSerializer(Model model) { - this(model, NQuadsSerializerOptions.defaultConfig()); + super(model, NQuadsSerializerOptions.defaultConfig()); } /** @@ -44,9 +50,24 @@ public NQuadsSerializer(Model model) { * This config object should be an instance of {@code NQuadsConfig} or a subclass thereof. * @throws NullPointerException if the provided model or config is null. */ - public NQuadsSerializer(Model model, NQuadsSerializerOptions config) { - super(model, config); + public NQuadsSerializer(Model model, IOOptions config) { + this(model); Objects.requireNonNull(config, "NQuadsConfig cannot be null"); + if(config instanceof AbstractNFamilyOptions nFamilyOptions) { + this.config = nFamilyOptions; + } else { + NTriplesSerializerOptions.Builder optionBuilder = new NTriplesSerializerOptions.Builder(); + if(config instanceof BaseIRIOptions baseIRIOptions) { + optionBuilder.baseIRI(baseIRIOptions.getBaseIRI()); + } + if(config instanceof LineEndingOptions lineEndingOptions) { + optionBuilder.lineEnding(lineEndingOptions.getLineEnding()); + } + if(config instanceof BlankNodeIdGenerationOptions blankNodeIdGenerationOptions) { + optionBuilder.stableBlankNodeIds(blankNodeIdGenerationOptions.stableBlankNodeIds()); + } + this.config = optionBuilder.build(); + } } /** diff --git a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/nquads/NQuadsSerializerOptions.java b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/nquads/NQuadsSerializerOptions.java index 1367d6697..935f2f752 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/nquads/NQuadsSerializerOptions.java +++ b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/nquads/NQuadsSerializerOptions.java @@ -1,16 +1,16 @@ package fr.inria.corese.core.next.impl.io.serialization.nquads; -import fr.inria.corese.core.next.impl.io.serialization.option.AbstractNFamilyOption; +import fr.inria.corese.core.next.impl.io.serialization.option.AbstractNFamilyOptions; /** * Configuration for N-Quads serialization format. - * This class extends {@link AbstractNFamilyOption} and provides specific defaults + * This class extends {@link AbstractNFamilyOptions} and provides specific defaults * and options tailored for N-Quads, which extends N-Quads with named graphs. * *

Use the {@link Builder} class to create instances of {@code NQuadsConfig}. * A predefined default configuration is available via {@link #defaultConfig()}.

*/ -public class NQuadsSerializerOptions extends AbstractNFamilyOption { +public class NQuadsSerializerOptions extends AbstractNFamilyOptions { /** * Protected constructor to be used by the {@link Builder}. @@ -26,7 +26,7 @@ protected NQuadsSerializerOptions(Builder builder) { * Provides a fluent API for constructing {@code NQuadsConfig} instances with default values * specific to the N-Quads format. */ - public static class Builder extends AbstractNFamilyOption.AbstractNFamilyBuilder { + public static class Builder extends AbstractNFamilyOptions.AbstractNFamilyBuilder { /** * Default constructor initializes all options with their default values for N-Quads. */ diff --git a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/ntriples/NTriplesSerializer.java b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/ntriples/NTriplesSerializer.java index d4b1370e7..eb6c50840 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/ntriples/NTriplesSerializer.java +++ b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/ntriples/NTriplesSerializer.java @@ -5,6 +5,11 @@ import java.util.Objects; import fr.inria.corese.core.next.api.base.io.RDFFormat; +import fr.inria.corese.core.next.api.io.IOOptions; +import fr.inria.corese.core.next.api.io.common.BaseIRIOptions; +import fr.inria.corese.core.next.api.io.serializer.BlankNodeIdGenerationOptions; +import fr.inria.corese.core.next.api.io.serializer.LineEndingOptions; +import fr.inria.corese.core.next.impl.io.serialization.option.AbstractNFamilyOptions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,7 +38,7 @@ public class NTriplesSerializer extends AbstractLineBasedSerializer { * @throws NullPointerException if the provided model is null. */ public NTriplesSerializer(Model model) { - this(model, NTriplesSerializerOptions.defaultConfig()); + super(model, NTriplesSerializerOptions.defaultConfig()); } /** @@ -44,9 +49,24 @@ public NTriplesSerializer(Model model) { * This config object should be an instance of {@code NTriplesConfig} or a subclass thereof. * @throws NullPointerException if the provided model or config is null. */ - public NTriplesSerializer(Model model, NTriplesSerializerOptions config) { - super(model, config); + public NTriplesSerializer(Model model, IOOptions config) { + this(model); Objects.requireNonNull(config, "NTriplesConfig cannot be null"); + if(config instanceof AbstractNFamilyOptions nFamilyOptions) { + this.config = nFamilyOptions; + } else { + NTriplesSerializerOptions.Builder optionBuilder = new NTriplesSerializerOptions.Builder(); + if(config instanceof BaseIRIOptions baseIRIOptions) { + optionBuilder.baseIRI(baseIRIOptions.getBaseIRI()); + } + if(config instanceof LineEndingOptions lineEndingOptions) { + optionBuilder.lineEnding(lineEndingOptions.getLineEnding()); + } + if(config instanceof BlankNodeIdGenerationOptions blankNodeIdGenerationOptions) { + optionBuilder.stableBlankNodeIds(blankNodeIdGenerationOptions.stableBlankNodeIds()); + } + this.config = optionBuilder.build(); + } } /** * Retrieves the RDF format supported by this serializer, which is N-TRIPLES. diff --git a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/ntriples/NTriplesSerializerOptions.java b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/ntriples/NTriplesSerializerOptions.java index b611d2425..4cdec4029 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/ntriples/NTriplesSerializerOptions.java +++ b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/ntriples/NTriplesSerializerOptions.java @@ -1,16 +1,16 @@ package fr.inria.corese.core.next.impl.io.serialization.ntriples; -import fr.inria.corese.core.next.impl.io.serialization.option.AbstractNFamilyOption; +import fr.inria.corese.core.next.impl.io.serialization.option.AbstractNFamilyOptions; /** * Configuration for N-Triples serialization format. - * This class extends {@link AbstractNFamilyOption} and provides specific defaults + * This class extends {@link AbstractNFamilyOptions} and provides specific defaults * and options tailored for N-Triples, which is a simple, line-oriented format. * *

Use the {@link Builder} class to create instances of {@code NTriplesConfig}. * A predefined default configuration is available via {@link #defaultConfig()}.

*/ -public class NTriplesSerializerOptions extends AbstractNFamilyOption { +public class NTriplesSerializerOptions extends AbstractNFamilyOptions { /** * Protected constructor to be used by the {@link Builder}. @@ -26,7 +26,7 @@ protected NTriplesSerializerOptions(Builder builder) { * Provides a fluent API for constructing {@code NTriplesConfig} instances with default values * specific to the N-Triples format. */ - public static class Builder extends AbstractNFamilyOption.AbstractNFamilyBuilder { + public static class Builder extends AbstractNFamilyOptions.AbstractNFamilyBuilder { /** * Default constructor initializes all options with their default values for N-Triples. */ diff --git a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/option/AbstractNFamilyOption.java b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/option/AbstractNFamilyOptions.java similarity index 82% rename from src/main/java/fr/inria/corese/core/next/impl/io/serialization/option/AbstractNFamilyOption.java rename to src/main/java/fr/inria/corese/core/next/impl/io/serialization/option/AbstractNFamilyOptions.java index 0ebf8efc3..a43616d09 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/option/AbstractNFamilyOption.java +++ b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/option/AbstractNFamilyOptions.java @@ -2,7 +2,7 @@ /** * An abstract base class for serialization configurations of N-Family RDF formats (e.g., N-Triples, N-Quads). - * This class extends {@link AbstractSerializerOption} and provides a common foundation + * This class extends {@link AbstractSerializerOptions} and provides a common foundation * for formats that typically have simpler, line-based structures and specific default behaviors * regarding literal datatypes and character escaping. * @@ -10,7 +10,7 @@ * nested {@link AbstractNFamilyBuilder}. Subclasses are expected to extend this * configuration and its builder to add format-specific options.

*/ -public abstract class AbstractNFamilyOption extends AbstractSerializerOption { +public abstract class AbstractNFamilyOptions extends AbstractSerializerOptions { /** * Protected constructor to be used by concrete builder implementations. @@ -19,14 +19,14 @@ public abstract class AbstractNFamilyOption extends AbstractSerializerOption { * * @param builder The builder instance containing the desired configuration values. */ - protected AbstractNFamilyOption(AbstractNFamilyBuilder builder) { + protected AbstractNFamilyOptions(AbstractNFamilyBuilder builder) { super(builder); } /** - * An abstract base builder for {@link AbstractNFamilyOption}. + * An abstract base builder for {@link AbstractNFamilyOptions}. * This builder provides methods for setting N-Family serialization configuration options. - * It extends {@link AbstractSerializerOption.AbstractBuilder} and uses a recursive type + * It extends {@link AbstractSerializerOptions.AbstractBuilder} and uses a recursive type * parameter (`S`) to allow concrete subclass builders to return their own specific type, * enabling fluent API chaining. * @@ -36,7 +36,7 @@ protected AbstractNFamilyOption(AbstractNFamilyBuilder builder) { * @param The type of the concrete builder extending this abstract builder. */ public abstract static class AbstractNFamilyBuilder> - extends AbstractSerializerOption.AbstractBuilder { + extends AbstractSerializerOptions.AbstractBuilder { /** * Default constructor for the builder. @@ -52,11 +52,11 @@ protected AbstractNFamilyBuilder() { } /** - * Builds and returns a new {@link AbstractNFamilyOption} instance with the current builder settings. + * Builds and returns a new {@link AbstractNFamilyOptions} instance with the current builder settings. * This method must be implemented by concrete builder subclasses to return their specific configuration type. * * @return A new {@code AbstractNFamilyConfig} instance or a subclass instance. */ - public abstract AbstractNFamilyOption build(); + public abstract AbstractNFamilyOptions build(); } } diff --git a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/option/AbstractSerializerOption.java b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/option/AbstractSerializerOptions.java similarity index 86% rename from src/main/java/fr/inria/corese/core/next/impl/io/serialization/option/AbstractSerializerOption.java rename to src/main/java/fr/inria/corese/core/next/impl/io/serialization/option/AbstractSerializerOptions.java index 353c229b5..9a971efa1 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/option/AbstractSerializerOption.java +++ b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/option/AbstractSerializerOptions.java @@ -2,6 +2,9 @@ import fr.inria.corese.core.next.api.io.IOOptions; import fr.inria.corese.core.next.api.io.common.BaseIRIOptions; +import fr.inria.corese.core.next.api.io.serializer.BlankNodeIdGenerationOptions; +import fr.inria.corese.core.next.api.io.serializer.DatatypePolicyOptions; +import fr.inria.corese.core.next.api.io.serializer.LineEndingOptions; import fr.inria.corese.core.next.impl.io.serialization.util.SerializationConstants; import java.util.Objects; @@ -15,7 +18,7 @@ * nested {@link AbstractBuilder}. Subclasses are expected to extend this * configuration and its builder to add format-specific options.

*/ -public abstract class AbstractSerializerOption implements IOOptions , BaseIRIOptions { +public abstract class AbstractSerializerOptions implements IOOptions, BaseIRIOptions, LineEndingOptions, BlankNodeIdGenerationOptions, DatatypePolicyOptions { /** * The policy for how literal datatypes are printed. @@ -72,7 +75,7 @@ public abstract class AbstractSerializerOption implements IOOptions , BaseIRIOpt * @param builder The builder instance containing the desired configuration values. * @throws NullPointerException if any required field from the builder is null. */ - protected AbstractSerializerOption(AbstractBuilder builder) { + protected AbstractSerializerOptions(AbstractBuilder builder) { this.literalDatatypePolicy = Objects.requireNonNull(builder.literalDatatypePolicy, "Literal datatype policy cannot be null"); this.escapeUnicode = builder.escapeUnicode; this.trailingDot = builder.trailingDot; @@ -119,6 +122,7 @@ public boolean trailingDot() { * * @return The base IRI string, or {@code null} if no base IRI is specified. */ + @Override public String getBaseIRI() { return baseIRI; } @@ -128,6 +132,7 @@ public String getBaseIRI() { * * @return {@code true} if stable blank node IDs are enabled, {@code false} otherwise. */ + @Override public boolean stableBlankNodeIds() { return stableBlankNodeIds; } @@ -137,6 +142,7 @@ public boolean stableBlankNodeIds() { * * @return The line ending string (e.g., `"\n"` for Unix, `"\r\n"` for Windows). */ + @Override public String getLineEnding() { return lineEnding; } @@ -169,7 +175,7 @@ public boolean includeContext() { } /** - * An abstract base builder for {@link AbstractSerializerOption}. + * An abstract base builder for {@link AbstractSerializerOptions}. * This builder provides methods for setting common serialization configuration options. * It uses a recursive type parameter (`S`) to allow concrete subclass builders * to return their own specific type, enabling fluent API chaining. @@ -188,6 +194,31 @@ public abstract static class AbstractBuilder> { protected boolean validateURIs = true; protected boolean includeContext = false; + protected AbstractBuilder(IOOptions otherOptions) { + if(otherOptions instanceof AbstractSerializerOptions abstractSerializerOptions) { + this.escapeUnicode(abstractSerializerOptions.escapeUnicode()); + this.trailingDot(abstractSerializerOptions.trailingDot()); + this.strictMode(abstractSerializerOptions.isStrictMode()); + this.validateURIs(abstractSerializerOptions.validateURIs()); + this.includeContext(abstractSerializerOptions.includeContext()); + } + if(otherOptions instanceof BaseIRIOptions baseIRIOptions) { + this.baseIRI(baseIRIOptions.getBaseIRI()); + } + if(otherOptions instanceof LineEndingOptions lineEndingOptions) { + this.lineEnding(lineEndingOptions.getLineEnding()); + } + if(otherOptions instanceof BlankNodeIdGenerationOptions blankNodeIdGenerationOptions) { + this.stableBlankNodeIds(blankNodeIdGenerationOptions.stableBlankNodeIds()); + } + if(otherOptions instanceof DatatypePolicyOptions datatypePolicyOptions) { + this.literalDatatypePolicy(datatypePolicyOptions.getLiteralDatatypePolicy()); + } + } + + protected AbstractBuilder() { + } + /** * Sets the policy for how literal datatypes are printed. * @@ -290,12 +321,12 @@ public S includeContext(boolean include) { } /** - * Builds and returns a new {@link AbstractSerializerOption} instance with the current builder settings. + * Builds and returns a new {@link AbstractSerializerOptions} instance with the current builder settings. * This method must be implemented by concrete builder subclasses to return their specific configuration type. * * @return A new {@code AbstractSerializerConfig} instance or a subclass instance. */ - public abstract AbstractSerializerOption build(); + public abstract AbstractSerializerOptions build(); /** * Helper method to return the concrete builder instance for fluent API chaining. diff --git a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/option/AbstractTFamilyOption.java b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/option/AbstractTFamilyOptions.java similarity index 89% rename from src/main/java/fr/inria/corese/core/next/impl/io/serialization/option/AbstractTFamilyOption.java rename to src/main/java/fr/inria/corese/core/next/impl/io/serialization/option/AbstractTFamilyOptions.java index 735ec7edf..8bf85d17e 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/option/AbstractTFamilyOption.java +++ b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/option/AbstractTFamilyOptions.java @@ -1,5 +1,8 @@ package fr.inria.corese.core.next.impl.io.serialization.option; +import fr.inria.corese.core.next.api.io.IOOptions; +import fr.inria.corese.core.next.api.io.serializer.PrettyPrintOptions; +import fr.inria.corese.core.next.api.io.serializer.UsesPrefixOptions; import fr.inria.corese.core.next.impl.common.prefix.PrefixHandler; import fr.inria.corese.core.next.impl.io.serialization.util.SerializationConstants; @@ -8,14 +11,14 @@ /** * An abstract base class for serialization configurations of Turtle Trig RDF formats (e.g., Turtle, TriG). - * This class extends {@link AbstractSerializerOption} and introduces parameters specific to + * This class extends {@link AbstractSerializerOptions} and introduces parameters specific to * formats that utilize syntax sugar, pretty-printing, and collection syntax. * *

It enforces the use of the Builder pattern for construction through its * nested {@link AbstractTFamilyBuilder}. Subclasses are expected to extend this * configuration and its builder to add format-specific options.

*/ -public abstract class AbstractTFamilyOption extends AbstractSerializerOption { +public abstract class AbstractTFamilyOptions extends AbstractSerializerOptions implements UsesPrefixOptions, PrettyPrintOptions { /** * Whether prefix declarations (e.g., `@prefix`, `PREFIX`) should be used for compact IRIs. @@ -105,7 +108,7 @@ public abstract class AbstractTFamilyOption extends AbstractSerializerOption { * @throws NullPointerException if any required field from the builder is null. * @throws IllegalArgumentException if incompatible options (e.g., escapeUnicode and useMultilineLiterals) are enabled. */ - protected AbstractTFamilyOption(AbstractTFamilyBuilder builder) { + protected AbstractTFamilyOptions(AbstractTFamilyBuilder builder) { super(builder); this.usePrefixes = builder.usePrefixes; @@ -135,6 +138,7 @@ protected AbstractTFamilyOption(AbstractTFamilyBuilder builder) { * * @return {@code true} if prefixes are used, {@code false} otherwise. */ + @Override public boolean usePrefixes() { return usePrefixes; } @@ -144,6 +148,7 @@ public boolean usePrefixes() { * * @return {@code true} if auto-declaration is enabled, {@code false} otherwise. */ + @Override public boolean autoDeclarePrefixes() { return autoDeclarePrefixes; } @@ -153,6 +158,7 @@ public boolean autoDeclarePrefixes() { * * @return The {@link PrefixOrderingEnum} for prefix ordering. */ + @Override public PrefixOrderingEnum getPrefixOrdering() { return prefixOrdering; } @@ -162,6 +168,7 @@ public PrefixOrderingEnum getPrefixOrdering() { * * @return The {@link PrefixHandler} managing all prefix mappings. */ + @Override public PrefixHandler getPrefixHandler() { return prefixHandler; } @@ -216,6 +223,7 @@ public boolean useMultilineLiterals() { * * @return {@code true} if pretty-printing is enabled, {@code false} otherwise. */ + @Override public boolean prettyPrint() { return prettyPrint; } @@ -225,6 +233,7 @@ public boolean prettyPrint() { * * @return The indentation string. */ + @Override public String getIndent() { return indent; } @@ -234,6 +243,7 @@ public String getIndent() { * * @return The maximum line length. */ + @Override public int getMaxLineLength() { return maxLineLength; } @@ -252,6 +262,7 @@ public boolean groupBySubject() { * * @return {@code true} if subject sorting is enabled, {@code false} otherwise. */ + @Override public boolean sortSubjects() { return sortSubjects; } @@ -261,12 +272,13 @@ public boolean sortSubjects() { * * @return {@code true} if predicate sorting is enabled, {@code false} otherwise. */ + @Override public boolean sortPredicates() { return sortPredicates; } /** - * An abstract base builder for {@link AbstractTFamilyOption}. + * An abstract base builder for {@link AbstractTFamilyOptions}. * This builder provides methods for setting Turtle Trig serialization configuration options. * parameter (`S`) to allow concrete subclass builders to return their own specific type, * enabling fluent API chaining. @@ -295,6 +307,36 @@ public abstract static class AbstractTFamilyBuilder iriToPrefixMapping; - private final Map prefixToIriMapping; + private final IOOptions config; + private final PrefixHandler prefixHandler; private final Map blankNodeIds; private int blankNodeCounter = 0; private List cachedStatements; /** * Constructs a new {@code XmlSerializer} instance with the specified model and default configuration. - * The default configuration is obtained from {@link RDFXMLSerializerOption#defaultConfig()}. + * The default configuration is obtained from {@link RDFXMLSerializerOptions#defaultConfig()}. * * @param model the {@link Model} to serialize. Must not be null. * @throws NullPointerException if the provided model is null. */ public RDFXMLSerializer(Model model) { - this(model, RDFXMLSerializerOption.defaultConfig()); + this(model, RDFXMLSerializerOptions.defaultConfig()); } /** * Constructs a new {@code XmlSerializer} instance with the specified model and custom configuration. * * @param model the {@link Model} to serialize. Must not be null. - * @param config the {@link RDFXMLSerializerOption} to use for serialization. Must not be null. + * @param config the {@link RDFXMLSerializerOptions} to use for serialization. Must not be null. * @throws NullPointerException if the provided model or configuration is null. */ - public RDFXMLSerializer(Model model, RDFXMLSerializerOption config) { + public RDFXMLSerializer(Model model, IOOptions config) { this.model = Objects.requireNonNull(model, "Model cannot be null"); this.config = Objects.requireNonNull(config, "Configuration cannot be null"); - this.iriToPrefixMapping = new HashMap<>(); - this.prefixToIriMapping = new HashMap<>(); - this.blankNodeIds = new HashMap<>(); - initializePrefixes(); - } - /** - * Initializes prefix mappings by adding custom prefixes from the configuration. - * The custom prefixes map in XmlConfig is expected to be {prefix: namespaceURI}. - */ - private void initializePrefixes() { - if (config.usePrefixes()) { - for (Map.Entry entry : config.getCustomPrefixes().entrySet()) { - addPrefixMapping(entry.getValue(), entry.getKey()); - } + if(config instanceof UsesPrefixOptions usesPrefixOptions + && usesPrefixOptions.usePrefixes()) { + this.prefixHandler = usesPrefixOptions.getPrefixHandler(); + } else { + this.prefixHandler = new PrefixHandler(false); + // These namespaces are part of the RDF/XML standard + this.prefixHandler.setPrefix(RDF.getVocabularyPreferredPrefix(), RDF.getVocabularyNamespace()); + this.prefixHandler.setPrefix(XSD.getVocabularyPreferredPrefix(), XSD.getVocabularyNamespace()); } + this.blankNodeIds = new HashMap<>(); } /** @@ -132,7 +117,9 @@ public RDFFormat getRDFFormat() { */ private void writeXmlDeclaration(Writer writer) throws IOException { writer.write(SerializationConstants.XML_DECLARATION_START); - writer.write(config.getLineEnding()); + if(this.config instanceof LineEndingOptions lineEndingOptions) { + writer.write(lineEndingOptions.getLineEnding()); + } } /** @@ -143,30 +130,44 @@ private void writeXmlDeclaration(Writer writer) throws IOException { * @throws IOException if an I/O error occurs. */ private void writeRdfRootElement(Writer writer) throws IOException { - if (config.usePrefixes() && config.autoDeclarePrefixes()) { - collectUsedNamespaces(); + Set actuallyUsedNamespaces = new HashSet<>(); + actuallyUsedNamespaces.add(RDF.getVocabularyNamespace()); + if (this.config instanceof UsesPrefixOptions usesPrefixOptions + && usesPrefixOptions.usePrefixes() + && usesPrefixOptions.autoDeclarePrefixes()) { + actuallyUsedNamespaces.addAll(collectUsedNamespaces()); } writer.write(SerializationConstants.RDF_ROOT_START); - writeNamespaceAttributes(writer); + writeNamespaceAttributes(writer, actuallyUsedNamespaces); writer.write(">"); - writer.write(config.getLineEnding()); + if(this.config instanceof LineEndingOptions lineEndingOptions) { + writer.write(lineEndingOptions.getLineEnding()); + } Map> statementsBySubject = cachedStatements.stream() .collect(Collectors.groupingBy(Statement::getSubject)); List sortedSubjects = new ArrayList<>(statementsBySubject.keySet()); - if (config.sortSubjects()) { + if (this.config instanceof PrettyPrintOptions prettyPrintOptions + && prettyPrintOptions.sortSubjects()) { Collections.sort(sortedSubjects, Comparator.comparing(Value::stringValue)); } + String zeroIndent = ""; for (Resource subject : sortedSubjects) { - writeDescriptionElement(writer, subject, statementsBySubject.get(subject), config.getIndent()); + if(this.config instanceof PrettyPrintOptions prettyPrintOptions) { + writeDescriptionElement(writer, subject, statementsBySubject.get(subject), prettyPrintOptions.getIndent()); + } else { + writeDescriptionElement(writer, subject, statementsBySubject.get(subject), zeroIndent); + } } writer.write(SerializationConstants.RDF_ROOT_END); - writer.write(config.getLineEnding()); + if(this.config instanceof LineEndingOptions lineEndingOptions) { + writer.write(lineEndingOptions.getLineEnding()); + } } /** @@ -175,19 +176,33 @@ private void writeRdfRootElement(Writer writer) throws IOException { * @param writer the {@link Writer} to which attributes will be written. * @throws IOException if an I/O error occurs. */ - private void writeNamespaceAttributes(Writer writer) throws IOException { - if (!iriToPrefixMapping.containsKey(RDF.getVocabularyNamespace())) { - addPrefixMapping(RDF.getVocabularyNamespace(), RDF.getVocabularyPreferredPrefix()); + private void writeNamespaceAttributes(Writer writer, Set actuallyUsedNamespaces) throws IOException { + ArrayList namespacelist = new ArrayList<>(actuallyUsedNamespaces); + + if(this.config instanceof UsesPrefixOptions usesPrefixOptions + && usesPrefixOptions.autoDeclarePrefixes()) { + + namespacelist.forEach(namespace -> { + if (! this.prefixHandler.hasNamespace(namespace)) { + String prefix = getSuggestedPrefix(namespace); + if (prefix != null) { + this.prefixHandler.setPrefix(prefix, namespace); + } + } + }); } - List prefixes = new ArrayList<>(prefixToIriMapping.keySet()); - if (config.getPrefixOrdering() == PrefixOrderingEnum.ALPHABETICAL) { - Collections.sort(prefixes); + if (this.config instanceof PrettyPrintOptions prettyPrintOptions + && prettyPrintOptions.getPrefixOrdering() == PrefixOrderingEnum.ALPHABETICAL) { + namespacelist.sort( + (ns1, ns2) -> + prefixHandler.getPrefix(ns1).compareTo(prefixHandler.getPrefix(ns2)) + ); } - for (String prefix : prefixes) { - String namespaceURI = prefixToIriMapping.get(prefix); - writer.write(String.format(" %s%s=\"%s\"", SerializationConstants.XMLNS_PREFIX, prefix, escapeXmlAttribute(namespaceURI))); + for(String namespace : namespacelist) { + String prefix = this.prefixHandler.getPrefix(namespace); + writer.write(String.format(" %s%s=\"%s\"", SerializationConstants.XMLNS_PREFIX, prefix, escapeXmlAttribute(namespace))); } } @@ -195,26 +210,30 @@ private void writeNamespaceAttributes(Writer writer) throws IOException { * Collects all namespaces used in the model (subjects, predicates, objects, contexts) * and attempts to assign prefixes if auto-declaration is enabled and they are not already mapped. */ - private void collectUsedNamespaces() { - Set namespaces = this.cachedStatements.stream() - .flatMap(stmt -> Arrays.asList( - stmt.getSubject(), - stmt.getPredicate(), - stmt.getObject() - ).stream()) + private Set collectUsedNamespaces() { + // Collecting namespaces of all IRIs in the data + Set potentialNamespaces = this.cachedStatements.stream() + .flatMap(stmt -> { + List values = new ArrayList<>(Arrays.asList( + stmt.getSubject(), + stmt.getPredicate(), + stmt.getObject() + )); + if (stmt.getContext() != null) { + values.add(stmt.getContext()); + } + if(stmt.getObject().isLiteral() + && ((Literal) stmt.getObject()).getDatatype() != null) { + values.add(((Literal) stmt.getObject()).getDatatype()); + } + return values.stream(); + }) + .filter(Objects::nonNull) .filter(Value::isIRI) - .map(v -> getNamespace(v.stringValue())) + .map(v -> IRIUtils.guessNamespace(v.stringValue())) .collect(Collectors.toSet()); - - namespaces.forEach(namespace -> { - if (!iriToPrefixMapping.containsKey(namespace)) { - String prefix = getSuggestedPrefix(namespace); - if (prefix != null) { - addPrefixMapping(namespace, prefix); - } - } - }); + return potentialNamespaces; } @@ -230,11 +249,9 @@ private String getPrefixedNameInternal(String iriString) { String correspondingPrefix = null; int longestMatchLength = -1; - for (Map.Entry entry : iriToPrefixMapping.entrySet()) { - String namespace = entry.getKey(); - String prefix = entry.getValue(); - + for (String namespace : this.prefixHandler.getNamespaces()) { if (iriString.startsWith(namespace)) { + String prefix = this.prefixHandler.getPrefix(namespace); if (namespace.length() > longestMatchLength) { longestMatchLength = namespace.length(); longestMatchingNamespace = namespace; @@ -245,7 +262,6 @@ private String getPrefixedNameInternal(String iriString) { if (longestMatchingNamespace != null) { String localName = iriString.substring(longestMatchingNamespace.length()); - if (localName.isEmpty()) { return correspondingPrefix + SerializationConstants.COLON; } @@ -254,60 +270,6 @@ private String getPrefixedNameInternal(String iriString) { return null; } - /** - * Adds a prefix-namespace URI mapping to the internal mappings. - * Handles potential conflicts to ensure uniqueness. - * - * @param namespaceURI The namespace URI. - * @param prefix The associated prefix. - */ - private void addPrefixMapping(String namespaceURI, String prefix) { - if (iriToPrefixMapping.containsKey(namespaceURI)) { - if (iriToPrefixMapping.get(namespaceURI).equals(prefix)) { - return; - } else { - - if (logger.isWarnEnabled()) { - logger.warn("Namespace URI '{}' is already mapped to prefix '{}'. Cannot map to new prefix '{}'. " + - "Existing mapping for this namespace will be retained.", - namespaceURI, iriToPrefixMapping.get(namespaceURI), prefix); - } - return; - } - } - - String effectivePrefix = prefix; - if (prefixToIriMapping.containsKey(prefix)) { - if (!prefixToIriMapping.get(prefix).equals(namespaceURI)) { - if (logger.isWarnEnabled()) { - logger.warn("Prefix '{}' is already mapped to namespace '{}'. Cannot map to new namespace '{}'. " + - "A new unique prefix will be generated for '{}'.", - prefix, prefixToIriMapping.get(prefix), namespaceURI, namespaceURI); - } - effectivePrefix = generateUniquePrefix(prefix); - } - } - - iriToPrefixMapping.put(namespaceURI, effectivePrefix); - prefixToIriMapping.put(effectivePrefix, namespaceURI); - } - - /** - * Generates a unique prefix based on a given base string, ensuring it's not already in use. - * This method appends numbers to the base prefix until a unique one is found. - * - * @param basePrefix The desired base prefix (e.g., "foaf"). - * @return A unique prefix (e.g., "foaf", "foaf1", "foaf2"). - */ - private String generateUniquePrefix(String basePrefix) { - String candidate = basePrefix; - int i = 0; - while (prefixToIriMapping.containsKey(candidate)) { - candidate = basePrefix + (++i); - } - return candidate; - } - /** * Writes an `` element for a given subject. * This element contains all properties (predicates and objects) for that subject. @@ -319,7 +281,10 @@ private String generateUniquePrefix(String basePrefix) { * @throws IOException if an I/O error occurs. */ private void writeDescriptionElement(Writer writer, Resource subject, List statements, String currentIndent) throws IOException { - String nextIndent = currentIndent + config.getIndent(); + String nextIndent = currentIndent; + if(this.config instanceof PrettyPrintOptions prettyPrintOptions) { + nextIndent = currentIndent + prettyPrintOptions.getIndent(); + } writer.write(currentIndent); if (subject.isIRI()) { @@ -327,13 +292,15 @@ private void writeDescriptionElement(Writer writer, Resource subject, List", SerializationConstants.RDF_DESCRIPTION_START, SerializationConstants.RDF_NODEID_ATTRIBUTE, getBlankNodeId(subject))); } - writer.write(config.getLineEnding()); + if(this.config instanceof LineEndingOptions lineEndingOptions) { + writer.write(lineEndingOptions.getLineEnding()); + } Map> statementsByPredicate = statements.stream() .collect(Collectors.groupingBy(Statement::getPredicate)); List sortedPredicates = new ArrayList<>(statementsByPredicate.keySet()); - if (config.sortPredicates()) { + if (this.config instanceof PrettyPrintOptions prettyPrintOptions && prettyPrintOptions.sortPredicates()) { Collections.sort(sortedPredicates, Comparator.comparing(Value::stringValue)); } @@ -345,7 +312,9 @@ private void writeDescriptionElement(Writer writer, Resource subject, List", SerializationConstants.RDF_RESOURCE_ATTRIBUTE, escapeXmlAttribute(object.stringValue()))); - writer.write(config.getLineEnding()); + if(this.config instanceof LineEndingOptions lineEndingOptions) { + writer.write(lineEndingOptions.getLineEnding()); + } } else if (object.isBNode()) { writer.write(String.format(" %s=\"%s\"/>", SerializationConstants.RDF_NODEID_ATTRIBUTE, getBlankNodeId((Resource) object))); - writer.write(config.getLineEnding()); + if(this.config instanceof LineEndingOptions lineEndingOptions) { + writer.write(lineEndingOptions.getLineEnding()); + } } else if (object.isLiteral()) { Literal literal = (Literal) object; @@ -397,15 +369,12 @@ private void writePropertyElement(Writer writer, IRI predicate, Value object, St writer.write(">"); } - if (config.useMultilineLiterals() && (literal.stringValue().contains(SerializationConstants.LINE_FEED) || literal.stringValue().contains(SerializationConstants.CARRIAGE_RETURN))) { - - writer.write(escapeXmlContent(literal.stringValue())); - } else { - writer.write(escapeXmlContent(literal.stringValue())); - } + writer.write(escapeXmlContent(literal.stringValue())); writer.write(String.format("", elementName)); - writer.write(config.getLineEnding()); + if(this.config instanceof LineEndingOptions lineEndingOptions) { + writer.write(lineEndingOptions.getLineEnding()); + } } else { throw new IllegalArgumentException("Unsupported value type for RDF/XML serialization: " + object.getClass().getName()); } @@ -419,7 +388,7 @@ private void writePropertyElement(Writer writer, IRI predicate, Value object, St */ private String getBlankNodeId(Resource bNode) { return blankNodeIds.computeIfAbsent(bNode, k -> { - if (config.stableBlankNodeIds()) { + if (this.config instanceof BlankNodeIdGenerationOptions bnGenOptions && bnGenOptions.stableBlankNodeIds()) { return "b" + (blankNodeCounter++); } else { return bNode.stringValue().substring(2); @@ -443,33 +412,10 @@ private boolean shouldWriteDatatype(Literal literal) { return false; } - return config.getLiteralDatatypePolicy() == LiteralDatatypePolicyEnum.ALWAYS_TYPED || - (!datatype.equals(XSD.xsdString.getIRI()) && - config.getLiteralDatatypePolicy() == LiteralDatatypePolicyEnum.MINIMAL); - } - - - /** - * Extracts the namespace URI part from an IRI string. - * This is a common heuristic for RDF IRIs. - * - * @param iriString The full IRI. - * @return The namespace URI part. - */ - private String getNamespace(String iriString) { - int hashIdx = iriString.lastIndexOf(SerializationConstants.HASH); - int slashIdx = iriString.lastIndexOf(SerializationConstants.SLASH); - - if (hashIdx > -1) { - return iriString.substring(0, hashIdx + 1); - } else if (slashIdx > -1 && slashIdx < iriString.length() - 1) { - int dotIdx = iriString.lastIndexOf(SerializationConstants.POINT); - if (dotIdx > slashIdx) { - return iriString.substring(0, slashIdx + 1); - } - return iriString.substring(0, slashIdx + 1); - } - return iriString; + return config instanceof DatatypePolicyOptions datatypePolicyOptions + && (datatypePolicyOptions.getLiteralDatatypePolicy() == LiteralDatatypePolicyEnum.ALWAYS_TYPED + || (!datatype.equals(XSD.xsdString.getIRI()) + && datatypePolicyOptions.getLiteralDatatypePolicy() == LiteralDatatypePolicyEnum.MINIMAL)); } /** @@ -480,14 +426,6 @@ private String getNamespace(String iriString) { * @return A suggested prefix, or null if suggestion is not possible. */ private String getSuggestedPrefix(String namespace) { - - if (namespace.equals(RDF.getVocabularyNamespace())) return RDF.getVocabularyPreferredPrefix(); - if (namespace.equals(RDFS.getVocabularyNamespace())) return RDFS.getVocabularyPreferredPrefix(); - if (namespace.equals(XSD.getVocabularyNamespace())) return XSD.getVocabularyPreferredPrefix(); - if (namespace.equals(OWL.getVocabularyNamespace())) return OWL.getVocabularyPreferredPrefix(); - if (namespace.equals(FOAF.getVocabularyNamespace())) return FOAF.getVocabularyPreferredPrefix(); - - String base = namespace; if (base.endsWith(SerializationConstants.HASH) || base.endsWith(SerializationConstants.SLASH)) { base = base.substring(0, base.length() - 1); @@ -509,7 +447,6 @@ private String getSuggestedPrefix(String namespace) { base = "p"; } } catch (java.net.URISyntaxException e) { - logger.warn("Malformed URI encountered while suggesting prefix: {}", namespace, e); base = "p"; } } @@ -519,7 +456,7 @@ private String getSuggestedPrefix(String namespace) { String candidate = base; int i = 0; - while (prefixToIriMapping.containsKey(candidate) && !prefixToIriMapping.get(candidate).equals(namespace)) { + while (this.prefixHandler.hasPrefix(candidate) && !this.prefixHandler.getPrefix(candidate).equals(namespace)) { candidate = base + (++i); } return candidate; diff --git a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/rdfxml/RDFXMLSerializerOption.java b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/rdfxml/RDFXMLSerializerOptions.java similarity index 92% rename from src/main/java/fr/inria/corese/core/next/impl/io/serialization/rdfxml/RDFXMLSerializerOption.java rename to src/main/java/fr/inria/corese/core/next/impl/io/serialization/rdfxml/RDFXMLSerializerOptions.java index b6855b61b..7876e181f 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/rdfxml/RDFXMLSerializerOption.java +++ b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/rdfxml/RDFXMLSerializerOptions.java @@ -1,7 +1,9 @@ package fr.inria.corese.core.next.impl.io.serialization.rdfxml; +import fr.inria.corese.core.next.api.io.serializer.PrettyPrintOptions; +import fr.inria.corese.core.next.api.io.serializer.UsesPrefixOptions; import fr.inria.corese.core.next.impl.common.prefix.PrefixHandler; -import fr.inria.corese.core.next.impl.io.serialization.option.AbstractSerializerOption; +import fr.inria.corese.core.next.impl.io.serialization.option.AbstractSerializerOptions; import fr.inria.corese.core.next.impl.io.serialization.option.LiteralDatatypePolicyEnum; import fr.inria.corese.core.next.impl.io.serialization.option.PrefixOrderingEnum; import fr.inria.corese.core.next.impl.io.serialization.util.SerializationConstants; @@ -11,13 +13,13 @@ /** * Configuration for RDF/XML serialization format. - * This class extends {@link AbstractSerializerOption} directly as RDF/XML has + * This class extends {@link AbstractSerializerOptions} directly as RDF/XML has * distinct serialization characteristics not shared by the Turtle or N-Family formats. * - *

Use the {@link Builder} class to create instances of {@code RDFXMLSerializerOption}. + *

Use the {@link Builder} class to create instances of {@code RDFXMLSerializerOptions}. * A predefined default configuration is available via {@link #defaultConfig()}.

*/ -public class RDFXMLSerializerOption extends AbstractSerializerOption { +public class RDFXMLSerializerOptions extends AbstractSerializerOptions implements PrettyPrintOptions, UsesPrefixOptions { /** * Whether prefix declarations (e.g., `xmlns:prefix="uri"`) should be used for compact IRIs. @@ -76,7 +78,7 @@ public class RDFXMLSerializerOption extends AbstractSerializerOption { * * @param builder The builder instance containing the desired configuration values. */ - protected RDFXMLSerializerOption(Builder builder) { + protected RDFXMLSerializerOptions(Builder builder) { super(builder); this.usePrefixes = builder.usePrefixes; @@ -97,6 +99,7 @@ protected RDFXMLSerializerOption(Builder builder) { * * @return {@code true} if prefixes are used, {@code false} otherwise. */ + @Override public boolean usePrefixes() { return usePrefixes; } @@ -106,6 +109,7 @@ public boolean usePrefixes() { * * @return {@code true} if auto-declaration is enabled, {@code false} otherwise. */ + @Override public boolean autoDeclarePrefixes() { return autoDeclarePrefixes; } @@ -115,6 +119,7 @@ public boolean autoDeclarePrefixes() { * * @return The {@link PrefixOrderingEnum} for prefix ordering. */ + @Override public PrefixOrderingEnum getPrefixOrdering() { return prefixOrdering; } @@ -124,26 +129,17 @@ public PrefixOrderingEnum getPrefixOrdering() { * * @return The {@link PrefixHandler} instance. */ + @Override public PrefixHandler getPrefixHandler() { return prefixHandler; } - /** - * Returns an unmodifiable map of custom URI prefixes for backward compatibility. - * - * @return A map where keys are prefix names and values are namespace URIs. - * @deprecated Use {@link #getPrefixHandler()} instead for full prefix management capabilities. - */ - @Deprecated - public Map getCustomPrefixes() { - return prefixHandler.getPrefixMap(); - } - /** * Checks if human-readable formatting (pretty-printing) is enabled. * * @return {@code true} if pretty-printing is enabled, {@code false} otherwise. */ + @Override public boolean prettyPrint() { return prettyPrint; } @@ -153,6 +149,7 @@ public boolean prettyPrint() { * * @return The indentation string. */ + @Override public String getIndent() { return indent; } @@ -162,6 +159,7 @@ public String getIndent() { * * @return The maximum line length. */ + @Override public int getMaxLineLength() { return maxLineLength; } @@ -171,6 +169,7 @@ public int getMaxLineLength() { * * @return {@code true} if subject sorting is enabled, {@code false} otherwise. */ + @Override public boolean sortSubjects() { return sortSubjects; } @@ -180,6 +179,7 @@ public boolean sortSubjects() { * * @return {@code true} if predicate sorting is enabled, {@code false} otherwise. */ + @Override public boolean sortPredicates() { return sortPredicates; } @@ -194,11 +194,11 @@ public boolean useMultilineLiterals() { } /** - * Public Builder for {@link RDFXMLSerializerOption}. - * Provides a fluent API for constructing {@code RDFXMLSerializerOption} instances with default values + * Public Builder for {@link RDFXMLSerializerOptions}. + * Provides a fluent API for constructing {@code RDFXMLSerializerOptions} instances with default values * specific to the RDF/XML format. */ - public static class Builder extends AbstractSerializerOption.AbstractBuilder { + public static class Builder extends AbstractSerializerOptions.AbstractBuilder { protected boolean usePrefixes = true; protected boolean autoDeclarePrefixes = true; protected PrefixOrderingEnum prefixOrdering = PrefixOrderingEnum.ALPHABETICAL; @@ -364,13 +364,13 @@ public Builder useMultilineLiterals(boolean useMultilineLiterals) { } /** - * Builds and returns a new {@link RDFXMLSerializerOption} instance with the current builder settings. + * Builds and returns a new {@link RDFXMLSerializerOptions} instance with the current builder settings. * - * @return A new {@code RDFXMLSerializerOption} instance. + * @return A new {@code RDFXMLSerializerOptions} instance. */ @Override - public RDFXMLSerializerOption build() { - return new RDFXMLSerializerOption(this); + public RDFXMLSerializerOptions build() { + return new RDFXMLSerializerOptions(this); } } @@ -379,9 +379,9 @@ public RDFXMLSerializerOption build() { * This provides a convenient way to get a standard RDF/XML configuration without * manually building it. * - * @return A {@code RDFXMLSerializerOption} instance with default settings. + * @return A {@code RDFXMLSerializerOptions} instance with default settings. */ - public static RDFXMLSerializerOption defaultConfig() { + public static RDFXMLSerializerOptions defaultConfig() { return new Builder().build(); } } \ No newline at end of file diff --git a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/trig/TriGSerializer.java b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/trig/TriGSerializer.java index 7177757b6..a3f7cfab9 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/trig/TriGSerializer.java +++ b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/trig/TriGSerializer.java @@ -15,7 +15,12 @@ import fr.inria.corese.core.next.api.Resource; import fr.inria.corese.core.next.api.Statement; import fr.inria.corese.core.next.api.base.io.RDFFormat; +import fr.inria.corese.core.next.api.io.IOOptions; +import fr.inria.corese.core.next.api.io.serializer.LineEndingOptions; +import fr.inria.corese.core.next.api.io.serializer.PrettyPrintOptions; import fr.inria.corese.core.next.impl.io.serialization.base.AbstractGraphSerializer; +import fr.inria.corese.core.next.impl.io.serialization.option.AbstractSerializerOptions; +import fr.inria.corese.core.next.impl.io.serialization.option.AbstractTFamilyOptions; import fr.inria.corese.core.next.impl.io.serialization.util.SerializationConstants; /** @@ -61,24 +66,9 @@ public TriGSerializer(Model model) { * This config object should be an instance of {@code TriGConfig} or a subclass thereof. * @throws NullPointerException if the provided model or configuration is null. */ - public TriGSerializer(Model model, TriGSerializerOptions config) { + public TriGSerializer(Model model, IOOptions config) { super(model, config); - Objects.requireNonNull(config, "TriGConfig cannot be null"); - } - - /** - * Helper method to safely cast the generic config to TriGConfig. - * This should be called before accessing any methods specific to TriGConfig. - * - * @return The config cast to TriGConfig. - * @throws IllegalStateException if the config is not an instance of TriGConfig. - */ - private TriGSerializerOptions getTriGConfig() { - if (!(option instanceof TriGSerializerOptions)) { - throw new IllegalStateException("Current serializer configuration is not an instance of TriGConfig. " + - "TriGSerializer requires a TriGConfig instance."); - } - return (TriGSerializerOptions) option; + Objects.requireNonNull(config, "config cannot be null"); } /** @@ -90,11 +80,9 @@ private TriGSerializerOptions getTriGConfig() { */ @Override protected void doWriteStatements(Writer writer) throws IOException { - TriGSerializerOptions trigConfig = getTriGConfig(); - - if (trigConfig.includeContext()) { + if (this.option instanceof AbstractTFamilyOptions trigConfig && trigConfig.includeContext()) { writeStatementsWithContext(writer); - } else if (trigConfig.useCompactTriples() && trigConfig.groupBySubject()) { + } else if (this.option instanceof AbstractTFamilyOptions trigConfig && trigConfig.useCompactTriples() && trigConfig.groupBySubject()) { writeOptimizedStatements(writer); } else { writeSimpleStatements(writer); @@ -110,19 +98,18 @@ protected void doWriteStatements(Writer writer) throws IOException { * @throws IOException if an I/O error occurs. */ private void writeStatementsWithContext(Writer writer) throws IOException { - TriGSerializerOptions trigConfig = getTriGConfig(); - Map> byContext = new HashMap<>(); model.stream() .filter(stmt -> !isConsumed(stmt.getSubject())) .forEach(stmt -> byContext.computeIfAbsent(stmt.getContext(), k -> new ArrayList<>()).add(stmt)); + for (Map.Entry> contextEntry : byContext.entrySet()) { Resource context = contextEntry.getKey(); List statementsInContext = contextEntry.getValue(); String initialIndent = ""; - String graphIndent = trigConfig.prettyPrint() ? trigConfig.getIndent() : ""; + String graphIndent = this.option instanceof PrettyPrintOptions prettyConfig && prettyConfig.prettyPrint() ? prettyConfig.getIndent() : ""; if (context != null) { if (context.isIRI()) { @@ -132,11 +119,13 @@ private void writeStatementsWithContext(Writer writer) throws IOException { } writer.write(SerializationConstants.SPACE); writer.write(SerializationConstants.OPEN_BRACE); - writer.write(trigConfig.getLineEnding()); + if(this.option instanceof LineEndingOptions lineEndingOptions) { + writer.write(lineEndingOptions.getLineEnding()); + } initialIndent = graphIndent; } - Map> bySubject = trigConfig.sortSubjects() + Map> bySubject = this.option instanceof PrettyPrintOptions prettyConfig && prettyConfig.sortSubjects() ? new TreeMap<>(Comparator.nullsFirst(Comparator.comparing(Resource::stringValue))) : new HashMap<>(); @@ -147,7 +136,7 @@ private void writeStatementsWithContext(Writer writer) throws IOException { writeValue(writer, subjectEntry.getKey()); writer.write(SerializationConstants.SPACE); - Map> byPredicate = trigConfig.sortPredicates() ? + Map> byPredicate = this.option instanceof PrettyPrintOptions prettyConfig && prettyConfig.sortPredicates() ? new TreeMap<>(Comparator.comparing(IRI::stringValue)) : new HashMap<>(); @@ -157,8 +146,8 @@ private void writeStatementsWithContext(Writer writer) throws IOException { for (Map.Entry> predicateEntry : byPredicate.entrySet()) { if (!firstPredicate) { writer.write(SerializationConstants.SEMICOLON); - if (trigConfig.prettyPrint()) { - writer.write(trigConfig.getLineEnding() + initialIndent + trigConfig.getIndent()); + if (this.option instanceof PrettyPrintOptions prettyConfig && this.option instanceof LineEndingOptions lineEndingOptions && prettyConfig.prettyPrint()) { + writer.write(lineEndingOptions.getLineEnding() + initialIndent + prettyConfig.getIndent()); } else { writer.write(SerializationConstants.SPACE); } @@ -172,8 +161,10 @@ private void writeStatementsWithContext(Writer writer) throws IOException { for (Statement stmt : predicateEntry.getValue()) { if (!firstObject) { writer.write(SerializationConstants.COMMA); - if (trigConfig.prettyPrint()) { - writer.write(trigConfig.getLineEnding() + initialIndent + trigConfig.getIndent() + trigConfig.getIndent()); + if (this.option instanceof PrettyPrintOptions prettyConfig + && this.option instanceof LineEndingOptions lineEndingOptions + && prettyConfig.prettyPrint()) { + writer.write(lineEndingOptions.getLineEnding() + initialIndent + prettyConfig.getIndent() + prettyConfig.getIndent()); } else { writer.write(SerializationConstants.SPACE); } @@ -183,14 +174,20 @@ private void writeStatementsWithContext(Writer writer) throws IOException { } } writer.write(SerializationConstants.SPACE + SerializationConstants.POINT); - writer.write(trigConfig.getLineEnding()); + if(this.option instanceof LineEndingOptions lineEndingOptions) { + writer.write(lineEndingOptions.getLineEnding()); + } } if (context != null) { writer.write(SerializationConstants.CLOSE_BRACE); - writer.write(trigConfig.getLineEnding()); + if(this.option instanceof LineEndingOptions lineEndingOptions) { + writer.write(lineEndingOptions.getLineEnding()); + } + } + if(this.option instanceof LineEndingOptions lineEndingOptions) { + writer.write(lineEndingOptions.getLineEnding()); } - writer.write(trigConfig.getLineEnding()); } } @@ -251,7 +248,11 @@ protected String escapeLiteralString(String value) { sb.append(SerializationConstants.BACK_SLASH).append(SerializationConstants.BACK_SLASH); break; default: - if (option.escapeUnicode() && (c <= 0x1F || c == 0x7F || (c >= 0x80 && c <= 0xFFFF))) { + if (this.option instanceof AbstractSerializerOptions abstractSerializerOptions + && abstractSerializerOptions.escapeUnicode() + && (c <= 0x1F + || c == 0x7F + || (c >= 0x80 && c <= 0xFFFF))) { sb.append(String.format("\\u%04X", (int) c)); } else if (Character.isHighSurrogate(c)) { int codePoint = value.codePointAt(i); diff --git a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/trig/TriGSerializerOptions.java b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/trig/TriGSerializerOptions.java index 02633159c..31adeb113 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/trig/TriGSerializerOptions.java +++ b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/trig/TriGSerializerOptions.java @@ -1,17 +1,17 @@ package fr.inria.corese.core.next.impl.io.serialization.trig; -import fr.inria.corese.core.next.impl.io.serialization.option.AbstractTFamilyOption; +import fr.inria.corese.core.next.impl.io.serialization.option.AbstractTFamilyOptions; import fr.inria.corese.core.next.impl.io.serialization.option.BlankNodeStyleEnum; /** * Configuration for TriG serialization format. - * This class extends {@link AbstractTFamilyOption} and provides specific defaults + * This class extends {@link AbstractTFamilyOptions} and provides specific defaults * and options tailored for TriG, which extends Turtle with named graphs. * *

Use the {@link Builder} class to create instances of {@code TriGConfig}. * A predefined default configuration is available via {@link #defaultConfig()}.

*/ -public class TriGSerializerOptions extends AbstractTFamilyOption { +public class TriGSerializerOptions extends AbstractTFamilyOptions { /** * Protected constructor to be used by the {@link Builder}. @@ -27,7 +27,7 @@ protected TriGSerializerOptions(Builder builder) { * Provides a fluent API for constructing {@code TriGConfig} instances with default values * specific to the TriG format. */ - public static class Builder extends AbstractTFamilyOption.AbstractTFamilyBuilder { + public static class Builder extends AbstractTFamilyOptions.AbstractTFamilyBuilder { /** * Default constructor initializes all options with their default values for TriG. */ diff --git a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/turtle/TurtleSerializer.java b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/turtle/TurtleSerializer.java index 917c4f664..14f5115ef 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/turtle/TurtleSerializer.java +++ b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/turtle/TurtleSerializer.java @@ -5,13 +5,15 @@ import java.util.Objects; import fr.inria.corese.core.next.api.base.io.RDFFormat; +import fr.inria.corese.core.next.api.io.IOOptions; +import fr.inria.corese.core.next.impl.io.serialization.option.AbstractSerializerOptions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import fr.inria.corese.core.next.api.Model; import fr.inria.corese.core.next.api.Statement; import fr.inria.corese.core.next.impl.io.serialization.base.AbstractGraphSerializer; -import fr.inria.corese.core.next.impl.io.serialization.option.AbstractTFamilyOption; +import fr.inria.corese.core.next.impl.io.serialization.option.AbstractTFamilyOptions; import fr.inria.corese.core.next.impl.io.serialization.util.SerializationConstants; /** @@ -57,10 +59,11 @@ public TurtleSerializer(Model model) { * @param config the {@link TurtleSerializerOptions} to use for serialization. Must not be null. * @throws NullPointerException if the provided model or configuration is null. */ - public TurtleSerializer(Model model, TurtleSerializerOptions config) { + public TurtleSerializer(Model model, IOOptions config) { super(model, config); - Objects.requireNonNull(config, "TurtleConfig cannot be null"); + Objects.requireNonNull(config, "config cannot be null"); } + /** * Retrieves the RDF format supported by this serializer, which is Turtle * @@ -81,7 +84,7 @@ public RDFFormat getRDFFormat() { */ @Override protected void doWriteStatements(Writer writer) throws IOException { - AbstractTFamilyOption tFamilyConfig = (AbstractTFamilyOption) option; + AbstractTFamilyOptions tFamilyConfig = (AbstractTFamilyOptions) option; if (tFamilyConfig.useCompactTriples() && tFamilyConfig.groupBySubject()) { writeOptimizedStatements(writer); @@ -150,12 +153,16 @@ protected String escapeLiteralString(String value) { if (Character.isISOControl(c) ||c == 0x7F) { sb.append(String.format("\\u%04X", (int) c)); } - else if (option.escapeUnicode() && c >= 0x80 && c <= 0xFFFF) { + else if (this.option instanceof AbstractSerializerOptions abstractSerializerOptions + && abstractSerializerOptions.escapeUnicode() + && c >= 0x80 + && c <= 0xFFFF) { sb.append(String.format("\\u%04X", (int) c)); } else if (Character.isHighSurrogate(c)) { int codePoint = value.codePointAt(i); if (Character.isValidCodePoint(codePoint)) { - if (option.escapeUnicode()) { + if (this.option instanceof AbstractSerializerOptions abstractSerializerOptions + && abstractSerializerOptions.escapeUnicode()) { sb.append(String.format("\\U%08X", codePoint)); } else { sb.append(Character.toChars(codePoint)); diff --git a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/turtle/TurtleSerializerOptions.java b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/turtle/TurtleSerializerOptions.java index bfbc71624..8a001358c 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/turtle/TurtleSerializerOptions.java +++ b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/turtle/TurtleSerializerOptions.java @@ -1,17 +1,19 @@ package fr.inria.corese.core.next.impl.io.serialization.turtle; -import fr.inria.corese.core.next.impl.io.serialization.option.AbstractTFamilyOption; +import fr.inria.corese.core.next.api.io.IOOptions; +import fr.inria.corese.core.next.api.io.serializer.LineEndingOptions; +import fr.inria.corese.core.next.impl.io.serialization.option.AbstractTFamilyOptions; import fr.inria.corese.core.next.impl.io.serialization.option.BlankNodeStyleEnum; /** * Configuration for Turtle serialization format. - * This class extends {@link AbstractTFamilyOption} and provides specific defaults + * This class extends {@link AbstractTFamilyOptions} and provides specific defaults * and options tailored for Turtle, such as using collections and anonymous blank nodes. * *

Use the {@link Builder} class to create instances of {@code TurtleConfig}. * A predefined default configuration is available via {@link #defaultConfig()}.

*/ -public class TurtleSerializerOptions extends AbstractTFamilyOption { +public class TurtleSerializerOptions extends AbstractTFamilyOptions { /** * Protected constructor to be used by the {@link Builder}. @@ -27,16 +29,31 @@ protected TurtleSerializerOptions(Builder builder) { * Provides a fluent API for constructing {@code TurtleConfig} instances with default values * specific to the Turtle format. */ - public static class Builder extends AbstractTFamilyOption.AbstractTFamilyBuilder { + public static class Builder extends AbstractTFamilyOptions.AbstractTFamilyBuilder { /** * Default constructor initializes all options with their default values for Turtle. */ public Builder() { + super(); lineEnding(System.lineSeparator()); validateURIs(false); useCollections(true); blankNodeStyle(BlankNodeStyleEnum.ANONYMOUS); + } + /** + * Copy constructor for easy creation of option inheriting setting from others + */ + public Builder(IOOptions otherOption) { + super(otherOption); + if(otherOption instanceof LineEndingOptions lineEndingOptions) { + lineEnding(lineEndingOptions.getLineEnding()); + } + if(otherOption instanceof AbstractTFamilyOptions abstractTFamilyOptions) { + validateURIs(abstractTFamilyOptions.validateURIs()); + useCollections(abstractTFamilyOptions.useCollections()); + blankNodeStyle(abstractTFamilyOptions.getBlankNodeStyle()); + } } /** diff --git a/src/test/java/fr/inria/corese/core/next/impl/common/util/IRIUtilsTest.java b/src/test/java/fr/inria/corese/core/next/impl/common/util/IRIUtilsTest.java index 4336896b5..b1626962b 100644 --- a/src/test/java/fr/inria/corese/core/next/impl/common/util/IRIUtilsTest.java +++ b/src/test/java/fr/inria/corese/core/next/impl/common/util/IRIUtilsTest.java @@ -20,6 +20,8 @@ public class IRIUtilsTest { private static final String uriToHTMLPageWithQueryAndFragment = "https://www.syuno-pit.biz/tezukayama-bandai-2.html?query=1#fragment"; private static final String uriToHTMLPageWithFragment = "https://www.syuno-pit.biz/tezukayama-bandai-2.html#fragment"; private static final String blankNode = "_:n2d65906b09534cabb44314ff2e2b248axb4"; + private static final String uriWithUnexpectedCharactersObject = "http://example.org/obj"ect&apos"; + private static final String uriWithUnexpectedCharactersSubject = "http://example.org/sub&ject"; // Array of strings that should be recognized as correct IRIs. Some of them taken from the official IRI documentation. private static final String[] correctARIs = { uriSchema, uriWithFragment, uriWithQuery, uriWithPort, uriWithPortAndQuery, uriWithPortAndQueryAndFragment, uriWithPortAndFragment, uriToHTMLPage, uriToHTMLPageWithQuery, uriToHTMLPageWithQueryAndFragment, uriToHTMLPageWithFragment, "ftp://ftp.is.co.za/rfc/rfc1808.txt", "http://www.ietf.org/rfc/rfc2396.txt", "ldap://[2001:db8::7]/c=GB?objectClass?one", "mailto:John.Doe@example.com", "news:comp.infosystems.www.servers.unix", "tel:+1-816-555-1212", "telnet://192.0.2.16:80/", "urn:oasis:names:specification:docbook:dtd:xml:4.1.2", "http://foo.co.uk/", "http://regexr.com/foo.html?q=bar" }; @@ -40,6 +42,8 @@ public void guessNamespaceTest() { assertEquals("https://www.syuno-pit.biz/tezukayama-bandai-2.html#", IRIUtils.guessNamespace(uriToHTMLPageWithFragment)); assertEquals("", IRIUtils.guessNamespace(blankNode)); assertEquals("http://www.w3.org/2001/XMLSchema#", IRIUtils.guessNamespace("http://www.w3.org/2001/XMLSchema#")); + assertEquals("http://example.org/", IRIUtils.guessNamespace(uriWithUnexpectedCharactersObject)); + assertEquals("http://example.org/", IRIUtils.guessNamespace(uriWithUnexpectedCharactersSubject)); } @Test @@ -56,6 +60,8 @@ public void guessLocalNameTest() { assertEquals("fragment", IRIUtils.guessLocalName(uriToHTMLPageWithQueryAndFragment)); assertEquals("fragment", IRIUtils.guessLocalName(uriToHTMLPageWithFragment)); assertEquals("", IRIUtils.guessLocalName(blankNode)); + assertEquals("obj"ect&apos", IRIUtils.guessLocalName(uriWithUnexpectedCharactersObject)); + assertEquals("sub&ject", IRIUtils.guessLocalName(uriWithUnexpectedCharactersSubject)); } @Test diff --git a/src/test/java/fr/inria/corese/core/next/impl/io/parser/jsonld/JSONLDCircularTest.java b/src/test/java/fr/inria/corese/core/next/impl/io/parser/jsonld/JSONLDCircularTest.java index f196531b5..c79c85d3c 100644 --- a/src/test/java/fr/inria/corese/core/next/impl/io/parser/jsonld/JSONLDCircularTest.java +++ b/src/test/java/fr/inria/corese/core/next/impl/io/parser/jsonld/JSONLDCircularTest.java @@ -3,7 +3,7 @@ import fr.inria.corese.core.next.api.*; import fr.inria.corese.core.next.api.base.io.RDFFormat; import fr.inria.corese.core.next.api.io.parser.RDFParser; -import fr.inria.corese.core.next.api.io.serialization.RDFSerializer; +import fr.inria.corese.core.next.api.io.serializer.RDFSerializer; import fr.inria.corese.core.next.impl.io.common.JSONLDOptions; import fr.inria.corese.core.next.impl.io.parser.ParserFactory; import fr.inria.corese.core.next.impl.io.serialization.SerializerFactory; @@ -33,7 +33,7 @@ class JSONLDCircularTest { private ValueFactory valueFactory; - private fr.inria.corese.core.next.api.io.serialization.SerializerFactory serializerFactory; + private fr.inria.corese.core.next.api.io.serializer.SerializerFactory serializerFactory; private ParserFactory parserFactory; private JSONLDOptions defaultConfig; diff --git a/src/test/java/fr/inria/corese/core/next/impl/io/parser/nquads/NQuadsCircularTest.java b/src/test/java/fr/inria/corese/core/next/impl/io/parser/nquads/NQuadsCircularTest.java index 2ea8b638d..1bad1a5cc 100644 --- a/src/test/java/fr/inria/corese/core/next/impl/io/parser/nquads/NQuadsCircularTest.java +++ b/src/test/java/fr/inria/corese/core/next/impl/io/parser/nquads/NQuadsCircularTest.java @@ -3,7 +3,7 @@ import fr.inria.corese.core.next.api.*; import fr.inria.corese.core.next.api.base.io.RDFFormat; import fr.inria.corese.core.next.api.io.parser.RDFParser; -import fr.inria.corese.core.next.api.io.serialization.RDFSerializer; +import fr.inria.corese.core.next.api.io.serializer.RDFSerializer; import fr.inria.corese.core.next.impl.io.parser.ParserFactory; import fr.inria.corese.core.next.impl.io.serialization.SerializerFactory; import fr.inria.corese.core.next.impl.io.serialization.nquads.NQuadsSerializerOptions; @@ -32,7 +32,7 @@ class NQuadsCircularTest { private ValueFactory valueFactory; - private fr.inria.corese.core.next.api.io.serialization.SerializerFactory serializerFactory; + private fr.inria.corese.core.next.api.io.serializer.SerializerFactory serializerFactory; private ParserFactory parserFactory; private NQuadsSerializerOptions defaultConfig; diff --git a/src/test/java/fr/inria/corese/core/next/impl/io/parser/ntriples/NTriplesCircularTest.java b/src/test/java/fr/inria/corese/core/next/impl/io/parser/ntriples/NTriplesCircularTest.java index bbfc741be..155b601a7 100644 --- a/src/test/java/fr/inria/corese/core/next/impl/io/parser/ntriples/NTriplesCircularTest.java +++ b/src/test/java/fr/inria/corese/core/next/impl/io/parser/ntriples/NTriplesCircularTest.java @@ -3,7 +3,7 @@ import fr.inria.corese.core.next.api.*; import fr.inria.corese.core.next.api.base.io.RDFFormat; import fr.inria.corese.core.next.api.io.parser.RDFParser; -import fr.inria.corese.core.next.api.io.serialization.RDFSerializer; +import fr.inria.corese.core.next.api.io.serializer.RDFSerializer; import fr.inria.corese.core.next.impl.io.parser.ParserFactory; import fr.inria.corese.core.next.impl.io.serialization.SerializerFactory; import fr.inria.corese.core.next.impl.io.serialization.ntriples.NTriplesSerializerOptions; @@ -30,7 +30,7 @@ class NTriplesCircularTest { private ValueFactory valueFactory; - private fr.inria.corese.core.next.api.io.serialization.SerializerFactory serializerFactory; + private fr.inria.corese.core.next.api.io.serializer.SerializerFactory serializerFactory; private ParserFactory parserFactory; private NTriplesSerializerOptions defaultConfig; diff --git a/src/test/java/fr/inria/corese/core/next/impl/io/parser/rdfxml/RDFXMLCircularTest.java b/src/test/java/fr/inria/corese/core/next/impl/io/parser/rdfxml/RDFXMLCircularTest.java index 997cc0a64..491ca3502 100644 --- a/src/test/java/fr/inria/corese/core/next/impl/io/parser/rdfxml/RDFXMLCircularTest.java +++ b/src/test/java/fr/inria/corese/core/next/impl/io/parser/rdfxml/RDFXMLCircularTest.java @@ -3,10 +3,10 @@ import fr.inria.corese.core.next.api.*; import fr.inria.corese.core.next.api.base.io.RDFFormat; import fr.inria.corese.core.next.api.io.parser.RDFParser; -import fr.inria.corese.core.next.api.io.serialization.RDFSerializer; +import fr.inria.corese.core.next.api.io.serializer.RDFSerializer; import fr.inria.corese.core.next.impl.io.parser.ParserFactory; import fr.inria.corese.core.next.impl.io.serialization.SerializerFactory; -import fr.inria.corese.core.next.impl.io.serialization.rdfxml.RDFXMLSerializerOption; +import fr.inria.corese.core.next.impl.io.serialization.rdfxml.RDFXMLSerializerOptions; import fr.inria.corese.core.next.impl.temp.CoreseAdaptedValueFactory; import fr.inria.corese.core.next.impl.temp.CoreseModel; import org.junit.jupiter.api.BeforeEach; @@ -36,9 +36,9 @@ class RDFXMLCircularTest { private ValueFactory valueFactory; - private fr.inria.corese.core.next.api.io.serialization.SerializerFactory serializerFactory; + private fr.inria.corese.core.next.api.io.serializer.SerializerFactory serializerFactory; private ParserFactory parserFactory; - private RDFXMLSerializerOption defaultConfig; + private RDFXMLSerializerOptions defaultConfig; // Test data constants private static final String EXAMPLE_NS = "http://example.org/"; @@ -60,7 +60,7 @@ void setUp() { valueFactory = new CoreseAdaptedValueFactory(); serializerFactory = new SerializerFactory(); parserFactory = new ParserFactory(); - defaultConfig = RDFXMLSerializerOption.defaultConfig(); + defaultConfig = RDFXMLSerializerOptions.defaultConfig(); } /** diff --git a/src/test/java/fr/inria/corese/core/next/impl/io/parser/rdfxml/RDFXMLUtilsTest.java b/src/test/java/fr/inria/corese/core/next/impl/io/parser/rdfxml/RDFXMLUtilsTest.java index 727d6209f..592ff3623 100644 --- a/src/test/java/fr/inria/corese/core/next/impl/io/parser/rdfxml/RDFXMLUtilsTest.java +++ b/src/test/java/fr/inria/corese/core/next/impl/io/parser/rdfxml/RDFXMLUtilsTest.java @@ -75,7 +75,7 @@ public void testExtractSubjectWithID() { AttributesImpl attrs = new AttributesImpl(); attrs.addAttribute(RDF.type.getNamespace(), "ID", "", "CDATA", "id123"); Resource subject = RDFXMLUtils.extractSubject(attrs, factory, "http://example.org/", null); - assertEquals("http://example.org/id123", subject.stringValue()); + assertEquals("http://example.org/#id123", subject.stringValue()); } /** diff --git a/src/test/java/fr/inria/corese/core/next/impl/io/parser/trig/TriGCircularTest.java b/src/test/java/fr/inria/corese/core/next/impl/io/parser/trig/TriGCircularTest.java index 0fa9d4720..9cf5fdaa6 100644 --- a/src/test/java/fr/inria/corese/core/next/impl/io/parser/trig/TriGCircularTest.java +++ b/src/test/java/fr/inria/corese/core/next/impl/io/parser/trig/TriGCircularTest.java @@ -15,7 +15,7 @@ import fr.inria.corese.core.next.api.ValueFactory; import fr.inria.corese.core.next.api.base.io.RDFFormat; import fr.inria.corese.core.next.api.io.parser.RDFParser; -import fr.inria.corese.core.next.api.io.serialization.RDFSerializer; +import fr.inria.corese.core.next.api.io.serializer.RDFSerializer; import fr.inria.corese.core.next.impl.io.parser.ParserFactory; import fr.inria.corese.core.next.impl.io.serialization.SerializerFactory; import fr.inria.corese.core.next.impl.io.serialization.trig.TriGSerializerOptions; @@ -37,7 +37,7 @@ class TriGCircularTest { private ValueFactory valueFactory; - private fr.inria.corese.core.next.api.io.serialization.SerializerFactory serializerFactory; + private fr.inria.corese.core.next.api.io.serializer.SerializerFactory serializerFactory; private ParserFactory parserFactory; private TriGSerializerOptions defaultConfig; diff --git a/src/test/java/fr/inria/corese/core/next/impl/io/parser/turtle/TurtleCircularTest.java b/src/test/java/fr/inria/corese/core/next/impl/io/parser/turtle/TurtleCircularTest.java index 848c1336e..ab59f1c80 100644 --- a/src/test/java/fr/inria/corese/core/next/impl/io/parser/turtle/TurtleCircularTest.java +++ b/src/test/java/fr/inria/corese/core/next/impl/io/parser/turtle/TurtleCircularTest.java @@ -15,7 +15,7 @@ import fr.inria.corese.core.next.api.ValueFactory; import fr.inria.corese.core.next.api.base.io.RDFFormat; import fr.inria.corese.core.next.api.io.parser.RDFParser; -import fr.inria.corese.core.next.api.io.serialization.RDFSerializer; +import fr.inria.corese.core.next.api.io.serializer.RDFSerializer; import fr.inria.corese.core.next.impl.io.parser.ParserFactory; import fr.inria.corese.core.next.impl.io.serialization.SerializerFactory; import fr.inria.corese.core.next.impl.io.serialization.turtle.TurtleSerializerOptions; @@ -35,7 +35,7 @@ class TurtleCircularTest { private ValueFactory valueFactory; - private fr.inria.corese.core.next.api.io.serialization.SerializerFactory serializerFactory; + private fr.inria.corese.core.next.api.io.serializer.SerializerFactory serializerFactory; private ParserFactory parserFactory; private TurtleSerializerOptions defaultConfig; diff --git a/src/test/java/fr/inria/corese/core/next/impl/io/serialization/SerializerFactoryTest.java b/src/test/java/fr/inria/corese/core/next/impl/io/serialization/SerializerFactoryTest.java index d2d041c09..d23d6165d 100644 --- a/src/test/java/fr/inria/corese/core/next/impl/io/serialization/SerializerFactoryTest.java +++ b/src/test/java/fr/inria/corese/core/next/impl/io/serialization/SerializerFactoryTest.java @@ -3,7 +3,7 @@ import fr.inria.corese.core.next.api.Model; import fr.inria.corese.core.next.api.base.io.RDFFormat; import fr.inria.corese.core.next.api.io.IOOptions; -import fr.inria.corese.core.next.api.io.serialization.RDFSerializer; +import fr.inria.corese.core.next.api.io.serializer.RDFSerializer; import fr.inria.corese.core.next.impl.exception.SerializationException; import fr.inria.corese.core.next.impl.io.common.JSONLDOptions; import fr.inria.corese.core.next.impl.io.serialization.canonical.RDFC10Serializer; @@ -14,7 +14,7 @@ import fr.inria.corese.core.next.impl.io.serialization.ntriples.NTriplesSerializer; import fr.inria.corese.core.next.impl.io.serialization.ntriples.NTriplesSerializerOptions; import fr.inria.corese.core.next.impl.io.serialization.rdfxml.RDFXMLSerializer; -import fr.inria.corese.core.next.impl.io.serialization.rdfxml.RDFXMLSerializerOption; +import fr.inria.corese.core.next.impl.io.serialization.rdfxml.RDFXMLSerializerOptions; import fr.inria.corese.core.next.impl.io.serialization.trig.TriGSerializer; import fr.inria.corese.core.next.impl.io.serialization.trig.TriGSerializerOptions; import fr.inria.corese.core.next.impl.io.serialization.turtle.TurtleSerializer; @@ -104,7 +104,7 @@ void createSerializer_shouldReturnTriGSerializer_forTriGFormat() { @Test @DisplayName("createSerializer should return XmlSerializer for RDFXML format") void createSerializer_shouldReturnXmlSerializer_forRdfXmlFormat() { - RDFXMLSerializerOption config = RDFXMLSerializerOption.defaultConfig(); + RDFXMLSerializerOptions config = RDFXMLSerializerOptions.defaultConfig(); try (MockedConstruction mockedConstruction = mockConstruction(RDFXMLSerializer.class)) { RDFSerializer serializer = factory.createSerializer(RDFFormat.RDFXML, mockModel, config); @@ -141,16 +141,6 @@ void createSerializer_shouldReturnJSONLDSerializer_forJSONLDFormat() { } } - @Test - @DisplayName("createSerializer should throw SerializationException for wrong config type") - void createSerializer_shouldThrowSerializationException_forWrongConfigType() { - IOOptions wrongConfig = mock(IOOptions.class); - - assertThrows(SerializationException.class, - () -> factory.createSerializer(RDFFormat.TURTLE, mockModel, wrongConfig), - "Should throw SerializationException for wrong config type"); - } - @Test @DisplayName("createSerializer should throw NullPointerException for a null format") void createSerializer_shouldThrowNPE_forNullFormat() { @@ -283,4 +273,40 @@ void createSerializerWithoutConfig_shouldThrowNPE_forNullModel() { () -> factory.createSerializer(RDFFormat.TURTLE, null), "Should throw NullPointerException for null Model"); } + + @Test + @DisplayName("Should accept cross-use of config objects") + void configCrossUse() { + JSONLDOptions jsonldOptions = new JSONLDOptions.Builder().build(); + NQuadsSerializerOptions nQuadsSerializerOptions = new NQuadsSerializerOptions.Builder().build(); + NTriplesSerializerOptions nTriplesSerializerOptions = new NTriplesSerializerOptions.Builder().build(); + RDFXMLSerializerOptions rdfxmlSerializerOptions = new RDFXMLSerializerOptions.Builder().build(); + TriGSerializerOptions triGSerializerOptions = new TriGSerializerOptions.Builder().build(); + TurtleSerializerOptions turtleSerializerOptions = new TurtleSerializerOptions.Builder().build(); + + assertDoesNotThrow(() -> { + // JSONLDOptions -- NQuads + factory.createSerializer(RDFFormat.JSONLD, mockModel, nQuadsSerializerOptions); + }); + assertDoesNotThrow(() -> { + // NQuads -- NTriples + factory.createSerializer(RDFFormat.NQUADS, mockModel, nTriplesSerializerOptions); + }); + assertDoesNotThrow(() -> { + // NTriples -- RDFXML + factory.createSerializer(RDFFormat.NTRIPLES, mockModel, rdfxmlSerializerOptions); + }); + assertDoesNotThrow(() -> { + // RDFXML -- TriG + factory.createSerializer(RDFFormat.RDFXML, mockModel, triGSerializerOptions); + }); + assertDoesNotThrow(() -> { + // TriG -- Turtle + factory.createSerializer(RDFFormat.TRIG, mockModel, turtleSerializerOptions); + }); + assertDoesNotThrow(() -> { + // Turtle -- JSONLD + factory.createSerializer(RDFFormat.TURTLE, mockModel, jsonldOptions); + }); + } } \ No newline at end of file diff --git a/src/test/java/fr/inria/corese/core/next/impl/io/serialization/canonical/RDFC10SerializerTest.java b/src/test/java/fr/inria/corese/core/next/impl/io/serialization/canonical/RDFC10SerializerTest.java index 7233acf46..a258dc44e 100644 --- a/src/test/java/fr/inria/corese/core/next/impl/io/serialization/canonical/RDFC10SerializerTest.java +++ b/src/test/java/fr/inria/corese/core/next/impl/io/serialization/canonical/RDFC10SerializerTest.java @@ -3,7 +3,7 @@ import fr.inria.corese.core.next.api.*; import fr.inria.corese.core.next.api.base.io.RDFFormat; import fr.inria.corese.core.next.api.io.parser.RDFParser; -import fr.inria.corese.core.next.api.io.serialization.RDFSerializer; +import fr.inria.corese.core.next.api.io.serializer.RDFSerializer; import fr.inria.corese.core.next.impl.exception.SerializationException; import fr.inria.corese.core.next.impl.io.parser.ParserFactory; import fr.inria.corese.core.next.impl.io.serialization.SerializerFactory; @@ -297,11 +297,11 @@ void testSerializeFigure3() { assertFalse(canonicalOutput.isEmpty(), "Canonical output should not be empty"); String actual = canonicalOutput.trim().replace("\r\n", "\n"); String expected = """ - _:c14n2 . - _:c14n3 . - _:c14n0 _:c14n1 . - _:c14n2 _:c14n1 . - _:c14n3 _:c14n0 ."""; + _:c14n2 . + _:c14n3 . + _:c14n0 _:c14n1 . + _:c14n2 _:c14n1 . + _:c14n3 _:c14n0 ."""; assertEquals(expected, actual, "Canonical output should match expected format"); } @@ -317,10 +317,10 @@ void testSerializeFigure2() { String actual = canonicalOutput.trim().replace("\r\n", "\n"); String expected = """ - _:c14n0 . - _:c14n1 . - _:c14n0 . - _:c14n1 ."""; + _:c14n0 . + _:c14n1 . + _:c14n0 . + _:c14n1 ."""; assertEquals(expected, actual, "Canonical output should match RDFC-1.0 specification"); } diff --git a/src/test/java/fr/inria/corese/core/next/impl/io/serialization/jsonld/JSONLDSerializerTest.java b/src/test/java/fr/inria/corese/core/next/impl/io/serialization/jsonld/JSONLDSerializerTest.java index 0a758c136..a22938244 100644 --- a/src/test/java/fr/inria/corese/core/next/impl/io/serialization/jsonld/JSONLDSerializerTest.java +++ b/src/test/java/fr/inria/corese/core/next/impl/io/serialization/jsonld/JSONLDSerializerTest.java @@ -2,7 +2,7 @@ import com.apicatalog.jsonld.json.JsonLdComparison; import fr.inria.corese.core.next.api.*; -import fr.inria.corese.core.next.api.io.serialization.RDFSerializer; +import fr.inria.corese.core.next.api.io.serializer.RDFSerializer; import fr.inria.corese.core.next.impl.io.common.JSONLDOptions; import fr.inria.corese.core.next.impl.temp.CoreseAdaptedValueFactory; import fr.inria.corese.core.next.impl.temp.CoreseModel; diff --git a/src/test/java/fr/inria/corese/core/next/impl/io/serialization/rdfxml/RDFXMLSerializerTest.java b/src/test/java/fr/inria/corese/core/next/impl/io/serialization/rdfxml/RDFXMLSerializerTest.java index 777d0bf5a..e05250020 100644 --- a/src/test/java/fr/inria/corese/core/next/impl/io/serialization/rdfxml/RDFXMLSerializerTest.java +++ b/src/test/java/fr/inria/corese/core/next/impl/io/serialization/rdfxml/RDFXMLSerializerTest.java @@ -2,6 +2,8 @@ import fr.inria.corese.core.next.api.Model; import fr.inria.corese.core.next.api.Statement; +import fr.inria.corese.core.next.api.io.IOOptions; +import fr.inria.corese.core.next.impl.common.prefix.PrefixHandler; import fr.inria.corese.core.next.impl.common.vocabulary.XSD; import fr.inria.corese.core.next.impl.exception.SerializationException; import fr.inria.corese.core.next.impl.io.serialization.TestStatementFactory; @@ -13,12 +15,13 @@ import org.junit.jupiter.api.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.StringWriter; import java.util.stream.Stream; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.when; /** @@ -28,7 +31,7 @@ class RDFXMLSerializerTest { @Mock private Model mockModel; - RDFXMLSerializerOption mockConfig; + RDFXMLSerializerOptions mockConfig; private TestStatementFactory factory; private StringWriter writer; private AutoCloseable closeable; @@ -38,7 +41,7 @@ void setUp() { closeable = MockitoAnnotations.openMocks(this); writer = new StringWriter(); factory = new TestStatementFactory(); - mockConfig = RDFXMLSerializerOption.defaultConfig(); + mockConfig = RDFXMLSerializerOptions.defaultConfig(); } @AfterEach @@ -58,21 +61,20 @@ void shouldSerializeSimpleIriTriple() throws SerializationException { when(mockModel.stream()).thenReturn(Stream.of(stmt)); - RDFXMLSerializerOption testConfig = new RDFXMLSerializerOption.Builder() + RDFXMLSerializerOptions testConfig = new RDFXMLSerializerOptions.Builder() .autoDeclarePrefixes(true) .usePrefixes(true) .addPrefix("foaf", "http://xmlns.com/foaf/0.1/") .prefixOrdering(PrefixOrderingEnum.ALPHABETICAL) .build(); - RDFXMLSerializer serializer = new RDFXMLSerializer(mockModel, testConfig); serializer.write(writer); String expected = """ - + @@ -93,7 +95,7 @@ void shouldHandleBlankNodeSubject() throws SerializationException { when(mockModel.stream()).thenReturn(Stream.of(stmt)); - RDFXMLSerializerOption testConfig = new RDFXMLSerializerOption.Builder() + RDFXMLSerializerOptions testConfig = new RDFXMLSerializerOptions.Builder() .stableBlankNodeIds(true) .addPrefix("foaf", "http://xmlns.com/foaf/0.1/") .prefixOrdering(PrefixOrderingEnum.ALPHABETICAL) @@ -104,7 +106,7 @@ void shouldHandleBlankNodeSubject() throws SerializationException { String expected = """ - + @@ -126,7 +128,7 @@ void shouldHandleBlankNodeObject() throws SerializationException { when(mockModel.stream()).thenReturn(Stream.of(stmt)); - RDFXMLSerializerOption testConfig = new RDFXMLSerializerOption.Builder() + RDFXMLSerializerOptions testConfig = new RDFXMLSerializerOptions.Builder() .stableBlankNodeIds(true) .addPrefix("dc", "http://purl.org/dc/elements/1.1/") .prefixOrdering(PrefixOrderingEnum.ALPHABETICAL) @@ -138,7 +140,7 @@ void shouldHandleBlankNodeObject() throws SerializationException { String expected = """ - + @@ -159,7 +161,7 @@ void shouldSerializeLiteralWithStringDatatypeMinimalPolicy() throws Serializatio when(mockModel.stream()).thenReturn(Stream.of(stmt)); - RDFXMLSerializerOption testConfig = new RDFXMLSerializerOption.Builder() + RDFXMLSerializerOptions testConfig = new RDFXMLSerializerOptions.Builder() .literalDatatypePolicy(LiteralDatatypePolicyEnum.MINIMAL) .addPrefix("foaf", "http://xmlns.com/foaf/0.1/") .prefixOrdering(PrefixOrderingEnum.ALPHABETICAL) @@ -170,7 +172,7 @@ void shouldSerializeLiteralWithStringDatatypeMinimalPolicy() throws Serializatio String expected = """ - + John Doe @@ -190,7 +192,7 @@ void shouldSerializeLiteralWithCustomDatatypeMinimalPolicy() throws Serializatio when(mockModel.stream()).thenReturn(Stream.of(stmt)); - RDFXMLSerializerOption testConfig = new RDFXMLSerializerOption.Builder() + RDFXMLSerializerOptions testConfig = new RDFXMLSerializerOptions.Builder() .literalDatatypePolicy(LiteralDatatypePolicyEnum.MINIMAL) .addPrefix("ex", "http://example.org/vocabulary/") .addPrefix("xsd", "http://www.w3.org/2001/XMLSchema#") @@ -202,7 +204,7 @@ void shouldSerializeLiteralWithCustomDatatypeMinimalPolicy() throws Serializatio String expected = """ - + 123 @@ -222,7 +224,7 @@ void shouldSerializeLiteralWithLanguage() throws SerializationException { when(mockModel.stream()).thenReturn(Stream.of(stmt)); - RDFXMLSerializerOption testConfig = new RDFXMLSerializerOption.Builder() + RDFXMLSerializerOptions testConfig = new RDFXMLSerializerOptions.Builder() .addPrefix("dc", "http://purl.org/dc/elements/1.1/") .prefixOrdering(PrefixOrderingEnum.ALPHABETICAL) .build(); @@ -232,7 +234,7 @@ void shouldSerializeLiteralWithLanguage() throws SerializationException { String expected = """ - + The Book @@ -259,7 +261,7 @@ void shouldRespectPrefixOrderingDefault() throws SerializationException { when(mockModel.stream()).thenReturn(Stream.of(stmt1, stmt2)); - RDFXMLSerializerOption testConfig = new RDFXMLSerializerOption.Builder() + RDFXMLSerializerOptions testConfig = new RDFXMLSerializerOptions.Builder() .addPrefix("exorg", "http://ex.org/") .addPrefix("excom", "http://ex.com/") .prefixOrdering(PrefixOrderingEnum.USAGE_ORDER) @@ -276,7 +278,6 @@ void shouldRespectPrefixOrderingDefault() throws SerializationException { assertTrue(actual.contains("xmlns:exorg=\"http://ex.org/\"")); assertTrue(actual.contains("xmlns:excom=\"http://ex.com/\"")); - assertTrue(actual.contains("xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"")); String desc1 = " \n \n "; String desc2 = " \n \n "; @@ -301,7 +302,7 @@ void shouldSortSubjectsAlphabetically() throws SerializationException { when(mockModel.stream()).thenReturn(Stream.of(stmt1, stmt2)); - RDFXMLSerializerOption testConfig = new RDFXMLSerializerOption.Builder() + RDFXMLSerializerOptions testConfig = new RDFXMLSerializerOptions.Builder() .sortSubjects(true) .addPrefix("ex", "http://ex.org/") .prefixOrdering(PrefixOrderingEnum.ALPHABETICAL) @@ -312,7 +313,7 @@ void shouldSortSubjectsAlphabetically() throws SerializationException { String expected = """ - + @@ -337,7 +338,7 @@ void shouldEscapeXmlAttributeValues() throws SerializationException { when(mockModel.stream()).thenReturn(Stream.of(stmt)); - RDFXMLSerializerOption testConfig = new RDFXMLSerializerOption.Builder() + RDFXMLSerializerOptions testConfig = new RDFXMLSerializerOptions.Builder() .prefixOrdering(PrefixOrderingEnum.ALPHABETICAL) .build(); @@ -346,7 +347,7 @@ void shouldEscapeXmlAttributeValues() throws SerializationException { String expected = """ - + @@ -367,7 +368,7 @@ void shouldEscapeXmlContentValues() throws SerializationException { when(mockModel.stream()).thenReturn(Stream.of(stmt)); - RDFXMLSerializerOption testConfig = new RDFXMLSerializerOption.Builder() + RDFXMLSerializerOptions testConfig = new RDFXMLSerializerOptions.Builder() .literalDatatypePolicy(LiteralDatatypePolicyEnum.ALWAYS_TYPED) .addPrefix("ex", "http://example.org/") .prefixOrdering(PrefixOrderingEnum.ALPHABETICAL) @@ -379,7 +380,7 @@ void shouldEscapeXmlContentValues() throws SerializationException { String expected = """ - + Value with <tags> & entities @@ -401,7 +402,7 @@ void shouldNotAutoDeclarePrefixesIfDisabled() throws SerializationException { when(mockModel.stream()).thenReturn(Stream.of(stmt)); - RDFXMLSerializerOption testConfig = new RDFXMLSerializerOption.Builder() + RDFXMLSerializerOptions testConfig = new RDFXMLSerializerOptions.Builder() .autoDeclarePrefixes(false) .usePrefixes(true) .prefixOrdering(PrefixOrderingEnum.ALPHABETICAL) @@ -412,9 +413,9 @@ void shouldNotAutoDeclarePrefixesIfDisabled() throws SerializationException { String expected = """ - + - + """; @@ -433,7 +434,7 @@ void shouldNotUsePrefixesIfDisabled() throws SerializationException { when(mockModel.stream()).thenReturn(Stream.of(stmt)); - RDFXMLSerializerOption testConfig = new RDFXMLSerializerOption.Builder() + RDFXMLSerializerOptions testConfig = new RDFXMLSerializerOptions.Builder() .usePrefixes(false) .autoDeclarePrefixes(true) .prefixOrdering(PrefixOrderingEnum.ALPHABETICAL) @@ -471,7 +472,7 @@ void shouldNotGenerateStableBlankNodeIds() throws SerializationException { when(mockModel.stream()).thenReturn(Stream.of(stmt1, stmt2)); - RDFXMLSerializerOption testConfig = new RDFXMLSerializerOption.Builder() + RDFXMLSerializerOptions testConfig = new RDFXMLSerializerOptions.Builder() .stableBlankNodeIds(false) .sortSubjects(true) .addPrefix("ex", "http://example.org/") @@ -484,7 +485,7 @@ void shouldNotGenerateStableBlankNodeIds() throws SerializationException { String expected = """ - + @@ -502,7 +503,7 @@ void shouldNotGenerateStableBlankNodeIds() throws SerializationException { void shouldHandleEmptyModel() throws SerializationException { when(mockModel.stream()).thenReturn(Stream.empty()); - RDFXMLSerializerOption testConfig = new RDFXMLSerializerOption.Builder() + RDFXMLSerializerOptions testConfig = new RDFXMLSerializerOptions.Builder() .prefixOrdering(PrefixOrderingEnum.ALPHABETICAL) .build(); @@ -511,10 +512,21 @@ void shouldHandleEmptyModel() throws SerializationException { String expected = """ - + """; assertEquals(expected, writer.toString()); } + + @Test + @DisplayName("Should accept any IOOptions object") + void shouldAcceptAnyOptionFile() { + assertDoesNotThrow(() -> { + IOOptions option = new IOOptions() { + }; + when(mockModel.stream()).thenReturn(Stream.empty()); + RDFXMLSerializer serializer = new RDFXMLSerializer(mockModel, option); + }); + } } diff --git a/src/test/java/fr/inria/corese/core/next/impl/io/serialization/rdfxml/XmlConfigTest.java b/src/test/java/fr/inria/corese/core/next/impl/io/serialization/rdfxml/XmlConfigTest.java index 126406e5e..dbf9085f2 100644 --- a/src/test/java/fr/inria/corese/core/next/impl/io/serialization/rdfxml/XmlConfigTest.java +++ b/src/test/java/fr/inria/corese/core/next/impl/io/serialization/rdfxml/XmlConfigTest.java @@ -17,7 +17,7 @@ import static org.junit.jupiter.api.Assertions.*; /** - * Unit tests for the {@link RDFXMLSerializerOption} class. + * Unit tests for the {@link RDFXMLSerializerOptions} class. * These tests verify the default configuration settings and the functionality * of the builder pattern for customizing RDF/XML serialization options. */ @@ -26,7 +26,7 @@ class XmlConfigTest { @Test @DisplayName("defaultConfig() should return a config with expected RDF/XML defaults") void defaultConfig_shouldReturnExpectedDefaults() { - RDFXMLSerializerOption config = RDFXMLSerializerOption.defaultConfig(); + RDFXMLSerializerOptions config = RDFXMLSerializerOptions.defaultConfig(); assertNotNull(config, "Default config should not be null"); @@ -64,7 +64,7 @@ void defaultConfig_shouldReturnExpectedDefaults() { @Test @DisplayName("Builder should allow overriding usePrefixes") void builder_shouldAllowOverridingUsePrefixes() { - RDFXMLSerializerOption config = new RDFXMLSerializerOption.Builder() + RDFXMLSerializerOptions config = new RDFXMLSerializerOptions.Builder() .usePrefixes(false) .build(); assertFalse(config.usePrefixes(), "usePrefixes should be overridden to false"); @@ -73,7 +73,7 @@ void builder_shouldAllowOverridingUsePrefixes() { @Test @DisplayName("Builder should allow overriding autoDeclarePrefixes") void builder_shouldAllowOverridingAutoDeclarePrefixes() { - RDFXMLSerializerOption config = new RDFXMLSerializerOption.Builder() + RDFXMLSerializerOptions config = new RDFXMLSerializerOptions.Builder() .autoDeclarePrefixes(false) .build(); assertFalse(config.autoDeclarePrefixes(), "autoDeclarePrefixes should be overridden to false"); @@ -82,7 +82,7 @@ void builder_shouldAllowOverridingAutoDeclarePrefixes() { @Test @DisplayName("Builder should allow overriding prefixOrdering") void builder_shouldAllowOverridingPrefixOrdering() { - RDFXMLSerializerOption config = new RDFXMLSerializerOption.Builder() + RDFXMLSerializerOptions config = new RDFXMLSerializerOptions.Builder() .prefixOrdering(PrefixOrderingEnum.USAGE_ORDER) .build(); assertEquals(PrefixOrderingEnum.USAGE_ORDER, config.getPrefixOrdering(), "prefixOrdering should be overridden to USAGE_ORDER"); @@ -94,7 +94,7 @@ void builder_shouldAllowAddingCustomPrefixes() { String customPrefix = "my"; String customNamespace = "http://my.example.org/"; - RDFXMLSerializerOption config = new RDFXMLSerializerOption.Builder() + RDFXMLSerializerOptions config = new RDFXMLSerializerOptions.Builder() .addPrefix(customPrefix, customNamespace) .build(); @@ -114,7 +114,7 @@ void builder_shouldAllowSettingCustomPrefixHandler() { customHandler.setPrefix("ex", "http://example.org/"); customHandler.setPrefix("custom", "http://custom.org/"); - RDFXMLSerializerOption config = new RDFXMLSerializerOption.Builder() + RDFXMLSerializerOptions config = new RDFXMLSerializerOptions.Builder() .prefixHandler(customHandler) .build(); @@ -132,7 +132,7 @@ void builder_shouldAllowAddingMultiplePrefixes() { customPrefixes.put("ex", "http://example.org/"); customPrefixes.put("custom", "http://custom.org/"); - RDFXMLSerializerOption config = new RDFXMLSerializerOption.Builder() + RDFXMLSerializerOptions config = new RDFXMLSerializerOptions.Builder() .addPrefixes(customPrefixes) .build(); @@ -146,7 +146,7 @@ void builder_shouldAllowAddingMultiplePrefixes() { @Test @DisplayName("Builder should allow overriding prettyPrint") void builder_shouldAllowOverridingPrettyPrint() { - RDFXMLSerializerOption config = new RDFXMLSerializerOption.Builder() + RDFXMLSerializerOptions config = new RDFXMLSerializerOptions.Builder() .prettyPrint(false) .build(); assertFalse(config.prettyPrint(), "prettyPrint should be overridden to false"); @@ -156,7 +156,7 @@ void builder_shouldAllowOverridingPrettyPrint() { @DisplayName("Builder should allow overriding indent") void builder_shouldAllowOverridingIndent() { String customIndent = "\t"; - RDFXMLSerializerOption config = new RDFXMLSerializerOption.Builder() + RDFXMLSerializerOptions config = new RDFXMLSerializerOptions.Builder() .indent(customIndent) .build(); assertEquals(customIndent, config.getIndent(), "indent should be overridden to custom value"); @@ -166,7 +166,7 @@ void builder_shouldAllowOverridingIndent() { @DisplayName("Builder should allow overriding maxLineLength") void builder_shouldAllowOverridingMaxLineLength() { int customLength = 120; - RDFXMLSerializerOption config = new RDFXMLSerializerOption.Builder() + RDFXMLSerializerOptions config = new RDFXMLSerializerOptions.Builder() .maxLineLength(customLength) .build(); assertEquals(customLength, config.getMaxLineLength(), "maxLineLength should be overridden to custom value"); @@ -175,7 +175,7 @@ void builder_shouldAllowOverridingMaxLineLength() { @Test @DisplayName("Builder should allow overriding sortSubjects") void builder_shouldAllowOverridingSortSubjects() { - RDFXMLSerializerOption config = new RDFXMLSerializerOption.Builder() + RDFXMLSerializerOptions config = new RDFXMLSerializerOptions.Builder() .sortSubjects(true) .build(); assertTrue(config.sortSubjects(), "sortSubjects should be overridden to true"); @@ -184,7 +184,7 @@ void builder_shouldAllowOverridingSortSubjects() { @Test @DisplayName("Builder should allow overriding sortPredicates") void builder_shouldAllowOverridingSortPredicates() { - RDFXMLSerializerOption config = new RDFXMLSerializerOption.Builder() + RDFXMLSerializerOptions config = new RDFXMLSerializerOptions.Builder() .sortPredicates(true) .build(); assertTrue(config.sortPredicates(), "sortPredicates should be overridden to true"); @@ -193,7 +193,7 @@ void builder_shouldAllowOverridingSortPredicates() { @Test @DisplayName("Builder should allow overriding useMultilineLiterals") void builder_shouldAllowOverridingUseMultilineLiterals() { - RDFXMLSerializerOption config = new RDFXMLSerializerOption.Builder() + RDFXMLSerializerOptions config = new RDFXMLSerializerOptions.Builder() .useMultilineLiterals(false) .build(); assertFalse(config.useMultilineLiterals(), "useMultilineLiterals should be overridden to false"); @@ -202,7 +202,7 @@ void builder_shouldAllowOverridingUseMultilineLiterals() { @Test @DisplayName("Builder should allow overriding strictMode") void builder_shouldAllowOverridingStrictMode() { - RDFXMLSerializerOption config = new RDFXMLSerializerOption.Builder() + RDFXMLSerializerOptions config = new RDFXMLSerializerOptions.Builder() .strictMode(false) .build(); assertFalse(config.isStrictMode(), "strictMode should be overridden to false"); @@ -211,7 +211,7 @@ void builder_shouldAllowOverridingStrictMode() { @Test @DisplayName("Builder should allow overriding escapeUnicode") void builder_shouldAllowOverridingEscapeUnicode() { - RDFXMLSerializerOption config = new RDFXMLSerializerOption.Builder() + RDFXMLSerializerOptions config = new RDFXMLSerializerOptions.Builder() .escapeUnicode(true) .build(); assertTrue(config.escapeUnicode(), "escapeUnicode should be overridden to true"); @@ -220,7 +220,7 @@ void builder_shouldAllowOverridingEscapeUnicode() { @Test @DisplayName("Builder should allow overriding literalDatatypePolicy") void builder_shouldAllowOverridingLiteralDatatypePolicy() { - RDFXMLSerializerOption config = new RDFXMLSerializerOption.Builder() + RDFXMLSerializerOptions config = new RDFXMLSerializerOptions.Builder() .literalDatatypePolicy(LiteralDatatypePolicyEnum.MINIMAL) .build(); assertEquals(LiteralDatatypePolicyEnum.MINIMAL, config.getLiteralDatatypePolicy(), "literalDatatypePolicy should be overridden to MINIMAL"); @@ -230,7 +230,7 @@ void builder_shouldAllowOverridingLiteralDatatypePolicy() { @DisplayName("Builder should allow setting baseIRI") void builder_shouldAllowSettingBaseIRI() { String testBaseIRI = "http://example.org/base/"; - RDFXMLSerializerOption config = new RDFXMLSerializerOption.Builder() + RDFXMLSerializerOptions config = new RDFXMLSerializerOptions.Builder() .baseIRI(testBaseIRI) .build(); assertEquals(testBaseIRI, config.getBaseIRI(), "baseIRI should be set correctly"); @@ -240,7 +240,7 @@ void builder_shouldAllowSettingBaseIRI() { @DisplayName("Builder should allow overriding lineEnding") void builder_shouldAllowOverridingLineEnding() { String customLineEnding = "\r\n"; - RDFXMLSerializerOption config = new RDFXMLSerializerOption.Builder() + RDFXMLSerializerOptions config = new RDFXMLSerializerOptions.Builder() .lineEnding(customLineEnding) .build(); assertEquals(customLineEnding, config.getLineEnding(), "lineEnding should be overridden to custom value"); @@ -249,7 +249,7 @@ void builder_shouldAllowOverridingLineEnding() { @Test @DisplayName("Builder should allow overriding validateURIs") void builder_shouldAllowOverridingValidateURIs() { - RDFXMLSerializerOption config = new RDFXMLSerializerOption.Builder() + RDFXMLSerializerOptions config = new RDFXMLSerializerOptions.Builder() .validateURIs(true) .build(); assertTrue(config.validateURIs(), "validateURIs should be overridden to true"); @@ -258,7 +258,7 @@ void builder_shouldAllowOverridingValidateURIs() { @Test @DisplayName("Builder should allow overriding stableBlankNodeIds") void builder_shouldAllowOverridingStableBlankNodeIds() { - RDFXMLSerializerOption config = new RDFXMLSerializerOption.Builder() + RDFXMLSerializerOptions config = new RDFXMLSerializerOptions.Builder() .stableBlankNodeIds(false) .build(); assertFalse(config.stableBlankNodeIds(), "stableBlankNodeIds should be overridden to false"); @@ -267,7 +267,7 @@ void builder_shouldAllowOverridingStableBlankNodeIds() { @Test @DisplayName("Builder should allow overriding includeContext") void builder_shouldAllowOverridingIncludeContext() { - RDFXMLSerializerOption config = new RDFXMLSerializerOption.Builder() + RDFXMLSerializerOptions config = new RDFXMLSerializerOptions.Builder() .includeContext(true) .build(); assertTrue(config.includeContext(), "includeContext should be overridden to true"); @@ -276,7 +276,7 @@ void builder_shouldAllowOverridingIncludeContext() { @Test @DisplayName("Builder should maintain default prefixes when adding custom ones") void builder_shouldMaintainDefaultPrefixesWhenAddingCustomOnes() { - RDFXMLSerializerOption config = new RDFXMLSerializerOption.Builder() + RDFXMLSerializerOptions config = new RDFXMLSerializerOptions.Builder() .addPrefix("ex", "http://example.org/") .build(); diff --git a/src/test/java/fr/inria/corese/core/next/impl/io/serialization/trig/TriGSerializerTest.java b/src/test/java/fr/inria/corese/core/next/impl/io/serialization/trig/TriGSerializerTest.java index 02e375c32..fa5f04a65 100644 --- a/src/test/java/fr/inria/corese/core/next/impl/io/serialization/trig/TriGSerializerTest.java +++ b/src/test/java/fr/inria/corese/core/next/impl/io/serialization/trig/TriGSerializerTest.java @@ -68,10 +68,6 @@ void testBasicTriGSerialization() throws SerializationException { String expected = """ @prefix ns: . - @prefix owl: . - @prefix rdf: . - @prefix rdfs: . - @prefix xsd: . ns:person1 ns:hasName "John Doe" . @@ -114,10 +110,7 @@ void testRdfTypeShortcut() throws SerializationException { String expected = """ @prefix foaf: . @prefix ns: . - @prefix owl: . @prefix rdf: . - @prefix rdfs: . - @prefix xsd: . ns:person1 a foaf:Person . @@ -164,10 +157,7 @@ void testLiteralWithLanguageTag() throws SerializationException { String expected = """ @prefix 11: . @prefix data: . - @prefix owl: . @prefix rdf: . - @prefix rdfs: . - @prefix xsd: . data:book1 11:title "The Odyssey"@en . @@ -213,9 +203,6 @@ void testLiteralWithExplicitXsdStringType() throws SerializationException { String expected = """ @prefix 11: . @prefix data: . - @prefix owl: . - @prefix rdf: . - @prefix rdfs: . @prefix xsd: . data:book2 11:creator "Homer"^^xsd:string . @@ -260,10 +247,6 @@ void testBaseIRI() throws SerializationException { String expected = """ @base . @prefix base: . - @prefix owl: . - @prefix rdf: . - @prefix rdfs: . - @prefix xsd: . base:resource1 base:prop "Test" . @@ -297,13 +280,7 @@ void testEmptyModel() throws SerializationException { verify(emptyModel, times(2)).stream(); - String expected = """ - @prefix owl: . - @prefix rdf: . - @prefix rdfs: . - @prefix xsd: . - - """; + String expected = ""; String actual = writer.toString().replace("\r\n", "\n"); assertEquals(expected, actual); } @@ -413,11 +390,7 @@ void testMultilineLiteralSerialization() throws SerializationException { String expected = """ @prefix book: . - @prefix owl: . @prefix properties: . - @prefix rdf: . - @prefix rdfs: . - @prefix xsd: . book:1 properties:description\s""" + "\"\"\"" + multilineText + "\"\"\"" + " .\n\n"; @@ -455,10 +428,6 @@ void testBasicTrigSerializationWithNamedGraph() throws SerializationException { String expected = """ @prefix data: . @prefix graph: . - @prefix owl: . - @prefix rdf: . - @prefix rdfs: . - @prefix xsd: . graph:g1 { data:person1 data:name "Alice" . diff --git a/src/test/java/fr/inria/corese/core/next/impl/io/serialization/turtle/TurtleSerializerTest.java b/src/test/java/fr/inria/corese/core/next/impl/io/serialization/turtle/TurtleSerializerTest.java index 2ab5eb370..4a33b006e 100644 --- a/src/test/java/fr/inria/corese/core/next/impl/io/serialization/turtle/TurtleSerializerTest.java +++ b/src/test/java/fr/inria/corese/core/next/impl/io/serialization/turtle/TurtleSerializerTest.java @@ -5,8 +5,10 @@ import fr.inria.corese.core.next.impl.common.literal.XSD; import fr.inria.corese.core.next.impl.common.prefix.PrefixHandler; import fr.inria.corese.core.next.impl.exception.SerializationException; +import fr.inria.corese.core.next.impl.io.parser.ParserFactory; import fr.inria.corese.core.next.impl.io.serialization.TestStatementFactory; import fr.inria.corese.core.next.impl.io.serialization.option.LiteralDatatypePolicyEnum; +import fr.inria.corese.core.next.impl.exception.SerializationException; import fr.inria.corese.core.next.impl.temp.CoreseAdaptedValueFactory; import fr.inria.corese.core.next.impl.temp.CoreseModel; import org.junit.jupiter.api.BeforeEach; @@ -70,10 +72,6 @@ void testBasicTurtleSerialization() throws SerializationException { String expected = """ @prefix ns: . - @prefix owl: . - @prefix rdf: . - @prefix rdfs: . - @prefix xsd: . ns:person1 ns:hasName "John Doe" . """; @@ -104,6 +102,9 @@ void testRdfTypeShortcut() throws SerializationException { .thenReturn(Stream.of(mockStatement)); StringWriter writer = new StringWriter(); + TurtleSerializerOptions options = TurtleSerializerOptions.builder() + .prefixHandler(new PrefixHandler(true)) + .build(); TurtleSerializer turtleSerializer = new TurtleSerializer(mockModel, defaultConfig); @@ -114,10 +115,7 @@ void testRdfTypeShortcut() throws SerializationException { String expected = """ @prefix foaf: . @prefix ns: . - @prefix owl: . @prefix rdf: . - @prefix rdfs: . - @prefix xsd: . ns:person1 a foaf:Person . """; @@ -172,10 +170,7 @@ void testLiteralWithLanguageTag() throws SerializationException { String expected = """ @prefix 11: . @prefix data: . - @prefix owl: . @prefix rdf: . - @prefix rdfs: . - @prefix xsd: . data:book1 11:title "The Odyssey"@en . """; @@ -209,15 +204,12 @@ void testLiteralWithExplicitXsdStringType() throws SerializationException { StringWriter writer = new StringWriter(); - PrefixHandler prefixHandler = new PrefixHandler(true); - prefixHandler.setPrefix("data", "http://example.org/data/"); - prefixHandler.setPrefix("dc", "http://purl.org/dc/elements/1.1/"); - TurtleSerializerOptions config = new TurtleSerializerOptions.Builder() .literalDatatypePolicy(LiteralDatatypePolicyEnum.ALWAYS_TYPED) .usePrefixes(true) .autoDeclarePrefixes(true) - .prefixHandler(prefixHandler) + .addPrefix("data", "http://example.org/data/") + .addPrefix("dc", "http://purl.org/dc/elements/1.1/") .build(); TurtleSerializer turtleSerializer = new TurtleSerializer(mockModel, config); @@ -229,9 +221,6 @@ void testLiteralWithExplicitXsdStringType() throws SerializationException { String expected = """ @prefix data: . @prefix dc: . - @prefix owl: . - @prefix rdf: . - @prefix rdfs: . @prefix xsd: . data:book2 dc:creator "Homer"^^xsd:string . @@ -396,10 +385,6 @@ void testBaseIRI() throws SerializationException { String expected = """ @base . @prefix base: . - @prefix owl: . - @prefix rdf: . - @prefix rdfs: . - @prefix xsd: . base:resource1 base:prop "Test" . """; @@ -432,13 +417,7 @@ void testEmptyModel() throws SerializationException { verify(emptyModel, times(2)).stream(); - String expected = """ - @prefix owl: . - @prefix rdf: . - @prefix rdfs: . - @prefix xsd: . - - """; + String expected = ""; String actual = writer.toString().replace("\r\n", "\n"); assertEquals(expected, actual); } @@ -546,11 +525,7 @@ void testMultilineLiteralSerialization() throws SerializationException { String expected = """ @prefix book: . - @prefix owl: . @prefix properties: . - @prefix rdf: . - @prefix rdfs: . - @prefix xsd: . book:1 properties:description\s""" + "\"\"\"" + multilineText + "\"\"\"" + " .\n";