AbstractMapModuleConfig.java

/**
 * Copyright (c) 2018, 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.commons.config;

import com.powsybl.commons.PowsyblException;

import java.nio.file.FileSystem;
import java.nio.file.Path;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Provides implementation for most methods of {@link ModuleConfig},
 * based on a single "mapping" method to be implemented {@link this#getValue(String)}.
 * A {@link FileSystem} also needs to be provided to build {@link Path} properties.
 *
 * @author Geoffroy Jamgotchian {@literal <geoffroy.jamgotchian at rte-france.com>}
 */
public abstract class AbstractMapModuleConfig extends AbstractModuleConfig {

    private final FileSystem fs;

    private static PowsyblException createUnexpectedPropertyTypeException(String name, Class<?> type, Class<?>[] expectedTypes) {
        return new PowsyblException("Unexpected value type " + type.getName()
            + " for property " + name + ", " + Arrays.toString(expectedTypes) + " is expected ");
    }

    private static PowsyblException createPropertyIsNotException(String name, String what, Exception e) {
        return new PowsyblException("Property " + name + " is not " + what, e);
    }

    /**
     * @param fs File system to provide {@link Path} objects.
     */
    protected AbstractMapModuleConfig(FileSystem fs) {
        this.fs = Objects.requireNonNull(fs);
    }

    /**
     * Returns the value of the property with the specified name, or {@code null} if it does not exist.
     * The returned object may be a {@link String} or directly a more specialized type
     * ({@link Date} or {@link Float} for instance).
     *
     * @param propertyName The name of the property to be looked up
     * @return The value of the specified property it it exists, {@code null} otherwise.
     */
    protected abstract Object getValue(String propertyName);

    @Override
    public Optional<String> getOptionalStringProperty(String name) {
        Objects.requireNonNull(name);
        Object value = getValue(name);
        if (value == null) {
            return Optional.empty();
        }
        if (!(value instanceof String)) {
            throw createUnexpectedPropertyTypeException(name, value.getClass(), new Class[]{String.class});
        }
        return Optional.of((String) value).map(PlatformEnv::substitute);
    }

    @Override
    public Optional<List<String>> getOptionalStringListProperty(String name) {
        Objects.requireNonNull(name);
        Object value = getValue(name);
        if (value == null) {
            return Optional.empty();
        } else {
            if (value instanceof String val) {
                String trimmedString = val.trim();
                if (trimmedString.isEmpty()) {
                    return Optional.of(Collections.emptyList());
                } else {
                    return Optional.of(Arrays.asList((PlatformEnv.substitute(trimmedString)).split("[:,]")));
                }
            } else if (value instanceof List) {
                return Optional.of(((List<String>) value).stream().map(PlatformEnv::substitute).collect(Collectors.toList()));
            } else {
                throw createUnexpectedPropertyTypeException(name, value.getClass(), new Class[]{String.class, List.class});
            }
        }
    }

    @Override
    public OptionalInt getOptionalIntProperty(String name) {
        Objects.requireNonNull(name);
        Object value = getValue(name);
        if (value == null) {
            return OptionalInt.empty();
        }
        if (value instanceof Integer) {
            return OptionalInt.of((int) value);
        } else if (value instanceof String val) {
            try {
                return OptionalInt.of(Integer.parseInt(val));
            } catch (NumberFormatException e) {
                throw createPropertyIsNotException(name, "an integer", e);
            }
        } else {
            throw createUnexpectedPropertyTypeException(name, value.getClass(), new Class[]{String.class, Integer.class});
        }
    }

    @Override
    public OptionalLong getOptionalLongProperty(String name) {
        Objects.requireNonNull(name);
        Object value = getValue(name);
        if (value == null) {
            return OptionalLong.empty();
        }
        if (value instanceof Long val) {
            return OptionalLong.of(val);
        } else if (value instanceof Integer val) {
            return OptionalLong.of(val);
        } else if (value instanceof String val) {
            try {
                return OptionalLong.of(Long.parseLong(val));
            } catch (NumberFormatException e) {
                throw createPropertyIsNotException(name, "a long", e);
            }
        } else {
            throw createUnexpectedPropertyTypeException(name, value.getClass(), new Class[]{String.class, Long.class, Integer.class});
        }
    }

    @Override
    public Optional<Float> getOptionalFloatProperty(String name) {
        Objects.requireNonNull(name);
        Object value = getValue(name);
        if (value == null) {
            return Optional.empty();
        }
        if (value instanceof Number val) {
            return Optional.of(val.floatValue());
        } else if (value instanceof String val) {
            try {
                return Optional.of(Float.parseFloat(val));
            } catch (NumberFormatException e) {
                throw createPropertyIsNotException(name, "a float", e);
            }
        } else {
            throw createUnexpectedPropertyTypeException(name, value.getClass(), new Class[]{Number.class, String.class});
        }
    }

    @Override
    public OptionalDouble getOptionalDoubleProperty(String name) {
        Objects.requireNonNull(name);
        Object value = getValue(name);
        if (value == null) {
            return OptionalDouble.empty();
        }
        if (value instanceof Number val) {
            return OptionalDouble.of(val.doubleValue());
        } else if (value instanceof String val) {
            try {
                return OptionalDouble.of(Double.parseDouble(val));
            } catch (NumberFormatException e) {
                throw createPropertyIsNotException(name, "a double", e);
            }
        } else {
            throw createUnexpectedPropertyTypeException(name, value.getClass(), new Class[]{Number.class, String.class});
        }
    }

    @Override
    public Optional<Boolean> getOptionalBooleanProperty(String name) {
        Objects.requireNonNull(name);
        Object value = getValue(name);
        if (value == null) {
            return Optional.empty();
        }
        if (value instanceof Boolean val) {
            return Optional.of(val);
        } else if (value instanceof String val) {
            return Optional.of(Boolean.parseBoolean(val));
        } else {
            throw createUnexpectedPropertyTypeException(name, value.getClass(), new Class[]{Boolean.class, String.class});
        }
    }

    @Override
    public Optional<ZonedDateTime> getOptionalDateTimeProperty(String name) {
        Objects.requireNonNull(name);
        Object value = getValue(name);
        if (value == null) {
            return Optional.empty();
        }
        if (value instanceof Date date) {
            return Optional.of(ZonedDateTime.ofInstant(date.toInstant(), ZoneOffset.UTC));
        } else if (value instanceof String val) {
            try {
                return Optional.of(ZonedDateTime.parse(val));
            } catch (IllegalArgumentException e) {
                throw createPropertyIsNotException(name, "an ISO date time", e);
            }
        } else {
            throw createUnexpectedPropertyTypeException(name, value.getClass(), new Class[]{Date.class, String.class});
        }
    }

    @Override
    public Optional<Path> getOptionalPathProperty(String name) {
        return getOptionalStringProperty(name).map(fs::getPath);
    }

    @Override
    public Optional<List<Path>> getOptionalPathListProperty(String name) {
        return getOptionalStringListProperty(name).flatMap(strings -> Optional.of(strings.stream()
            .map(fs::getPath)
            .collect(Collectors.toList())));
    }
}