001/*
002 * SPDX-License-Identifier: Apache-2.0
003 *
004 * Copyright 2023-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.cli;
019
020import com.google.protobuf.ExtensionRegistryLite;
021import com.google.protobuf.Message;
022
023import dev.enola.common.io.iri.URIs;
024import dev.enola.common.io.resource.WritableResource;
025import dev.enola.common.io.resource.WriterResource;
026import dev.enola.common.protobuf.ProtoIO;
027import dev.enola.common.protobuf.TypeRegistryWrapper;
028import dev.enola.core.proto.EnolaServiceGrpc.EnolaServiceBlockingStub;
029import dev.enola.core.proto.GetFileDescriptorSetRequest;
030import dev.enola.core.view.EnolaMessages;
031import dev.enola.rdf.io.RdfWriterConverter;
032import dev.enola.rdf.proto.ProtoThingRdfConverter;
033import dev.enola.thing.gen.graphcommons.GraphCommonsJsonGenerator;
034import dev.enola.thing.gen.graphviz.GraphvizGenerator;
035import dev.enola.thing.message.ProtoThings;
036import dev.enola.thing.metadata.ThingMetadataProvider;
037import dev.enola.thing.proto.Thing;
038import dev.enola.thing.proto.Things;
039import dev.enola.web.EnolaThingProvider;
040
041import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
042import org.eclipse.rdf4j.model.util.ModelBuilder;
043import org.eclipse.rdf4j.rio.helpers.StatementCollector;
044
045import picocli.CommandLine;
046
047import java.io.IOException;
048
049public abstract class CommandWithIRI extends CommandWithModelAndOutput {
050
051    @CommandLine.Option(
052            names = {"--format", "-f"},
053            required = true,
054            defaultValue = "Turtle",
055            description = "Output Format: ${COMPLETION-CANDIDATES}; default=${DEFAULT-VALUE}")
056    Format format;
057
058    // TODO @Parameters(index = "0..*") List<String> eris;
059    @CommandLine.Parameters(index = "0", paramLabel = "iri", description = "IRI of Thing")
060    String iri;
061
062    private WritableResource resource;
063    private TypeRegistryWrapper typeRegistryWrapper;
064    protected EnolaMessages enolaMessages;
065    private ThingMetadataProvider thingMetadataProvider;
066
067    @Override
068    protected final void run(EnolaServiceBlockingStub service) throws Exception {
069        thingMetadataProvider = getMetadataProvider(new EnolaThingProvider(service));
070
071        var gfdsr = GetFileDescriptorSetRequest.newBuilder().build();
072        var fds = service.getFileDescriptorSet(gfdsr).getProtos();
073        typeRegistryWrapper = TypeRegistryWrapper.from(fds);
074        var extensionRegistry = ExtensionRegistryLite.getEmptyRegistry();
075        enolaMessages = new EnolaMessages(typeRegistryWrapper, extensionRegistry);
076
077        // See CommandWithModelAndOutput
078        if (output == null || output.output.equals(Output.DEFAULT_OUTPUT_URI)) {
079            resource = new WriterResource(spec.commandLine().getOut(), format.toMediaType());
080        } else {
081            resource =
082                    rp.getWritableResource(
083                            URIs.addMediaType(Output.get(output), format.toMediaType()));
084        }
085
086        run(service, iri);
087    }
088
089    protected abstract void run(EnolaServiceBlockingStub service, String eri) throws Exception;
090
091    protected void write(Message thing) throws IOException {
092        if (Format.Turtle.equals(format) || Format.JSONLD.equals(format)) {
093            var model = new ModelBuilder().build();
094            var statementCollector = new StatementCollector(model);
095            if (thing instanceof Thing protoThing) {
096                var vf = SimpleValueFactory.getInstance();
097                new ProtoThingRdfConverter(vf).convertInto(protoThing, statementCollector);
098            } else if (thing instanceof Things protoThings) {
099                for (var protoThing : protoThings.getThingsList())
100                    new ProtoThingRdfConverter().convertInto(protoThing, statementCollector);
101            }
102            new RdfWriterConverter().convertIntoOrThrow(model, resource);
103            return;
104        }
105
106        if (Format.Graphviz.equals(format) && thing instanceof Things protoThings) {
107            var javaThings = ProtoThings.proto2java(protoThings.getThingsList());
108            new GraphvizGenerator(thingMetadataProvider).convertIntoOrThrow(javaThings, resource);
109            return;
110        }
111
112        if (Format.GraphCommons.equals(format) && thing instanceof Things protoThings) {
113            var javaThings = ProtoThings.proto2java(protoThings.getThingsList());
114            new GraphCommonsJsonGenerator(thingMetadataProvider)
115                    .convertIntoOrThrow(javaThings, resource);
116            return;
117        }
118
119        // Otherwise
120        new ProtoIO(typeRegistryWrapper.get()).write(thing, resource);
121    }
122}