Package nodebox.node

Source Code of nodebox.node.NodeLibraryUpgrades$ConvertOSCPropertyFormatOp

package nodebox.node;

import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.Files;
import nodebox.graphics.Point;
import nodebox.util.LoadException;
import org.python.google.common.collect.ImmutableList;
import org.w3c.dom.*;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;

/**
* Helper class that contains all NodeLibrary upgrade migrations.
*/
public class NodeLibraryUpgrades {

    private static final Pattern formatVersionPattern = Pattern.compile("formatVersion=['\"]([\\d\\.]+)['\"]");
    private static Map<String, Method> upgradeMap = new HashMap<String, Method>();

    /**
     * Lookup an upgrade method by name.
     * <p/>
     * This method is not compatible
     *
     * @param methodName The upgrade method name.
     * @return The Method object, to be invoked.
     */
    private static Method upgradeMethod(String methodName) {
        try {
            return NodeLibraryUpgrades.class.getMethod(methodName, String.class);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }

    static {
        upgradeMap.put("1.0", upgradeMethod("upgrade1to2"));
        upgradeMap.put("2", upgradeMethod("upgrade2to3"));
        upgradeMap.put("3", upgradeMethod("upgrade3to4"));
        upgradeMap.put("4", upgradeMethod("upgrade4to5"));
        upgradeMap.put("5", upgradeMethod("upgrade5to6"));
        upgradeMap.put("6", upgradeMethod("upgrade6to7"));
        upgradeMap.put("7", upgradeMethod("upgrade7to8"));
        upgradeMap.put("8", upgradeMethod("upgrade8to9"));
        upgradeMap.put("9", upgradeMethod("upgrade9to10"));
        upgradeMap.put("10", upgradeMethod("upgrade10to11"));
        upgradeMap.put("11", upgradeMethod("upgrade11to12"));
        upgradeMap.put("12", upgradeMethod("upgrade12to13"));
        upgradeMap.put("13", upgradeMethod("upgrade13to14"));
        upgradeMap.put("14", upgradeMethod("upgrade14to15"));
        upgradeMap.put("15", upgradeMethod("upgrade15to16"));
        upgradeMap.put("16", upgradeMethod("upgrade16to17"));
        upgradeMap.put("17", upgradeMethod("upgrade17to18"));
        upgradeMap.put("18", upgradeMethod("upgrade18to19"));
        upgradeMap.put("19", upgradeMethod("upgrade19to20"));
    }

    public static String parseFormatVersion(String xml) {
        Matcher m = formatVersionPattern.matcher(xml);
        if (!m.find()) throw new RuntimeException("Invalid NodeBox file: " + xml);
        return m.group(1);
    }

    private static String readFile(File file) {
        try {
            return Files.toString(file, Charsets.UTF_8);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Upgrade the given file to the latest library version. The file is supposed to be a NDBX file.
     * <p/>
     * It is harmless to pass in current version NDBX files.
     *
     * @param file The .ndbx file to upgrade.
     * @return An upgrade result, containing warnings, correct XML code and a NodeLibrary object.
     * @throws nodebox.util.LoadException If the upgrade fails for some reason.
     */
    public static UpgradeResult upgrade(File file) throws LoadException {
        return upgradeTo(file, NodeLibrary.CURRENT_FORMAT_VERSION);
    }

    /**
     * Upgrade the given file to the target version. The file is supposed to be a NDBX file.
     * <p/>
     * It is harmless to pass in current version NDBX files.
     *
     * @param file The .ndbx file to upgrade.
     * @return An upgrade result, containing warnings, correct XML code and a NodeLibrary object.
     * @throws LoadException If the upgrade fails for some reason.
     */
    public static UpgradeResult upgradeTo(File file, String targetVersion) throws LoadException {
        String currentXml = readFile(file);
        String currentVersion = parseFormatVersion(currentXml);
        ArrayList<String> warnings = new ArrayList<String>();
        // Avoid upgrades getting stuck in an infinite loop.
        int tries = 0;
        if (currentVersion.equals("0.9")) {
            throw new LoadException(file, "This is a NodeBox 2 file. Download NodeBox 2 from http://beta.nodebox.net/");
        }
        while (!currentVersion.equals(targetVersion) && tries < 100) {
            Method upgradeMethod = upgradeMap.get(currentVersion);
            if (upgradeMethod == null) {
                throw new LoadException(file, "Unsupported version " + currentVersion + ": this file is too new. Try downloading a new version of NodeBox from http://nodebox.net/download/");
            }
            try {
                UpgradeStringResult result = (UpgradeStringResult) upgradeMethod.invoke(null, currentXml);
                warnings.addAll(result.warnings);
                currentXml = result.xml;
            } catch (Exception e) {
                throw new LoadException(file, "Upgrading to " + currentVersion + " failed.", e);
            }
            currentVersion = parseFormatVersion(currentXml);
            tries++;
        }
        if (tries >= 100) {
            throw new LoadException(file, "Got stuck in an infinite loop when trying to upgrade from " + currentVersion);
        }
        return new UpgradeResult(file, currentXml, warnings);
    }

    public static UpgradeStringResult upgrade1to2(String inputXml) throws LoadException {
        // Version 2: Vertical node networks
        // 1. Rotate all nodes 90 degrees by reversing X and Y positions.
        // 2. Convert from pixel units to grid units by dividing by GRID_CELL_SIZE.
        final int GRID_CELL_SIZE = 48;
        UpgradeOp verticalNodesOp = new UpgradeOp() {
            @Override
            public void apply(Element e) {
                if (!e.getTagName().equals("node")) return;
                Attr position = e.getAttributeNode("position");
                if (position == null) return;
                Point pt = Point.valueOf(position.getValue());
                Point reversedPoint = new Point(pt.y, pt.x);
                Point gridPoint = new Point(Math.round(reversedPoint.x / GRID_CELL_SIZE) * 3, Math.round(reversedPoint.y / GRID_CELL_SIZE));
                position.setValue(String.valueOf(gridPoint));
            }

            @Override
            public void end(Element root) {
                addWarning("Nodes have been rotated. Your network will look different.");
            }
        };
        return transformXml(inputXml, "2", verticalNodesOp);
    }

    public static UpgradeStringResult upgrade2to3(String inputXml) throws LoadException {
        // Version 3: Rename math.to_integer to math.round.
        UpgradeOp changePrototypeOp = new ChangePrototypeOp("math.to_integer", "math.round");
        UpgradeOp renameOp = new RenameNodeOp("to_integer", "round");
        return transformXml(inputXml, "3", changePrototypeOp, renameOp);
    }

    public static UpgradeStringResult upgrade3to4(String inputXml) throws LoadException {
        // Version 4: Convert corevector.to_points nodes to corevector.point nodes.
        UpgradeOp changePrototypeOp = new ChangePrototypeOp("corevector.to_points", "corevector.point");
        UpgradeOp renameOp = new RenameNodeOp("to_points", "point");
        // todo: write test code for renamePortOp addition.
        UpgradeOp renamePortOp = new RenamePortOp("corevector.to_points", "shape", "value");
        return transformXml(inputXml, "4", renamePortOp, changePrototypeOp, renameOp);
    }

    public static UpgradeStringResult upgrade4to5(String inputXml) throws LoadException {
        // Version 5: The corevector.textpath node loses the height port.
        UpgradeOp removeInputOp = new RemoveInputOp("corevector.textpath", "height");
        return transformXml(inputXml, "5", removeInputOp);
    }

    public static UpgradeStringResult upgrade5to6(String inputXml) throws LoadException {
        // Version 6: Change delete.delete_selected boolean to menu options.
        Map<String, String> mappings = ImmutableMap.of("true", "selected", "false", "non-selected");
        UpgradeOp renamePortOp = new RenamePortOp("corevector.delete", "delete_selected", "operation");
        UpgradeOp changePortTypeOp = new ChangePortTypeOp("corevector.delete", "operation", "string", mappings);
        return transformXml(inputXml, "6", renamePortOp, changePortTypeOp);
    }

    public static UpgradeStringResult upgrade6to7(String inputXml) throws LoadException {
        // Version 7: Replace instances of list.filter with list.cull.
        UpgradeOp changePrototypeOp = new ChangePrototypeOp("list.filter", "list.cull");
        UpgradeOp renameOp = new RenameNodeOp("filter", "cull");
        return transformXml(inputXml, "7", changePrototypeOp, renameOp);
    }

    public static UpgradeStringResult upgrade7to8(String inputXml) throws LoadException {
        // Version 8: The corevector.point_on_path node loses the range port.
        UpgradeOp removeInputOp = new RemoveInputOp("corevector.point_on_path", "range");
        return transformXml(inputXml, "8", removeInputOp);
    }

    public static UpgradeStringResult upgrade8to9(String inputXml) throws LoadException {
        // Version 9: corevector's resample_by_amount and resample_by_length nodes
        // are replaced by the more generic resample node.
        UpgradeOp addInputOp1 = new AddInputOp("corevector.resample_by_amount", "method", "string", "amount");
        UpgradeOp changePrototypeOp1 = new ChangePrototypeOp("corevector.resample_by_amount", "corevector.resample");
        UpgradeOp renameOp1 = new RenameNodeOp("resample_by_amount", "resample");

        UpgradeOp addInputOp2 = new AddInputOp("corevector.resample_by_length", "method", "string", "length");
        UpgradeOp changePrototypeOp2 = new ChangePrototypeOp("corevector.resample_by_length", "corevector.resample");
        UpgradeOp renameOp2 = new RenameNodeOp("resample_by_length", "resample");

        return transformXml(inputXml, "9",
                addInputOp1, changePrototypeOp1, renameOp1,
                addInputOp2, changePrototypeOp2, renameOp2);
    }

    public static UpgradeStringResult upgrade9to10(String inputXml) throws LoadException {
        // Version 10: corevector's wiggle_contours, wiggle_paths and wiggle_points nodes
        // are replaced by the more generic wiggle node.
        UpgradeOp addInputOp1 = new AddInputOp("corevector.wiggle_contours", "scope", "string", "contours");
        UpgradeOp changePrototypeOp1 = new ChangePrototypeOp("corevector.wiggle_contours", "corevector.wiggle");
        UpgradeOp renameOp1 = new RenameNodeOp("wiggle_contours", "wiggle");

        UpgradeOp addInputOp2 = new AddInputOp("corevector.wiggle_paths", "scope", "string", "paths");
        UpgradeOp changePrototypeOp2 = new ChangePrototypeOp("corevector.wiggle_paths", "corevector.wiggle");
        UpgradeOp renameOp2 = new RenameNodeOp("wiggle_paths", "wiggle");

        UpgradeOp addInputOp3 = new AddInputOp("corevector.wiggle_points", "scope", "string", "points");
        UpgradeOp changePrototypeOp3 = new ChangePrototypeOp("corevector.wiggle_points", "corevector.wiggle");
        UpgradeOp renameOp3 = new RenameNodeOp("wiggle_points", "wiggle");

        return transformXml(inputXml, "10",
                addInputOp1, changePrototypeOp1, renameOp1,
                addInputOp2, changePrototypeOp2, renameOp2,
                addInputOp3, changePrototypeOp3, renameOp3);
    }

    public static UpgradeStringResult upgrade10to11(String inputXml) throws LoadException {
        UpgradeOp removeNodeOp = new RemoveNodeOp("corevector.draw_path");
        return transformXml(inputXml, "11", removeNodeOp);
    }

    public static UpgradeStringResult upgrade11to12(String inputXml) throws LoadException {
        UpgradeOp renamePortOp1 = new RenamePortOp("corevector.shape_on_path", "template", "path");
        UpgradeOp renamePortOp2 = new RenamePortOp("corevector.shape_on_path", "dist", "spacing");
        UpgradeOp renamePortOp3 = new RenamePortOp("corevector.shape_on_path", "start", "margin");
        return transformXml(inputXml, "12", renamePortOp1, renamePortOp2, renamePortOp3);
    }

    public static UpgradeStringResult upgrade12to13(String inputXml) throws LoadException {
        UpgradeOp renamePortOp1 = new RenamePortOp("corevector.text_on_path", "shape", "path");
        UpgradeOp renamePortOp2 = new RenamePortOp("corevector.text_on_path", "position", "margin");
        UpgradeOp renamePortOp3 = new RenamePortOp("corevector.text_on_path", "offset", "baseline_offset");
        UpgradeOp removeInputOp = new RemoveInputOp("corevector.text_on_path", "keep_geometry");
        return transformXml(inputXml, "13", renamePortOp1, renamePortOp2, renamePortOp3, removeInputOp);
    }

    public static UpgradeStringResult upgrade13to14(String inputXml) throws LoadException {
        UpgradeOp renamePortOp1 = new RenamePortOp("math.wave", "speed", "period");
        UpgradeOp renamePortOp2 = new RenamePortOp("math.wave", "frame", "offset");
        return transformXml(inputXml, "14", renamePortOp1, renamePortOp2);
    }

    public static UpgradeStringResult upgrade14to15(String inputXml) throws LoadException {
        UpgradeOp renameNodeOp = new RenameNodeOp("make_strings", "split");
        return transformXml(inputXml, "15", renameNodeOp);
    }

    public static UpgradeStringResult upgrade15to16(String inputXml) throws LoadException {
        // Version 16: 'network' and 'node' are reserved names. Nodes with those names have to be renamed.
        // Besides this, only the top level node in any network is allowed to have the name 'root'.
        UpgradeOp renameNodeOp1 = new ExactRenameNodeOp("network", "network");
        UpgradeOp renameNodeOp2 = new ExactRenameNodeOp("node", "node");
        ExactRenameNodeOp renameNodeOp3 = new ExactRenameNodeOp("root", "node");
        renameNodeOp3.skipRootNode();
        UpgradeOp addAttributeOp = new AddAttributeOp("corevector.geonet", "outputType", "geometry");
        UpgradeOp changePrototypeOp = new ChangePrototypeOp("corevector.geonet", "core.network");
        return transformXml(inputXml, "16", renameNodeOp1, renameNodeOp2, renameNodeOp3, addAttributeOp, changePrototypeOp);
    }

    public static UpgradeStringResult upgrade16to17(String inputXml) throws LoadException {
        UpgradeOp convertOSCPropertyOp = new ConvertOSCPropertyFormatOp();
        return transformXml(inputXml, "17", convertOSCPropertyOp);
    }

    public static UpgradeStringResult upgrade17to18(String inputXml) throws LoadException {
        // Version 18: "switch" and "combine" nodes have more ports. This doesn't change anything in the file
        // but does make the files backward-incompatible.
        UpgradeOp convertOSCPropertyOp = new ConvertOSCPropertyFormatOp();
        return transformXml(inputXml, "18");
    }

    public static UpgradeStringResult upgrade18to19(String inputXml) throws LoadException {
        // Version 19: audioplayer devices previously had their default device name set to "audioplayer1".
        // This has changed to "audio1", so to have backward compatibility we have to make sure the
        // old name is set explicitly and not derived from the prototype.

        UpgradeOp renameDeviceNameOp1 = new SetOldDefaultAudioDeviceNameOp("device.audio_analysis", "device_name", "audioplayer1");
        UpgradeOp renameDeviceNameOp2 = new SetOldDefaultAudioDeviceNameOp("device.audio_wave", "device_name", "audioplayer1");
        UpgradeOp renameDeviceNameOp3 = new SetOldDefaultAudioDeviceNameOp("device.beat_detect", "device_name", "audioplayer1");
        return transformXml(inputXml, "19", renameDeviceNameOp1, renameDeviceNameOp2, renameDeviceNameOp3);
    }

    public static UpgradeStringResult upgrade19to20(String inputXml) throws LoadException {
        UpgradeOp renameDevicePropertyOp1 = new ConvertDevicePropertyNameOp("osc", "autostart", "sync_with_timeline");
        UpgradeOp renameDevicePropertyOp2 = new ConvertDevicePropertyNameOp("audioplayer", "autostart", "sync_with_timeline");
        UpgradeOp renameDevicePropertyOp3 = new ConvertDevicePropertyNameOp("audioinput", "autostart", "sync_with_timeline");
        return transformXml(inputXml, "20", renameDevicePropertyOp1, renameDevicePropertyOp2, renameDevicePropertyOp3);
    }

    private static List<Node> childNodes(Node parent) {
        ArrayList<Node> childNodes = new ArrayList<Node>();
        NodeList children = parent.getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
            childNodes.add(children.item(i));
        }
        return childNodes;
    }

    private static List<Element> childElements(Node parent) {
        ArrayList<Element> childElements = new ArrayList<Element>();
        NodeList children = parent.getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
            Node node = children.item(i);
            if (node instanceof Element) {
                childElements.add((Element) node);
            }
        }
        return childElements;
    }

    /**
     * Return direct descendant elements of parent node with the given childName.
     */
    private static List<Element> childElementsWithName(Node parent, String childName) {
        ArrayList<Element> childElements = new ArrayList<Element>();
        NodeList children = parent.getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
            Node node = children.item(i);
            if (node instanceof Element && ((Element) node).getTagName().equals(childName)) {
                childElements.add((Element) node);
            }
        }
        return childElements;
    }

    private static Set<String> getChildNodeNames(Element parent) {
        HashSet<String> names = new HashSet<String>();
        for (Element e : childElementsWithName(parent, "node")) {
            names.add(e.getAttribute("name"));
        }
        return names;
    }

    private static String uniqueName(String prefix, Set<String> existingNames) {
        int counter = 1;
        while (true) {
            String suggestedName = prefix + counter;
            if (!existingNames.contains(suggestedName)) {
                return suggestedName;
            }
            counter++;
        }
    }

    private static void renameRenderedChildReference(Element element, String oldNodeName, String newNodeName) {
        Attr renderedChildReference = element.getAttributeNode("renderedChild");
        if (renderedChildReference == null) return;
        String oldRenderedChild = renderedChildReference.getValue();
        if (oldRenderedChild.equals(oldNodeName)) {
            if (newNodeName == null || newNodeName.length() == 0)
                element.removeAttributeNode(renderedChildReference);
            else
                renderedChildReference.setValue(newNodeName);
        }
    }

    private static void renamePortReference(List<Element> elements, String attributeName, String oldNodeName, String newNodeName) {
        for (Element c : elements) {
            Attr portReference = c.getAttributeNode(attributeName);
            if (portReference == null) continue;
            Iterator<String> portRefIterator = NodeLibrary.PORT_NAME_SPLITTER.split(portReference.getValue()).iterator();
            String nodeName = portRefIterator.next();
            String portName = portRefIterator.next();
            if (oldNodeName.equals(nodeName)) {
                portReference.setValue(String.format("%s.%s", newNodeName, portName));
            }
        }
    }

    private static void renamePortInNodeList(List<Element> elements, String attributeName, String nodeName, String oldPortName, String newPortName) {
        for (Element c : elements) {
            Attr portReference = c.getAttributeNode(attributeName);
            if (portReference == null) continue;
            Iterator<String> portRefIterator = NodeLibrary.PORT_NAME_SPLITTER.split(portReference.getValue()).iterator();
            String nodeRef = portRefIterator.next();
            String portRef = portRefIterator.next();
            if (nodeRef.equals(nodeName) && portRef.equals(oldPortName)) {
                portReference.setValue(String.format("%s.%s", nodeName, newPortName));
            }
        }
    }

    private static void renameNodeReference(List<Element> elements, String attributeName, String oldNodeName, String newNodeName) {
        for (Element c : elements) {
            Attr nodeRef = c.getAttributeNode(attributeName);
            String nodeName = nodeRef.getValue();
            if (oldNodeName.equals(nodeName)) {
                nodeRef.setValue(newNodeName);
            }
        }
    }

    private static void removeNodeInput(Element node, String input) {
        for (Element port : childElementsWithName(node, "port")) {
            Attr nameAttr = port.getAttributeNode("name");
            String portName = nameAttr.getValue();
            if (portName.equals(input)) {
                node.removeChild(port);
            }
        }
        if (node.getAttributeNode("name") != null) {
            Element parent = (Element) node.getParentNode();
            String child = node.getAttributeNode("name").getValue();
            removeConnection(parent, child, input);
            String publishedInput = getParentPublishedInput(parent, child, input);
            if (publishedInput != null)
                removeNodeInput(parent, publishedInput);
        }

    }

    private static void removeConnection(Element parent, String child, String input) {
        for (Element conn : childElementsWithName(parent, "conn")) {
            String inputPort = conn.getAttribute("input");
            if (inputPort.equals(String.format("%s.%s", child, input))) {
                parent.removeChild(conn);
            }
        }
    }

    private static void removeConnections(Element parent, String child) {
        for (Element conn : childElementsWithName(parent, "conn")) {
            String inputPort = conn.getAttribute("input");
            String inputNode = inputPort.split("\\.")[0];

            String outputNode = conn.getAttribute("output");

            if (inputNode.equals(child) || outputNode.equals(child))
                parent.removeChild(conn);
        }
    }

    private static String getParentPublishedInput(Element parent, String child, String input) {
        for (Element port : childElementsWithName(parent, "port")) {
            Attr childRef = port.getAttributeNode("childReference");
            if (childRef != null && childRef.getValue().equals(String.format("%s.%s", child, input))) {
                return port.getAttribute("name");
            }
        }
        return null;
    }

    private static List<String> getParentPublishedInputs(Element parent, String child) {
        ImmutableList.Builder<String> builder = ImmutableList.builder();
        for (Element port : childElementsWithName(parent, "port")) {
            Attr childRef = port.getAttributeNode("childReference");
            if (childRef != null && childRef.getValue().split("\\.")[0].equals(child)) {
                builder.add(port.getAttribute("name"));
            }
        }
        return builder.build();
    }

    private static boolean isNodeWithPrototype(Element e, String nodePrototype) {
        if (e.getTagName().equals("node")) {
            Attr prototype = e.getAttributeNode("prototype");
            if (prototype != null && prototype.getValue().equals(nodePrototype)) {
                return true;
            }
        }
        return false;
    }

    private static Element portWithName(Element nodeElement, String portName) {
        for (Element port : childElementsWithName(nodeElement, "port")) {
            if (port.getAttribute("name").equals(portName)) {
                return port;
            }
        }
        return null;
    }

    private static UpgradeStringResult transformXml(String xml, String newFormatVersion, UpgradeOp... ops) {
        try {

            DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder;
            builder = builderFactory.newDocumentBuilder();
            Document document = builder.parse(new InputSource(new StringReader(xml)));


            // Check that this is a NodeBox document and set the new formatVersion.
            Element root = document.getDocumentElement();
            checkArgument(root.getTagName().equals("ndbx"), "This is not a valid NodeBox document.");
            root.setAttribute("formatVersion", newFormatVersion);

            // Loop through all upgrade operations.
            ArrayList<String> warnings = new ArrayList<String>();
            for (UpgradeOp op : ops) {
                op.start(root);
                transformXmlRecursive(document.getDocumentElement(), op);
                op.end(root);
                warnings.addAll(op.getWarnings());
            }


            TransformerFactory transformerFactory = TransformerFactory.newInstance();
            Transformer transformer = transformerFactory.newTransformer();
            DOMSource source = new DOMSource(document);
            StringWriter sw = new StringWriter();
            StreamResult result = new StreamResult(sw);
            transformer.transform(source, result);
            return new UpgradeStringResult(sw.toString(), warnings);
        } catch (Exception e) {
            throw new RuntimeException("Error while upgrading to " + newFormatVersion + ".", e);
        }
    }

    private static void transformXmlRecursive(Element e, UpgradeOp op) {
        op.apply(e);
        for (Element child : childElements(e)) {
            transformXmlRecursive(child, op);
        }
    }

    private static abstract class UpgradeOp {
        private List<String> warnings = new ArrayList<String>();

        public void start(Element root) {
        }

        public void end(Element root) {
        }

        public abstract void apply(Element e);

        public void addWarning(String warning) {
            warnings.add(warning);
        }

        public List<String> getWarnings() {
            return warnings;
        }
    }

    private static class ChangePrototypeOp extends UpgradeOp {
        private String oldPrototype;
        private String newPrototype;

        private ChangePrototypeOp(String oldPrototype, String newPrototype) {
            this.oldPrototype = oldPrototype;
            this.newPrototype = newPrototype;
        }

        public void apply(Element e) {
            if (isNodeWithPrototype(e, oldPrototype)) {
                Attr prototype = e.getAttributeNode("prototype");
                prototype.setValue(newPrototype);
            }
        }
    }

    private static class SetOldDefaultAudioDeviceNameOp extends UpgradeOp {
        private String prototype;
        private String portName;
        private String deviceName;

        private SetOldDefaultAudioDeviceNameOp(String prototype, String portName, String deviceName) {
            this.prototype = prototype;
            this.portName = portName;
            this.deviceName = deviceName;
        }

        public void apply(Element e) {
            if (isNodeWithPrototype(e, prototype)) {
                Element port = portWithName(e, portName);
                if (port == null) {
                    port = e.getOwnerDocument().createElement("port");
                    port.setAttribute("name", portName);
                    port.setAttribute("type", "string");
                    port.setAttribute("value", deviceName);
                    e.appendChild(port);
                }
            }
        }
    }

    private static class AddAttributeOp extends UpgradeOp {
        private String prototype;
        private String attributeName;
        private String attributeValue;

        private AddAttributeOp(String prototype, String attributeName, String attributeValue) {
            this.prototype = prototype;
            this.attributeName = attributeName;
            this.attributeValue = attributeValue;
        }

        public void apply(Element e) {
            if (isNodeWithPrototype(e, prototype)) {
                e.setAttribute(attributeName, attributeValue);
            }
        }
    }

    private static class ConvertOSCPropertyFormatOp extends UpgradeOp {
        @Override
        public void apply(Element e) {
            if (e.getTagName().equals("property")) {
                Element parent = (Element) e.getParentNode();
                if (parent != null && parent.getTagName().equals("ndbx")) {
                    Attr name = e.getAttributeNode("name");
                    Attr value = e.getAttributeNode("value");
                    if (name != null && name.getValue().equals("oscPort")) {
                        if (value != null) {
                            Element device = e.getOwnerDocument().createElement("device");
                            device.setAttribute("name", "osc1");
                            device.setAttribute("type", "osc");
                            Element portProperty = e.getOwnerDocument().createElement("property");
                            portProperty.setAttribute("name", "port");
                            portProperty.setAttribute("value", value.getValue());
                            device.appendChild(portProperty);
                            Element autostartProperty = e.getOwnerDocument().createElement("property");
                            autostartProperty.setAttribute("name", "autostart");
                            autostartProperty.setAttribute("value", "true");
                            device.appendChild(autostartProperty);
                            parent.replaceChild(device, e);
                        } else {
                            parent.removeChild(e);
                        }
                    }
                }
            }
        }
    }

    private static class ConvertDevicePropertyNameOp extends UpgradeOp {
        private String deviceType;
        private String oldPropertyName;
        private String newPropertyName;

        private ConvertDevicePropertyNameOp(String deviceType, String oldPropertyName, String newPropertyName) {
            this.deviceType = deviceType;
            this.oldPropertyName = oldPropertyName;
            this.newPropertyName = newPropertyName;
        }

        @Override
        public void apply(Element e) {
            if (e.getTagName().equals("property")) {
                Element parent = (Element) e.getParentNode();
                if (parent != null && parent.getTagName().equals("device")) {
                    Attr type = parent.getAttributeNode("type");
                    if (type != null && type.getValue().equals(this.deviceType)) {
                        Attr name = e.getAttributeNode("name");
                        if (name != null && name.getValue().equals(oldPropertyName))
                            name.setValue(newPropertyName);
                    }
                }
            }
        }
    }

    private static class RenameNodeOp extends UpgradeOp {
        private String oldPrefix;
        private String newPrefix;

        private RenameNodeOp(String oldPrefix, String newPrefix) {
            this.oldPrefix = oldPrefix;
            this.newPrefix = newPrefix;
        }

        @Override
        public void apply(Element e) {
            if (e.getTagName().equals("node")) {
                Attr name = e.getAttributeNode("name");
                if (name != null && name.getValue().startsWith(oldPrefix)) {
                    String oldNodeName = name.getValue();
                    Set<String> childNames = getChildNodeNames((Element) e.getParentNode());
                    String newNodeName = uniqueName(newPrefix, childNames);
                    name.setValue(newNodeName);

                    Element parent = (Element) e.getParentNode();
                    renameRenderedChildReference(parent, oldNodeName, newNodeName);
                    List<Element> connections = childElementsWithName(parent, "conn");
                    renamePortReference(connections, "input", oldNodeName, newNodeName);
                    renameNodeReference(connections, "output", oldNodeName, newNodeName);

                    List<Element> ports = childElementsWithName(parent, "port");
                    renamePortReference(ports, "childReference", oldNodeName, newNodeName);
                }
            }
        }
    }

    private static class ExactRenameNodeOp extends UpgradeOp {
        private String oldNodeName;
        private String newPrefix;
        private boolean shouldSkipRoot = false;

        private ExactRenameNodeOp(String oldNodeName, String newPrefix) {
            this.oldNodeName = oldNodeName;
            this.newPrefix = newPrefix;
        }

        private void skipRootNode() {
            this.shouldSkipRoot = true;
        }

        @Override
        public void apply(Element e) {
            if (e.getTagName().equals("node")) {
                if (shouldSkipRoot) {
                    Element parent = (Element) e.getParentNode();
                    if (parent != null && !parent.getTagName().equals("node"))
                        return;
                }
                Attr name = e.getAttributeNode("name");
                if (name != null && name.getValue().equals(oldNodeName)) {
                    Set<String> childNames = getChildNodeNames((Element) e.getParentNode());
                    String newNodeName = uniqueName(newPrefix, childNames);
                    name.setValue(newNodeName);

                    Element parent = (Element) e.getParentNode();
                    renameRenderedChildReference(parent, oldNodeName, newNodeName);
                    List<Element> connections = childElementsWithName(parent, "conn");
                    renamePortReference(connections, "input", oldNodeName, newNodeName);
                    renameNodeReference(connections, "output", oldNodeName, newNodeName);

                    List<Element> ports = childElementsWithName(parent, "port");
                    renamePortReference(ports, "childReference", oldNodeName, newNodeName);
                }
            }
        }
    }

    private static class RemoveInputOp extends UpgradeOp {
        private String nodePrototype;
        private String inputToRemove;

        private RemoveInputOp(String nodePrototype, String inputToRemove) {
            this.nodePrototype = nodePrototype;
            this.inputToRemove = inputToRemove;
        }

        @Override
        public void apply(Element e) {
            if (isNodeWithPrototype(e, nodePrototype)) {
                removeNodeInput(e, inputToRemove);
            }
        }
    }

    private static class RenamePortOp extends UpgradeOp {
        private String nodePrototype;
        private String oldPortName;
        private String newPortName;

        private RenamePortOp(String nodePrototype, String oldPortName, String newPortName) {
            this.nodePrototype = nodePrototype;
            this.oldPortName = oldPortName;
            this.newPortName = newPortName;
        }

        @Override
        public void apply(Element e) {
            if (isNodeWithPrototype(e, nodePrototype)) {
                String nodeName = e.getAttribute("name");
                Element port = portWithName(e, oldPortName);
                if (port != null)
                    port.setAttribute("name", newPortName);
                Element parent = (Element) e.getParentNode();
                List<Element> connections = childElementsWithName(parent, "conn");
                renamePortInNodeList(connections, "input", nodeName, oldPortName, newPortName);
                List<Element> ports = childElementsWithName(parent, "port");
                renamePortInNodeList(ports, "childReference", nodeName, oldPortName, newPortName);
            }
        }
    }

    private static class ChangePortTypeOp extends UpgradeOp {
        private String nodePrototype;
        private String portName;
        private String newType;
        // The value mappings are strings, since that's what's stored in the XML file.
        private Map<String, String> valueMappings;


        public ChangePortTypeOp(String nodePrototype, String portName, String newType, Map<String, String> valueMappings) {
            this.nodePrototype = nodePrototype;
            this.portName = portName;
            this.newType = newType;
            this.valueMappings = valueMappings;
        }

        @Override
        public void apply(Element e) {
            if (isNodeWithPrototype(e, nodePrototype)) {
                Element port = portWithName(e, portName);
                if (port != null) {
                    Attr type = port.getAttributeNode("type");
                    type.setValue(newType);
                    Attr value = port.getAttributeNode("value");
                    if (value != null) {
                        String newValue = valueMappings.get(value.getValue());
                        checkState(newValue != null,
                                "Change port type (%s.%s -> %s): value %s not found in value mappings.",
                                nodePrototype, portName, newType, value.getValue());
                        value.setValue(newValue);
                    }
                }
            }
        }
    }

    private static class AddInputOp extends UpgradeOp {
        private String nodePrototype;
        private String name;
        private String type;
        private String value;

        private AddInputOp(String nodePrototype, String name, String type, String value) {
            this.nodePrototype = nodePrototype;
            this.name = name;
            this.type = type;
            this.value = value;
        }

        @Override
        public void apply(Element e) {
            if (isNodeWithPrototype(e, nodePrototype)) {
                Element port = e.getOwnerDocument().createElement("port");
                port.setAttribute("name", this.name);
                port.setAttribute("type", this.type);
                port.setAttribute("value", this.value);
                e.appendChild(port);
            }
        }
    }

    private static class RemoveNodeOp extends UpgradeOp {
        private String nodePrototype;
        private List<String> removedNodes;

        private RemoveNodeOp(String nodePrototype) {
            this.nodePrototype = nodePrototype;
            removedNodes = new ArrayList<String>();
        }

        @Override
        public void apply(Element e) {
            if (isNodeWithPrototype(e, nodePrototype)) {
                Element parent = (Element) e.getParentNode();
                String child = e.getAttributeNode("name").getValue();
                removedNodes.add(child);

                List<String> publishedInputs = getParentPublishedInputs(parent, child);
                for (String publishedInput : publishedInputs)
                    removeNodeInput(parent, publishedInput);


                removeConnections(parent, child);
                renameRenderedChildReference(parent, child, null);
                e.getParentNode().removeChild(e);
            }
        }

        @Override
        public void end(Element root) {
            if (removedNodes.size() > 0)
                addWarning(String.format("The '%s' node became obsolete, the following nodes in your network got removed: %s", nodePrototype, removedNodes));
        }
    }

    private static class UpgradeStringResult {
        private final String xml;
        private final List<String> warnings;

        private UpgradeStringResult(String xml, List<String> warnings) {
            this.xml = xml;
            this.warnings = warnings;
        }
    }

}
TOP

Related Classes of nodebox.node.NodeLibraryUpgrades$ConvertOSCPropertyFormatOp

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.