All Downloads are FREE. Search and download functionalities are using the official Maven repository.

guru.nidi.graphviz.attribute.validate.AttributeValidator Maven / Gradle / Ivy

There is a newer version: 0.18.1
Show newest version
/*
 * Copyright © 2015 Stefan Niederhauser ([email protected])
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package guru.nidi.graphviz.attribute.validate;

import guru.nidi.graphviz.attribute.Attributes;
import guru.nidi.graphviz.attribute.For;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.stream.StreamSupport;

import static guru.nidi.graphviz.attribute.validate.Datatype.*;
import static guru.nidi.graphviz.attribute.validate.ValidatorEngine.*;
import static guru.nidi.graphviz.attribute.validate.ValidatorFormat.OTHER;
import static guru.nidi.graphviz.attribute.validate.ValidatorFormat.UNKNOWN_FORMAT;
import static guru.nidi.graphviz.attribute.validate.ValidatorMessage.Severity.*;
import static java.util.Collections.singletonList;
import static java.util.Locale.ENGLISH;
import static java.util.stream.Collectors.toList;

public final class AttributeValidator {
    private static final Logger LOG = LoggerFactory.getLogger(AttributeValidator.class);

    public enum Scope {
        GRAPH, SUB_GRAPH, CLUSTER, NODE, EDGE;

        @Override
        public String toString() {
            return name().toLowerCase(ENGLISH).replace("_", "");
        }
    }

    private final ValidatorEngine engine;
    private final ValidatorFormat format;

    public AttributeValidator() {
        this(UNKNOWN_ENGINE, UNKNOWN_FORMAT);
    }

    private AttributeValidator(ValidatorEngine engine, ValidatorFormat format) {
        this.engine = engine;
        this.format = format;
    }

    public AttributeValidator forEngine(ValidatorEngine engine) {
        return new AttributeValidator(engine, format);
    }

    public AttributeValidator forFormat(ValidatorFormat format) {
        return new AttributeValidator(engine, format);
    }

    public List validate(Attributes attrs, Scope scope) {
        return StreamSupport.stream(attrs.spliterator(), false)
                .flatMap(entry -> validate(entry.getKey(), entry.getValue(), scope).stream())
                .collect(toList());
    }

    public List validate(String key, Object value, Scope scope) {
        final List configs = AttributeConfigs.get(key);
        if (configs == null) {
            return singletonList(new ValidatorMessage(ERROR, key, "Attribute is unknown."));
        }

        final List engineConfigs = findConfigsForEngine(configs);
        if (engineConfigs.isEmpty()) {
            return singletonList(new ValidatorMessage(
                    ERROR, key, "Attribute is not allowed for engine '" + engine + "'."));
        }

        final List formatConfigs = findConfigsForFormat(configs);
        if (formatConfigs.isEmpty()) {
            return singletonList(new ValidatorMessage(
                    ERROR, key, "Attribute is not allowed for format '" + format + "'."));
        }

        List matchConfigs = intersect(engineConfigs, formatConfigs);
        if (matchConfigs.isEmpty()) {
            return singletonList(new ValidatorMessage(
                    ERROR, key, "Attribute is not allowed for engine '" + engine + "' and format '" + format + "'."));
        }
        if (matchConfigs.size() > 1) {
            matchConfigs = matchConfigs.stream().filter(c -> c.scopes.contains(scope)).collect(toList());
        }
        if (matchConfigs.isEmpty()) {
            return singletonList(new ValidatorMessage(ERROR, key, "Attribute is not allowed for " + scope + "s."));
        }
        if (matchConfigs.size() > 1) {
            LOG.warn("Found multiple attribute configurations for " + engine + ", " + format + " and " + scope + "."
                    + " This should not happen.");
        }
        final List messages = new ArrayList<>();
        validateScope(messages, key, matchConfigs.get(0), scope);
        validateValue(messages, key, matchConfigs.get(0), value);
        validateType(messages, key, matchConfigs.get(0), value);
        return messages;
    }

    private static  List intersect(List as, List bs) {
        return as.stream().filter(bs::contains).collect(toList());
    }

    private List findConfigsForEngine(List configs) {
        return configs.stream()
                .filter(c -> {
                    if (engine == UNKNOWN_ENGINE || c.engines.isEmpty()) {
                        return true;
                    }
                    if (c.engines.contains(NOT_DOT) && engine == DOT) {
                        return false;
                    }
                    return c.engines.contains(engine);
                })
                .collect(toList());
    }

    private List findConfigsForFormat(List configs) {
        return configs.stream()
                .filter(c -> {
                    if (format == OTHER && !c.formats.isEmpty()) {
                        return false;
                    }
                    if (format == UNKNOWN_FORMAT || c.formats.isEmpty()) {
                        return true;
                    }
                    return c.formats.contains(format);
                })
                .collect(toList());
    }

    private void validateScope(List messages, String key, AttributeConfig config, Scope scope) {
        if (!config.scopes.contains(scope)) {
            messages.add(new ValidatorMessage(ERROR, key, "Attribute is not allowed for " + scope + "s."));
        }
    }

    private void validateValue(List messages, String key, AttributeConfig config, Object value) {
        if (config.defVal != null && isValueEquals(config.defVal, value)) {
            messages.add(new ValidatorMessage(
                    INFO, key, "Attribute is set to its default value '" + config.defVal + "'."));
        }
        final Double val = tryParseDouble(value.toString());
        if (config.min != null && val != null && val < config.min) {
            messages.add(new ValidatorMessage(
                    WARN, key, "Attribute has a minimum of '" + config.min + "' but is set to '" + value + "'."));
        }
        if (config.max != null && val != null && val > config.max) {
            messages.add(new ValidatorMessage(
                    WARN, key, "Attribute has a maximum of '" + config.max + "' but is set to '" + value + "'."));
        }
    }

    private void validateType(List messages, String key, AttributeConfig config, Object value) {
        final List typeMessages = config.types.stream().map(t -> t.validate(value)).collect(toList());
        if (typeMessages.size() == 1) {
            if (typeMessages.get(0) != null) {
                messages.add(typeMessages.get(0).atAttribute(key));
            }
        } else {
            if (typeMessages.stream().noneMatch(Objects::isNull)) {
                final List lines = new ArrayList<>();
                for (int i = 0; i < config.types.size(); i++) {
                    lines.add("As " + config.types.get(i).name + ": " + typeMessages.get(i).message);
                }
                messages.add(new ValidatorMessage(ERROR, key,
                        "'" + value + "' is not valid for any of the possible types:\n" + String.join("\n", lines)));
            }
        }
    }

    private boolean isValueEquals(Object config, Object value) {
        if (config instanceof Double) {
            final Double val = doubleValue(value);
            return val != null && Math.abs((Double) config - val) < .0001;
        }
        if (config instanceof Integer) {
            final Integer val = intValue(value);
            return val != null && val.equals(config);
        }
        if (config instanceof Boolean) {
            final Boolean val = boolValue(value);
            return val != null && val.equals(config);
        }
        return config.toString().equals(value.toString());
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy