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

org.praxislive.project.GraphBuilder Maven / Gradle / Ivy

Go to download

Forest-of-actors runtime supporting real-time systems and real-time recoding - bringing aspects of Erlang, Smalltalk and Extempore to Java.

There is a newer version: 6.0.0-beta2
Show newest version
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 2024 Neil C Smith.
 * 
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version 3 only, as
 * published by the Free Software Foundation.
 * 
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
 * version 3 for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License version 3
 * along with this work; if not, see http://www.gnu.org/licenses/
 * 
 * 
 * Please visit https://www.praxislive.org if you need additional information or
 * have any questions.
 */
package org.praxislive.project;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.SequencedMap;
import java.util.SequencedSet;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Stream;
import org.praxislive.core.ComponentAddress;
import org.praxislive.core.ComponentType;
import org.praxislive.core.ControlAddress;
import org.praxislive.core.OrderedMap;
import org.praxislive.core.OrderedSet;
import org.praxislive.core.Value;

/**
 * Builders for graph component elements.
 */
public final class GraphBuilder {

    private GraphBuilder() {
    }

    /**
     * Create a component element builder for the provided component type.
     *
     * @param type component type
     * @return component builder
     */
    public static Component component(ComponentType type) {
        return new Component(type);
    }

    /**
     * Create a component element builder initialized with the type and
     * sub-elements of the provided component element.
     *
     * @param component base component
     * @return builder
     */
    public static Component component(GraphElement.Component component) {
        return new Component(component);
    }

    /**
     * Create a root element builder for the provided ID and type.
     *
     * @param id root ID
     * @param type root type
     * @return builder
     */
    public static Root root(String id, ComponentType type) {
        return new Root(id, type);
    }

    /**
     * Create a root element builder initialized with the ID, type and
     * sub-elements of the provided root element. If the provided root is
     * synthetic then the builder will be too.
     *
     * @param root base root
     * @return builder
     */
    public static Root root(GraphElement.Root root) {
        return new Root(root);
    }

    /**
     * Create a root element builder for a synthetic root.
     *
     * @return builder
     */
    public static Root syntheticRoot() {
        return new Root("", GraphElement.Root.SYNTHETIC);
    }

    /**
     * Abstract base class of component and root element builders.
     *
     * @param  builder type
     */
    @SuppressWarnings("unchecked")
    public static sealed abstract class Base> {

        final ComponentType type;
        final List comments;
        final SequencedMap properties;
        final SequencedMap children;
        final SequencedSet connections;

        private Base(ComponentType type) {
            this.type = Objects.requireNonNull(type);
            comments = new ArrayList<>();
            properties = new LinkedHashMap<>();
            children = new LinkedHashMap<>();
            connections = new LinkedHashSet<>();
        }

        private Base(GraphElement.Component component) {
            this(component.type());
            comments.addAll(component.comments());
            properties.putAll(component.properties());
            children.putAll(component.children());
            connections.addAll(component.connections());
        }

        /**
         * Add a child component element.
         *
         * @param id child ID
         * @param child child element
         * @return this
         */
        public B child(String id, GraphElement.Component child) {
            if (!ComponentAddress.isValidID(id)) {
                throw new IllegalArgumentException(id + " is not a valid child ID");
            }
            children.put(id, Objects.requireNonNull(child));
            return (B) this;
        }

        /**
         * Add a child of the given type, configured by the passed in builder
         * consumer.
         *
         * @param id child ID
         * @param type child component type
         * @param constructor builder consumer to configure the component
         * @return this
         */
        public B child(String id, ComponentType type, Consumer constructor) {
            Component childBuilder = new Component(type);
            constructor.accept(childBuilder);
            return child(id, childBuilder.build());
        }

        /**
         * Add a comment element.
         *
         * @param text comment text
         * @return this
         */
        public B comment(String text) {
            return comment(GraphElement.comment(text));
        }

        /**
         * Add a comment element.
         *
         * @param comment comment element
         * @return this
         */
        public B comment(GraphElement.Comment comment) {
            comments.add(Objects.requireNonNull(comment));
            return (B) this;
        }

        /**
         * Add a connection element.
         *
         * @param sourceComponent source component ID
         * @param sourcePort source port ID
         * @param targetComponent target component ID
         * @param targetPort target port ID
         * @return this
         */
        public B connection(String sourceComponent, String sourcePort,
                String targetComponent, String targetPort) {
            return connection(GraphElement.connection(sourceComponent, sourcePort,
                    targetComponent, targetPort));
        }

        /**
         * Add a connection element.
         *
         * @param connection connection element
         * @return this
         */
        public B connection(GraphElement.Connection connection) {
            connections.add(Objects.requireNonNull(connection));
            return (B) this;
        }

        /**
         * Add a property element.
         *
         * @param id property ID
         * @param value property value
         * @return this
         */
        public B property(String id, Value value) {
            return property(id, GraphElement.property(value));
        }

        /**
         * Add a property element.
         *
         * @param id property ID
         * @param property property element
         * @return this
         */
        public B property(String id, GraphElement.Property property) {
            if (!ControlAddress.isValidID(id)) {
                throw new IllegalArgumentException(id + " is not a valid property ID");
            }
            properties.put(id, Objects.requireNonNull(property));
            return (B) this;
        }

        /**
         * Clear the existing children.
         *
         * @return this
         */
        public B clearChildren() {
            children.clear();
            return (B) this;
        }

        /**
         * Clear the existing comments.
         *
         * @return this
         */
        public B clearComments() {
            comments.clear();
            return (B) this;
        }

        /**
         * Clear the existing connections.
         *
         * @return this
         */
        public B clearConnections() {
            connections.clear();
            return (B) this;
        }

        /**
         * Clear the existing properties.
         *
         * @return this
         */
        public B clearProperties() {
            properties.clear();
            return (B) this;
        }

        /**
         * Transform the existing children. The transform function is called
         * with a stream of the existing child map entries, and should return a
         * list of desired child elements. The returned list will be used to
         * replace the existing children. The map entries in the stream are
         * immutable, but the component elements may be reused.
         *
         * @param transform children transform function
         * @return this
         */
        public B transformChildren(
                Function>, List>> transform) {
            var transformed = transform.apply(children.entrySet().stream());
            clearChildren();
            transformed.forEach(c -> child(c.getKey(), c.getValue()));
            return (B) this;
        }

        /**
         * Transform the existing comments. The transform function is called
         * with a stream of the existing comment elements, and should return a
         * list of desired comment elements. The returned list will be used to
         * replace the existing comments.
         *
         * @param transform comment transform function
         * @return this
         */
        public B transformComments(
                Function, List> transform) {
            var transformed = transform.apply(comments.stream());
            clearComments();
            transformed.forEach(c -> comment(c));
            return (B) this;
        }

        /**
         * Transform the existing connections. The transform function is called
         * with a stream of the existing connection elements, and should return
         * a list of desired connection elements. The returned list will be used
         * to replace the existing connections.
         *
         * @param transform connection transform function
         * @return this
         */
        public B transformConnections(
                Function, List> transform) {
            var transformed = transform.apply(connections.stream());
            clearConnections();
            transformed.forEach(c -> connection(c));
            return (B) this;
        }

        /**
         * Transform the existing properties. The transform function is called
         * with a stream of the existing property map entries, and should return
         * a list of desired property elements. The returned list will be used
         * to replace the existing properties. The map entries in the stream are
         * immutable, but the property elements may be reused.
         *
         * @param transform property transform function
         * @return this
         */
        public B transformProperties(
                Function>, List>> transform) {
            var transformed = transform.apply(properties.entrySet().stream());
            clearProperties();
            transformed.forEach(p -> property(p.getKey(), p.getValue()));
            return (B) this;
        }

        /**
         * Component type.
         *
         * @return type
         */
        public ComponentType type() {
            return type;
        }

        /**
         * Immutable snapshot of comments.
         *
         * @return comments
         */
        public List comments() {
            return List.copyOf(comments);
        }

        /**
         * Immutable snapshot of properties.
         *
         * @return properties
         */
        public SequencedMap properties() {
            return OrderedMap.copyOf(properties);
        }

        /**
         * Immutable snapshot of children.
         *
         * @return children
         */
        public SequencedMap children() {
            return OrderedMap.copyOf(children);
        }

        /**
         * Immutable snapshot of connections.
         *
         * @return connections
         */
        public SequencedSet connections() {
            return OrderedSet.copyOf(connections);
        }
    }

    /**
     * Component element builder.
     */
    public static final class Component extends Base {

        private Component(ComponentType type) {
            super(type);
        }

        private Component(GraphElement.Component component) {
            super(component);
        }

        /**
         * Build a component element from this builder.
         *
         * @return created component element
         */
        public GraphElement.Component build() {
            return GraphElement.component(type, comments, properties, children, connections);
        }

    }

    /**
     * Root element builder.
     */
    public static final class Root extends Base {

        private final String id;
        private final List commands;

        private Root(String id, ComponentType type) {
            super(type);
            this.id = id;
            this.commands = new ArrayList<>();
        }

        private Root(GraphElement.Root root) {
            super(root);
            this.id = root.id();
            this.commands = new ArrayList<>(root.commands());
        }

        /**
         * Add a command element.
         *
         * @param command command element
         * @return this
         */
        public Root command(GraphElement.Command command) {
            commands.add(Objects.requireNonNull(command));
            return this;
        }

        /**
         * Add a command element.
         *
         * @param command command line
         * @return this
         */
        public Root command(String command) {
            return command(GraphElement.command(command));
        }

        /**
         * Clear the existing commands.
         *
         * @return this
         */
        public Root clearCommands() {
            commands.clear();
            return this;
        }

        @Override
        public Root property(String id, GraphElement.Property property) {
            if (isSynthetic()) {
                throw new IllegalStateException("Synthetic roots cannot have properties.");
            }
            return super.property(id, property);
        }

        /**
         * Transform the existing commands. The transform function is called
         * with a stream of the existing command elements, and should return a
         * list of desired command elements. The returned list will be used to
         * replace the existing commands.
         *
         * @param transform command transform function
         * @return this
         */
        public Root transformCommands(
                Function, List> transform) {
            var transformed = transform.apply(commands.stream());
            clearCommands();
            transformed.forEach(c -> command(c));
            return this;
        }

        /**
         * Immutable snapshot of commands.
         *
         * @return commands
         */
        public List commands() {
            return List.copyOf(commands);
        }

        /**
         * Root ID.
         *
         * @return id
         */
        public String id() {
            return id;
        }

        /**
         * Query whether the root is synthetic.
         *
         * @return true if synthetic
         */
        public boolean isSynthetic() {
            return id.isEmpty();
        }

        /**
         * Build a root element from this builder.
         *
         * @return created root element
         */
        public GraphElement.Root build() {
            return GraphElement.root(id, type, comments, commands,
                    properties, children, connections);
        }

    }

}