--- /dev/null
+/*******************************************************************************\r
+ * Copyright (c) 2007, 2011 Association for Decentralized Information Management in\r
+ * Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ * VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.history.util;\r
+\r
+import org.simantics.databoard.Bindings;\r
+import org.simantics.databoard.accessor.ArrayAccessor;\r
+import org.simantics.databoard.accessor.CloseableAccessor;\r
+import org.simantics.databoard.accessor.StreamAccessor;\r
+import org.simantics.databoard.accessor.error.AccessorException;\r
+import org.simantics.databoard.adapter.AdaptException;\r
+import org.simantics.databoard.binding.Binding;\r
+import org.simantics.databoard.binding.NumberBinding;\r
+import org.simantics.databoard.binding.RecordBinding;\r
+import org.simantics.databoard.binding.error.BindingException;\r
+import org.simantics.databoard.type.Datatype;\r
+import org.simantics.databoard.type.NumberType;\r
+import org.simantics.databoard.type.RecordType;\r
+import org.simantics.history.HistoryException;\r
+\r
+/**\r
+ * This utility adds random access (time) to array accessor.\r
+ * \r
+ * @author toni.kalajainen\r
+ */\r
+public class Stream {\r
+ \r
+ public ArrayAccessor accessor;\r
+ public RecordType sampleType;\r
+ public RecordBinding sampleBinding;\r
+ \r
+ public int timeIndex;\r
+ public Datatype timeType;\r
+ public Binding timeBinding;\r
+ \r
+ public int endTimeIndex;\r
+ public Datatype endTimeType;\r
+ public Binding endTimeBinding;\r
+ \r
+ public int valueIndex;\r
+ public Datatype valueType;\r
+ public Binding valueBinding;\r
+\r
+ public int qualityIndex=-1;\r
+ public NumberType qualityType;\r
+ public NumberBinding qualityBinding;\r
+ \r
+ /**\r
+ * Construct stream \r
+ * \r
+ * @param accessor \r
+ */\r
+ public Stream(ArrayAccessor accessor)\r
+ {\r
+ this.accessor = accessor;\r
+ this.sampleType = (RecordType) accessor.type().componentType();\r
+ this.sampleBinding = (RecordBinding) Bindings.getBeanBinding( sampleType );\r
+ \r
+ this.valueIndex = sampleType.getComponentIndex2("value");\r
+ if (valueIndex<0) throw new IllegalArgumentException("Array is not a sample array, value field is missing");\r
+ this.valueType = sampleType.getComponentType(valueIndex);\r
+ this.valueBinding = this.sampleBinding.getComponentBinding(valueIndex); \r
+ \r
+ this.timeIndex = sampleType.getComponentIndex2("time");\r
+ if (timeIndex<0) throw new IllegalArgumentException("Array is not a sample array, time field is missing");\r
+ this.timeType = sampleType.getComponentType(timeIndex);\r
+ this.timeBinding = this.sampleBinding.getComponentBinding(timeIndex);\r
+ \r
+ this.endTimeIndex = sampleType.getComponentIndex2("endTime");\r
+ \r
+ this.qualityIndex = sampleType.getComponentIndex2("quality");\r
+ this.qualityType = qualityIndex>=0?(NumberType)sampleType.getComponentType(qualityIndex):null;\r
+ this.qualityBinding = qualityType!=null?(NumberBinding)this.sampleBinding.getComponentBinding(qualityIndex):null; \r
+ }\r
+\r
+ /**\r
+ * Construct stream \r
+ * \r
+ * @param accessor \r
+ */\r
+ public Stream(ArrayAccessor accessor, RecordBinding recordBinding)\r
+ {\r
+ this.accessor = accessor;\r
+ this.sampleType = (RecordType) accessor.type().componentType();\r
+ if (!this.sampleType.equals(recordBinding.type())) throw new IllegalArgumentException("Wrong binding. Got " + recordBinding.type() + ", expected " + this.sampleType);\r
+ this.sampleBinding = recordBinding;\r
+\r
+ this.valueIndex = sampleType.getComponentIndex2("value");\r
+ this.timeIndex = sampleType.getComponentIndex2("time");\r
+ this.endTimeIndex = sampleType.getComponentIndex2("endTime");\r
+\r
+ if (valueIndex<0) throw new IllegalArgumentException("Array is not a sample array, value field is missing");\r
+ if (timeIndex<0) throw new IllegalArgumentException("Array is not a sample array, time field is missing");\r
+ //if (endTimeIndex<0) throw new IllegalArgumentException("Array is not a sample array, time field is missing");\r
+ \r
+ this.valueType = sampleType.getComponentType(valueIndex);\r
+ this.timeType = sampleType.getComponentType(timeIndex);\r
+ this.endTimeType = endTimeIndex>=0 ? sampleType.getComponentType(endTimeIndex) : null;\r
+ \r
+ this.valueBinding = this.sampleBinding.getComponentBinding("value"); \r
+ this.timeBinding = this.sampleBinding.getComponentBinding("time");\r
+ this.endTimeBinding = endTimeIndex>=0 ? this.sampleBinding.getComponentBinding("endTime") : null;\r
+ }\r
+\r
+ public void close() {\r
+ if (accessor instanceof CloseableAccessor) {\r
+ CloseableAccessor ca = (CloseableAccessor) accessor;\r
+ try {\r
+ ca.close();\r
+ } catch (AccessorException e) {\r
+ }\r
+ }\r
+ }\r
+ \r
+ public void reset() {\r
+ if (accessor instanceof StreamAccessor) {\r
+ StreamAccessor sa = (StreamAccessor) accessor;\r
+ try {\r
+ sa.reset();\r
+ } catch (AccessorException e) {\r
+ }\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * Make a binary search to stream data \r
+ * \r
+ * @param array\r
+ * @param timeBinding\r
+ * @param time \r
+ * @return index of the search key, if it is contained in the array\r
+ * within the specified range;\r
+ * otherwise, <tt>(-(<i>insertion point</i>) - 1)</tt>. The\r
+ * <i>insertion point</i> is defined as the point at which the\r
+ * key would be inserted into the array: the index of the first\r
+ * element in the range greater than the key,\r
+ * or <tt>toIndex</tt> if all\r
+ * elements in the range are less than the specified key. Note\r
+ * that this guarantees that the return value will be >= 0 if\r
+ * and only if the key is found.\r
+ * @throws HistoryException \r
+ */\r
+ public int binarySearch(Binding timeBinding, Object time) throws HistoryException \r
+ { \r
+ try {\r
+ Object time_ = Bindings.adapt(time, timeBinding, this.timeBinding);\r
+ \r
+ int fromIndex = 0;\r
+ int toIndex = accessor.size();\r
+ \r
+ int ix = binarySearch0(fromIndex, toIndex, time_);\r
+ return ix;\r
+ } catch (AccessorException e) {\r
+ throw new HistoryException(e);\r
+ } catch (BindingException e) {\r
+ throw new HistoryException(e);\r
+ } catch (AdaptException e) {\r
+ throw new HistoryException(e);\r
+ }\r
+ }\r
+ \r
+ // Like public version, but without range checks.\r
+ private int binarySearch0(int fromIndex, int toIndex, Object key) throws AccessorException, BindingException {\r
+ int low = fromIndex;\r
+ int high = toIndex - 1;\r
+ Binding timeBinding = sampleBinding.getComponentBinding(timeIndex);\r
+ \r
+ while (low <= high) {\r
+ int mid = (low + high) >>> 1;\r
+ Object midSam = accessor.get(mid, sampleBinding);\r
+ Object midVal = sampleBinding.getComponent(midSam, timeIndex);\r
+ int cmp = timeBinding.compare(midVal, key);\r
+ if (cmp < 0)\r
+ low = mid + 1;\r
+ else if (cmp > 0)\r
+ high = mid - 1;\r
+ else\r
+ return mid; // key found\r
+ }\r
+ return -(low + 1); // key not found.\r
+ }\r
+ \r
+ public Object getLowerSample(Binding timeBinding, Object time) throws HistoryException\r
+ {\r
+ try {\r
+ int index = binarySearch(timeBinding, time);\r
+ // Exact match\r
+ if (index==0) return null;\r
+ if (index>0) {\r
+ return accessor.get(index-1, sampleBinding);\r
+ }\r
+ index = -index-2;\r
+ if (index<0) return null;\r
+ if (index>=accessor.size()) return null;\r
+ return accessor.get(index, sampleBinding);\r
+ } catch (AccessorException e) {\r
+ throw new HistoryException( e );\r
+ }\r
+ }\r
+\r
+ \r
+ public Object getFloorSample(Binding timeBinding, Object time) throws HistoryException\r
+ {\r
+ try {\r
+ int index = binarySearch(timeBinding, time);\r
+ // Exact match\r
+ if (index>=0) {\r
+ return accessor.get(index, sampleBinding);\r
+ }\r
+ // The position where the sample would be inserted\r
+ index = -index-2;\r
+ if (index<0) return null;\r
+ if (index>=accessor.size()) return null;\r
+ return accessor.get(index, sampleBinding);\r
+ } catch (AccessorException e) {\r
+ throw new HistoryException( e );\r
+ }\r
+ }\r
+ \r
+ public Object getSample(Binding timeBinding, Object time) throws HistoryException\r
+ {\r
+ try {\r
+ int pos = binarySearch(timeBinding, time);\r
+ if (pos>=0) {\r
+ return accessor.get(pos, sampleBinding);\r
+ }\r
+ return null;\r
+ } catch (AccessorException e) {\r
+ throw new HistoryException(e);\r
+ }\r
+ }\r
+ \r
+ public boolean getSample(Binding timeBinding, Object time, Object sample) throws HistoryException\r
+ {\r
+ try {\r
+ int pos = binarySearch(timeBinding, time);\r
+ if (pos>=0) {\r
+ accessor.get(pos, sampleBinding, sample);\r
+ return true;\r
+ }\r
+ return false;\r
+ } catch (AccessorException e) {\r
+ throw new HistoryException(e);\r
+ }\r
+ }\r
+ \r
+ public Object getCeilingSample(Binding timeBinding, Object time) throws HistoryException\r
+ {\r
+ try {\r
+ int index = binarySearch(timeBinding, time);\r
+ // Exact match\r
+ if (index>=0) {\r
+ return accessor.get(index, sampleBinding);\r
+ }\r
+ // The position where the sample would be inserted\r
+ index = -index-1;\r
+ if (index<0) return null;\r
+ if (index>=accessor.size()) return null;\r
+ return accessor.get(index, sampleBinding);\r
+ } catch (AccessorException e) {\r
+ throw new HistoryException( e );\r
+ }\r
+ }\r
+ \r
+ public Object getHigherSample(Binding timeBinding, Object time) throws HistoryException\r
+ {\r
+ try {\r
+ int index = binarySearch(timeBinding, time);\r
+ // Exact match\r
+ int count = accessor.size();\r
+ if (index>=0) {\r
+ index++; // exact match, -> next\r
+ } else {\r
+ // The position where the sample would be inserted\r
+ index = -index-1;\r
+ }\r
+ if (index<0 || index>=count) return null;\r
+ return accessor.get(index, sampleBinding);\r
+ } catch (AccessorException e) {\r
+ throw new HistoryException( e );\r
+ }\r
+ }\r
+\r
+ private Object _getTime(Object sample, Binding timeBinding) throws HistoryException\r
+ {\r
+ try {\r
+ Object time__ = sampleBinding.getComponent( sample, timeIndex );\r
+ return Bindings.adapt(time__, this.timeBinding, timeBinding);\r
+ } catch (AdaptException e) {\r
+ throw new HistoryException(e);\r
+ } catch (BindingException e) {\r
+ throw new HistoryException(e);\r
+ }\r
+ }\r
+ \r
+ public Object getLowerTime(Binding timeBinding, Object time) throws HistoryException\r
+ {\r
+ try {\r
+ int index = binarySearch(timeBinding, time);\r
+ // Exact match\r
+ if (index==0) return null;\r
+ if (index>0) {\r
+ return time;\r
+ }\r
+ index = -index-2;\r
+ if (index<0) return null;\r
+ if (index>=accessor.size()) return null;\r
+ return _getTime(accessor.get(index, sampleBinding), timeBinding);\r
+ } catch (AccessorException e) {\r
+ throw new HistoryException( e );\r
+ }\r
+ }\r
+\r
+ \r
+ public Object getFloorTime(Binding timeBinding, Object time) throws HistoryException\r
+ {\r
+ try {\r
+ int index = binarySearch(timeBinding, time);\r
+ // Exact match\r
+ if (index>=0) {\r
+ return time;\r
+ }\r
+ // The position where the sample would be inserted\r
+ index = -index-2;\r
+ if (index<0) return null;\r
+ if (index>=accessor.size()) return null;\r
+ return _getTime( accessor.get(index, sampleBinding), timeBinding );\r
+ } catch (AccessorException e) {\r
+ throw new HistoryException( e );\r
+ }\r
+ }\r
+ \r
+ public Object getCeilingTime(Binding timeBinding, Object time) throws HistoryException\r
+ {\r
+ try {\r
+ int index = binarySearch(timeBinding, time);\r
+ // Exact match\r
+ if (index>=0) {\r
+ return time;\r
+ }\r
+ // The position where the sample would be inserted\r
+ index = -index-1;\r
+ if (index<0) return null;\r
+ if (index>=accessor.size()) return null;\r
+ return _getTime( accessor.get(index, sampleBinding), timeBinding );\r
+ } catch (AccessorException e) {\r
+ throw new HistoryException( e );\r
+ }\r
+ }\r
+ \r
+ public Object getHigherTime(Binding timeBinding, Object time) throws HistoryException\r
+ {\r
+ try {\r
+ int index = binarySearch(timeBinding, time);\r
+ \r
+ // Exact match\r
+ int count = accessor.size();\r
+ if (index>=0) {\r
+ index++; // exact match, -> next\r
+ } else {\r
+ // The position where the sample would be inserted\r
+ index = -index-1;\r
+ }\r
+ if (index<0 || index>=count) return null;\r
+ return _getTime( accessor.get(index, sampleBinding), timeBinding );\r
+ } catch (AccessorException e) {\r
+ throw new HistoryException( e );\r
+ }\r
+ }\r
+ \r
+ \r
+ public Object getValue(Binding timeBinding, Object time, Binding valueBinding) throws HistoryException\r
+ {\r
+ try {\r
+ int pos = binarySearch(timeBinding, time);\r
+ if (pos>=0) {\r
+ Object sample = accessor.get(pos, sampleBinding);\r
+ Object value = sampleBinding.getComponent(sample, valueIndex);\r
+ \r
+ if (valueBinding != this.valueBinding) {\r
+ value = Bindings.adapt(value, this.valueBinding, valueBinding);\r
+ } \r
+ return value;\r
+ }\r
+ return null;\r
+ } catch (AccessorException e) {\r
+ throw new HistoryException(e);\r
+ } catch (BindingException e) {\r
+ throw new HistoryException(e);\r
+ } catch (AdaptException e) {\r
+ throw new HistoryException(e);\r
+ }\r
+ }\r
+ \r
+ public Object getValue(Binding timeBinding, Object time) throws HistoryException\r
+ {\r
+ try {\r
+ int pos = binarySearch(timeBinding, time);\r
+ if (pos>=0) {\r
+ Object sample = accessor.get(pos, sampleBinding);\r
+ Object value = sampleBinding.getComponent(sample, valueIndex);\r
+ return value;\r
+ }\r
+ return null;\r
+ } catch (AccessorException e) {\r
+ throw new HistoryException(e);\r
+ } catch (BindingException e) {\r
+ throw new HistoryException(e);\r
+ }\r
+ }\r
+ \r
+ public Object getQuality(Binding timeBinding, Object time) throws HistoryException\r
+ {\r
+ try {\r
+ int pos = binarySearch(timeBinding, time);\r
+ if (pos>=0) {\r
+ Object sample = accessor.get(pos, sampleBinding);\r
+ Object value = sampleBinding.getComponent(sample, qualityIndex);\r
+ return value;\r
+ }\r
+ return null;\r
+ } catch (AccessorException e) {\r
+ throw new HistoryException(e);\r
+ } catch (BindingException e) {\r
+ throw new HistoryException(e);\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * Get value if exists, otherwise null\r
+ * @param timeBinding\r
+ * @param time\r
+ * @return value or null\r
+ * @throws HistoryException\r
+ */\r
+ public Object getPossibleValue(Binding timeBinding, Object time) throws HistoryException\r
+ {\r
+ try {\r
+ int pos = binarySearch(timeBinding, time);\r
+ if (pos>=0) {\r
+ Object sample = accessor.get(pos, sampleBinding);\r
+ if (qualityBinding != null) { \r
+ Object quality = sampleBinding.getComponent(sample, qualityIndex); \r
+ if ( !qualityBinding.getValue(quality).equals( ValueBand.QUALITY_GOOD ) ) return null; \r
+ }\r
+ Object value = sampleBinding.getComponent(sample, valueIndex);\r
+ return value;\r
+ }\r
+ return null;\r
+ } catch (AccessorException e) {\r
+ throw new HistoryException(e);\r
+ } catch (BindingException e) {\r
+ throw new HistoryException(e);\r
+ }\r
+ }\r
+ \r
+ public int count() throws HistoryException {\r
+ try {\r
+ return accessor.size();\r
+ } catch (AccessorException e) {\r
+ throw new HistoryException(e);\r
+ }\r
+ }\r
+ \r
+ public Object getFirstTime(Binding binding) throws HistoryException {\r
+ try {\r
+ if (accessor.size()==0) {\r
+ return null;\r
+ }\r
+ Object sample = accessor.get(0, sampleBinding);\r
+ Object time = sampleBinding.getComponent(sample, timeIndex);\r
+ if (timeBinding!=binding) time = Bindings.adapt(time, timeBinding, binding);\r
+ return time;\r
+ } catch (BindingException e) {\r
+ throw new HistoryException(e);\r
+ } catch (AccessorException e) {\r
+ throw new HistoryException(e);\r
+ } catch (AdaptException e) {\r
+ throw new HistoryException(e);\r
+ }\r
+ }\r
+ \r
+ public Object getEndTime(Binding binding) throws HistoryException {\r
+ try {\r
+ if (accessor.size()==0) {\r
+ return null;\r
+ }\r
+ Object sample = accessor.get(0, sampleBinding);\r
+ \r
+ if (endTimeIndex>=0) {\r
+ Object endtime = sampleBinding.getComponent(sample, endTimeIndex);\r
+ if (endTimeBinding!=binding) endtime = Bindings.adapt(endtime, endTimeBinding, binding);\r
+ return endtime;\r
+ } else {\r
+ Object time = sampleBinding.getComponent(sample, timeIndex);\r
+ if (timeBinding!=binding) time = Bindings.adapt(time, timeBinding, binding);\r
+ return time; \r
+ }\r
+ } catch (BindingException e) {\r
+ throw new HistoryException(e);\r
+ } catch (AccessorException e) {\r
+ throw new HistoryException(e);\r
+ } catch (AdaptException e) {\r
+ throw new HistoryException(e);\r
+ }\r
+ }\r
+\r
+ public boolean isEmpty() throws HistoryException {\r
+ try {\r
+ return accessor.size() == 0;\r
+ } catch (AccessorException e) {\r
+ throw new HistoryException(e);\r
+ }\r
+ }\r
+ \r
+}\r