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

com.mindoo.domino.jna.utils.NotesStringUtils Maven / Gradle / Ivy

There is a newer version: 0.9.53
Show newest version
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(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy