com.act.reachables.Network.java Source code

Java tutorial

Introduction

Here is the source code for com.act.reachables.Network.java

Source

/*************************************************************************
*                                                                        *
*  This file is part of the 20n/act project.                             *
*  20n/act enables DNA prediction for synthetic biology/bioengineering.  *
*  Copyright (C) 2017 20n Labs, Inc.                                     *
*                                                                        *
*  Please direct all queries to act@20n.com.                             *
*                                                                        *
*  This program is free software: you can redistribute it and/or modify  *
*  it under the terms of the GNU General Public License as published by  *
*  the Free Software Foundation, either version 3 of the License, or     *
*  (at your option) any later version.                                   *
*                                                                        *
*  This program is distributed in the hope that it will be useful,       *
*  but WITHOUT ANY WARRANTY; without even the implied warranty of        *
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
*  GNU General Public License for more details.                          *
*                                                                        *
*  You should have received a copy of the GNU General Public License     *
*  along with this program.  If not, see <http://www.gnu.org/licenses/>. *
*                                                                        *
*************************************************************************/

package com.act.reachables;

import act.server.MongoDB;
import act.shared.Chemical;
import act.shared.Reaction;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

public class Network implements Serializable {
    private static final long serialVersionUID = 4643733150478812924L;
    String name;
    HashSet<Node> nodes;
    HashMap<Node, Node> nodeMapping;
    HashSet<Edge> edges;
    HashMap<Long, Node> idToNode;
    HashMap<Pair<Node, Node>, Edge> edgeHash;
    HashMap<Node, Long> nids; // it goes from Node -> Id coz sometimes same ids might be prefixed with r_ or c_ to distinguish categories of nodeMapping
    HashMap<Long, Edge> toParentEdge; // indexed by nodeid
    HashMap<Long, Long> parents; // indexed by nodeid
    HashMap<Long, Integer> tree_depth;
    HashMap<Node, Set<Edge>> edgesGoingToNode;
    HashMap<Long, Set<Edge>> edgesGoingToId;

    Network(String name) {
        this.name = name;
        this.nodes = new HashSet<Node>();
        this.nodeMapping = new HashMap<Node, Node>();
        this.edges = new HashSet<Edge>();
        this.nids = new HashMap<Node, Long>();
        this.tree_depth = new HashMap<Long, Integer>();
        this.edgesGoingToNode = new HashMap<>();
        this.edgesGoingToId = new HashMap<>();
        this.edgeHash = new HashMap<>();
        this.idToNode = new HashMap<>();

        this.selectedNodes = new HashSet<Node>();
        this.parents = new HashMap<>();
        this.toParentEdge = new HashMap<>();
    }

    public Map<Node, Long> nodesAndIds() {
        return this.nids;
    }

    public JSONArray disjointGraphs(MongoDB db) throws JSONException {
        return JSONDisjointGraphs.get(db, new HashSet(this.nodeMapping.values()), this.edges);
    }

    public JSONObject disjointTrees(MongoDB db) throws JSONException {
        return JSONDisjointTrees.get(db, new HashSet(this.nodeMapping.values()), this.edges, this.parents,
                this.toParentEdge);
    }

    void addNode(Node n, Long nid) {
        if (this.nodeMapping.containsKey(n)) {
            if (!Boolean.valueOf((String) Node.getAttribute(n.id, "isrxn"))) {
                return;
            }

            Node currentNode = this.nodeMapping.get(n);
            if (Node.getAttribute(nid, "reaction_ids") != null) {
                HashSet s = ((HashSet) Node.getAttribute(currentNode.id, "reaction_ids"));
                s.addAll((HashSet) Node.getAttribute(nid, "reaction_ids"));

                Node.setAttribute(currentNode.id, "reaction_ids", s);
                Node.setAttribute(currentNode.id, "reaction_count", s.size());
            }

            if (Node.getAttribute(nid, "organisms") != null) {
                HashSet orgs = ((HashSet) Node.getAttribute(currentNode.id, "organisms"));
                orgs.addAll((HashSet) Node.getAttribute(nid, "organisms"));

                Node.setAttribute(currentNode.id, "organisms", orgs);
            }
        } else {
            this.idToNode.put(nid, n);
            this.nodeMapping.put(n, n);
            this.nids.put(n, nid);
        }
    }

    public Node getNodeById(Long id) {
        return this.idToNode.get(id);
    }

    void addEdge(Edge e) {
        this.edges.add(e);

        if (this.edgesGoingToNode.containsKey(e.getDst())) {
            this.edgesGoingToNode.get(e.getDst()).add(e);
            this.edgesGoingToId.get(e.getDst().id).add(e);

        } else {
            Set<Edge> newEdgeList = new HashSet<>();
            newEdgeList.add(e);
            this.edgesGoingToNode.put(e.getDst(), newEdgeList);
            this.edgesGoingToId.put(e.getDst().id, newEdgeList);
        }
    }

    public Edge getEdge(Node src, Node dst) {
        return this.edgeHash.get(Pair.of(src, dst));
    }

    public Set<Edge> getEdgesGoingInto(Node n) {
        return this.edgesGoingToNode.get(n);
    }

    public Set<Edge> getEdgesGoingInto(Long id) {
        return this.edgesGoingToId.get(id);
    }

    void mergeInto(Network that) {
        // this is only written to work for graphs, not
        // specifically trees, expect undefined behaviour
        // if you are keeping track of trees
        that.nodeMapping.values().forEach(n -> addNode(n, n.id));
        that.edges.stream().forEach(this::addEdge);

        this.nids.putAll(that.nids);

    }

    public void serialize(String toFile) {
        try {
            OutputStream file = new FileOutputStream(toFile);
            OutputStream buffer = new BufferedOutputStream(file);
            ObjectOutput output = new ObjectOutputStream(buffer);
            try {
                output.writeObject(this);
            } finally {
                output.close();
            }
        } catch (IOException ex) {
            throw new RuntimeException("Network serialize failed: " + ex);
        }
    }

    public static Network deserialize(String fromFile) {
        try {
            InputStream file = new FileInputStream(fromFile);
            InputStream buffer = new BufferedInputStream(file);
            ObjectInput input = new ObjectInputStream(buffer);
            try {
                return (Network) input.readObject();
            } finally {
                input.close();
            }
        } catch (ClassNotFoundException ex) {
            throw new RuntimeException("Network deserialize failed: Class not found: " + ex);
        } catch (IOException ex) {
            throw new RuntimeException("Network deserialize failed: IO problem: " + ex);
        }
    }

    public String toDOT() {
        List<String> lines = new ArrayList<String>();

        lines.add("digraph " + this.name + " {");

        for (Node n : new ArrayList<Node>(this.nodeMapping.values())) {
            String id;
            String label;
            String tooltip;
            String url;
            String color = Cascade.quote("black");

            if (Boolean.valueOf((String) Node.getAttribute(n.id, "isrxn"))) {
                id = String.valueOf(n.getIdentifier());

                int reactionCount = (int) Node.getAttribute(n.id, "reaction_count");

                Set<String> rawLabel = (HashSet) Node.getAttribute(n.id, "label_string");
                List<String> filteredRawLabel = rawLabel.stream().filter(x -> !x.equals(""))
                        .collect(Collectors.toList());

                Long labelId = n.getIdentifier() - Cascade.rxnIdShift();
                if (labelId < 0) {
                    labelId = Reaction.reverseNegativeId(labelId);
                }

                HashSet<String> organisms = (HashSet<String>) Node.getAttribute(n.id, "organisms");

                String fullLabel;
                if (filteredRawLabel.isEmpty()) {
                    if ((boolean) Node.getAttribute(n.id, "isSpontaneous")) {
                        fullLabel = "Spontaneous";
                    } else {
                        fullLabel = "Not Available";
                    }
                } else {
                    fullLabel = filteredRawLabel.get(0);
                    if (filteredRawLabel.size() > 1) {
                        fullLabel += " and " + String.valueOf(filteredRawLabel.size() - 1) + " more";
                    }
                }

                label = Cascade.quote(fullLabel);
                tooltip = Cascade.quote((String) Node.getAttribute(n.id, "tooltip_string"));
                if ((boolean) Node.getAttribute(n.id, "hasSequence")) {
                    String forestGreen = "#228B22";
                    color = Cascade.quote(forestGreen);
                } else if ((boolean) Node.getAttribute(n.id, "isSpontaneous")) {
                    String goldenrodYellow = "#E8BD2B";
                    color = Cascade.quote(goldenrodYellow);
                } else {
                    String crimsonRed = "#DC143C";
                    color = Cascade.quote(crimsonRed);
                }

                url = Cascade.quote((String) Node.getAttribute(n.id, "url_string"));
            } else {
                id = String.valueOf(n.getIdentifier());

                label = (String) Node.getAttribute(n.id, "label_string");
                if (label == null) {
                    label = n.id >= Cascade.rxnIdShift() ? "Reaction_" + n.id.toString()
                            : "Chemical_" + n.id.toString();
                }
                tooltip = (String) Node.getAttribute(n.id, "tooltip_string");
                url = (String) Node.getAttribute(n.id, "url_string");
            }

            String node_line = id + " [shape=box," + " label=" + label + "," + " tooltip=" + tooltip + "," + " URL="
                    + url + "," + " color=" + color + "," + "];";
            lines.add(node_line);
        }

        for (Edge e : new ArrayList<Edge>(this.edges)) {
            // create a line for nodeMapping like so:
            // id -> id;
            Long src_id = e.getSrc().getIdentifier();
            Long dst_id = e.getDst().getIdentifier();

            String edge_line;
            if (e.getAttribute("color") != null) {
                edge_line = src_id + " -> " + dst_id + " [color=" + e.getAttribute("color") + "]" + ";";
            } else {
                edge_line = src_id + " -> " + dst_id + ";";
            }

            lines.add(edge_line);

            Edge.setAttribute(e, "color", null);
        }

        lines.add("}");

        return StringUtils.join(lines.toArray(new String[0]), "\n");
    }

    void addNodeTreeSpecific(Node n, Long nid, Integer atDepth, Long parentid) {
        this.nodeMapping.put(n, n);
        this.nids.put(n, nid);
        this.parents.put(n.id, parentid);
        this.tree_depth.put(nid, atDepth);
    }

    public HashMap<Long, Integer> nodeDepths() {
        return this.tree_depth;
    }

    void addEdgeTreeSpecific(Edge e, Long childnodeid) {
        this.edges.add(e);
        this.toParentEdge.put(childnodeid, e);
    }

    Long get_parent(Long n) {
        // all addNode's are called with Node's whose id is (id: Long).toString()
        return this.parents.get(n);
    }

    HashSet<Node> selectedNodes;

    void unselectAllNodes() {
        this.selectedNodes.clear();
    }

    void setSelectedNodeState(Set<Node> nodes, boolean flag) {
        this.selectedNodes.addAll(nodes);
    }

}

class JSONDisjointTrees {
    public static JSONObject get(MongoDB db, Set<Node> nodes, Set<Edge> edges, HashMap<Long, Long> parentIds,
            HashMap<Long, Edge> toParentEdges) throws JSONException {
        // init the json object with structure:
        // {
        //   "name": "nodeid"
        //   "children": [
        //     { "name": "childnodeid", toparentedge: {}, nodedata:.. }, ...
        //   ]
        // }

        HashMap<Long, Node> nodeById = new HashMap<>();
        for (Node n : nodes)
            nodeById.put(n.id, n);

        HashMap<Long, JSONObject> nodeObjs = new HashMap<>();
        // un-deconstruct tree...
        for (Long nid : parentIds.keySet()) {
            JSONObject nObj = JSONHelper.nodeObj(db, nodeById.get(nid));
            nObj.put("name", nid);

            if (toParentEdges.get(nid) != null) {
                JSONObject eObj = JSONHelper.edgeObj(toParentEdges.get(nid),
                        null /* no ordering reqd for referencing nodeMapping */);
                nObj.put("edge_up", eObj);
            } else {
            }
            nodeObjs.put(nid, nObj);
        }

        // now that we know that each node has an associated obj
        // link the objects together into the tree structure
        // put each object inside its parent
        HashSet<Long> unAssignedToParent = new HashSet<>(parentIds.keySet());
        for (Long nid : parentIds.keySet()) {
            JSONObject child = nodeObjs.get(nid);
            // append child to "children" key within parent
            JSONObject parent = nodeObjs.get(parentIds.get(nid));
            if (parent != null) {
                parent.append("children", child);
                unAssignedToParent.remove(nid);
            } else {
            }
        }

        // outputting a single tree makes front end processing easier
        // we can always remove the root in the front end and get the forest again

        // if many trees remain, assuming they indicate a disjoint forest,
        //    add then as child to a proxy root.
        // if only one tree then return it

        JSONObject json;
        if (unAssignedToParent.size() == 0) {
            json = null;
            throw new RuntimeException("All nodeMapping have parents! Where is the root? Abort.");
        } else if (unAssignedToParent.size() == 1) {
            json = unAssignedToParent.toArray(new JSONObject[0])[0]; // return the only element in the set
        } else {
            json = new JSONObject();
            for (Long cid : unAssignedToParent) {
                json.put("name", "root");
                json.append("children", nodeObjs.get(cid));
            }
        }

        return json;
    }
    /* Note: there is a streaming variant of JSON tree serialization in the commit history if it is ever needed.
     * See f45bc81818322b8054cf88ae1c22ba5ad654a5d1 for details. */
}

class JSONHelper {

    public static JSONObject nodeObj(MongoDB db, Node n) throws JSONException {
        Chemical thisChemical = db.getChemicalFromChemicalUUID(n.id);
        JSONObject no = thisChemical == null ? new JSONObject()
                : new JSONObject(ComputeReachablesTree.getExtendedChemicalInformationJSON(thisChemical));
        no.put("id", n.id);
        HashMap<String, Serializable> attr = n.getAttr();
        for (String k : attr.keySet()) {
            // only output the fields relevants to the reachables tree structure
            if (k.equals("NameOfLen20") || k.equals("ReadableName") || k.equals("Synonyms") || k.equals("InChI")
                    || k.equals("InChiKEY") || k.equals("parent") || k.equals("under_root")
                    || k.equals("num_children") || k.equals("subtreeVendorsSz") || k.equals("subtreeSz")
                    || k.equals("SMILES"))
                no.put(k, attr.get(k).toString());

            if (k.equals("has"))
                no.put(k, attr.get(k));
        }
        // Object v;
        // String label = "" + ((v = n.getAttribute("canonical")) != null ? v : n.id );
        // no.put("name", label ); // required
        // String layer = "" + ((v = n.getAttribute("globalLayer")) != null ? v : 1);
        // no.put("group", layer ); // required: node color by group
        return no;
    }

    public static JSONObject edgeObj(Edge e, HashMap<Node, Integer> order) throws JSONException {
        JSONObject eo = new JSONObject();
        if (order != null) {
            // 1. when printing a graph (and not a tree), the source and target nodeMapping are identified
            // by the array index they appear in the nodeMapping JSONArray. Those indices are contained in the order-map.
            // 2. such an ordering is not required when we are working with trees, so these fields not output there.
            eo.put("source", order.get(e.src)); // required, and have to lookup its order in the node spec
            eo.put("target", order.get(e.dst)); // required, and have to lookup its order in the node spec
        }
        // eo.put("source_id", e.src.id); // only informational
        // eo.put("target_id", e.dst.id); // only informational
        // eo.put("value", 1); // weight of edge: not really needed
        HashMap<String, Serializable> attr = e.getAttr();
        for (String k : attr.keySet()) {
            // only output the fields relevant to the reachables tree structures
            if (k.equals("under_root") || k.equals("functionalCategory") || k.equals("importantAncestor"))
                eo.put(k, attr.get(k).toString());
        }
        return eo;
    }
}

class JSONDisjointGraphs {

    public static JSONArray get(MongoDB db, Set<Node> nodes, Set<Edge> edges) throws JSONException {
        // init the json object with structure:
        // {
        //   "nodeMapping":[
        //     { "name":"Myriel", "group":1 }, ...
        //   ],
        //   "links":[
        //     { "source":1, "target":0, "value":1 }, ...
        //   ]
        // }
        // nodeMapping.group specifies the node color
        // links.value specifies the edge weight
        JSONArray json = new JSONArray();

        HashMap<Long, Set<Node>> treenodes = new HashMap<Long, Set<Node>>();
        HashMap<Long, Set<Edge>> treeedges = new HashMap<Long, Set<Edge>>();
        for (Node n : nodes) {
            Long k = (Long) n.getAttribute("under_root");
            if (!treenodes.containsKey(k)) {
                treenodes.put(k, new HashSet<Node>());
                treeedges.put(k, new HashSet<Edge>());
            }
            treenodes.get(k).add(n);
        }

        for (Edge e : edges) {
            Long k = (Long) e.getAttribute("under_root");
            if (!treeedges.containsKey(k)) {
                throw new RuntimeException("Fatal: Edge found rooted under a tree (under_root) that has no node!");
            }
            treeedges.get(k).add(e);
        }

        for (Long root : treenodes.keySet()) {
            JSONObject tree = new JSONObject();
            HashMap<Node, Integer> nodeOrder = new HashMap<Node, Integer>();
            tree.put("nodeMapping", nodeListObj(db, treenodes.get(root), nodeOrder /*inits this ordering*/));
            tree.put("links", edgeListObj(treeedges.get(root), nodeOrder /* uses the ordering */));

            json.put(tree);
        }

        return json;

    }

    private static JSONArray nodeListObj(MongoDB db, Set<Node> treenodes, HashMap<Node, Integer> nodeOrder)
            throws JSONException {
        JSONArray a = new JSONArray();
        Node[] nodesAr = treenodes.toArray(new Node[0]);
        for (int i = 0; i < nodesAr.length; i++) {
            Node n = nodesAr[i];
            a.put(i, JSONHelper.nodeObj(db, n)); // put the object at index i in the array
            nodeOrder.put(n, i);
        }
        return a;
    }

    private static JSONArray edgeListObj(Set<Edge> treeedges, HashMap<Node, Integer> order) throws JSONException {
        JSONArray a = new JSONArray();
        for (Edge e : treeedges)
            a.put(JSONHelper.edgeObj(e, order));
        return a;
    }
}