/* ========================
* JSynoptic : a free Synoptic editor
* ========================
*
* Project Info: http://jsynoptic.sourceforge.net/index.html
*
* This program is free software; you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Foundation;
* either version 2.1 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*
* (C) Copyright 2001-2003, by :
* Corporate:
* Astrium SAS
* EADS CRC
* Individual:
* Nicolas Brodu
*
* $Id: SourceCategoryDataset.java,v 1.4 2006/02/15 18:29:06 cazenave Exp $
*
* Changes
* -------
* 05-Nov-2003 : Initial version (NB);
*
*/
package jsynoptic.plugins.jfreechart;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import org.jfree.data.AbstractDataset;
import org.jfree.data.CategoryDataset;
import simtools.data.DataException;
import simtools.data.DataInfo;
import simtools.data.DataSource;
import simtools.data.DataSourceCollection;
import simtools.data.DataSourceListener;
import simtools.data.DataSourcePool;
import simtools.data.EndNotificationListener;
import simtools.data.UnsupportedOperation;
/**
* A JFreeChart category data set, using data sources.
*/
public class SourceCategoryDataset extends AbstractDataset implements CategoryDataset, DataSourceListener, CategoryClassifierListener, EndNotificationListener {
static final long serialVersionUID = 4732659452902199039L;
public class SourceHolder implements Comparable {
public DataSource source;
public long startIndex;
/** Name override. Null => use default*/
public String name;
/** How many data source entries in each category */
protected int[] categoryCount;
/** When set to false, categoryCount is recomputed on demand */
public boolean needCompute;
public SourceHolder(DataSource ds) {
this(ds,0);
}
public SourceHolder(DataSource ds, long idx) {
source = ds; startIndex = idx;
needCompute = true;
}
public SourceHolder(DataSource ds, long idx, String name, int[] categoryCount, boolean needCompute) {
this.name = name;
source = ds; startIndex = idx;
this.needCompute = needCompute;
this.categoryCount = categoryCount;
}
/* (non-Javadoc)
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
public int compareTo(Object o) {
SourceHolder sh = (SourceHolder)o; // throws ClassCastException, but that's OK
// compare our respective index in outer vector
return SourceCategoryDataset.this.sources.indexOf(this) - SourceCategoryDataset.this.sources.indexOf(o);
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
public String toString() {
if (name!=null) return name;
String label = DataInfo.getLabel(source);
if (label!=null) return label;
return super.toString();
}
public int getCategoryValue(String category) {
return getCategoryValue(SourceCategoryDataset.this.classifier.categories.indexOf(category));
}
public int getCategoryValue(int category) {
if (needCompute) compute();
return categoryCount[category];
}
/** Compute the number of data source entries in each catagory */
public void compute() {
reset();
try {
startIndex = source.computeStartIndex();
} catch (UnsupportedOperation e1) {
try {
startIndex = source.getStartIndex();
} catch (UnsupportedOperation e) {
startIndex = 0;
}
}
update();
needCompute = false;
}
public void reset() {
List list = SourceCategoryDataset.this.classifier.categories;
categoryCount = new int[list.size()]; // starts at 0, init by JVM
}
public void update() {
List list = SourceCategoryDataset.this.classifier.categories;
for (long l = startIndex; ;++l) {
Object o;
try {
o = source.getValue(l);
} catch (DataException e) {
// stop when there is no more value
startIndex = l; // And will start from here again at next update()
break;
}
String s = SourceCategoryDataset.this.classifier.getMapper().getString(o);
// Value not taken in account if it does not belong to a category
if (s==null) continue;
int idx = list.indexOf(s);
if (idx==-1) continue;
categoryCount[idx]++;
}
}
}
protected transient Vector sources = new Vector(); // of Holders
protected transient ArrayList holdersToUpdate = null; // of Holders
protected transient boolean dirty = false;
protected CategoryClassifier classifier;
protected boolean notifySourceChange = false;
protected DataInfo info = null;
/**
* @return Returns the mapper.
*/
public CategoryClassifier getClassifier() {
return classifier;
}
/**
* @param mapper The mapper to set.
*/
public void setClassifier(CategoryClassifier classifier) {
if (this.classifier!=null) this.classifier.removeListener(this);
this.classifier = classifier;
if (this.classifier!=null) this.classifier.addListener(this);
fireDatasetChanged();
}
public SourceCategoryDataset() {
notifySourceChange = true;
}
public SourceCategoryDataset(DataSourceCollection dsc) {
setDataSourceCollection(dsc);
notifySourceChange = true;
}
public void setDataSourceCollection(DataSourceCollection dsc) {
boolean notify = notifySourceChange;
notifySourceChange = false;
clear();
info = dsc.getInformation();
for (int i=0; i<dsc.size(); ++i) {
addSource((DataSource)dsc.get(i));
}
notifySourceChange = notify;
if (notifySourceChange) fireDatasetChanged();
}
public void addDataSourceCollection(DataSourceCollection dsc) {
boolean notify = notifySourceChange;
notifySourceChange = false;
for (int i=0; i<dsc.size(); ++i) {
addSource((DataSource)dsc.get(i));
}
notifySourceChange = notify;
if (notifySourceChange) fireDatasetChanged();
}
public DataSource getSource(int i) {
return ((SourceHolder)sources.get(i)).source;
}
public void addSource(DataSource ds) {
addSource(ds,0);
}
public void addSource(DataSource ds, long index) {
if (ds==null) return;
sources.add(new SourceHolder(ds, index));
ds.addListener(this);
ds.addEndNotificationListener(this);
if (notifySourceChange) fireDatasetChanged();
}
public void removeSource(DataSource ds) {
if (ds==null) return;
ds.removeListener(this);
ds.removeEndNotificationListener(this);
for (Iterator it = sources.iterator(); it.hasNext();) {
SourceHolder sh = (SourceHolder)it.next();
if ((sh.source!=null) && sh.source.equals(ds)) {
sh.source.removeListener(this);
sh.source.removeEndNotificationListener(this);
it.remove();
}
}
if (notifySourceChange) fireDatasetChanged();
}
public void removeSource(int i) {
SourceHolder sh = (SourceHolder)sources.get(i);
if (sh.source!=null) {
sh.source.removeListener(this);
sh.source.removeEndNotificationListener(this);
}
sources.remove(i);
if (notifySourceChange) fireDatasetChanged();
}
public void clear() {
for (Iterator it = sources.iterator(); it.hasNext();) {
SourceHolder sh = (SourceHolder)it.next();
if (sh.source!=null) {
sh.source.removeListener(this);
sh.source.removeEndNotificationListener(this);
}
it.remove();
}
info = null;
if (notifySourceChange) fireDatasetChanged();
}
/** Changes the name of the source entry at index i, overrides the default.
* You can revert to the default name by setting the override to null
* @param series the series for which to set a name override
* @param name the new name
*/
public void setName(int series, String name) {
SourceHolder sh = (SourceHolder)sources.get(series);
sh.name = name;
}
public String getName(int series) {
SourceHolder sh = (SourceHolder)sources.get(series);
return sh.toString();
}
public void setName(String name) {
if (info==null) info = new DataInfo(name);
else info.label = name;
}
public String getName() {
if (info==null) return "";
return info.label;
}
// Category data set methods
/* (non-Javadoc)
* @see org.jfree.data.KeyedValues2D#getRowKey(int)
*/
public Comparable getRowKey(int row) {
return (Comparable)sources.get(row);
}
/* (non-Javadoc)
* @see org.jfree.data.KeyedValues2D#getRowIndex(java.lang.Comparable)
*/
public int getRowIndex(Comparable key) {
return sources.indexOf(key);
}
/* (non-Javadoc)
* @see org.jfree.data.KeyedValues2D#getRowKeys()
*/
public List getRowKeys() {
return sources;
}
/* (non-Javadoc)
* @see org.jfree.data.KeyedValues2D#getColumnKey(int)
*/
public Comparable getColumnKey(int column) {
if (classifier==null) return null;
return (Comparable)classifier.categories.get(column);
}
/* (non-Javadoc)
* @see org.jfree.data.KeyedValues2D#getColumnIndex(java.lang.Comparable)
*/
public int getColumnIndex(Comparable key) {
if (classifier==null) return -1;
return classifier.categories.indexOf(key);
}
/* (non-Javadoc)
* @see org.jfree.data.KeyedValues2D#getColumnKeys()
*/
public List getColumnKeys() {
if (classifier==null) return new Vector(); // empty
return classifier.categories;
}
/* (non-Javadoc)
* @see org.jfree.data.KeyedValues2D#getValue(java.lang.Comparable, java.lang.Comparable)
*/
public Number getValue(Comparable rowKey, Comparable columnKey) {
return new Integer(((SourceHolder)rowKey).getCategoryValue((String)columnKey));
}
/* (non-Javadoc)
* @see org.jfree.data.Values2D#getRowCount()
*/
public int getRowCount() {
return sources.size();
}
/* (non-Javadoc)
* @see org.jfree.data.Values2D#getColumnCount()
*/
public int getColumnCount() {
if (classifier==null) return 0;
return classifier.categories.size();
}
/* (non-Javadoc)
* @see org.jfree.data.Values2D#getValue(int, int)
*/
public Number getValue(int row, int column) {
return new Integer(((SourceHolder)sources.get(row)).getCategoryValue(column));
}
// Bridge between listeners from different worlds
/* (non-Javadoc)
* @see simtools.data.DataSourceListener#DataSourceValueChanged(simtools.data.DataSource, long, long)
*/
public void DataSourceValueChanged(DataSource ds, long minIndex, long maxIndex) {
for (Iterator it = sources.iterator(); it.hasNext();) {
SourceHolder sh = (SourceHolder)it.next();
if (ds.equals(sh.source)) {
if (sh.startIndex > minIndex) sh.needCompute = true; // lazy update
else {
if (holdersToUpdate == null) holdersToUpdate = new ArrayList();
holdersToUpdate.add(sh);
}
dirty = true;
break;
}
}
}
/* (non-Javadoc)
* @see simtools.data.DataSourceListener#DataSourceIndexRangeChanged(simtools.data.DataSource, long, long)
*/
public void DataSourceIndexRangeChanged(DataSource ds, long startIndex, long lastIndex) {
for (Iterator it = sources.iterator(); it.hasNext();) {
SourceHolder sh = (SourceHolder)it.next();
if (ds.equals(sh.source)) {
// if start index outside range, restart computations
if ((sh.startIndex > lastIndex) || (sh.startIndex < startIndex)) {
sh.reset();
sh.startIndex = startIndex;
}
// Otherwise, just update with values not taken in account
if (holdersToUpdate == null) holdersToUpdate = new ArrayList();
holdersToUpdate.add(sh);
dirty = true;
break;
}
}
}
/* (non-Javadoc)
* @see simtools.data.DataSourceListener#DataSourceInfoChanged(simtools.data.DataSource, simtools.data.DataInfo)
*/
public void DataSourceInfoChanged(DataSource ds, DataInfo newInfo) {
dirty = true; // update legend
}
/* (non-Javadoc)
* @see simtools.data.DataSourceListener#DataSourceValueRangeChanged(simtools.data.DataSource)
*/
public void DataSourceValueRangeChanged(DataSource ds) {
// don't care, the mapper will do its job
}
/* (non-Javadoc)
* @see simtools.data.DataSourceListener#DataSourceOrderChanged(simtools.data.DataSource, int)
*/
public void DataSourceOrderChanged(DataSource ds, int newOrder) {
// don't care at all
}
/* (non-Javadoc)
* @see simtools.data.DataSourceListener#DataSourceReplaced(simtools.data.DataSource, simtools.data.DataSource)
*/
public void DataSourceReplaced(DataSource oldData, DataSource newData) {
for (int i=0; i<sources.size(); ++i) {
SourceHolder sh = (SourceHolder)sources.get(i);
if(sh.source==oldData){
sh.source=newData;
if(newData!=null){
sh.source.addListener(this);
sh.source.addEndNotificationListener(this);
sh.compute();
}
oldData.removeListener(this);
oldData.removeEndNotificationListener(this);
}
}
}
/* (non-Javadoc)
* @see simtools.data.EndNotificationListener#notificationEnd(java.lang.Object)
*/
public void notificationEnd(Object referer) {
if (dirty) {
if (holdersToUpdate!=null) {
for (Iterator it = holdersToUpdate.iterator(); it.hasNext();) {
SourceHolder sh = (SourceHolder)it.next();
sh.update();
}
holdersToUpdate = null;
}
dirty = false;
fireDatasetChanged();
}
}
/* (non-Javadoc)
* @see jsynoptic.plugins.jfreechart.CategoryClassifierListener#classifierModified(jsynoptic.plugins.jfreechart.CategoryClassifier)
*/
public void classifierModified(CategoryClassifier classifier) {
for (Iterator it = sources.iterator(); it.hasNext();)
((SourceHolder)it.next()).needCompute = true;
fireDatasetChanged();
}
// Take care of serialisation. Special handling for datasources
private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
out.defaultWriteObject();
int n = sources.size();
out.writeInt(n);
for (int i=0; i<n; ++i) {
SourceHolder sh = (SourceHolder)sources.get(i);
DataSourcePool.global.writeDataSource(out, sh.source);
out.writeLong(sh.startIndex);
out.writeObject(sh.name);
}
}
private void readObject(java.io.ObjectInputStream in) throws java.lang.ClassNotFoundException, java.io.IOException {
in.defaultReadObject();
int n =in.readInt();
sources = new Vector();
for (int i=0; i<n; ++i) {
SourceHolder sh;
sources.add(sh = new SourceHolder(
DataSourcePool.global.readDataSource(in),
in.readLong()
));
sh.name = (String)in.readObject();
if (sh.source!=null) {
sh.source.addListener(this);
sh.source.addEndNotificationListener(this);
}
}
}
/* (non-Javadoc)
* @see java.lang.Object#clone()
*/
public Object clone() throws CloneNotSupportedException {
SourceCategoryDataset c = (SourceCategoryDataset)super.clone();
c.sources = new Vector();
c.info = DataInfo.clone(info);
// Deep copy, and correct handling of listeners
for (Iterator it = sources.iterator(); it.hasNext();) {
SourceHolder sh = (SourceHolder)it.next();
c.sources.add(c.new SourceHolder(sh.source, sh.startIndex, sh.name, sh.categoryCount, sh.needCompute));
if (sh.source != null) {
sh.source.addListener(c);
sh.source.addEndNotificationListener(c);
}
}
return c;
}
/**
* @return
*/
public SourceCategoryDataset cloneSet() {
try {
return (SourceCategoryDataset)clone();
} catch (CloneNotSupportedException e) {
return null;
}
}
}