ResourcesComponentLibrary.java

/**
 * Copyright (c) 2019, 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/.
 */
package com.powsybl.sld.library;

import com.powsybl.commons.exceptions.UncheckedSaxException;
import com.powsybl.sld.model.coordinate.Orientation;
import com.powsybl.sld.util.DomUtil;
import org.apache.commons.io.FilenameUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URL;
import java.util.*;

/**
 * Library of resources components, that is, the SVG image files representing the components, together with the styles
 * associated to each component
 * @author Benoit Jeanson {@literal <benoit.jeanson at rte-france.com>}
 * @author Nicolas Duchene
 * @author Geoffroy Jamgotchian {@literal <geoffroy.jamgotchian at rte-france.com>}
 * @author Franck Lecuyer {@literal <franck.lecuyer at rte-france.com>}
 */
public class ResourcesComponentLibrary implements ComponentLibrary {

    private static final Logger LOGGER = LoggerFactory.getLogger(ResourcesComponentLibrary.class);

    private final String name;

    private final Map<String, Map<String, List<Element>>> svgDocuments = new HashMap<>();

    private final Map<String, Component> components = new HashMap<>();

    private final List<String> cssFilenames = new ArrayList<>();

    private final List<URL> cssUrls = new ArrayList<>();

    /**
     * Constructs a new library containing the components in the given directories
     * @param name name of the library
     * @param directory main directory containing the resources components: SVG files, with associated components.json
     *                 (containing the list of SVG files) and components.css (containing the style applied to each
     *                  component)
     * @param additionalDirectories directories for additional components (each directory containing SVG files,
     *                              associated components.json and components.css).
     */
    public ResourcesComponentLibrary(String name, String directory, String... additionalDirectories) {
        this.name = Objects.requireNonNull(name);
        Objects.requireNonNull(directory);
        loadLibrary(directory);
        for (String addDir : additionalDirectories) {
            loadLibrary(addDir);
        }
    }

    @Override
    public String getName() {
        return name;
    }

    private void loadLibrary(String directory) {
        LOGGER.info("Loading component library from {}...", directory);

        // preload SVG documents
        DocumentBuilder db = DomUtil.getDocumentBuilder();
        Components.load(directory).getComponents().forEach(c -> {
            String componentType = c.getType();
            c.getSubComponents().forEach(s -> {
                String resourceName = directory + "/" + s.getFileName();
                LOGGER.debug("Reading subComponent {}", resourceName);
                try {
                    Document doc = db.parse(getClass().getResourceAsStream(resourceName));
                    svgDocuments.computeIfAbsent(componentType, k -> new LinkedHashMap<>()).put(s.getName(), getElements(doc));
                } catch (SAXException e) {
                    throw new UncheckedSaxException(e);
                } catch (IOException e) {
                    throw new UncheckedIOException(e);
                }
            });
            components.put(componentType, c);
        });

        cssFilenames.add(FilenameUtils.getName(directory) + "_components.css");
        cssUrls.add(getClass().getResource(directory + "/components.css"));
    }

    protected List<Element> getElements(Document doc) {
        // Getting the node corresponding to the svg tag
        Node svgNode = doc.getChildNodes().item(0);

        // Listing all the elements which are children of the svg node.
        List<Element> elements = new ArrayList<>();
        NodeList subComponentChildren = svgNode.getChildNodes();
        for (int i = 0; i < subComponentChildren.getLength(); i++) {
            org.w3c.dom.Node n = subComponentChildren.item(i);
            if (n instanceof Element) {
                elements.add((Element) n.cloneNode(true));
            }
        }
        return elements;
    }

    @Override
    public Map<String, List<Element>> getSvgElements(String type) {
        Objects.requireNonNull(type);
        Map<String, List<Element>> result = svgDocuments.get(type);
        if (result == null && isDisplayed(type)) {
            result = svgDocuments.get(ComponentTypeName.UNKNOWN_COMPONENT);
        }
        return result;
    }

    @Override
    public List<AnchorPoint> getAnchorPoints(String type) {
        Objects.requireNonNull(type);
        Component component = getComponent(type);
        return component != null ? component.getAnchorPoints()
                                 : Collections.singletonList(new AnchorPoint(0, 0, AnchorOrientation.NONE));
    }

    @Override
    public ComponentSize getSize(String type) {
        Objects.requireNonNull(type);
        Component component = getComponent(type);
        return component != null ? component.getSize() : new ComponentSize(0, 0);
    }

    @Override
    public Map<String, ComponentSize> getComponentsSize() {
        Map<String, ComponentSize> res = new HashMap<>();
        components.forEach((key, value) -> res.put(key, value.getSize()));
        return res;
    }

    @Override
    public List<String> getCssFilenames() {
        return cssFilenames;
    }

    @Override
    public List<URL> getCssUrls() {
        return cssUrls;
    }

    @Override
    public Optional<String> getComponentStyleClass(String type) {
        Objects.requireNonNull(type);
        Component component = getComponent(type);
        return component != null ? Optional.ofNullable(component.getStyleClass()) : Optional.empty();
    }

    @Override
    public Optional<String> getSubComponentStyleClass(String type, String subComponent) {
        Objects.requireNonNull(type);
        Objects.requireNonNull(subComponent);
        Component component = getComponent(type);
        if (component != null) {
            return component.getSubComponents().stream().filter(sc -> sc.getName().equals(subComponent)).findFirst().map(SubComponent::getStyleClass);
        }
        return Optional.empty();
    }

    @Override
    public Map<Orientation, Component.Transformation> getTransformations(String type) {
        Objects.requireNonNull(type);
        Component component = getComponent(type);
        return component != null ? component.getTransformations() : Collections.emptyMap();
    }

    protected Component getComponent(String type) {
        Objects.requireNonNull(type);
        Component component = components.get(type);
        if (component == null && isDisplayed(type)) {
            component = components.get(ComponentTypeName.UNKNOWN_COMPONENT);
        }
        return component;
    }

    protected boolean isDisplayed(String componentType) {
        return !(componentType.equals(ComponentTypeName.PHASE_SHIFT_TRANSFORMER_LEG) ||
                 componentType.equals(ComponentTypeName.TWO_WINDINGS_TRANSFORMER_LEG) ||
                 componentType.equals(ComponentTypeName.THREE_WINDINGS_TRANSFORMER_LEG) ||
                 componentType.equals(ComponentTypeName.LINE) ||
                 componentType.equals(ComponentTypeName.TIE_LINE) ||
                 componentType.equals(ComponentTypeName.DANGLING_LINE) ||
                 componentType.equals(ComponentTypeName.BUSBAR_SECTION));
    }
}