/*
i-net software provides programming examples for illustration only, without warranty
either expressed or implied, including, but not limited to, the implied warranties
of merchantability and/or fitness for a particular purpose. This programming example
assumes that you are familiar with the programming language being demonstrated and
the tools used to create and debug procedures. i-net software support professionals
can help explain the functionality of a particular procedure, but they will not modify
these examples to provide added functionality or construct procedures to meet your
specific needs.
© i-net software 1998-2013
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Serialization;
using System.Drawing;
namespace Inet.Viewer.Data
{
///
/// This class is doing all the Page Caching, so that the data does not need to be
/// received from the server.
///
internal partial class ReportDataCache : IDisposable
{
private enum KeyType
{
Page, Font, Count, Group, PageLimit
}
///
/// A class that combines the two keys. This is needed as we only need one list
///
private class PageCacheKey
{
private readonly KeyType type;
private readonly int pageNumber;
///
///
///
/// The type of data
/// the id of data
internal PageCacheKey(KeyType type, int pageNumber)
{
this.type = type;
this.pageNumber = pageNumber;
}
///
/// override Equals to make the caching happening
///
///
///
public override bool Equals(object toCheck)
{
PageCacheKey equals = toCheck as PageCacheKey;
return equals != null && this.type == equals.type && this.pageNumber == equals.pageNumber;
}
///
/// Override HashCode to ensure correct handling in the Collection
///
/// the combined HasCode of ReportData and PageNumber
public override int GetHashCode()
{
return (int)type * 31 + pageNumber.GetHashCode();
}
}
// 10 MB
private const int MaxCacheSize = 10000000;
// cacheSize in Bytes
private int cacheSize = 0;
private readonly IRenderData reportData;
// the cache collection
private Dictionary pageDataDict = new Dictionary();
// for the font caching
private Dictionary fontDataDict = new Dictionary();
// fifo list needed for removing items when the cache is full
private List fifoList = new List();
///
/// constructor
///
internal ReportDataCache(IRenderData renderData)
{
this.reportData = renderData;
}
///
/// The size of the cache in bytes
///
public int CacheSize
{
get
{
return cacheSize;
}
set
{
cacheSize = value;
if (IsCacheFull())
{
ReduceCache();
}
}
}
internal IRenderData ReportData
{
get { return reportData; }
}
///
/// Clears the Cache and resets the CacheSize
///
public void Clear()
{
lock (pageDataDict)
{
this.fifoList.Clear();
this.pageDataDict.Clear();
this.fontDataDict.Clear();
this.cacheSize = 0;
}
}
///
/// Clear the memory
///
public void Dispose()
{
Clear();
}
///
/// returns the page data,
///
///
/// if true the data will be received from server, even if the data is cached
///
internal byte[] PageData(int page, bool refresh)
{
if (refresh)
{
Clear();
}
return (byte[])GetData(KeyType.Page, page, refresh);
}
internal int PageCount()
{
return (int)GetData(KeyType.Count, 0, false);
}
internal byte[] FontDataBytes(int embeddedFontID, int revision, int oldRevision)
{
if (revision > oldRevision)
{
lock (pageDataDict)
{
pageDataDict.Remove(new PageCacheKey(KeyType.Font, embeddedFontID));
}
}
return (byte[])GetData(KeyType.Font, embeddedFontID, false);
}
internal byte[] GetGroupTree()
{
return (byte[])GetData(KeyType.Group, 0, false);
}
internal bool IsPageLimmitExcced()
{
return (bool)GetData(KeyType.PageLimit, 0, false);
}
private object GetData(KeyType type, int id, bool refresh)
{
PageCacheKey cacheKey = new PageCacheKey(type, id);
// find a monitor for the request
lock (pageDataDict)
{
object data;
pageDataDict.TryGetValue(cacheKey, out data);
if (data != null && data.GetType() != typeof(PageCacheKey))
{
// return cached page data
// put it to the top of the fifo List
fifoList.Remove(cacheKey);
fifoList.Add(cacheKey);
return data;
}
if (data == null)
{
pageDataDict[cacheKey] = cacheKey; // save the monitor
}
else
{
cacheKey = (PageCacheKey)data; // use it as monitor
}
}
// get page data from server
lock (cacheKey)
{
object data;
lock (pageDataDict)
{
// we are in the lock of the cacheKey, check if another thread has put an result for this cacheKey
pageDataDict.TryGetValue(cacheKey, out data);
if (data != cacheKey)
{
if (data != null && data.GetType() != typeof(PageCacheKey))
{
return data; // the data are different, it must be valid data
}
else
{
pageDataDict[cacheKey] = cacheKey; // save the monitor after a clear
}
}
}
byte[] bytes;
int length;
switch (type)
{
case KeyType.Page:
data = bytes = reportData.GetPageData(id, refresh);
length = bytes.Length;
break;
case KeyType.Font:
data = bytes = reportData.GetFontData(id);
length = bytes.Length;
break;
case KeyType.Count:
data = reportData.GetPageCount();
length = 0;
break;
case KeyType.Group:
data = bytes = reportData.GetGrouptreeData();
length = bytes.Length;
break;
case KeyType.PageLimit:
data = reportData.IsPageLimitExceeded;
length = 0;
break;
default:
throw new InvalidOperationException();
}
lock (pageDataDict)
{
pageDataDict[cacheKey] = data; // put the requested data
fifoList.Add(cacheKey);
this.CacheSize += length;
}
return data;
}
}
///
/// Brings the cache size back to MAX_CACHE_SIZE
///
private void ReduceCache()
{
// remove last used item from the list
while (IsCacheFull() && fifoList.Count > 1)
{
PageCacheKey removeKey = fifoList[0];
if (pageDataDict.ContainsKey(removeKey))
{
byte[] value = pageDataDict[removeKey] as byte[];
if (value != null)
{
int removeSize = value.Length;
this.cacheSize -= removeSize;
}
pageDataDict.Remove(removeKey);
}
fifoList.RemoveAt(0);
}
}
///
/// Checks if the Cache has reached maximum capacity depending on the constant MAX_CACHE_SIZE
///
///
private bool IsCacheFull()
{
// if cache size is over 10 MB and has more than one page (for the case there is one page with data over 10 MB)
return cacheSize > MaxCacheSize && fifoList.Count >= 1;
}
///
/// Gets the requested Font with the version if version >= fontRev. If needed gets the font data from the ReportData
/// and creates the font.
///
/// FontID
/// Version of the Font that should be fetched
/// Was in ViewerUtils in java version
/// Embedded Font, that has at the version number of fontRev or higher
internal Font GetEmbeddedFont(int embeddedFontID, int fontRevision)
{
lock (fontDataDict)
{
FontData oldFont;
fontDataDict.TryGetValue(embeddedFontID, out oldFont);
// case 1. Font wasen't loaded yet --> fetch, create, add
// case 2. old version of the font --> old version of the fonts --> fetch, create, replace old by new font
// case 3. current version of the font --> return this font
if (oldFont == null || oldFont.Revision < fontRevision)
{
// If font is new or not up-to-date: fetch from ReportData.
byte[] fontData = FontDataBytes(embeddedFontID, fontRevision, oldFont == null ? 0 : oldFont.Revision);
if (fontData != null)
{
// Convert Font-Data to Font with the FontLoader
FontData font = new FontLoader().ReadFont(fontData);
fontDataDict[embeddedFontID] = font;
// case 1 + 2:
if (oldFont != null)
{
ViewerUtils.Debug("replacing font " + oldFont.Font.Name + oldFont.Font.Style + " with "
+ font.Font.Name + font.Font.Style);
}
return font.Font;
}
// PROBLEMS reading the font
// (Error -> return Default-Font )
ViewerUtils.Debug("Problems reading Font " + embeddedFontID);
// 150 Twips
return new Font(FontFamily.GenericSansSerif, 150, FontStyle.Regular);
}
else
{
// 3. Font is known and up-to-date: nothing needs to be done
return oldFont.Font;
}
}
}
}
}