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

com.hcl.domino.jna.data.JNACollectionEntry Maven / Gradle / Ivy

There is a newer version: 1.41.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.data;

import java.io.Serializable;
import java.lang.ref.SoftReference;
import java.text.MessageFormat;
import java.time.OffsetDateTime;
import java.time.temporal.Temporal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.util.function.Function;
import java.util.stream.Collectors;

import com.hcl.domino.commons.data.AbstractCollectionEntry;
import com.hcl.domino.commons.data.AbstractTypedAccess;
import com.hcl.domino.commons.util.StringUtil;
import com.hcl.domino.commons.views.ReadMask;
import com.hcl.domino.data.CollectionEntryValueConverter;
import com.hcl.domino.data.Database;
import com.hcl.domino.data.Database.DocInfo;
import com.hcl.domino.data.Document;
import com.hcl.domino.data.DocumentClass;
import com.hcl.domino.data.DominoDateTime;
import com.hcl.domino.jna.internal.LMBCSString;
import com.hcl.domino.jna.internal.NotesStringUtils;
import com.hcl.domino.misc.DominoEnumUtil;
import com.hcl.domino.misc.JNXServiceFinder;
import com.hcl.domino.misc.NotesConstants;

/**
 * Data object that contains all data read from collection entries
 * 
 * @author Karsten Lehmann
 */
public class JNACollectionEntry extends AbstractCollectionEntry {
	private JNADominoCollection m_parentCollection;
	private int[] m_pos;
	private String m_posStr;
	private Integer m_noteId;
	private String m_unid;
	private long[] m_unidAsLongs;
	private Integer m_noteClass;
	private Integer m_siblingCount;
	private Integer m_childCount;
	private Integer m_descendantCount;
	private Boolean m_isAnyUnread;
	private Integer m_indentLevels;
	private Integer m_ftScore;
	private Boolean m_isUnread;
	private Object[] m_columnValues;
	private int[] m_columnValueSizes;
	private Map m_summaryData;
	private SoftReference> m_convertedDataRef;
	private String m_singleColumnLookupName;
	private AbstractTypedAccess m_typedAccess;
	private ThreadLocal readingItemType = new ThreadLocal<>();
	
	private Integer m_sequenceNumber;
	private DominoDateTime m_sequenceTime;
	
	/**
	 * Creates a new instance
	 * 
	 * @param parentCollection parent notes collection
	 */
	public JNACollectionEntry(JNADominoCollection parentCollection) {
		m_parentCollection = parentCollection;
		
		m_typedAccess = new AbstractTypedAccess() {
			
			@Override
			public boolean hasItem(String itemName) {
				return JNACollectionEntry.this.hasItem(itemName);
			}

			@Override
			public List getItemNames() {
				return JNACollectionEntry.this.getItemNames();
			}
			
			@Override
			protected List getItemValue(String itemName) {
				return JNACollectionEntry.this.getItemValue(itemName);
			}
			
			@Override
			public int getIndexedValueCount() {
				return m_columnValues == null ? 0 : m_columnValues.length;
			}
			
			@Override
			protected  T getViaValueConverter(String itemName, Class valueType, T defaultValue) {
				return tryWithConverters(itemName, valueType, defaultValue,
					converter -> converter.getValue(JNACollectionEntry.this, itemName, valueType, defaultValue)
				);
			}
			
			@Override
			protected  List getAsListViaValueConverter(String itemName, Class valueType, List defaultValue) {
				return tryWithConverters(itemName, valueType, defaultValue,
					converter -> converter.getValueAsList(JNACollectionEntry.this, itemName, valueType, defaultValue)
				);
			}
			
			@Override
			protected List getItemValue(int index) {
				if(m_columnValues == null) {
					return null;
				} else {
					return cleanValue(m_columnValues[index]);
				}
			}
			
			@Override
			protected  T getViaValueConverter(int index, Class valueType, T defaultValue) {
				return tryWithConverters(index, valueType, defaultValue,
					converter -> converter.getValue(JNACollectionEntry.this, index, valueType, defaultValue)
				);
			}
			
			@Override
			protected  List getAsListViaValueConverter(int index, Class valueType, List defaultValue) {
				return tryWithConverters(index, valueType, defaultValue,
					converter -> converter.getValueAsList(JNACollectionEntry.this, index, valueType, defaultValue)
				);
			}
			
			private  RESULT tryWithConverters(IDENT itemIdentifier, Class valueType, RESULT defaultValue, Function supplier) {
				CollectionEntryValueConverter converter = JNXServiceFinder.findServices(CollectionEntryValueConverter.class)
					.filter(c -> c.supportsRead(valueType))
					.sorted(Comparator.comparing(CollectionEntryValueConverter::getPriority).reversed())
					.findFirst()
					.orElse(null);

				if (converter!=null) {
					if (Boolean.TRUE.equals(readingItemType.get())) {
						throw new IllegalStateException(
							MessageFormat.format(
								"Infinite loop detected reading the value of item {0} as type {1}",
								itemIdentifier, valueType.getName()
							)
						);
					}
					readingItemType.set(Boolean.TRUE);
					try {
						return supplier.apply(converter);
					}
					finally {
						readingItemType.set(null);
					}
				}
				else {
					throw new IllegalArgumentException(MessageFormat.format("Unsupported return value type: {0}", valueType.getName()));
				}
			}
		};
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public  T getAdapter(Class clazz) {
		if (JNADocument.class.equals(clazz) || Document.class.equals(clazz)) {
			return (T) openDocument();
		}

		return null;
	}

	class CacheableViewEntryData implements Serializable {
		private static final long serialVersionUID = -6919729244434994355L;
		
		private int[] m_pos;
		private String m_posStr;
		private Integer m_noteId;
		private String m_unid;
		private long[] m_unidAsLongs;
		private Integer m_noteClass;
		private Integer m_siblingCount;
		private Integer m_childCount;
		private Integer m_descendantCount;
		private Boolean m_isAnyUnread;
		private Integer m_indentLevels;
		private Integer m_ftScore;
		private Boolean m_isUnread;
		private Object[] m_columnValues;
		private int[] m_columnValueSizes;
		private Map m_summaryData;
		private transient SoftReference> m_convertedDataRef;
		private String m_singleColumnLookupName;
	}
	
	/**
	 * Method to read the cacheable and serializable data from this object
	 * 
	 * @return data
	 */
	CacheableViewEntryData getCacheableData() {
		CacheableViewEntryData data = new CacheableViewEntryData();
		data.m_pos = m_pos;
		data.m_posStr = m_posStr;
		data.m_noteId = m_noteId;
		data.m_unid = m_unid;
		data.m_unidAsLongs = m_unidAsLongs;
		data.m_noteClass = m_noteClass;
		data.m_siblingCount = m_siblingCount;
		data.m_childCount = m_childCount;
		data.m_descendantCount = m_descendantCount;
		data.m_isAnyUnread = m_isAnyUnread;
		data.m_indentLevels = m_indentLevels;
		data.m_ftScore = m_ftScore;
		data.m_isUnread = m_isUnread;
		data.m_columnValues = m_columnValues;
		data.m_columnValueSizes = m_columnValueSizes;
		data.m_summaryData = m_summaryData;
		data.m_convertedDataRef = m_convertedDataRef;
		data.m_singleColumnLookupName = m_singleColumnLookupName;
		return data;
	}
	
	/**
	 * Method to update the internal state from a cache entry
	 * 
	 * @param data cache entry data
	 */
	void updateFromCache(CacheableViewEntryData data) {
		if (m_noteId.intValue()!=data.m_noteId.intValue()) {
			throw new IllegalArgumentException(MessageFormat.format("Note ids do not match: {0} != {1}", m_noteId, data.m_noteId));
		}
		
		m_pos = data.m_pos;
		m_posStr = data.m_posStr;
		m_noteId = data.m_noteId;
		m_unid = data.m_unid;
		m_unidAsLongs = data.m_unidAsLongs;
		m_noteClass = data.m_noteClass;
		m_siblingCount = data.m_siblingCount;
		m_childCount = data.m_childCount;
		m_descendantCount = data.m_descendantCount;
		m_isAnyUnread = data.m_isAnyUnread;
		m_indentLevels = data.m_indentLevels;
		m_ftScore = data.m_ftScore;
		m_isUnread = data.m_isUnread;
		m_columnValues = data.m_columnValues;
		m_columnValueSizes = data.m_columnValueSizes;
		m_summaryData = data.m_summaryData;
		m_convertedDataRef = data.m_convertedDataRef;
		m_singleColumnLookupName = data.m_singleColumnLookupName;
	}
	
	/**
	 * Returns the parent collection
	 * 
	 * @return parent collection
	 */
	public JNADominoCollection getParent() {
		return m_parentCollection;
	}
	
	/**
	 * Method to check whether an entry is a conflict document. Can only returns a true value
	 * if {@link ReadMask#SUMMARYVALUES} or {@link ReadMask#SUMMARY} is used for the lookup.
	 * 
	 * @return true if conflict
	 */
	public boolean isConflict() {
		//C API documentation regarding conflict flags in views
		//VIEW_TABLE_FLAG_CONFLICT	  -  Replication conflicts will be flagged. If TRUE, the '$Conflict' item must be SECOND-TO-LAST in the list of summary items for this view.
		if (m_columnValues!=null) {
			if (!m_parentCollection.isConflict()) {
				return false;
			}
			else if (m_parentCollection.isHierarchical()) {
				return m_columnValues.length>=2 && m_columnValues[m_columnValues.length-2] != null;
			}
			else {
				//special case for views which have "show response hierarchy" = false:
				//here the response column value is missing
				return m_columnValues.length>=1 && m_columnValues[m_columnValues.length-1] != null;
			}
		}
		else if (m_summaryData!=null) {
			return m_summaryData.get("$Conflict") != null; //$NON-NLS-1$
		}
		return false;
	}

	/**
	 * Method to check whether an entry is a conflict document. Can only returns a true value
	 * if {@link ReadMask#SUMMARYVALUES} or {@link ReadMask#SUMMARY} is used for the lookup.
	 * 
	 * @return true if response
	 */
	public boolean isResponse() {
		//C API documentation regarding response flags in views
		//VIEW_TABLE_FLAG_FLATINDEX	  -  Do not index hierarchically If FALSE, the '$REF' item must be LAST in the list of summary items for this view.
		if (m_columnValues!=null) {
			if (m_parentCollection.isHierarchical()) {
				return m_columnValues.length>=1 && m_columnValues[m_columnValues.length-1] != null;
			}
			else {
				//fallback to isConflict as this is the only info we have
				return isConflict();
			}
		}
		else if (m_summaryData!=null) {
			return m_summaryData.get("$Ref") != null; //$NON-NLS-1$
		}
		return false;
	}

	/**
	 * Returns the level of the entry in the view (position 1 = level 0, position 1.1 = level 1)
	 * 
	 * @return level, only available when position is loaded, otherwise the method returns -1
	 */
	public int getLevel() {
		return m_pos!=null ? (m_pos.length-1) : -1;
	}
	
	/**
	 * Returns the entry position in the view as an int[] array (e.g. [1,2,3])
	 * or a string (e.g. "1.2.3"). Only returns a non-null value if
	 * {@link ReadMask#INDEXPOSITION} is used for the lookup.
	 * 
	 * @param clazz requested return value class
	 * @param defaultValue default value is returned if index position has not been read or class is unsupported
	 * @return position or default value
	 */
	@SuppressWarnings("unchecked")
	private  T getPosition(Class clazz, T defaultValue) {
		if (int[].class == clazz) {
			if (m_pos!=null) {
				return (T) m_pos;
			}
			else {
				return defaultValue;
			}
		}
		else if (String.class == clazz) {
			if (m_pos!=null && m_pos.length>0) {
				if (m_posStr==null) {
					StringBuilder sb = new StringBuilder();
					for (int i=0; i0) {
							sb.append("."); //$NON-NLS-1$
						}
						sb.append(m_pos[i]);
					}
					m_posStr = sb.toString();
				}
				return (T) m_posStr;
			}
			else {
				return defaultValue;
			}
		}
		
		return null;
	}
	
	/**
	 * Sets the position
	 * 
	 * @param pos new position
	 */
	public void setPosition(int[] pos) {
		m_pos = pos;
	}
	
	/**
	 * Sets the note id
	 * 
	 * @param noteId note id
	 */
	public void setNoteID(int noteId) {
		m_noteId = noteId;
	}

	/**
	 * Method to check whether the entry is a document. Only returns a value if {@link ReadMask#NOTEID}
	 * is used for the lookup
	 * 
	 * @return true if document
	 */
	@Override
	public boolean isDocument() {
		return !isCategory() && !isTotal();
	}

	/**
	 * Method to check whether the entry is a category. Only returns a value if {@link ReadMask#NOTEID}
	 * is used for the lookup
	 * 
	 * @return true if category
	 */
	@Override
	public boolean isCategory()  {
		if (m_noteId!=null) {
			return (m_noteId.intValue() & NotesConstants.NOTEID_CATEGORY) == NotesConstants.NOTEID_CATEGORY;
		}
		return false;
	}

	/**
	 * Method to check whether the entry is a total value. Only returns a value if {@link ReadMask#NOTEID}
	 * is used for the lookup
	 * 
	 * @return true if total
	 */
	public boolean isTotal() {
		if (m_noteId!=null) {
			return (m_noteId.intValue() & NotesConstants.NOTEID_CATEGORY_TOTAL) == NotesConstants.NOTEID_CATEGORY_TOTAL;
		}
		return false;
	}

	/**
	 * Returns the UNID of the entry. Only returns a value if {@link ReadMask#NOTEUNID}
	 * is used for the lookup
	 * 
	 * @return UNID or null
	 */
	@Override
	public String getUNID() {
		if (m_unid==null) {
			if (m_unidAsLongs!=null) {
				m_unid = NotesStringUtils.toUNID(m_unidAsLongs[0], m_unidAsLongs[1]);
			}
		}
		return m_unid;
	}

	/**
	 * Sets the UNID
	 * 
	 * @param unid UNID
	 */
	public void setUNID(String unid) {
		m_unid = unid;
	}

	/**
	 * Sets the UNID as a long array
	 * 
	 * @param unidAsLongs long array with file / note timedates
	 */
	public void setUNID(long[] unidAsLongs) {
		m_unidAsLongs = unidAsLongs;
	}
	
	/**
	 * Returns the entry's note class. Only returns a value if {@link ReadMask#NOTECLASS}
	 * is used for the lookup
	 * 
	 * @return the document class
	 */
	@Override
	public Optional getDocumentClass() {
		return Optional.ofNullable(m_noteClass)
			.map(c -> DominoEnumUtil.valueOf(DocumentClass.class, c))
			.orElse(Optional.empty());
	}

	/**
	 * Sets the note class
	 * 
	 * @param noteClass note class
	 */
	public void setNoteClass(int noteClass) {
		m_noteClass = noteClass;
	}

	/**
	 * Returns the sibling count. Only returns a value if {@link ReadMask#INDEXSIBLINGS}
	 * is used for the lookup
	 * 
	 * @param clazz requested return value class
	 * @param defaultValue default value is returned if count has not been read or class is unsupported
	 * @return count or 0
	 */
	@SuppressWarnings("unchecked")
	private  T getSiblingCount(Class clazz, T defaultValue) {
		if (Integer.class == clazz) {
			if (m_siblingCount!=null) {
				return (T) m_siblingCount;
			}
			else {
				return defaultValue;
			}
		}
		else if (String.class == clazz) {
			if (m_siblingCount!=null) {
				return (T) m_siblingCount.toString();
			}
			else {
				return defaultValue;
			}
		}
		return defaultValue;
	}

	/**
	 * Sets the sibling count
	 * 
	 * @param siblingCount count
	 */
	public void setSiblingCount(int siblingCount) {
		m_siblingCount = siblingCount;
	}

	/**
	 * Returns the sibling count. Only returns a value if {@link ReadMask#INDEXCHILDREN}
	 * is used for the lookup
	 * 
	 * @param clazz requested return value class
	 * @param defaultValue default value is returned if count has not been read or class is unsupported
	 * @return count or default value
	 */
	@SuppressWarnings("unchecked")
	private  T getChildCount(Class clazz, T defaultValue) {
		if (Integer.class == clazz) {
			if (m_childCount!=null) {
				return (T) m_childCount;
			}
			else {
				return defaultValue;
			}
		}
		else if (String.class == clazz) {
			if (m_childCount!=null) {
				return (T) m_childCount.toString();
			}
			else {
				return defaultValue;
			}
		}
		return defaultValue;
	}

	/**
	 * Sets the child count
	 * 
	 * @param childCount count
	 */
	public void setChildCount(int childCount) {
		m_childCount = childCount;
	}

	/**
	 * Returns the descendant count. Only returns a value if {@link ReadMask#INDEXDESCENDANTS}
	 * is used for the lookup
	 * 
	 * @param clazz requested return value class
	 * @param defaultValue default value is returned if count has not been read or class is unsupported
	 * @return count or default value
	 */
	@SuppressWarnings("unchecked")
	private  T getDescendantCount(Class clazz, T defaultValue) {
		if (Integer.class == clazz) {
			if (m_descendantCount!=null) {
				return (T) m_descendantCount;
			}
			else {
				return defaultValue;
			}
		}
		else if (String.class == clazz) {
			if (m_descendantCount!=null) {
				return (T) m_descendantCount.toString();
			}
			else {
				return defaultValue;
			}
		}
		return defaultValue;
	}

	/**
	 * Sets the descendant count
	 * 
	 * @param descendantCount count
	 */
	public void setDescendantCount(int descendantCount) {
		m_descendantCount = descendantCount;
	}

	/**
	 * Returns true if the entry is unread. Only returns a value if {@link ReadMask#INDEXUNREAD}
	 * is used for the lookup
	 * 
	 * @param clazz requested return value class
	 * @param defaultValue default value is returned if unread state has not been read or class is unsupported
	 * @return unread
	 */
	@SuppressWarnings("unchecked")
	private  T isUnread(Class clazz, T defaultValue) {
		if (m_isUnread!=null) {
			if (Boolean.class == clazz) {
				return (T) m_isUnread;
			}
			else if (String.class == clazz) {
				return (T) m_isUnread.toString();
			}
		}
		
		return defaultValue;
	}

	/**
	 * Sets the unread flag
	 * 
	 * @param isUnread flag
	 */
	public void setUnread(boolean isUnread) {
		m_isUnread = isUnread;
	}

	/**
	 * Returns the indent levels in the view. Only returns a value if {@link ReadMask#INDENTLEVELS}
	 * is used for the lookup
	 * 
	 * @return levels or 0
	 */
	public int getIndentLevels() {
		return m_indentLevels!=null ? m_indentLevels : 0;
	}

	/**
	 * Sets the indent levels
	 * 
	 * @param indentLevels levels
	 */
	public void setIndentLevels(int indentLevels) {
		m_indentLevels = indentLevels;
	}

	/**
	 * Returns the fulltext search score. Only returns a value if {@link ReadMask#SCORE}
	 * is used for the lookup
	 * 
	 * @return score or 0
	 */
	public int getFTScore() {
		return m_ftScore!=null ? m_ftScore : 0;
	}

	/**
	 * Sets the fulltext search score
	 * 
	 * @param ftScore score
	 */
	public void setFTScore(int ftScore) {
		m_ftScore = ftScore;
	}

	/**
	 * Returns a flag whether the entry or any descendents are unread. Only returns a value if {@link ReadMask#INDEXANYUNREAD}
	 * is used for the lookup
	 * 
	 * @param clazz requested return value class
	 * @param defaultValue default value is returned if unread state has not been read or class is unsupported
	 * @return unread
	 */
	@SuppressWarnings("unchecked")
	private  T isAnyUnread(Class clazz, T defaultValue) {
		if (m_isAnyUnread!=null) {
			if (Boolean.class == clazz) {
				return (T) m_isAnyUnread;
			}
			else if (String.class == clazz) {
				return (T) m_isAnyUnread.toString();
			}
		}
		
		return defaultValue;
	}

	/**
	 * Sets the any unread flag
	 * 
	 * @param isAnyUnread flag
	 */
	public void setAnyUnread(boolean isAnyUnread) {
		m_isAnyUnread = isAnyUnread;
	}

	/**
	 * Sets the collection entry column values.
	 * 
	 * @param itemValues new values
	 */
	public void setColumnValues(Object[] itemValues) {
		m_columnValues = itemValues;
	}
	
	public Object[] getColumnValues() {
		return m_columnValues;
	}
	
	/**
	 * Returns an iterator of all available columns for which we can read column values
	 * (e.g. does not return static column names).
	 * 
	 * @return programmatic column names converted to lowercase
	 */
	public List getColumnNames() {
		if ((m_parentCollection.getNoteId() & NotesConstants.NOTE_ID_SPECIAL) == NotesConstants.NOTE_ID_SPECIAL) {
			//special collection (e.g. design collection) where we cannot read the column names from the design element
			if (m_summaryData!=null) {
				//if we have used ReadMask.SUMMARY to read the data, we can take the summary map keys
				return new ArrayList<>(m_summaryData.keySet());
			}
			else {
				return Collections.emptyList();
			}
		}
		else {
			return m_parentCollection.getColumnNames();
		}
	}

	@Override
	public List getItemNames() {
		return getColumnNames();
	}
	
	/**
	 * Returns the number of columns for which we can read column data (e.g. does not count columns
	 * with static values)
	 * 
	 * @return number of columns
	 */
	public int getNumberOfColumnsWithValues() {
		return m_parentCollection.getNumberOfColumns();
	}
	
	/**
	 * Converts the column values to a map. If you are only interested in specific columns,
	 * you get way better performance calling {@link #get(String)} for those columns
	 * directly, because we lazily convert text/text list column data from LMBCS format to Java String format.
* Calling this method converts all string columns at once. * * @return map with programmatic column names as keys */ private Map getColumnDataAsMap() { Map data = m_convertedDataRef==null ? null : m_convertedDataRef.get(); if (data==null) { data = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); List colNames = getColumnNames(); for (String currColName : colNames) { List currColValue = getItemValue(currColName); if(currColValue != null) { currColValue = currColValue.stream() .map(v -> { if (v instanceof GregorianCalendar) { return new JNADominoDateTime(((GregorianCalendar) v).toZonedDateTime()); } else if (v instanceof Date) { return new JNADominoDateTime(((Date) v).getTime()); } else { return v; } }) .collect(Collectors.toList()); } if(currColValue != null && currColValue.size() == 1) { data.put(currColName, currColValue.get(0)); } else { data.put(currColName, currColValue); } } m_convertedDataRef = new SoftReference<>(data); } return data; } /** * Returns a list of reader that are allowed to see this view entry. * This data is only retrieved when {@link ReadMask#SUMMARY} and * {@link ReadMask#RETURN_READERSLIST} are both used to read the * collection data. * * @return an {@link Optional} representing the readers or an empty one * if this was not read */ @SuppressWarnings("unchecked") public Optional> getReadersList() { Object readersList = get("$C1$"); //$NON-NLS-1$ if (readersList instanceof List) { return Optional.of((List) readersList); } else if (readersList instanceof String) { return Optional.of(Arrays.asList((String) readersList)); } else { return Optional.empty(); } } /** * Method to check whether the the view entry contains a non-null value for a programmatic column * name * * @param columnName column name * @return true if exists */ @Override public boolean hasItem(String columnName) { if (m_summaryData!=null) { return m_summaryData.containsKey(columnName); } else if (m_columnValues!=null) { int colIdx = m_parentCollection.getColumnValuesIndex(columnName); if (colIdx==-1) { return false; } else { return m_columnValues[colIdx] != null; } } else { return false; } } /** * Returns a column value. Only returns data of either {@link ReadMask#SUMMARY} or {@link ReadMask#SUMMARYVALUES} * was used to read the collection data *
* The following data types are returned for the different column data types:
*
    *
  • {@link NotesItem#TYPE_TEXT} - {@link String}
  • *
  • {@link NotesItem#TYPE_TEXT_LIST} - {@link List} of {@link String}
  • *
  • {@link NotesItem#TYPE_NUMBER} - {@link Double}
  • *
  • {@link NotesItem#TYPE_NUMBER_RANGE} - {@link List} with {@link Double} values for number lists or double[] values for number ranges (not sure if Notes views really supports them)
  • *
  • {@link NotesItem#TYPE_TIME} - {@link Calendar} or {@link DominoDateTime} if {@link #setPreferNotesTimeDates(boolean)} has been called
  • *
  • {@link NotesItem#TYPE_TIME_RANGE} - {@link List} with {@link Calendar} values for datetime lists or Calendar[] values for datetime ranges {@link DominoDateTime} if {@link #setPreferNotesTimeDates(boolean)} has been called
  • *
* * @param columnNameOrTitle programatic column name or column title * @return column value or null */ private List getItemValue(String columnNameOrTitle) { Object val = null; if (m_summaryData!=null) { if (m_summaryData.containsKey(columnNameOrTitle)) { val = m_summaryData.get(columnNameOrTitle); } else { //try to find the programmatic column name if columnNameOrTitle contains the column title int colIdx = m_parentCollection.getColumnValuesIndex(columnNameOrTitle); if (colIdx!=-1 && colIdx!=65535) { String progColName = m_parentCollection.getColumnName(colIdx); if (progColName!=null) { val = m_summaryData.get(progColName); } } } } else if (m_columnValues!=null) { int colIdx = m_parentCollection.getColumnValuesIndex(columnNameOrTitle); if (colIdx!=-1 && colIdx!=65535) { if (colIdx < m_columnValues.length) { val = m_columnValues[colIdx]; } else { val = null; } } } return cleanValue(val); } @SuppressWarnings("unchecked") private List cleanValue(Object val) { if (val instanceof List) { List valAsList = (List) val; for (int i=0; i) val; } else { return Arrays.asList(val); } } /** * Convenience method to check whether there are any column values stored in this entry * * @return true if we have column values */ public boolean hasAnyColumnValues() { if (m_summaryData!=null) { if (!m_summaryData.isEmpty()) { return true; } } if (m_columnValues!=null) { for (int i=0; i summaryData) { m_summaryData = summaryData; } /** * If this view entry data was received by an optimized lookup that read only one column, you * can use this method to get the programmatic name of the collection column. The method * is mainly used for collection data caching purposes. * * @return column name or null */ public String getSingleColumnLookupName() { return m_singleColumnLookupName; } /** * If this view entry data was received by an optimized lookup that read only one column, * this method is used to set the programmatic name of that column. * * @param colName column name or null */ public void setSingleColumnLookupName(String colName) { m_singleColumnLookupName = colName; } @Override public String toString() { StringBuilder sb = new StringBuilder(); if (m_noteId!=null) { sb.append(",noteid="+m_noteId); //$NON-NLS-1$ } String unid = getUNID(); if (unid!=null) { sb.append(",unid="+unid); //$NON-NLS-1$ } String posStr = getPosition(String.class, ""); //$NON-NLS-1$ if (posStr.length()>0) { sb.append(",pos="+posStr); //$NON-NLS-1$ } if (m_noteClass!=null) { sb.append(",class="+m_noteClass.intValue()); //$NON-NLS-1$ } if (isDocument()) { sb.append(",type=document"); //$NON-NLS-1$ } else if (isCategory()) { sb.append(",type=category"); //$NON-NLS-1$ } else if (isTotal()) { sb.append(",type=total"); //$NON-NLS-1$ } if (m_indentLevels!=null) { sb.append(",indentlevel="+m_indentLevels.intValue()); //$NON-NLS-1$ } if (m_childCount!=null) { sb.append(",childcount="+m_childCount.intValue()); //$NON-NLS-1$ } if (m_descendantCount!=null) { sb.append(",descendantcount="+m_descendantCount.intValue()); //$NON-NLS-1$ } if (m_siblingCount!=null) { sb.append(",siblingcount="+m_siblingCount.intValue()); //$NON-NLS-1$ } if (m_summaryData!=null) { sb.append(",summary="+m_summaryData.toString()); //$NON-NLS-1$ } if (m_columnValues!=null) { sb.append(",columns=["); //$NON-NLS-1$ for (int i=0; i0) { sb.append(","); //$NON-NLS-1$ } sb.append(colValueToString(m_columnValues[i])); } sb.append("]"); //$NON-NLS-1$ } if (m_ftScore!=null) { sb.append(",score="+m_ftScore.intValue()); //$NON-NLS-1$ } if (m_isUnread!=null) { sb.append(",unread="+m_isUnread.booleanValue()); //$NON-NLS-1$ } if (m_isAnyUnread!=null) { sb.append(",anyunread="+m_isAnyUnread.booleanValue()); //$NON-NLS-1$ } if (sb.length()>0) { //remove first "," sb.delete(0, 1); } sb.insert(0, "ViewEntry["); //$NON-NLS-1$ sb.append("]"); //$NON-NLS-1$ return sb.toString(); } /** * Converts a column value to a string, used for debugging values * * @param val column value * @return value as string */ private String colValueToString(Object val) { if (val==null) { return "null"; //$NON-NLS-1$ } if (val instanceof Calendar) { return ((Calendar)val).getTime().toString(); } else { return val.toString(); } } @Override public T get(String itemName, Class valueType, T defaultValue) { return m_typedAccess.get(itemName, valueType, defaultValue); } @Override public List getAsList(String itemName, Class valueType, List defaultValue) { return m_typedAccess.getAsList(itemName, valueType, defaultValue); } @Override public int size() { return getColumnDataAsMap().size(); } @Override public boolean isEmpty() { return getColumnDataAsMap().isEmpty(); } @Override public boolean containsKey(Object key) { if (key instanceof String) { return hasItem((String) key); } return false; } @Override public boolean containsValue(Object value) { return getColumnDataAsMap().containsValue(value); } @Override public Object get(Object key) { return getColumnDataAsMap().get(key); } @Override public Object put(String key, Object value) { return getColumnDataAsMap().put(key, value); } @Override public Object remove(Object key) { return getColumnDataAsMap().remove(key); } @Override public void putAll(Map m) { getColumnDataAsMap().putAll(m); } @Override public void clear() { getColumnDataAsMap().clear(); } @Override public Set keySet() { return getColumnDataAsMap().keySet(); } @Override public Collection values() { return getColumnDataAsMap().values(); } @Override public Set> entrySet() { return getColumnDataAsMap().entrySet(); } @Override public int getNoteID() { return m_noteId!=null ? m_noteId : 0; } @Override public Optional openDocument() { int noteId = getNoteID(); if (noteId!=0) { return ((Database) getParent().getParent()).getDocumentById(noteId); } String unid = getUNID(); if (!StringUtil.isEmpty(unid)) { return ((Database) getParent().getParent()).getDocumentByUNID(unid); } return Optional.empty(); } private void loadSequenceNumberAndTime() { if (m_sequenceNumber==null && m_sequenceTime==null && isDocument()) { DocInfo[] docInfos = getParent().getParentDatabase().getMultiDocumentInfo(new int[] {getNoteID()}); if (docInfos!=null && docInfos.length>0 && docInfos[0]!=null) { m_sequenceNumber = docInfos[0].getSequence(); m_sequenceTime = docInfos[0].getSequenceTime().orElse(null); } } } @SuppressWarnings("unchecked") private T getSequenceNumber(Class clazz, T defaultValue) { loadSequenceNumberAndTime(); if (m_sequenceNumber!=null) { if (Integer.class == clazz) { return (T) m_sequenceNumber; } else if (String.class == clazz) { return (T) m_sequenceNumber.toString(); } } return defaultValue; } @SuppressWarnings("unchecked") private T getSequenceTime(Class clazz, T defaultValue) { loadSequenceNumberAndTime(); if (m_sequenceTime!=null) { if (DominoDateTime.class == clazz) { return (T) m_sequenceTime; } else if (OffsetDateTime.class == clazz) { return (T) m_sequenceTime.toOffsetDateTime(); } else if (Temporal.class == clazz) { return (T) m_sequenceTime.toTemporal(); } } return defaultValue; } @Override public T getSpecialValue(SpecialValue value, Class clazz, T defaultValue) { switch (value) { case CHILDCOUNT: return getChildCount(clazz, defaultValue); case DESCENDANTCOUNT: return getDescendantCount(clazz, defaultValue); case SIBLINGCOUNT: return getSiblingCount(clazz, defaultValue); case INDEXPOSITION: return getPosition(clazz, defaultValue); case SEQUENCENUMBER: return getSequenceNumber(clazz, defaultValue); case SEQUENCETIME: return getSequenceTime(clazz, defaultValue); case UNREAD: return isUnread(clazz, defaultValue); case ANYUNREAD: return isAnyUnread(clazz, defaultValue); default: return defaultValue; } } @Override public int getIndexedValueCount() { return m_typedAccess.getIndexedValueCount(); } @Override public T get(int index, Class valueType, T defaultValue) { return m_typedAccess.get(index, valueType, defaultValue); } @Override public List getAsList(int index, Class valueType, List defaultValue) { return m_typedAccess.getAsList(index, valueType, defaultValue); } @Override public Optional getOptional(String itemName, Class valueType) { return m_typedAccess.getOptional(itemName, valueType); } @Override public Optional> getAsListOptional(String itemName, Class valueType) { return m_typedAccess.getAsListOptional(itemName, valueType); } }