
com.mindoo.domino.jna.internal.CompoundTextWriter 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.internal;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.drew.imaging.FileType;
import com.drew.imaging.FileTypeDetector;
import com.drew.imaging.ImageMetadataReader;
import com.drew.imaging.ImageProcessingException;
import com.drew.metadata.Metadata;
import com.drew.metadata.MetadataException;
import com.drew.metadata.bmp.BmpHeaderDirectory;
import com.drew.metadata.gif.GifHeaderDirectory;
import com.drew.metadata.jpeg.JpegDirectory;
import com.drew.metadata.png.PngDirectory;
import com.mindoo.domino.jna.IAdaptable;
import com.mindoo.domino.jna.NotesAttachment;
import com.mindoo.domino.jna.NotesCollection;
import com.mindoo.domino.jna.NotesDatabase;
import com.mindoo.domino.jna.NotesNote;
import com.mindoo.domino.jna.constants.NoteClass;
import com.mindoo.domino.jna.errors.NotesError;
import com.mindoo.domino.jna.errors.NotesErrorUtils;
import com.mindoo.domino.jna.gc.IAllocatedMemory;
import com.mindoo.domino.jna.gc.IRecyclableNotesObject;
import com.mindoo.domino.jna.gc.NotesGC;
import com.mindoo.domino.jna.internal.structs.NotesTimeDateStruct;
import com.mindoo.domino.jna.internal.structs.NotesUniversalNoteIdStruct;
import com.mindoo.domino.jna.internal.structs.compoundtext.NotesCompoundStyleStruct;
import com.mindoo.domino.jna.internal.structs.compoundtext.NotesFontIDFieldsStruct;
import com.mindoo.domino.jna.internal.structs.viewformat.NotesColorValueStruct;
import com.mindoo.domino.jna.richtext.CaptionPosition;
import com.mindoo.domino.jna.richtext.FontStyle;
import com.mindoo.domino.jna.richtext.ICompoundText;
import com.mindoo.domino.jna.richtext.RichTextBuilder;
import com.mindoo.domino.jna.richtext.StandaloneRichText;
import com.mindoo.domino.jna.richtext.TextStyle;
import com.mindoo.domino.jna.utils.NotesStringUtils;
import com.mindoo.domino.jna.utils.PlatformUtils;
import com.mindoo.domino.jna.utils.StringUtil;
import com.sun.jna.Memory;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.LongByReference;
/**
* Internal helper class to write compound text with the high-level methods IBM is offering in their
* C API (e.g. to create doclinks, add text, render other notes) and with some additions like methods to
* add images or file hotspots.
* The class is used in {@link RichTextBuilder} and {@link StandaloneRichText} and should not be used
* directly, since it has some potentially dangerous methods like adding C records via direct pointers that
* might cause crashes when used in the wrong way.
*
* @author Karsten Lehmann
*/
public class CompoundTextWriter implements IRecyclableNotesObject, ICompoundText, IAdaptable {
private long m_handle64;
private int m_handle32;
private CloseResult m_closeResult;
private boolean m_isStandalone;
private boolean m_closed;
private boolean m_hasData;
private Map m_definedStyleId;
public CompoundTextWriter(long handle, boolean isStandalone) {
if (!PlatformUtils.is64Bit())
throw new IllegalStateException("Constructor is 64bit only");
m_handle64 = handle;
m_isStandalone = isStandalone;
m_definedStyleId = new HashMap();
}
public CompoundTextWriter(int handle, boolean isStandalone) {
if (PlatformUtils.is64Bit())
throw new IllegalStateException("Constructor is 32bit only");
m_handle32 = handle;
m_isStandalone = isStandalone;
m_definedStyleId = new HashMap();
}
@Override
public T getAdapter(Class clazz) {
if (clazz==CompoundTextWriter.class) {
return (T) this;
}
else {
return null;
}
}
public boolean isClosed() {
return m_closed;
}
public void checkHandle() {
if (PlatformUtils.is64Bit()) {
if (m_handle64==0)
throw new NotesError(0, "Compound text already recycled");
NotesGC.__b64_checkValidObjectHandle(CompoundTextWriter.class, m_handle64);
}
else {
if (m_handle32==0)
throw new NotesError(0, "Compound text already recycled");
NotesGC.__b32_checkValidObjectHandle(CompoundTextWriter.class, m_handle32);
}
}
private int getDefaultFontId() {
return getFontId(NotesConstants.FONT_FACE_SWISS, (byte) 0, (byte) 0, (byte) 10);
}
private int getFontId(byte face, byte attrib, byte color, byte pointSize) {
NotesFontIDFieldsStruct fontIdStruct = NotesFontIDFieldsStruct.newInstance();
fontIdStruct.Face = face;
fontIdStruct.Attrib = attrib;
fontIdStruct.Color = color;
fontIdStruct.PointSize = pointSize;
fontIdStruct.write();
int fontId = fontIdStruct.getPointer().getInt(0);
return fontId;
}
@Override
public CompoundTextWriter addDatabaseLink(NotesDatabase db, String comment) {
checkHandle();
if (isClosed())
throw new NotesError(0, "CompoundText already closed");
if (db.isRecycled())
throw new NotesError(0, "Provided database is already recycled");
String replicaId = db.getReplicaID();
addDocLink(replicaId, null, null, comment);
return this;
}
@Override
public CompoundTextWriter addCollectionLink(NotesCollection collection, String comment) {
checkHandle();
if (isClosed())
throw new NotesError(0, "CompoundText already closed");
if (collection.isRecycled())
throw new NotesError(0, "Provided collection is already recycled");
NotesDatabase parentDb = collection.getParent();
if (parentDb.isRecycled())
throw new NotesError(0, "Parent database is already recycled");
String replicaId = parentDb.getReplicaID();
addDocLink(replicaId, null, null, comment);
return this;
}
@Override
public CompoundTextWriter addDocLink(NotesNote note, String comment) {
checkHandle();
if (isClosed())
throw new NotesError(0, "CompoundText already closed");
if (note.isRecycled())
throw new NotesError(0, "Provided note is already recycled");
NotesDatabase parentDb = note.getParent();
if (parentDb.isRecycled())
throw new NotesError(0, "Parent database is already recycled");
String replicaId = parentDb.getReplicaID();
String noteUnid = note.getUNID();
NotesCollection defaultCollection = parentDb.openDefaultCollection();
if (defaultCollection==null) {
throw new NotesError(0, "Unable to locate the required default view in database "+parentDb.getServer()+"!!"+parentDb.getRelativeFilePath());
}
addDocLink(replicaId, defaultCollection.getUNID(), noteUnid, comment);
return this;
}
@Override
public CompoundTextWriter addDocLink(String dbReplicaId, String viewUnid, String noteUNID, String comment) {
checkHandle();
if (isClosed())
throw new NotesError(0, "CompoundText already closed");
if (!StringUtil.isEmpty(dbReplicaId)) {
int[] dbReplicaIdInnards = NotesStringUtils.replicaIdToInnards(dbReplicaId);
NotesTimeDateStruct.ByValue dbReplicaIdStructByVal = NotesTimeDateStruct.ByValue.newInstance();
dbReplicaIdStructByVal.Innards[0] = dbReplicaIdInnards[0];
dbReplicaIdStructByVal.Innards[1] = dbReplicaIdInnards[1];
NotesUniversalNoteIdStruct.ByValue viewUNIDStructByVal = NotesUniversalNoteIdStruct.ByValue.newInstance();
NotesUniversalNoteIdStruct.ByValue noteUNIDStructByVal = NotesUniversalNoteIdStruct.ByValue.newInstance();
if (!StringUtil.isEmpty(viewUnid)) {
NotesUniversalNoteIdStruct viewUNIDStruct = NotesUniversalNoteIdStruct.fromString(viewUnid);
viewUNIDStructByVal.File = viewUNIDStruct.File;
viewUNIDStructByVal.Note = viewUNIDStruct.Note;
if (!StringUtil.isEmpty(noteUNID)) {
NotesUniversalNoteIdStruct noteUNIDStruct = NotesUniversalNoteIdStruct.fromString(noteUNID);
noteUNIDStructByVal.File = noteUNIDStruct.File;
noteUNIDStructByVal.Note = noteUNIDStruct.Note;
}
}
Memory commentMem = NotesStringUtils.toLMBCS(comment, true);
short result;
if (PlatformUtils.is64Bit()) {
result = NotesNativeAPI64.get().CompoundTextAddDocLink(m_handle64, dbReplicaIdStructByVal, viewUNIDStructByVal, noteUNIDStructByVal, commentMem, 0);
}
else {
result = NotesNativeAPI32.get().CompoundTextAddDocLink(m_handle32, dbReplicaIdStructByVal, viewUNIDStructByVal, noteUNIDStructByVal, commentMem, 0);
}
NotesErrorUtils.checkResult(result);
m_hasData=true;
}
return this;
}
@Override
public CompoundTextWriter addRenderedNote(NotesNote note) {
String formName = note.getItemValueString("Form");
addRenderedNote(note, formName);
return this;
}
@Override
public CompoundTextWriter addRenderedNote(NotesNote note, String form) {
if (note.isRecycled())
throw new NotesError(0, "Provided note is already recycled");
checkHandle();
if (isClosed())
throw new NotesError(0, "CompoundText already closed");
NotesDatabase db = note.getParent();
NotesNote formNote = StringUtil.isEmpty(form) ? null : db.findDesignNote(form, NoteClass.FORM);
short result;
if (PlatformUtils.is64Bit()) {
result = NotesNativeAPI64.get().CompoundTextAddRenderedNote(m_handle64, note.getHandle64(), formNote==null ? 0 : formNote.getHandle64(), 0);
}
else {
result = NotesNativeAPI32.get().CompoundTextAddRenderedNote(m_handle32, note.getHandle32(), formNote==null ? 0 : formNote.getHandle32(), 0);
}
NotesErrorUtils.checkResult(result);
m_hasData=true;
return this;
}
@Override
public CompoundTextWriter addText(String txt) {
addText(txt, (TextStyle) null, (FontStyle) null);
return this;
}
/**
* Converts the {@link TextStyle} to a style id, reusing already defined
* styles if all attributes are matching
*
* @param style text style
* @return style id
*/
private int getStyleId(TextStyle style) {
int styleHash = style.hashCode();
Integer styleId = m_definedStyleId.get(styleHash);
if (styleId==null) {
checkHandle();
if (isClosed())
throw new NotesError(0, "CompoundText already closed");
IntByReference retStyleId = new IntByReference();
NotesCompoundStyleStruct styleStruct = style.getAdapter(NotesCompoundStyleStruct.class);
if (styleStruct==null)
throw new NotesError(0, "Unable to get style struct from TextStyle");
Memory styleNameMem = NotesStringUtils.toLMBCS(style.getName(), true);
short result;
if (PlatformUtils.is64Bit()) {
result = NotesNativeAPI64.get().CompoundTextDefineStyle(m_handle64, styleNameMem, styleStruct, retStyleId);
}
else {
result = NotesNativeAPI32.get().CompoundTextDefineStyle(m_handle32, styleNameMem, styleStruct, retStyleId);
}
NotesErrorUtils.checkResult(result);
styleId = retStyleId.getValue();
m_definedStyleId.put(styleHash, styleId);
m_hasData=true;
}
return styleId;
}
@Override
public CompoundTextWriter addText(String txt, TextStyle textStyle, FontStyle fontStyle) {
addText(txt, textStyle, fontStyle, true);
return this;
}
@Override
public CompoundTextWriter addText(String txt, TextStyle textStyle, FontStyle fontStyle, boolean createParagraphOnLinebreak) {
checkHandle();
if (isClosed())
throw new NotesError(0, "CompoundText already closed");
Memory txtMem = NotesStringUtils.toLMBCS(txt, false);
Memory lineDelimMem = new Memory(3);
lineDelimMem.setByte(0, (byte) '\r');
lineDelimMem.setByte(1, (byte) '\n');
lineDelimMem.setByte(2, (byte) 0);
int fontId;
if (fontStyle==null) {
fontId = getDefaultFontId();
}
else {
FontId fontIdObj = fontStyle.getAdapter(FontId.class);
if (fontIdObj==null)
throw new NotesError(0, "Unable to get FontId from FontStyle");
fontId = fontIdObj.getFontId();
}
int dwStyleID = textStyle==null ? NotesConstants.STYLE_ID_SAMEASPREV : getStyleId(textStyle);
Pointer nlsInfoPtr = NotesNativeAPI.get().OSGetLMBCSCLS();
short result;
int dwFlags = NotesConstants.COMP_PRESERVE_LINES | NotesConstants.COMP_PARA_BLANK_LINE;
if (createParagraphOnLinebreak) {
dwFlags = dwFlags | NotesConstants.COMP_PARA_LINE;
}
if (PlatformUtils.is64Bit()) {
result = NotesNativeAPI64.get().CompoundTextAddTextExt(m_handle64, dwStyleID, fontId, txtMem, txtMem==null ? 0 : (int) txtMem.size(),
lineDelimMem, dwFlags, nlsInfoPtr);
}
else {
result = NotesNativeAPI32.get().CompoundTextAddTextExt(m_handle32, dwStyleID, fontId, txtMem, txtMem==null ? 0 : (int) txtMem.size(),
lineDelimMem, dwFlags, nlsInfoPtr);
}
NotesErrorUtils.checkResult(result);
m_hasData=true;
return this;
}
@Override
public CompoundTextWriter addRichTextItem(NotesNote otherNote, String itemName) {
if (otherNote.isRecycled())
throw new NotesError(0, "Provided note is already recycled");
checkHandle();
if (isClosed())
throw new NotesError(0, "CompoundText already closed");
Memory itemNameMem = NotesStringUtils.toLMBCS(itemName, true);
short result;
if (PlatformUtils.is64Bit()) {
result = NotesNativeAPI64.get().CompoundTextAssimilateItem(m_handle64, otherNote.getHandle64(), itemNameMem, 0);
}
else {
result = NotesNativeAPI32.get().CompoundTextAssimilateItem(m_handle32, otherNote.getHandle32(), itemNameMem, 0);
}
NotesErrorUtils.checkResult(result);
m_hasData=true;
return this;
}
public CompoundTextWriter addCDRecords(Memory cdRecordMem) {
addCDRecords(cdRecordMem, (int) cdRecordMem.size());
return this;
}
/**
* Method to add raw CD records to the compound text
*
* @param cdRecordPtr pointer to CD records
* @param recordLength the length of the buffer
*/
public CompoundTextWriter addCDRecords(Pointer cdRecordPtr, int recordLength) {
checkHandle();
if (isClosed())
throw new NotesError(0, "CompoundText already closed");
short result;
if (PlatformUtils.is64Bit()) {
result = NotesNativeAPI64.get().CompoundTextAddCDRecords(m_handle64, cdRecordPtr, recordLength);
}
else {
result = NotesNativeAPI32.get().CompoundTextAddCDRecords(m_handle32, cdRecordPtr, recordLength);
}
NotesErrorUtils.checkResult(result);
m_hasData=true;
return this;
}
@Override
public CompoundTextWriter addClosedStandaloneRichText(StandaloneRichText rt) {
CompoundTextWriter ctWriter = rt.getAdapter(CompoundTextWriter.class);
if (ctWriter==null)
throw new NotesError(0, "Could not get "+CompoundTextWriter.class.getSimpleName()+" from "+StandaloneRichText.class.getSimpleName());
addClosedStandaloneCompoundText(ctWriter);
return this;
}
/**
* Closes the specified standalone compound text and appends its content to this compound text
*
* @param ct standalone compound text
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public CompoundTextWriter addClosedStandaloneCompoundText(CompoundTextWriter ct) {
checkHandle();
if (isClosed())
throw new NotesError(0, "CompoundText already closed");
if (!ct.isStandalone())
throw new NotesError(0, "Provided compound text is not of type standalone");
short result;
CloseResult closeResult = ct.closeStandaloneContext();
if (closeResult.getType()==CloseResultType.Buffer) {
final CompoundTextStandaloneBuffer buffer = closeResult.getBuffer();
final int bufferLength = buffer.getSize();
try {
AccessController.doPrivileged(new PrivilegedExceptionAction() {
@Override
public Object run() throws Exception {
File tmpFile = File.createTempFile("comptext", ".tmp");
FileOutputStream fOut = null;
Pointer ptr;
if (PlatformUtils.is64Bit()) {
ptr = Mem64.OSLockObject(buffer.getHandle64());
}
else {
ptr = Mem32.OSLockObject(buffer.getHandle32());
}
try {
byte[] bufferData = ptr.getByteArray(0, bufferLength);
fOut = new FileOutputStream(tmpFile);
fOut.write(bufferData, 0, bufferData.length);
fOut.flush();
}
finally {
if (PlatformUtils.is64Bit()) {
Mem64.OSUnlockObject(buffer.getHandle64());
}
else {
Mem32.OSUnlockObject(buffer.getHandle32());
}
if (fOut!=null)
fOut.close();
}
try {
String filePath = tmpFile.getAbsolutePath();
Memory filePathMem = NotesStringUtils.toLMBCS(filePath, true);
short result;
if (PlatformUtils.is64Bit()) {
result = NotesNativeAPI64.get().CompoundTextAssimilateFile(m_handle64, filePathMem, 0);
}
else {
result = NotesNativeAPI32.get().CompoundTextAssimilateFile(m_handle32, filePathMem, 0);
}
NotesErrorUtils.checkResult(result);
}
finally {
if (!tmpFile.delete())
tmpFile.deleteOnExit();
}
return null;
}
});
} catch (PrivilegedActionException e) {
if (e.getCause() instanceof NotesError) {
throw (NotesError) e.getCause();
}
else
throw new NotesError(0, "Error appending standalone compound text content", e);
}
}
else {
String filePath = closeResult.getFilePath();
Memory filePathMem = NotesStringUtils.toLMBCS(filePath, true);
if (PlatformUtils.is64Bit()) {
result = NotesNativeAPI64.get().CompoundTextAssimilateFile(m_handle64, filePathMem, 0);
}
else {
result = NotesNativeAPI32.get().CompoundTextAssimilateFile(m_handle32, filePathMem, 0);
}
NotesErrorUtils.checkResult(result);
}
m_hasData = m_hasData | ct.hasData();
return this;
}
/**
* This routine assimilates the contents of a CD file (a file containing rich text) into a CompoundText context.
* The contents are assimilated in that PABIDs and styles are fixed up (renumbered/renamed as needed) before they are
* appended to the CompoundText context.
*
* A CD file is a file containing data in Domino rich text format. CompoundTextClose() creates a CD file when closing
* a stand alone context containing more than 64K bytes of rich text.
* A CD file consists of a datatype word (usually TYPE_COMPOSITE) followed by any number of CD records.
*
* The data type word is always in Host (machine-specific) format.
* The remainder of the data in a CD file is in Domino Canonical format.
*
* @param filePath path to file
*/
public CompoundTextWriter addCompoundTextFromFile(final String filePath) {
checkHandle();
if (isClosed())
throw new NotesError(0, "CompoundText already closed");
long fileSize;
try {
fileSize = AccessController.doPrivileged(new PrivilegedExceptionAction() {
@Override
public Long run() throws Exception {
File file = new File(filePath);
if (!file.exists())
throw new FileNotFoundException("File does not exist: "+filePath);
return file.length();
}
});
} catch (PrivilegedActionException e) {
throw new NotesError(0, "Error reading file size of file "+filePath, e);
}
if (fileSize==0) {
//nothing to do
return this;
}
Memory filePathMem = NotesStringUtils.toLMBCS(filePath, true);
short result;
if (PlatformUtils.is64Bit()) {
result = NotesNativeAPI64.get().CompoundTextAssimilateFile(m_handle64, filePathMem, 0);
}
else {
result = NotesNativeAPI32.get().CompoundTextAssimilateFile(m_handle32, filePathMem, 0);
}
NotesErrorUtils.checkResult(result);
m_hasData=true;
return this;
}
@Override
public CompoundTextWriter addImage(File f) throws IOException {
return addImage(-1, -1, f);
}
@Override
public CompoundTextWriter addImage(int resizeToWidth, int resizeToHeight, File f) throws IOException {
FileInputStream fIn = new FileInputStream(f);
try {
return addImage(resizeToWidth, resizeToHeight, (int) f.length(), fIn);
}
finally {
fIn.close();
}
}
@Override
public CompoundTextWriter addImage(int fileSize, InputStream imageData) throws IOException {
return addImage(-1, -1, fileSize, imageData);
}
@Override
public CompoundTextWriter addImage(int resizeToWidth, int resizeToHeight, int fileSize, InputStream imageData) throws IOException {
checkHandle();
if (isClosed())
throw new NotesError(0, "CompoundText already closed");
int bufSize = fileSize; // must be fileSize, otherwise PNG image width/height cannot be extracted
BufferedInputStream innerBufIn = new BufferedInputStream(imageData, bufSize);
innerBufIn.mark(bufSize);
BufferedInputStream outerBufIn = new BufferedInputStream(innerBufIn, bufSize);
FileType fileType = FileTypeDetector.detectFileType(outerBufIn);
if (fileType == FileType.Unknown)
throw new NotesError(0, "Unable to detect filetype of image");
innerBufIn.reset();
boolean isSupported = false;
if (fileType == FileType.Gif || fileType == FileType.Jpeg || fileType == FileType.Bmp || fileType == FileType.Png) {
isSupported = true;
}
if (!isSupported)
throw new NotesError(0, "Unsupported filetype "+fileType+". Only GIF, PNG, JPEG and BMP are supported");
int imgWidth = -1;
int imgHeight = -1;
outerBufIn.mark(bufSize);
Metadata metadata;
try {
metadata = ImageMetadataReader.readMetadata(outerBufIn);
} catch (ImageProcessingException e) {
throw new NotesError(0, "Unable to read image metadata", e);
}
innerBufIn.reset();
switch (fileType) {
case Gif:
Collection gifHeaderDirs = metadata.getDirectoriesOfType(GifHeaderDirectory.class);
if (gifHeaderDirs!=null && !gifHeaderDirs.isEmpty()) {
GifHeaderDirectory header = gifHeaderDirs.iterator().next();
try {
imgWidth = header.getInt(GifHeaderDirectory.TAG_IMAGE_WIDTH);
imgHeight = header.getInt(GifHeaderDirectory.TAG_IMAGE_HEIGHT);
} catch (MetadataException e) {
throw new NotesError(0, "Error reading GIF image size", e);
}
}
break;
case Jpeg:
Collection jpegHeaderDirs = metadata.getDirectoriesOfType(JpegDirectory.class);
if (jpegHeaderDirs!=null && !jpegHeaderDirs.isEmpty()) {
JpegDirectory jpegDir = jpegHeaderDirs.iterator().next();
try {
imgWidth = jpegDir.getImageWidth();
imgHeight = jpegDir.getImageHeight();
} catch (MetadataException e) {
throw new NotesError(0, "Error reading JPEG image size", e);
}
}
break;
case Bmp:
Collection bmpHeaderDirs = metadata.getDirectoriesOfType(BmpHeaderDirectory.class);
if (bmpHeaderDirs!=null && !bmpHeaderDirs.isEmpty()) {
BmpHeaderDirectory bmpHeader = bmpHeaderDirs.iterator().next();
try {
imgWidth = bmpHeader.getInt(BmpHeaderDirectory.TAG_IMAGE_WIDTH);
imgHeight = bmpHeader.getInt(BmpHeaderDirectory.TAG_IMAGE_HEIGHT);
} catch (MetadataException e) {
throw new NotesError(0, "Error reading BMP image size", e);
}
}
break;
case Png:
Collection pngHeaderDirs = metadata.getDirectoriesOfType(PngDirectory.class);
if (pngHeaderDirs!=null && !pngHeaderDirs.isEmpty()) {
PngDirectory pngHeader = pngHeaderDirs.iterator().next();
try {
imgWidth = pngHeader.getInt(PngDirectory.TAG_IMAGE_WIDTH);
imgHeight = pngHeader.getInt(PngDirectory.TAG_IMAGE_HEIGHT);
} catch (MetadataException e) {
throw new NotesError(0, "Error reading BMP image size", e);
}
}
break;
default:
//
}
if (imgWidth<=0 || imgHeight<=0) {
throw new IllegalArgumentException("Width/Height cannot be extracted from the image data");
}
short result;
// write graphic header:
// typedef struct {
// LSIG Header; /* Signature and Length */
// RECTSIZE DestSize; /* Destination Display size in TWIPS
// (1/1440 inch) */
// RECTSIZE CropSize; /* Reserved */
// CROPRECT CropOffset; /* Reserved */
// WORD fResize; /* True if user resized object */
// BYTE Version; /* CDGRAPHIC_VERSIONxxx */
// BYTE bFlags; /* Ignored before CDGRAPHIC_VERSION3 */
// WORD wReserved;
// } CDGRAPHIC;
Memory graphicMem = new Memory(
6 + //LSIG
2 + 2 + // RECTSIZE
2 + 2 + // RECTSIZE
2 + 2 + 2 + 2 + // CROPRECT
2 + // fResize
1 + // Verson
1 + // Flags
2 // Reserved
);
graphicMem.clear();
graphicMem.setShort(0, NotesConstants.SIG_CD_GRAPHIC);
graphicMem.share(2).setInt(0, (int) graphicMem.size());
boolean isResized = resizeToWidth!=-1 && resizeToHeight!=-1;
// DestSize : RECTSIZE (Word/Word)
if (isResized) {
graphicMem.share(6).setShort(0, (short) (resizeToWidth & 0xffff));
graphicMem.share(6 + 2).setShort(0, (short) (resizeToHeight & 0xffff));
}
else {
graphicMem.share(6).setShort(0, (short) 0);
graphicMem.share(6 + 2).setShort(0, (short) 0);
}
// CropSize : RECTSIZE (Word/Word)
graphicMem.share(6 + 4).setShort(0, (short) 0);
graphicMem.share(6 + 6).setShort(0, (short) 0);
// CropOffset : CROPRECT
graphicMem.share(6 + 8).setShort(0, (short) 0);
graphicMem.share(6 + 10).setShort(0, (short) 0);
graphicMem.share(6 + 12).setShort(0, (short) 0);
graphicMem.share(6 + 14).setShort(0, (short) 0);
// fResize : WORD
if (isResized) {
graphicMem.share(6 + 16).setShort(0, (short) 1);
}
else {
graphicMem.share(6 + 16).setShort(0, (short) 0);
}
//Version: BYTE
graphicMem.share(6 + 18).setByte(0, NotesConstants.CDGRAPHIC_VERSION3);
//Flags:
graphicMem.share(6 + 19).setByte(0, (byte) NotesConstants.CDGRAPHIC_FLAG_DESTSIZE_IS_PIXELS);
//Reserved:
graphicMem.share(6 + 20).setShort(0, (short) 0);
if (PlatformUtils.is64Bit()) {
result = NotesNativeAPI64.get().CompoundTextAddCDRecords(m_handle64, graphicMem, (int) graphicMem.size());
NotesErrorUtils.checkResult(result);
}
else {
result = NotesNativeAPI32.get().CompoundTextAddCDRecords(m_handle32, graphicMem, (int) graphicMem.size());
NotesErrorUtils.checkResult(result);
}
m_hasData=true;
//write image header
// typedef struct {
// LSIG Header; /* Signature and Length */
// WORD ImageType; /* Type of image (e.g., GIF, JPEG) */
// WORD Width; /* Width of the image (in pixels) */
// WORD Height; /* Height of the image (in pixels) */
// DWORD ImageDataSize; /* Size (in bytes) of the image data */
// DWORD SegCount; /* Number of CDIMAGESEGMENT records
// expected to follow */
// DWORD Flags; /* Flags (currently unused) */
// DWORD Reserved; /* Reserved for future use */
// } CDIMAGEHEADER;
Memory imageHeaderMem = new Memory(
2+4 + //LSIG
2 + //ImageType = 0x0003
2 + //Width = 48
2 + //Height = 48
4 + //ImageDataSize = 0
4 + //SegCount = 0
4 + //Flags = 0
4 //Reserved
);
imageHeaderMem.clear();
imageHeaderMem.setShort(0, NotesConstants.SIG_CD_IMAGEHEADER);
imageHeaderMem.share(2).setInt(0, (int) imageHeaderMem.size());
short imageTypeShort;
switch (fileType) {
case Gif:
imageTypeShort = NotesConstants.CDIMAGETYPE_GIF;
break;
case Jpeg:
imageTypeShort = NotesConstants.CDIMAGETYPE_JPEG;
break;
case Png:
case Bmp:
//R9 introduced PNG rendering support, but for the type they use BMP
//and added a new (undocumented) CD record type right after CDIMAGEHEADER that
//probably contains the actual image type (0x0004 for PNG) and the image size as DWORD
imageTypeShort = NotesConstants.CDIMAGETYPE_BMP;
break;
default:
throw new IllegalArgumentException("Unknown image type: "+fileType);
}
final int MAX_SEGMENT_SIZE = 10240;
imageHeaderMem.share(6).setShort(0, imageTypeShort);
short imgWidthShort = (short) (imgWidth & 0xffff);
imageHeaderMem.share(8).setShort(0, imgWidthShort);
short imgHeightShort = (short) (imgHeight & 0xffff);
imageHeaderMem.share(10).setShort(0, imgHeightShort);
if (fileType == FileType.Png) {
//for PNG, set filesize 0x00000000, probably to prevent older clients from reading the data
imageHeaderMem.share(12).setInt(0, 0);
}
else {
imageHeaderMem.share(12).setInt(0, fileSize);
}
int fullSegments = (int) (fileSize / MAX_SEGMENT_SIZE);
int segments = fullSegments;
int dataBytesInLastSegment = fileSize - fullSegments * MAX_SEGMENT_SIZE;
if (dataBytesInLastSegment>0) {
segments++;
}
if (fileType == FileType.Png) {
//for PNG, set segments to 0x00000000, probably to prevent older clients from reading the data
imageHeaderMem.share(16).setInt(0, 0);
}
else {
imageHeaderMem.share(16).setInt(0, segments & 0xffff);
}
imageHeaderMem.share(20).setInt(0, 0); //flags
imageHeaderMem.share(24).setInt(0, 0); //reserved
if (PlatformUtils.is64Bit()) {
result = NotesNativeAPI64.get().CompoundTextAddCDRecords(m_handle64, imageHeaderMem, (int) imageHeaderMem.size());
NotesErrorUtils.checkResult(result);
}
else {
result = NotesNativeAPI32.get().CompoundTextAddCDRecords(m_handle32, imageHeaderMem, (int) imageHeaderMem.size());
NotesErrorUtils.checkResult(result);
}
if (fileType==FileType.Png) {
//for PNG we add an undocumented CD record type to define the image type,
//number of chunks and filesize
// typedef struct
// {
// BSIG Header; /* Signature and Length */
// WORD ImageType; /* Type of image (e.g., PNG, etc.) */
// DWORD ImageDataSize; /* Size (in bytes) of the image data */
// DWORD SegCount; /* Number of CDIMAGESEGMENT records expected to follow */
// DWORD Flags; /* Flags (currently unused) */
// DWORD Reserved; /* Reserved for future use */
// } CDIMAGEHEADER2;
Memory imageHeader2Mem = new Memory(20);
imageHeader2Mem.clear();
//BSIG Header with type and size
imageHeader2Mem.setByte(0, (byte) (NotesConstants.SIG_CD_IMAGEHEADER2 & 0xff));
imageHeader2Mem.share(1).setByte(0, (byte) (imageHeader2Mem.size() & 0xff));
imageHeader2Mem.share(2).setShort(0, NotesConstants.CDIMAGETYPE_PNG);
imageHeader2Mem.share(4).setInt(0, fileSize);
imageHeader2Mem.share(8).setShort(0, (short) (segments & 0xffff));
if (PlatformUtils.is64Bit()) {
result = NotesNativeAPI64.get().CompoundTextAddCDRecords(m_handle64, imageHeader2Mem, (int) imageHeader2Mem.size());
NotesErrorUtils.checkResult(result);
}
else {
result = NotesNativeAPI32.get().CompoundTextAddCDRecords(m_handle32, imageHeader2Mem, (int) imageHeader2Mem.size());
NotesErrorUtils.checkResult(result);
}
}
byte[] buf = new byte[MAX_SEGMENT_SIZE];
int len;
int bytesRead = 0;
for (int i=0; i0) {
bOut.write(buf,0, len);
}
ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray());
return addFileHotspot(attachmentProgrammaticName, filenameToDisplay, filenameToDisplay, new FontStyle(), CaptionPosition.BELOWCENTER, 0, 0, 0, -1, -1, bOut.size(), bIn);
} catch (IOException e) {
throw new IllegalStateException("Unexpected problems reading the default icon", e);
}
finally {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public CompoundTextWriter addFileHotspot(NotesAttachment attachment, String filenameToDisplay, String captionText, File image) throws IOException {
return addFileHotspot(attachment.getFileName(), filenameToDisplay, captionText, image);
}
@Override
public CompoundTextWriter addFileHotspot(String attachmentProgrammaticName, String filenameToDisplay, String captionText, File image) throws IOException {
FileInputStream fIn = new FileInputStream(image);
try {
return addFileHotspot(attachmentProgrammaticName, filenameToDisplay, captionText, new FontStyle(), CaptionPosition.BELOWCENTER,
0, 0, 0, -1, -1, (int) image.length(), fIn);
}
finally {
fIn.close();
}
}
@Override
public CompoundTextWriter addFileHotspot(NotesAttachment attachment, String filenameToDisplay, String captionText, FontStyle captionStyle,
CaptionPosition captionPos, int captionColorRed, int captionColorGreen, int captionColorBlue,
int resizeToWidth, int resizeToHeight, int fileSize, InputStream imageData) throws IOException {
return addFileHotspot(attachment.getFileName(), filenameToDisplay, captionText, captionStyle, captionPos,
captionColorRed, captionColorGreen, captionColorBlue, resizeToWidth, resizeToHeight, fileSize,
imageData);
}
@Override
public CompoundTextWriter addFileHotspot(String attachmentProgrammaticName, String filenameToDisplay, String captionText, FontStyle captionStyle,
CaptionPosition captionPos, int captionColorRed, int captionColorGreen, int captionColorBlue,
int resizeToWidth, int resizeToHeight, int fileSize, InputStream imageData) throws IOException {
checkHandle();
if (isClosed())
throw new NotesError(0, "CompoundText already closed");
if (captionColorRed<0 || captionColorRed>255)
throw new IllegalArgumentException("Red value of color can only be between 0 and 255");
if (captionColorGreen<0 || captionColorGreen>255)
throw new IllegalArgumentException("Green value of color can only be between 0 and 255");
if (captionColorBlue<0 || captionColorBlue>255)
throw new IllegalArgumentException("Blue value of color can only be between 0 and 255");
Memory beginMem = new Memory(
2 + //BSIG
2 + //Version
2 //Signature
);
beginMem.clear();
// Read record BEGIN (221) with 4 data bytes, cdrecord length: 6
// Data:
// [00 00 ad ff ] [.... ]
beginMem.setShort(0, NotesConstants.SIG_CD_BEGIN);
beginMem.share(1).setByte(0, (byte) (beginMem.size() & 0xff));
beginMem.share(2).setShort(0, (short) 0);
beginMem.share(4).setShort(0, (short) (NotesConstants.SIG_CD_V4HOTSPOTBEGIN & 0xffff));
short result;
if (PlatformUtils.is64Bit()) {
result = NotesNativeAPI64.get().CompoundTextAddCDRecords(m_handle64, beginMem, (int) beginMem.size());
NotesErrorUtils.checkResult(result);
}
else {
result = NotesNativeAPI32.get().CompoundTextAddCDRecords(m_handle32, beginMem, (int) beginMem.size());
NotesErrorUtils.checkResult(result);
}
m_hasData=true;
//
// Read record HOTSPOTBEGIN (-87) with 24 data bytes, cdrecord length: 28
// Data:
// [04 00 08 00 00 00 10 00] [........]
// [6d 61 69 6e 2e 6a 73 00] [main.js.]
// [6d 61 69 6e 2e 6a 73 00] [main.js.]
// typedef struct {
// WSIG Header; /* Signature and length of this record */
// WORD Type;
// DWORD Flags;
// WORD DataLength;
// /* Data follows... */
// /* if HOTSPOTREC_RUNFLAG_SIGNED, WORD SigLen then SigData follows. */
// } CDHOTSPOTBEGIN;
Memory uniqueFileNameAttachment = NotesStringUtils.toLMBCS(attachmentProgrammaticName, true);
Memory fileNameToDisplayMem = NotesStringUtils.toLMBCS(filenameToDisplay, true);
Memory hotspotBeginMem = new Memory(
2 + 2 + //WSIG
2 + //Type
4 + //Flags
2 + //DataLength
uniqueFileNameAttachment.size() +
fileNameToDisplayMem.size()
);
hotspotBeginMem.clear();
hotspotBeginMem.setShort(0, NotesConstants.SIG_CD_HOTSPOTBEGIN);
hotspotBeginMem.share(2).setShort(0, (short) (hotspotBeginMem.size() & 0xffff));
hotspotBeginMem.share(4).setShort(0, NotesConstants.HOTSPOTREC_TYPE_FILE);
int flags = NotesConstants.HOTSPOTREC_RUNFLAG_NOBORDER;
hotspotBeginMem.share(6).setInt(0, flags);
hotspotBeginMem.share(10).setShort(0, (short) ((uniqueFileNameAttachment.size() + fileNameToDisplayMem.size()) & 0xffff));
hotspotBeginMem.share(12).write(0, uniqueFileNameAttachment.getByteArray(0, (int) uniqueFileNameAttachment.size()), 0, (int) uniqueFileNameAttachment.size());
hotspotBeginMem.share(12 + uniqueFileNameAttachment.size()).write(0, fileNameToDisplayMem.getByteArray(0, (int) fileNameToDisplayMem.size()), 0, (int) fileNameToDisplayMem.size());
if (PlatformUtils.is64Bit()) {
result = NotesNativeAPI64.get().CompoundTextAddCDRecords(m_handle64, hotspotBeginMem, (int) hotspotBeginMem.size());
NotesErrorUtils.checkResult(result);
}
else {
result = NotesNativeAPI32.get().CompoundTextAddCDRecords(m_handle32, hotspotBeginMem, (int) hotspotBeginMem.size());
NotesErrorUtils.checkResult(result);
}
addImage(resizeToWidth, resizeToHeight, fileSize, imageData);
// typedef struct {
// WSIG Header; /* Tag and length */
// WORD wLength; /* Text length */
// BYTE Position; /* One of the position flags above */
// FONTID FontID; /* Font to use for the text */
// COLOR_VALUE FontColor; /* RGB font color info */
// BYTE Reserved[11]; /* Reserved for future use */
// /* The 8-bit text string follows... */
// } CDCAPTION;
Memory captionTextMem = NotesStringUtils.toLMBCS(captionText, false);
Memory captionMem = new Memory(
2 + 2 + //WSIG
2 + // Text Length
1 + // Position
4 + // FontID
6 + // FontColor
11 + //Reserved
captionTextMem.size()
);
captionMem.clear();
captionMem.setShort(0, NotesConstants.SIG_CD_CAPTION);
captionMem.share(2).setShort(0, (short) (captionMem.size() & 0xffff));
captionMem.share(4).setShort(0, (short) (captionTextMem.size() & 0xffff));
byte captionPosition;
if (captionPos==CaptionPosition.BELOWCENTER) {
captionPosition = NotesConstants.CAPTION_POSITION_BELOW_CENTER;
}
else if (captionPos==CaptionPosition.MIDDLECENTER) {
captionPosition = NotesConstants.CAPTION_POSITION_MIDDLE_CENTER;
}
else {
captionPosition = 0;
}
captionMem.share(6).setByte(0, captionPosition);
FontId fontIdObj = captionStyle.getAdapter(FontId.class);
if (fontIdObj==null)
throw new NotesError(0, "Unable to get FontId from FontStyle");
int fontId = fontIdObj.getFontId();
captionMem.share(7).setInt(0, fontId);
byte captionColorRedByte = (byte) (captionColorRed & 0xff);
byte captionColorGreenByte = (byte) (captionColorGreen & 0xff);
byte captionColorBlueByte = (byte) (captionColorBlue & 0xff);
NotesColorValueStruct colorStruct = NotesColorValueStruct.newInstance();
colorStruct.Component1 = captionColorRedByte;
colorStruct.Component2 = captionColorGreenByte;
colorStruct.Component3 = captionColorBlueByte;
colorStruct.Flags = NotesConstants.COLOR_VALUE_FLAGS_ISRGB;
colorStruct.write();
captionMem.share(11).write(0, colorStruct.getPointer().getByteArray(0, 6), 0, 6);
captionMem.share(17).write(0, new byte[11], 0, 11);
captionMem.share(28).write(0, captionTextMem.getByteArray(0, (int) captionTextMem.size()), 0, (int) captionTextMem.size());
if (PlatformUtils.is64Bit()) {
result = NotesNativeAPI64.get().CompoundTextAddCDRecords(m_handle64, captionMem, (int) captionMem.size());
NotesErrorUtils.checkResult(result);
}
else {
result = NotesNativeAPI32.get().CompoundTextAddCDRecords(m_handle32, captionMem, (int) captionMem.size());
NotesErrorUtils.checkResult(result);
}
//
// Read record HOTSPOTEND (170) with 0 data bytes, cdrecord length: 2
// Data:
Memory hotspotEndMem = new Memory(2);
hotspotEndMem.clear();
hotspotEndMem.setShort(0, NotesConstants.SIG_CD_HOTSPOTEND);
hotspotEndMem.share(1).setByte(0, (byte) (hotspotEndMem.size() & 0xff));
if (PlatformUtils.is64Bit()) {
result = NotesNativeAPI64.get().CompoundTextAddCDRecords(m_handle64, hotspotEndMem, (int) hotspotEndMem.size());
NotesErrorUtils.checkResult(result);
}
else {
result = NotesNativeAPI32.get().CompoundTextAddCDRecords(m_handle32, hotspotEndMem, (int) hotspotEndMem.size());
NotesErrorUtils.checkResult(result);
}
Memory endMem = new Memory(6);
endMem.clear();
endMem.setShort(0, NotesConstants.SIG_CD_END);
endMem.share(1).setByte(0, (byte) (endMem.size() & 0xff));
endMem.share(2).setShort(0, (short) 0);
endMem.share(4).setShort(0, (short) (NotesConstants.SIG_CD_V4HOTSPOTEND & 0xffff));
if (PlatformUtils.is64Bit()) {
result = NotesNativeAPI64.get().CompoundTextAddCDRecords(m_handle64, endMem, (int) endMem.size());
NotesErrorUtils.checkResult(result);
}
else {
result = NotesNativeAPI32.get().CompoundTextAddCDRecords(m_handle32, endMem, (int) endMem.size());
NotesErrorUtils.checkResult(result);
}
//
// Read record END (222) with 4 data bytes, cdrecord length: 6
// Data:
// [00 00 ae 00 ] [.... ]
return this;
}
public boolean hasData() {
return m_hasData;
}
public boolean isStandalone() {
return m_isStandalone;
}
/**
* This routine closes the build process. Use {@link NotesNote#update()}
* after {@link #closeItemContext()} to update and save the document.
*/
public void closeItemContext() {
if (isStandalone())
throw new UnsupportedOperationException("This is a standalone compound text");
checkHandle();
if (isClosed())
return;
short result;
if (PlatformUtils.is64Bit()) {
NotesGC.__objectBeeingBeRecycled(CompoundTextWriter.class, this);
result = NotesNativeAPI64.get().CompoundTextClose(m_handle64, null, null, null, (short) 0);
m_handle64 = 0;
m_closed = true;
}
else {
NotesGC.__objectBeeingBeRecycled(CompoundTextWriter.class, this);
result = NotesNativeAPI32.get().CompoundTextClose(m_handle32, null, null, null, (short) 0);
m_handle32 = 0;
m_closed = true;
}
NotesErrorUtils.checkResult(result);
}
/**
* This routine closes the standalone CompoundText. The result is either an in-memory buffer or
* a temporary file on disk, depending on the memory size of the CD records
*
* @return close result
*/
public CloseResult closeStandaloneContext() {
if (!isStandalone())
throw new UnsupportedOperationException("This is not a standalone compound text");
if (isClosed())
return m_closeResult;
checkHandle();
Memory returnFileMem = new Memory(NotesConstants.MAXPATH);
returnFileMem.clear();
short result;
if (PlatformUtils.is64Bit()) {
LongByReference rethBuffer = new LongByReference();
rethBuffer.setValue(0);
IntByReference retBufSize = new IntByReference();
retBufSize.setValue(0);
NotesGC.__objectBeeingBeRecycled(CompoundTextWriter.class, this);
result = NotesNativeAPI64.get().CompoundTextClose(m_handle64, rethBuffer, retBufSize, returnFileMem, (short) (NotesConstants.MAXPATH & 0xffff));
NotesErrorUtils.checkResult(result);
m_handle64 = 0;
m_closed = true;
long hBuffer = rethBuffer.getValue();
if (hBuffer!=0) {
//content was small enough to fit into an in-memory buffer
int bufSize = retBufSize.getValue();
CompoundTextStandaloneBuffer buf = new CompoundTextStandaloneBuffer(hBuffer, bufSize);
//register buffer instance to auto-gc it
NotesGC.__memoryAllocated(buf);
m_closeResult = new CloseResult();
m_closeResult.setType(CloseResultType.Buffer);
m_closeResult.setBuffer(buf);
return m_closeResult;
}
else {
//content had to be written to a temp file
String fileName = NotesStringUtils.fromLMBCS(returnFileMem, -1);
m_closeResult = new CloseResult();
m_closeResult.setType(CloseResultType.File);
m_closeResult.setFilePath(fileName);
return m_closeResult;
}
}
else {
IntByReference rethBuffer = new IntByReference();
rethBuffer.setValue(0);
IntByReference retBufSize = new IntByReference();
retBufSize.setValue(0);
NotesGC.__objectBeeingBeRecycled(CompoundTextWriter.class, this);
result = NotesNativeAPI32.get().CompoundTextClose(m_handle32, rethBuffer, retBufSize, returnFileMem, (short) (NotesConstants.MAXPATH & 0xffff));
NotesErrorUtils.checkResult(result);
m_handle32 = 0;
m_closed = true;
int hBuffer = rethBuffer.getValue();
if (hBuffer!=0) {
//content was small enough to fit into an in-memory buffer
int bufSize = retBufSize.getValue();
CompoundTextStandaloneBuffer buf = new CompoundTextStandaloneBuffer(hBuffer, bufSize);
//register buffer instance to auto-gc it
NotesGC.__memoryAllocated(buf);
m_closeResult = new CloseResult();
m_closeResult.setType(CloseResultType.Buffer);
m_closeResult.setBuffer(buf);
return m_closeResult;
}
else {
//content had to be written to a temp file
String fileName = NotesStringUtils.fromLMBCS(returnFileMem, -1);
m_closeResult = new CloseResult();
m_closeResult.setType(CloseResultType.File);
m_closeResult.setFilePath(fileName);
return m_closeResult;
}
}
}
public static enum CloseResultType {Buffer, File}
public static class CloseResult {
private CloseResultType m_type;
private CompoundTextStandaloneBuffer m_buffer;
private String m_filePath;
public CloseResultType getType() {
return m_type;
}
private void setType(CloseResultType type) {
this.m_type = type;
}
public CompoundTextStandaloneBuffer getBuffer() {
return m_buffer;
}
private void setBuffer(CompoundTextStandaloneBuffer buf) {
m_buffer = buf;
}
public String getFilePath() {
return m_filePath;
}
private void setFilePath(String fileName) {
this.m_filePath = fileName;
}
}
@Override
public void recycle() {
if (isRecycled())
return;
if (PlatformUtils.is64Bit()) {
if (m_handle64!=0) {
NotesNativeAPI64.get().CompoundTextDiscard(m_handle64);
NotesGC.__objectBeeingBeRecycled(CompoundTextWriter.class, this);
m_handle64=0;
}
}
else {
if (m_handle32!=0) {
NotesNativeAPI32.get().CompoundTextDiscard(m_handle32);
NotesGC.__objectBeeingBeRecycled(CompoundTextWriter.class, this);
m_handle32=0;
}
}
}
@Override
public boolean isRecycled() {
if (PlatformUtils.is64Bit()) {
return m_handle64==0;
}
else {
return m_handle32==0;
}
}
@Override
public boolean isNoRecycle() {
return false;
}
@Override
public int getHandle32() {
return m_handle32;
}
@Override
public long getHandle64() {
return m_handle64;
}
@Override
public String toString() {
if (isRecycled()) {
return "CompoundTextWriter [recycled]";
}
else {
return "CompoundTextWriter [handle="+(PlatformUtils.is64Bit() ? m_handle64 : m_handle32)+", standalone="+isStandalone()+", closed="+isClosed()+"]";
}
}
/**
* Container to store and track the in-memory buffer that Domino creates when a
* compound text is closed and its content is small enough to fit into a memory segment.
*
* @author Karsten Lehmann
*/
public static class CompoundTextStandaloneBuffer implements IAllocatedMemory {
private long m_handle64;
private int m_handle32;
private int m_size;
private List m_createdTempFileStreams;
private File m_file;
public CompoundTextStandaloneBuffer(long handle, int size) {
if (!PlatformUtils.is64Bit())
throw new NotesError(0, "Constructor is 64 bit only");
m_handle64 = handle;
m_size = size;
}
public CompoundTextStandaloneBuffer(int handle, int size) {
if (PlatformUtils.is64Bit())
throw new NotesError(0, "Constructor is 32 bit only");
m_handle32 = handle;
m_size = size;
}
public static class FileInfo {
private String m_filePath;
private long m_fileSize;
private FileInputStream m_fileIn;
public FileInfo(String filePath, long fileSize, FileInputStream fileIn) {
m_filePath = filePath;
m_fileSize = fileSize;
m_fileIn = fileIn;
}
public String getFilePath() {
return m_filePath;
}
public long getFileSize() {
return m_fileSize;
}
public FileInputStream getStream() {
return m_fileIn;
}
}
/**
* Writes the memory buffer content to a temporary file and returns a {@link FileInputStream}
* to read the data. The stream will be auto-closed and the file deleted when this buffer is freed.
*
* @return stream to temporary file on disk
* @throws FileNotFoundException in case of I/O errors
*/
public FileInfo asFileOnDisk() throws FileNotFoundException {
if (m_file==null || !m_file.exists()) {
try {
m_file = AccessController.doPrivileged(new PrivilegedExceptionAction() {
@Override
public File run() throws Exception {
File tmpFile = File.createTempFile("comptext", ".tmp");
FileOutputStream fOut = null;
Pointer ptr;
if (PlatformUtils.is64Bit()) {
ptr = Mem64.OSLockObject(m_handle64);
}
else {
ptr = Mem32.OSLockObject(m_handle32);
}
try {
byte[] bufferData = ptr.getByteArray(0, m_size);
fOut = new FileOutputStream(tmpFile);
fOut.write(bufferData, 0, bufferData.length);
fOut.flush();
}
finally {
if (PlatformUtils.is64Bit()) {
Mem64.OSUnlockObject(m_handle64);
}
else {
Mem32.OSUnlockObject(m_handle32);
}
if (fOut!=null)
fOut.close();
}
return tmpFile;
}
});
} catch (PrivilegedActionException e) {
if (e.getCause() instanceof NotesError) {
throw (NotesError) e.getCause();
}
else
throw new NotesError(0, "Could not write content of memory buffer to disk", e);
}
}
FileInputStream in = new FileInputStream(m_file);
//keep track of stream to auto-close when memory is freed
if (m_createdTempFileStreams==null)
m_createdTempFileStreams = new ArrayList();
m_createdTempFileStreams.add(in);
return new FileInfo(m_file.getAbsolutePath(), m_file.length(), in);
}
public int getSize() {
return m_size;
}
@Override
public void free() {
if (isFreed())
return;
if (PlatformUtils.is64Bit()) {
NotesGC.__memoryBeeingFreed(this);
short result = Mem64.OSMemFree(m_handle64);
NotesErrorUtils.checkResult(result);
m_handle64=0;
}
else {
NotesGC.__memoryBeeingFreed(this);
short result = Mem32.OSMemFree(m_handle32);
NotesErrorUtils.checkResult(result);
m_handle32=0;
}
if (m_createdTempFileStreams!=null && !m_createdTempFileStreams.isEmpty()) {
for (FileInputStream currIn : m_createdTempFileStreams) {
try {
currIn.close();
} catch (IOException e) {
//just write to stderr, go on and try to delete the file
e.printStackTrace();
}
}
}
if (m_file!=null && m_file.exists()) {
if (!m_file.delete())
m_file.deleteOnExit();
}
}
@Override
public boolean isFreed() {
if (PlatformUtils.is64Bit()) {
return m_handle64==0;
}
else {
return m_handle32==0;
}
}
@Override
public int getHandle32() {
return m_handle32;
}
@Override
public long getHandle64() {
return m_handle64;
}
@Override
public String toString() {
if (isFreed()) {
return "CompoundTextStandaloneBuffer [freed]";
}
else {
return "CompoundTextStandaloneBuffer [handle="+(PlatformUtils.is64Bit() ? m_handle64 : m_handle32)+", size="+getSize()+"]";
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy