001/*
002 * SPDX-FileCopyrightText: none
003 * SPDX-License-Identifier: CC0-1.0
004 */
005
006package dev.metaschema.cli.commands;
007
008import org.apache.commons.cli.CommandLine;
009import org.apache.commons.cli.Option;
010
011import java.io.FileNotFoundException;
012import java.io.IOException;
013import java.io.InputStream;
014import java.io.Writer;
015import java.net.URI;
016import java.net.URL;
017import java.util.ArrayList;
018import java.util.Collection;
019import java.util.List;
020
021import dev.metaschema.cli.processor.CallingContext;
022import dev.metaschema.cli.processor.command.CommandExecutionException;
023import dev.metaschema.cli.processor.command.ICommandExecutor;
024import dev.metaschema.core.model.IBoundObject;
025import dev.metaschema.core.model.IModule;
026import dev.metaschema.core.model.MetaschemaException;
027import dev.metaschema.core.util.CollectionUtil;
028import dev.metaschema.core.util.ObjectUtils;
029import dev.metaschema.databind.IBindingContext;
030import dev.metaschema.databind.io.Format;
031import dev.metaschema.databind.io.FormatDetector;
032import dev.metaschema.databind.io.IBoundLoader;
033import dev.metaschema.databind.io.IDeserializer;
034import dev.metaschema.databind.io.ISerializer;
035import dev.metaschema.databind.io.ModelDetector;
036import edu.umd.cs.findbugs.annotations.NonNull;
037
038/**
039 * This command implementation supports the conversion of a content instance
040 * between supported formats based on a provided Metaschema module.
041 */
042public class ConvertContentUsingModuleCommand
043    extends AbstractConvertSubcommand {
044  @NonNull
045  private static final String COMMAND = "convert";
046
047  @Override
048  public String getName() {
049    return COMMAND;
050  }
051
052  @Override
053  public String getDescription() {
054    return "Convert the provided resource aligned to the provided Metaschema module to the specified format.";
055  }
056
057  @Override
058  public Collection<? extends Option> gatherOptions() {
059    Collection<? extends Option> orig = super.gatherOptions();
060
061    List<Option> retval = new ArrayList<>(orig.size() + 1);
062    retval.addAll(orig);
063    retval.add(MetaschemaCommands.METASCHEMA_REQUIRED_OPTION);
064
065    return CollectionUtil.unmodifiableCollection(retval);
066  }
067
068  @Override
069  public ICommandExecutor newExecutor(CallingContext callingContext, CommandLine commandLine) {
070
071    return new CommandExecutor(callingContext, commandLine);
072  }
073
074  private final class CommandExecutor
075      extends AbstractConversionCommandExecutor {
076
077    private CommandExecutor(
078        @NonNull CallingContext callingContext,
079        @NonNull CommandLine commandLine) {
080      super(callingContext, commandLine);
081    }
082
083    @Override
084    protected IBindingContext getBindingContext() throws CommandExecutionException, MetaschemaException {
085      IBindingContext retval = MetaschemaCommands.newBindingContextWithDynamicCompilation();
086
087      @SuppressWarnings("synthetic-access")
088      IModule module = MetaschemaCommands.loadModule(
089          getCommandLine(),
090          MetaschemaCommands.METASCHEMA_REQUIRED_OPTION,
091          ObjectUtils.notNull(getCurrentWorkingDirectory().toUri()),
092          retval);
093      retval.registerModule(module);
094      return retval;
095    }
096
097    @Override
098    protected void handleConversion(URI source, Format toFormat, Writer writer, IBoundLoader loader)
099        throws FileNotFoundException, IOException {
100      URI resourceUri = loader.resolve(source);
101      URL resource = resourceUri.toURL();
102
103      try (InputStream is = resource.openStream()) {
104        assert is != null;
105
106        FormatDetector.Result formatMatch = loader.detectFormat(is, resourceUri);
107        Format format = formatMatch.getFormat();
108
109        try (InputStream formatStream = formatMatch.getDataStream()) {
110          try (ModelDetector.Result modelMatch = loader.detectModel(formatStream, resourceUri, format)) {
111
112            IBindingContext bindingContext = loader.getBindingContext();
113
114            IDeserializer<?> deserializer = bindingContext.newDeserializer(format, modelMatch.getBoundClass());
115            deserializer.applyConfiguration(loader);
116            try (InputStream modelStream = modelMatch.getDataStream()) {
117              IBoundObject obj = deserializer.deserialize(modelStream, resourceUri);
118
119              ISerializer<?> serializer = bindingContext.newSerializer(toFormat, modelMatch.getBoundClass());
120              serializer.serialize(obj, writer);
121            }
122
123          }
124        }
125      }
126    }
127
128  }
129
130}