/* * (C) Copyright IBM Corp. 2009 * * LICENSE: Eclipse Public License v1.0 * http://www.eclipse.org/legal/epl-v10.html */ package com.ibm.gaiandb.draw; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.HashSet; import java.util.Hashtable; import java.util.Map; import java.util.Set; import prefuse.action.ActionList; import prefuse.action.assignment.ShapeAction; import prefuse.action.assignment.ColorAction; import prefuse.data.Table; import prefuse.data.expression.Predicate; import prefuse.data.expression.parser.ExpressionParser; import prefuse.util.collections.IntIterator; public abstract class Graph extends DatabaseDiagram { // Use PROPRIETARY notice if class contains a main() method, otherwise use COPYRIGHT notice. public static final String COPYRIGHT_NOTICE = "(c) Copyright IBM Corp. 2009"; private static final long serialVersionUID = 8651247478054867510L; private class Edge { public int source = -1; public int target = -1; public Edge(int source, int target) { this.source = source; this.target = target; } public boolean equals(Object o) { if (o instanceof Edge) { Edge other = (Edge)o; return super.equals(o) || (this.source == other.source && this.target == other.target) || (this.source == other.target && this.target == other.source); } else { return super.equals(o); } } public int hashCode() { if (source < target) { return (source << 16) + (target & 0xFFFF); } else { return (target << 16) + (source & 0xFFFF); } } } public static final String NODE_GROUP = GROUP + ".nodes"; public static final String EDGE_GROUP = GROUP + ".edges"; public static final String NODE_ID_COLUMN = "NODE"; public static final String SOURCE_ID_COLUMN = "SOURCE"; public static final String TARGET_ID_COLUMN = "TARGET"; public static final String TIMESTAMP_COLUMN = "UPDATED"; public static final String NODE_PREFIX = "NODE"; public static final String SOURCE_PREFIX = "SOURCE"; public static final String TARGET_PREFIX = "TARGET"; protected prefuse.data.Graph data; protected Table nodes; protected Table edges; protected PreparedStatement statement; protected int firstStatementVar = 1; protected Set<String> extraNodeColumns; protected Set<String> extraEdgeColumns; protected Map<Integer, Integer> nodeIndex = new Hashtable<Integer, Integer>(); protected Map<Edge, Integer> edgeIndex = new Hashtable<Edge, Integer>(); protected ActionList draw = new ActionList(); protected ShapeAction nodeShapeAction; public ShapeAction getNodeShapeAction() { return nodeShapeAction; } public void setNodeShapeAction(ShapeAction nodeShapeAction) { draw.remove(this.nodeShapeAction); this.nodeShapeAction = nodeShapeAction; draw.add(this.nodeShapeAction); } protected ColorAction nodeColorAction; public ColorAction getNodeColorAction() { return nodeColorAction; } public void setNodeColorAction(ColorAction nodeColorAction) { draw.remove(this.nodeColorAction); this.nodeColorAction = nodeColorAction; draw.add(this.nodeColorAction); } protected ColorAction edgeColorAction; public ColorAction getEdgeColorAction() { return edgeColorAction; } public void setEdgeColorAction(ColorAction edgeColorAction) { draw.remove(this.edgeColorAction); this.edgeColorAction = edgeColorAction; draw.add(this.edgeColorAction); } public Graph(PreparedStatement statement, Object... params) throws SQLException { super(); this.statement = statement; firstStatementVar = TableUtil.bindToPreparedStatement(statement, params); } public void populateGraph(Object... params) throws SQLException { TableUtil.bindToPreparedStatement(statement, params, firstStatementVar); nodes = new Table(); nodes.addColumn(NODE_ID_COLUMN, int.class); nodes.addColumn(TIMESTAMP_COLUMN, long.class); edges = new Table(); edges.addColumn(SOURCE_ID_COLUMN, int.class); edges.addColumn(TARGET_ID_COLUMN, int.class); edges.addColumn(TIMESTAMP_COLUMN, long.class); ResultSet resultSet = statement.executeQuery(); extraNodeColumns = new HashSet<String>(); extraEdgeColumns = new HashSet<String>(); addExtraColumns(resultSet); beforeUpdate(); addResultSet(resultSet); afterUpdate(); data = new prefuse.data.Graph( nodes, edges, false, NODE_ID_COLUMN, SOURCE_ID_COLUMN, TARGET_ID_COLUMN); prepareVisualization(); } public void updateGraph(Object... params) throws SQLException { if (null == data) { populateGraph(params); } else { TableUtil.bindToPreparedStatement(statement, params, firstStatementVar); ResultSet resultSet = statement.executeQuery(); beforeUpdate(); addResultSet(resultSet); afterUpdate(); removeOldNodes(); updateVisualization(); } } // These are abstract, but not required. protected void beforeUpdate() throws SQLException { } protected void afterUpdate() throws SQLException { } public void prepareVisualization() { if (null == data || 0 == data.getNodeCount()) { preparation = PreparationStages.UNPREPARED; return; } super.prepareVisualization(); } public void updateVisualization() { if (null == data || 0 == data.getNodeCount()) { return; } super.updateVisualization(); } private void addExtraColumns(ResultSet resultSet) throws SQLException { ResultSetMetaData metadata = resultSet.getMetaData(); int cols = metadata.getColumnCount(); String[] prefixes = { SOURCE_PREFIX, TARGET_PREFIX }; for (int i = 1; i <= cols; i++) { String edgeColumnName = metadata.getColumnName(i); Class<?> type = handler.getDataType(edgeColumnName, metadata.getColumnType(i)); if (null == edges.getColumn(edgeColumnName)) { edges.addColumn(edgeColumnName, type); extraEdgeColumns.add(edgeColumnName); } for (String prefix : prefixes) { if (edgeColumnName.startsWith(prefix + "_")) { String suffix = edgeColumnName.substring(prefix.length() + 1); String nodeColumnName = NODE_PREFIX + "_" + suffix; if (null == nodes.getColumn(nodeColumnName)) { nodes.addColumn(nodeColumnName, type); extraNodeColumns.add(suffix); } break; } } } } private int addResultSet(ResultSet resultSet) throws SQLException { int count = 0; while (resultSet.next()) { int sourceId = resultSet.getInt(SOURCE_ID_COLUMN); int targetId = resultSet.getInt(TARGET_ID_COLUMN); long updated = System.currentTimeMillis() / 1000; synchronized (nodes) { int sourceRow; Integer row = nodeIndex.get(sourceId); if (null == row) { sourceRow = nodes.addRow(); count++; nodeIndex.put(sourceId, sourceRow); nodes.setInt(sourceRow, NODE_ID_COLUMN, sourceId); } else { sourceRow = row; } for (String suffix : extraNodeColumns) { nodes.set(sourceRow, NODE_PREFIX + "_" + suffix, resultSet.getObject(SOURCE_PREFIX + "_" + suffix)); } nodes.setLong(sourceRow, TIMESTAMP_COLUMN, updated); if (sourceId != targetId) { int targetRow; row = nodeIndex.get(targetId); if (null == row) { targetRow = nodes.addRow(); count++; nodeIndex.put(targetId, targetRow); nodes.setInt(targetRow, NODE_ID_COLUMN, targetId); } else { targetRow = row; } for (String suffix : extraNodeColumns) { nodes.set(targetRow, NODE_PREFIX + "_" + suffix, resultSet.getObject(TARGET_PREFIX + "_" + suffix)); } nodes.setLong(targetRow, TIMESTAMP_COLUMN, updated); } } if (sourceId != targetId) { synchronized (edges) { int edgeRow; Edge edge = new Edge(sourceId, targetId); Integer row = edgeIndex.get(edge); if (null == row) { edgeRow = edges.addRow(); count++; edgeIndex.put(edge, edgeRow); edges.setInt(edgeRow, SOURCE_ID_COLUMN, sourceId); edges.setInt(edgeRow, TARGET_ID_COLUMN, targetId); for (String column : extraEdgeColumns) { edges.set(edgeRow, column, resultSet.getObject(column)); } } else { edgeRow = row; } edges.setLong(edgeRow, TIMESTAMP_COLUMN, updated); } } } return count; } public int removeOldNodes() { int now = (int)(System.currentTimeMillis() / 1000); int timeThreshold = nodes.getRowCount() / 100 + 2; Predicate old = ExpressionParser.predicate( now + " - [" + TIMESTAMP_COLUMN + "] > " + timeThreshold); int count = 0; IntIterator oldEdges = edges.rows(old); IntIterator oldNodes = nodes.rows(old); while (oldEdges.hasNext()) { int row = -1; try { row = oldEdges.nextInt(); edgeIndex.remove(new Edge( edges.getInt(row, SOURCE_ID_COLUMN), edges.getInt(row, TARGET_ID_COLUMN))); edges.removeRow(row); count++; } catch (Exception e) { System.err.println("Could not remove edge " + row + "."); } } while (oldNodes.hasNext()) { int row = -1; try { row = oldNodes.nextInt(); nodeIndex.remove(nodes.getInt(row, NODE_ID_COLUMN)); nodes.removeRow(row); count++; } catch (Exception e) { System.err.println("Could not remove node " + row + "."); } } return count; } }