All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hcl.domino.jna.internal.views.NotesLookupResultBufferDecoder Maven / Gradle / Ivy

There is a newer version: 1.44.0
Show newest version
/*
 * ==========================================================================
 * Copyright (C) 2019-2022 HCL America, Inc. ( http://www.hcl.com/ )
 *                            All rights reserved.
 * ==========================================================================
 * 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 .
 *
 * 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 com.hcl.domino.jna.internal.views;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.EnumSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;

import com.hcl.domino.DominoException;
import com.hcl.domino.commons.data.AbstractTypedAccess;
import com.hcl.domino.commons.data.DefaultDominoDateRange;
import com.hcl.domino.commons.util.NotesDateTimeUtils;
import com.hcl.domino.commons.util.NotesErrorUtils;
import com.hcl.domino.commons.views.IItemTableData;
import com.hcl.domino.commons.views.IItemValueTableData;
import com.hcl.domino.commons.views.NotesCollectionStats;
import com.hcl.domino.commons.views.ReadMask;
import com.hcl.domino.data.Database;
import com.hcl.domino.data.Database.OpenDocumentMode;
import com.hcl.domino.data.Document;
import com.hcl.domino.data.DominoCollection;
import com.hcl.domino.data.DominoDateTime;
import com.hcl.domino.data.ItemDataType;
import com.hcl.domino.data.TypedAccess;
import com.hcl.domino.jna.data.JNACollectionEntry;
import com.hcl.domino.jna.data.JNADocument;
import com.hcl.domino.jna.data.JNADominoCollection;
import com.hcl.domino.jna.data.JNADominoDateTime;
import com.hcl.domino.jna.internal.ItemDecoder;
import com.hcl.domino.jna.internal.JNANotesConstants;
import com.hcl.domino.jna.internal.LMBCSString;
import com.hcl.domino.jna.internal.Mem;
import com.hcl.domino.jna.internal.NotesStringUtils;
import com.hcl.domino.jna.internal.gc.handles.DHANDLE;
import com.hcl.domino.jna.internal.gc.handles.LockUtil;
import com.hcl.domino.jna.internal.search.ItemTableDataDocAdapter;
import com.hcl.domino.jna.internal.structs.NotesCollectionStatsStruct;
import com.hcl.domino.jna.internal.structs.NotesItemTableLargeStruct;
import com.hcl.domino.jna.internal.structs.NotesItemTableStruct;
import com.sun.jna.Pointer;

/**
 * Utility class to decode the buffer returned by data lookups, e.g. in {@link DominoCollection}s
 * and for database searches.
 * 
 * @author Karsten Lehmann
 */
public class NotesLookupResultBufferDecoder {
	
	/**
	 * Decodes the buffer
	 * 
	 * @param parentCollection parent collection
	 * @param bufferHandle buffer handle
	 * @param numEntriesSkipped entries skipped during collection scan
	 * @param numEntriesReturned entries read during collection scan
	 * @param returnMask bitmask used to fill the buffer with data
	 * @param signalFlags signal flags returned by NIFReadEntries, e.g. whether we have more data to read
	 * @param pos position to add to NotesViewLookupResultData object in case view data is read via {@link JNADominoCollection#findByKeyExtended2(Set, Set, Object...)}
	 * @param indexModifiedSequenceNo index modified sequence no
	 * @param retDiffTime only set in {@link JNADominoCollection#readEntriesExt(com.hcl.domino.jna.data.JNADominoCollectionPosition, com.hcl.domino.data.Navigate, boolean, int, com.hcl.domino.data.Navigate, int, Set, DominoDateTime, com.hcl.domino.jna.data.JNAIDTable, Integer)}
	 * @param convertStringsLazily true to delay string conversion until the first use
	 * @param convertDominoDateTimeToCalendar true to convert {@link JNADominoDateTime} values to {@link Calendar}
	 * @param singleColumnLookupName for single column lookups, programmatic name of lookup column
	 * @return collection data
	 */
	public static NotesViewLookupResultData decodeCollectionLookupResultBuffer(JNADominoCollection parentCollection,
			DHANDLE bufferHandle, int numEntriesSkipped, int numEntriesReturned,
			Set returnMask, short signalFlags, String pos, int indexModifiedSequenceNo, DominoDateTime retDiffTime,
			boolean convertStringsLazily, boolean convertDominoDateTimeToCalendar, String singleColumnLookupName) {
		
		return LockUtil.lockHandle(bufferHandle, (handleByVal) -> {
			Pointer bufferPtr = Mem.OSLockObject(handleByVal);
			try {
				return decodeCollectionLookupResultBuffer(parentCollection, bufferPtr, numEntriesSkipped,
						numEntriesReturned, returnMask, signalFlags, pos, indexModifiedSequenceNo, retDiffTime,
						convertStringsLazily, convertDominoDateTimeToCalendar, singleColumnLookupName);
			}
			finally {
				Mem.OSUnlockObject(handleByVal);
				short result = Mem.OSMemFree(handleByVal);
				NotesErrorUtils.checkResult(result);
			}
		});
	}
	
	/**
	 * Decodes the buffer
	 * 
	 * @param parentCollection parent collection
	 * @param bufferPtr buffer pointer
	 * @param numEntriesSkipped entries skipped during collection scan
	 * @param numEntriesReturned entries read during collection scan
	 * @param returnMask bitmask used to fill the buffer with data
	 * @param signalFlags signal flags returned by NIFReadEntries, e.g. whether we have more data to read
	 * @param pos position to add to NotesViewLookupResultData object in case view data is read via {@link JNADominoCollection#findByKeyExtended2(Set, Set, Object...)}
	 * @param indexModifiedSequenceNo index modified sequence no
	 * @param retDiffTime only set in {@link JNADominoCollection#readEntriesExt(com.hcl.domino.jna.data.JNADominoCollectionPosition, com.hcl.domino.data.Navigate, boolean, int, com.hcl.domino.data.Navigate, int, Set, DominoDateTime, com.hcl.domino.jna.data.JNAIDTable, Integer)}
	 * @param convertStringsLazily true to delay string conversion until the first use
	 * @param convertDominoDateTimeToCalendar true to convert {@link JNADominoDateTime} values to {@link Calendar}
	 * @param singleColumnLookupName for single column lookups, programmatic name of lookup column
	 * @return collection data
	 */
	public static NotesViewLookupResultData decodeCollectionLookupResultBuffer(JNADominoCollection parentCollection,
			Pointer bufferPtr, int numEntriesSkipped, int numEntriesReturned,
			Set returnMask, short signalFlags, String pos, int indexModifiedSequenceNo, DominoDateTime retDiffTime,
			boolean convertStringsLazily, boolean convertDominoDateTimeToCalendar, String singleColumnLookupName) {

		int bufferPos = 0;
		
		NotesCollectionStats collectionStats = null;

		if (returnMask.contains(ReadMask.COLLECTIONSTATS)) {
			NotesCollectionStatsStruct tmpStats = NotesCollectionStatsStruct.newInstance(bufferPtr);
			tmpStats.read();
			
			collectionStats = new NotesCollectionStats(tmpStats.TopLevelEntries, tmpStats.LastModifiedTime);
					
			bufferPos += tmpStats.size();
		}

		List viewEntries = new ArrayList<>();
		
		final boolean decodeAllValues = true;

		if (returnMask.size()==1 && returnMask.contains(ReadMask.NOTEID)) {
			//special optimized case for reading only note ids
			int[] noteIds = new int[numEntriesReturned];
			bufferPtr.read(0, noteIds, 0, numEntriesReturned);
			
			for (int i=0; i itemValues = itemTableData.asMap(false);
					newData.setSummaryData(itemValues);
				}
				if (singleColumnLookupName!=null) {
					newData.setSingleColumnLookupName(singleColumnLookupName);
				}
			}
		}
		
		return new NotesViewLookupResultData(collectionStats, viewEntries, numEntriesSkipped, numEntriesReturned, signalFlags, pos, indexModifiedSequenceNo, retDiffTime);
	}

	/**
	 * Produces an item table by decoding an ITEM_VALUE_TABLE structure, which contains an ordered list of item values,
	 * and adding an array of column names
	 * 
	 * @param db parent database
	 * @param noteId note ID of document
	 * @param columnFormulasFixedOrder column item names/formulas in a fixed order (matching the summary buffer content)
	 * @param bufferPtr pointer to a buffer
	 * @param convertStringsLazily true to delay string conversion until the first use
	 * @param convertJNADominoDateTimeToCalendar true to convert {@link JNADominoDateTime} values to {@link Calendar}
	 * @param decodeAllValues true to decode all values in the buffer
	 * @return item value table data
	 */
	public static IItemTableData decodeItemValueTableWithColumnNames(
			Database db,
			int noteId,
			LinkedHashMap columnFormulasFixedOrder,
			Pointer bufferPtr, boolean convertStringsLazily, boolean convertJNADominoDateTimeToCalendar, boolean decodeAllValues) {
		
		ItemValueTableDataImpl valueTable = (ItemValueTableDataImpl) decodeItemValueTable(bufferPtr, convertStringsLazily, convertJNADominoDateTimeToCalendar, decodeAllValues);
		IItemTableData itemTableData = new ItemTableDataImpl(db, noteId, columnFormulasFixedOrder, valueTable);
		return itemTableData;
	}
	
	/**
	 * Produces an item table by decoding an ITEM_VALUE_TABLE structure, which contains an ordered list of item values,
	 * and adding an array of column names
	 * 
	 * @param db parent database
	 * @param noteId note ID of document
	 * @param columnFormulasFixedOrder column item names/formulas in a fixed order (matching the summary buffer content)
	 * @param bufferPtr pointer to a buffer
	 * @param convertStringsLazily true to delay string conversion until the first use
	 * @param convertJNADominoDateTimeToCalendar true to convert {@link JNADominoDateTime} values to {@link Calendar}
	 * @param decodeAllValues true to decode all values in the buffer
	 * @return item value table data
	 */
	public static IItemTableData decodeItemValueTableLargeWithColumnNames(
			Database db,
			int noteId,
			LinkedHashMap columnFormulasFixedOrder,
			Pointer bufferPtr, boolean convertStringsLazily, boolean convertJNADominoDateTimeToCalendar, boolean decodeAllValues) {

		ItemValueTableDataImpl valueTable = (ItemValueTableDataImpl) decodeItemValueTableLarge(bufferPtr, convertStringsLazily, convertJNADominoDateTimeToCalendar, decodeAllValues);
		IItemTableData itemTableData = new ItemTableDataImpl(db, noteId, columnFormulasFixedOrder, valueTable);
		return itemTableData;
	}
	
	/**
	 * Decodes a large item value table structure, which contains an ordered list of item values
	 * 
	 * @param bufferPtr pointer to a buffer
	 * @param convertStringsLazily true to delay string conversion until the first use
	 * @param convertJNADominoDateTimeToCalendar true to convert {@link JNADominoDateTime} values to {@link Calendar}
	 * @param decodeAllValues true to decode all values in the buffer
	 * @return item value table data
	 */
	public static IItemValueTableData decodeItemValueTableLarge(Pointer bufferPtr,
			boolean convertStringsLazily, boolean convertJNADominoDateTimeToCalendar, boolean decodeAllValues) {
		int bufferPos = 0;
		
		//skip item value table header
		bufferPos += JNANotesConstants.itemValueTableLargeSize;
		
//		The information in a view summary of values is as follows:
//
//			ITEM_VALUE_TABLE containing header information (total length of summary, number of items in summary)
//			WORD containing the length of item #1 (including data type)
//			WORD containing the length of item #2 (including data type)
//			WORD containing the length of item #3 (including data type)
//			...
//			USHORT containing the data type of item #1
//			value of item #1
//			USHORT containing the data type of item #2
//			value of item #2
//			USHORT containing the data type of item #3
//			value of item #3
//			....
		
		int totalBufferLength = bufferPtr.getInt(0);// & 0xffff;
		int itemsCount = bufferPtr.getShort(4) & 0xffff;
		
		int[] itemValueLengths = new int[itemsCount];
		//we don't have any item names:
		int[] itemNameLengths = null;
		bufferPos+=2;
		//read all item lengths
		for (int j=0; j0) {
				itemNames[j] = NotesStringUtils.fromLMBCS(bufferPtr.share(bufferPos), itemNameLengths[j]);
				bufferPos += itemNameLengths[j];
			}
			
			//read data type
			if (itemValueLengths[j] == 0) {
				/* If an item has zero length it indicates an "empty" item in the
				summary. This might occur in a lower-level category and stand for a
				higher-level category that has already appeared. Or an empty item might
				be a field that is missing in a response doc. Just print * as a place
				holder and go on to the next item in the pSummary. */
				continue;
			}
			else {
				itemDataTypes[j] = bufferPtr.getShort(bufferPos) & 0xffff;
				
				//add data type size to position
				bufferPos += 2;
				
				//read item values
				itemValueBufferPointers[j] = bufferPtr.share(bufferPos);
				itemValueBufferSizes[j] = itemValueLengths[j] - 2;
				
				//skip item value
				bufferPos += (itemValueLengths[j] - 2);

				if (decodeAllValues) {
					int itemValueBufferSizeAsInt = (int) (itemValueBufferSizes[j] & 0xffffffff);

					if (itemDataTypes[j] == ItemDataType.TYPE_TEXT.getValue()) {
						Object strVal = ItemDecoder.decodeTextValue(itemValueBufferPointers[j], itemValueBufferSizeAsInt, convertStringsLazily);
						decodedItemValues[j] = strVal;
					}
					else if (itemDataTypes[j] == ItemDataType.TYPE_TEXT_LIST.getValue()) {
						//read a text list item value
						List listValues = itemValueBufferSizeAsInt==0 ? Collections.emptyList() : ItemDecoder.decodeTextListValue(itemValueBufferPointers[j], convertStringsLazily);
						decodedItemValues[j]  = listValues;
					}
					else if (itemDataTypes[j] == ItemDataType.TYPE_NUMBER.getValue()) {
						double numVal = ItemDecoder.decodeNumber(itemValueBufferPointers[j], itemValueBufferSizeAsInt);
						decodedItemValues[j] = numVal;
					}
					else if (itemDataTypes[j] == ItemDataType.TYPE_TIME.getValue()) {
						if (convertJNADominoDateTimeToCalendar) {
							Calendar cal = ItemDecoder.decodeTimeDate(itemValueBufferPointers[j], itemValueBufferSizeAsInt);
							decodedItemValues[j]  = cal;
						}
						else {
							DominoDateTime td = ItemDecoder.decodeTimeDateAsNotesTimeDate(itemValueBufferPointers[j], itemValueBufferSizeAsInt);
							decodedItemValues[j]  = td;
						}
					}
					else if (itemDataTypes[j] == ItemDataType.TYPE_NUMBER_RANGE.getValue()) {
						List numberList = ItemDecoder.decodeNumberList(itemValueBufferPointers[j], itemValueBufferSizeAsInt);
						decodedItemValues[j]  = numberList;
					}
					else if (itemDataTypes[j] == ItemDataType.TYPE_TIME_RANGE.getValue()) {
						List calendarValues;
						if (convertJNADominoDateTimeToCalendar) {
							calendarValues = ItemDecoder.decodeTimeDateList(itemValueBufferPointers[j]);
						}
						else {
							calendarValues = ItemDecoder.decodeTimeDateListAsNotesTimeDate(itemValueBufferPointers[j]);
						}
						decodedItemValues[j] = calendarValues;
					}
				}
			}
		}
		
		retData.m_itemValueBufferPointers = itemValueBufferPointers;
		retData.m_itemValueBufferSizes = itemValueBufferSizes;
		retData.m_itemValues = decodedItemValues;
		retData.m_itemDataTypes = itemDataTypes;
		retData.m_itemValueLengthsInBytes = itemValueLengths;
		
		if (retData instanceof ItemTableDataImpl) {
			((ItemTableDataImpl)retData).m_itemNames = itemNames;
		}
	}

	/**
	 * This utility method extracts the item values from the buffer
	 * 
	 * @param bufferPtr buffer pointer
	 * @param itemsCount number of items in the buffer
	 * @param itemValueLengths lengths of the item values
	 * @param retData data object to populate
	 * @param convertStringsLazily true to delay string conversion until the first use
	 * @param convertJNADominoDateTimeToCalendar true to convert {@link JNADominoDateTime} values to {@link Calendar}
	 * @param decodeAllValues true to decode all values in the buffer
	 */
	@SuppressWarnings("deprecation")
	private static void populateItemValueTableLargeData(Pointer bufferPtr, int itemsCount,
			int[] itemNameLengths, int[] itemValueLengths, ItemValueTableDataImpl retData, boolean convertStringsLazily,
			boolean convertJNADominoDateTimeToCalendar, boolean decodeAllValues) {
		int bufferPos = 0;
		String[] itemNames = new String[itemsCount];
		int[] itemDataTypes = new int[itemsCount];
		Pointer[] itemValueBufferPointers = new Pointer[itemsCount];
		int[] itemValueBufferSizes = new int[itemsCount];
		Object[] decodedItemValues = new Object[itemsCount];
		
		for (int j=0; j0) {
				itemNames[j] = NotesStringUtils.fromLMBCS(bufferPtr.share(bufferPos), itemNameLengths[j]);
				bufferPos += itemNameLengths[j];
			}
			
			//read data type
			if (itemValueLengths[j] == 0) {
				/* If an item has zero length it indicates an "empty" item in the
				summary. This might occur in a lower-level category and stand for a
				higher-level category that has already appeared. Or an empty item might
				be a field that is missing in a response doc. Just print * as a place
				holder and go on to the next item in the pSummary. */
				continue;
			}
			else {
				itemDataTypes[j] = bufferPtr.getShort(bufferPos) & 0xffff;
				
				//add data type size to position
				bufferPos += 2;
				
				//read item values
				itemValueBufferPointers[j] = bufferPtr.share(bufferPos);
				itemValueBufferSizes[j] = itemValueLengths[j] - 2;
				
				//skip item value
				bufferPos += (itemValueLengths[j] - 2);

				if (decodeAllValues) {
					if (itemValueBufferSizes[j] > Integer.MAX_VALUE) {
						throw new IllegalArgumentException("Item value lengths exceeds MAXDWORD: "+itemValueBufferSizes[j]);
					}
					int itemValueBufferSizeAsInt = (int) (itemValueBufferSizes[j] & 0xffffffff);
					
					if (itemDataTypes[j] == ItemDataType.TYPE_TEXT.getValue()) {
						Object strVal = ItemDecoder.decodeTextValue(itemValueBufferPointers[j], itemValueBufferSizeAsInt, convertStringsLazily);
						decodedItemValues[j] = strVal;
					}
					else if (itemDataTypes[j] == ItemDataType.TYPE_TEXT_LIST.getValue()) {
						//read a text list item value
						List listValues = itemValueBufferSizeAsInt==0 ? Collections.emptyList() : ItemDecoder.decodeTextListValue(itemValueBufferPointers[j], convertStringsLazily);
						decodedItemValues[j]  = listValues;
					}
					else if (itemDataTypes[j] == ItemDataType.TYPE_NUMBER.getValue()) {
						double numVal = ItemDecoder.decodeNumber(itemValueBufferPointers[j], itemValueBufferSizeAsInt);
						decodedItemValues[j] = numVal;
					}
					else if (itemDataTypes[j] == ItemDataType.TYPE_TIME.getValue()) {
						if (convertJNADominoDateTimeToCalendar) {
							Calendar cal = ItemDecoder.decodeTimeDate(itemValueBufferPointers[j], itemValueBufferSizeAsInt);
							decodedItemValues[j]  = cal;
						}
						else {
							DominoDateTime td = ItemDecoder.decodeTimeDateAsNotesTimeDate(itemValueBufferPointers[j], itemValueBufferSizeAsInt);
							decodedItemValues[j]  = td;
						}
					}
					else if (itemDataTypes[j] == ItemDataType.TYPE_NUMBER_RANGE.getValue()) {
						List numberList = ItemDecoder.decodeNumberList(itemValueBufferPointers[j], itemValueBufferSizeAsInt);
						decodedItemValues[j]  = numberList;
					}
					else if (itemDataTypes[j] == ItemDataType.TYPE_TIME_RANGE.getValue()) {
						List calendarValues;
						if (convertJNADominoDateTimeToCalendar) {
							calendarValues = ItemDecoder.decodeTimeDateList(itemValueBufferPointers[j]);
						}
						else {
							calendarValues = ItemDecoder.decodeTimeDateListAsNotesTimeDate(itemValueBufferPointers[j]);
						}
						decodedItemValues[j] = calendarValues;
					}
				}
			}
		}
		
		retData.m_itemValueBufferPointers = itemValueBufferPointers;
		retData.m_itemValueBufferSizes = itemValueBufferSizes;
		retData.m_itemValues = decodedItemValues;
		retData.m_itemDataTypes = itemDataTypes;
		retData.m_itemValueLengthsInBytes = itemValueLengths;
		
		if (retData instanceof ItemTableDataImpl) {
			((ItemTableDataImpl)retData).m_itemNames = itemNames;
		}
	}
	
	/**
	 * Decodes an ITEM_TABLE_LARGE structure with item names and item values
	 * 
	 * @param bufferPtr pointer to a buffer
	 * @param convertStringsLazily true to delay string conversion until the first use
	 * @param convertJNADominoDateTimeToCalendar true to convert {@link JNADominoDateTime} values to {@link Calendar}
	 * @param decodeAllValues true to decode all values in the buffer
	 * @return data
	 */
	public static IItemTableData decodeItemTableLarge(Pointer bufferPtr,
			boolean convertStringsLazily, boolean convertJNADominoDateTimeToCalendar, boolean decodeAllValues) {
		int bufferPos = 0;
		NotesItemTableLargeStruct itemTable = NotesItemTableLargeStruct.newInstance(bufferPtr);
		itemTable.read();

		//skip item table header
		bufferPos += itemTable.size();

		int itemsCount = itemTable.getItemsAsInt();
		int[] itemValueLengths = new int[itemsCount];
		int[] itemNameLengths = new int[itemsCount];

		//read  ITEM_LARGE structures for each item
		for (int j=0; j skip filler WORD
			itemValueLengths[j] = itemPtr.share(4).getInt(0);
			
			bufferPos += JNANotesConstants.tableItemLargeSize;
		}

		ItemTableDataImpl data = new ItemTableDataImpl(convertStringsLazily);
		data.setPreferNotesTimeDates(!convertJNADominoDateTimeToCalendar);
		data.m_totalBufferLength = itemTable.getLengthAsInt();
		data.m_itemsCount = itemsCount;
		
		Pointer itemValuePtr = bufferPtr.share(bufferPos);
		populateItemValueTableLargeData(itemValuePtr, itemsCount, itemNameLengths, itemValueLengths,
				data, convertStringsLazily, convertJNADominoDateTimeToCalendar, decodeAllValues);
		
		return data;
	}
	
	/**
	 * Decodes an ITEM_TABLE structure with item names and item values
	 * 
	 * @param bufferPtr pointer to a buffer
	 * @param convertStringsLazily true to delay string conversion until the first use
	 * @param convertJNADominoDateTimeToCalendar true to convert {@link JNADominoDateTime} values to {@link Calendar}
	 * @param decodeAllValues true to decode all values in the buffer
	 * @return data
	 */
	public static IItemTableData decodeItemTable(Pointer bufferPtr,
			boolean convertStringsLazily, boolean convertJNADominoDateTimeToCalendar, boolean decodeAllValues) {
		int bufferPos = 0;
		NotesItemTableStruct itemTable = NotesItemTableStruct.newInstance(bufferPtr);
		itemTable.read();
		
		//skip item table header
		bufferPos += itemTable.size();

//		typedef struct {
//			   USHORT Length; /*  total length of this buffer */
//			   USHORT Items;  /* number of items in the table */
//			/* now come an array of ITEMs */
//			/* now comes the packed text containing the item names. */
//			} ITEM_TABLE;					
		
		int itemsCount = itemTable.getItemsAsInt();
		int[] itemValueLengths = new int[itemsCount];
		int[] itemNameLengths = new int[itemsCount];
		
		//read ITEM structures for each item
		for (int j=0; j tdList = (List) m_itemValues[index];
				
				List calList = new ArrayList<>(tdList.size());
				
				for (int i=0; i m_itemExistence;
		private TypedAccess m_typedItems;
		private Database m_db;
		private int m_noteId;
		
		private LinkedHashMap m_columnFormulasFixedOrder;
		private ItemTableDataDocAdapter m_itemTableDocAdapter;
		private boolean m_itemTableDocAdapterLoadFailed;
		
		public ItemTableDataImpl(Database db, int noteId, LinkedHashMap columnFormulasFixedOrder,
				ItemValueTableDataImpl valueTable) {
			super(valueTable.m_convertStringsLazily);
			
			m_db = db;
			m_noteId = noteId;
			m_columnFormulasFixedOrder = columnFormulasFixedOrder;
			m_itemNames = columnFormulasFixedOrder==null ? new String[0] : columnFormulasFixedOrder.keySet().toArray(new String[0]);
			
			m_wrappedValueTable = valueTable;
			m_itemValueBufferPointers = valueTable.m_itemValueBufferPointers;
			m_itemValueBufferSizes = valueTable.m_itemValueBufferSizes;
			m_itemValues = valueTable.m_itemValues;
			m_itemDataTypes = valueTable.m_itemDataTypes;
			m_totalBufferLength = valueTable.m_totalBufferLength;
			m_itemsCount = valueTable.m_itemsCount;
			m_itemValueLengthsInBytes = valueTable.m_itemValueLengthsInBytes;
			
			m_typedItems = new AbstractTypedAccess() {
				
				@Override
				public List getItemNames() {
					return ItemTableDataImpl.this.getItemNames();
				}

				@Override
				public boolean hasItem(String itemName) {
					return ItemTableDataImpl.this.hasItem(itemName);
				}
				
				@Override
				protected List getItemValue(String itemName) {
					Object val = ItemTableDataImpl.this.get(itemName);
					if (val==null) {
						return null;
					}
					else if (val instanceof List) {
						return (List) val;
					}
					else {
						return Arrays.asList(val);
					}
				}
			};
			setPreferNotesTimeDates(true);
		}
		
		public ItemTableDataImpl(boolean convertStringsLazily) {
			super(convertStringsLazily);
			
			m_typedItems = new AbstractTypedAccess() {
				
				@Override
				public List getItemNames() {
					return ItemTableDataImpl.this.getItemNames();
				}

				@Override
				public boolean hasItem(String itemName) {
					return ItemTableDataImpl.this.hasItem(itemName);
				}

				@Override
				protected List getItemValue(String itemName) {
					Object val = ItemTableDataImpl.this.get(itemName);
					if (val==null) {
						return null;
					}
					else if (val instanceof List) {
						return (List) val;
					}
					else {
						return Arrays.asList(val);
					}
				}
			};
		}
		
		@Override
		public void free() {
			super.free();
			
			if (m_itemTableDocAdapter!=null && !m_itemTableDocAdapter.isFreed()) {
				m_itemTableDocAdapter.free();
			}
		}
		
		@Override
		public  T get(String itemName, Class valueType, T defaultValue) {
			return m_typedItems.get(itemName, valueType, defaultValue);
		}
		
		@Override
		public  List getAsList(String itemName, Class valueType, List defaultValue) {
			return m_typedItems.getAsList(itemName, valueType, defaultValue);
		}
		
		@Override
		public boolean hasItem(String itemName) {
			Boolean exists = null;
			if (m_itemExistence!=null) {
				exists = m_itemExistence.get(itemName);
			}
			if (exists==null) {
				//hash the result in case we have some really frequent calls for the same item
				for (String currItem : m_itemNames) {
					if (currItem.equalsIgnoreCase(itemName)) {
						exists = Boolean.TRUE;
						break;
					}
				}
				if (exists==null) {
					exists = Boolean.FALSE;
				}
				
				if (m_itemExistence==null) {
					m_itemExistence = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
				}
				m_itemExistence.put(itemName, exists);
			}
			return exists;
		}
		
		@Override
		public List getItemNames() {
			return Arrays.asList(m_itemNames);
		}
		
		@Override
		public  Optional getOptional(String itemName, Class valueType) {
		  return m_typedItems.getOptional(itemName, valueType);
		}
		
		@Override
		public  Optional> getAsListOptional(String itemName, Class valueType) {
		  return m_typedItems.getAsListOptional(itemName, valueType);
		}
		
		private ItemTableDataDocAdapter getTableDocAdapter() {
			if (m_itemTableDocAdapter==null || m_itemTableDocAdapter.isFreed()) {
				if (!m_itemTableDocAdapterLoadFailed) {
					Optional doc = m_db.getDocumentById(m_noteId, EnumSet.of(OpenDocumentMode.SUMMARY_ONLY, OpenDocumentMode.NOOBJECTS));
					if (doc.isPresent()) {
						m_itemTableDocAdapter = new ItemTableDataDocAdapter((JNADocument) doc.get(), m_columnFormulasFixedOrder);
					}
					else {
						m_itemTableDocAdapterLoadFailed = true;
					}
				}
				
			}
			return m_itemTableDocAdapter;
		}
		
		public Object get(String itemName) {
			if (m_wrappedValueTable!=null && m_wrappedValueTable.isFreed()) {
				throw new DominoException("Buffer already freed");
			}
			
			for (int i=0; i valAsList = (List) val;
						for (int j=0; j asMap() {
			return asMap(true);
		}
		
		@Override
		public Map asMap(boolean decodeLMBCS) {
			Map data = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
			int itemCount = getItemsCount();
			for (int i=0; i valAsList = (List) val;
						boolean hasLMBCS = false;
						boolean hasTimeDate = false;
						
						for (int j=0; j convList = new ArrayList<>(valAsList.size());
							for (int j=0; j