CgmesOnDataSource.java

/**
 * Copyright (c) 2017-2025, RTE (http://www.rte-france.com)
 * 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.cgmes.model;

import com.powsybl.commons.compress.SafeZipInputStream;
import com.powsybl.commons.datasource.CompressionFormat;
import com.powsybl.commons.datasource.ReadOnlyDataSource;

import java.io.IOException;
import java.io.InputStream;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.zip.ZipInputStream;

import static com.powsybl.cgmes.model.CgmesNamespace.*;

/**
 * @author Luma Zamarre��o {@literal <zamarrenolm at aia.es>}
 */
public class CgmesOnDataSource {
    private static final String LISTING_CGMES_NAMES_IN_DATA_SOURCE = "Listing CGMES names in data source %s";
    private static final String EXTENSION = "xml";

    public CgmesOnDataSource(ReadOnlyDataSource ds) {
        this.dataSource = ds;
    }

    public ReadOnlyDataSource dataSource() {
        return dataSource;
    }

    private boolean checkIfMainFileNotWithCgmesData() throws IOException {
        if (dataSource.getDataExtension() == null || dataSource.getDataExtension().isEmpty() || !dataSource.exists(null, dataSource.getDataExtension())) {
            return false;
        } else if (EXTENSION.equals(dataSource.getDataExtension())) {
            try (InputStream is = dataSource.newInputStream(null, EXTENSION)) {
                return !existsNamespaces(NamespaceReader.namespaces(is));
            }
        }
        return true;
    }

    public boolean exists() throws IOException {
        // Check that the main file is a CGMES file
        if (checkIfMainFileNotWithCgmesData()) {
            return false;
        }
        // check that RDF and CIM16 are defined as namespaces in the data source
        return existsNamespaces(namespaces());
    }

    private boolean existsNamespaces(Set<String> namespaces) {
        if (!namespaces.contains(RDF_NAMESPACE)) {
            return false;
        }
        return namespaces.contains(CIM_16_NAMESPACE) || namespaces.contains(CIM_100_NAMESPACE);
    }

    public String baseName() {
        // Get the base URI if present, else build an absolute URI from the data source base name
        return names().stream()
                .map(n -> loadInputStreamAndGetNamespace(n, NamespaceReader::base))
                .filter(Objects::nonNull)
                .findFirst()
                .orElseGet(() -> {
                    String name = dataSource.getBaseName().toLowerCase();
                    if (name.isEmpty()) {
                        name = "default-cgmes-model";
                    }
                    return "http://" + name;
                });
    }

    public Set<String> names() {
        try {
            // the set of names may be empty if the data source does not contain CGMES data
            Set<String> allNames = dataSource.listNames(REGEX_VALID_NAME);
            allNames.removeIf(n -> !existsInDatasource(n) || !containsValidNamespace(n));
            return allNames;
        } catch (IOException x) {
            throw new CgmesModelException(String.format(LISTING_CGMES_NAMES_IN_DATA_SOURCE, dataSource), x);
        }
    }

    private boolean existsInDatasource(String fileName) {
        try {
            return dataSource.exists(fileName);
        } catch (IOException e) {
            return false;
        }
    }

    private <T> T loadInputStreamAndGetNamespace(String n, Function<InputStream, T> namespaceGetter) {
        try (InputStream in = dataSource.newInputStream(n)) {
            String fileExtension = n.substring(n.lastIndexOf('.') + 1);
            if (fileExtension.equals(CompressionFormat.ZIP.getExtension())) {
                try (SafeZipInputStream zis = new SafeZipInputStream(new ZipInputStream(in), 1, 1024000L)) {
                    zis.getNextEntry();
                    return namespaceGetter.apply(zis);
                }
            } else {
                return namespaceGetter.apply(in);
            }
        } catch (IOException e) {
            throw new CgmesModelException(String.format(LISTING_CGMES_NAMES_IN_DATA_SOURCE, dataSource), e);
        }
    }

    private boolean containsValidNamespace(String name) {
        Set<String> ns = loadInputStreamAndGetNamespace(name, NamespaceReader::namespacesOrEmpty);
        return ns.contains(RDF_NAMESPACE) && ns.stream().anyMatch(CgmesNamespace::isValid);
    }

    public Set<String> namespaces() {
        return names().stream()
                .map(name -> loadInputStreamAndGetNamespace(name, NamespaceReader::namespaces))
                .flatMap(Set::stream)
                .collect(Collectors.toSet());
    }

    public String cimNamespace() {
        // If no cim namespace is found, return CIM16 namespace
        return namespaces().stream()
            .filter(CgmesNamespace::isValid)
            .findFirst()
            .orElseThrow(() -> new CgmesModelException("CIM Namespace not found"));
    }

    private final ReadOnlyDataSource dataSource;
    private static final String REGEX_VALID_NAME = ""
        // Ignore case
        + "(?i)"
        // Any number of characters from the start
        + "^.*"
        // Ending with extension .xml
        + "\\.(XML|ZIP)$";
}