/*
* Copyright (c) 2007, intarsys consulting GmbH
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of intarsys nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package de.intarsys.pdf.st;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import de.intarsys.pdf.cds.CDSDate;
import de.intarsys.pdf.cos.COSCatalog;
import de.intarsys.pdf.cos.COSDictionary;
import de.intarsys.pdf.cos.COSDocument;
import de.intarsys.pdf.cos.COSIndirectObject;
import de.intarsys.pdf.cos.COSInfoDict;
import de.intarsys.pdf.cos.COSName;
import de.intarsys.pdf.cos.COSObject;
import de.intarsys.pdf.cos.COSObjectKey;
import de.intarsys.pdf.cos.COSObjectWalkerDeep;
import de.intarsys.pdf.cos.COSRuntimeException;
import de.intarsys.pdf.cos.COSTrailer;
import de.intarsys.pdf.cos.COSVisitorException;
import de.intarsys.pdf.crypt.COSEncryption;
import de.intarsys.pdf.crypt.COSSecurityException;
import de.intarsys.pdf.crypt.ISystemSecurityHandler;
import de.intarsys.pdf.crypt.PasswordProvider;
import de.intarsys.pdf.crypt.SystemSecurityHandler;
import de.intarsys.pdf.parser.COSDocumentParser;
import de.intarsys.pdf.parser.COSLoadError;
import de.intarsys.pdf.parser.COSLoadException;
import de.intarsys.pdf.writer.COSWriter;
import de.intarsys.tools.locator.ILocator;
import de.intarsys.tools.locator.ILocatorSupport;
import de.intarsys.tools.locator.TransientLocator;
import de.intarsys.tools.message.MessageBundle;
import de.intarsys.tools.randomaccess.BufferedRandomAccess;
import de.intarsys.tools.randomaccess.IRandomAccess;
import de.intarsys.tools.stream.StreamTools;
/**
* The most physical abstraction of a PDF document. This object handles the
* random access representation of the PDF file.
* <p>
* An STDocument manages the cross ref access to data stream positions from COS
* level objects. As such the ST and the COS package are highly interdependent.
*/
public class STDocument implements ILocatorSupport {
/**
* A counter for naming new documents
*/
private static int COUNTER = 0;
/** our current fdf version number * */
public static final STDocType DOCTYPE_FDF = new STDocType("FDF", "1.2"); //$NON-NLS-1$ //$NON-NLS-2$
/** our current pdf version number * */
public static final STDocType DOCTYPE_PDF = new STDocType("PDF", "1.4"); //$NON-NLS-1$ //$NON-NLS-2$
/** The logger to be used in this package */
private static Logger Log = PACKAGE.Log;
/**
* NLS
*/
private static final MessageBundle Msg = PACKAGE.Messages;
public static final String OPTION_WRITEMODEHINT = "writeModeHint"; //$NON-NLS-1$
/**
* Create a new document representing the data referenced by locator.
*
* @param locator
* The locator to the documents data
*
* @return A new document representing the data referenced by locator.
* @throws IOException
* @throws COSLoadException
*/
public static STDocument createFromLocator(ILocator locator)
throws IOException, COSLoadException {
if (!locator.exists()) {
throw new FileNotFoundException("'" + locator.getFullName() //$NON-NLS-1$
+ "' not found"); //$NON-NLS-1$
}
STDocument result = new STDocument(locator);
result.initializeFromLocator();
return result;
}
protected static String createName(String typeName) {
COUNTER++;
return Msg.getString(
"STDocument.documentName.new", typeName, new Integer(COUNTER)); //$NON-NLS-1$
}
/**
* create a new empty pdf document.
*
* @return A new empty pdf document
*/
public static STDocument createNew() {
return createNew(DOCTYPE_PDF);
}
/**
* create a new empty document.
*
* @return A new empty document
*/
public static STDocument createNew(STDocType docType) {
STDocument doc = new STDocument();
doc.initializeFromScratch(docType);
return doc;
}
private Object accessLock = new Object();
/**
* The collection of changed objects within the document since last save
*/
private Set changes = new HashSet();
private boolean closed = false;
/**
* Flag if this document is changed
*/
private boolean dirty = false;
private COSDocument doc;
/**
* The document's doc type.
*
* <p>
* This value is read from the file document header.
* </p>
*/
private STDocType docType;
/**
* A map of indirect objects in the document.
*/
private Map keyToObject = new HashMap();
/**
* The locator for the document physics
*/
private ILocator locator;
/**
* The next free COSObjectKey to use for a new indirect object
*/
private COSObjectKey nextKey;
/**
* The parser used for this document
*/
private COSDocumentParser parser;
/**
* The random access stream to read the documents data
*/
private IRandomAccess randomAccess;
/**
* The security handler used for encrypting/decrypting this documents
* content
*/
private ISystemSecurityHandler systemSecurityHandler;
private EnumWriteMode writeModeHint = (EnumWriteMode) EnumWriteMode.META
.getDefault();
/**
* The most recent x reference section.
* <p>
* When a new document is created or initialized from a data stream, a new
* empty XRef Section is always created for holding the changes to come.
*/
private STXRefSection xRefSection;
/**
* A new empty document.
* <p>
* Use always the factory method, this is not completely initialized.
*/
protected STDocument() {
//
}
/**
* A new document bound to a locator.
*
* @param locator
* The locator to the documents data.
*/
protected STDocument(ILocator locator) {
setLocator(locator);
}
/**
* Mark object as changed within this document.
*
* @param object
* The object that is new or changed
*/
public void addChangedReference(COSIndirectObject object) {
setDirty(true);
changes.add(object);
}
/**
* Add another indirect object to the document.
*
* @param newRef
* The new indirect object.
*/
public void addObjectReference(COSIndirectObject newRef) {
COSObjectKey key = newRef.getKey();
getKeyToObject().put(key, newRef);
}
protected void checkConsistency() throws COSLoadError {
if (getDocType() == null) {
throw new COSLoadError("unknown document type"); //$NON-NLS-1$
}
if (getDocType().isPDF()) {
if (getXRefSection() == null) {
throw new COSLoadError("x ref section missing"); //$NON-NLS-1$
}
if (getXRefSection().cosGetDict() == null) {
throw new COSLoadError("trailer missing"); //$NON-NLS-1$
}
}
}
/**
* Close the document. Accessing a documents content is undefined after
* <code>close</code>.
*
* @throws IOException
*/
public void close() throws IOException {
synchronized (getAccessLock()) {
if (isClosed()) {
return;
}
if (getRandomAccess() != null) {
getRandomAccess().close();
setClosed(true);
setRandomAccess(null);
}
}
}
/**
* Return a deep copy of the document. This will create a copy of the
* documents content. The new documents location (random access) is
* undefined. The objects will not preserve their key values.
*
* @return A deep copy of this.
*/
public STDocument copyDeep() {
STDocument result = STDocument.createNew();
COSDictionary newTrailer = (COSDictionary) cosGetTrailer().copyDeep();
newTrailer.remove(COSTrailer.DK_Prev);
newTrailer.remove(COSTrailer.DK_Size);
newTrailer.remove(STXRefSection.DK_XRefStm);
((STTrailerXRefSection) result.getXRefSection()).cosSetDict(newTrailer);
result.systemSecurityHandler = getSystemSecurityHandler();
String name = Msg
.getString("STDocument.documentName.copyOf", getName()); //$NON-NLS-1$
result.locator = new TransientLocator(name, getDocType().getTypeName());
return result;
}
/**
* The documents trailer dictionary
*
* @return The documents trailer dictionary
*/
public COSDictionary cosGetTrailer() {
return getXRefSection().cosGetDict();
}
public STXRefSection createNewXRefSection() {
if (getXRefSection().getOffset() != -1) {
// create a new empty xref section for changes...
return getXRefSection().createSuccessor();
}
return getXRefSection();
}
/**
* Create a new valid key for use in the document.
*
* @return A new valid key for use in the document.
*/
public COSObjectKey createObjectKey() {
nextKey = nextKey.createNextKey();
return nextKey;
}
/**
* Create a new random access object for the document data.
*
* @param pLocator
* The locator to the document data.
* @return Create a new random access object for the document data.
* @throws IOException
*/
protected IRandomAccess createRandomAccess(ILocator pLocator)
throws IOException {
if (pLocator == null) {
return null;
}
IRandomAccess baseAccess = pLocator.getRandomAccess();
// return baseAccess;
BufferedRandomAccess bufferedAccess = new BufferedRandomAccess(
baseAccess, 4096);
return bufferedAccess;
}
/**
* Start a garbage collection for the receiver. In a garbage collection
* every indirect object currently unused (unreachable from the catalog) is
* removed.
*
*/
public void garbageCollect() {
COSObjectWalkerDeep walker = new COSObjectWalkerDeep();
try {
cosGetTrailer().accept(walker);
} catch (COSVisitorException e) {
// won't happen
}
// prepare new empty x ref section
STTrailerXRefSection emptyXRefSection = new STTrailerXRefSection(this);
COSDictionary emptyTrailer = emptyXRefSection.cosGetDict();
emptyTrailer.addAll(cosGetTrailer());
emptyTrailer.remove(COSTrailer.DK_Prev);
emptyTrailer.remove(COSTrailer.DK_Size);
emptyTrailer.remove(STXRefSection.DK_XRefStm);
setXRefSection(emptyXRefSection);
// prepare new object collection
getKeyToObject().clear();
getChanges().clear();
nextKey = new COSObjectKey(0, 0);
for (Iterator i = walker.getVisited().iterator(); i.hasNext();) {
COSIndirectObject o = (COSIndirectObject) i.next();
// force new key
o.setKey(null);
addObjectReference(o);
o.setDirty(true);
}
}
public Object getAccessLock() {
return accessLock;
}
public Collection getChanges() {
return changes;
}
public COSDocument getDoc() {
return doc;
}
public STDocType getDocType() {
return docType;
}
public int getIncrementalCount() {
return getXRefSection().getIncrementalCount();
}
/**
* THe documents objects.
*
* @return THe documents objects.
*/
protected Map getKeyToObject() {
return keyToObject;
}
/**
* THe locator for the document data.
*
* @return THe locator for the document data.
*/
public ILocator getLocator() {
return locator;
}
/**
* A name for the document.
* <p>
* This is either a "local" name or the name of the locator reference if
* present.
*
* @return A name for the document
*/
public String getName() {
return getLocator().getLocalName();
}
/**
* The indirect object with object number objNum and generation number
* genNum is looked up in the document. If the indirect object is not yet
* available, it is created and registered.
*
* @param key
*
* @return The indirect object with object number objNum and generation
* number genNum
*/
public COSIndirectObject getObjectReference(COSObjectKey key) {
COSIndirectObject result = (COSIndirectObject) getKeyToObject()
.get(key);
if (result == null) {
result = COSIndirectObject.create(this, key);
// todo 1 @mit this call should not be necessary
addObjectReference(result);
}
return result;
}
/**
* The parser used for decoding the document data stream.
*
* @return The parser used for decoding the document data stream.
*/
public COSDocumentParser getParser() {
return parser;
}
/**
* The random access object for the documents data. Be aware that using the
* IRandomAccess after it is closed will throw an IOException.
*
* @return The random access object for the documents data.
*/
public IRandomAccess getRandomAccess() {
return randomAccess;
}
/**
* The documents security handler
*
* @return The documents security handler
*/
public ISystemSecurityHandler getSystemSecurityHandler() {
return systemSecurityHandler;
}
public COSTrailer getTrailer() {
return (COSTrailer) COSTrailer.META.createFromCos(cosGetTrailer());
}
/**
* The version of the PDF spec for this document
*
* @return The version of the PDF spec for this document
*/
public String getVersion() {
// todo 1 @mit fix version
return getDocType().toString();
}
/**
* The write mode to be used when the document is written the next time. If
* defined this overrides any hint that is used when saving the document.
* The write mode is reset after each "save".
*
* @return The write mode to be used when the document is written.
*/
public EnumWriteMode getWriteModeHint() {
return writeModeHint;
}
/**
* The most recent STXrefSection of the document.
*
* @return The most recent STXrefSection of the document.
*/
public STXRefSection getXRefSection() {
return xRefSection;
}
public void incrementalGarbageCollect() {
final Set unknown = new HashSet(getChanges());
COSObjectWalkerDeep stripper = new COSObjectWalkerDeep(false) {
public Object visitFromIndirectObject(COSIndirectObject io)
throws COSVisitorException {
unknown.remove(io);
return super.visitFromIndirectObject(io);
}
};
try {
cosGetTrailer().accept(stripper);
} catch (COSVisitorException e) {
// won't happen
}
getChanges().removeAll(unknown);
}
/**
* Load the encryption parameters and initialize the security handler
* context.
*
* @throws IOException
*
*/
protected void initEncryption() throws IOException {
systemSecurityHandler = null;
COSEncryption encryption = COSEncryption
.getEncryptionIn(getXRefSection());
if (encryption != null) {
systemSecurityHandler = SystemSecurityHandler.create(encryption);
if (systemSecurityHandler == null) {
throw new COSRuntimeException(
"Unsupported encryption algorithm"); //$NON-NLS-1$
}
try {
systemSecurityHandler.init(this, encryption);
// force authentication early, /AuthEvent not yet supported
systemSecurityHandler.authenticate(PasswordProvider.get());
} catch (COSSecurityException e) {
IOException ioe = new IOException(e.getMessage());
ioe.initCause(e);
throw ioe;
}
}
}
/**
* Initialize the document from its data.
*
* @throws IOException
* @throws COSLoadException
*/
protected void initializeFromLocator() throws IOException, COSLoadException {
parser = new COSDocumentParser(this);
streamLoad();
}
/**
* Initialize a new empty document
*/
protected void initializeFromScratch(STDocType pDocType) {
setDocType(pDocType);
String name = createName(getDocType().getTypeName());
locator = new TransientLocator(name, pDocType.getTypeName());
parser = new COSDocumentParser(this);
setXRefSection(new STTrailerXRefSection(this));
nextKey = new COSObjectKey(0, 0);
cosGetTrailer().put(COSTrailer.DK_Root,
COSCatalog.META.createNew().cosGetDict());
setDirty(true);
}
public boolean isClosed() {
return closed;
}
/**
* <code>true</code> if this has been changed.
*
* @return <code>true</code> if this has been changed.
*/
public boolean isDirty() {
return dirty;
}
/**
* @return if the document has an {@link ISystemSecurityHandler}
*/
public boolean isEncrypted() {
return getSystemSecurityHandler() != null;
}
public boolean isNew() {
return (getXRefSection().getOffset() == -1)
&& (getXRefSection().getPrevious() == null);
}
/**
* <code>true</code> if this is read only.
*
* @return <code>true</code> if this is read only.
*/
public boolean isReadOnly() {
return (getRandomAccess() == null) || getRandomAccess().isReadOnly();
}
/**
* <code>true</code> if this has only streamed xref sections.
*
* @return <code>true</code> if this has only streamed xref sections.
*/
public boolean isStreamed() {
if (getXRefSection() != null) {
return getXRefSection().isStreamed();
}
return false;
}
/**
* Load a COSObject from the documents data.
*
* @param ref
* The object reference to be loaded.
* @throws IOException
* @throws COSLoadException
*/
public COSObject load(COSIndirectObject ref) throws IOException,
COSLoadException {
int objectNumber = ref.getKey().getObjectNumber();
return load(objectNumber);
}
protected COSObject load(int objectNumber) throws IOException,
COSLoadException {
synchronized (getAccessLock()) {
return getXRefSection().load(objectNumber,
getSystemSecurityHandler());
}
}
public void loadAll() throws IOException, COSLoadException {
synchronized (getAccessLock()) {
for (int i = 0; i < getXRefSection().getSize(); i++) {
getXRefSection().load(i, getSystemSecurityHandler());
}
}
}
/**
* The number of objects currently loaded.
*
* @return The number of objects currently loaded.
*/
public int loadedSize() {
int result = 0;
for (Iterator it = getKeyToObject().values().iterator(); it.hasNext();) {
COSIndirectObject ref = (COSIndirectObject) it.next();
if (!ref.isSwapped()) {
result++;
}
}
return result;
}
/**
* An iterator on the indirect objects of the storage layer document. This
* includes garbage and purely technical objects like x ref streams.
*
* @return An iterator on the indirect objects of the storage layer
* document. This includes garbage and purely technical objects like
* x ref streams.
*/
public Iterator objects() {
return new Iterator() {
int i = 1;
public boolean hasNext() {
return i < getXRefSection().getSize();
}
public Object next() {
if (!hasNext()) {
throw new NoSuchElementException(""); //$NON-NLS-1$
}
COSObjectKey key = new COSObjectKey(i++, 0);
return getObjectReference(key);
}
public void remove() {
throw new UnsupportedOperationException("remove not supported"); //$NON-NLS-1$
}
};
}
/**
* @throws IOException
*
*/
protected void open() throws IOException {
synchronized (getAccessLock()) {
if ((randomAccess != null) && !isClosed()) {
throw new IllegalStateException("can't open an open document"); //$NON-NLS-1$
}
setRandomAccess(createRandomAccess(getLocator()));
}
}
/**
* Reparses the XREF sections without actually instantiating. Used for
* collecting errors on XREF level
*
* @throws IOException
* @throws COSLoadException
*/
public void reparseFromLocator() throws IOException, COSLoadException {
synchronized (getAccessLock()) {
int offset = getParser().searchLastStartXRef(getRandomAccess());
AbstractXRefParser xRefParser;
if (getParser().isTokenXRefAt(getRandomAccess(), offset)) {
xRefParser = new XRefTrailerParser(this, getParser());
} else {
xRefParser = new XRefStreamParser(this, getParser());
}
getRandomAccess().seek(offset);
xRefParser.parse(getRandomAccess());
}
}
/**
* Assign a new locator to the document.
* <p>
* The documents data is completely copied to the new location.
*
* @param newLocator
* The new locator for the documents data.
*
* @throws IOException
*/
protected void replaceLocator(ILocator newLocator) throws IOException {
synchronized (getAccessLock()) {
if (newLocator.equals(getLocator())) {
return;
}
ILocator oldLocator = getLocator();
IRandomAccess oldRandomAccess = getRandomAccess();
try {
setLocator(newLocator);
setRandomAccess(null);
open();
IRandomAccess newRandomAccess = getRandomAccess();
if (newRandomAccess.isReadOnly()) {
throw new FileNotFoundException();
}
if (newRandomAccess.getLength() > 0) {
newRandomAccess.setLength(0);
}
if (oldRandomAccess != null) {
InputStream is = oldRandomAccess.asInputStream();
OutputStream os = newRandomAccess.asOutputStream();
oldRandomAccess.seek(0);
StreamTools.copyStream(is, false, os, false);
}
StreamTools.close(oldRandomAccess);
} catch (Exception e) {
// undo changes
StreamTools.close(getRandomAccess());
setLocator(oldLocator);
setRandomAccess(oldRandomAccess);
}
}
}
public void restore(ILocator newLocator) throws IOException,
COSLoadException {
synchronized (getAccessLock()) {
if (newLocator.equals(getLocator())) {
return;
}
IRandomAccess oldRandomAccess = getRandomAccess();
StreamTools.close(oldRandomAccess);
setRandomAccess(null);
setLocator(newLocator);
changes.clear();
keyToObject.clear();
closed = false;
dirty = false;
streamLoad();
}
getDoc().triggerChangedAll();
}
public void save() throws IOException {
save(getLocator(), null);
}
public void save(ILocator pLocator) throws IOException {
save(pLocator, null);
}
public void save(ILocator pLocator, Map options) throws IOException {
// options could be null, when called from save(), even if a locator
// exists.
if (options == null) {
options = new HashMap();
}
if ((pLocator != null) && (pLocator != getLocator())) {
replaceLocator(pLocator);
}
boolean incremental = true;
EnumWriteMode writeMode = doc.getWriteModeHint();
// reset write mode
doc.setWriteModeHint(EnumWriteMode.UNDEFINED);
if (writeMode.isUndefined()) {
Object tempHint = options.get(OPTION_WRITEMODEHINT);
if (tempHint instanceof EnumWriteMode) {
writeMode = (EnumWriteMode) tempHint;
}
}
if (writeMode.isFull()) {
incremental = false;
}
IRandomAccess tempRandomAccess = getRandomAccess();
if (tempRandomAccess == null) {
throw new IOException("nowhere to write to"); //$NON-NLS-1$
}
if (tempRandomAccess.isReadOnly()) {
throw new FileNotFoundException("destination is read only"); //$NON-NLS-1$
}
COSWriter writer = new COSWriter(tempRandomAccess,
getSystemSecurityHandler());
writer.setIncremental(incremental);
writer.writeDocument(this);
}
protected void setClosed(boolean closed) {
this.closed = closed;
}
/**
* Set the change flag of this.
*
* @param dirty
* <code>true</code> if this should be marked as changed
*/
public void setDirty(boolean dirty) {
this.dirty = dirty;
if (!dirty) {
changes.clear();
}
}
public void setDoc(COSDocument doc) {
this.doc = doc;
getXRefSection().setCOSDoc(getDoc());
}
protected void setDocType(STDocType docType) {
this.docType = docType;
}
protected void setLocator(ILocator locator) {
this.locator = locator;
}
/**
* Rename the document locally.
* <p>
* This has no effect if a locator is present.
*
* @param name
* The new local name of this
*/
public void setName(String name) {
if (getLocator() instanceof TransientLocator) {
((TransientLocator) getLocator()).setLocalName(name);
}
}
/**
* Assign the IRAndomAccess to the documetns data.
*
* @param randomAccess
* the IRAndomAccess to the documetns data.
*/
protected void setRandomAccess(IRandomAccess randomAccess) {
this.randomAccess = randomAccess;
}
/**
* The write mode to be used when the document is written the next time. If
* defined this overrides any hint that is used when saving the document.
* The write mode is reset after each "save".
*
* @param writeMode
* The write mode to be used when the document is written.
*/
public void setWriteModeHint(EnumWriteMode writeMode) {
if (writeMode == null) {
throw new IllegalArgumentException("write mode can't be null"); //$NON-NLS-1$
}
this.writeModeHint = writeMode;
}
/**
* Attach the most recent x ref section to the document.
*
* @param xRefSection
* The x ref section representing the most recent document
* changes.
*/
public void setXRefSection(STXRefSection xRefSection) {
this.xRefSection = xRefSection;
if (getDoc() != null) {
getXRefSection().setCOSDoc(getDoc());
}
}
protected void streamLoad() throws IOException, COSLoadException {
try {
open();
STXRefSection initialXRefSection;
setDocType(getParser().parseHeader(getRandomAccess()));
try {
int offset = getParser().searchLastStartXRef(getRandomAccess());
AbstractXRefParser xRefParser;
if (getParser().isTokenXRefAt(getRandomAccess(), offset)) {
xRefParser = new XRefTrailerParser(this, getParser());
} else {
xRefParser = new XRefStreamParser(this, getParser());
}
getRandomAccess().seek(offset);
initialXRefSection = xRefParser.parse(getRandomAccess());
} catch (Exception ex) {
Log.log(Level.FINEST, "error parsing " //$NON-NLS-1$
+ getLocator().getFullName(), ex);
// TODO 2 log warning, trailer can't be parsed
initialXRefSection = new XRefFallbackParser(this, getParser())
.parse(getRandomAccess());
}
setXRefSection(initialXRefSection);
nextKey = new COSObjectKey(initialXRefSection.getSize() - 1, 0);
initEncryption();
checkConsistency();
} catch (IOException e) {
try {
close();
} catch (IOException ce) {
// ignore
}
throw e;
} catch (COSLoadException e) {
try {
close();
} catch (IOException ce) {
// ignore
}
throw e;
}
}
public void updateModificationDate() {
COSDictionary infoDict = cosGetTrailer().get(COSTrailer.DK_Info)
.asDictionary();
if (infoDict == null) {
return;
}
infoDict.put(COSInfoDict.DK_ModDate, new CDSDate().cosGetObject());
}
/**
* <code>true</code> if this document is linearized.
* <p>
* When linearized reading is truly implemented, this check should be made
* using the document length instead for performance reasons.
*
* @return <code>true</code> if this document is linearized.
*/
public boolean isLinearized() {
return getLinearizedDict() != null;
}
/**
* The /Linearized dictionary of the document. The /Linearized dictionary is
* represented by the first entry in the (logically) first XRef section.
* <p>
* Note that this method may NOT return a dictionary even if the document
* contains a /Linearized dictionary as the first object. This is the case
* when the document was linearized and was written with an incremental
* change so that the linearization is obsolete.
*
* @return The valid /Linearized dictionary of the document.
*/
public COSDictionary getLinearizedDict() {
int objectNumber = 0;
Iterator it = getXRefSection().entryIterator();
while (it.hasNext()) {
STXRefEntry entry = (STXRefEntry) it.next();
if (entry.getObjectNumber() != 0) {
objectNumber = entry.getObjectNumber();
break;
}
}
try {
COSDictionary result = load(objectNumber).asDictionary();
if (result != null) {
COSObject version = result.get(COSName.constant("Linearized"));
if (!version.isNull()) {
return result;
}
}
} catch (Exception e) {
// ignore
}
return null;
}
}
|