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 <data> 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 <data> 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 <data> 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;
}
}