/*
* Copyright 2008-2010 Gephi
* Authors : Cezary Bartosiak
* Mathieu Bastian <mathieu.bastian@gephi.org>
* 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.dynamic;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.gephi.data.attributes.api.AttributeColumn;
import org.gephi.data.attributes.api.AttributeController;
import org.gephi.data.attributes.api.AttributeEvent;
import org.gephi.data.attributes.api.AttributeListener;
import org.gephi.data.attributes.api.AttributeModel;
import org.gephi.data.attributes.api.AttributeUtils;
import org.gephi.data.attributes.api.AttributeValue;
import org.gephi.data.attributes.api.Estimator;
import org.gephi.data.attributes.type.DynamicType;
import org.gephi.data.attributes.type.Interval;
import org.gephi.data.attributes.type.TimeInterval;
import org.gephi.dynamic.api.DynamicGraph;
import org.gephi.dynamic.api.DynamicModel;
import org.gephi.dynamic.api.DynamicModelEvent;
import org.gephi.filters.api.FilterController;
import org.gephi.filters.api.FilterModel;
import org.gephi.filters.api.Query;
import org.gephi.filters.plugin.dynamic.DynamicRangeBuilder;
import org.gephi.filters.plugin.dynamic.DynamicRangeBuilder.DynamicRangeFilter;
import org.gephi.filters.spi.FilterBuilder;
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.GraphEvent;
import org.gephi.graph.api.GraphListener;
import org.gephi.graph.api.GraphModel;
import org.gephi.graph.api.Node;
import org.gephi.project.api.Workspace;
import org.openide.util.Lookup;
/**
* The default implementation of {@code DynamicModel}.
*
* @author Cezary Bartosiak
* @author Mathieu Bastian
*/
public final class DynamicModelImpl implements DynamicModel {
protected final DynamicControllerImpl controller;
private final FilterController filterController;
private final DynamicIndex timeIntervalIndex;
private final GraphModel graphModel;
private final AttributeModel attributeModel;
private final FilterModel filterModel;
private final List<AttributeColumn> nodeDynamicColumns;
private final List<AttributeColumn> edgeDynamicColumns;
//Variables
private TimeInterval visibleTimeInterval;
private TimeFormat timeFormat;
private Estimator estimator = Estimator.FIRST;
private Estimator numberEstimator = Estimator.AVERAGE;
/**
* The default constructor.
*
* @param workspace workspace related to this model
*
* @throws NullPointerException if {@code workspace} is null.
*/
public DynamicModelImpl(DynamicControllerImpl controller, Workspace workspace) {
this(controller, workspace, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
}
/**
* Constructs a new {@code DynamicModel} for the {@code workspace}.
*
* @param workspace workspace related to this model
* @param low the left endpoint of the visible time interval
* @param high the right endpoint of the visible time interval
*
* @throws NullPointerException if {@code workspace} is null or the graph model
* and/or its underlying graph are nulls.
*/
public DynamicModelImpl(DynamicControllerImpl controller, Workspace workspace, double low, double high) {
if (workspace == null) {
throw new NullPointerException("The workspace cannot be null.");
}
this.timeFormat = TimeFormat.DOUBLE;
this.controller = controller;
graphModel = Lookup.getDefault().lookup(GraphController.class).getModel(workspace);
if (graphModel == null || graphModel.getGraph() == null) {
throw new NullPointerException("The graph model and its underlying graph cannot be nulls.");
}
filterController = Lookup.getDefault().lookup(FilterController.class);
filterModel = filterController.getModel(workspace);
if (filterModel == null) {
throw new NullPointerException("The filter model.");
}
//Index intervals
timeIntervalIndex = new DynamicIndex(this);
attributeModel = Lookup.getDefault().lookup(AttributeController.class).getModel(workspace);
nodeDynamicColumns = Collections.synchronizedList(new ArrayList<AttributeColumn>());
edgeDynamicColumns = Collections.synchronizedList(new ArrayList<AttributeColumn>());
refresh();
//Visible interval
visibleTimeInterval = new TimeInterval(timeIntervalIndex.getMin(), timeIntervalIndex.getMax());
//AttUtils
final AttributeUtils attUtils = AttributeUtils.getDefault();
//Listen columns
AttributeListener attributeListener = new AttributeListener() {
@Override
public void attributesChanged(AttributeEvent event) {
switch (event.getEventType()) {
case ADD_COLUMN:
AttributeColumn[] addedColumns = event.getData().getAddedColumns();
for (int i = 0; i < addedColumns.length; i++) {
AttributeColumn col = addedColumns[i];
if (col.getType().isDynamicType() && attUtils.isNodeColumn(col)) {
nodeDynamicColumns.add(col);
} else if (col.getType().isDynamicType() && attUtils.isEdgeColumn(col)) {
edgeDynamicColumns.add(col);
}
}
break;
case REMOVE_COLUMN:
AttributeColumn[] removedColumns = event.getData().getRemovedColumns();
for (int i = 0; i < removedColumns.length; i++) {
AttributeColumn col = removedColumns[i];
if (col.getType().isDynamicType() && attUtils.isNodeColumn(col)) {
nodeDynamicColumns.remove(col);
} else if (col.getType().isDynamicType() && attUtils.isEdgeColumn(col)) {
edgeDynamicColumns.remove(col);
}
}
break;
case SET_VALUE:
AttributeValue[] values = event.getData().getTouchedValues();
for (int i = 0; i < values.length; i++) {
AttributeValue val = values[i];
if (val.getValue() != null) {
AttributeColumn col = values[i].getColumn();
if (col.getType().isDynamicType()) {
DynamicType<?> dynamicType = (DynamicType) val.getValue();
for (Interval interval : dynamicType.getIntervals(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY)) {
timeIntervalIndex.add(interval);
}
}
}
}
break;
default:
break;
}
}
};
attributeModel.addAttributeListener(attributeListener);
GraphListener graphListener = new GraphListener() {
@Override
public void graphChanged(GraphEvent event) {
if (event.getSource().isMainView()) {
switch (event.getEventType()) {
case REMOVE_EDGES:
if (!edgeDynamicColumns.isEmpty()) {
AttributeColumn[] dynamicCols = edgeDynamicColumns.toArray(new AttributeColumn[0]);
for (Edge e : event.getData().removedEdges()) {
Attributes attributeRow = e.getEdgeData().getAttributes();
for (int i = 0; i < dynamicCols.length; i++) {
DynamicType<?> ti = (DynamicType) attributeRow.getValue(dynamicCols[i].getIndex());
if (ti != null) {
for (Interval interval : ti.getIntervals(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY)) {
timeIntervalIndex.remove(interval);
}
}
}
}
}
break;
case REMOVE_NODES:
if (!nodeDynamicColumns.isEmpty()) {
AttributeColumn[] dynamicCols = edgeDynamicColumns.toArray(new AttributeColumn[0]);
for (Node n : event.getData().removedNodes()) {
Attributes attributeRow = n.getNodeData().getAttributes();
for (int i = 0; i < dynamicCols.length; i++) {
DynamicType<?> ti = (DynamicType) attributeRow.getValue(dynamicCols[i].getIndex());
if (ti != null) {
for (Interval interval : ti.getIntervals(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY)) {
timeIntervalIndex.remove(interval);
}
}
}
}
}
break;
default:
break;
}
}
}
};
graphModel.addGraphListener(graphListener);
}
private void refresh() {
timeIntervalIndex.clear();
for (AttributeColumn col : attributeModel.getNodeTable().getColumns()) {
if (col.getType().isDynamicType()) {
nodeDynamicColumns.add(col);
}
}
AttributeColumn[] dynamicCols = nodeDynamicColumns.toArray(new AttributeColumn[0]);
if (dynamicCols.length > 0) {
Graph graph = graphModel.getGraph();
for (Node n : graph.getNodes()) {
Attributes attributeRow = n.getNodeData().getAttributes();
for (int i = 0; i < dynamicCols.length; i++) {
DynamicType<?> ti = (DynamicType) attributeRow.getValue(dynamicCols[i].getIndex());
if (ti != null) {
for (Interval interval : ti.getIntervals(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY)) {
timeIntervalIndex.add(interval);
}
}
}
}
}
for (AttributeColumn col : attributeModel.getNodeTable().getColumns()) {
if (col.getType().isDynamicType()) {
edgeDynamicColumns.add(col);
}
}
dynamicCols = nodeDynamicColumns.toArray(new AttributeColumn[0]);
if (dynamicCols.length > 0) {
Graph graph = graphModel.getGraph();
for (Edge e : graph.getEdges()) {
Attributes attributeRow = e.getEdgeData().getAttributes();
for (int i = 0; i < dynamicCols.length; i++) {
DynamicType<?> ti = (DynamicType) attributeRow.getValue(dynamicCols[i].getIndex());
if (ti != null) {
for (Interval interval : ti.getIntervals(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY)) {
timeIntervalIndex.add(interval);
}
}
}
}
}
}
@Override
public DynamicGraph createDynamicGraph(Graph graph) {
return new DynamicGraphImpl(graph);
}
@Override
public DynamicGraph createDynamicGraph(Graph graph, TimeInterval interval) {
return new DynamicGraphImpl(graph, interval.getLow(), interval.getHigh());
}
@Override
public TimeInterval getVisibleInterval() {
return visibleTimeInterval;
}
public void setVisibleTimeInterval(TimeInterval visibleTimeInterval) {
if (!Double.isNaN(visibleTimeInterval.getLow()) && !Double.isNaN(visibleTimeInterval.getHigh()) && !this.visibleTimeInterval.equals(visibleTimeInterval)) {
this.visibleTimeInterval = visibleTimeInterval;
//Filters
Query dynamicQuery = null;
boolean selecting = false;
//Get or create Dynamic Query
if (filterModel.getCurrentQuery() != null) {
//Look if current query is dynamic - filtering must be active
Query query = filterModel.getCurrentQuery();
Query[] dynamicQueries = query.getQueries(DynamicRangeFilter.class);
if (dynamicQueries.length > 0) {
dynamicQuery = query;
selecting = filterModel.isSelecting();
}
} else if (filterModel.getQueries().length == 1) {
//Look if a dynamic query alone exists
Query query = filterModel.getQueries()[0];
Query[] dynamicQueries = query.getQueries(DynamicRangeFilter.class);
if (dynamicQueries.length > 0) {
dynamicQuery = query;
}
}
if (Double.isInfinite(visibleTimeInterval.getLow()) && Double.isInfinite(visibleTimeInterval.getHigh())) {
if (dynamicQuery != null) {
filterController.remove(dynamicQuery);
}
} else {
if (dynamicQuery == null) {
//Create dynamic filter
DynamicRangeBuilder rangeBuilder = filterModel.getLibrary().getLookup().lookup(DynamicRangeBuilder.class);
FilterBuilder[] fb = rangeBuilder.getBuilders();
if (fb.length > 0) {
DynamicRangeFilter filter = (DynamicRangeFilter) fb[0].getFilter();
dynamicQuery = filterController.createQuery(filter);
filterController.add(dynamicQuery);
}
}
if (dynamicQuery != null) {
if (selecting) {
filterController.selectVisible(dynamicQuery);
} else {
filterController.filterVisible(dynamicQuery);
}
}
}
// Trigger Event
controller.fireModelEvent(new DynamicModelEvent(DynamicModelEvent.EventType.VISIBLE_INTERVAL, this, visibleTimeInterval));
}
}
@Override
public boolean isDynamicGraph() {
return !Double.isInfinite(timeIntervalIndex.getMax()) || !Double.isInfinite(timeIntervalIndex.getMin());
}
@Override
public TimeFormat getTimeFormat() {
return timeFormat;
}
public void setTimeFormat(TimeFormat timeFormat) {
this.timeFormat = timeFormat;
}
@Override
public double getMin() {
return timeIntervalIndex.getMin();
}
@Override
public double getMax() {
return timeIntervalIndex.getMax();
}
@Override
public Estimator getEstimator() {
return estimator;
}
@Override
public Estimator getNumberEstimator() {
return numberEstimator;
}
public void setEstimator(Estimator estimator) {
this.estimator = estimator;
}
public void setNumberEstimator(Estimator numberEstimator) {
this.numberEstimator = numberEstimator;
}
}