/*******************************************************************************
* 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 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);
}
}
}