/* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.redgeek.android.eventrend.primitives; import java.util.ArrayList; import java.util.Calendar; import java.util.HashMap; import java.util.Iterator; import net.redgeek.android.eventrend.db.EntryDbTable; import net.redgeek.android.eventrend.db.EvenTrendDbAdapter; import net.redgeek.android.eventrend.util.DateUtil; import net.redgeek.android.eventrend.util.DateUtil.Period; import android.database.Cursor; import android.util.Log; public class DatapointCache { private HashMap<Long, CategoryDatapointCache> mCache; private EvenTrendDbAdapter mDbh; public DatapointCache(EvenTrendDbAdapter dbh) { mDbh = dbh; mCache = new HashMap<Long, CategoryDatapointCache>(); } public void clearCache() { Iterator<CategoryDatapointCache> itr = mCache.values().iterator(); while (itr.hasNext()) { ((CategoryDatapointCache) itr.next()).clear(); } } public void clearCache(long catId) { CategoryDatapointCache catCache = mCache.get(Long.valueOf(catId)); if (catCache != null) catCache.clear(); return; } public void addCacheableCategory(long catId, int history) { mCache.put(new Long(catId), new CategoryDatapointCache(catId, history)); } public void setHistory(long catId, int history) { CategoryDatapointCache catCache = mCache.get(Long.valueOf(catId)); if (catCache != null) { catCache.setHistory(history); } return; } public long getCategoryCacheStart(long catId) { CategoryDatapointCache catCache = mCache.get(Long.valueOf(catId)); if (catCache == null || catCache.isValid() == false) return Long.MAX_VALUE; return catCache.getStart(); } public long getCategoryCacheEnd(long catId) { CategoryDatapointCache catCache = mCache.get(Long.valueOf(catId)); if (catCache == null || catCache.isValid() == false) return Long.MIN_VALUE; return catCache.getEnd(); } public boolean isCategoryCacheValid(long catId) { CategoryDatapointCache catCache = mCache.get(Long.valueOf(catId)); if (catCache == null) return false; return catCache.isValid(); } public synchronized void refresh(long catId) { CategoryDatapointCache catCache = mCache.get(Long.valueOf(catId)); if (catCache == null || catCache.isValid() == false) return; long start = catCache.getStart(); long end = catCache.getEnd(); catCache.clear(); populateRangeFromDb(catCache, start, end); } public synchronized void populateLatest(long catId, int nItems) { ArrayList<Datapoint> append = new ArrayList<Datapoint>(); CategoryDatapointCache catCache; catCache = mCache.get(Long.valueOf(catId)); if (catCache == null) return; boolean initialized = catCache.isValid(); long oldStart = catCache.getStart(); long oldEnd = catCache.getEnd(); boolean overlap = false; long lastEntryTS = Long.MAX_VALUE; Cursor c = mDbh.fetchRecentCategoryEntries(catId, nItems); int count = c.getCount(); if (count < 1) { c.close(); return; } EntryDbTable.Row entry = new EntryDbTable.Row(); c.moveToFirst(); for (int i = 0; i < count; i++) { entry.populateFromCursor(c); if (entry.getTimestamp() < lastEntryTS) lastEntryTS = entry.getTimestamp(); if (entry.getTimestamp() <= oldEnd && entry.getTimestamp() >= oldStart) overlap = true; else // we can't append the datapoints directly to the cache here, since that // may create a hole (and the caches are expected to be contiguous., so // save the datapoints away and add them after we fetch the hole append.add(new Datapoint(entry)); c.moveToNext(); } c.close(); if (initialized == true && overlap == false) { // Fill in any hole between the old cache and the recent data populateRangeFromDb(catCache, oldEnd + 1, lastEntryTS - 1); } for (int i = 0; i < append.size(); i++) catCache.addDatapoint(append.get(i)); return; } // inclusive public synchronized void populateRange(long catId, long milliStart, long milliEnd, long aggregationMs) { CategoryDatapointCache catCache = mCache.get(Long.valueOf(catId)); if (catCache == null) return; if (aggregationMs == 0) { // we actually double the range, hoping to gather data from each category // that we can use to connect the first datapoint to an offscreen // datapoint // (earlier in time) and to use the previous data to make the trend lines // accurate at the beginning of the chart. Same goes for the last // offscreen // datapoint (later in time). // TODO: fix to guarantee this, instead of hoping -- and do it with a // miminum // of sql queries. long quarter = (milliEnd - milliStart) / 4; milliStart -= quarter * 2; milliEnd += quarter; } else { // This fixup looks similar to the previous in // TimerSeriesCollector::gatherSeries, // however, the purpose of this one is to make sure we grab try to grab X // datapoints, // which during aggregation, are 1 per period. Note this is still a guess, // as the // previous 2*X periods may not actually contain >= X datapoints, so we // still may // mess up the trend because we're short on datapoints. Same goes for the // endMillis // adjustment. Calendar c1 = Calendar.getInstance(); Calendar c2 = Calendar.getInstance(); Period p = DateUtil.mapLongToPeriod(aggregationMs); int step = 2; if (p == Period.QUARTER) step = 6; c1.setTimeInMillis(milliStart); DateUtil.setToPeriodStart(c1, p); c1.add(DateUtil.mapLongToCal(aggregationMs), -(catCache.getHistory() * step)); milliStart = c1.getTimeInMillis(); c2.setTimeInMillis(milliEnd); DateUtil.setToPeriodStart(c2, p); c2.add(DateUtil.mapLongToCal(aggregationMs), step); milliEnd = c2.getTimeInMillis(); } populateRangeFromDb(catCache, milliStart, milliEnd); } private synchronized void populateRangeFromDb( CategoryDatapointCache catCache, long milliStart, long milliEnd) { long query1Start = milliStart; long query1End = milliEnd; long query2Start = -1; long query2End = -1; if (milliStart > milliEnd) return; // SQL ops are the expensive thing, so see if we can minimize or obviate the // query // by checking the cache. Note that if we're extending the cache in two // direction // (asking for a superset of the cache), we turn this into two queries for // the pre // and post. if (catCache.isValid() == true) { if (milliStart >= catCache.getStart() && milliEnd <= catCache.getEnd()) return; } if (catCache.isValid() == false) { query1Start = milliStart; query1End = milliEnd; } else { if (milliStart < catCache.getStart()) { query1Start = milliStart; query1End = catCache.getStart() - 1; if (milliEnd > catCache.getEnd()) { query2Start = catCache.getEnd() + 1; query2End = milliEnd; } } if (milliStart >= catCache.getStart()) { query1Start = catCache.getEnd() + 1; query1End = milliEnd; } } addDatapoints(catCache, query1Start, query1End); if (query2Start >= 0 && query2End >= 0) addDatapoints(catCache, query2Start, query2End); return; } private void addDatapoints(CategoryDatapointCache catCache, long start, long end) { EntryDbTable.Row entry = new EntryDbTable.Row(); Cursor entries = mDbh.fetchCategoryEntriesRange(catCache.getCategoryId(), start, end); if (entries != null && entries.getCount() > 0) { entries.moveToFirst(); for (int j = 0; j < entries.getCount(); j++) { entry.populateFromCursor(entries); catCache.addDatapoint(new Datapoint(entry)); entries.moveToNext(); } } entries.close(); catCache.updateStart(start); catCache.updateEnd(end); } public ArrayList<Datapoint> getDataInRange(long catId, long msStart, long msEnd) { CategoryDatapointCache catCache = mCache.get(Long.valueOf(catId)); if (catCache == null) return null; return catCache.getDataInRange(msStart, msEnd); } public ArrayList<Datapoint> getDataBefore(long catId, int number, long ms) { CategoryDatapointCache catCache = mCache.get(Long.valueOf(catId)); if (catCache == null) return null; return catCache.getDataBefore(number, ms); } public ArrayList<Datapoint> getDataAfter(long catId, int number, long ms) { CategoryDatapointCache catCache = mCache.get(Long.valueOf(catId)); if (catCache == null) return null; return catCache.getDataAfter(number, ms); } public ArrayList<Datapoint> getLast(long catId, int number) { CategoryDatapointCache catCache = mCache.get(Long.valueOf(catId)); if (catCache == null) return null; return catCache.getLast(number); } }