package nodebox.node;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import nodebox.function.FunctionRepository;
import nodebox.graphics.Point;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static com.google.common.base.Preconditions.checkArgument;
/**
* This class provides mutable access to the immutable NodeLibrary.
* <p/>
* This class is single-threaded. You can only access it from one concurrent thread.
* However, the internal nodeLibrary is immutable, so you can keep looking at a NodeLibrary while the controller
* generates a new one.
*/
public class NodeLibraryController {
private NodeLibrary nodeLibrary;
public static NodeLibraryController create() {
return new NodeLibraryController(NodeLibrary.create("untitled", Node.NETWORK, NodeRepository.of(), FunctionRepository.of()));
}
public static NodeLibraryController create(String libraryName, NodeRepository nodeRepository, FunctionRepository functionRepository) {
return new NodeLibraryController(NodeLibrary.create(libraryName, Node.NETWORK, nodeRepository, functionRepository));
}
public static NodeLibraryController withLibrary(NodeLibrary nodeLibrary) {
return new NodeLibraryController(nodeLibrary);
}
public NodeLibraryController(NodeLibrary nodeLibrary) {
this.nodeLibrary = nodeLibrary;
}
public NodeLibrary getNodeLibrary() {
return nodeLibrary;
}
public void setNodeLibrary(NodeLibrary nodeLibrary) {
this.nodeLibrary = nodeLibrary;
}
public Node getRootNode() {
return nodeLibrary.getRoot();
}
public void setNodeLibraryFile(File file) {
nodeLibrary = nodeLibrary.withFile(file);
}
public void setFunctionRepository(FunctionRepository functionRepository) {
nodeLibrary = nodeLibrary.withFunctionRepository(functionRepository);
}
public void setProperties(Map<String, String> properties) {
nodeLibrary = nodeLibrary.withProperties(properties);
}
public Node getNode(String nodePath) {
return nodeLibrary.getNodeForPath(nodePath);
}
/* public void reloadFunctionLibrary(String namespace) {
checkNotNull(namespace);
FunctionLibrary newLibrary = nodeLibrary.getFunctionRepository().getLibrary(namespace).reload();
functionRepository = nodeLibrary.getFunctionRepository().withLibraryAdded(newLibrary);
nodeLibrary = nodeLibrary.withFunctionRepository(functionRepository);
} */
public void reloadFunctionRepository() {
nodeLibrary.getFunctionRepository().reload();
}
public Node createNode(String parentPath, Node prototype) {
Node parent = getNode(parentPath);
String name = parent.uniqueName(prototype.getName());
Node newNode = prototype.extend().withName(name);
addNode(parentPath, newNode);
return newNode;
}
public void setNodePosition(String nodePath, Point point) {
Node newNode = getNode(nodePath).withPosition(point);
replaceNodeInPath(nodePath, newNode);
}
public void setNodeCategory(String nodePath, String category) {
Node newNode = getNode(nodePath).withCategory(category);
replaceNodeInPath(nodePath, newNode);
}
public void setNodeDescription(String nodePath, String description) {
Node newNode = getNode(nodePath).withDescription(description);
replaceNodeInPath(nodePath, newNode);
}
public void setNodeImage(String nodePath, String image) {
Node newNode = getNode(nodePath).withImage(image);
replaceNodeInPath(nodePath, newNode);
}
public void setNodeOutputType(String nodePath, String outputType) {
Node newNode = getNode(nodePath).withOutputType(outputType);
replaceNodeInPath(nodePath, newNode);
}
public void setNodeOutputRange(String nodePath, Port.Range outputRange) {
Node newNode = getNode(nodePath).withOutputRange(outputRange);
replaceNodeInPath(nodePath, newNode);
}
public void setNodeFunction(String nodePath, String function) {
Node newNode = getNode(nodePath).withFunction(function);
replaceNodeInPath(nodePath, newNode);
}
public void setNodeHandle(String nodePath, String handle) {
Node newNode = getNode(nodePath).withHandle(handle);
replaceNodeInPath(nodePath, newNode);
}
public void setRenderedChild(String parentPath, String nodeName) {
Node newParent = getNode(parentPath).withRenderedChildName(nodeName);
replaceNodeInPath(parentPath, newParent);
}
public Node addNode(String parentPath, Node node) {
Node newParent = getNode(parentPath).withChildAdded(node);
replaceNodeInPath(parentPath, newParent);
// We can't return the given node argument itself because withChildAdded might have chosen a new name,
// Instead return the child at the end of the parent's children list.
return Iterables.getLast(newParent.getChildren());
}
public List<Node> pasteNodes(String parentPath, Node nodesParent, Iterable<Node> nodes) {
return pasteNodes(parentPath, nodesParent, nodes, 4, 2);
}
public List<Node> pasteNodes(String parentPath, Node nodesParent, Iterable<Node> nodes, double xOffset, double yOffset) {
Node parent = getNode(parentPath);
Node newParent = parent.withChildrenAdded(nodesParent, nodes);
for (Node node : Iterables.skip(newParent.getChildren(), parent.getChildren().size()))
newParent = newParent.withChildPositionChanged(node.getName(), xOffset, yOffset);
replaceNodeInPath(parentPath, newParent);
return ImmutableList.copyOf(Iterables.skip(newParent.getChildren(), parent.getChildren().size()));
}
public Node groupIntoNetwork(String parentPath, Iterable<Node> nodes) {
return groupIntoNetwork(parentPath, nodes, "network");
}
public Node groupIntoNetwork(String parentPath, Iterable<Node> nodes, String networkName) {
Node parent = getNode(parentPath);
Node newParent = parent;
for (Node node : nodes) {
newParent = newParent.withChildRemoved(node.getName());
}
Node subnet = Node.NETWORK
.withName(newParent.uniqueName(networkName))
.withChildrenAdded(parent, nodes);
List<String> nodeNames = new ArrayList<String>();
for (Node node : subnet.getChildren()) {
subnet = subnet.withChildReplaced(node.getName(), node.withPosition(node.getPosition().moved(-4, -2)));
nodeNames.add(node.getName());
}
newParent = newParent.withChildAdded(subnet);
Map<String, Integer> portNameOccurrences = new HashMap<String, Integer>();
for (Connection c : parent.getConnections()) {
if (!subnet.hasChild(c.getOutputNode()) && subnet.hasChild(c.getInputNode())) {
Integer times = portNameOccurrences.get(c.getInputPort());
portNameOccurrences.put(c.getInputPort(), times == null ? 1 : times + 1);
}
}
// Input connections to the subnetwork.
for (Connection c : parent.getConnections()) {
String outputNodeName = c.getOutputNode();
String inputNodeName = c.getInputNode();
if (!subnet.hasChild(outputNodeName) && subnet.hasChild(inputNodeName)) {
String portName = c.getInputPort();
if (portNameOccurrences.get(portName) > 1)
portName = subnet.uniqueInputName(portName);
subnet = subnet.publish(inputNodeName, c.getInputPort(), portName);
newParent = newParent
.withChildReplaced(subnet.getName(), subnet)
.connect(outputNodeName, subnet.getName(), portName);
}
}
// Find the most best candidate to become the subnetwork's rendered child.
String renderedChild = findRenderedChildInSubnet(parent, subnet, parent.getRenderedChildName());
// No suitable candidate was found, return the first found node that has an outgoing connection.
if (renderedChild == null) {
for (Connection c : parent.getConnections()) {
if (subnet.hasChild(c.getOutputNode()) && !subnet.hasChild(c.getInputNode())) {
renderedChild = c.getOutputNode();
break;
}
}
}
if (renderedChild != null) {
for (Node node : subnet.getChildren()) {
if (node.getName().equals(renderedChild)) {
subnet = subnet.withRenderedChildName(node.getName());
newParent = newParent.withChildReplaced(subnet.getName(), subnet);
break;
}
}
// Subnetwork output connection(s).
for (Connection c : parent.getConnections()) {
if (renderedChild.equals(c.getOutputNode()) && newParent.hasChild(c.getInputNode()))
newParent = newParent.connect(subnet.getName(), c.getInputNode(), c.getInputPort());
}
}
replaceNodeInPath(parentPath, newParent);
return getNode(Node.path(parentPath, subnet.getName()));
}
private String findRenderedChildInSubnet(Node parent, Node subnet, String child) {
if (subnet.hasChild(child)) return child;
List<String> connectedNodes = new ArrayList<String>();
for (Connection c : parent.getConnections()) {
if (c.getInputNode().equals(child)) {
String outputNode = c.getOutputNode();
if (subnet.hasChild(outputNode)) return outputNode;
connectedNodes.add(outputNode);
}
}
for (String node : connectedNodes) {
String renderedChild = findRenderedChildInSubnet(parent, subnet, node);
if (renderedChild != null) return renderedChild;
}
return null;
}
public void removeNode(String parentPath, String nodeName) {
Node newParent = getNode(parentPath).withChildRemoved(nodeName);
replaceNodeInPath(parentPath, newParent);
}
public void removePort(String parentPath, String nodeName, String portName) {
Node newParent = getNode(parentPath).withChildInputRemoved(nodeName, portName);
replaceNodeInPath(parentPath, newParent);
}
public void renameNode(String parentPath, String oldName, String newName) {
Node newParent = getNode(parentPath).withChildRenamed(oldName, newName);
replaceNodeInPath(parentPath, newParent);
}
public void commentNode(String parentPath, String nodeName, String comment) {
Node newNode = getNode(parentPath).withChildCommented(nodeName, comment);
replaceNodeInPath(parentPath, newNode);
}
public void addPort(String nodePath, String portName, String portType) {
Node newNode = getNode(nodePath).withInputAdded(Port.portForType(portName, portType));
replaceNodeInPath(nodePath, newNode);
}
public void setPortLabel(String nodePath, String portName, String label) {
Node node = getNode(nodePath);
Port newPort = node.getInput(portName).withLabel(label);
Node newNode = node.withInputChanged(portName, newPort);
replaceNodeInPath(nodePath, newNode);
}
public void setPortDescription(String nodePath, String portName, String description) {
Node node = getNode(nodePath);
Port newPort = node.getInput(portName).withDescription(description);
Node newNode = node.withInputChanged(portName, newPort);
replaceNodeInPath(nodePath, newNode);
}
public void setPortWidget(String nodePath, String portName, Port.Widget widget) {
Node node = getNode(nodePath);
Port newPort = node.getInput(portName).withWidget(widget);
Node newNode = node.withInputChanged(portName, newPort);
replaceNodeInPath(nodePath, newNode);
}
public void setPortRange(String nodePath, String portName, Port.Range range) {
Node node = getNode(nodePath);
Port newPort = node.getInput(portName).withRange(range);
Node newNode = node.withInputChanged(portName, newPort);
replaceNodeInPath(nodePath, newNode);
}
public void setPortMinimumValue(String nodePath, String portName, Double minimumValue) {
Node node = getNode(nodePath);
Port newPort = node.getInput(portName).withMinimumValue(minimumValue);
Node newNode = node.withInputChanged(portName, newPort);
replaceNodeInPath(nodePath, newNode);
}
public void setPortMaximumValue(String nodePath, String portName, Double maximumValue) {
Node node = getNode(nodePath);
Port newPort = node.getInput(portName).withMaximumValue(maximumValue);
Node newNode = node.withInputChanged(portName, newPort);
replaceNodeInPath(nodePath, newNode);
}
public void addPortMenuItem(String nodePath, String portName, String key, String label) {
Node node = getNode(nodePath);
Port newPort = node.getInput(portName).withMenuItemAdded(key, label);
Node newNode = node.withInputChanged(portName, newPort);
replaceNodeInPath(nodePath, newNode);
}
public void removePortMenuItem(String nodePath, String portName, MenuItem menuItem) {
Node node = getNode(nodePath);
Port newPort = node.getInput(portName).withMenuItemRemoved(menuItem);
Node newNode = node.withInputChanged(portName, newPort);
replaceNodeInPath(nodePath, newNode);
}
public void movePortMenuItemUp(String nodePath, String portName, int index) {
Node node = getNode(nodePath);
Port newPort = node.getInput(portName).withMenuItemMovedUp(index);
Node newNode = node.withInputChanged(portName, newPort);
replaceNodeInPath(nodePath, newNode);
}
public void movePortMenuItemDown(String nodePath, String portName, int index) {
Node node = getNode(nodePath);
Port newPort = node.getInput(portName).withMenuItemMovedDown(index);
Node newNode = node.withInputChanged(portName, newPort);
replaceNodeInPath(nodePath, newNode);
}
public void updatePortMenuItem(String nodePath, String portName, int index, String key, String label) {
Node node = getNode(nodePath);
Port newPort = node.getInput(portName).withMenuItemChanged(index, key, label);
Node newNode = node.withInputChanged(portName, newPort);
replaceNodeInPath(nodePath, newNode);
}
public void setPortValue(String nodePath, String portName, Object value) {
Node newNode = getNode(nodePath).withInputValue(portName, value);
replaceNodeInPath(nodePath, newNode);
}
public void revertToDefaultPortValue(String nodePath, String portName) {
Node node = getNode(nodePath);
Port port = node.getPrototype().getInput(portName);
if (port != null) {
Node newNode = node.withInputValue(portName, port.getValue());
replaceNodeInPath(nodePath, newNode);
}
}
public void connect(String parentPath, Node outputNode, Node inputNode, Port inputPort) {
Node newParent = getNode(parentPath).connect(outputNode.getName(), inputNode.getName(), inputPort.getName());
replaceNodeInPath(parentPath, newParent);
}
public void connect(String parentPath, String outputNode, String inputNode, String inputPort) {
Node newParent = getNode(parentPath).connect(outputNode, inputNode, inputPort);
replaceNodeInPath(parentPath, newParent);
}
public void disconnect(String parentPath, Connection connection) {
Node newParent = getNode(parentPath).disconnect(connection);
replaceNodeInPath(parentPath, newParent);
}
public void publish(String parentPath, String childNode, String childPort, String publishedName) {
Node newParent = getNode(parentPath).publish(childNode, childPort, publishedName);
replaceNodeInPath(parentPath, newParent);
}
public void unpublish(String parentPath, String publishedName) {
Node newParent = getNode(parentPath).unpublish(publishedName);
replaceNodeInPath(parentPath, newParent);
}
public Device addDevice(String deviceType) {
return addDevice(deviceType, deviceType);
}
public Device addDevice(String deviceType, String name) {
String deviceName = nodeLibrary.uniqueName(name);
Device device = Device.deviceForType(deviceName, deviceType);
nodeLibrary = nodeLibrary.withDeviceAdded(device);
return device;
}
public void removeDevice(String deviceName) {
nodeLibrary = nodeLibrary.withDeviceRemoved(deviceName);
}
public void setDeviceProperty(String deviceName, String propertyName, String propertyValue) {
nodeLibrary = nodeLibrary.withDevicePropertyChanged(deviceName, propertyName, propertyValue);
}
/**
* Replace the node at the given path with the new node.
* Afterwards, the nodeLibrary field is set to the new NodeLibrary.
*
* @param nodePath The node path. This path needs to exist.
* @param node The new node to put in place of the old node.
*/
public void replaceNodeInPath(String nodePath, Node node) {
checkArgument(nodePath.startsWith("/"), "Node path needs to be an absolute path, starting with '/'.");
nodePath = nodePath.substring(1);
Node newRoot;
if (nodePath.isEmpty()) {
newRoot = node;
} else {
newRoot = replacedInPath(nodePath, node);
}
nodeLibrary = nodeLibrary.withRoot(newRoot);
}
/**
* Replace the node at the given path with the new node.
* Helper function that replaces nodes recursively (i.e. deepest sublevels first,
* then going up, until the root node has been reached).
*
* @param nodePath The node path. This path needs to exist.
* @param node The new node to put in place of the old node.
*/
private Node replacedInPath(String nodePath, Node node) {
if (!nodePath.contains("/"))
return getRootNode().withChildReplaced(nodePath, node);
List<String> parts = ImmutableList.copyOf(Splitter.on("/").split(nodePath));
List<String> parentParts = parts.subList(0, parts.size() - 1);
String childName = parts.get(parts.size() - 1);
String parentPath = Joiner.on("/").join(parentParts);
Node parent = nodeLibrary.getNodeForPath("/" + parentPath);
Node newParent = parent.withChildReplaced(childName, node);
return replacedInPath(parentPath, newParent);
}
}