Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.hcl.domino.jna.data.JNAIDTable Maven / Gradle / Ivy
/*
* ==========================================================================
* 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.lang.ref.ReferenceQueue;
import java.lang.reflect.Array;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.text.MessageFormat;
import java.time.temporal.TemporalAccessor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.SortedSet;
import java.util.stream.Collectors;
import com.hcl.domino.DominoException;
import com.hcl.domino.commons.errors.INotesErrorConstants;
import com.hcl.domino.commons.gc.APIObjectAllocations;
import com.hcl.domino.commons.gc.IAPIObject;
import com.hcl.domino.commons.gc.IGCDominoClient;
import com.hcl.domino.commons.util.NotesErrorUtils;
import com.hcl.domino.commons.util.PlatformUtils;
import com.hcl.domino.data.DominoDateTime;
import com.hcl.domino.data.IDTable;
import com.hcl.domino.jna.BaseJNAAPIObject;
import com.hcl.domino.jna.JNADominoClient;
import com.hcl.domino.jna.internal.Mem;
import com.hcl.domino.jna.internal.callbacks.NotesCallbacks;
import com.hcl.domino.jna.internal.callbacks.Win32NotesCallbacks;
import com.hcl.domino.jna.internal.capi.NotesCAPI;
import com.hcl.domino.jna.internal.gc.allocations.JNAIDTableAllocations;
import com.hcl.domino.jna.internal.gc.handles.DHANDLE;
import com.hcl.domino.jna.internal.gc.handles.LockUtil;
import com.hcl.domino.jna.internal.structs.NotesTimeDateStruct;
import com.hcl.domino.misc.NotesConstants;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
public class JNAIDTable extends BaseJNAAPIObject implements IDTable {
public JNAIDTable(IGCDominoClient> parent) {
this(parent, Collections.emptySet());
}
public JNAIDTable(IGCDominoClient> parent, Collection noteIds) {
super(parent);
init();
if (!noteIds.isEmpty()) {
addAll(noteIds);
}
setInitialized();
}
public JNAIDTable(IGCDominoClient> parent, DHANDLE handle, boolean noDispose) {
super(parent);
JNAIDTableAllocations allocations = getAllocations();
allocations.setIdTableHandle(handle);
if (noDispose) {
allocations.setNoDispose();
}
setInitialized();
}
@SuppressWarnings("unchecked")
@Override
protected T getAdapterLocal(Class clazz) {
if (clazz==DHANDLE.class) {
return (T) getAllocations().getIdTableHandle();
}
return null;
}
@SuppressWarnings("rawtypes")
@Override
protected JNAIDTableAllocations createAllocations(IGCDominoClient> parentDominoClient,
APIObjectAllocations parentAllocations, ReferenceQueue super IAPIObject> queue) {
return new JNAIDTableAllocations(parentDominoClient, parentAllocations, this, queue);
}
private void init() {
DHANDLE.ByReference retHandle = DHANDLE.newInstanceByReference();
short noteIdLength = NotesCAPI.get().ODSLength((short) 1); //_NOTEID
short result = NotesCAPI.get().IDCreateTable(noteIdLength, retHandle);
NotesErrorUtils.checkResult(result);
if (retHandle.isNull()) {
throw new DominoException(0, "Null handle received for id table");
}
JNAIDTableAllocations allocations = getAllocations();
allocations.setIdTableHandle(retHandle);
}
@Override
public int size() {
checkDisposed();
JNAIDTableAllocations allocations = getAllocations();
int entries = LockUtil.lockHandle(allocations.getIdTableHandle(), (handleByVal) -> {
return NotesCAPI.get().IDEntries(handleByVal);
});
return entries;
}
@Override
public boolean isEmpty() {
checkDisposed();
IntByReference retID = new IntByReference();
boolean first = true;
boolean hasData = LockUtil.lockHandle(getAllocations().getIdTableHandle(), (handleByVal) -> {
return NotesCAPI.get().IDScan(handleByVal, first, retID) && retID.getValue()!=0;
});
return !hasData;
}
@Override
public boolean contains(Object o) {
if (o instanceof Integer) {
checkDisposed();
JNAIDTableAllocations allocations = getAllocations();
return LockUtil.lockHandle(allocations.getIdTableHandle(), (handleByVal) -> {
return NotesCAPI.get().IDIsPresent(handleByVal, ((Integer)o));
});
}
return false;
}
@Override
public Iterator iterator() {
return new NoteIdIterator(this, false);
}
@Override
public Iterator reverseIterator() {
return new NoteIdIterator(this, true);
}
/**
* Callback interface for ID table scanning
*
* @author Karsten Lehmann
*/
private interface IEnumerateCallback {
public enum Action {Continue, Stop};
/**
* Method is called for each ID in the table
*
* @param noteId not id
* @return either {@link Action#Continue} to go on scanning or {@link Action#Stop}
*/
Action noteVisited(int noteId);
}
/**
* Traverses the ID table
*
* @param callback callback is called for each ID
*/
private void enumerate(final IEnumerateCallback callback) {
checkDisposed();
JNAIDTableAllocations allocations = getAllocations();
final NotesCallbacks.IdEnumerateProc proc;
if (PlatformUtils.isWin32()) {
proc = (Win32NotesCallbacks.IdEnumerateProcWin32) (parameter, noteId) -> {
IEnumerateCallback.Action result = callback.noteVisited(noteId);
if (result==IEnumerateCallback.Action.Stop) {
return INotesErrorConstants.ERR_CANCEL;
}
return 0;
};
}
else {
proc = (parameter, noteId) -> {
IEnumerateCallback.Action result = callback.noteVisited(noteId);
if (result==IEnumerateCallback.Action.Stop) {
return INotesErrorConstants.ERR_CANCEL;
}
return 0;
};
}
try {
//AccessController call required to prevent SecurityException when running in XPages
AccessController.doPrivileged((PrivilegedExceptionAction) () -> {
short result = LockUtil.lockHandle(allocations.getIdTableHandle(), (handleByVal) -> {
return NotesCAPI.get().IDEnumerate(handleByVal, proc, null);
});
if (result!=INotesErrorConstants.ERR_CANCEL) {
NotesErrorUtils.checkResult(result);
}
return null;
});
} catch (PrivilegedActionException e) {
if (e.getCause() instanceof RuntimeException) {
throw (RuntimeException) e.getCause();
} else {
throw new DominoException(0, "Error enumerating ID table", e);
}
}
}
/**
* Converts the content of this id table to a list of Integer
*
* @return list
*/
private List toList(int maxEntries) {
final List idsAsList = new ArrayList<>();
if (maxEntries > 0) {
enumerate(noteId -> {
idsAsList.add(noteId);
if (idsAsList.size() >= maxEntries) {
return IEnumerateCallback.Action.Stop;
}
else {
return IEnumerateCallback.Action.Continue;
}
});
}
return idsAsList;
}
@Override
public Object[] toArray() {
List idsAsList = toList(Integer.MAX_VALUE);
Object[] idsArr = new Object[idsAsList.size()];
for (int i=0; i T[] toArray(T[] a) {
int size = size();
List noteIds = toList(Integer.MAX_VALUE);
if (a.length < size) {
// Make a new array of a's runtime type
T[] arr = (T[]) Array.newInstance(a.getClass().getComponentType(), size);
for (int i=0; i size) {
a[size] = null;
}
return a;
}
}
@Override
public boolean add(Integer noteId) {
checkDisposed();
JNAIDTableAllocations allocations = getAllocations();
IntByReference retInserted = new IntByReference();
short result = LockUtil.lockHandle(allocations.getIdTableHandle(), (handleByVal) -> {
return NotesCAPI.get().IDInsert(handleByVal, noteId, retInserted);
});
NotesErrorUtils.checkResult(result);
int retInsertedAsInt = retInserted.getValue();
return retInsertedAsInt != 0;
}
@Override
public boolean remove(Object noteId) {
if (noteId instanceof Integer) {
checkDisposed();
JNAIDTableAllocations allocations = getAllocations();
IntByReference retDeleted = new IntByReference();
short result = LockUtil.lockHandle(allocations.getIdTableHandle(), (handleByVal) -> {
return NotesCAPI.get().IDDelete(handleByVal, (Integer) noteId, retDeleted);
});
NotesErrorUtils.checkResult(result);
int retDeletedAsInt = retDeleted.getValue();
return retDeletedAsInt != 0;
}
else {
return false;
}
}
@Override
public boolean containsAll(Collection> c) {
checkDisposed();
JNAIDTableAllocations allocations = getAllocations();
JNAIDTable otherIDTable;
boolean recycleOtherIDTable;
if (c instanceof JNAIDTable) {
otherIDTable = (JNAIDTable) c;
otherIDTable.checkDisposed();
recycleOtherIDTable = false;
}
else {
Collection intCol = toIntCollection(c);
if (intCol.size() != c.size()) {
return false;
}
otherIDTable = new JNAIDTable((JNADominoClient) getParent(), intCol);
recycleOtherIDTable = true;
}
try {
JNAIDTableAllocations otherIDTableAllocations = otherIDTable.getAllocations();
//intersect both IDTables and check if the result size matches our current size
return LockUtil.lockHandles(
allocations.getIdTableHandle(),
otherIDTableAllocations.getIdTableHandle(),
(ourHandleByVal, otherHandleByVal) -> {
DHANDLE.ByReference rethDstTable = DHANDLE.newInstanceByReference();
short result = NotesCAPI.get().IDTableIntersect(ourHandleByVal, otherHandleByVal, rethDstTable);
NotesErrorUtils.checkResult(result);
int intersectedEntries = LockUtil.lockHandle(rethDstTable, (rethDstTableByVal) -> {
int numEntries = NotesCAPI.get().IDEntries(rethDstTableByVal);
NotesCAPI.get().IDDestroyTable(rethDstTableByVal);
return numEntries;
});
return size() == intersectedEntries;
}
);
}
finally {
if (recycleOtherIDTable) {
otherIDTable.dispose();
}
}
}
@Override
public boolean addAll(Collection extends Integer> c) {
boolean addToEnd = false;
if (isEmpty()) {
addToEnd = true;
}
return addAll(c, addToEnd);
}
private final Comparator unsignedIntComparator = new Comparator() {
@Override
public int compare(Integer o1, Integer o2) {
long l1 = o1.longValue() & 0xFFFFFFFFL;
long l2 = o2.longValue() & 0xFFFFFFFFL;
return Long.compare(l1, l2);
}
};
private boolean addAll(Collection extends Integer> c, boolean addToEnd) {
checkDisposed();
JNAIDTableAllocations allocations = getAllocations();
int oldSize = size();
if (c instanceof JNAIDTable) {
JNAIDTable otherIDTable = (JNAIDTable) c;
otherIDTable.checkDisposed();
JNAIDTableAllocations otherIDTableAllocations = otherIDTable.getAllocations();
if (otherIDTableAllocations==null) {
throw new DominoException(0, "Unable to read handle of other IDTable");
}
short result = LockUtil.lockHandles(
allocations.getIdTableHandle(),
otherIDTableAllocations.getIdTableHandle(),
(ourIDTableHandleByVal, otherIDTableHandleByVal) -> {
return NotesCAPI.get().IDInsertTable(ourIDTableHandleByVal, otherIDTableHandleByVal);
});
NotesErrorUtils.checkResult(result);
}
else {
Integer[] noteIdsArr = c.toArray(new Integer[c.size()]);
//sorts category note ids to the end of the list (with NOTEID_CATEGORY bit set)
Arrays.sort(noteIdsArr, unsignedIntComparator);
LinkedList currIdRange = new LinkedList<>();
LockUtil.lockHandle(
allocations.getIdTableHandle(),
(ourIDTableHandleByVal) -> {
//find consecutive id ranges to reduce number of insert operations (insert ranges)
for (int i=0; i c) {
Collection noteIds = toIntCollection(c);
int oldSize = size();
if (c.isEmpty()) {
clear();
return oldSize > 0;
}
JNAIDTable intersectResult = (JNAIDTable) intersect(noteIds);
clear();
addAll(intersectResult, true);
return oldSize != size();
}
@SuppressWarnings("unchecked")
private Collection toIntCollection(Collection> c) {
if (c.isEmpty() || c instanceof IDTable) {
return (Collection) c;
}
boolean allInts = true;
for (Object currObj : c) {
if (!(currObj instanceof Integer)) {
allInts = false;
break;
}
}
Collection noteIds;
if (!allInts) {
noteIds = c
.stream()
.filter((o) -> { return o instanceof Integer; })
.mapToInt((o) -> { return (Integer) o; })
.boxed().collect(Collectors.toList());
if (noteIds.isEmpty()) {
return new ArrayList<>();
}
}
else {
noteIds = (Collection) c;
}
return noteIds;
}
@Override
public boolean removeAll(Collection> c) {
checkDisposed();
JNAIDTableAllocations ourAllocations = getAllocations();
Collection noteIds = toIntCollection(c);
if (c.isEmpty()) {
return false;
}
JNAIDTable otherIDTable;
boolean disposeOtherIDTable;
JNAIDTableAllocations otherAllocations;
if (noteIds instanceof JNAIDTable) {
otherIDTable = (JNAIDTable) noteIds;
otherIDTable.checkDisposed();
otherAllocations = otherIDTable.getAllocations();
if (otherAllocations==null) {
throw new DominoException(0, "Cannot access other IDTable's resources");
}
disposeOtherIDTable = false;
}
else {
otherIDTable = new JNAIDTable((JNADominoClient) getParent(), noteIds);
otherAllocations = (JNAIDTableAllocations) otherIDTable.getAdapter(APIObjectAllocations.class);
disposeOtherIDTable = true;
}
int oldSize = size();
try {
short result = LockUtil.lockHandles(
ourAllocations.getIdTableHandle(),
otherAllocations.getIdTableHandle(), (handleByVal, otherAllocationsHandleByVal) -> {
return NotesCAPI.get().IDDeleteTable(handleByVal, otherAllocationsHandleByVal);
});
NotesErrorUtils.checkResult(result);
return oldSize != size();
}
finally {
if (disposeOtherIDTable) {
otherIDTable.dispose();
}
}
}
@Override
public void clear() {
checkDisposed();
JNAIDTableAllocations allocations = getAllocations();
short result = LockUtil.lockHandle(allocations.getIdTableHandle(), (handleByVal) -> {
return NotesCAPI.get().IDDeleteAll(handleByVal);
});
NotesErrorUtils.checkResult(result);
}
@Override
public IDTable intersect(Collection noteIds) {
checkDisposed();
JNAIDTableAllocations ourAllocations = getAllocations();
JNAIDTable otherIDTable;
boolean disposeOtherIDTable;
JNAIDTableAllocations otherAllocations;
if (noteIds instanceof JNAIDTable) {
otherIDTable = (JNAIDTable) noteIds;
otherIDTable.checkDisposed();
otherAllocations = otherIDTable.getAllocations();
if (otherAllocations==null) {
throw new DominoException(0, "Cannot access other IDTable's resources");
}
disposeOtherIDTable = false;
}
else {
otherIDTable = new JNAIDTable((JNADominoClient) getParent(), noteIds);
otherAllocations = otherIDTable.getAllocations();
disposeOtherIDTable = true;
}
try {
DHANDLE.ByReference retTableHandle = DHANDLE.newInstanceByReference();
short result = LockUtil.lockHandles(
ourAllocations.getIdTableHandle(),
otherAllocations.getIdTableHandle(),
(handleByVal, otherAllocationsIDTableHandleByVal) -> {
return NotesCAPI.get().IDTableIntersect(handleByVal,
otherAllocationsIDTableHandleByVal, retTableHandle);
});
NotesErrorUtils.checkResult(result);
JNAIDTable retTable = new JNAIDTable((JNADominoClient)getParent(), retTableHandle, false);
return retTable;
}
finally {
if (disposeOtherIDTable) {
otherIDTable.dispose();
}
}
}
@Override
public boolean isInverted() {
checkDisposed();
JNAIDTableAllocations allocations = getAllocations();
return LockUtil.lockHandle(allocations.getIdTableHandle(), (handleByVal) -> {
Pointer ptr = Mem.OSLockObject(handleByVal);
try {
short flags = NotesCAPI.get().IDTableFlags(ptr);
if ((flags & NotesConstants.IDTABLE_INVERTED)==NotesConstants.IDTABLE_INVERTED) {
return true;
}
}
finally {
Mem.OSUnlockObject(handleByVal);
}
return false;
});
}
@Override
public void setInverted(boolean inverted) {
checkDisposed();
JNAIDTableAllocations allocations = getAllocations();
LockUtil.lockHandle(allocations.getIdTableHandle(), (handleByVal) -> {
short newFlags = (short) ((isModified() ? NotesConstants.IDTABLE_MODIFIED : 0) + (inverted ? NotesConstants.IDTABLE_INVERTED : 0));
Pointer ptr = Mem.OSLockObject(handleByVal);
try {
NotesCAPI.get().IDTableSetFlags(ptr, newFlags);
}
finally {
Mem.OSUnlockObject(handleByVal);
}
return 0;
});
}
@Override
public boolean isModified() {
checkDisposed();
JNAIDTableAllocations allocations = getAllocations();
return LockUtil.lockHandle(allocations.getIdTableHandle(), (handleByVal) -> {
Pointer ptr = Mem.OSLockObject(handleByVal);
try {
short flags = NotesCAPI.get().IDTableFlags(ptr);
if ((flags & NotesConstants.IDTABLE_MODIFIED)==NotesConstants.IDTABLE_MODIFIED) {
return true;
}
}
finally {
Mem.OSUnlockObject(handleByVal);
}
return false;
});
}
@Override
public void setModified(boolean modified) {
checkDisposed();
JNAIDTableAllocations allocations = getAllocations();
short newFlags = (short) ((isInverted() ? NotesConstants.IDTABLE_INVERTED : 0) + (modified ? NotesConstants.IDTABLE_MODIFIED : 0));
LockUtil.lockHandle(allocations.getIdTableHandle(), (handleByVal) -> {
Pointer ptr = Mem.OSLockObject(handleByVal);
try {
NotesCAPI.get().IDTableSetFlags(ptr, newFlags);
}
finally {
Mem.OSUnlockObject(handleByVal);
}
return 0;
});
}
@Override
public Optional getDateTime() {
checkDisposed();
JNAIDTableAllocations allocations = getAllocations();
return LockUtil.lockHandle(allocations.getIdTableHandle(), (handleByVal) -> {
Pointer ptr = Mem.OSLockObject(handleByVal);
try {
NotesTimeDateStruct timeStruct = NotesCAPI.get().IDTableTime(ptr);
return Optional.ofNullable(timeStruct==null ? null : new JNADominoDateTime(timeStruct));
}
finally {
Mem.OSUnlockObject(handleByVal);
}
});
}
@Override
public void setDateTime(TemporalAccessor dt) {
checkDisposed();
JNAIDTableAllocations allocations = getAllocations();
int[] dtInnards = JNADominoDateTime.from(dt).getInnards();
if (dtInnards==null) {
throw new DominoException(0, "Unable to read datetime innards");
}
NotesTimeDateStruct timeStruct = dt==null ? null : NotesTimeDateStruct.newInstance(dtInnards);
LockUtil.lockHandle(allocations.getIdTableHandle(), (handleByVal) -> {
Pointer ptr = Mem.OSLockObject(handleByVal);
try {
NotesCAPI.get().IDTableSetTime(ptr, timeStruct);
}
finally {
Mem.OSUnlockObject(handleByVal);
}
return 0;
});
}
private static class NoteIdIterator implements Iterator {
private JNAIDTable m_idTable;
private IntByReference m_nextIdRef;
private Integer m_nextId;
private boolean m_scanBackward;
private NoteIdIterator(JNAIDTable idTable, boolean scanBackward) {
m_idTable = idTable;
m_scanBackward = scanBackward;
fetchNext();
}
private void fetchNext() {
JNAIDTableAllocations allocations = m_idTable.getAllocations();
allocations.checkDisposed();
boolean isFirstVal;
if (m_nextIdRef==null) {
//first id
isFirstVal = true;
m_nextIdRef = new IntByReference();
}
else if (m_nextId==null) {
//no more data
return;
}
else {
isFirstVal = false;
m_nextIdRef.setValue(m_nextId);
}
LockUtil.lockHandle(allocations.getIdTableHandle(), (handleByVal) -> {
if (m_scanBackward) {
if (NotesCAPI.get().IDScanBack(handleByVal, isFirstVal, m_nextIdRef)) {
m_nextId = m_nextIdRef.getValue();
}
else {
m_nextId = null;
}
}
else {
if (NotesCAPI.get().IDScan(handleByVal, isFirstVal, m_nextIdRef)) {
m_nextId = m_nextIdRef.getValue();
}
else {
m_nextId = null;
}
}
return 0;
});
}
@Override
public boolean hasNext() {
return m_nextId!=null;
}
@Override
public Integer next() {
if (m_nextId==null) {
throw new NoSuchElementException("No more elements");
}
Integer nextId = m_nextId;
fetchNext();
return nextId;
}
}
/**
* Returns the first ID in the table
*
* @return ID
* @throws DominoException with {@link INotesErrorConstants#ERR_IDTABLE_LENGTH_MISMATCH} if ID table is empty
*/
public int getFirstId() {
checkDisposed();
JNAIDTableAllocations allocations = getAllocations();
return LockUtil.lockHandle(allocations.getIdTableHandle(), (handleByVal) -> {
IntByReference retID = new IntByReference();
if (NotesCAPI.get().IDScan(handleByVal, true, retID)) {
return retID.getValue();
}
else {
throw new DominoException(INotesErrorConstants.ERR_IDTABLE_LENGTH_MISMATCH, "ID table is empty");
}
});
}
/**
* Returns the last ID in the table
*
* @return ID
* @throws DominoException with {@link INotesErrorConstants#ERR_IDTABLE_LENGTH_MISMATCH} if ID table is empty
*/
public int getLastId() {
checkDisposed();
JNAIDTableAllocations allocations = getAllocations();
return LockUtil.lockHandle(allocations.getIdTableHandle(), (handleByVal) -> {
IntByReference retID = new IntByReference();
if (NotesCAPI.get().IDScanBack(handleByVal, true, retID)) {
return retID.getValue();
}
else {
throw new DominoException(INotesErrorConstants.ERR_IDTABLE_LENGTH_MISMATCH, "ID table is empty");
}
});
}
@Override
public int[] toIntArray() {
checkDisposed();
int size = size();
int[] result = new int[size];
int[] i = new int[] { 0 };
enumerate(noteId -> {
result[i[0]++] = noteId;
return IEnumerateCallback.Action.Continue;
});
return result;
}
/**
* Creates a new ID table with the IDs of this table, but with high order
* bit set (0x80000000L).
*
* @return ID table
*/
public JNAIDTable withHighOrderBit() {
List ids = toList(Integer.MAX_VALUE);
for (int i=0; i {
DHANDLE.ByReference rethTable = DHANDLE.newInstanceByReference();
short result = NotesCAPI.get().IDTableCopy(handleByVal, rethTable);
NotesErrorUtils.checkResult(result);
return new JNAIDTable(getParentDominoClient(), rethTable, false);
});
}
@Override
protected String toStringLocal() {
if (isDisposed()) {
return "JNAIDTable [disposed]"; //$NON-NLS-1$
}
else {
return MessageFormat.format(
"JNAIDTable [handle={0}, inverted={1}, {2} entries]", //$NON-NLS-1$
getAllocations().getIdTableHandle(), isInverted(), size()
);
}
}
}