com.jogden.spunkycharts.pricebyvolumechart.PriceByVolumeChartFragmentAdapter.java Source code

Java tutorial

Introduction

Here is the source code for com.jogden.spunkycharts.pricebyvolumechart.PriceByVolumeChartFragmentAdapter.java

Source

package com.jogden.spunkycharts.pricebyvolumechart;

/* 
Copyright (C) 2014 Jonathon Ogden     < jeog.dev@gmail.com >
    
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
    
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
    
You should have received a copy of the GNU General Public License
along with this program.  If not, see http://www.gnu.org/licenses.
*/
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.NavigableSet;
import java.util.UUID;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import android.annotation.SuppressLint;
import android.content.BroadcastReceiver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.Cursor;
import android.os.ConditionVariable;
import android.os.Handler;
import android.support.v4.content.LocalBroadcastManager;
import android.text.format.Time;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;

import com.jogden.spunkycharts.ApplicationPreferences;
import com.jogden.spunkycharts.MainApplication;
import com.jogden.spunkycharts.R;
import com.jogden.spunkycharts.data.DataClientLocalDebug;
import com.jogden.spunkycharts.data.DataContentService;
import com.jogden.spunkycharts.data.DataContentService.DataClientInterface;
import com.jogden.spunkycharts.data.DataContentService.DataConsumerInterface;
import com.jogden.spunkycharts.misc.OHLC;
import com.jogden.spunkycharts.misc.Pair;
import com.jogden.spunkycharts.traditionalchart.YAxisPriceLabel;
import com.jogden.spunkycharts.pricebyvolumechart.draw.DrawSemantics_SILO;

@SuppressLint("InflateParams")
public class PriceByVolumeChartFragmentAdapter implements DataContentService.DataConsumerInterface {
    @SuppressWarnings("serial")
    public static class PriceByVolumeChartLogicError extends RuntimeException {
        public PriceByVolumeChartLogicError(String msg) {
            super(msg);
        }
    }

    /**/
    public final String MY_ID = UUID.randomUUID().toString();
    /**/
    private String _symbol = null;

    private final int MSPM = 60000;
    private final int GMS = MSPM * DataClientInterface.TIME_GRANULARITY;

    /* the important child views */
    private final PriceByVolumeChartPanel _mainSgmnt;
    private final LinearLayout _priceAxis;
    private final LinearLayout _volumeAxis;
    private final ViewGroup _myContainer;

    private final LayoutInflater _inflater;
    private final Context _cntxt;
    private final Handler _guiThrdHndlr;

    /* also chart specific settings but
     * currently changed for all charts of this type */
    private int _axisFontSz = ApplicationPreferences.getAxisFontSize();
    private int _axisFontColor = ApplicationPreferences.getAxisFontColor();
    private int _axisTimeout = ApplicationPreferences.getLongTimeout();
    private int _axisTimeoutIncr = ApplicationPreferences.getTimeoutIncrement();

    private int _priceElemHght = 0;
    private int _volSegHght = 0;

    private volatile float _highPrice = 0;
    private volatile float _lowPrice = Float.MAX_VALUE;
    private volatile int _highVolume = 0;

    private int _segCount = 0;
    private float _segSize = 0;

    private volatile Time _updateTime = new Time();

    private Cursor _myCursor;

    static private String[] columns = { "OPEN_COLUMN", "HIGH_COLUMN", "LOW_COLUMN", "CLOSE_COLUMN",
            "VOLUMN_COLUMN" };

    @SuppressWarnings("serial")
    static private List<Pair<String, SQLType>> myColumnInfo = new ArrayList<Pair<String, SQLType>>() {
        {
            add(new Pair<String, SQLType>(columns[0], SQLType.FloatNotNull));
            add(new Pair<String, SQLType>(columns[1], SQLType.FloatNotNull));
            add(new Pair<String, SQLType>(columns[2], SQLType.FloatNotNull));
            add(new Pair<String, SQLType>(columns[3], SQLType.FloatNotNull));
            add(new Pair<String, SQLType>(columns[4], SQLType.IntNotNull));
        }
    };

    private AtomicBoolean _hasSegment = new AtomicBoolean(false);
    private AtomicBoolean _streamReady = new AtomicBoolean(false);
    private AtomicBoolean _updateReady = new AtomicBoolean(false);

    private volatile OHLC _lastGranularPriceSeg = new OHLC(0);
    private volatile int _lastGranularVolSeg = 0;
    private volatile Time _lastGranularSegTime = new Time();

    private final Lock _volumeAxisLck = new ReentrantLock();
    private final Lock _priceAxisLck = new ReentrantLock();

    private volatile Thread _priceAxisThrd = null;
    private final Object _priceAxisThrdMntr = new Object();

    private int _priceAxisPrmptCnt = 0;
    private final BlockingDeque<Thread> _priceAxisPrmptStck = new LinkedBlockingDeque<Thread>();

    private final NavigableSet<Thread> _activeThreads = new ConcurrentSkipListSet<Thread>(new Comparator<Thread>() {
        @Override
        public int compare(Thread lhs, Thread rhs) {
            return (int) lhs.getId() - (int) rhs.getId();
        }
    });

    private LocalBroadcastManager _localBcastMngr;
    private static final IntentFilter _rangeFltr = new IntentFilter();
    static {
        _rangeFltr.addAction(HighLowChange.NEW_HIGH_PRICE);
        _rangeFltr.addAction(HighLowChange.NEW_LOW_PRICE);
        _rangeFltr.addAction(HighLowChange.NEW_HIGH_VOL);
    }

    /*|--------->         begin PUBLIC interface         <--------|*/

    public PriceByVolumeChartFragmentAdapter(Context context, Handler handler,
            LocalBroadcastManager broadcastManager, PriceByVolumeChartPanel mainChartPanel,
            LinearLayout volumeAxisLayout, LinearLayout priceAxisLayout, ViewGroup fragmentViewGroup,
            int volumeSegmentHeight, Cursor cursor, String symbol, Boolean... bArgs) {
        this._cntxt = context;
        this._guiThrdHndlr = handler;
        this._localBcastMngr = broadcastManager;
        this._mainSgmnt = mainChartPanel;
        this._volumeAxis = volumeAxisLayout;
        this._priceAxis = priceAxisLayout;
        this._myContainer = fragmentViewGroup;
        this._volSegHght = volumeSegmentHeight;
        this._myCursor = cursor;
        this._symbol = symbol;
        this._localBcastMngr.registerReceiver(new HighLowChange(), _rangeFltr);
        this._inflater = LayoutInflater.from(_cntxt);
        this._setPricePanelDrawSemantics();
        _mainSgmnt.init(new PriceByVolumeChartPanel.OnNewHighVolume() {
            @Override
            public void onNewHighVolume(int v) {
                Intent iii = new Intent(HighLowChange.NEW_HIGH_VOL);
                iii.putExtra(HighLowChange.EXTRA_VALUE, v);
                iii.putExtra(HighLowChange.FRAGMENT_ID, MY_ID);
                _localBcastMngr.sendBroadcast(iii);
            }
        }, handler);
        if (_myCursor != null)
            this._init();
    }

    @Override
    public boolean updateReady() {
        return _updateReady.get();
    }

    @Override
    public Cursor swapCursor(Cursor cursor) {
        Cursor tmp = _myCursor;
        _myCursor = cursor;
        if (_myCursor != null)
            reset();
        return tmp;
    }

    @Override
    public void update(DataClientInterface dataClient, DataConsumerInterface.InsertCallback iCallback) {

        Pair<OHLC, Time> pricePair = dataClient.getLast(_symbol, DataClientLocalDebug.DATA_PRICE_DEF);
        Pair<Integer, Time> volPair = dataClient.getLast(_symbol, DataClientLocalDebug.DATA_VOL_DEF);

        OHLC price = new OHLC(pricePair.first);
        int vol = volPair.first.intValue();
        Time time = new Time(pricePair.second);

        /* if we've rolled past TIME_GRANULARITY */
        if (_hasSegment.get() && _segHasRolled(_lastGranularSegTime, time, DataClientInterface.TIME_GRANULARITY)) {
            iCallback.insertRow(
                    _createContentValues(_lastGranularPriceSeg, _lastGranularVolSeg, _lastGranularSegTime), _symbol,
                    (DataConsumerInterface) this);

            _lastGranularSegTime = _truncateTime(time, GMS, true, false); /*
                                                                          Log.d("Data-Consumer-Update-DB", 
                                                                          _lastGranularSegTime.format("%H:%M:%S")
                                                                          );   */
        }
        _lastGranularPriceSeg = new OHLC(price);
        _lastGranularVolSeg = vol;

        if (!_streamReady.get() || _myCursor == null) {
            _updateTime.set(time);
            return;
        }
        /* Log.d("Data-Consumer-Update-Current", 
        _lastPriceSeg.toString() + "    "
        + String.valueOf(_lastVolSeg) + "   " 
        +time.format("%H:%M:%S"));            */

        if (price.high > _highPrice) {
            _highPrice = price.high;
            Intent iii = new Intent(HighLowChange.NEW_HIGH_PRICE);
            iii.putExtra(HighLowChange.EXTRA_VALUE, price.high);
            iii.putExtra(HighLowChange.FRAGMENT_ID, MY_ID);
            _localBcastMngr.sendBroadcast(iii);
        }
        if (price.low < _lowPrice) {
            _lowPrice = price.low;
            Intent iii = new Intent(HighLowChange.NEW_LOW_PRICE);
            iii.putExtra(HighLowChange.EXTRA_VALUE, price.low);
            iii.putExtra(HighLowChange.FRAGMENT_ID, MY_ID);
            _localBcastMngr.sendBroadcast(iii);
        }

        try { /* remember vol is cumm. vis-a-vis the granular seg */
            _mainSgmnt.update(_getBucketIndex(price.close), vol);
        } catch (RuntimeException e) { //DEBUG
            Log.d("UPDATE EXCEPTION", "::BEGIN:: " + e.getMessage());
            e.printStackTrace();
            Log.d("UPDATE EXCEPTION", "::END:: " + e.getMessage());
        }

        /* high volume is checked in the panel, where it is agglomerated */

        _updateTime.set(time);
    }

    @Override
    public void bridge(DataClientInterface dataClient, DataConsumerInterface.InsertCallback iCallback) {
        _updateReady.set(false);
        iCallback.clear(_symbol, (DataConsumerInterface) this);

        Pair<OHLC[], Time> pricePair = dataClient.getBulk(_symbol, null, DataClientLocalDebug.DATA_PRICE_DEF);
        Pair<Integer[], Time> volPair = dataClient.getBulk(_symbol, null, DataClientLocalDebug.DATA_VOL_DEF);

        final OHLC[] prices = pricePair.first;
        final Integer[] vols = volPair.first;
        int len = vols.length;
        if (prices.length != len)
            throw new IllegalStateException("price / volume getBulk size inconconsistency");

        _updateTime.set(pricePair.second);
        --len;
        long endMs = _updateTime.toMillis(true);
        endMs /= GMS;
        endMs *= GMS;
        List<ContentValues> valsList = new ArrayList<ContentValues>();
        Time t = new Time();
        for (int i = 0; i < len; ++i) {
            t.set(endMs - ((len - i) * GMS));
            valsList.add(_createContentValues(prices[i], vols[i], t));
            /*Log.d("Data-Consumer-Get-Bulk", 
               prices[i].toString() +"   " +
               String.valueOf(vols[i]) + "   " + 
               t.format("%H:%M:%S")); */
        }

        OHLC price = prices[len];
        _lastGranularPriceSeg = new OHLC(price);
        _lastGranularVolSeg = vols[len];
        _lastGranularSegTime.set(endMs);
        _updateReady.set(true);

        iCallback.insertRows(valsList, _symbol, (DataConsumerInterface) this);

    }

    @Override
    public List<Pair<String, SQLType>> getColumns() {
        return myColumnInfo;
    }

    public void reset() {
        cleanUpAndUpdate();
        _init();
    }

    public void cleanUpAndUpdate() {
        _streamReady.set(false);
        for (Thread t : _activeThreads)
            t.interrupt();
        _clear(); /* leave display values */
        _highPrice = 0;
        _lowPrice = Float.MAX_VALUE;
        _highVolume = 0;
    }

    public void axisRefresh() {
        try {
            _guiThrdHndlr.post(new Runnable() {
                public void run() {
                    _volumeAxis.invalidate();
                }
            });
        } catch (IllegalStateException e) {
            e.printStackTrace();
        }
    }

    /* adjust vertical zoom 
     * NEED TO REDRAW EVERYTHING FOR THIS
    public void setSegmentHeight(int h)
    {
    if( h > MAX_SEG_HEIGHT )
        h = MAX_SEG_HEIGHT;
    else if( h < MIN_SEG_HEIGHT )
        h = MIN_SEG_HEIGHT;    
    _volSegHght = h;
    _mainSgmnt.setSegmentHeight(h);            
    _reset();
    }*/
    public int getSegmentHeight() {
        return _volSegHght;
    }

    public void setAxisFontSize(int sp) {
        _axisFontSz = sp;
        reset();
    }

    public void setAxisFontColor(int colorId) {
        _axisFontColor = colorId;
        axisRefresh();
    }

    public void setAxisTimeout(int value) { /* should reset but cost not worth benefit*/
        _axisTimeout = value;
        axisRefresh();
    }

    public void setTimeoutIncrement(int value) {
        /* should reset but cost not worth benefit*/
        _axisTimeoutIncr = value;
        axisRefresh();
    }

    /*|--------->         end PUBLIC interface         <--------|*/
    /*|--------->         begin PRIVATE methods         <--------|*/

    // CONSIDER MAKING THESE GLOBAL UTILITIES
    private boolean _segHasRolled(Time t1, Time t2, int minutes) { /* this only checks up to hours, i.e. same time tomorrow 
                                                                     won't be treated as a roll (erroneously) */
        int rMin1 = t1.minute / minutes;
        int rMin2 = t2.minute / minutes;
        return (rMin1 != rMin2 || t1.hour != t2.hour);
    }

    private Time _truncateTime(Time t, long msInterval, boolean ignoreDST, boolean inPlace) {
        long ms = t.toMillis(ignoreDST);
        ms /= msInterval;
        ms *= msInterval;
        if (inPlace) {
            t.set(ms);
            return t;
        } else {
            Time nTime = new Time();
            nTime.set(ms);
            return nTime;
        }
    }

    private void _createVolumeAxisX() {
        Thread thisThread = new Thread() {
            public void run() {
                if (!_volumeAxisLck.tryLock())
                    return;
                try {
                    final YAxisPriceLabel tv = (YAxisPriceLabel) _inflater.inflate(R.layout.y_axis_price_label,
                            null);
                    tv.setText(Integer.toString(_highVolume));
                    tv.setTextSize(_axisFontSz);
                    tv.setTextColor(_axisFontColor);
                    _guiThrdHndlr.post(new Runnable() {
                        public void run() {
                            _volumeAxis.removeAllViewsInLayout();
                            _volumeAxis.addView(tv);
                        }
                    });
                    /*adjust volume axis to align*/
                    int tvWidth = 0;
                    int failCount = 0;
                    while ((tvWidth = tv.getWidth() / 2) == 0) {
                        Thread.sleep(_axisTimeoutIncr);
                        if ((failCount += _axisTimeoutIncr) >= _axisTimeout)
                            return; /* if timeout forget about align */
                    }
                    _mainSgmnt.setHorizontalBuffers(0, tvWidth);
                    _guiThrdHndlr.post(new Runnable() {
                        public void run() {
                            _mainSgmnt.forceDraw();
                        }
                    });
                } catch (InterruptedException e) {
                } catch (RuntimeException e) {
                    throw e;
                } finally {
                    _activeThreads.remove(this);
                    _volumeAxisLck.unlock();
                }
            }
        };
        _activeThreads.add(thisThread);
        thisThread.start();

    }

    private int _getBucketIndex(float p) {
        return (int) ((p - _lowPrice + (_segSize / 2f)) / _segSize);
    }

    /* sync to avoid race with _agglomerate */
    private synchronized void _findPriceRange() {
        float tmpHigh, tmpLow;
        _myCursor.moveToPosition(-1);
        while (_myCursor.moveToNext()) {
            tmpHigh = _myCursor.getFloat(2);
            tmpLow = _myCursor.getFloat(3);
            if (tmpHigh > _highPrice)
                _highPrice = tmpHigh;
            if (tmpLow < _lowPrice)
                _lowPrice = tmpLow;
        }
        _myCursor.moveToPosition(-1);
    }

    /// adjust high and low the assumed high and low from
    // end buckets/segs
    private synchronized void _agglomerate() {
        // should we just pass these in vs global??
        if (_segCount <= 0 || _segSize <= 0) {
            throw new IllegalStateException("_segCount and _segSize can't be <= 0");
        }
        final int[] buckets = new int[_segCount];
        Arrays.fill(buckets, 0);
        float mPrice;
        int gVol;
        _myCursor.moveToPosition(-1);
        /* loop through and agglomerate the volumes by bucket */
        while (_myCursor.moveToNext()) {
            mPrice = /* take the midpoint of the OHLC */
                    (_myCursor.getFloat(2) + _myCursor.getFloat(3)) / 2;
            gVol = _myCursor.getInt(5);
            buckets[_getBucketIndex(mPrice)] += gVol;
        }
        _myCursor.moveToPosition(-1);

        /* pass the buckets to the panel */
        _mainSgmnt.populate(buckets); //, true);
        _streamReady.set(true);
    }

    private Thread _createPriceAxisAndMainPanel() {
        Thread thisThread = new Thread() {
            public void run() {
                ///////////////////////////////////
                /*     START PREEMPTION LOGIC     */
                /////////////////////////////////
                boolean noWait;
                final int MAX_PREEMPT_COUNT = ApplicationPreferences.getMaxPreemptCount();
                if (!(noWait = _priceAxisLck.tryLock())) {
                    synchronized (_priceAxisThrdMntr) {
                        if (_priceAxisPrmptCnt++ > MAX_PREEMPT_COUNT)
                            return;
                        if (_priceAxisThrd != null)
                            _priceAxisThrd.interrupt();
                    }
                    _priceAxisPrmptStck.offer(this);
                    _priceAxisLck.lock();
                }
                try { /* everything that follows should assume we own monitor */
                    if (!noWait) {
                        if (this != _priceAxisPrmptStck.peekLast())
                            return;
                        else { /* don't assume stack hasn't grown since the peek */
                            Iterator<Thread> dIter = _priceAxisPrmptStck.iterator();
                            do {
                                _priceAxisPrmptStck.poll();
                            } while (dIter.next() != this);
                        }
                    }
                    synchronized (_priceAxisThrdMntr) {
                        _priceAxisThrd = this;
                    }
                    /////////////////////////////////
                    /*     END PREEMPTION LOGIC     */
                    //////////////////////////////                            

                    float rangeDiff = _highPrice - _lowPrice;
                    /* deal with problematic high/low parameters */
                    if (rangeDiff <= 0 || _highPrice < 0 || _lowPrice < 0)
                        if (rangeDiff == 0 && _highPrice > 0) {
                            _highPrice *= 1.001;
                            _lowPrice *= .999;
                            rangeDiff = _highPrice - _lowPrice;
                        } else
                            throw new IllegalStateException("Invalid high and/or low price in the ChartAdapter");
                    /* if we haven't calculated the height of a price-label view */
                    if (_priceElemHght == 0) {/* can cached value doesn't go stale? */
                        final YAxisPriceLabel tv1 = (YAxisPriceLabel) _inflater.inflate(R.layout.y_axis_price_label,
                                null);
                        tv1.setText("X");
                        tv1.setTextSize(_axisFontSz);
                        tv1.setVisibility(View.INVISIBLE);
                        final ConditionVariable cond = new ConditionVariable();
                        _guiThrdHndlr.post(new Runnable() {
                            public void run() {
                                _priceAxis.removeAllViews();
                                _priceAxis.addView(tv1);
                                cond.open();
                            }
                        });
                        cond.block();
                        cond.close();
                        YAxisPriceLabel tv1b = (YAxisPriceLabel) _priceAxis.getChildAt(0);
                        /* make sure a valid priceElemHeightt, or quit entirely */
                        /* just spin, a new thread preempts us anyway */
                        while ((_priceElemHght = tv1b.getHeight()) == 0)
                            Thread.sleep(_axisTimeoutIncr);
                    }
                    _guiThrdHndlr.post(new Runnable() {
                        public void run() {
                            _priceAxis.removeAllViews();
                        }
                    });
                    int totalHeight;
                    /* make sure a valid totalHeight, or quit entirely */
                    /* just spin, a new thread preempts us anyway */
                    while ((totalHeight = _priceAxis.getHeight()) == 0 || totalHeight > _myContainer.getHeight())
                        Thread.sleep(_axisTimeoutIncr);
                    float[] incrVals = new float[2];
                    try {
                        int maxNodes = (int) (totalHeight / _priceElemHght);
                        if (rangeDiff < 0 || maxNodes < 0)
                            throw new PriceByVolumeChartLogicError("rangeDiff and maxNodes can't be negative.");
                        incrVals = /* call down to our native sub to find increment values */
                                MainApplication.IncrementRegressNative(rangeDiff, maxNodes);
                        if (incrVals[0] < 0 || incrVals[1] <= 0)
                            throw new PriceByVolumeChartLogicError("IncrementRegressNative() sub-routine aborted. "
                                    + "retVals[0]: " + incrVals[0] + "retVals[1]: " + incrVals[1] + "adjRangeDiff: "
                                    + rangeDiff + "maxNodes" + maxNodes);
                        _segCount = (int) incrVals[1];
                        _segSize = incrVals[0];
                    } catch (PriceByVolumeChartLogicError e) {
                        Log.e("PriceByVolumeChartLogicError", e.getMessage());
                        return; /* just leave the axis empty*/
                    }

                    /* adjust height to new increment values */
                    final int adjPriceElemHeight = (int) (totalHeight / _segCount);
                    /* we'll need to account for any unused spaced in the axis */
                    final int vacantHeight = totalHeight - (int) (adjPriceElemHeight * _segCount);
                    double distFromIncr = Math.IEEEremainder((double) _highPrice,
                            (double) _segSize); /* distance from rounded incr value */
                    double adjTopNodeVal = (double) _highPrice - distFromIncr;
                    if (distFromIncr > 0) /* be sure to round to the upper incr */
                        adjTopNodeVal += _segSize;
                    DecimalFormat df = new DecimalFormat("0.00");
                    double lastNodeVal = adjTopNodeVal;
                    int count = 0;
                    do { /* loop through the increments */
                        final YAxisPriceLabel tv = (YAxisPriceLabel) _inflater.inflate(R.layout.y_axis_price_label,
                                null);
                        tv.setTextSize(_axisFontSz);
                        tv.setText(df.format(lastNodeVal));
                        tv.setTextColor(_axisFontColor);
                        tv.setLayoutParams(
                                new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, 0, 1));
                        _guiThrdHndlr.post(new Runnable() {
                            public void run() {
                                _priceAxis.addView(tv);
                            }
                        }); /* for the loop-contingent side-effect */
                    } while (++count < (int) _segCount && (lastNodeVal -= _segSize) > 0);
                    final float halfVacantHeight = vacantHeight / 2;
                    _mainSgmnt.setVerticalBuffers((int) halfVacantHeight, (int) halfVacantHeight);
                    _guiThrdHndlr.post(new Runnable() {
                        public void run() {
                            _priceAxis.setPadding( /* center price-views */
                                    0, (int) halfVacantHeight, 0, (int) halfVacantHeight);
                            _priceAxis.setClipChildren(false);
                            _priceAxis.setClipToPadding(false);
                            _priceAxis.invalidate();
                        }
                    });

                    synchronized (_priceAxisThrdMntr) {
                        _priceAxisPrmptCnt = 0;
                    }
                } catch (InterruptedException e) {
                } catch (IllegalStateException e) {
                    Log.d("IllegalState(High, Low): ",
                            "IllegalStateException caught in _createPriceAxisAndMainPanelY: "
                                    + Float.toString(_highPrice) + " - " + Float.toString(_lowPrice));
                } catch (RuntimeException e) {
                    e.printStackTrace();
                    throw e;
                } finally {

                    synchronized (_priceAxisThrdMntr) {
                        _priceAxisThrd = null;
                    }
                    _activeThreads.remove(this);
                    _priceAxisLck.unlock();
                }
            }
        };
        _activeThreads.add(thisThread);
        thisThread.start();
        return thisThread;
    }

    private void _setPricePanelDrawSemantics() {
        _mainSgmnt.setDrawSemantics(DrawSemantics_SILO.class);
    }

    private ContentValues _createContentValues(OHLC price, int volume, Time t) {
        ContentValues tmp = new ContentValues();
        tmp.put(DataConsumerInterface.primaryKey, t.toMillis(true));
        tmp.put(columns[0], price.open);
        tmp.put(columns[1], price.high);
        tmp.put(columns[2], price.low);
        tmp.put(columns[3], price.close);
        tmp.put(columns[4], volume);
        return tmp;
    }

    private void _init() {
        if (_myCursor != null) {
            final int INCR = ApplicationPreferences.getTimeoutIncrement();
            final int TOUT = ApplicationPreferences.getLongTimeout();
            int timeOutCount = 0;
            /* stall for draw completion */
            while (_mainSgmnt.getMeasuredHeight() == 0 && (timeOutCount += INCR) < TOUT)
                try {
                    Thread.sleep(INCR);
                } catch (InterruptedException e) {
                }
            try {
                _findPriceRange();
                _createVolumeAxisX();
                _createPriceAxisAndMainPanel().join();
            } catch (InterruptedException e) {
            } finally {
                _agglomerate();
                _mainSgmnt.forceDraw();
            }
        } else
            throw new IllegalStateException(
                    "ChartFragmentAdapter can not be initialized " + "without a valid Cursor");
    }

    private void _clear() {
        _activeThreads.clear();
        _mainSgmnt.clear();
        _priceAxis.removeAllViewsInLayout();
        _volumeAxis.removeAllViewsInLayout();
    }

    /*|--------->         end PRIVATE methods         <--------|*/
    /*|--------->     begin NESTED OBJECT DEFS        <--------|*/

    /* APPLICATION WIDE BROADCASTS: broadcast when a price
     * or volume update is at a new extreme. Currently these broadcasts
     * are only relevant within the adapter. */
    class HighLowChange extends BroadcastReceiver {
        public static final String NEW_HIGH_PRICE = "com.jogden.integratedtrader.NEW_HIGH_PRICE";
        public static final String NEW_LOW_PRICE = "com.jogden.integratedtrader.NEW_LOW_PRICE";
        public static final String NEW_HIGH_VOL = "com.jogden.integratedtrader.NEW_HIGH_VOL";
        public static final String EXTRA_VALUE = "EXTRA_VALUE";
        public static final String FRAGMENT_ID = "FRAGMENT_ID";

        @Override
        public void onReceive(Context c, Intent i) { /* if its for another chart exit quietly */
            if (!i.getStringExtra(FRAGMENT_ID).equals(MY_ID))
                return;
            String action = i.getAction();
            float price;
            if (action.equals(HighLowChange.NEW_HIGH_PRICE)) {
                price = i.getFloatExtra(EXTRA_VALUE, _highPrice);
                if (price > _highPrice) {
                    _highPrice = price;
                    try {
                        _createPriceAxisAndMainPanel().join();
                    } catch (InterruptedException e) {
                    } finally {
                        _agglomerate();
                    }
                }
            } else if (action.equals(HighLowChange.NEW_LOW_PRICE)) {
                price = i.getFloatExtra(EXTRA_VALUE, _lowPrice);
                if (price < _lowPrice) {
                    _lowPrice = price;
                    try {
                        _createPriceAxisAndMainPanel().join();
                    } catch (InterruptedException e) {
                    } finally {
                        _agglomerate();
                    }
                }
            } else if (action.equals(HighLowChange.NEW_HIGH_VOL)) {
                int vol = i.getIntExtra(EXTRA_VALUE, _highVolume);
                if (vol > _highVolume) {
                    _highVolume = vol;
                    _createVolumeAxisX();
                }
            }
        }
    }

}