/*************************GO-LICENSE-START*********************************
* Copyright 2014 ThoughtWorks, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*************************GO-LICENSE-END***********************************/
package com.thoughtworks.go.domain.valuestreammap;
import com.thoughtworks.go.config.CaseInsensitiveString;
import com.thoughtworks.go.domain.MaterialInstance;
import com.thoughtworks.go.domain.MaterialRevision;
import com.thoughtworks.go.domain.materials.Material;
import com.thoughtworks.go.domain.materials.Modification;
import com.thoughtworks.go.i18n.LocalizedMessage;
import com.thoughtworks.go.server.presentation.models.ValueStreamMapPresentationModel;
import com.thoughtworks.go.server.valuestreammap.CrossingMinimization;
import com.thoughtworks.go.server.valuestreammap.DummyNodeCreation;
import com.thoughtworks.go.server.valuestreammap.LevelAssignment;
import java.util.*;
public class ValueStreamMap {
private Node currentPipeline;
private Node currentMaterial;
private MaterialInstance currentMaterialInstance;
private LinkedHashMap<String, Node> nodeIdToNodeMap = new LinkedHashMap<>();
private List<Node> rootNodes = new ArrayList<>();
private LevelAssignment levelAssignment = new LevelAssignment();
private DummyNodeCreation dummyNodeCreation = new DummyNodeCreation();
private CrossingMinimization crossingMinimization = new CrossingMinimization();
public ValueStreamMap(String pipeline, PipelineRevision pipelineRevision) {
currentPipeline = new PipelineDependencyNode(pipeline, pipeline);
nodeIdToNodeMap.put(currentPipeline.getId(), currentPipeline);
currentPipeline.addRevision(pipelineRevision);
}
public ValueStreamMap(Material material, MaterialInstance materialInstance, Modification modification) {
currentMaterial = new SCMDependencyNode(material.getFingerprint(), material.getUriForDisplay(), material.getTypeForDisplay());
currentMaterialInstance = materialInstance;
nodeIdToNodeMap.put(currentMaterial.getId(), currentMaterial);
((SCMDependencyNode)currentMaterial).addMaterialRevision(new MaterialRevision(material, false, modification));
}
//used in rails
public Node getCurrentPipeline() {
return currentPipeline;
}
public Node getCurrentMaterial() {
return currentMaterial;
}
public MaterialInstance getCurrentMaterialInstance() {
return currentMaterialInstance;
}
public Node addUpstreamNode(Node node, PipelineRevision revision, String dependentNodeId) {
node = addUpstreamNode(node, dependentNodeId);
node.addRevision(revision);
return node;
}
public Node addUpstreamMaterialNode(Node node, CaseInsensitiveString materialName, String dependentNodeId,
MaterialRevision materialRevision) {
SCMDependencyNode scmNode = (SCMDependencyNode) addUpstreamNode(node, dependentNodeId);
scmNode.addMaterialRevision(materialRevision);
if (materialName != null) {
scmNode.addMaterialName(materialName.toString());
}
return scmNode;
}
public Node addDownstreamNode(Node node, String parentNodeId) {
Node parentNode = findNode(parentNodeId);
if (hasNode(node.getId())) {
node = findNode(node.getId());
} else {
nodeIdToNodeMap.put(node.getId(), node);
}
parentNode.addEdge(node);
return node;
}
private Node addUpstreamNode(Node node, String dependentNodeId) {
Node dependentNode = findNode(dependentNodeId);
if (hasNode(node.getId())) {
node = findNode(node.getId());
} else {
nodeIdToNodeMap.put(node.getId(), node);
}
node.addEdge(dependentNode);
return node;
}
public Node findNode(String nodeId) {
return nodeIdToNodeMap.get(nodeId);
}
public Collection<Node> allNodes() {
return nodeIdToNodeMap.values();
}
public List<Node> getRootNodes() {
if (rootNodes.isEmpty()) {
populateRootNodes();
}
return rootNodes;
}
void populateRootNodes() {
rootNodes = new ArrayList<>();
for (Node currentNode : allNodes()) {
if (currentNode.getParents().isEmpty()) {
rootNodes.add(currentNode);
}
}
}
private boolean hasNode(String nodeId) {
return nodeIdToNodeMap.containsKey(nodeId);
}
public ValueStreamMapPresentationModel presentationModel() {
NodeLevelMap nodeLevelMap = levelAssignment.apply(this);
dummyNodeCreation.apply(this, nodeLevelMap);
crossingMinimization.apply(nodeLevelMap);
return new ValueStreamMapPresentationModel(currentPipeline, currentMaterial, nodeLevelMap.nodesAtEachLevel());
}
public boolean hasCycle() {
Set<Node> verifiedNodes = new HashSet<>();
Set<String> nodesInPath = new HashSet<>();
for (Node node : getRootNodes()) {
if (node.hasCycleInSubGraph(nodesInPath, verifiedNodes)) {
return true;
}
}
return false;
}
public void addWarningIfBuiltFromInCompatibleRevisions() {
if (anyRootNodeWithInCompatibleRevisions()) {
addWarning(currentPipeline);
}
}
private boolean anyRootNodeWithInCompatibleRevisions() {
boolean anyRootNodeWithInCompatibleRevisions = false;
for (Node rootNode : getRootNodes()) {
if (hasMultipleLatestRevisionString(((SCMDependencyNode)rootNode).getMaterialRevisions())) {
addWarning(rootNode);
anyRootNodeWithInCompatibleRevisions = true;
}
}
return anyRootNodeWithInCompatibleRevisions;
}
private boolean hasMultipleLatestRevisionString(List<MaterialRevision> materialRevisions) {
int VALID_LATEST_REVISION_STRING_COUNT = 1;
if(materialRevisions.size() == VALID_LATEST_REVISION_STRING_COUNT) return false;
Set<String> latestRevisions = new HashSet<>();
for(MaterialRevision revision : materialRevisions) {
latestRevisions.add(revision.getLatestRevisionString());
}
return latestRevisions.size() > VALID_LATEST_REVISION_STRING_COUNT;
}
private void addWarning(Node node) {
node.setViewType(VSMViewType.WARNING);
node.setMessage(LocalizedMessage.string("VSM_WARNING"));
}
@Override
public String toString() {
String s = "graph:\n";
for (Node currentNode : allNodes()) {
s += currentNode + "\n";
}
return s;
}
}