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.core.rosetta;
019
020import com.google.common.collect.ImmutableList;
021import com.google.protobuf.Descriptors.Descriptor;
022
023import dev.enola.common.context.TLC;
024import dev.enola.common.convert.ConversionException;
025import dev.enola.common.io.resource.ReadableResource;
026import dev.enola.common.io.resource.ResourceProvider;
027import dev.enola.common.io.resource.WritableResource;
028import dev.enola.common.io.resource.convert.CharResourceConverter;
029import dev.enola.common.io.resource.convert.ResourceConverter;
030import dev.enola.common.io.resource.convert.ResourceConverterChain;
031import dev.enola.common.protobuf.DescriptorProvider;
032import dev.enola.common.protobuf.MessageResourceConverter;
033import dev.enola.common.protobuf.ProtoIO;
034import dev.enola.common.protobuf.YamlJsonResourceConverter;
035import dev.enola.data.iri.NamespaceConverter;
036import dev.enola.format.tika.rdf.TikaResourceIntoRdfResourceConverter;
037import dev.enola.format.xml.XmlResourceConverter;
038import dev.enola.rdf.io.RdfResourceConverter;
039import dev.enola.thing.gen.gexf.GexfGenerator;
040import dev.enola.thing.gen.gexf.GexfResourceConverter;
041import dev.enola.thing.gen.graphcommons.GraphCommonsJsonGenerator;
042import dev.enola.thing.gen.graphcommons.GraphCommonsResourceConverter;
043import dev.enola.thing.gen.graphviz.GraphvizGenerator;
044import dev.enola.thing.gen.graphviz.GraphvizResourceConverter;
045import dev.enola.thing.io.Loader;
046import dev.enola.thing.metadata.ThingMetadataProvider;
047import dev.enola.thing.repo.ThingProvider;
048
049import java.io.IOException;
050
051/**
052 * <a href="https://en.wikipedia.org/wiki/Rosetta_Stone">Rosetta Stone</a> for converting between
053 * different model resource and other formats.
054 */
055public class Rosetta implements ResourceConverter {
056
057    // TODO Merge this with Canonicalizer (and move into dev.enola.common.canonicalize)
058
059    // This class orig. was in dev.enola.core.rosetta originally, in order to have classpath access
060    // to Entities.getDescriptor() and EntityKinds.getDescriptor() in the lookupDescriptor() below.
061    // But all of this has meanwhile been removed, so this could now moved e.g. to a new
062    // dev.enola[.common?].rosetta instead, and use a DescriptorProvider as generic proto Descriptor
063    // registry. TODO Move dev.enola.core.rosetta to dev.enola.action.rosetta?
064
065    private final ProtoIO protoIO = new ProtoIO();
066
067    private static final DescriptorProvider DESCRIPTOR_PROVIDER =
068            new DescriptorProvider() {
069
070                @Override
071                public Descriptor findByName(String messageTypeURL) {
072                    throw new IllegalArgumentException(
073                            "TODO Cannot find Descriptor for: " + messageTypeURL);
074                }
075
076                @Override
077                public Descriptor getDescriptorForTypeUrl(String protoMessageFullyQualifiedName) {
078                    throw new UnsupportedOperationException("Unimplemented method 'findByName'");
079                }
080            };
081
082    private final MessageResourceConverter messageResourceConverter =
083            new MessageResourceConverter(protoIO, DESCRIPTOR_PROVIDER);
084
085    private final ResourceConverterChain resourceConverterChain;
086    private final ResourceProvider rp;
087
088    public Rosetta(ResourceProvider rp, Loader loader) {
089        this.rp = rp;
090        // TODO Remove!
091        var tmp = new ThingMetadataProvider(ThingProvider.CTX, NamespaceConverter.CTX);
092        this.resourceConverterChain =
093                new ResourceConverterChain(
094                        ImmutableList.of(
095                                // TODO Use ServiceLoader with @AutoService
096                                new RdfResourceIntoProtoThingResourceConverter(rp),
097                                new RdfResourceConverter(rp),
098                                new TikaResourceIntoRdfResourceConverter(rp),
099                                messageResourceConverter,
100                                new YamlJsonResourceConverter(),
101                                new GraphvizResourceConverter(loader, new GraphvizGenerator(tmp)),
102                                new GexfResourceConverter(loader, new GexfGenerator(tmp)),
103                                new GraphCommonsResourceConverter(
104                                        loader, new GraphCommonsJsonGenerator(tmp)),
105                                new XmlResourceConverter(rp),
106                                new CharResourceConverter()));
107        // NOT new IdempotentCopyingResourceNonConverter()
108    }
109
110    @Override
111    public boolean convertInto(ReadableResource from, WritableResource into)
112            throws ConversionException, IOException {
113        try (var ctx = TLC.open()) {
114            ctx.push(ResourceProvider.class, rp);
115            if (!resourceConverterChain.convertInto(from, into)) {
116                throw new ConversionException(
117                        "No Converter (registered on the Chain) accepted to transform from "
118                                + from
119                                + " into "
120                                + into);
121            }
122            return true;
123        }
124    }
125}