com.mindoo.domino.jna.utils.NotesStringUtils Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of domino-jna Show documentation
Show all versions of domino-jna Show documentation
Java project to access the HCL Domino C API using Java Native Access (JNA)
package com.mindoo.domino.jna.utils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Formatter;
import java.util.List;
import com.mindoo.domino.jna.errors.NotesError;
import com.mindoo.domino.jna.errors.NotesErrorUtils;
import com.mindoo.domino.jna.gc.NotesGC;
import com.mindoo.domino.jna.internal.DisposableMemory;
import com.mindoo.domino.jna.internal.INotesNativeAPI;
import com.mindoo.domino.jna.internal.NotesConstants;
import com.mindoo.domino.jna.internal.NotesNativeAPI;
import com.mindoo.domino.jna.internal.ReadOnlyMemory;
import com.mindoo.domino.jna.internal.SizeLimitedLRUCache;
import com.sun.jna.Memory;
import com.sun.jna.Pointer;
/**
* String conversion functions between Java and LMBCS
*
* @author Karsten Lehmann
*/
public class NotesStringUtils {
private static final String PREF_USEOSLINEBREAK = "NotesStringUtils.useOSLineDelimiter";
//use simple cache for string-lmbcs conversion of short string
private static final boolean USE_STRING2LMBCS_CACHE = true;
//max length of each string-lmbcs cache entry in characters
private static final int MAX_STRING2LMBCS_KEY_LENGTH = 500;
private static final int MAX_STRING2LMBCS_SIZE_BYTES = 1000000;
private static LRUStringLMBCSCache m_string2LMBCSCache_NullTerminated_LinefeedLinebreaks = new LRUStringLMBCSCache(MAX_STRING2LMBCS_SIZE_BYTES);
private static LRUStringLMBCSCache m_string2LMBCSCache_NotNullTerminated_LinefeedLinebreaks = new LRUStringLMBCSCache(MAX_STRING2LMBCS_SIZE_BYTES);
private static LRUStringLMBCSCache m_string2LMBCSCache_NullTerminated_NullLinebreaks = new LRUStringLMBCSCache(MAX_STRING2LMBCS_SIZE_BYTES);
private static LRUStringLMBCSCache m_string2LMBCSCache_NotNullTerminated_NullLinebreaks = new LRUStringLMBCSCache(MAX_STRING2LMBCS_SIZE_BYTES);
private static LRUStringLMBCSCache m_string2LMBCSCache_NullTerminated_OriginalLinebreaks = new LRUStringLMBCSCache(MAX_STRING2LMBCS_SIZE_BYTES);
private static LRUStringLMBCSCache m_string2LMBCSCache_NotNullTerminated_OriginalLinebreaks = new LRUStringLMBCSCache(MAX_STRING2LMBCS_SIZE_BYTES);
private static final Charset charsetUTF8 = Charset.forName("UTF-8");
public static void flushCache() {
m_string2LMBCSCache_NullTerminated_LinefeedLinebreaks.clear();
m_string2LMBCSCache_NotNullTerminated_LinefeedLinebreaks.clear();
m_string2LMBCSCache_NullTerminated_NullLinebreaks.clear();
m_string2LMBCSCache_NotNullTerminated_NullLinebreaks.clear();
m_string2LMBCSCache_NullTerminated_OriginalLinebreaks.clear();
m_string2LMBCSCache_NotNullTerminated_OriginalLinebreaks.clear();
}
/**
* Method to control the LMBCS / Java String conversion for newline characters. By default
* we insert \r\n on Windows and \n on other platforms like IBM does.
* This setting is only valid for the current {@link NotesGC#runWithAutoGC(java.util.concurrent.Callable)}
* call.
*
* @param b true to use \r\n as newline on Windows, false to use \n everywhere
*/
public static void setUseOSLineDelimiter(boolean b) {
if (isUseOSLineDelimiter() != b) {
NotesGC.setCustomValue(PREF_USEOSLINEBREAK, Boolean.valueOf(b));
//remove all cached values that contain newlines
List keysWithNull = m_string2LMBCSCache_NullTerminated_LinefeedLinebreaks.getKeys();
for (String currKey : keysWithNull) {
if (currKey.indexOf('\n') != -1) {
m_string2LMBCSCache_NullTerminated_LinefeedLinebreaks.remove(currKey);
}
}
keysWithNull = m_string2LMBCSCache_NullTerminated_NullLinebreaks.getKeys();
for (String currKey : keysWithNull) {
if (currKey.indexOf('\n') != -1) {
m_string2LMBCSCache_NullTerminated_NullLinebreaks.remove(currKey);
}
}
keysWithNull = m_string2LMBCSCache_NullTerminated_OriginalLinebreaks.getKeys();
for (String currKey : keysWithNull) {
if (currKey.indexOf('\n') != -1) {
m_string2LMBCSCache_NullTerminated_OriginalLinebreaks.remove(currKey);
}
}
List keysWithoutNull = m_string2LMBCSCache_NotNullTerminated_LinefeedLinebreaks.getKeys();
for (String currKey : keysWithoutNull) {
if (currKey.indexOf('\n') != -1) {
m_string2LMBCSCache_NotNullTerminated_LinefeedLinebreaks.remove(currKey);
}
}
keysWithoutNull = m_string2LMBCSCache_NotNullTerminated_NullLinebreaks.getKeys();
for (String currKey : keysWithoutNull) {
if (currKey.indexOf('\n') != -1) {
m_string2LMBCSCache_NotNullTerminated_NullLinebreaks.remove(currKey);
}
}
keysWithoutNull = m_string2LMBCSCache_NotNullTerminated_OriginalLinebreaks.getKeys();
for (String currKey : keysWithoutNull) {
if (currKey.indexOf('\n') != -1) {
m_string2LMBCSCache_NotNullTerminated_OriginalLinebreaks.remove(currKey);
}
}
}
}
/**
* Returns whether an OS specific newline is used when converting between LMBCS and Java String.
* By default we insert \r\n on Windows and \n on other platforms like IBM does.
*
* @return true to use \r\n as newline on Windows, false to use \n everywhere
*/
public static boolean isUseOSLineDelimiter() {
Boolean b = (Boolean) NotesGC.getCustomValue(PREF_USEOSLINEBREAK);
if (b==null)
return Boolean.TRUE;
else
return b.booleanValue();
}
/**
* Scans the byte array for null values
*
* @param bytes array of bytes
* @return number of bytes before null byte in array
*/
public static int getNullTerminatedLength(byte[] bytes) {
if (bytes == null) {
return 0;
}
int textLen = bytes.length;
//search for terminating null character
for (int i=0; i fromLMBCSStringList(Pointer inPtr, int numEntries) {
List stringList = new ArrayList();
Pointer ptrStartOfString = inPtr;
for (int i=0; i lines = new ArrayList();
INotesNativeAPI api = NotesNativeAPI.get();
//output buffer shared across loop runs for each line
DisposableMemory outBufUTF8 = null;
try {
for (int i=0; i0) {
if (lineBreakConversion == LineBreakConversion.NULL) {
//replace line breaks with null characters
bOut.write(0);
}
else if (lineBreakConversion == LineBreakConversion.LINEFEED) {
//replace line breaks (e.g. \r\n on Windows) with \n
bOut.write('\n');
}
else {
//should not happen
throw new IllegalArgumentException("Unexpected line break conversion: "+lineBreakConversion);
}
}
if (lines[i].length() == 0) {
continue;
}
//check if string only contains ascii characters that map 1:1 to LMBCS;
//in this case we can skip the OSTranslate call
boolean isPureAscii = true;
for (int x=0; x= 0x80) {
isPureAscii = false;
break;
}
}
byte[] lineDataAsUTF8 = lines[i].getBytes(charsetUTF8);
if (isPureAscii) {
try {
bOut.write(lineDataAsUTF8);
} catch (IOException e) {
throw new NotesError(0, "Error writing to temporary byte stream", e);
}
}
else {
int worstCaseLMBCSLength = 3 * lines[i].length();
if (inputBufUTF8!=null && inputBufUTF8.size() < lineDataAsUTF8.length) {
inputBufUTF8.dispose();
inputBufUTF8 = null;
}
if (inputBufUTF8==null) {
inputBufUTF8 = new DisposableMemory(lineDataAsUTF8.length);
}
inputBufUTF8.write(0, lineDataAsUTF8, 0, lineDataAsUTF8.length);
if (outputBufLMBCS!=null && outputBufLMBCS.size() < worstCaseLMBCSLength) {
outputBufLMBCS.dispose();
outputBufLMBCS = null;
}
if (outputBufLMBCS==null) {
outputBufLMBCS = new DisposableMemory(worstCaseLMBCSLength);
}
do {
int retOutBufLength = api.OSTranslate32(
NotesConstants.OS_TRANSLATE_UTF8_TO_LMBCS,
inputBufUTF8, (int) lineDataAsUTF8.length,
outputBufLMBCS, (int) outputBufLMBCS.size());
if (retOutBufLength==outputBufLMBCS.size()) {
// output buffer not large enough, increase it and retry (not expected to happen because of
// our worst case computation)
long oldOutBufSize = outputBufLMBCS.size();
long newOutBufSize = (long) (((double) oldOutBufSize)*2);
outputBufLMBCS.dispose();
outputBufLMBCS = new DisposableMemory(newOutBufSize);
continue;
}
else if (retOutBufLength>0) {
//success
byte[] data = outputBufLMBCS.getByteArray(0, retOutBufLength);
try {
bOut.write(data);
} catch (IOException e) {
throw new NotesError(0, "Error writing to temporary byte stream", e);
}
break;
}
}
while (true);
}
}
}
finally {
if (inputBufUTF8!=null) {
inputBufUTF8.dispose();
}
if (outputBufLMBCS!=null) {
outputBufLMBCS.dispose();
}
}
if (addNull) {
int limit = bOut.size();
Memory m;
if (noCache) {
m = new DisposableMemory(limit + 1);
}
else {
m = new ReadOnlyMemory(limit + 1);
}
byte[] data = bOut.toByteArray();
m.write(0, data, 0, data.length);
m.setByte(limit, (byte) 0);
if (!noCache) {
((ReadOnlyMemory)m).seal();
if (USE_STRING2LMBCS_CACHE && inStr.length()<=MAX_STRING2LMBCS_KEY_LENGTH) {
if (cacheToUse!=null) {
cacheToUse.put(inStr, m);
}
}
}
return m;
}
else {
Memory m;
if (noCache) {
m = new DisposableMemory(bOut.size());
}
else {
m = new ReadOnlyMemory(bOut.size());
}
byte[] data = bOut.toByteArray();
m.write(0, data, 0, data.length);
if (!noCache) {
((ReadOnlyMemory)m).seal();
if (USE_STRING2LMBCS_CACHE && inStr.length()<=MAX_STRING2LMBCS_KEY_LENGTH) {
if (cacheToUse!=null) {
cacheToUse.put(inStr, m);
}
}
}
return m;
}
}
/**
* Converts bytes in memory to a UNID
*
* @param innardsFile innards of file part
* @param innardsNote innards of note part
* @return unid
*/
public static String toUNID(long innardsFile, long innardsNote) {
Formatter formatter = new Formatter();
formatter.format("%016x", innardsFile);
formatter.format("%016x", innardsNote);
String unid = formatter.toString().toUpperCase();
formatter.close();
return unid;
}
/**
* Reads a UNID from memory
*
* @param ptr memory
* @return UNID as string
*/
public static String pointerToUnid(Pointer ptr) {
Formatter formatter = new Formatter();
ByteBuffer data = ptr.getByteBuffer(0, 16).order(ByteOrder.LITTLE_ENDIAN);
formatter.format("%016x", data.getLong());
formatter.format("%016x", data.getLong());
String unidStr = formatter.toString().toUpperCase();
formatter.close();
return unidStr;
}
/**
* Writes a UNID string to memory
*
* @param unidStr UNID string
* @param target target memory
*/
public static void unidToPointer(String unidStr, Pointer target) {
try {
int fileInnards1 = (int) (Long.parseLong(unidStr.substring(0,8), 16) & 0xffffffff);
int fileInnards0 = (int) (Long.parseLong(unidStr.substring(8,16), 16) & 0xffffffff);
int noteInnards1 = (int) (Long.parseLong(unidStr.substring(16,24), 16) & 0xffffffff);
int noteInnards0 = (int) (Long.parseLong(unidStr.substring(24,32), 16) & 0xffffffff);
target.setInt(0, fileInnards0);
target.share(4).setInt(0, fileInnards1);
target.share(8).setInt(0, noteInnards0);
target.share(12).setInt(0, noteInnards1);
}
catch (Exception e) {
throw new NotesError(0, "Could not convert UNID to memory: "+unidStr, e);
}
}
/**
* This function takes a port name, a server name, and file path relative to the Domino or
* Notes data directory and creates a full network path specification for a Domino database
* file.
*
* To open a Domino database on a server, use this function to create the full path specification,
* and pass this specification as input to NSFDbOpen or NSFDbOpenExtended.
*
* @param portName network port name or NULL to allow Domino or Notes to use the "most available" port to the given server
* @param serverName Name of the server (either in abbreviated format, canonical format or as common name) or "" for local
* @param fileName filename of the Domino database you with to access, relative to the data directory
* @return fully qualified network path
*/
public static String osPathNetConstruct(String portName, String serverName, String fileName) {
serverName = NotesNamingUtils.toCanonicalName(serverName);
Memory portNameMem = toLMBCS(portName, true);
Memory serverNameMem = toLMBCS(serverName, true);
Memory fileNameMem = toLMBCS(fileName, true);
DisposableMemory retPathMem = new DisposableMemory(NotesConstants.MAXPATH);
short result = NotesNativeAPI.get().OSPathNetConstruct(portNameMem, serverNameMem, fileNameMem, retPathMem);
NotesErrorUtils.checkResult(result);
String retPath = fromLMBCS(retPathMem, getNullTerminatedLength(retPathMem));
retPathMem.dispose();
return retPath;
}
/**
* Given a fully-qualified network path to a Domino database file, this function breaks it
* into its port name, server name, and filename components.
* If the fully qualified path contains just the port name and/or server name components,
* then they will be the only ones returned.
*
* Expanded database filepath syntax:
*
* {Port} NetworkSeparator {servername} Serversuffix {filename}
* COM! {NetworkSeparator} NOTESBETA {ServerSuffix} NOTEFILE\APICOMMS.NSF
*
* Note: the NetworkSeparator and ServerSuffix are not system independent. To maintain the
* portability of your code, it is recommended that you make no explicit use of them
* anywhere in your programs.
*
* @param pathName expanded path specification of a Domino database file
* @return String array of portname, servername, filename
*/
public static String[] osPathNetParse(String pathName) {
DisposableMemory retPortNameMem = new DisposableMemory(NotesConstants.MAXPATH);
DisposableMemory retServerNameMem = new DisposableMemory(NotesConstants.MAXPATH);
DisposableMemory retFileNameMem = new DisposableMemory(NotesConstants.MAXPATH);
Memory pathNameMem = toLMBCS(pathName, true);
short result = NotesNativeAPI.get().OSPathNetParse(pathNameMem, retPortNameMem, retServerNameMem, retFileNameMem);
NotesErrorUtils.checkResult(result);
String portName = fromLMBCS(retPortNameMem, getNullTerminatedLength(retPortNameMem));
String serverName = fromLMBCS(retServerNameMem, getNullTerminatedLength(retServerNameMem));
String fileName = fromLMBCS(retFileNameMem, getNullTerminatedLength(retFileNameMem));
retPortNameMem.dispose();
retServerNameMem.dispose();
retFileNameMem.dispose();
return new String[] {portName, serverName, fileName};
}
/**
* Converts an innards array to hex format, e.g. used for replica ids
*
* @param innards innards array with two elements
* @return replica id (16 character hex string)
*/
public static String innardsToReplicaId(int[] innards) {
return StringUtil.pad(Integer.toHexString(innards[1]).toUpperCase(), 8, '0', false) +
StringUtil.pad(Integer.toHexString(innards[0]).toUpperCase(), 8, '0', false);
}
/**
* Converts a replica id to an innards array
*
* @param replicaId replica id
* @return innards array with two elements
*/
public static int[] replicaIdToInnards(String replicaId) {
if (replicaId.contains(":"))
replicaId = replicaId.replace(":", "");
if (replicaId.length() != 16) {
throw new IllegalArgumentException("Replica ID is expected to have 16 hex characters or 8:8 format");
}
int[] innards = new int[2];
innards[1] = (int) (Long.parseLong(replicaId.substring(0,8), 16) & 0xffffffff);
innards[0] = (int) (Long.parseLong(replicaId.substring(8), 16) & 0xffffffff);
return innards;
}
private static class LRUStringLMBCSCache extends SizeLimitedLRUCache {
public LRUStringLMBCSCache(int maxSizeUnits) {
super(maxSizeUnits);
}
@Override
protected int computeSize(String key, Memory value) {
return key.length()*2 + (int) value.size();
}
}
}