/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package org.autoplot.dom; import java.beans.IntrospectionException; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.beans.PropertyDescriptor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.text.ParseException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.WeakHashMap; import java.util.logging.Level; import java.util.logging.Logger; import org.autoplot.util.TickleTimer; import org.das2.datum.CacheTag; import org.das2.datum.Datum; import org.das2.datum.DatumRange; import org.das2.datum.DatumRangeUtil; import org.das2.datum.Units; import org.das2.datum.UnitsUtil; import org.das2.graph.DasAxis; import org.das2.graph.DasPlot; import org.das2.datum.format.DateTimeDatumFormatter; import org.das2.util.LoggerManager; import org.das2.qds.QDataSet; import org.autoplot.datasource.DataSetURI; import org.autoplot.datasource.capability.TimeSeriesBrowse; /** * When the data source supports loading additional data when the time axis (or plot context) changes, then * this is responsible for loading additional data. * @author jbf */ public class TimeSeriesBrowseController { private PlotElement p; private DasAxis xAxis; /** * set to true after release, to make sure it is not used again. This can * be reset to false when the TSB is rebound to the context property. */ private boolean released= false; // private DasPlot dasPlot; private Plot domPlot; private PlotElementController plotElementController; private final DataSourceController dataSourceController; private DataSourceFilter dsf; private ChangesSupport changesSupport; private static final String PENDING_AXIS_DIRTY= "tsbAxisDirty"; private static final String PENDING_TIMERANGE_DIRTY= "tsbTimerangeDirty"; private static final String PENDING_AXIS_OR_TIMERANGE_DIRTY= "tsbAxisOrTimerangeDirty"; private static final Logger logger = org.das2.util.LoggerManager.getLogger("autoplot.tsb"); TickleTimer updateTsbTimer; PropertyChangeListener timeSeriesBrowseListener; private DomNode listenNode=null; private String listenProp=null; private static List getTimerangeBoundAxes( Application dom ) { List bms= DomUtil.findBindings( dom, dom, Application.PROP_TIMERANGE ); List result= new ArrayList<>(); for ( BindingModel bm: bms ) { if ( bm.getDstProperty().equals( Axis.PROP_RANGE ) ) { result.add( (Axis)DomUtil.getElementById( dom, bm.getDstId() ) ); } } return result; } private static List getOtherBoundAxes( Application dom, Axis axis ) { List bms= DomUtil.findBindings( dom, axis, Axis.PROP_RANGE ); List result= new ArrayList<>(); for ( BindingModel bm: bms ) { if ( bm.getSrcProperty().equals(Application.PROP_TIMERANGE) ) { result.addAll( getTimerangeBoundAxes( dom ) ); } else if ( bm.getSrcId().equals(axis.getId()) ) { DomNode n= DomUtil.getElementById( dom, bm.getDstId() ); if ( n instanceof Axis ) result.add( (Axis)n ); } else if ( bm.getDstId().equals(axis.getId()) ) { DomNode n= DomUtil.getElementById( dom, bm.getSrcId() ); if ( n instanceof Axis ) result.add( (Axis)n ); } } return result; } protected TimeSeriesBrowseController( DataSourceController dataSourceController, final PlotElement p ) { this.changesSupport= new ChangesSupport(this.propertyChangeSupport,this); updateTsbTimer = new TickleTimer(300, new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { LoggerManager.logPropertyChangeEvent(evt,"updateTsbTimer"); //TODO: this would work, but the particular axis is not actually adjusting. We need //to figure out who it's attached to, and see if any are adjusting. Maybe even //put in new code in dasPlot.getXAxis.valueIsAdjusting to check bindings. if ( dsf==null ) { return; // transitional state } Application dom= dsf.getController().getApplication(); // if ( domPlot!=null ) { List otherAxes= getOtherBoundAxes( dom, domPlot.getXaxis() ); for ( Axis a: otherAxes ) { if ( a.getController().getDasAxis().valueIsAdjusting() ) { updateTsbTimer.tickle(); logger.log( Level.FINEST, "{0} is adjusting", a); return; } } } final PlotElement fpe= p; final DataSourceFilter fdsf= p!=null ? p.getController().getDataSourceFilter() : null; final TimeSeriesBrowse ftsb= fdsf!=null ? fdsf.getController().getTsb() : null; // TODO: why not just return if fpe is null? if ( dom.getController().isValueAdjusting() ) { updateTsbTimer.tickle(); logger.log( Level.FINEST, "applicationController is adjusting" ); } else { Map changes= new HashMap<>(); if ( fpe!=null ) { PlotElementController pec= fpe.getController(); if ( pec!=null ) { DataSourceFilter dsf= pec.getDataSourceFilter(); if ( dsf!=null ) { DataSourceController dsc= dsf.getController(); if ( dsc!=null ) { dsc.pendingChanges(changes); changes.remove( PENDING_AXIS_OR_TIMERANGE_DIRTY ); } } } } if ( fpe!=null && !changes.isEmpty() ) { logger.log( Level.FINEST, "DataSourceFilter is already pending changes, retickle"); //changesSupport.performingChange( TimeSeriesBrowseController.this, PENDING_AXIS_OR_TIMERANGE_DIRTY ); updateTsbTimer.tickle(); // bug 1253 } else { try { changesSupport.performingChange( TimeSeriesBrowseController.this, PENDING_AXIS_OR_TIMERANGE_DIRTY ); if ( fpe!=null && ( fdsf==null || ftsb == null ) ) { } else { if ( released ) { logger.fine("leftover update ignored because this is released."); } else { updateTsb(false); } } //} } finally { changesSupport.changePerformed( TimeSeriesBrowseController.this, PENDING_AXIS_OR_TIMERANGE_DIRTY ); } } } } }); this.dsf= dataSourceController.dsf; this.dataSourceController= dataSourceController; if ( p!=null ) { this.p = p; this.domPlot= dsf.getController().getApplication().getController().getPlotFor(p); this.plotElementController = p.getController(); } else { logger.fine("no plotElement provided, better come back to set up from timerange."); } if ( p!=null ) { this.dasPlot = plotElementController.getDasPlot(); this.xAxis = plotElementController.getDasPlot().getXAxis(); } } private boolean isBoundTimeRange( BindingModel[] bms, String dstId ) { for ( int i=0; i2 ) { newResolution = visibleRange.width().divide(xAxis.getDLength()); } else { Canvas c= this.dataSourceController.getApplication().getCanvases(0); xAxis.getCanvas().setSize(c.width,c.height); if ( xAxis.getDLength()<=2 ) { logger.warning("xaxis isn't sized, loading data at full resolution!"); //TODO: check into this } else { newResolution = visibleRange.width().divide(xAxis.getDLength()); } } // don't waste time by chasing after 1.0% of a dataset. DatumRange newRange = visibleRange; testCacheTag = new CacheTag( DatumRangeUtil.rescale(newRange, 0.01, 0.99), newResolution ); trange= newRange; if ( !UnitsUtil.isTimeLocation( visibleRange.getUnits() ) ) { logger.log(Level.FINE, "x-axis for TSB not time location units: {0}", visibleRange); trange= dsf.getController().getApplication().getTimeRange(); if ( UnitsUtil.isTimeLocation( trange.getUnits() ) ) { logger.fine(" rebinding application timeRange" ); this.release(); //Axis xaxis= null; //dom.getController().unbind( dom, Application.PROP_TIMERANGE, xaxis, Axis.PROP_RANGE ); //dom.setTimeRange( this.timeSeriesBrowseController.getTimeRange() );//TODO: think about if this is really correct //this.setupGen( dsf.getController().getApplication(), Application.PROP_TIMERANGE ); this.setupGen( domPlot, Plot.PROP_CONTEXT ); Application dom= domPlot.getController().getApplication(); if ( !dom.getController().isPendingChanges() ) { // I'm unable to figure out how to get the code to come through here dom.getController().bind( dom, Application.PROP_TIMERANGE, domPlot, Plot.PROP_CONTEXT ); } } else { logger.fine(" unable to bind to application timeRange because of units." ); return; } } } else { testCacheTag= new CacheTag(trange,null); } if ( !UnitsUtil.isTimeLocation( testCacheTag.getRange().getUnits() ) ) { testCacheTag= new CacheTag( this.getTimeRange(), null ); // transitional state? hudson autoplot-test034 was hitting this. } // 1224: if the xAxis is null, then we need to reload whenever the tag range changes. if (tag == null || ( xAxis==null ? !tag.equals(testCacheTag) : !tag.contains(testCacheTag) ) ) { TimeSeriesBrowse tsb= dataSourceController.getTsb(); if ( tsb==null ) { logger.warning("tsbc253: tsb was null"); return; } if ( xAxis!=null ) { //if (dasPlot.isOverSize() && autorange==false ) { // visibleRange = DatumRangeUtil.rescale(visibleRange, -0.3, 1.3); //TODO: be aware of //} tsb.setTimeRange(trange); tsb.setTimeResolution(newResolution); logger.log( Level.FINE, "updateTsb: {0} (@{1})", new Object[]{trange, newResolution}); } else { tsb.setTimeRange(trange); tsb.setTimeResolution(null); logger.log( Level.FINE, "updateTsb: {0} (@ intrinsic)", trange); } String surl; surl = tsb.getURI(); // check the registry for URLs, compare to surl, append prefix if necessary. if (!autorange && surl.equals( dataSourceController.getTsbSuri())) { logger.fine("we do no better with tsb"); if ( xAxis==null ) { // We really want this to clip off the data that we can't see. This is a fairly impactive // change and we will need to keep track of the original and the clipped dataset, Further, // this will also need to consider: // * not possible because there are no timetags. We have to use the TimeSeriesBrowse time and all data returned // * TimeSeriesBrowse does the filtering. This should already work, if it's URIs are correct. // * We do the filtering. } } else { if ( ! surl.equals( dataSourceController.getTsbSuri()) ) { logger.log( Level.FINER, "update b/c surl!=tsbSuri:\n {0}\n {1}", new Object[]{surl, dataSourceController.getTsbSuri()}); } dataSourceController.cancel(); logger.fine("calling update, which will reload data"); if ( this.released ) { System.err.append("here were shouldn't be"); } dataSourceController.update(false); dataSourceController.setTsbSuri(surl); if ( domPlot!=null ) { String uriMode= "reset"; // "blur" "none" if ( uriMode.equals("blur") ) { // this branch has issues, in particular inserting the timerange for histories. String newUri= DataSetURI.blurTsbUri( surl ); if ( newUri!=null ) { dataSourceController.dsf.uri= newUri; domPlot.controller.dom.controller.setFocusUri(newUri); } } else if ( uriMode.equals("reset") ) { String newUri= DataSetURI.blurTsbResolutionUri( surl ); if ( newUri!=null ) { dataSourceController.dsf.uri= newUri; domPlot.controller.dom.controller.setFocusUri(newUri); } } else { //none } } //String blurUri= DataSetURI.blurTsbUri( surl ); //if ( blurUri!=null ) dataSourceController.dsf.uri= blurUri; } } else { logger.fine("loaded dataset satifies request"); } } } /** * return the dasPlot element that is responsible for handling this data source. This * is the guy that was focused when this was created, and is attached to the dasPlot x-axis * that controls this (if there is one). * This was introduced because classes were accessing the local variable p. * * @return a PlotElement or null. */ private PlotElement getPlotElement() { return p; } public boolean isPendingChanges() { if ( changesSupport.isPendingChanges() ) { return true; } else { return changesSupport.isPendingChanges(); } } public void pendingChanges( Map c ) { c.putAll( changesSupport.changesPending ); } /** * the axis we were listening to turned out to not be a time axis (or * vise-versa), so we need to release the thing we were listening to. */ protected void release() { if ( isListeningToAxis() ) { logger.fine("releasing TSB controller"); this.dasPlot.getXAxis().removePropertyChangeListener(DasAxis.PROPERTY_DATUMRANGE,timeSeriesBrowseListener); this.domPlot.removePropertyChangeListener( Plot.PROP_CONTEXT, timeSeriesBrowseListener ) ; this.xAxis= null; this.released= true; } else { if ( listenNode!=null ) { if ( timeSeriesBrowseListener==null ) { logger.fine("here timeSeriesBrowseListener is null"); } listenNode.removePropertyChangeListener( listenProp, timeSeriesBrowseListener ); } } timeSeriesBrowseListener = null; } /** * make sure there are no references causing memory leak. * There's still a leak as profiling would show * See https://sourceforge.net/p/autoplot/bugs/1584/ */ protected void releaseAll() { release(); this.domPlot= null; this.xAxis= null; this.dasPlot= null; this.dsf= null; if ( listenNode!=null ) { listenNode.removePropertyChangeListener( listenProp, timeSeriesBrowseListener ); this.listenNode= null; } this.plotElementController= null; this.dataSourceController.releaseTimeSeriesBrowseController(); this.released= true; // it might have been listening to context. } protected DatumRange timeRange = null; public static final String PROP_TIMERANGE = "timeRange"; private DatumRange getTimeRange() { return timeRange; } private void setTimeRange(DatumRange timeRange) { if ( !UnitsUtil.isTimeLocation( timeRange.getUnits() ) ) { logger.log(Level.FINE, "ignoring call to setTimeRange with non-time-location {0}", timeRange); return; } DatumRange oldTimeRange = this.timeRange; this.timeRange = timeRange; propertyChangeSupport.firePropertyChange(PROP_TIMERANGE, oldTimeRange, timeRange); } private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this); public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) { propertyChangeSupport.removePropertyChangeListener(propertyName, listener); } public void removePropertyChangeListener(PropertyChangeListener listener) { propertyChangeSupport.removePropertyChangeListener(listener); } public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) { propertyChangeSupport.addPropertyChangeListener(propertyName, listener); } public void addPropertyChangeListener(PropertyChangeListener listener) { propertyChangeSupport.addPropertyChangeListener(listener); } @Override public String toString() { return this.dsf + " timeSeriesBrowse controller"; } /** * returns true if the TSB is listening to an axis and not the application timeRange property. * This is the typical mode. * @return */ public boolean isListeningToAxis() { return xAxis!=null; } /** * return the id of the plot to which we are listening. * @return the id of the plot */ public String getPlotId() { return this.domPlot.getId(); } /** * provide access to the TSB dasPlot. * @return */ Plot getPlot() { return this.domPlot; } }