001/*
002 * SPDX-License-Identifier: Apache-2.0
003 *
004 * Copyright 2024-2026 The Enola <https://enola.dev> Authors
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 *     https://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package dev.enola.rdf.io;
019
020import dev.enola.common.io.resource.WritableResource;
021
022import org.eclipse.rdf4j.model.Statement;
023import org.eclipse.rdf4j.rio.*;
024import org.eclipse.rdf4j.rio.helpers.BasicWriterSettings;
025
026import java.io.Closeable;
027import java.io.IOException;
028import java.io.Writer;
029import java.net.URISyntaxException;
030import java.util.Optional;
031
032public class WritableResourceRDFHandler implements RDFHandler, Closeable {
033
034    public static Optional<WritableResourceRDFHandler> create(WritableResource resource)
035            throws IOException {
036        try {
037            String baseURI = resource.uri().toString();
038            var mediaType = resource.mediaType().withoutParameters().toString();
039            var writerFormat = Rio.getWriterFormatForMIMEType(mediaType);
040            // TODO See https://github.com/eclipse-rdf4j/rdf4j/issues/5272:
041            if (mediaType.equals("text/plain")
042                    && writerFormat.isPresent()
043                    && writerFormat.get().equals(RDFFormat.NTRIPLES))
044                writerFormat = Optional.empty();
045            if (writerFormat.isEmpty()) writerFormat = Rio.getWriterFormatForFileName(baseURI);
046            if (writerFormat.isEmpty()) return Optional.empty();
047
048            var ioWriter = resource.charSink().openBufferedStream();
049            var rdfWriter = Rio.createWriter(writerFormat.get(), ioWriter, baseURI);
050            var writerConfig = new WriterConfig();
051            writerConfig.set(BasicWriterSettings.BASE_DIRECTIVE, false);
052            writerConfig.set(BasicWriterSettings.INLINE_BLANK_NODES, true);
053            rdfWriter.setWriterConfig(writerConfig);
054
055            var it = new WritableResourceRDFHandler(rdfWriter, ioWriter);
056            rdfWriter.startRDF();
057            return Optional.of(it);
058
059        } catch (URISyntaxException e) {
060            throw new IOException("URISyntaxException: " + resource, e);
061        }
062    }
063
064    private final RDFWriter /* IS-A RDFHandler */ rdfWriter;
065    private final Writer ioWriter;
066
067    private WritableResourceRDFHandler(RDFWriter rdfWriter, Writer ioWriter)
068            throws IOException, URISyntaxException {
069        this.rdfWriter = rdfWriter;
070        this.ioWriter = ioWriter;
071    }
072
073    @Override
074    public void close() throws IOException {
075        rdfWriter.endRDF();
076        ioWriter.close();
077    }
078
079    @Override
080    public void startRDF() throws RDFHandlerException {
081        // SUPPRESS rdfWriter.startRDF();
082    }
083
084    @Override
085    public void endRDF() throws RDFHandlerException {
086        // SUPPRESS rdfWriter.endRDF();
087    }
088
089    @Override
090    public void handleNamespace(String prefix, String uri) throws RDFHandlerException {
091        rdfWriter.handleNamespace(prefix, uri);
092    }
093
094    @Override
095    public void handleStatement(Statement st) throws RDFHandlerException {
096        rdfWriter.handleStatement(st);
097    }
098
099    @Override
100    public void handleComment(String comment) throws RDFHandlerException {
101        rdfWriter.handleComment(comment);
102    }
103}