Importer.java

/**
 * Copyright (c) 2016, All partners of the iTesla project (http://www.itesla-project.eu/consortium)
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 * SPDX-License-Identifier: MPL-2.0
 */
package com.powsybl.iidm.network;

import com.powsybl.commons.PowsyblException;
import com.powsybl.commons.datasource.DataSource;
import com.powsybl.commons.datasource.ReadOnlyDataSource;
import com.powsybl.commons.report.ReportNode;
import com.powsybl.computation.ComputationManager;
import com.powsybl.computation.local.LocalComputationManager;
import com.powsybl.commons.parameters.Parameter;
import org.jspecify.annotations.Nullable;

import java.util.*;
import java.util.stream.Collectors;

/**
 * This is the base class for all IIDM importers.
 *
 * <p><code>Importer</code> lookup is based on the <code>ServiceLoader</code>
 * architecture so do not forget to create a
 * <code>META-INF/services/com.powsybl.iidm.network.Importer</code> file
 * with the fully qualified name of your <code>Importer</code> implementation.
 *
 * @see java.util.ServiceLoader
 * @see Importers
 *
 * @author Geoffroy Jamgotchian {@literal <geoffroy.jamgotchian at rte-france.com>}
 */
public interface Importer {

    class ImporterWrapper implements Importer {

        private final Importer importer;

        private final ComputationManager computationManager;

        private final List<String> names;

        private final ImportersLoader loader;

        ImporterWrapper(ImportersLoader loader, Importer importer, ComputationManager computationManager, List<String> names) {
            this.loader = Objects.requireNonNull(loader);
            this.importer = importer;
            this.computationManager = computationManager;
            this.names = names;
        }

        public Importer getImporter() {
            return importer;
        }

        @Override
        public String getFormat() {
            return importer.getFormat();
        }

        @Override
        public List<String> getSupportedExtensions() {
            return importer.getSupportedExtensions();
        }

        @Override
        public List<Parameter> getParameters() {
            return importer.getParameters();
        }

        @Override
        public String getComment() {
            return importer.getComment();
        }

        @Override
        public boolean exists(ReadOnlyDataSource dataSource) {
            return importer.exists(dataSource);
        }

        private static ImportPostProcessor getPostProcessor(ImportersLoader loader, String name) {
            for (ImportPostProcessor ipp : loader.loadPostProcessors()) {
                if (ipp.getName().equals(name)) {
                    return ipp;
                }
            }
            throw new PowsyblException("Post processor " + name + " not found");
        }

        @Override
        public Network importData(ReadOnlyDataSource dataSource, NetworkFactory networkFactory, Properties parameters, ReportNode reportNode) {
            Network network = importer.importData(dataSource, networkFactory, parameters, reportNode);
            for (String name : names) {
                try {
                    getPostProcessor(loader, name).process(network, computationManager, reportNode);
                } catch (Exception e) {
                    throw new PowsyblException(e);
                }
            }
            return network;
        }

        @Override
        public Network importData(ReadOnlyDataSource dataSource, NetworkFactory networkFactory, Properties parameters) {
            return importData(dataSource, networkFactory, parameters, ReportNode.NO_OP);
        }

        @Override
        public void copy(ReadOnlyDataSource fromDataSource, DataSource toDataSource) {
            importer.copy(fromDataSource, toDataSource);
        }
    }

    /**
     * Get all supported import formats.
     */
    static Collection<String> getFormats(ImportersLoader loader) {
        Objects.requireNonNull(loader);
        return loader.loadImporters().stream().map(Importer::getFormat).collect(Collectors.toSet());
    }

    static Collection<String> getFormats() {
        return getFormats(new ImportersServiceLoader());
    }

    private static Importer wrapImporter(ImportersLoader loader, Importer importer, ComputationManager computationManager, ImportConfig config) {
        Objects.requireNonNull(computationManager);
        Objects.requireNonNull(config);
        List<String> postProcessorNames = config.getPostProcessors();
        if (postProcessorNames != null && !postProcessorNames.isEmpty()) {
            return new ImporterWrapper(loader, importer, computationManager, postProcessorNames);
        }
        return importer;
    }

    static Collection<Importer> list(ImportersLoader loader, ComputationManager computationManager, ImportConfig config) {
        Objects.requireNonNull(loader);
        return loader.loadImporters().stream()
                .map(importer -> wrapImporter(loader, importer, computationManager, config))
                .collect(Collectors.toList());
    }

    static Collection<Importer> list(ComputationManager computationManager, ImportConfig config) {
        return list(new ImportersServiceLoader(), computationManager, config);
    }

    static Collection<Importer> list() {
        return list(LocalComputationManager.getDefault(), ImportConfig.CACHE.get());
    }

    /**
     * Find an importer for the specified format name. The returned importer will apply configured
     * {@link ImportPostProcessor}s on imported networks.
     *
     * @param loader             the loader responsible for providing the list of available importers and post processors
     * @param format             the import format
     * @param computationManager a computation manager which may be used by configured {@link ImportPostProcessor}s
     * @param config             the import configuration
     * @return the importer if one exists for the given format or <code>null</code> otherwise.
     */
    static Importer find(ImportersLoader loader, String format, @Nullable ComputationManager computationManager, ImportConfig config) {
        Objects.requireNonNull(format);
        Objects.requireNonNull(loader);
        for (Importer importer : loader.loadImporters()) {
            if (format.equals(importer.getFormat())) {
                return wrapImporter(loader, importer, computationManager, config);
            }
        }
        return null;
    }

    /**
     * Find an importer for the specified format name. The returned importer will apply configured
     * {@link ImportPostProcessor}s on imported networks.
     *
     * <p>All declared services implementing the {@link Importer} interface are available.
     *
     * @param format             the import format
     * @param computationManager a computation manager which may be used by configured {@link ImportPostProcessor}s
     * @param config             the import configuration
     * @return the importer if one exists for the given format or <code>null</code> otherwise.
     */
    static Importer find(String format, @Nullable ComputationManager computationManager, ImportConfig config) {
        return find(new ImportersServiceLoader(), format, computationManager, config);
    }

    /**
     * Find an importer for the specified format name. The returned importer will apply configured
     * {@link ImportPostProcessor}s on imported networks.
     *
     * <p>All declared services implementing the {@link Importer} interface are available.
     * The import configuration is loaded from default platform config.
     *
     * @param format             the import format
     * @param computationManager a computation manager which may be used by configured {@link ImportPostProcessor}s
     * @return the importer if one exists for the given format or <code>null</code> otherwise.
     */
    static Importer find(String format, @Nullable ComputationManager computationManager) {
        return find(format, computationManager, ImportConfig.CACHE.get());
    }

    /**
     * Find an importer for the specified format name. The returned importer will apply configured
     * {@link ImportPostProcessor}s on imported networks.
     *
     * <p>All declared services implementing the {@link Importer} interface are available.
     * The import configuration is loaded from default platform config.
     * Import post processors will use the default instance of {@link LocalComputationManager},
     * as configured in default platform config.
     *
     * @param format             the import format
     * @return the importer if one exists for the given format or <code>null</code> otherwise.
     */
    static Importer find(String format) {
        return find(format, LocalComputationManager.getDefault());
    }

    static Collection<String> getPostProcessorNames(ImportersLoader loader) {
        Objects.requireNonNull(loader);
        return loader.loadPostProcessors().stream().map(ImportPostProcessor::getName).collect(Collectors.toList());
    }

    static Collection<String> getPostProcessorNames() {
        return getPostProcessorNames(new ImportersServiceLoader());
    }

    static Importer addPostProcessors(ImportersLoader loader, Importer importer, ComputationManager computationManager, String... names) {
        return new ImporterWrapper(loader, importer, computationManager, Arrays.asList(names));
    }

    static Importer addPostProcessors(Importer importer, ComputationManager computationManager, String... names) {
        return addPostProcessors(new ImportersServiceLoader(), importer, computationManager, names);
    }

    static Importer addPostProcessors(Importer importer, String... names) {
        return addPostProcessors(importer, LocalComputationManager.getDefault(), names);
    }

    static Importer setPostProcessors(ImportersLoader loader, Importer importer, ComputationManager computationManager, String... names) {
        Importer importer2 = removePostProcessors(importer);
        return addPostProcessors(loader, importer2, computationManager, names);
    }

    static Importer setPostProcessors(Importer importer, ComputationManager computationManager, String... names) {
        return setPostProcessors(new ImportersServiceLoader(), importer, computationManager, names);
    }

    static Importer setPostProcessors(Importer importer, String... names) {
        return setPostProcessors(importer, LocalComputationManager.getDefault(), names);
    }

    static Importer removePostProcessors(Importer importer) {
        Objects.requireNonNull(importer);
        if (importer instanceof ImporterWrapper importerWrapper) {
            return removePostProcessors(importerWrapper.getImporter());
        }
        return importer;
    }

    static Importer find(ReadOnlyDataSource dataSource, ImportersLoader loader, ComputationManager computationManager, ImportConfig config) {
        for (Importer importer : list(loader, computationManager, config)) {
            if (importer.exists(dataSource)) {
                return importer;
            }
        }
        return null;
    }

    static Importer find(ReadOnlyDataSource dataSource, ComputationManager computationManager) {
        return find(dataSource, new ImportersServiceLoader(), computationManager, ImportConfig.CACHE.get());
    }

    static Importer find(ReadOnlyDataSource dataSource) {
        return find(dataSource, LocalComputationManager.getDefault());
    }

    /**
     * Get a unique identifier of the format.
     */
    String getFormat();

    default List<String> getSupportedExtensions() {
        return Collections.emptyList();
    }

    /**
     * Get a description of import parameters
     * @return
     */
    default List<Parameter> getParameters() {
        return Collections.emptyList();
    }

    /**
     * Get some information about this importer.
     */
    String getComment();

    /**
     * Check if the data source is importable
     * @param dataSource the data source
     * @return true if the data source is importable, false otherwise
     */
    boolean exists(ReadOnlyDataSource dataSource);

    /**
     * @deprecated Use {@link Importer#importData(ReadOnlyDataSource, NetworkFactory, Properties)} instead.
     */
    @Deprecated(since = "2.6.0")
    default Network importData(ReadOnlyDataSource dataSource, Properties parameters) {
        return importData(dataSource, NetworkFactory.findDefault(), parameters);
    }

    /**
     * Create a model.
     *
     * @param dataSource data source
     * @param networkFactory network factory
     * @param parameters some properties to configure the import
     * @return the model
     */
    default Network importData(ReadOnlyDataSource dataSource, NetworkFactory networkFactory, Properties parameters) {
        return importData(dataSource, networkFactory, parameters, ReportNode.NO_OP);
    }

    /**
     * Create a model.
     *
     * @param dataSource data source
     * @param networkFactory network factory
     * @param parameters some properties to configure the import
     * @param reportNode the reportNode used for functional logs
     * @return the model
     */
    default Network importData(ReadOnlyDataSource dataSource, NetworkFactory networkFactory, Properties parameters, ReportNode reportNode) {
        return importData(dataSource, networkFactory, parameters);
    }

    /**
     * Copy data from one data source to another.
     * @param fromDataSource from data source
     * @param toDataSource destination data source
     */
    default void copy(ReadOnlyDataSource fromDataSource, DataSource toDataSource) {
        throw new UnsupportedOperationException("Copy not implemented");
    }

    /**
     * Update a given network with contents coming from a data source.
     *
     * @param network network
     * @param dataSource data source
     * @param parameters some properties to configure the import
     * @param reportNode the reportNode used for functional logs
     */
    default void update(Network network, ReadOnlyDataSource dataSource, Properties parameters, ReportNode reportNode) {
        throw new UnsupportedOperationException("Importer do not implement updates");
    }

    /**
     * Update a given network with contents coming from a data source.
     *
     * @param network network
     * @param dataSource data source
     * @param parameters some properties to configure the import
     */
    default void update(Network network, ReadOnlyDataSource dataSource, Properties parameters) {
        update(network, dataSource, parameters, ReportNode.NO_OP);
    }
}