/* ========================
* 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-2007, by :
* Corporate:
* EADS Astrium
* Individual:
* Claude Cazenave
*
* $Id: DataAnimator.java,v 1.7 2008/11/06 18:11:21 cazenave Exp $
*
* Changes
* -------
* 19 janv. 08 : Initial public release
*
*/
package jsynoptic.plugins.java3d;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import javax.media.j3d.Locale;
import javax.media.j3d.SceneGraphObject;
import simtools.data.DataInfo;
import simtools.data.DataSource;
import simtools.data.DataSourceListener;
import simtools.data.DataSourcePool;
import simtools.data.EndNotificationListener;
import simtools.data.UnsupportedOperation;
import com.sun.j3d.utils.scenegraph.io.SceneGraphStateProvider;
import com.sun.j3d.utils.scenegraph.io.retained.Controller;
import com.sun.j3d.utils.scenegraph.io.retained.SymbolTableData;
import com.sun.j3d.utils.scenegraph.io.state.javax.media.j3d.SceneGraphObjectState;
/**
*/
public abstract class DataAnimator extends Animator implements SceneGraphStateProvider, DirtyNode{
private Class<? extends Data> _dataClass;
private int _dataClassParam;
protected Data _data;
protected Universe _universe;
/**
* Create a SceneGraphData linked with a SceneGraphObject
*/
public DataAnimator() {
this(null,-1);
}
/**
* Create a SceneGraphData linked with a SceneGraphObject
*/
public DataAnimator(Class<? extends Data> dataClass, int dataClassParam) {
_dataClass=dataClass;
_dataClassParam=dataClassParam;
createData();
if(_data!=null){
_data._references=_references;
_data._animator=this;
}
_universe=null;
}
public SceneGraphObject getCapableObject(SceneGraphObject o){
return getData().getCapableObject(o);
}
public Data getData(){
return _data;
}
public boolean isCompatibleWith(Animator an){
if(!super.isCompatibleWith(an)){
return false;
}
if(!_data.getClass().equals(((DataAnimator)an).getData().getClass())){
return false;
}
return _dataClassParam==((DataAnimator)an)._dataClassParam;
}
private void createData() {
if(_dataClass==null){
return;
}
try {
if(_dataClassParam>=0){
Constructor<? extends Data> ctor=_dataClass.getConstructor(int.class);
_data=ctor.newInstance(_dataClassParam);
}
else{
_data=_dataClass.newInstance();
}
} catch (InstantiationException e1) {
e1.printStackTrace();
throw new RuntimeException("Invalid Data class : "+_dataClass.getName(),e1);
} catch (IllegalAccessException e1) {
throw new RuntimeException("Invalid Data class : "+_dataClass.getName(),e1);
} catch (SecurityException e) {
throw new RuntimeException("Invalid Data class : "+_dataClass.getName(),e);
} catch (NoSuchMethodException e) {
throw new RuntimeException("Invalid Data class : "+_dataClass.getName(),e);
} catch (IllegalArgumentException e) {
throw new RuntimeException("Invalid Data class : "+_dataClass.getName(),e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Invalid Data class : "+_dataClass.getName(),e);
}
}
public void setDirty(){
if(_universe==null){
Locale c=getLocale();
if(c!=null){
_universe=(Universe)c.getVirtualUniverse();
_universe.addDirtyNode(this);
_data.setDelegateListener(_universe);
}
}
else{
_universe.addDirtyNode(this);
}
}
@Override
public Class<? extends SceneGraphObjectState> getStateClass() {
return State.class;
}
protected static class SourceHolder {
public DataSource _ds;
public long _index;
public SourceHolder(DataSource ds) {
this._ds = ds;
try {
_index = ds.getLastIndex();
} catch (UnsupportedOperation e) {
try {
_index = ds.computeLastIndex();
} catch (UnsupportedOperation e1) {
_index = -1;
}
}
}
}
public static abstract class Data implements DataSourceListener, EndNotificationListener, Cloneable, Serializable {
static final long serialVersionUID = -6024176491980593280L;
protected transient SourceHolder[] _sources;
private transient ArrayList<GraphObjectReference> _references;
/** @see setDelegateListener */
protected transient EndNotificationListener _delegateListener;
protected transient DataAnimator _animator;
public Data() {
_sources = null;
// lazy creation
_delegateListener = this;
_references=null;
_animator=null;
}
public SceneGraphObject getCapableObject(SceneGraphObject o){
return o;
}
public abstract void set(Values v);
public abstract Values get();
public ArrayList<GraphObjectReference> getReferences(){
return _references;
}
/**
* @return Returns the delegateListener.
*/
public EndNotificationListener getDelegateListener() {
return _delegateListener;
}
/**
* Setting a delegate end notification listener is quite important to avoid duplicate events.
* If the same object listens to multiple SceneGraphData, it will be notified only once.
* The data themselves are of course listeners for the data source they manage.
* They are also the default end notificationlistener when there is no delegation
*
* @param delegateListener The delegateListener to set.
*/
public void setDelegateListener(EndNotificationListener delegateListener) {
if (this._delegateListener == delegateListener)
return; // no change
// unregister old listener
if (_sources != null) {
for (int i = 0; i < _sources.length; ++i)
if ((_sources[i] != null) && (_sources[i]._ds != null))
_sources[i]._ds.removeEndNotificationListener(this._delegateListener);
}
this._delegateListener = delegateListener;
// register new one
if (_sources != null) {
for (int i = 0; i < _sources.length; ++i)
if ((_sources[i] != null) && (_sources[i]._ds != null))
_sources[i]._ds.addEndNotificationListener(delegateListener);
}
}
public void removeSceneGraphData() {
// unregister old listener
if (_sources != null) {
for (int i = 0; i < _sources.length; ++i)
if ((_sources[i] != null) && (_sources[i]._ds != null)) {
_sources[i]._ds.removeEndNotificationListener(this._delegateListener);
_sources[i]._ds.removeListener(this);
_sources[i] = null;
}
}
}
/**
* Get number of fields for this data
*/
public int length() {
if (_sources == null) {
return 0;
}
return _sources.length;
}
/**
* Get data source
* @param index = field number 0..length-1
*/
public DataSource getDataSource(int index) {
if (_sources[index] == null)
return null;
return _sources[index]._ds;
}
/**
* Get data sources
*/
public DataSource[] getDataSources() {
if(_sources==null){
return new DataSource[0];
}
DataSource[] res=new DataSource[_sources.length];
int i=0;
for(SourceHolder sh : _sources){
if(sh != null){
res[i]=sh._ds;
}
else{
res[i]=null;
}
i++;
}
return res;
}
/**
* Set data source
* @param index = field number 0..length-1
* @param d new value for the DataSource
*/
public void setDataSource(int index, DataSource d) {
if (d == null) {
if (_sources[index] != null) {
_sources[index]._ds.removeEndNotificationListener(_delegateListener);
_sources[index]._ds.removeListener(this);
_sources[index] = null;
}
} else {
if (_sources[index] == null)
_sources[index] = new SourceHolder(d);
else {
_sources[index]._ds.removeEndNotificationListener(_delegateListener);
_sources[index]._ds.removeListener(this);
_sources[index]._ds = d;
}
_sources[index]._ds.addEndNotificationListener(_delegateListener);
_sources[index]._ds.addListener(this);
}
}
public void DataSourceIndexRangeChanged(DataSource ds, long startIndex, long lastIndex) {
if (_sources == null)
return;
// It is possible to optimize this by storing which data source has changed and for which
// index, then update in only one loop at notification end.
// But, this would require to store the data source that changed and the index
// => is dynamic object management really faster than a few loops ?
// => could be determined arbitrarily by how many elements are present in the array
// Is all this worth the bother?
for (int i = 0; i < _sources.length; ++i)
if ((_sources[i] != null) && (ds.equals(_sources[i]._ds)))
_sources[i]._index = lastIndex;
if (_animator!=null) _animator.setDirty();
}
public void DataSourceReplaced(DataSource oldData, DataSource newData) {
if (_sources == null)
return;
for (int i = 0; i < _sources.length; ++i)
if (_sources[i] != null && _sources[i]._ds == oldData) {
_sources[i]._ds = newData;
_sources[i]._ds.addEndNotificationListener(_delegateListener);
_sources[i]._ds.addListener(this);
}
}
public void DataSourceInfoChanged(DataSource ds, DataInfo newInfo) {
}
public void DataSourceOrderChanged(DataSource ds, int newOrder) {
}
public void DataSourceValueChanged(DataSource ds, long minIndex, long maxIndex) {
}
public void DataSourceValueRangeChanged(DataSource ds) {
}
public void notificationEnd(Object referer) {
}
public Object clone() throws CloneNotSupportedException {
Data res = (Data) super.clone();
if (_delegateListener == this)
res._delegateListener = res;
if (_sources != null) {
res._sources = new SourceHolder[_sources.length];
System.arraycopy(_sources, 0, res._sources, 0, _sources.length);
for (int i = 0; i < _sources.length; ++i)
if ((_sources[i] != null) && (_sources[i]._ds != null)) {
// delegate listener may be the same in the clone, but need to add ref count
_sources[i]._ds.addEndNotificationListener(res._delegateListener);
_sources[i]._ds.addListener(res);
}
} else {
res._sources = null;
}
return res;
}
/**
* Inverse operation from clone()
* Update this data from the content of another one
*/
public void updateFrom(Data dataCopy) {
if (_sources != null) {
// cleanup listeners
for (int i = 0; i < _sources.length; ++i) {
if ((_sources[i] != null) && (_sources[i]._ds != null)) {
_sources[i]._ds.removeListener(this);
_sources[i]._ds.removeEndNotificationListener(_delegateListener);
}
}
}
if (dataCopy._sources != null) {
_sources = new SourceHolder[dataCopy._sources.length];
System.arraycopy(dataCopy._sources, 0, _sources, 0, _sources.length);
} else {
_sources = null;
}
if (dataCopy._delegateListener == dataCopy)
_delegateListener = this;
else
_delegateListener = dataCopy._delegateListener;
// re-register listeners on the new sources
if (_sources != null) {
for (int i = 0; i < _sources.length; ++i) {
if ((_sources[i] != null) && (_sources[i]._ds != null)) {
_sources[i]._ds.addListener(this);
_sources[i]._ds.addEndNotificationListener(_delegateListener);
}
}
}
}
/**
* Cleanup anything that could possibly help the garbage collector.
* Ref counted listeners may be a good start...
*/
public void dispose() {
if (_sources != null) {
// cleanup listeners
for (int i = 0; i < _sources.length; ++i) {
if ((_sources[i] != null) && (_sources[i]._ds != null)) {
_sources[i]._ds.removeListener(this);
_sources[i]._ds.removeEndNotificationListener(_delegateListener);
}
_sources[i] = null; // release object
}
}
_sources = null; // release object
}
private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
out.defaultWriteObject();
if (_sources == null)
out.writeBoolean(false);
else {
out.writeBoolean(true);
out.writeInt(_sources.length);
for (int i = 0; i < _sources.length; i++)
if (_sources[i] != null) {
out.writeBoolean(true);
DataSourcePool.global.writeDataSource(out, _sources[i]._ds);
} else
out.writeBoolean(false);
}
}
private void readObject(java.io.ObjectInputStream in) throws java.lang.ClassNotFoundException,
java.io.IOException {
in.defaultReadObject();
_delegateListener = this;
if (in.readBoolean()) {
int size = in.readInt();
_sources = new SourceHolder[size];
for (int i = 0; i < _sources.length; ++i) {
if (in.readBoolean()) {
_sources[i] = new SourceHolder(DataSourcePool.global.readDataSource(in));
_sources[i]._ds.addEndNotificationListener(_delegateListener);
_sources[i]._ds.addListener(this);
}
}
} else
_sources = null;
}
}
public static class State extends Animator.State {
public State(SymbolTableData symbol, Controller control) {
super(symbol, control);
}
public void writeObject(DataOutput out) throws IOException {
super.writeObject(out);
DataAnimator an=(DataAnimator)node;
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream objOut = new ObjectOutputStream( bout );
objOut.writeObject( an._data );
objOut.writeInt( an._dataClassParam );
objOut.flush();
bout.close();
byte[] bytes = bout.toByteArray();
out.writeInt( bytes.length );
if (bytes.length!=0)
out.write( bytes );
}
public void readObject(DataInput in) throws IOException {
super.readObject(in);
DataAnimator an=(DataAnimator)node;
int size = in.readInt();
if (size<=0) {
throw new IOException("Missing data serialization");
}
byte[] bytes = new byte[size];
in.readFully( bytes );
ByteArrayInputStream bin = new ByteArrayInputStream( bytes );
ObjectInputStream objIn = new ObjectInputStream( bin );
try {
Object o = objIn.readObject();
if(o instanceof Data){
an._data=(Data)o;
an._data._references=an._references;
an._data._animator=an;
an._dataClass=an._data.getClass();
an._dataClassParam=objIn.readInt();
}
else{
throw new IOException("Missing data serialization : invalid class="+o.getClass().getName());
}
} catch( ClassNotFoundException e ) {
throw new IOException("Missing data serialization ",e);
}
finally{
objIn.close();
}
}
}
}