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