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