View Javadoc

1   /*
2    * Copyright (c) 2006 Stiftung Deutsches Elektronen-Synchroton,
3    * Member of the Helmholtz Association, (DESY), HAMBURG, GERMANY.
4    *
5    * THIS SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN "../AS IS" BASIS.
6    * WITHOUT WARRANTY OF ANY KIND, EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
7    * TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR PARTICULAR PURPOSE AND
8    * NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
9    * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
10   * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
11   * THE USE OR OTHER DEALINGS IN THE SOFTWARE. SHOULD THE SOFTWARE PROVE DEFECTIVE
12   * IN ANY RESPECT, THE USER ASSUMES THE COST OF ANY NECESSARY SERVICING, REPAIR OR
13   * CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
14   * NO USE OF ANY SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER.
15   * DESY HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS,
16   * OR MODIFICATIONS.
17   * THE FULL LICENSE SPECIFYING FOR THE SOFTWARE THE REDISTRIBUTION, MODIFICATION,
18   * USAGE AND OTHER RIGHTS AND OBLIGATIONS IS INCLUDED WITH THE DISTRIBUTION OF THIS
19   * PROJECT IN THE FILE LICENSE.HTML. IF THE LICENSE IS NOT INCLUDED YOU MAY FIND A COPY
20   * AT HTTP://WWW.DESY.DE/LEGAL/LICENSE.HTM
21   */
22  
23  package de.desy.acop.displayers;
24  
25  import java.beans.PropertyVetoException;
26  import java.util.ArrayList;
27  import java.util.concurrent.ExecutorService;
28  import java.util.concurrent.ThreadPoolExecutor;
29  import java.util.concurrent.TimeUnit;
30  
31  import com.cosylab.gui.displayers.DataConsumer;
32  import com.cosylab.gui.displayers.DataSource;
33  import com.cosylab.util.CommonException;
34  
35  import de.desy.acop.chart.AcopGraphStyleEnum;
36  import de.desy.acop.displayers.chart.AcopChartConsumer;
37  import de.desy.acop.displayers.chart.AcopTrendChartConsumer;
38  import de.desy.acop.displayers.chart.HistoryParameters;
39  import de.desy.acop.displayers.chart.THistoryConnector;
40  import de.desy.acop.displayers.tools.AcopTrendChartEvent;
41  import de.desy.acop.displayers.tools.AcopTrendChartListener;
42  import de.desy.acop.displayers.tools.AcopGraphParameters;
43  import de.desy.acop.displayers.tools.AcopGraphHistoryParameters;
44  import de.desy.acop.displayers.tools.DismissableBlockingQueue;
45  import de.desy.acop.transport.ConnectionParameters;
46  
47  /**
48   * <code>AcopTrendChart</code> is a wrapper for the <code>Acop</code> which can
49   * show multiple trends in a single chart.
50   * 
51   * @author <a href="mailto:jaka.bobnar@cosylab.com">Jaka Bobnar</a>
52   * @version $Id: Templates.xml,v 1.10 2004/01/13 16:17:13 jbobnar Exp $
53   *
54   */
55  public class AcopTrendChart extends AcopChartReorg {
56  	
57  	private static final long serialVersionUID = 1L;
58  
59  	public static final String TIME_SPAN = "timeSpan";
60  	public static final String AUTOSCALE_TO_LIVE_DATA = "autoScaleToLiveData";
61  	public static final String CHECK_FOR_BAD_DATA = "checkForBadData";
62  	public static final String TIMEOUT = "timeout";
63  	
64  	private boolean scaleInitialized = false;
65  	private boolean autoScaleToLiveData = true;
66  	private double loadedHistoryStart;
67  	private double loadedHistoryStop;
68  //	private boolean loading = false;
69  	private boolean normalized = false;
70  	private THistoryConnector tHistoryConnector;
71  	private ExecutorService executorService;
72  	private ArrayList<AcopTrendChartListener> acopTrendChartListeners = new ArrayList<AcopTrendChartListener>();
73  	
74  	/**
75  	 * Constructs a new AcopTrendChart.
76  	 *
77  	 */
78  	public AcopTrendChart() {
79  		super();
80  		getAcop().setGraphStyle(AcopGraphStyleEnum.TimeLin.ordinal());
81  	}
82  	
83  	/**
84  	 * Returns the {@link THistoryConnector}.
85  	 * 
86  	 * @return the connector
87  	 */
88  	public THistoryConnector getTHistoryConnector() {
89  		if (tHistoryConnector == null) {
90  			tHistoryConnector = new THistoryConnector();
91  		}
92  		return tHistoryConnector;
93  	}
94  	
95  	/**
96  	 * Returns the single thread executor service.
97  	 * @return
98  	 */
99  	public ExecutorService getExecutorService() {
100 		if (executorService == null) {
101 			executorService = new ThreadPoolExecutor(1, 1, 0, TimeUnit.NANOSECONDS,
102 		              new DismissableBlockingQueue<Runnable>(1)){
103 				@Override
104 				protected void afterExecute(Runnable r, Throwable t) {
105 					super.afterExecute(r, t);
106 					if (t != null) {
107 						reportProgress(new AcopTrendChartEvent(AcopTrendChart.this, AcopTrendChartEvent.EventType.ERROR, t));
108 					}
109 				}
110 			};
111 		}
112 		return executorService;
113 	}
114 	
115 	private void loadHistory() {
116 		//why this???
117 //		if (getLastTimestamp()-getTimeSpan() < loadedHistoryStart) {
118 //			if (loading) {
119 //				getExecutorService().shutdownNow();
120 //				executorService = null;
121 //				loading = false;
122 //			}
123 //			obtainHistory();
124 //		}
125 //		else {
126 //			loadedHistoryStart = getLastTimestamp()-getTimeSpan();
127 //		}
128 		obtainHistory();		
129 	}
130 	
131 	private void obtainHistory() {
132 		loadedHistoryStart = getLastTimestamp()-getTimeSpan();
133 		loadedHistoryStop = getLastTimestamp();
134 //		loading = true;
135 		getExecutorService().execute(new Runnable() {
136 
137 			public void run() {
138 				double start = loadedHistoryStart;
139 				double stop = loadedHistoryStop;
140 				boolean reload;
141 				double[][] hd;
142 				reportProgress(new AcopTrendChartEvent(
143 						AcopTrendChart.this,
144 						AcopTrendChartEvent.EventType.MULTIPLE_OPERATION_STARTED,
145 						"Started loading histories."));
146 				for (AcopChartConsumer c : consumersList) {
147 					reload = true;
148 					if (c instanceof AcopTrendChartConsumer) {
149 						HistoryParameters hp = ((AcopTrendChartConsumer) c).getHistoryParameters();
150 						if (hp == null) continue;
151 						reportProgress(new AcopTrendChartEvent(
152 								AcopTrendChart.this,
153 								AcopTrendChartEvent.EventType.OPERATION_STARTED,
154 								"Started loading history for "+hp,
155 								(AcopGraphHistoryParameters) c.getDisplayerParameters()));
156 						while (reload) {
157 							hd = getTHistoryConnector().loadHistory(hp, start, stop);
158 							if (hd == null) {
159 								reload = reportError(new AcopTrendChartEvent(
160 										AcopTrendChart.this,
161 										AcopTrendChartEvent.EventType.ERROR,
162 										AcopTrendChartEvent.TIMEOUT,
163 										(AcopGraphHistoryParameters) c.getDisplayerParameters()));
164 							}
165 							else if (hd.length == 0) {
166 								reload = reportError(new AcopTrendChartEvent(
167 										AcopTrendChart.this,
168 										AcopTrendChartEvent.EventType.ERROR,
169 										AcopTrendChartEvent.NO_DATA,
170 										(AcopGraphHistoryParameters) c.getDisplayerParameters()));
171 							}
172 							else {
173 								reload = false;
174 								((AcopTrendChartConsumer) c).loadHistoryData(hd);
175 							}
176 						}
177 						reportProgress(new AcopTrendChartEvent(
178 								AcopTrendChart.this,
179 								AcopTrendChartEvent.EventType.OPERATION_ENDED,
180 								"Finished loading history for "+hp,
181 								(AcopGraphHistoryParameters) c.getDisplayerParameters()));
182 					}
183 				}
184 //				loading = false;
185 				reportProgress(new AcopTrendChartEvent(
186 						AcopTrendChart.this,
187 						AcopTrendChartEvent.EventType.MULTIPLE_OPERATION_ENDED,
188 						"Finished loading histories."));
189 			}});
190 	}
191 	
192 	private void obtainHistory(final AcopTrendChartConsumer c) {
193 		final HistoryParameters hp = c.getHistoryParameters();
194 		if (hp == null) return;
195 		getExecutorService().execute(new Runnable() {
196 
197 			public void run() {
198 				double start = getLastTimestamp()-getTimeSpan();
199 				double stop = getLastTimestamp();
200 				boolean reload = true;
201 				double[][] hd;
202 				reportProgress(new AcopTrendChartEvent(
203 						AcopTrendChart.this,
204 						AcopTrendChartEvent.EventType.OPERATION_STARTED,
205 						"Started loading history for "+hp,
206 						(AcopGraphHistoryParameters) c.getDisplayerParameters()));
207 				while (reload) {
208 					hd = getTHistoryConnector().loadHistory(hp, start, stop);
209 					if (hd == null) {
210 						reload = reportError(new AcopTrendChartEvent(
211 								AcopTrendChart.this,
212 								AcopTrendChartEvent.EventType.ERROR,
213 								AcopTrendChartEvent.TIMEOUT,
214 								(AcopGraphHistoryParameters) c.getDisplayerParameters()));
215 					}
216 					else if (hd.length == 0) {
217 						reload = reportError(new AcopTrendChartEvent(
218 								AcopTrendChart.this,
219 								AcopTrendChartEvent.EventType.ERROR,
220 								AcopTrendChartEvent.NO_DATA,
221 								(AcopGraphHistoryParameters) c.getDisplayerParameters()));
222 					}
223 					else {
224 						reload = false;
225 						c.loadHistoryData(hd);
226 					}
227 				}
228 				reportProgress(new AcopTrendChartEvent(
229 						AcopTrendChart.this,
230 						AcopTrendChartEvent.EventType.OPERATION_ENDED,
231 						"Finished loading history for "+hp,
232 						(AcopGraphHistoryParameters) c.getDisplayerParameters()));
233 			}});
234 	}
235 	
236 	protected void reportProgress(AcopTrendChartEvent event) {
237 		for (AcopTrendChartListener listener : getAcopTrendChartListeners()) {
238 			listener.reportProgress(event);
239 		}
240 	}
241 	
242 	protected boolean reportError(AcopTrendChartEvent event) {
243 		for (AcopTrendChartListener listener : getAcopTrendChartListeners()) {
244 			if (listener.reportError(event)) {
245 				String operation = (event.getMessage() == null) ? "" : ": "+event.getMessage();
246 				AcopTrendChartEvent e = new AcopTrendChartEvent(listener, AcopTrendChartEvent.EventType.PROGRESS, "Operation "+operation+" aborted.");
247 				reportProgress(e);
248 				return true;
249 			}
250 		}
251 		return false;
252 	}
253 	
254 	/**
255 	 * Adds the AcopTrendChartListener. Listener receives notifications
256 	 * about the progress and errors during the history loading.
257 	 * 
258 	 * @param listener listener to be added
259 	 */
260 	public void addAcopTrendChartListener(AcopTrendChartListener listener) {
261 		if (acopTrendChartListeners.contains(listener)) return;
262 		acopTrendChartListeners.add(listener);
263 	}
264 	
265 	/**
266 	 * Removes the AcopTrendChartListener.
267 	 * 
268 	 * @param listener listener to be removed
269 	 */
270 	public void removeAcopTrendChartListener(AcopTrendChartListener listener) {
271 		acopTrendChartListeners.remove(listener);
272 	}
273 	
274 	/**
275 	 * Returns an array of all AcopTrendChartListeners.
276 	 * 
277 	 * @return all trend chart listeners
278 	 */
279 	public AcopTrendChartListener[] getAcopTrendChartListeners() {
280 		return acopTrendChartListeners.toArray(new AcopTrendChartListener[acopTrendChartListeners.size()]);
281 	}
282 
283 	/*
284 	 * (non-Javadoc)
285 	 * @see de.desy.acop.displayers.AcopChartReorg#getConsumer(java.lang.String, java.lang.Class)
286 	 */
287 	@Override
288 	public <D extends DataConsumer> D getConsumer(String name, Class<D> type) {
289 		if (type.isAssignableFrom(AcopChartConsumer.class)) {
290 			for (AcopChartConsumer c : consumersList) {
291 				if (c.getName().equals(name)) return type.cast(c);
292 			}
293 			
294 			DataConsumer[] oldConumers = getConsumers();
295 			AcopTrendChartConsumer c= new AcopTrendChartConsumer(this, name);
296 			c.setNormalizedData(isNormalizedData());
297 			c.setColor(colorManager.pickColor());
298 			maxLinkIndex++;
299 			consumersList.add(c);
300 			firePropertyChange(CONSUMERS, oldConumers, getConsumers());
301 			return type.cast(c);
302 		}
303 		return null;
304 	}
305 
306 	/*
307 	 * (non-Javadoc)
308 	 * @see de.desy.acop.displayers.AcopChartReorg#addDisplayerParameters(de.desy.acop.displayers.tools.AcopGraphParameters)
309 	 */
310 	@Override
311 	public void addDisplayerParameters(AcopGraphParameters parameters) throws CommonException, PropertyVetoException {
312 		super.addDisplayerParameters(parameters);
313 		reloadHistory(parameters);
314 	}
315 	
316 	/**
317 	 * Reloads the history for selected parameters.
318 	 * 
319 	 * @param parameters the parameters for which the history should be reloaded.
320 	 */
321 	public void reloadHistory(AcopGraphParameters parameters) {
322 		ConnectionParameters cp = parameters.getConnectionParameters();
323 		for (AcopChartConsumer c : consumersList) {
324 			if (c instanceof AcopTrendChartConsumer && c.getDisplayerParameters().getConnectionParameters().equals(cp)) {
325 				obtainHistory((AcopTrendChartConsumer) c);
326 			}
327 		}
328 	}
329 
330 	/*
331 	 * (non-Javadoc)
332 	 * @see de.desy.acop.displayers.AcopChartReorg#synchronizeGraphParameters(de.desy.acop.displayers.tools.AcopGraphParameters)
333 	 */
334 	@Override
335 	protected AcopGraphParameters synchronizeGraphParameters(AcopGraphParameters parameters) {
336 		HistoryParameters hp = null;
337 		if (parameters instanceof AcopGraphHistoryParameters) {
338 			hp = ((AcopGraphHistoryParameters) parameters).getHistoryParameters();
339 		}
340 		AcopGraphHistoryParameters trendParameters = new AcopGraphHistoryParameters(super.synchronizeGraphParameters(parameters), hp);
341 		return trendParameters;
342 	}
343 
344 	/*
345 	 * (non-Javadoc)
346 	 * @see de.desy.acop.displayers.AcopChartReorg#updateXScale(de.desy.acop.displayers.chart.AcopChartConsumer)
347 	 */
348 	@Override
349 	public void updateXScale(AcopChartConsumer consumer) {
350 		// do nothing (XScale is handled by timeSpan)
351 	}
352 
353 	/*
354 	 * (non-Javadoc)
355 	 * @see de.desy.acop.displayers.AcopChartReorg#updateXScaleOnRemove(de.desy.acop.displayers.chart.AcopChartConsumer)
356 	 */
357 	@Override
358 	protected void updateXScaleOnRemove(AcopChartConsumer consumer) {
359 		// do nothing (XScale is handled by timeSpan)
360 	}
361 
362 	/**
363 	 * Returns the time span for this </code>AcopTrendChart</code>.
364 	 * @return the timeSpan
365 	 */
366 	public double getTimeSpan() {
367 		return getXRangeMax() - getXRangeMin();
368 	}
369 
370 	/**
371 	 * Sets time span for this </code>AcopTrendChart</code>.
372 	 * @param timeSpan the timeSpan to set
373 	 */
374 	public void setTimeSpan(double timeSpan) {
375 		if (getTimeSpan() == timeSpan) return;
376 		double oldSpan = getTimeSpan();
377 		setXRangeMin(getLastTimestamp() - timeSpan);
378 		firePropertyChange(TIME_SPAN, oldSpan, getTimeSpan());
379 		loadHistory();
380 	}
381 
382 	/**
383 	 * Returns last time stamp for this </code>AcopTrendChart</code>.
384 	 * @return the last timestamp
385 	 */
386 	public double getLastTimestamp() {
387 		return getXRangeMax();
388 	}
389 
390 	/**
391 	 * Sets last time stamp for this </code>AcopTrendChart</code>. Its value can only
392 	 * be increased.
393 	 * @param timestamp the timestamp to set
394 	 */
395 	public void setLastTimestamp(double timestamp) {
396 		if (!scaleInitialized) {
397 			if (isAutoScaleToLiveData()) {
398 				// workaround for Acop.setXMin method: sca[0].usermax = xMin + 0.5 * Math.abs(xMin);
399 				setXRangeMax(timestamp+1);
400 				setXRangeMin(timestamp);
401 				loadedHistoryStart = timestamp;
402 			}
403 			scaleInitialized = true;
404 		}
405 		if (getXRangeMax() >= timestamp) return;
406 		if (!isAutoScaleToLiveData()) {
407 			setXRangeMin(timestamp - getTimeSpan());
408 		}
409 		setXRangeMax(timestamp);
410 	}
411 
412 	/**
413 	 * Returns true if history data is checked for bad data. Bad data is any
414 	 * value higher than 1E30.
415 	 * 
416 	 * @return the checkForBadData 
417 	 */
418 	public boolean isCheckForBadData() {
419 		return getTHistoryConnector().isCheckForBadData();
420 	}
421 
422 	/**
423 	 * Enables/disables checking for bad data in the history. Bad data is any
424 	 * value higher than 1E30. These values are filtered out if this property is true.
425 	 * 
426 	 * @param checkForBadData new checkForBadData
427 	 */
428 	public void setCheckForBadData(boolean checkForBadData) {
429 		boolean oldValue = isCheckForBadData();
430 		getTHistoryConnector().setCheckForBadData(checkForBadData);
431 		firePropertyChange(CHECK_FOR_BAD_DATA, oldValue, checkForBadData);
432 	}
433 
434 	/**
435 	 * Returns the history simulator switch.
436 	 * 
437 	 * @return the simulate
438 	 */
439 	public boolean isSimulate() {
440 		return getTHistoryConnector().isSimulate();
441 	}
442 
443 	/**
444 	 * Sets the history simulator.
445 	 * 
446 	 * @param simulate the simulate to set
447 	 */
448 	public void setSimulate(boolean simulate) {
449 		getTHistoryConnector().setSimulate(simulate);
450 	}
451 
452 	/**
453 	 * Returns the timeout in millis.
454 	 * 
455 	 * @return the timeout
456 	 */
457 	public int getTimeout() {
458 		return getTHistoryConnector().getTimeout();
459 	}
460 
461 	/**
462 	 * Sets the timeout for all history based TINE calls. All calls will terminate
463 	 * after timeout even if no data was obtained yet.
464 	 * 
465 	 * @param timeout the timeout to set
466 	 */
467 	public void setTimeout(int timeout) {
468 		int oldValue = getTimeout();
469 		getTHistoryConnector().setTimeout(timeout);
470 		firePropertyChange(TIMEOUT, oldValue, timeout);
471 	}
472 
473 	/**
474 	 * Returns true if charts automatically scales to live data.
475 	 * 
476 	 * @return the autoScaleToLiveData
477 	 */
478 	public boolean isAutoScaleToLiveData() {
479 		return autoScaleToLiveData;
480 	}
481 
482 	/**
483 	 * Enables/disables autoscaling to live data. If this flag is true
484 	 * the cart will automatically adjust its vertical axis to include
485 	 * all the values in all graphs.
486 	 * 
487 	 * @param autoScaleToLiveData the autoScaleToLiveData to set
488 	 */
489 	public void setAutoScaleToLiveData(boolean autoScaleToLiveData) {
490 		boolean oldValue = isAutoScaleToLiveData();
491 		this.autoScaleToLiveData = autoScaleToLiveData;
492 		firePropertyChange(AUTOSCALE_TO_LIVE_DATA, oldValue, autoScaleToLiveData);
493 		if (!autoScaleToLiveData) {
494 			setXRangeMin(getLastTimestamp() - getTimeSpan());
495 		}
496 	}
497 
498 	/*
499 	 * (non-Javadoc)
500 	 * @see de.desy.acop.displayers.AcopChartReorg#removeDisplayerParameters(de.desy.acop.displayers.tools.AcopGraphParameters)
501 	 */
502 	@Override
503 	public DataSource removeDisplayerParameters(AcopGraphParameters parameters) {
504 		DataSource ds = super.removeDisplayerParameters(parameters);
505 		if (getDisplayerParameters().length == 0) scaleInitialized = false;
506 		return ds;
507 	}
508 		
509 	/**
510 	 * Sets the data normalized or absolute. When normalized, data is plotted
511 	 * as percentage in the min-max area.
512 	 * 
513 	 * @param normalized true for normalzied or false for absolute
514 	 */
515 	public void setNormalizedData(boolean normalized) {
516 		if (this.normalized == normalized) return;
517 		this.normalized = normalized;
518 		DataConsumer[] consumers = getConsumers();
519 		for (DataConsumer c : consumers) {
520 			if (c instanceof AcopTrendChartConsumer) {
521 				((AcopTrendChartConsumer)c).setNormalizedData(normalized);
522 			}
523 		}
524 		updateYScale(null);
525 		firePropertyChange("normalizedData",!normalized, normalized);
526 	}
527 	
528 	/**
529 	 * Returns true if the data in the channels is normalzied.
530 	 * 
531 	 * @return true if normalized or false otherwise
532 	 */
533 	public boolean isNormalizedData() {
534 		return normalized;
535 	}
536 	
537 	/*
538 	 * (non-Javadoc)
539 	 * @see de.desy.acop.displayers.AcopChartReorg#updateYScale(de.desy.acop.displayers.chart.AcopChartConsumer)
540 	 */
541 	@Override
542 	public void updateYScale(AcopChartConsumer consumer) {
543 		if (isNormalizedData()) {
544 			//do this twice (acop has scaling issues)
545 			setYRangeMax(100.);
546 			setYRangeMin(0.);
547 			setYRangeMax(100.);
548 			setYRangeMin(0.);
549 		} else {
550     		DataConsumer[] consumers = getConsumers();
551     		double max = -Double.MAX_VALUE;
552     		double min = Double.MAX_VALUE;
553     		for (DataConsumer c : consumers) {
554     			if (!(c instanceof AcopChartConsumer)) continue;
555     			max = Math.max(max, ((AcopChartConsumer)c).getPreferredYMax());
556     			min = Math.min(min, ((AcopChartConsumer)c).getPreferredYMin());
557     		}
558     		setYRangeMax(max);
559     		setYRangeMin(min);
560 		}
561 	}
562 	
563 	/*
564 	 * (non-Javadoc)
565 	 * @see de.desy.acop.displayers.AcopChartReorg#updateYScaleOnRemove(de.desy.acop.displayers.chart.AcopChartConsumer)
566 	 */
567 	@Override
568 	protected void updateYScaleOnRemove(AcopChartConsumer consumer) {
569 		updateYScale(consumer);
570 	}
571 
572 }