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

it.unibo.alchemist.loader.YamlLoader Maven / Gradle / Ivy

There is a newer version: 17.7.1
Show newest version
/*
 * Copyright (C) 2023, Danilo Pianini and contributors listed in the project's build.gradle.kts file.
 *
 * This file is part of Protelis, and is distributed under the terms of the GNU General Public License,
 * with a linking exception, as described in the file LICENSE.txt in this project's top directory.
 */

package it.unibo.alchemist.loader;

import static java.util.ResourceBundle.getBundle;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Reader;
import java.io.Serializable;
import java.io.StringReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Deque;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.ResourceBundle;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.apache.commons.math3.random.MersenneTwister;
import org.apache.commons.math3.random.RandomGenerator;
import org.danilopianini.lang.PrimitiveUtils;
import org.protelis.test.infrastructure.TestIncarnation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yaml.snakeyaml.Yaml;

import com.google.common.base.Charsets;
import com.google.common.collect.Lists;

import it.unibo.alchemist.SupportedIncarnations;
import it.unibo.alchemist.loader.displacements.Displacement;
import it.unibo.alchemist.loader.export.Extractor;
import it.unibo.alchemist.loader.export.FilteringPolicy;
import it.unibo.alchemist.loader.export.MoleculeReader;
import it.unibo.alchemist.loader.export.NumberOfNodes;
import it.unibo.alchemist.loader.export.filters.CommonFilters;
import it.unibo.alchemist.loader.shapes.Shape;
import it.unibo.alchemist.loader.variables.ArbitraryVariable;
import it.unibo.alchemist.loader.variables.DependentScriptVariable;
import it.unibo.alchemist.loader.variables.DependentVariable;
import it.unibo.alchemist.loader.variables.LinearVariable;
import it.unibo.alchemist.loader.variables.Variable;
import it.unibo.alchemist.model.implementations.environments.Continuous2DEnvironment;
import it.unibo.alchemist.model.implementations.linkingrules.NoLinks;
import it.unibo.alchemist.model.implementations.positions.Continuous2DEuclidean;
import it.unibo.alchemist.model.implementations.times.DoubleTime;
import it.unibo.alchemist.model.interfaces.Action;
import it.unibo.alchemist.model.interfaces.Condition;
import it.unibo.alchemist.model.interfaces.Environment;
import it.unibo.alchemist.model.interfaces.Incarnation;
import it.unibo.alchemist.model.interfaces.Layer;
import it.unibo.alchemist.model.interfaces.LinkingRule;
import it.unibo.alchemist.model.interfaces.Molecule;
import it.unibo.alchemist.model.interfaces.Node;
import it.unibo.alchemist.model.interfaces.Position;
import it.unibo.alchemist.model.interfaces.Reaction;
import it.unibo.alchemist.model.interfaces.Time;
import it.unibo.alchemist.model.interfaces.TimeDistribution;
import java8.util.J8Arrays;
import java8.util.Optional;
import java8.util.function.Supplier;
import java8.util.stream.Collectors;
import java8.util.stream.RefStreams;
import java8.util.stream.Stream;
import java8.util.stream.StreamSupport;

// CHECKSTYLE:OFF
/**
 * Loads a properly formatted YAML file and provides method for instancing a batch of scenarios.
 */
@SuppressFBWarnings
public final class YamlLoader implements Loader, Serializable {

    /**
     * 
     */
    private static final long serialVersionUID = -8503453282930319680L;
    private static final String ALCHEMIST_PACKAGE_ROOT = "it.unibo.alchemist.";
    private static final String SYNTAX_NAME = "YamlSyntax";
    private static final ResourceBundle SYNTAX = getBundle(YamlLoader.class.getPackage().getName() + '.' + SYNTAX_NAME, Locale.US);
    private static final String ACTIONS = SYNTAX.getString("actions");
    private static final String ACTIONS_PACKAGE_ROOT = ALCHEMIST_PACKAGE_ROOT + "model.implementations.actions.";
    private static final String AGGREGATORS = SYNTAX.getString("aggregators");
    private static final String CONCENTRATION = SYNTAX.getString("concentration");
    private static final String CONCENTRATIONS_PACKAGE_ROOT = ALCHEMIST_PACKAGE_ROOT + "model.implementations.concentrations.";
    private static final String CONDITIONS = SYNTAX.getString("conditions");
    private static final String CONDITIONS_PACKAGE_ROOT = ALCHEMIST_PACKAGE_ROOT + "model.implementations.conditions.";
    private static final String CONTENTS = SYNTAX.getString("contents");
    private static final String DEFAULT = SYNTAX.getString("default");
    private static final String DISPLACEMENTS = SYNTAX.getString("displacements");
    private static final String DISPLACEMENTS_PACKAGE_ROOT = ALCHEMIST_PACKAGE_ROOT + "loader.displacements.";
    private static final String ENV_PACKAGE_ROOT = ALCHEMIST_PACKAGE_ROOT + "model.implementations.environments.";
    private static final String ENVIRONMENT = SYNTAX.getString("environment");
    private static final String EXPORT = SYNTAX.getString("export");
    private static final String FORMULA = SYNTAX.getString("formula");
    private static final String IN = SYNTAX.getString("in");
    private static final String INCARNATION = SYNTAX.getString("incarnation");
    private static final String LAYERS = SYNTAX.getString("layers");
    private static final String LAYERS_PACKAGE_ROOT = ALCHEMIST_PACKAGE_ROOT + "model.implementations.layers.";
    private static final String LINKING_PACKAGE_ROOT = ALCHEMIST_PACKAGE_ROOT + "model.implementations.linkingrules.";
    private static final String LINKING_RULE = SYNTAX.getString("linking-rule");
    private static final String MIN = SYNTAX.getString("min");
    private static final String MAX = SYNTAX.getString("max");
    private static final String MOLECULE = SYNTAX.getString("molecule");
    private static final String MOLECULES_PACKAGE_ROOT = ALCHEMIST_PACKAGE_ROOT + "model.implementations.molecules.";
    private static final String NODE = SYNTAX.getString("node");
    private static final String NODES = SYNTAX.getString("nodes");
    private static final String NODES_PACKAGE_ROOT = ALCHEMIST_PACKAGE_ROOT + "model.implementations.nodes.";
    private static final String PARAMS = SYNTAX.getString("parameters");
    private static final String POSITIONS = SYNTAX.getString("positions");
    private static final String POSITIONS_PACKAGE_ROOT = ALCHEMIST_PACKAGE_ROOT + "model.implementations.positions.";
    private static final String PROGRAMS = SYNTAX.getString("programs");
    private static final String PROPERTY = SYNTAX.getString("property");
    private static final String REACTION = SYNTAX.getString("reaction");
    private static final String REACTIONS_PACKAGE_ROOT = ALCHEMIST_PACKAGE_ROOT + "model.implementations.reactions.";
    private static final String SCENARIO_SEED = SYNTAX.getString("scenario-seed");
    private static final String SEEDS = SYNTAX.getString("seeds");
    private static final String SHAPES_PACKAGE_ROOT = ALCHEMIST_PACKAGE_ROOT + "loader.shapes.";
    private static final String SIMULATION_SEED = SYNTAX.getString("simulation-seed");
    private static final String STEP = SYNTAX.getString("step");
    private static final String TIME = SYNTAX.getString("time");
    private static final String TIMEDISTRIBUTION = SYNTAX.getString("time-distribution");
    private static final String TIMEDISTRIBUTIONS_PACKAGE_ROOT = ALCHEMIST_PACKAGE_ROOT + "model.implementations.timedistributions.";
    private static final String TYPE = SYNTAX.getString("type");
    private static final String VALUES = SYNTAX.getString("values");
    private static final String VALUE_FILTER = SYNTAX.getString("value-filter");
    private static final String VALUE_FILTER_PACKAGE_ROOT = ALCHEMIST_PACKAGE_ROOT + "loader.export.filters";
    private static final String VARIABLES = SYNTAX.getString("variables");
    private static final String VARIABLE = "is a variable";

    private static final Logger L = LoggerFactory.getLogger(YamlLoader.class);
    private static final String UNCHECKED = "unchecked";

    @SuppressWarnings({ UNCHECKED, "rawtypes" })
    private static final Class> DEFAULT_ENVIRONMENT_CLASS =
        (Class>) (Class) Continuous2DEnvironment.class;
    @SuppressWarnings({ UNCHECKED, "rawtypes" })
    private static final Class> DEFAULT_LINKING_CLASS =
        (Class>) (Class) NoLinks.class;
    private static final Class DEFAULT_POSITION_CLASS = Continuous2DEuclidean.class;
    private static final Shape IN_ALL = (p) -> true;

    private Object simulationSeed;
    private Object scenarioSeed;
    private final Map reverseLookupTable;
    private final Map lookupTable;
    private final Map computableVariables;
    private final List extractors;
    private final Class> envClass;
    private final Class posClass;
    private final Class> linkingClass;
    private final List envArgs;
    private final List linkingArgs;
    private List layersList = new ArrayList<>(); // NOPMD: false positive

    private final List> displacements;

    private transient Incarnation incarnation;

    private final class PlaceHolder {
        private final String str;

        private PlaceHolder(final String str) {
            this.str = str;
        }

        private String get() {
            return str;
        }

        @Override
        public String toString() {
            return str + ":" + lookupTable.get(str);
        }
    }

    /**
     * @param source
     *            the YAML file
     */
    public YamlLoader(final InputStream source) {
        this(new BufferedReader(new InputStreamReader(source, Charsets.UTF_8)));
    }

    /**
     * @param source
     *            the YAML file
     */
    @SuppressWarnings(UNCHECKED)
    public YamlLoader(final Reader source) {
        final Yaml yaml = new Yaml();
        final Object yamlObj = yaml.load(source);
        L.debug("Parsed yaml: {}", yamlObj);
        if (!(yamlObj instanceof Map)) {
            throw new IllegalArgumentException("Not a valid Alchemist YAML file.");
        }
        final Map contents = (Map) yamlObj;
        /*
         * Extract variables
         */
        final Object varObj = contents.get(VARIABLES);
        if (varObj != null && !(varObj instanceof Map)) {
            throw new IllegalAlchemistYAMLException(
                    "Variables configuration is wrong. A YAML map was expected, instead of " + varObj);
        }
        final Map> originalVars = Optional
                .ofNullable((Map>) varObj)
                .orElse(Collections.emptyMap());
        L.debug("Variables: {}", originalVars);
        reverseLookupTable = StreamSupport.stream(originalVars.entrySet())
                .collect(Collectors.toMap(e -> univoqueId(e.getValue()), Entry::getKey));
        L.debug("Reverse lookup table: {}", reverseLookupTable);
        lookupTable = StreamSupport.stream(originalVars.entrySet())
                .filter(e -> e.getValue() instanceof Map && !((Map) e.getValue()).containsKey(FORMULA))
                .collect(Collectors.toMap(Entry::getKey, e -> makeVar(e.getValue())));
        L.debug("Lookup table: {}", lookupTable);
        computableVariables = StreamSupport.stream(originalVars.entrySet())
                .filter(e -> e.getValue() instanceof Map && ((Map) e.getValue()).containsKey(FORMULA))
                .collect(Collectors.toMap(Entry::getKey, e -> makeDepVar(e.getValue())));
        /*
         * Incarnation
         */
        final Object incObj = contents.get(INCARNATION);
        if (incObj == null) {
            throw new IllegalAlchemistYAMLException("You must specify an incarnation.",
                    new IllegalStateException("No incarnation specified in YAML simulation file"));
        }
        incarnation = TestIncarnation.instance();
        /*
         * RNG
         */
        final Object seedMapObj = contents.get(SEEDS);
        if (seedMapObj instanceof Map) {
            final Map seedMap = (Map) seedMapObj;
            simulationSeed = Optional.ofNullable(makePlaceHolderIfNeeded(seedMap.get(SIMULATION_SEED))).orElse(0);
            scenarioSeed = Optional.ofNullable(makePlaceHolderIfNeeded(seedMap.get(SCENARIO_SEED))).orElse(0);
        } else {
            missingPart(SEEDS, "0 for all random seeds");
            simulationSeed = 0;
            scenarioSeed = 0;
        }
        L.debug("Simulation seed: {}", simulationSeed);
        L.debug("Scenario seed: {}", scenarioSeed);
        /*
         * Environment
         */
        final Object envObj = contents.get(ENVIRONMENT);
        if (envObj instanceof Map) {
            final Map envYaml = (Map) envObj;
            envClass = extractClass(envYaml, ENV_PACKAGE_ROOT, DEFAULT_ENVIRONMENT_CLASS);
            envArgs = extractParams(envYaml);
            L.trace("Environment parameters: {}", envArgs);
            // if envYaml contains LAYERS key 
            if (envYaml.containsKey(LAYERS)) {
                // get its value;
                final Object layersObj = envYaml.get(LAYERS);
                // if layersObj is a List
                if (layersObj instanceof List) {
                    // extract classes and parameter from the list and put them inside layersMap.
                    layersList = (List) envYaml.get(LAYERS);
                } else {
                    throw new IllegalAlchemistYAMLException(layersObj + " is not a valid layer list");
                }
            } else {
                L.info("\"" + LAYERS + "\" key not found in environment. The " + envClass + " won't contain any layer.");
            }
        } else {
            missingPart(ENVIRONMENT, DEFAULT_ENVIRONMENT_CLASS.getName());
            envClass = DEFAULT_ENVIRONMENT_CLASS;
            envArgs = Collections.emptyList();
        }
        /*
         * Linking rule
         */
        final Object linkingObj = contents.get(LINKING_RULE);
        if (linkingObj instanceof Map) {
            final Map linkingYaml = (Map) linkingObj;
            L.trace("Linking rule: {}", linkingYaml);
            linkingClass = extractClass(linkingYaml, LINKING_PACKAGE_ROOT, DEFAULT_LINKING_CLASS);
            linkingArgs = extractParams(linkingYaml);
            L.trace("Linking rule args: {}", linkingArgs);
        } else {
            missingPart(LINKING_RULE, DEFAULT_LINKING_CLASS.getName());
            linkingClass = DEFAULT_LINKING_CLASS;
            linkingArgs = Collections.emptyList();
        }
        /*
         * Positions
         */
        final Object posObj = contents.get(POSITIONS);
        if (posObj instanceof Map) {
            posClass = extractClass((Map) posObj, POSITIONS_PACKAGE_ROOT, DEFAULT_POSITION_CLASS);
        } else {
            missingPart(POSITIONS, DEFAULT_POSITION_CLASS.getName());
            posClass = DEFAULT_POSITION_CLASS;
        }
        /*
         * Displacements
         */
        final Object dispObj = contents.get(DISPLACEMENTS);
        List> tmpDisp = Collections.emptyList();
        if (dispObj instanceof List) {
            final List dispList = (List) dispObj;
            if (!dispList.isEmpty()) {
                if (dispList.get(0) instanceof Map) {
                    tmpDisp = (List>) contents.get(DISPLACEMENTS);
                } else {
                    missingPart(DISPLACEMENTS + " inner", "an empty List");
                }
            }
        } else {
            missingPart(DISPLACEMENTS, "an empty list.");
        }
        displacements = tmpDisp;
        if (displacements.isEmpty()) {
            L.warn("Your {} section is empty. No nodes will be placed in this scenario, making it pretty useless.", DISPLACEMENTS);
        }
        L.debug("Displacements: " + displacements);
        /*
         * Extractors
         */
        final Object extrObj = contents.get(EXPORT);
        if (extrObj instanceof List) {
            extractors = Collections.unmodifiableList(StreamSupport.parallelStream((List) extrObj)
                .map(obj -> {
                    if (obj instanceof String) {
                        final String strDesc = (String) obj;
                        if (TIME.equalsIgnoreCase(strDesc)) {
                            return new it.unibo.alchemist.loader.export.Time();
                        }
                        if (NODES.equalsIgnoreCase(strDesc)) {
                            return new NumberOfNodes();
                        }
                    }
                    if (obj instanceof Map) {
                        final Map mapObj = (Map) obj;
                        if (mapObj.containsKey(MOLECULE)) {
                            final Object aggregatorObj = mapObj.getOrDefault(AGGREGATORS, Collections.emptyList());
                            final List aggregators = aggregatorObj instanceof List ? (List) aggregatorObj : Collections.emptyList();
                            final Object filterObj = mapObj.getOrDefault(VALUE_FILTER, "NoFilter");
                            final FilteringPolicy filter = filterObj instanceof Map
                                    ? extractClassIfDeclared((Map) filterObj, VALUE_FILTER_PACKAGE_ROOT)
                                        .map(clazz -> create(clazz, extractParams((Map) filterObj)))
                                        .map(f -> (FilteringPolicy) f)
                                        .orElseThrow(() -> new IllegalAlchemistYAMLException(filterObj + " is not a valid value filter."))
                                    : CommonFilters.fromString(filterObj.toString());
                            return new MoleculeReader<>(
                                    mapObj.get(MOLECULE).toString(),
                                    mapObj.getOrDefault(PROPERTY, "").toString(),
                                    incarnation,
                                    filter,
                                    aggregators);
                        }
                        final Optional> extrClass = extractClassIfDeclared(mapObj, "it.unibo.alchemist.loader.export.");
                        if (extrClass.isPresent()) {
                            final List params = extractParams(mapObj);
                            return create(extrClass.get(), params, incarnation);
                        }
                    }
                    throw new IllegalAlchemistYAMLException("Could not create an exporter with " + obj);
                })
                .filter(e -> e != null)
                .collect(Collectors.toList())
            );
        } else {
            extractors = Collections.emptyList();
            L.info("{} is not a valid list of exports. No data will be exported.", extrObj);
        }
        try {
            source.close();
        } catch (IOException e1) {
            L.error("Could not close {}", source);
        }
    }

    /**
     * @param yaml
     *            the YAML file content in {@link String} format
     */
    public YamlLoader(final String yaml) {
        this(new StringReader(yaml));
    }

    @SuppressWarnings(UNCHECKED)
    private Map castOrEmpty(final Object target) {
        return target instanceof Map ? (Map) target : Collections.emptyMap();
    }

    private List extractParams(final Map yaml) {
        return StreamSupport.stream(
                    Optional.ofNullable((List) yaml.get(PARAMS))
                    .orElse(Collections.emptyList())
                )
                .map(this::makePlaceHolderIfNeeded)
                .collect(Collectors.toList());
    }

    @Override
    public  Environment getDefault() {
        return getWith(Collections.emptyMap());
    }

    @Override
    public Map getVariables() {
        return Collections.unmodifiableMap(lookupTable);
    }

    @SuppressWarnings(UNCHECKED)
    @Override
    public  Environment getWith(final Map values) {
        final Map actualVars = StreamSupport.stream(lookupTable.entrySet()).collect(Collectors.toMap(
            Entry::getKey,
            entry -> Optional.ofNullable(values.get(entry.getKey())).orElse(entry.getValue().getDefault())
        ));
        int previousSize = -1;
        final Map computableCopy = new LinkedHashMap<>(computableVariables);
        while (previousSize != actualVars.size()) {
            previousSize = actualVars.size();
            final Iterator> it = computableCopy.entrySet().iterator();
            while (it.hasNext()) {
                final Entry entry = it.next();
                final DependentVariable depvar = entry.getValue();
                final String name = entry.getKey();
                try {
                    final double value = depvar.getWith(actualVars);
                    actualVars.putIfAbsent(name, value);
                    it.remove();
                } catch (IllegalStateException e) {
                    L.debug("{} could not be initialized: maybe it depends on another, not yet initialized variable.\nReason: {}", name, e);
                }
            }
        }
        if (!computableCopy.isEmpty()) {
            throw new IllegalStateException("Could not initialize some variables: " + computableCopy);
        }
        L.debug("Variable bindings: {}", actualVars);
        final RandomGenerator simRandom = create(MersenneTwister.class, Lists.newArrayList(simulationSeed), incarnation, actualVars);
        L.debug("Simulation random engine has been initialized: {}.", simRandom);
        final RandomGenerator scenarioRandom = create(MersenneTwister.class, Lists.newArrayList(simulationSeed), incarnation, actualVars);
        L.debug("Scenario random engine has been initialized: {}.", scenarioRandom);
        /*
         * Environment
         */
        final Environment env = (Environment) create(envClass, envArgs, incarnation, actualVars);
        L.debug("Created environment: {}", Objects.requireNonNull(env, "Could not initialize the requested environment."));
        final LinkingRule linking = (LinkingRule) create(linkingClass, linkingArgs, incarnation, actualVars, scenarioRandom, env);
        L.debug("Linking rule is: {}", linking);
        env.setLinkingRule(Objects.requireNonNull(linking, "The linking rule can not be null."));
        /*
         * Layers.
         */
        if (!layersList.isEmpty()) {
            for (final Object layerObj: layersList) {
                if (layerObj instanceof Map) {
                    final Map layer = (Map) layerObj;
                    if (layer.containsKey(MOLECULE) && layer.get(MOLECULE) instanceof String) {
                        final Optional>> layerDescriptor = extractClassIfDeclared(layer, LAYERS_PACKAGE_ROOT);
                        if (layerDescriptor.isPresent()) {
                            final Class> layerClass = layerDescriptor.get();
                            final List layArgs = extractParams(layer);
                            final Molecule molecule = incarnation.createMolecule((String) layer.get(MOLECULE));
                            env.addLayer(molecule, create(layerClass, layArgs));
                        } else {
                            throw new IllegalAlchemistYAMLException(layerObj + " is not a valid layer description: layer class missing or invald layer class name");
                        }
                    } else {
                        throw new IllegalAlchemistYAMLException(layerObj + " is not a valid layer description: molecule missing or invald molecule name");
                    }
                } else {
                    throw new IllegalAlchemistYAMLException(layerObj + " is not a valid layer description");
                }
            }
        }
        final PositionMaker pmaker = new PositionMaker(posClass);
        final Incarnation currIncarnation = (Incarnation) incarnation;
        for (final Map displacement : displacements) {
            final Map displacementShapeMap = (Map) displacement.get(IN);
            final Class displacementClass = extractClass(displacementShapeMap, DISPLACEMENTS_PACKAGE_ROOT, null);
            if (displacementClass != null) {
                final List parameters = extractParams(displacementShapeMap);
                final Displacement displ = create(displacementClass, parameters, incarnation, actualVars, scenarioRandom, env, pmaker);
                L.debug("Displacement initialized: {}", displ);
                final List> contents = Optional
                        .ofNullable((List>) displacement.get(CONTENTS))
                        .orElse(Collections.emptyList());
                for (final Map content: contents) {
                    final Object shapeObj = content.get(IN);
                    if (shapeObj instanceof Map) {
                        final Map shapeMap = (Map) shapeObj;
                        content.put(IN, makeSupplier(
                                () -> IN_ALL,
                                shapeMap, SHAPES_PACKAGE_ROOT, actualVars, simRandom, env, pmaker)
                            .get());
                    } else if (!(shapeObj instanceof Shape)) {
                        content.put(IN, IN_ALL);
                    }
                }
                L.debug("Modified contents: {}", contents);
                Object programsObj = displacement.get(PROGRAMS);
                if (!(programsObj instanceof List)) {
                    missingPart(PROGRAMS, "empty list");
                    programsObj = Collections.emptyList();
                }
                final List programsList = (List) programsObj;
                if (!programsList.isEmpty()) {
                    final Object programObj = programsList.get(0);
                    if (programObj instanceof List) {
                        final List programList = (List) programObj;
                        if (!programList.isEmpty()) {
                            final Object reactionObj = programList.get(0);
                            if (!(reactionObj instanceof Map)) {
                                throw new IllegalAlchemistYAMLException("The reaction should be a YAML map. I got a "
                                        + reactionObj.getClass().getSimpleName() + " instead.");
                            }
                        }
                    } else {
                        throw new IllegalAlchemistYAMLException("The program should be a list of reactions. I got a "
                                + programObj.getClass().getSimpleName() + " instead.");
                    }
                }
                final List> programs = StreamSupport.stream((List>>) programsObj)
                        .flatMap(pool -> StreamSupport.stream(pool))
                        .collect(Collectors.toList());
                final Object nodeDescriptor = displacement.get(NODE);
                final Supplier> nodeSupplier = makeSupplier(
                        () -> currIncarnation.createNode(simRandom, env, stringOrNull(nodeDescriptor)),
                        nodeDescriptor instanceof Map ? (Map) nodeDescriptor : Collections.emptyMap(),
                        NODES_PACKAGE_ROOT, actualVars, simRandom, env);
                for (final Position pos: displ) {
                    final Node node = nodeSupplier.get();
                    for (final Map content: contents) {
                        final Shape shape = (Shape) content.get(IN);
                        if (shape.contains(pos)) {
                            final Molecule mol = makeMolecule(content, actualVars, simRandom, currIncarnation, env, node);
                            node.setConcentration(mol, makeConcentration(content, actualVars, simRandom, currIncarnation, env, node));
                        }
                    }
                    for (final Map program: programs) {
                        final TimeDistribution td = makeTimeDistribution(program, actualVars, simRandom, currIncarnation, env, node);
                        L.trace("{}", td);
                        final Reaction reaction = makeSupplier(
                                () -> currIncarnation.createReaction(simRandom, env, node, td, stringOrNull(program.get(REACTION))),
                                program, REACTIONS_PACKAGE_ROOT, actualVars, simRandom, env, node, td)
                            .get();
                        node.addReaction(reaction);
                        populateConditions(actualVars, program, simRandom, currIncarnation, env, pmaker, node, td, reaction);
                        populateActions(actualVars, program, simRandom, currIncarnation, env, pmaker, node, td, reaction);
                        L.trace("{}", reaction);
                    }
                    env.addNode(node, pos);
                }
            } else {
                L.error("Cannot instance the required displacement: {}", displacement);
            }
        }
        return env;
    }

    private   T makeConcentration(final Map content,
            final Map actualVars,
            final RandomGenerator rand,
            final Incarnation incarnation,
            final Environment env,
            final Node node) {
        final Object concSource = resolveVariable(content.get(CONCENTRATION), actualVars);
        return makeSupplier(
                () -> incarnation.createConcentration(concSource == null ? null : concSource.toString()),
                castOrEmpty(concSource), CONCENTRATIONS_PACKAGE_ROOT, actualVars, rand, env, node)
            .get();
    }

    private  Molecule makeMolecule(final Map content, final Map actualVars, final RandomGenerator rand, final Incarnation incarnation, final Environment env, final Node node) {
        final Object molSource = content.get(MOLECULE);
        return makeSupplier(
                () -> incarnation.createMolecule(molSource == null ? null : molSource.toString()),
                castOrEmpty(molSource), MOLECULES_PACKAGE_ROOT, actualVars, rand, env, node)
            .get();
    }

    private Object makePlaceHolderIfNeeded(final Object in) {
        final Optional> varopt = aVariable(in);
        if (varopt.isPresent()) {
            return new PlaceHolder(reverseLookupTable.get(univoqueId(in)));
        }
        return in;
    }

    private  Supplier makeSupplier(
            final Supplier orElse,
            final Map target,
            final String pkg,
            final Map actualVars,
            final RandomGenerator rand,
            final Environment env) {
        assert target != null;
        assert pkg != null;
        return makeSupplier(orElse, target, pkg, actualVars, rand, env, null, null);
    }

    private  Supplier makeSupplier(
            final Supplier orElse,
            final Map target,
            final String pkg,
            final Map actualVars,
            final RandomGenerator rand,
            final Environment env,
            final Node node) {
        return makeSupplier(orElse, target, pkg, actualVars, rand, env, node, null);
    }

    private  Supplier makeSupplier(
            final Supplier orElse,
            final Map target,
            final String pkg,
            final Map actualVars,
            final RandomGenerator rand,
            final Environment env,
            final Node node,
            final TimeDistribution timedist) {
        return makeSupplier(orElse, target, pkg, actualVars, rand, env, null, node, timedist, null);
    }

    private  Supplier makeSupplier(
            final Supplier orElse,
            final Map target,
            final String pkg,
            final Map actualVars,
            final RandomGenerator rand,
            final Environment env,
            final PositionMaker posMaker) {
        assert target != null;
        return makeSupplier(orElse, target, pkg, actualVars, rand, env, posMaker, null, null, null);
    }

    private  Supplier makeSupplier(
            final Supplier orElse,
            final Map target,
            final String pkg,
            final Map actualVars,
            final RandomGenerator rand,
            final Environment env,
            final PositionMaker posMaker,
            final Node node,
            final TimeDistribution timedist,
            final Reaction reaction) {
        assert target != null;
        assert pkg != null;
        final Optional> clazz = extractClassIfDeclared(target, pkg);
        return clazz.isPresent()
                ? () -> create(clazz.get(), extractParams(target), incarnation, actualVars, rand, env, posMaker, node, timedist, reaction)
                : orElse;
    }

    private  TimeDistribution makeTimeDistribution(final Map content, final Map actualVars, final RandomGenerator rand, final Incarnation incarnation, final Environment env, final Node node) {
        final Object tdSource = resolveVariable(content.get(TIMEDISTRIBUTION), actualVars);
        return makeSupplier(
                () -> incarnation.createTimeDistribution(rand, env, node, stringOrNull(tdSource)),
                castOrEmpty(tdSource), TIMEDISTRIBUTIONS_PACKAGE_ROOT, actualVars, rand, env, node)
            .get();
    }

    private  void populateActions(
            final Map actualVars,
            final Map program,
            final RandomGenerator rand,
            final Incarnation incarnation,
            final Environment env,
            final PositionMaker pMaker,
            final Node node,
            final TimeDistribution td,
            final Reaction reaction) {
        final List actList = (List) program.get(ACTIONS);
        if (actList != null) {
            /*
             * Merge existing actions and those listed.
             */
            final List> actions = RefStreams.concat(StreamSupport.stream(reaction.getActions()), 
                    StreamSupport.stream(actList)
                    .map(actObj -> Optional.ofNullable(
                        makeSupplier(
                                () -> incarnation.createAction(rand, env, node, td, reaction, stringOrNull(actObj)),
                                castOrEmpty(actObj), ACTIONS_PACKAGE_ROOT, actualVars, rand, env, pMaker, node, td, reaction).get())
                    .orElseThrow(() -> new IllegalAlchemistYAMLException("Can not build an action with " + actObj))))
                .collect(Collectors.toList());
            L.trace("actions: {}", actions);
            reaction.setActions(actions);
        }
    }

    private  void populateConditions(
            final Map actualVars,
            final Map program,
            final RandomGenerator rand,
            final Incarnation incarnation,
            final Environment env,
            final PositionMaker pMaker,
            final Node node,
            final TimeDistribution td,
            final Reaction reaction) {
        final List condList = (List) program.get(CONDITIONS);
        if (condList != null) {
            /*
             * Merge existing conditions and those listed.
             */
            final List> conditions = RefStreams.concat(StreamSupport.stream(reaction.getConditions()), 
                    StreamSupport.stream(condList)
                    .map(condObj -> Optional.ofNullable(
                        makeSupplier(
                            () -> incarnation.createCondition(rand, env, node, td, reaction, stringOrNull(condObj)),
                            castOrEmpty(condObj), CONDITIONS_PACKAGE_ROOT, actualVars, rand, env, pMaker, node, td, reaction).get())
                    .orElseThrow(() -> new IllegalAlchemistYAMLException("Can not build a condition with " + condObj))))
                .collect(Collectors.toList());
            L.trace("conditions: {}", conditions);
            reaction.setConditions(conditions);
        }
    }

    private Object resolveVariable(final Object target, final Map variables) {
        final Object extracted = makePlaceHolderIfNeeded(target);
        return extracted instanceof PlaceHolder ? resolvePlaceHolder(variables, extracted) : extracted;
    }

    private void writeObject(final ObjectOutputStream oos) throws IOException {
        oos.defaultWriteObject();
        oos.writeObject(incarnation.toString());
    }

    private void readObject(final ObjectInputStream ois) throws IOException, ClassNotFoundException {
        ois.defaultReadObject();
        incarnation = SupportedIncarnations.get((String) ois.readObject()).get();
    }

    @SuppressWarnings(UNCHECKED)
    private static Optional> aVariable(final Object o) {
        if (o instanceof Map) {
            final Map var = (Map) o;
            final Object maybeVar = var.get(VARIABLE);
            if (maybeVar instanceof Boolean) {
                final boolean isVar = (boolean) maybeVar;
                if (isVar) {
                    return Optional.of((Map) var);
                }
            }
        }
        return Optional.empty();
    }

    private static  O create(
            final Class clazz,
            final List newArrayList) {
        return create(clazz, newArrayList, null);
    }

    private static  O create(
            final Class clazz,
            final List newArrayList,
            final Incarnation incarnation) {
        return create(clazz, newArrayList, incarnation, null);
    }
    private static  O create(
            final Class clazz,
            final List newArrayList,
            final Incarnation incarnation,
            final Map actualVars) {
        return create(clazz, newArrayList, incarnation, actualVars, null, null, null, null, null, null);
    }
    private static  O create(
            final Class clazz,
            final List newArrayList,
            final Incarnation incarnation,
            final Map actualVars,
            final RandomGenerator random,
            final Environment env) {
        return create(clazz, newArrayList, incarnation, actualVars, random, env, null);
    }
    private static  O create(
            final Class clazz,
            final List newArrayList,
            final Incarnation incarnation,
            final Map actualVars,
            final RandomGenerator random,
            final Environment env,
            final PositionMaker pmaker) {
        return create(clazz, newArrayList, incarnation, actualVars, random, env, pmaker, null, null, null);
    }
    private static int countUndecidableParameters(final Constructor constructor, final Object... parameters) {
        return (int) Arrays.stream(constructor.getParameterTypes())
            .filter(requested -> makeClassStream(parameters)
                    .noneMatch(available -> available != null && requested.isAssignableFrom(available)))
            .count();
    }
    private static Stream> makeClassStream(final Object... objects) {
        return J8Arrays.stream(objects)
            .filter(o -> o != null)
            .map(Object::getClass);
    }
    private static  O create(
            final Class clazz,
            final List params,
            final Incarnation incarnation,
            final Map variables,
            final RandomGenerator rand,
            final Environment env,
            final PositionMaker posMaker,
            final Node node,
            final TimeDistribution timedist,
            final Reaction reaction) {
        @SuppressWarnings(UNCHECKED)
        final Optional result = J8Arrays.stream(clazz.getConstructors())
            .sorted((c1, c2) -> {
                final int n1 = countUndecidableParameters(c1, incarnation, rand, env, posMaker, node, timedist, reaction);
                final int n2 = countUndecidableParameters(c2, incarnation, rand, env, posMaker, node, timedist, reaction);
                if (n1 == n2) {
                    /*
                     * Sort using types.
                     */
                    final Class[] paramTypes1 = c1.getParameterTypes();
                    final Class[] paramTypes2 = c2.getParameterTypes();
                    for (int i = 0; i < n1; i++) {
                        final Class p1 = paramTypes1[i];
                        final Class p2 = paramTypes2[i];
                        if (!p1.equals(p2)) {
                            if (Double.class.isAssignableFrom(p1) || double.class.isAssignableFrom(p1)) {
                                return -1;
                            }
                            if (Double.class.isAssignableFrom(p2) || double.class.isAssignableFrom(p2)) {
                                return 1;
                            }
                            if (Long.class.isAssignableFrom(p1) || long.class.isAssignableFrom(p1)) {
                                return -1;
                            }
                            if (Long.class.isAssignableFrom(p2) || long.class.isAssignableFrom(p2)) {
                                return 1;
                            }
                            if (Integer.class.isAssignableFrom(p1) || int.class.isAssignableFrom(p1)) {
                                return -1;
                            }
                            if (Integer.class.isAssignableFrom(p2) || int.class.isAssignableFrom(p2)) {
                                return 1;
                            }
                            L.trace("Fall back to lexicographic comparison for {} and {}", p1, p2);
                            if (p1.getSimpleName().equals(p1.getSimpleName())) {
                                return p1.toString().compareTo(p2.toString());
                            }
                            return p1.getSimpleName().compareTo(p2.getSimpleName());
                        }
                    }
                    L.warn("There are apparently two identical constructors.");
                    return 0;
                }
                final int target = params.size();
                return n1 == target ? -1
                        : n2 == target ? 1
                        : n1 < target ? n2 - n1
                        : n2 < target ? -1
                        : n1 - n2;
            })
            .map(c -> (Constructor) c)
            .map(c -> createBestEffort(c, params, incarnation, variables, rand, env, posMaker, node, timedist, reaction))
            .filter(Optional::isPresent)
            .map(Optional::get)
            .findFirst();
        if (result.isPresent()) {
            return result.get();
        }
        L.error("Unable to create a {} with {}", clazz.getSimpleName(), params);
        return null;
    }
    private static  Optional createBestEffort(
            final Constructor constructor,
            final List params,
            final Incarnation incarnation,
            final Map variables,
            final RandomGenerator rand,
            final Environment env,
            final PositionMaker posMaker,
            final Node node,
            final TimeDistribution timedist,
            final Reaction reaction) {
        final Deque paramsLeft = Lists.newLinkedList(params);
        final Object[] actualArgs = Arrays.stream(constructor.getParameterTypes()).map(expectedClass -> {
            if (Incarnation.class.isAssignableFrom(expectedClass)) {
                return incarnation;
            }
            if (RandomGenerator.class.isAssignableFrom(expectedClass)) {
                return rand;
            }
            if (Environment.class.isAssignableFrom(expectedClass)) {
                return env;
            }
            if (PositionMaker.class.isAssignableFrom(expectedClass)) {
                return posMaker;
            }
            if (Node.class.isAssignableFrom(expectedClass)) {
                return node;
            }
            if (TimeDistribution.class.isAssignableFrom(expectedClass)) {
                return timedist;
            }
            if (Reaction.class.isAssignableFrom(expectedClass)) {
                return reaction;
            }
            while (!paramsLeft.isEmpty()) {
                Object param = paramsLeft.pop();
                if (param instanceof PlaceHolder) {
                    param = resolvePlaceHolder(variables, param);
                }
                if (param == null) {
                    return null;
                }
                if (expectedClass.isAssignableFrom(param.getClass())) {
                    return param;
                }
                if (PrimitiveUtils.classIsNumber(expectedClass) && param instanceof Number) {
                    final Optional attempt = optional2Optional(PrimitiveUtils.castIfNeeded(expectedClass, (Number) param));
                    if (attempt.isPresent()) {
                        return attempt.get();
                    }
                }
                if (PrimitiveUtils.classIsNumber(expectedClass) && param instanceof String) {
                    try {
                        final double d = Double.parseDouble((String) param);
                        final Optional attempt = optional2Optional(PrimitiveUtils.castIfNeeded(expectedClass, d));
                        if (attempt.isPresent()) {
                            return attempt.get();
                        }
                    } catch (final NumberFormatException e) {
                        return null;
                    }
                }
                if (Boolean.class.isAssignableFrom(expectedClass) || boolean.class.isAssignableFrom(expectedClass)) {
                    if (param instanceof Boolean) {
                        return param;
                    }
                    if (param instanceof String) {
                        try {
                            return Boolean.parseBoolean((String) param);
                        } catch (final NumberFormatException e) {
                            return null;
                        }
                    }
                }
                if (CharSequence.class.isAssignableFrom(expectedClass)) {
                    return param.toString();
                }
                if (Time.class.isAssignableFrom(expectedClass)) {
                    if (param instanceof Number) {
                        return new DoubleTime(((Number) param).doubleValue());
                    }
                    return new DoubleTime();
                }
                if (Molecule.class.isAssignableFrom(expectedClass) && param instanceof String) {
                    return incarnation.createMolecule(param.toString());
                }
                if (Position.class.isAssignableFrom(expectedClass) && param instanceof List) {
                    final List coordList = (List) param;
                    if (coordList.stream().allMatch(n -> n instanceof Number)) {
                        return posMaker.makePosition(coordList.stream().map(v -> (Number) v).toArray(i -> new Number[i]));
                    }
                    return null;
                }
                /*
                 * Automatically convert to arrays to lists and vice versa
                 */
                if (List.class.isAssignableFrom(expectedClass) && param.getClass().isArray()) {
                    return Arrays.asList((Object[]) param);
                }
                if (expectedClass.isArray() && param instanceof List) {
                    return ((List) param).toArray();
                }
            }
            return null;
        }).toArray();
        try {
            final O result = constructor.newInstance(actualArgs);
            L.debug("{} produced {} with arguments {}", constructor, result, actualArgs);
            return Optional.of(result);
        } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
                | InvocationTargetException e) {
            L.debug("No luck with {} and arguments {}", constructor, actualArgs);
        }
        return Optional.empty();
    }

    @SuppressWarnings(UNCHECKED)
    private static  Class extractClass(final Map yaml, final String root, final Class defaultClass) {
        assert root != null;
        final Object clazz = yaml.get(TYPE);
        if (clazz != null) {
            final String className = clazz.toString();
            try {
                final String prefix = className.contains(".") ? "" : root;
                return (Class) Class.forName(prefix + className);
            } catch (ClassNotFoundException e) {
                L.debug("Cannot instance {}", className);
            }
        }
        return defaultClass;
    }

    private static  Optional> extractClassIfDeclared(final Map target, final String pkg) {
        assert target != null;
        assert pkg != null;
        return Optional.ofNullable(extractClass(target, pkg, null));
    }

    @SuppressWarnings(UNCHECKED)
    private static Variable makeVar(final Object varObj) {
        final Map var = (Map) varObj;
        var.put(VARIABLE, true);
        final Optional> clazz = extractClassIfDeclared(var, "it.unibo.alchemist.loader.variables.");
        if (clazz.isPresent()) {
            return create(clazz.get(), Optional.ofNullable((List) var.get(PARAMS)).orElse(Collections.emptyList()));
        }
        final Object vals = var.get(VALUES);
        if (vals != null) {
            if (!(vals instanceof List)) {
                throw new IllegalArgumentException(
                        VALUES + " is " + vals + ", but it must be a List of Numbers in " + varObj);
            }
            return new ArbitraryVariable(((Number) var.get(DEFAULT)).doubleValue(), (List) vals);
        }
        final double def = ((Number) var.get(DEFAULT)).doubleValue();
        final double min = ((Number) var.getOrDefault(MIN, def)).doubleValue();
        final double max = ((Number) var.getOrDefault(MAX, def)).doubleValue();
        final double step = ((Number) var.getOrDefault(STEP, 1)).doubleValue();
        return new LinearVariable(def, min, max, step);
    }

    private static DependentVariable makeDepVar(final Object varObj) {
        @SuppressWarnings(UNCHECKED)
        final Map var = (Map) varObj;
        final Object formula = var.get(FORMULA);
        var.put(VARIABLE, true);
        return new DependentScriptVariable(formula.toString());
    }

    private static void missingPart(final String what, final String fallback) {
        L.warn("No {} section provided, or wrong type. Falling back to {}", what, fallback);
    }

    private static Double resolvePlaceHolder(final Map variables, final Object param) {
        return variables.get(((PlaceHolder) param).get());
    }

    private static String stringOrNull(final Object target) {
        return target == null ? null : target.toString();
    }

    private static long univoqueId(final Object obj) {
        return System.identityHashCode(obj);
    }

    @Override
    public List getDataExtractors() {
        return extractors;
    }

    private static  Optional optional2Optional(final java8.util.Optional in) { // NOPMD false positive
        if (in.isPresent()) {
            return Optional.of(in.get());
        }
        return Optional.empty();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy