/* Copyright 2008-2010 Gephi Authors : Eduardo Ramos <eduramiba@gmail.com> Website : http://www.gephi.org This file is part of Gephi. Gephi is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Gephi 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with Gephi. If not, see <http://www.gnu.org/licenses/>. */ package org.gephi.datalab.impl; import com.csvreader.CsvReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.math.BigDecimal; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.gephi.data.attributes.api.AttributeColumn; import org.gephi.data.attributes.api.AttributeController; import org.gephi.data.attributes.api.AttributeOrigin; import org.gephi.data.attributes.api.AttributeRow; import org.gephi.data.attributes.api.AttributeTable; import org.gephi.data.attributes.api.AttributeType; import org.gephi.data.attributes.api.AttributeUtils; import org.gephi.data.attributes.api.AttributeValue; import org.gephi.data.attributes.type.BooleanList; import org.gephi.data.attributes.type.DynamicType; import org.gephi.data.attributes.type.NumberList; import org.gephi.data.attributes.type.StringList; import org.gephi.data.properties.PropertiesColumn; import org.gephi.datalab.api.AttributeColumnsController; import org.gephi.datalab.api.GraphElementsController; import org.gephi.dynamic.api.DynamicModel; import org.gephi.graph.api.Attributes; import org.gephi.graph.api.Edge; import org.gephi.graph.api.Graph; import org.gephi.graph.api.GraphController; import org.gephi.graph.api.Node; import org.gephi.utils.StatisticsUtils; import org.openide.util.Exceptions; import org.openide.util.Lookup; import org.openide.util.lookup.ServiceProvider; /** * Implementation of the AttributeColumnsController interface * declared in the Data Laboratory API. * @author Eduardo Ramos <eduramiba@gmail.com> * @see AttributeColumnsController */ @ServiceProvider(service = AttributeColumnsController.class) public class AttributeColumnsControllerImpl implements AttributeColumnsController { public boolean setAttributeValue(Object value, Attributes row, AttributeColumn column) { AttributeType targetType = column.getType(); if (value != null && !value.getClass().equals(targetType.getType())) { try { value = targetType.parse(value.toString());//Try to convert to target type } catch (Exception ex) { value = null;//Could not parse } } if (value == null && !canClearColumnData(column)) { return false;//Do not set a null value when the column can't have a null value. } else { row.setValue(column.getIndex(), value); return true; } } public AttributeColumn addAttributeColumn(AttributeTable table, String title, AttributeType type) { if (title == null || title.isEmpty()) { return null; } if (table.hasColumn(title)) { return null; } if (type == AttributeType.TIME_INTERVAL && table.getColumn(DynamicModel.TIMEINTERVAL_COLUMN) == null) { return table.addColumn(DynamicModel.TIMEINTERVAL_COLUMN, title, type, AttributeOrigin.PROPERTY, null); } return table.addColumn(title, title, type, AttributeOrigin.DATA, null); } public void deleteAttributeColumn(AttributeTable table, AttributeColumn column) { if (canDeleteColumn(column)) { table.removeColumn(column); } } public AttributeColumn duplicateColumn(AttributeTable table, AttributeColumn column, String title, AttributeType type) { AttributeColumn newColumn = addAttributeColumn(table, title, type); if (newColumn == null) { return null; } copyColumnDataToOtherColumn(table, column, newColumn); return newColumn; } public void copyColumnDataToOtherColumn(AttributeTable table, AttributeColumn sourceColumn, AttributeColumn targetColumn) { if (sourceColumn == targetColumn) { throw new IllegalArgumentException("Source and target columns can't be equal"); } final int sourceColumnIndex = sourceColumn.getIndex(); final int targetColumnIndex = targetColumn.getIndex(); AttributeType targetType = targetColumn.getType(); if (targetType != sourceColumn.getType()) { Object value; for (Attributes row : getTableAttributeRows(table)) { value = row.getValue(sourceColumnIndex); setAttributeValue(value, row, targetColumn); } } else { for (Attributes row : getTableAttributeRows(table)) { row.setValue(targetColumnIndex, row.getValue(sourceColumnIndex)); } } } public void fillColumnWithValue(AttributeTable table, AttributeColumn column, String value) { if (canChangeColumnData(column)) { for (Attributes row : getTableAttributeRows(table)) { setAttributeValue(value, row, column); } } } public void clearColumnData(AttributeTable table, AttributeColumn column) { if (canClearColumnData(column)) { final int columnIndex = column.getIndex(); for (Attributes attributes : getTableAttributeRows(table)) { attributes.setValue(columnIndex, null); } } } public Map<Object, Integer> calculateColumnValuesFrequencies(AttributeTable table, AttributeColumn column) { Map<Object, Integer> valuesFrequencies = new HashMap<Object, Integer>(); Object value; for (Attributes row : getTableAttributeRows(table)) { value = row.getValue(column.getIndex()); if (valuesFrequencies.containsKey(value)) { valuesFrequencies.put(value, new Integer(valuesFrequencies.get(value) + 1)); } else { valuesFrequencies.put(value, new Integer(1)); } } return valuesFrequencies; } public AttributeColumn createBooleanMatchesColumn(AttributeTable table, AttributeColumn column, String newColumnTitle, Pattern pattern) { if (pattern != null) { AttributeColumn newColumn = addAttributeColumn(table, newColumnTitle, AttributeType.BOOLEAN); if (newColumn == null) { return null; } Matcher matcher; Object value; for (Attributes row : getTableAttributeRows(table)) { value = row.getValue(column.getIndex()); if (value != null) { matcher = pattern.matcher(value.toString()); } else { matcher = pattern.matcher(""); } row.setValue(newColumn.getIndex(), matcher.matches()); } return newColumn; } else { return null; } } public void negateBooleanColumn(AttributeTable table, AttributeColumn column) { AttributeUtils attributeUtils = AttributeUtils.getDefault(); if (attributeUtils.isColumnOfType(column, AttributeType.BOOLEAN)) { negateColumnBooleanType(table, column); } else if (attributeUtils.isColumnOfType(column, AttributeType.LIST_BOOLEAN)) { negateColumnListBooleanType(table, column); } else { throw new IllegalArgumentException(); } } public AttributeColumn createFoundGroupsListColumn(AttributeTable table, AttributeColumn column, String newColumnTitle, Pattern pattern) { if (pattern != null) { AttributeColumn newColumn = addAttributeColumn(table, newColumnTitle, AttributeType.LIST_STRING); if (newColumn == null) { return null; } Matcher matcher; Object value; ArrayList<String> foundGroups = new ArrayList<String>(); for (Attributes attributes : getTableAttributeRows(table)) { value = attributes.getValue(column.getIndex()); if (value != null) { matcher = pattern.matcher(value.toString()); } else { matcher = pattern.matcher(""); } while (matcher.find()) { foundGroups.add(matcher.group()); } if (foundGroups.size() > 0) { attributes.setValue(newColumn.getIndex(), new StringList(foundGroups.toArray(new String[0]))); foundGroups.clear(); } else { attributes.setValue(newColumn.getIndex(), null); } } return newColumn; } else { return null; } } public void clearNodeData(Node node, AttributeColumn[] columnsToClear) { clearRowData((AttributeRow) node.getNodeData().getAttributes(), columnsToClear); } public void clearNodesData(Node[] nodes, AttributeColumn[] columnsToClear) { for (Node n : nodes) { clearNodeData(n, columnsToClear); } } public void clearEdgeData(Edge edge, AttributeColumn[] columnsToClear) { clearRowData((AttributeRow) edge.getEdgeData().getAttributes(), columnsToClear); } public void clearEdgesData(Edge[] edges, AttributeColumn[] columnsToClear) { for (Edge e : edges) { clearEdgeData(e, columnsToClear); } } public void clearRowData(Attributes row, AttributeColumn[] columnsToClear) { AttributeRow attributeRow = (AttributeRow) row; if (columnsToClear != null) { for (AttributeColumn column : columnsToClear) { //Clear all except id and computed attributes: if (canClearColumnData(column)) { row.setValue(column.getIndex(), null); } } } else { AttributeValue[] values = attributeRow.getValues(); for (int i = 0; i < values.length; i++) { //Clear all except id and computed attributes: if (canClearColumnData(values[i].getColumn())) { row.setValue(i, null); } } } } public void copyNodeDataToOtherNodes(Node node, Node[] otherNodes, AttributeColumn[] columnsToCopy) { Attributes row = node.getNodeData().getAttributes(); Attributes[] otherRows = new Attributes[otherNodes.length]; for (int i = 0; i < otherNodes.length; i++) { otherRows[i] = otherNodes[i].getNodeData().getAttributes(); } copyRowDataToOtherRows(row, otherRows, columnsToCopy); } public void copyEdgeDataToOtherEdges(Edge edge, Edge[] otherEdges, AttributeColumn[] columnsToCopy) { Attributes row = edge.getEdgeData().getAttributes(); Attributes[] otherRows = new Attributes[otherEdges.length]; for (int i = 0; i < otherEdges.length; i++) { otherRows[i] = otherEdges[i].getEdgeData().getAttributes(); } copyRowDataToOtherRows(row, otherRows, columnsToCopy); } public void copyRowDataToOtherRows(Attributes row, Attributes[] otherRows, AttributeColumn[] columnsToCopy) { AttributeRow attributeRow = (AttributeRow) row; if (columnsToCopy != null) { for (AttributeColumn column : columnsToCopy) { //Copy all except id and computed attributes: if (canChangeColumnData(column)) { for (Attributes otherRow : otherRows) { otherRow.setValue(column.getIndex(), row.getValue(column.getIndex())); } } } } else { AttributeColumn column; AttributeValue[] values = attributeRow.getValues(); for (int i = 0; i < values.length; i++) { column = values[i].getColumn(); //Copy all except id and computed attributes: if (canChangeColumnData(column)) { for (Attributes otherRow : otherRows) { otherRow.setValue(column.getIndex(), row.getValue(column.getIndex())); } } } } } public Attributes[] getTableAttributeRows(AttributeTable table) { Attributes[] attributes; if (isNodeTable(table)) { Node[] nodes = getNodesArray(); attributes = new Attributes[nodes.length]; for (int i = 0; i < nodes.length; i++) { attributes[i] = nodes[i].getNodeData().getAttributes(); } } else { Edge[] edges = getEdgesArray(); attributes = new Attributes[edges.length]; for (int i = 0; i < edges.length; i++) { attributes[i] = edges[i].getEdgeData().getAttributes(); } } return attributes; } public int getTableRowsCount(AttributeTable table) { if (isNodeTable(table)) { return Lookup.getDefault().lookup(GraphElementsController.class).getNodesCount(); } else { return Lookup.getDefault().lookup(GraphElementsController.class).getEdgesCount(); } } public boolean isNodeTable(AttributeTable table) { AttributeController ac = Lookup.getDefault().lookup(AttributeController.class); return table == ac.getModel().getNodeTable(); } public boolean isEdgeTable(AttributeTable table) { AttributeController ac = Lookup.getDefault().lookup(AttributeController.class); return table == ac.getModel().getEdgeTable(); } public boolean canDeleteColumn(AttributeColumn column) { return column.getOrigin() != AttributeOrigin.PROPERTY; } public boolean canChangeColumnData(AttributeColumn column) { AttributeUtils au = Lookup.getDefault().lookup(AttributeUtils.class); if (au.isNodeColumn(column)) { return canChangeGenericColumnData(column) && column.getIndex() != PropertiesColumn.NODE_ID.getIndex(); } else if (au.isEdgeColumn(column)) { return canChangeGenericColumnData(column) && column.getIndex() != PropertiesColumn.EDGE_ID.getIndex(); } else { return canChangeGenericColumnData(column); } } public boolean canClearColumnData(AttributeColumn column) { AttributeUtils au = Lookup.getDefault().lookup(AttributeUtils.class); if (au.isNodeColumn(column)) { return canChangeGenericColumnData(column) && column.getIndex() != PropertiesColumn.NODE_ID.getIndex(); } else if (au.isEdgeColumn(column)) { return canChangeGenericColumnData(column) && column.getIndex() != PropertiesColumn.EDGE_ID.getIndex() && column.getIndex() != PropertiesColumn.EDGE_WEIGHT.getIndex(); } else { return canChangeGenericColumnData(column); } } public BigDecimal[] getNumberOrNumberListColumnStatistics(AttributeTable table, AttributeColumn column) { return StatisticsUtils.getAllStatistics(getColumnNumbers(table, column)); } public Number[] getColumnNumbers(AttributeTable table, AttributeColumn column) { AttributeUtils attributeUtils = AttributeUtils.getDefault(); if (!attributeUtils.isNumberOrNumberListColumn(column)) { throw new IllegalArgumentException("The column has to be a number or number list column"); } ArrayList<Number> numbers = new ArrayList<Number>(); final int columnIndex = column.getIndex(); Number number; if (attributeUtils.isNumberColumn(column)) {//Number column for (Attributes row : getTableAttributeRows(table)) { number = (Number) row.getValue(columnIndex); if (number != null) { numbers.add(number); } } } else {//Number list column for (Attributes row : getTableAttributeRows(table)) { numbers.addAll(getNumberListColumnNumbers(row, column)); } } return numbers.toArray(new Number[0]); } public Number[] getRowNumbers(Attributes row, AttributeColumn[] columns) { AttributeUtils attributeUtils = AttributeUtils.getDefault(); checkColumnsAreNumberOrNumberList(columns); ArrayList<Number> numbers = new ArrayList<Number>(); Number number; for (AttributeColumn column : columns) { if (attributeUtils.isNumberColumn(column)) {//Single number column: number = (Number) row.getValue(column.getIndex()); if (number != null) { numbers.add(number); } } else if (attributeUtils.isNumberListColumn(column)) {//Number list column: numbers.addAll(getNumberListColumnNumbers(row, column)); } else if (attributeUtils.isDynamicNumberColumn(column)) {//Dynamic number column numbers.addAll(getDynamicNumberColumnNumbers(row, column)); } } return numbers.toArray(new Number[0]); } public void importCSVToNodesTable(File file, Character separator, Charset charset, String[] columnNames, AttributeType[] columnTypes, boolean assignNewNodeIds) { if (columnNames == null || columnNames.length == 0) { return; } if (columnTypes == null || columnNames.length != columnTypes.length) { throw new IllegalArgumentException("Column names length must be the same as column types lenght"); } CsvReader reader = null; try { //Prepare attribute columns for the column names, creating the not already existing columns: AttributeTable nodesTable = Lookup.getDefault().lookup(AttributeController.class).getModel().getNodeTable(); String idColumn = null; ArrayList<AttributeColumn> columnsList = new ArrayList<AttributeColumn>(); for (int i = 0; i < columnNames.length; i++) { //Separate first id column found from the list to use as id. If more are found later, the will not be in the list and be ignored. if (columnNames[i].equalsIgnoreCase("id")) { if (idColumn == null) { idColumn = columnNames[i]; } } else if (nodesTable.hasColumn(columnNames[i])) { columnsList.add(nodesTable.getColumn(columnNames[i])); } else { columnsList.add(addAttributeColumn(nodesTable, columnNames[i], columnTypes[i])); } } //Create nodes: GraphElementsController gec = Lookup.getDefault().lookup(GraphElementsController.class); Graph graph = Lookup.getDefault().lookup(GraphController.class).getModel().getGraph(); String id = null; Node node; Attributes nodeAttributes; reader = new CsvReader(new FileInputStream(file), separator, charset); reader.readHeaders(); while (reader.readRecord()) { //Prepare the correct node to assign the attributes: if (idColumn != null) { id = reader.get(idColumn); if (id == null || id.isEmpty()) { node = gec.createNode(null);//id null or empty, assign one } else { graph.readLock(); node = graph.getNode(id); graph.readUnlock(); if (node != null) {//Node with that id already in graph if (assignNewNodeIds) { node = gec.createNode(null); } } else { node = gec.createNode(null, id);//New id in the graph } } } else { node = gec.createNode(null); } //Assign attributes to the current node: nodeAttributes = node.getNodeData().getAttributes(); for (AttributeColumn column : columnsList) { setAttributeValue(reader.get(column.getTitle()), nodeAttributes, column); } } } catch (FileNotFoundException ex) { Exceptions.printStackTrace(ex); } catch (IOException ex) { Exceptions.printStackTrace(ex); } finally { reader.close(); } } public void importCSVToEdgesTable(File file, Character separator, Charset charset, String[] columnNames, AttributeType[] columnTypes, boolean createNewNodes) { if (columnNames == null || columnNames.length == 0) { return; } if (columnTypes == null || columnNames.length != columnTypes.length) { throw new IllegalArgumentException("Column names length must be the same as column types lenght"); } CsvReader reader = null; try { //Prepare attribute columns for the column names, creating the not already existing columns: AttributeTable edges = Lookup.getDefault().lookup(AttributeController.class).getModel().getEdgeTable(); String idColumn = null; String sourceColumn = null; String targetColumn = null; String typeColumn = null; ArrayList<AttributeColumn> columnsList = new ArrayList<AttributeColumn>(); for (int i = 0; i < columnNames.length; i++) { //Separate first id column found from the list to use as id. If more are found later, the will not be in the list and be ignored. if (columnNames[i].equalsIgnoreCase("id")) { if (idColumn == null) { idColumn = columnNames[i]; } } else if (columnNames[i].equalsIgnoreCase("source") && sourceColumn == null) {//Separate first source column found from the list to use as source node id sourceColumn = columnNames[i]; } else if (columnNames[i].equalsIgnoreCase("target") && targetColumn == null) {//Separate first target column found from the list to use as target node id targetColumn = columnNames[i]; } else if (columnNames[i].equalsIgnoreCase("type") && typeColumn == null) {//Separate first type column found from the list to use as edge type (directed/undirected) typeColumn = columnNames[i]; } else if (edges.hasColumn(columnNames[i])) { columnsList.add(edges.getColumn(columnNames[i])); } else { columnsList.add(addAttributeColumn(edges, columnNames[i], columnTypes[i])); } } //Create edges: GraphElementsController gec = Lookup.getDefault().lookup(GraphElementsController.class); Graph graph = Lookup.getDefault().lookup(GraphController.class).getModel().getGraph(); String id = null; Edge edge; String sourceId, targetId; Node source, target; String type; boolean directed; Attributes edgeAttributes; reader = new CsvReader(new FileInputStream(file), separator, charset); reader.readHeaders(); while (reader.readRecord()) { sourceId = reader.get(sourceColumn); targetId = reader.get(targetColumn); if (sourceId == null || sourceId.isEmpty() || targetId == null || targetId.isEmpty()) { continue;//No correct source and target ids were provided, ignore row } graph.readLock(); source = graph.getNode(sourceId); graph.readUnlock(); if (source == null) { if (createNewNodes) {//Create new nodes when they don't exist already and option is enabled if (source == null) { source = gec.createNode(null, sourceId); } } else { continue;//Ignore this edge row, since no new nodes should be created. } } graph.readLock(); target = graph.getNode(targetId); graph.readUnlock(); if (target == null) { if (createNewNodes) {//Create new nodes when they don't exist already and option is enabled if (target == null) { target = gec.createNode(null, targetId); } } else { continue;//Ignore this edge row, since no new nodes should be created. } } if (typeColumn != null) { type = reader.get(typeColumn); //Undirected if indicated correctly, otherwise always directed: if (type != null) { directed = !type.equalsIgnoreCase("undirected"); } else { directed = true; } } else { directed = true;//Directed by default when not indicated } //Prepare the correct edge to assign the attributes: if (idColumn != null) { id = reader.get(idColumn); if (id == null || id.isEmpty()) { edge = gec.createEdge(source, target, directed);//id null or empty, assign one } else { edge = gec.createEdge(id, source, target, directed); if (edge == null) {//Edge with that id already in graph edge = gec.createEdge(source, target, directed); } } } else { edge = gec.createEdge(source, target, directed); } if (edge != null) {//Edge could be created because it does not already exist: //Assign attributes to the current edge: edgeAttributes = edge.getEdgeData().getAttributes(); for (AttributeColumn column : columnsList) { setAttributeValue(reader.get(column.getTitle()), edgeAttributes, column); } } } } catch (FileNotFoundException ex) { Exceptions.printStackTrace(ex); } catch (IOException ex) { Exceptions.printStackTrace(ex); } finally { reader.close(); } } /************Private methods : ************/ /** * Used for iterating through all nodes of the graph * @return Array with all graph nodes */ private Node[] getNodesArray() { return Lookup.getDefault().lookup(GraphController.class).getModel().getHierarchicalGraph().getNodesTree().toArray(); } /** * Used for iterating through all edges of the graph * @return Array with all graph edges */ private Edge[] getEdgesArray() { return Lookup.getDefault().lookup(GraphController.class).getModel().getHierarchicalGraph().getEdges().toArray(); } /** * Only checks that a column is not <code>COMPUTED</code> or <code>DELEGATE</code> */ private boolean canChangeGenericColumnData(AttributeColumn column) { return column.getOrigin() != AttributeOrigin.COMPUTED && column.getOrigin() != AttributeOrigin.DELEGATE; } /** * Used to negate the values of a single boolean column. */ private void negateColumnBooleanType(AttributeTable table, AttributeColumn column) { final int columnIndex = column.getIndex(); Object value; Boolean newValue; for (Attributes row : getTableAttributeRows(table)) { value = row.getValue(columnIndex); if (value != null) { newValue = !((Boolean) value); row.setValue(columnIndex, newValue); } } } /** * Used to negate all values of a list of boolean values column. */ private void negateColumnListBooleanType(AttributeTable table, AttributeColumn column) { final int columnIndex = column.getIndex(); Object value; BooleanList list; Boolean[] newValues; for (Attributes row : getTableAttributeRows(table)) { value = row.getValue(columnIndex); if (value != null) { list = (BooleanList) value; newValues = new Boolean[list.size()]; for (int i = 0; i < list.size(); i++) { newValues[i] = !list.getItem(i); } row.setValue(columnIndex, new BooleanList(newValues)); } } } /** * Used for obtaining a list of the numbers of row of a number list column. */ private ArrayList<Number> getNumberListColumnNumbers(Attributes row, AttributeColumn column) { if (!AttributeUtils.getDefault().isNumberListColumn(column)) { throw new IllegalArgumentException("Column must be a number list column"); } ArrayList<Number> numbers = new ArrayList<Number>(); NumberList list = (NumberList) row.getValue(column.getIndex()); if (list == null) { return numbers; } Number n; for (int i = 0; i < list.size(); i++) { n = (Number) list.getItem(i); if (n != null) { numbers.add((Number) n); } } return numbers; } /** * Used for obtaining a list of the numbers of row of a dynamic number column. */ private ArrayList<Number> getDynamicNumberColumnNumbers(Attributes row, AttributeColumn column) { if (!AttributeUtils.getDefault().isDynamicNumberColumn(column)) { throw new IllegalArgumentException("Column must be a dynamic number column"); } ArrayList<Number> numbers = new ArrayList<Number>(); DynamicType dynamicList = (DynamicType) row.getValue(column.getIndex()); if (dynamicList == null) { return numbers; } Number[] dynamicNumbers; dynamicNumbers = (Number[]) dynamicList.getValues().toArray(new Number[0]); Number n; for (int i = 0; i < dynamicNumbers.length; i++) { n = (Number) dynamicNumbers[i]; if (n != null) { numbers.add((Number) n); } } return numbers; } private void checkColumnsAreNumberOrNumberList(AttributeColumn[] columns) { if (columns == null || (!AttributeUtils.getDefault().areAllNumberOrNumberListColumns(columns) && !AttributeUtils.getDefault().areAllDynamicNumberColumns(columns))) { throw new IllegalArgumentException("All columns have to be number or number list columns and can't be null"); } } }