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

com.hcl.domino.jna.richtext.JNARichtextWriter Maven / Gradle / Ivy

The newest version!
/*
 * ==========================================================================
 * Copyright (C) 2019-2022 HCL America, Inc. ( http://www.hcl.com/ )
 *                            All rights reserved.
 * ==========================================================================
 * Licensed under the  Apache License, Version 2.0  (the "License").  You may
 * not use this file except in compliance with the License.  You may obtain a
 * copy of the License at .
 *
 * Unless  required  by applicable  law or  agreed  to  in writing,  software
 * distributed under the License is distributed on an  "AS IS" BASIS, WITHOUT
 * WARRANTIES OR  CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the  specific language  governing permissions  and limitations
 * under the License.
 * ==========================================================================
 */
package com.hcl.domino.jna.richtext;

import static java.text.MessageFormat.format;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.lang.ref.ReferenceQueue;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

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.hcl.domino.DominoException;
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.richtext.IDefaultRichTextWriter;
import com.hcl.domino.commons.richtext.DefaultRichTextList;
import com.hcl.domino.commons.richtext.RichTextUtil;
import com.hcl.domino.commons.structures.MemoryStructureUtil;
import com.hcl.domino.commons.util.NotesErrorUtils;
import com.hcl.domino.commons.util.StringUtil;
import com.hcl.domino.data.Document;
import com.hcl.domino.data.FontAttribute;
import com.hcl.domino.data.ItemDataType;
import com.hcl.domino.data.StandardColors;
import com.hcl.domino.data.StandardFonts;
import com.hcl.domino.exception.IncompatibleImplementationException;
import com.hcl.domino.exception.ObjectDisposedException;
import com.hcl.domino.jna.BaseJNAAPIObject;
import com.hcl.domino.jna.data.JNADocument;
import com.hcl.domino.jna.data.JNAItem;
import com.hcl.domino.jna.internal.NotesStringUtils;
import com.hcl.domino.jna.internal.capi.NotesCAPI;
import com.hcl.domino.jna.internal.gc.allocations.JNADocumentAllocations;
import com.hcl.domino.jna.internal.gc.allocations.JNARichtextWriterAllocations;
import com.hcl.domino.jna.internal.gc.allocations.JNARichtextWriterAllocations.CloseResult;
import com.hcl.domino.jna.internal.gc.allocations.JNARichtextWriterAllocations.CloseResultType;
import com.hcl.domino.jna.internal.gc.handles.DHANDLE;
import com.hcl.domino.jna.internal.gc.handles.LockUtil;
import com.hcl.domino.jna.internal.richtext.JNACDFileRichTextNavigator;
import com.hcl.domino.jna.internal.richtext.JNACompoundTextStandaloneBuffer;
import com.hcl.domino.jna.internal.richtext.JNACompoundTextStandaloneBuffer.FileInfo;
import com.hcl.domino.jna.internal.structs.NotesCompoundStyleStruct;
import com.hcl.domino.jna.internal.structs.NotesTimeDateStruct;
import com.hcl.domino.jna.internal.structs.NotesUniversalNoteIdStruct;
import com.hcl.domino.misc.DominoEnumUtil;
import com.hcl.domino.misc.NotesConstants;
import com.hcl.domino.richtext.HotspotType;
import com.hcl.domino.richtext.RichTextRecordList;
import com.hcl.domino.richtext.RichTextWriter;
import com.hcl.domino.richtext.TextStyle;
import com.hcl.domino.richtext.records.CDBegin;
import com.hcl.domino.richtext.records.CDCaption;
import com.hcl.domino.richtext.records.CDEnd;
import com.hcl.domino.richtext.records.CDHotspotBegin;
import com.hcl.domino.richtext.records.CDHotspotEnd;
import com.hcl.domino.richtext.records.CDImageHeader;
import com.hcl.domino.richtext.records.RecordType;
import com.hcl.domino.richtext.records.RichTextRecord;
import com.hcl.domino.richtext.structures.ColorValue;
import com.hcl.domino.richtext.structures.FontStyle;
import com.sun.jna.Memory;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;

/**
 * Provides convenience methods to produce richtext content
 * 
 * @author Karsten Lehmann
 */
public class JNARichtextWriter extends BaseJNAAPIObject implements IDefaultRichTextWriter {
	private static final Logger log = Logger.getLogger(JNARichtextWriter.class.getPackage().getName());
	
	private JNADocument m_parentDoc;
	private String m_itemName;
	private boolean m_hasData;
	private Map m_definedStyleId;

	/**
	 * Creates a new richtext writer to produce a richtext item in a document
	 * 
	 * @param parentDoc parent document
	 * @param itemName richtext item name
	 */
	public JNARichtextWriter(JNADocument parentDoc, String itemName) {
		super(parentDoc);
		m_parentDoc = parentDoc;
		m_itemName = itemName;
		
		JNADocumentAllocations docAllocations = (JNADocumentAllocations) parentDoc.getAdapter(APIObjectAllocations.class);
		
		Memory itemNameMem = NotesStringUtils.toLMBCS(itemName, true);
		
		LockUtil.lockHandle(docAllocations.getNoteHandle(), (hNoteByVal) -> {
			DHANDLE.ByReference rethCompound = DHANDLE.newInstanceByReference();
			
			short result = NotesCAPI.get().CompoundTextCreate(hNoteByVal, itemNameMem, rethCompound);
			NotesErrorUtils.checkResult(result);
			
			JNARichtextWriterAllocations writerAllocations = getAllocations();
			writerAllocations.setCompoundTextHandle(rethCompound, false);
			
			return 0;
		});
		m_definedStyleId = Collections.synchronizedMap(new HashMap());
		
		setInitialized();
	}

	/**
	 * Creates standalone richtext
	 * 
	 * @param client parent Domino client
	 */
	public JNARichtextWriter(IGCDominoClient client) {
		super(client);
		
		DHANDLE.ByReference rethCompound = DHANDLE.newInstanceByReference();
		
		short result = NotesCAPI.get().CompoundTextCreate(null, null, rethCompound);
		NotesErrorUtils.checkResult(result);
		
		JNARichtextWriterAllocations writerAllocations = getAllocations();
		writerAllocations.setCompoundTextHandle(rethCompound, true);
		
		m_definedStyleId = Collections.synchronizedMap(new HashMap());
		
		setInitialized();
	}
	
	@Override
	public boolean isEmpty() {
		return !m_hasData;
	}
	
	@Override
	public Document getParentDocument() {
		return m_parentDoc;
	}
	
	@Override
	public String getItemName() {
		return m_itemName;
	}

	@SuppressWarnings("rawtypes")
	@Override
	protected JNARichtextWriterAllocations createAllocations(IGCDominoClient parentDominoClient,
			APIObjectAllocations parentAllocations, ReferenceQueue queue) {
		
		return new JNARichtextWriterAllocations(parentDominoClient, parentAllocations, this, queue);
	}
	
	@Override
	public TextStyle createTextStyle(String styleName) {
		return new JNATextStyle(styleName);
	}
	
	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) {
		FontStyle fontIdStruct = MemoryStructureUtil.newStructure(FontStyle.class, 0);
		fontIdStruct.setStandardFont(DominoEnumUtil.valueOf(StandardFonts.class, face).orElse(StandardFonts.SWISS));
		fontIdStruct.setAttributes(DominoEnumUtil.valuesOf(FontAttribute.class, attrib));
		fontIdStruct.setColor(DominoEnumUtil.valueOf(StandardColors.class, color).orElse(StandardColors.Black));
		fontIdStruct.setPointSize(pointSize);

		int fontId = fontIdStruct.getData().getInt(0);
		return fontId;
	}

	/**
	 * 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) {
			checkDisposed();

			IntByReference retStyleId = new IntByReference();

			NotesCompoundStyleStruct styleStruct = style.getAdapter(NotesCompoundStyleStruct.class);
			if (styleStruct==null) {
				throw new DominoException("Unable to get style struct from TextStyle");
			}

			Memory styleNameMem = NotesStringUtils.toLMBCS(style.getName(), true);

			JNARichtextWriterAllocations allocations = getAllocations();
			short result = LockUtil.lockHandle(allocations.getCompoundTextHandle(), (hCompoundTextByVal) -> {
				return NotesCAPI.get().CompoundTextDefineStyle(hCompoundTextByVal, styleNameMem, styleStruct, retStyleId);
			});
			NotesErrorUtils.checkResult(result);
			
			styleId = retStyleId.getValue();

			m_definedStyleId.put(styleHash, styleId);
			m_hasData=true;
		}
		return styleId;
	}
	
	@Override
	public RichTextWriter addText(String txt, TextStyle textStyle, FontStyle fontStyle, boolean createParagraphOnLinebreak) {
		checkDisposed();
		
		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 = fontStyle.getData().getInt();
		}
		
		int dwStyleID = textStyle==null ? NotesConstants.STYLE_ID_SAMEASPREV : getStyleId(textStyle);

		Pointer nlsInfoPtr = NotesCAPI.get().OSGetLMBCSCLS();
		short result;
		int dwFlags = NotesConstants.COMP_PRESERVE_LINES | NotesConstants.COMP_PARA_BLANK_LINE;
		if (createParagraphOnLinebreak) {
			dwFlags = dwFlags | NotesConstants.COMP_PARA_LINE;
		}
		final int fDWFlags = dwFlags;
		
		JNARichtextWriterAllocations allocations = getAllocations();
		
		
		result = LockUtil.lockHandle(allocations.getCompoundTextHandle(), (hCompoundTextByVal) -> {
			return  NotesCAPI.get().CompoundTextAddTextExt(hCompoundTextByVal, dwStyleID, fontId, txtMem,
					txtMem==null ? 0 : (int) txtMem.size(),
					lineDelimMem, fDWFlags, nlsInfoPtr);
		});
		NotesErrorUtils.checkResult(result);
		
		m_hasData=true;
		
		return this;
	}

	@Override
	public RichTextWriter addImage(InputStream imageStream, int resizeToWidth, int resizeToHeight) {
		Path tmpFile = null;
		try {
			try {
				tmpFile = Files.createTempFile("jnx_image_", ".tmp"); //$NON-NLS-1$ //$NON-NLS-2$
				Files.copy(imageStream, tmpFile, StandardCopyOption.REPLACE_EXISTING);
			}
			catch (IOException e) {
				throw new DominoException("Could not write image data to temp file", e);
			}
			
			return addImage(tmpFile, resizeToWidth, resizeToHeight);
		}
		finally {
			if (tmpFile!=null) {
				try {
					Files.deleteIfExists(tmpFile);
				} catch(IOException e) {
					if(log.isLoggable(Level.SEVERE)) {
						log.log(Level.SEVERE, "Encountered exception deleting temporary file", e);
					}
				}
			}
		}
	}
	
	@Override
	public RichTextWriter addImage(Path imagePath, int resizeToWidth, int resizeToHeight) {
		checkDisposed();
		
		FileType fileType;
		try (InputStream fIn = Files.newInputStream(imagePath); BufferedInputStream bufIn = new BufferedInputStream(fIn)) {
			fileType = FileTypeDetector.detectFileType(bufIn);
		} catch (IOException e1) {
			throw new DominoException(format("Error reading filetype of image with path {0}", imagePath), e1);
		}
		
		if (fileType == FileType.Unknown) {
			throw new DominoException("Unable to detect filetype of image");
		}
		
		boolean isSupported = false;
		if (fileType == FileType.Gif || fileType == FileType.Jpeg || fileType == FileType.Bmp || fileType == FileType.Png) {
			isSupported = true;
		}
		
		if (!isSupported) {
			throw new DominoException(format("Unsupported filetype {0}. Only GIF, PNG, JPEG and BMP are supported", fileType));
		}
		
		int imgWidth = -1;
		int imgHeight = -1;

		Metadata metadata;
		try (InputStream fIn = Files.newInputStream(imagePath)) {
			metadata = ImageMetadataReader.readMetadata(fIn);
		} catch (ImageProcessingException e) {
			throw new DominoException("Unable to read image metadata", e);
		} catch (NoSuchFileException e1) {
			throw new DominoException(format("Image with path {0} could not be found", imagePath), e1);
		} catch (IOException e1) {
			throw new DominoException(format("Error reading metadata of image with path {0}", imagePath), e1);
		}
		
		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 DominoException("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 DominoException("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 DominoException("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 DominoException("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");
		}
		
		CDImageHeader.ImageType imageType;
		switch (fileType) {
		case Gif:
			imageType = CDImageHeader.ImageType.GIF;
			break;
		case Jpeg:
			imageType= CDImageHeader.ImageType.JPEG;
			break;
		case Png:
			imageType = CDImageHeader.ImageType.PNG;
			break;
		case Bmp:
			imageType = CDImageHeader.ImageType.BMP;
			break;
		default:
			throw new IllegalArgumentException(format("Unknown image type: {0}", fileType));
		}

		int fileSize;
		try {
			fileSize = (int) Files.size(imagePath);
		} catch(IOException e) {
			throw new UncheckedIOException(e);
		}
		
		try(InputStream is = Files.newInputStream(imagePath)) {
			RichTextUtil.writeImageRecords(this, is, fileSize, imgWidth, imgHeight, resizeToWidth, resizeToHeight, imageType);
		} catch (NoSuchFileException e1) {
			throw new DominoException(format("Image with path {0} could not be found", imagePath), e1);
		} catch(IOException e) {
			throw new DominoException(format("Image with path {0} could not be read", imagePath), e);
		}
		
		m_hasData=true;
		
		return this;
	}

	@Override
	public RichTextWriter addDocLink(String dbReplicaId, String viewUnid, String docUNID, String comment) {
		checkDisposed();

		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(docUNID)) {
					NotesUniversalNoteIdStruct noteUNIDStruct = NotesUniversalNoteIdStruct.fromString(docUNID);
					noteUNIDStructByVal.File = noteUNIDStruct.File;
					noteUNIDStructByVal.Note = noteUNIDStruct.Note;
				}
			}
			
			//prevent NSD when passing a null value for the comment
			Memory commentMem = NotesStringUtils.toLMBCS(comment==null ? "" : comment, true);
			
			short result = LockUtil.lockHandle(getAllocations().getCompoundTextHandle(), (hCompoundTextByVal) -> {
				return NotesCAPI.get().CompoundTextAddDocLink(hCompoundTextByVal, dbReplicaIdStructByVal, viewUNIDStructByVal, noteUNIDStructByVal, commentMem, 0);
			});
			NotesErrorUtils.checkResult(result);
			
			m_hasData=true;
		}
		
		return this;
	}
	
	@Override
	public RichTextWriter addAttachmentIcon(String attachmentProgrammaticName, String filenameToDisplay, String captionText,
			FontStyle captionStyle,
			CaptionPosition captionPos, int captionColorRed, int captionColorGreen, int captionColorBlue,
			int resizeToWidth, int resizeToHeight, Path imagePath, InputStream imageData) throws IOException {

		checkDisposed();

		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");
		}
		
		addRichTextRecord(CDBegin.class, begin -> {
			begin.setSignature(RecordType.V4HOTSPOTBEGIN.getConstant());
		});
		m_hasData=true;

		addRichTextRecord(CDHotspotBegin.class, hotspotBegin -> {
		    hotspotBegin.getHeader().setSignature(RecordType.HOTSPOTBEGIN.getConstant());
			hotspotBegin.setHotspotType(HotspotType.FILE);
			hotspotBegin.setFlags(EnumSet.of(CDHotspotBegin.Flag.NOBORDER));
			
			hotspotBegin.setFileNames(attachmentProgrammaticName, filenameToDisplay);
		});
		
		if (imageData!=null) {
			addImage(imageData, resizeToWidth, resizeToHeight);
		}
		else {
			addImage(imagePath, resizeToWidth, resizeToHeight);
		}
		
		addRichTextRecord(CDCaption.class, caption -> {
			if (captionPos==CaptionPosition.BELOWCENTER) {
				caption.setPosition(CDCaption.Position.BELOW_CENTER);
			} else if (captionPos==CaptionPosition.MIDDLECENTER) {
				caption.setPosition(CDCaption.Position.MIDDLE_CENTER);
			}
			caption.getFontID().getData().putInt(0, captionStyle.getData().getInt());
			
			caption.getFontColor().setRed((short)captionColorRed);
			caption.getFontColor().setGreen((short)captionColorGreen);
			caption.getFontColor().setBlue((short)captionColorBlue);
			caption.getFontColor().setFlags(EnumSet.of(ColorValue.Flag.ISRGB));
			
			caption.setCaptionText(captionText);
		});
		
		addRichTextRecord(CDHotspotEnd.class, hotspotEnd -> {
		    hotspotEnd.getHeader().setSignature((byte)RecordType.HOTSPOTEND.getConstant());
		});
		addRichTextRecord(CDEnd.class, end -> {
			end.setVersion(0);
			end.setSignature(RecordType.V4HOTSPOTEND.getConstant());
		});
		
		return this;
	}
	
	@Override
	public RichTextWriter addRichText(Document doc, String itemName) {
		if (!(doc instanceof JNADocument)) {
			throw new IncompatibleImplementationException(doc, JNADocument.class);
		}
		
		JNADocument jnaDoc = (JNADocument) doc;
		
		if (jnaDoc.isDisposed()) {
			throw new ObjectDisposedException(jnaDoc);
		}

		checkDisposed();
		
		Memory itemNameMem = NotesStringUtils.toLMBCS(itemName, true);

		JNADocumentAllocations docAllocations = (JNADocumentAllocations) jnaDoc.getAdapter(APIObjectAllocations.class);
		
		short result = LockUtil.lockHandles(getAllocations().getCompoundTextHandle(), docAllocations.getNoteHandle(),
				(hCompoundTextByVal, hNoteByVal) -> {
			return NotesCAPI.get().CompoundTextAssimilateItem(hCompoundTextByVal, hNoteByVal, itemNameMem, 0);
		});
		
		NotesErrorUtils.checkResult(result);
		m_hasData=true;
		
		return this;
	}

	@Override
	public RichTextWriter addRichText(RichTextWriter rt) {
		if (!(rt instanceof JNARichtextWriter)) {
			throw new IncompatibleImplementationException(rt, JNARichtextWriter.class);
		}
		
		JNARichtextWriter jnaRT = (JNARichtextWriter) rt;
		if (jnaRT.isDisposed()) {
			throw new ObjectDisposedException(jnaRT);
		}

		checkDisposed();

		try {
			jnaRT.close();
		} catch (Exception e) {
			throw new DominoException("Error closing specified richtext writer", e);
		}
		
		Document parentDoc = jnaRT.getParentDocument();
		String itemName = jnaRT.getItemName();
		
		return addRichText(parentDoc, itemName);
	}

	@Override
	public RichTextWriter addRichTextRecord(RichTextRecord record) {
		checkDisposed();

		ByteBuffer recordData = record.getData();
		int len = record.getCDRecordLength();
		
		short result = LockUtil.lockHandle(getAllocations().getCompoundTextHandle(), (hCompoundTextByVal) -> {
			Pointer recordMem;
			try {
				recordMem = Native.getDirectBufferPointer(recordData);
			} catch(IllegalArgumentException e) {
				recordMem = new Memory(len);
				recordMem.write(0, recordData.array(), 0, len);
			}
			return NotesCAPI.get().CompoundTextAddCDRecords(hCompoundTextByVal, recordMem, len);
		});
		NotesErrorUtils.checkResult(result);
		m_hasData=true;
		
		return this;
	}

	@Override
	public void close() {
		if (isDisposed()) {
			return;
		}
		
		JNARichtextWriterAllocations allocations = getAllocations();
		if (allocations.isStandalone()) {
			allocations.closeStandaloneContext();
		}
		else {
			allocations.closeItemContext();
		}
	}
	
	public CloseResult getStandaloneRichtextCloseResult() {
		return getAllocations().getStandaloneContextCloseResult();
	}
	
	@Override
	public void discard() {
		checkDisposed();
		
		getAllocations().discard();
	}
	
	@Override
	public String toStringLocal() {
		return format("JNARichtextWriter [parentDoc={0}, itemname={1}]", (m_parentDoc!=null ? m_parentDoc.getUNID() : "null"), m_itemName); //$NON-NLS-1$ //$NON-NLS-2$
	}

	/**
	 * Closes this standalone richtext (so no more additions are allowed) and copies its content
	 * to a note. This method may be called multiple times if you need to copy the content
	 * to more than one document.
	 * 
	 * @param doc target document
	 * @param richTextItemName name of richtext item in target note, existing richtext items with this name will be removed
	 */
	public void closeAndCopyToDoc(final Document doc, String richTextItemName) {
		if (!getAllocations().isStandalone()) {
			throw new DominoException("This is no standalone richtext");
		}
		
		CloseResult result = getAllocations().closeStandaloneContext();
		if (result.getType()!=CloseResultType.Buffer && result.getType()!=CloseResultType.File) {
			//should not happen
			throw new IllegalStateException(format("Unexpected close type received from compound text: {0}", result.getType()));
		}
		
		//collect old composite items to prepare later deletion
		final LinkedList items = new LinkedList<>();
		
		doc.forEachItem(richTextItemName, (item, loop) -> {
			if (item.getType()==ItemDataType.TYPE_COMPOSITE && item instanceof JNAItem) {
				items.add((JNAItem)item);
			}
		});
		
		//build new compound text
		try (RichTextWriter rt = doc.createRichTextItem(richTextItemName)) {
			if (!(rt instanceof JNARichtextWriter)) {
				throw new IllegalArgumentException(format("Returned RichtextWriter is not a {0}", JNARichtextWriter.class.getName()));
			}
			try(JNARichtextWriter jnaRTWriter = (JNARichtextWriter) rt) {

				//and transfer the CD records from this compound text
				if (result.getType()==CloseResultType.Buffer) {
					final JNACompoundTextStandaloneBuffer buffer = result.getBuffer();
					FileInfo fileInfo;
					try {
						fileInfo = buffer.asFileOnDisk();
					} catch (IOException e1) {
						throw new DominoException(format("Could not extract compound text buffer to disk: {0}", buffer), e1);
					}
					jnaRTWriter.addCompoundTextFromFile(fileInfo.getFilePath());
				}
				else {
					//Domino created a temp file
					String filePath = result.getFilePath();
					jnaRTWriter.addCompoundTextFromFile(filePath);
				}
			}
		}

		//cleanup obsolete items read earlier
		for (JNAItem currOldItem : items) {
			currOldItem.remove();
		}
	}
	
	public RichTextRecordList closeAndGetRichTextNavigator() {
		CloseResult result = getAllocations().closeStandaloneContext();
		InputStream fIn;
		String filePath;
		long fileSize;
		
		if (result.getType()==CloseResultType.Buffer) {
			final JNACompoundTextStandaloneBuffer buffer = result.getBuffer();
			try {
				FileInfo fileInfo = buffer.asFileOnDisk();
				
				fIn = fileInfo.getStream();
				filePath = fileInfo.getFilePath();
				fileSize = fileInfo.getFileSize();
			} catch (IOException e) {
				throw new DominoException(0, "Error opening file stream for standalone compound text", e);
			}
		}
		else {
			//Domino created a temp file
			filePath = result.getFilePath();
			Path tmpFile = Paths.get(filePath);
			try {
				fileSize = Files.size(tmpFile);
				fIn = Files.newInputStream(tmpFile);
			} catch (IOException e) {
				throw new DominoException("Error opening file stream for standalone compound text", e);
			}
		}
		try {
			JNACDFileRichTextNavigator nav = new JNACDFileRichTextNavigator(getParentDominoClient(), filePath, fIn, fileSize, true);
			return new DefaultRichTextList(nav, null);
		} catch (IOException e) {
			throw new DominoException("Error creating richtext navigator for file stream", e);
		}
	}
	
	/** 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 void addCompoundTextFromFile(final String filePath) { checkDisposed(); if (getAllocations().isClosed()) { throw new DominoException("CompoundText already closed"); } long fileSize; try { fileSize = AccessController.doPrivileged((PrivilegedExceptionAction) () -> { Path file = Paths.get(filePath); if (!Files.exists(file)) { throw new NoSuchFileException(format("File does not exist: {0}", filePath)); } return Files.size(file); }); } catch (PrivilegedActionException e) { throw new DominoException(format("Error reading file size of file {0}", filePath), e); } if (fileSize==0) { //nothing to do return; } Memory filePathMem = NotesStringUtils.toLMBCS(filePath, true); short result = LockUtil.lockHandle(getAllocations().getCompoundTextHandle(), (hCompoundTextByVal) -> { return NotesCAPI.get().CompoundTextAssimilateFile(hCompoundTextByVal, filePathMem, 0); }); NotesErrorUtils.checkResult(result); m_hasData=true; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy