# 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 &lt;edge&gt; 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;
        }
    }