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

com.hcl.domino.jna.internal.richtext.JNACDFileRichTextNavigator 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.internal.richtext;

import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.ReferenceQueue;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.SeekableByteChannel;
import java.text.MessageFormat;
import java.util.Map;
import java.util.TreeMap;

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.RichTextUtil;
import com.hcl.domino.commons.richtext.RichtextNavigator;
import com.hcl.domino.data.Document;
import com.hcl.domino.jna.BaseJNAAPIObject;
import com.hcl.domino.jna.internal.DisposableMemory;
import com.hcl.domino.jna.internal.gc.allocations.JNACDFileRichTextNavigatorAllocations;
import com.hcl.domino.richtext.RichTextConstants;
import com.hcl.domino.richtext.RichTextWriter;
import com.hcl.domino.richtext.records.RichTextRecord;
import com.sun.jna.Memory;

/**
 * Implementation of {@link RichtextNavigator} that works with an on-disk CD record file
 * 
 * @author Karsten Lehmann
 */
public class JNACDFileRichTextNavigator extends BaseJNAAPIObject implements RichtextNavigator {
	private long m_position;
	private RichTextRecord m_currentCDRecord;
	private Map m_cdRecordSizeAtIndex;
	private Map m_cdRecordIndexAtFilePos;
	private int m_currentCDRecordIndex;
	private long m_lastElementPosition = -1;
	private int m_lastElementIndex = -1;
	
	@SuppressWarnings("rawtypes")
  public JNACDFileRichTextNavigator(IGCDominoClient client, String filePath, InputStream cdFileStream,
			long fileSize,
			boolean autoDelete) throws IOException {
		super(client);
		
		m_cdRecordSizeAtIndex = new TreeMap<>();
		m_cdRecordIndexAtFilePos = new TreeMap<>();
		gotoFirst();
		
		getAllocations().init(filePath, cdFileStream, fileSize, autoDelete);
		setInitialized();
	}

	@SuppressWarnings("rawtypes")
	@Override
	protected JNACDFileRichTextNavigatorAllocations createAllocations(IGCDominoClient parentDominoClient,
			APIObjectAllocations parentAllocations, ReferenceQueue queue) {

		return new JNACDFileRichTextNavigatorAllocations(parentDominoClient, parentAllocations, this, queue);
	}
	
	@Override
	public Document getParentDocument() {
		return null;
	}
	
	@Override
	public String getItemName() {
		return null;
	}

	/**
	 * Reads the CD record information at the current file position
	 * 
	 * @throws IOException
	 */
	private RichTextRecord readCurrentCDRecordUnchecked() {
		try {
			return readCurrentCDRecord();
		} catch (IOException e) {
			throw new DominoException(MessageFormat.format("Error reading CD record at position {0} of file {1}", m_position, getAllocations().getFilePath()), e);
		}
	}
	
	/**
	 * Reads the CD record information at the current file position
	 * 
	 * @throws IOException
	 */
	private RichTextRecord readCurrentCDRecord() throws IOException {
		checkDisposed();
		
		JNACDFileRichTextNavigatorAllocations allocations = getAllocations();
		SeekableByteChannel fileChannel = allocations.getFileChannel();
		
		fileChannel.position(m_position);
		
		Memory signatureMem = new Memory(2);
		ByteBuffer signatureBuf = signatureMem.getByteBuffer(0, signatureMem.size());
		fileChannel.read(signatureBuf);
		
		short signature = signatureMem.getShort(0);
		int dwLength;

		/* structures used to define and read the signatures 

			 0		   1
		+---------+---------+
		|   Sig   |  Length	|						Byte signature
		+---------+---------+

			 0		   1        2         3
		+---------+---------+---------+---------+
		|   Sig   |   ff    |		Length	   |		Word signature
		+---------+---------+---------+---------+

			 0		   1        2         3          4         5
		+---------+---------+---------+---------+---------+---------+
		|   Sig   |   00	    |                 Length		           | DWord signature
		+---------+---------+---------+---------+---------+---------+

		 */

		short highOrderByte = (short) (signature & 0xFF00);

		switch (highOrderByte) {
		case RichTextConstants.LONGRECORDLENGTH:      /* LSIG */
		    try(DisposableMemory intLengthMem = new DisposableMemory(4)) {
    			ByteBuffer intLengthBuf = intLengthMem.getByteBuffer(0, intLengthMem.size());
    			fileChannel.read(intLengthBuf);
    			dwLength = intLengthMem.getInt(0);
		    }

			break;

		case RichTextConstants.WORDRECORDLENGTH:      /* WSIG */
		    try(DisposableMemory shortLengthMem = new DisposableMemory(2)) {
    			ByteBuffer shortLengthBuf = shortLengthMem.getByteBuffer(0, shortLengthMem.size());
    			fileChannel.read(shortLengthBuf);
    			dwLength = shortLengthMem.getShort(0) & 0xffff;
		    }

			break;

		default:                    /* BSIG */
			dwLength = (signature >> 8) & 0x00ff;
			signature &= 0x00FF; /* Length not part of signature */
		}
		
		//file channel position points to the start of data, so reset it to the CD record start
		fileChannel.position(m_position);
		int cdRecordTotalLength = dwLength;
		@SuppressWarnings("resource")  // Held by the RT record object
        DisposableMemory cdRecordMem = new DisposableMemory(cdRecordTotalLength);
		int bytesRead = fileChannel.read(cdRecordMem.getByteBuffer(0, cdRecordMem.size()));
		if (bytesRead != cdRecordTotalLength) {
			throw new IllegalStateException(
				MessageFormat.format(
					"Bytes read from CD record file for CD record at index {0} is expected to be {1} but we only could read {2} bytes",
					m_currentCDRecordIndex, cdRecordTotalLength, bytesRead
				)
			);
		}
		
		ByteBuffer data = cdRecordMem.getByteBuffer(0, cdRecordTotalLength).order(ByteOrder.nativeOrder());
		RichTextRecord record = RichTextUtil.encapsulateRecord(signature, data);
		//remember the length of the CD records
		m_cdRecordSizeAtIndex.put(m_currentCDRecordIndex, record.getCDRecordLength());
		m_cdRecordIndexAtFilePos.put(m_position, m_currentCDRecordIndex);

		return record;
	}
	
	@Override
	public boolean isEmpty() {
		checkDisposed();
		
		return getAllocations().getFileSize()<=2;
	}

	@Override
	public boolean gotoFirst() {
		checkDisposed();
		
		if (isEmpty()) {
			return false;
		}
		
		if (m_position!=2 || m_currentCDRecord==null) {
			m_position = 2; // datatype TYPE_COMPOSITE (WORD)
			m_currentCDRecordIndex = 0;
			m_currentCDRecord = readCurrentCDRecordUnchecked();
		}
		return true;
	}

	@Override
	public boolean gotoLast() {
		checkDisposed();
		
		if (m_lastElementPosition!=-1 && m_lastElementIndex!=-1) {
			//we already know the exact position, because we have been there before
			m_position = m_lastElementPosition;
			m_currentCDRecordIndex = m_lastElementIndex;
			m_currentCDRecord = readCurrentCDRecordUnchecked();
			return true;
		}
		else {
			if (gotoFirst()) {
				RichTextRecord lastReadRecord = null;
				long lastReadRecordPosition;
				int lastReadRecordIndex;
				
				do {
					lastReadRecord = readCurrentCDRecordUnchecked();
					lastReadRecordPosition = m_position;
					lastReadRecordIndex = m_currentCDRecordIndex;
				}
				while (gotoNext());
				
				m_position = lastReadRecordPosition;
				m_currentCDRecord = lastReadRecord;
				m_currentCDRecordIndex = lastReadRecordIndex;
				return true;
			}
			else {
				return false;
			}
		}
	}

	@Override
	public boolean gotoNext() {
		checkDisposed();
		
		int cdRecordLength = m_currentCDRecord.getCDRecordLength();
		long nextPosition = m_position + cdRecordLength;
		if ((nextPosition & 1L)==1) {
            nextPosition += 1;
        }
		
		JNACDFileRichTextNavigatorAllocations allocations = getAllocations();
		
		if (nextPosition>=allocations.getFileSize()) {
			return false;
		}
		try {
			allocations.getFileChannel().position(nextPosition);
		} catch (IOException e) {
			throw new DominoException(
				MessageFormat.format(
					"Error navigating to position {0} of file {1} with size {2}",
					nextPosition, allocations.getFilePath(), allocations.getFileSize()
				),
				e
			);
		}
		m_position = nextPosition;
		m_currentCDRecord = readCurrentCDRecordUnchecked();
		return true;
	}

	@Override
	public boolean gotoPrev() {
		checkDisposed();
		
		JNACDFileRichTextNavigatorAllocations allocations = getAllocations();
		SeekableByteChannel fileChannel = allocations.getFileChannel();
		
		if (m_currentCDRecordIndex==-1) {
			return false;
		}
		else if (m_currentCDRecordIndex>0) {
			m_currentCDRecordIndex--;
			long prevRecordLength = m_cdRecordSizeAtIndex.get(m_currentCDRecordIndex);
			long prevPosition = m_position - prevRecordLength;
			try {
				fileChannel.position(prevPosition);
			} catch (IOException e) {
				throw new DominoException(
					MessageFormat.format(
						"Error navigating to position {0} of file {1} with size {2}",
						prevPosition, allocations.getFilePath(), allocations.getFileSize()
					),
					e
				);
			}
			
			m_position = prevPosition;
			m_currentCDRecord = readCurrentCDRecordUnchecked();
			return true;
		}
		else {
			return false;
		}
	}

	@Override
	public boolean hasNext() {
		checkDisposed();
		
		if (m_currentCDRecord==null) {
			return false;
		}
		return (m_position + m_currentCDRecord.getCDRecordLength()) < getAllocations().getFileSize();
	}

	@Override
	public boolean hasPrev() {
		checkDisposed();
		
		return m_currentCDRecordIndex>0;
	}

	@Override
	public RichTextRecord getCurrentRecord() {
		checkDisposed();
		
		return m_currentCDRecord;
	}
	
	@Override
	public ByteBuffer getCurrentRecordData() {
		RichTextRecord record = getCurrentRecord();
		if (record==null) {
			return null;
		}
		else {
			return record.getDataWithoutHeader();
		}
	}

	@Override
	public ByteBuffer getCurrentRecordDataWithHeader() {
		RichTextRecord record = getCurrentRecord();
		if (record==null) {
			return null;
		}
		else {
			return record.getData();
		}
	}
	
	@Override
	public int getCurrentRecordHeaderLength() {
		RichTextRecord record = getCurrentRecord();
		if (record==null) {
			return 0;
		}
		else {
			return record.getRecordHeaderLength();
		}
	}
	
	@Override
	public short getCurrentRecordTypeConstant() {
		RichTextRecord record = getCurrentRecord();
		return record==null ? 0 : record.getTypeValue();
	}

	@Override
	public int getCurrentRecordDataLength() {
		RichTextRecord record = getCurrentRecord();
		return record==null ? 0 : record.getPayloadLength();
	}
	
	@Override
	public int getCurrentRecordTotalLength() {
		RichTextRecord record = getCurrentRecord();
		return record==null ? 0 : record.getCDRecordLength();
	}
	

	@Override
	public RichtextPosition getCurrentPosition() {
		checkDisposed();
		
		return new CDFileRichTextPosition(this, m_position);
	}

	@Override
	public void restorePosition(RichtextPosition pos) {
		if (!(pos instanceof CDFileRichTextPosition)) {
			throw new IllegalArgumentException("Invalid position, not generated by this navigator");
		}
		
		CDFileRichTextPosition posImpl = (CDFileRichTextPosition) pos;
		if (posImpl.m_parentNav!=this) {
			throw new IllegalArgumentException("Invalid position, not generated by this navigator");
		}

		if (!gotoFirst()) {
			throw new IllegalStateException(MessageFormat.format("File does not have any content: {0}", getAllocations().getFilePath()));
		}
		long targetFilePos = posImpl.m_filePosition;
		Integer indexAtFilePos = m_cdRecordIndexAtFilePos.get(targetFilePos);
		if (indexAtFilePos==null) {
			throw new IllegalArgumentException("Unknown position");
		}
		m_position = posImpl.m_filePosition;
		m_currentCDRecordIndex = indexAtFilePos;
		m_currentCDRecord = readCurrentCDRecordUnchecked();
	}
	
	@Override
	public void copyCurrentRecordTo(RichTextWriter ct) {
		checkDisposed();
		
		RichTextRecord record = getCurrentRecord();
		if (record==null) {
			throw new IllegalStateException("Current record is null");
		}
		
		ct.addRichTextRecord(record);
	}

	@Override
	protected String toStringLocal() {
		return MessageFormat.format(
			"JNACDFileRichTextNavigator [filepath={0}, filesize={1}]", //$NON-NLS-1$
			getAllocations().getFilePath(), getAllocations().getFileSize()
		);
	}
	
	private static class CDFileRichTextPosition implements RichtextPosition {
		private RichtextNavigator m_parentNav;
		private long m_filePosition;
		
		public CDFileRichTextPosition(RichtextNavigator parentNav, long filePosition) {
			m_parentNav = parentNav;
			m_filePosition = filePosition;
		}
		
		@Override
		public int hashCode() {
			RichtextNavigator nav = m_parentNav;
			
			final int prime = 31;
			int result = 1;
			result = prime * result + (nav == null ? 0 : nav.hashCode());
			result = prime * result + (int) (m_filePosition ^ (m_filePosition >>> 32));
			result = prime * result + (nav == null ? 0 : nav.hashCode());
			return result;
		}

		@Override
		public boolean equals(Object obj) {
			RichtextNavigator nav = m_parentNav;
			if (this == obj) {
				return true;
			}
			if (obj == null) {
				return false;
			}
			if (getClass() != obj.getClass()) {
				return false;
			}
			CDFileRichTextPosition other = (CDFileRichTextPosition) obj;
			if(nav == null && other.m_parentNav != null) {
				return false;
			}
			if (nav != null && !nav.equals(other.m_parentNav)) {
				return false;
			}
			if (m_filePosition != other.m_filePosition) {
				return false;
			}
			return true;
		}

	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy