EdgeModel.java

package org.thewonderlemming.c4plantuml.graphml.model;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.Optional;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

import org.apache.commons.codec.digest.DigestUtils;
import org.thewonderlemming.c4plantuml.grammars.SourceType;
import org.thewonderlemming.c4plantuml.graphml.Build;
import org.thewonderlemming.c4plantuml.graphml.model.builder.WithId;
import org.thewonderlemming.c4plantuml.graphml.model.builder.WithSource;
import org.thewonderlemming.c4plantuml.graphml.model.builder.WithTarget;

/**
 * The <edge> part of the representation of a GraphML stream.
 * <p>
 * It is based on JAXB.
 *
 * @author thewonderlemming
 *
 */
@XmlRootElement(name = EdgeModel.TAG_NAME)
@XmlAccessorType(XmlAccessType.FIELD)
public class EdgeModel {

    /**
     * The current model XML tag name.
     */
    public static final String TAG_NAME = "edge";

    @XmlElement(name = DataModel.TAG_NAME, required = true)
    private MappedListDecorator<DataModel, String> data;

    @XmlAttribute(name = "id", required = true)
    private String id;

    @XmlAttribute(name = "source", required = true)
    private String source;

    @XmlAttribute(name = "target", required = true)
    private String target;


    /**
     * A builder to the current {@link EdgeModel} class.
     *
     * @return a new {@link EdgeModel} instance.
     */
    public static WithId<String, WithSource<String, WithTarget<String, WithData<Build<EdgeModel>>>>> builder() {

        return id -> source -> target -> data -> () -> {

            final EdgeModel edge = new EdgeModel();
            edge.setId(id);
            edge.setSource(source);
            edge.setTarget(target);
            edge.setData(data);

            return edge;
        };
    }

    /**
     * Generates a hopefully unique and repeatably consistent ID for a given graph edge, based on its properties.
     *
     * @param graphId the ID of the graph holding the current edge.
     * @return the generated ID.
     */
    public static WithSource<String, WithTarget<String, WithLabel<WithProtocol<WithC4Level<String>>>>> generateIdFrom(
        final String graphId) {

        return source -> target -> label -> protocol -> c4Level -> {

            try {

                final String hashString = label + protocol;
                final MessageDigest digest = MessageDigest.getInstance("SHA-256");
                final String digested = new DigestUtils(digest).digestAsHex(hashString);

                return String.format("%s__%s__%s__%s__%s", graphId, source, target, c4Level, digested);

            } catch (final NoSuchAlgorithmException e) {
                throw new CannotComputeEdgeModelId(e);
            }
        };
    }

    /**
     * Default constructor.
     */
    private EdgeModel() {
        // Does nothing but hiding the current constructor.
    }

    /**
     * Adds the given {@link DataModel} to the list if its {@link DataModel#getKey()} is not already there or replaces
     * it else.
     *
     * @param data the given {@link DataModel} to add/replace.
     */
    public void addOrReplaceData(final DataModel data) {
        this.data.addOrReplaceData(data);
    }

    /**
     * Returns the {@link C4Keys#C4_LEVEL} property.
     *
     * @return the {@link C4Keys#C4_LEVEL}.
     */
    public Optional<String> getC4Level() {
        return this.data
            .stream()
                .filter(model -> model.getKey().equals(C4Keys.C4_LEVEL.getId()))
                .map(DataModel::getValue)
                .findFirst();
    }

    /**
     * Returns the edge's list of {@link DataModel}.
     *
     * @return the list of {@link DataModel}.
     */
    public List<DataModel> getData() {
        return data;
    }

    /**
     * Returns the edge's ID.
     *
     * @return the current ID.
     */
    public String getId() {
        return id;
    }

    /**
     * Returns the {@link C4Keys#LABEL} property.
     *
     * @return the {@link C4Keys#LABEL}.
     */
    public Optional<String> getLabel() {
        return this.data
            .stream()
                .filter(model -> model.getKey().equals(C4Keys.LABEL.getId()))
                .map(DataModel::getValue)
                .findFirst();
    }

    /**
     * Returns the {@link C4Keys#PROTOCOL} property.
     *
     * @return the {@link C4Keys#PROTOCOL}.
     */
    public Optional<String> getProtocol() {
        return this.data
            .stream()
                .filter(model -> model.getKey().equals(C4Keys.PROTOCOL.getId()))
                .map(DataModel::getValue)
                .findFirst();
    }

    /**
     * Returns the edge's source.
     *
     * @return the current source.
     */
    public String getSource() {
        return source;
    }

    /**
     * Returns the edge's target.
     *
     * @return the current target.
     */
    public String getTarget() {
        return target;
    }

    /**
     * Sets a &lt;data&gt; tag of type {@link C4Keys#C4_LEVEL} and with the given value.
     *
     * @param c4Level the data value to set.
     */
    public void setC4Level(final SourceType c4Level) {
        addOrReplaceData(DataModel
            .builder()
                .withKey(C4Keys.C4_LEVEL)
                .withValue(c4Level.getC4Level())
                .build());
    }

    /**
     * Sets a &lt;data&gt; tag of type {@link C4Keys#LABEL} and with the given value.
     *
     * @param label the data value to set.
     */
    public void setLabel(final String label) {
        addOrReplaceData(DataModel
            .builder()
                .withKey(C4Keys.LABEL)
                .withValue(label)
                .build());
    }

    /**
     * Sets a &lt;data&gt; tag of type {@link C4Keys#PROTOCOL} and with the given value.
     *
     * @param protocol the data value to set.
     */
    public void setProtocol(final String protocol) {
        addOrReplaceData(DataModel
            .builder()
                .withKey(C4Keys.PROTOCOL)
                .withValue(protocol)
                .build());
    }

    private void setData(final List<DataModel> data) {
        this.data = new MappedListDecorator<>(DataModel.class, data, DataModel::getKey);
    }

    private void setId(final String id) {
        this.id = id;
    }

    private void setSource(final String source) {
        this.source = source;
    }

    private void setTarget(final String target) {
        this.target = target;
    }
}