0001: /*
0002: * Copyright (c) 2007, intarsys consulting GmbH
0003: *
0004: * Redistribution and use in source and binary forms, with or without
0005: * modification, are permitted provided that the following conditions are met:
0006: *
0007: * - Redistributions of source code must retain the above copyright notice,
0008: * this list of conditions and the following disclaimer.
0009: *
0010: * - Redistributions in binary form must reproduce the above copyright notice,
0011: * this list of conditions and the following disclaimer in the documentation
0012: * and/or other materials provided with the distribution.
0013: *
0014: * - Neither the name of intarsys nor the names of its contributors may be used
0015: * to endorse or promote products derived from this software without specific
0016: * prior written permission.
0017: *
0018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
0019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
0020: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
0021: * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
0022: * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
0023: * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
0024: * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
0025: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
0026: * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
0027: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
0028: * POSSIBILITY OF SUCH DAMAGE.
0029: */
0030: package de.intarsys.pdf.st;
0031:
0032: import java.io.FileNotFoundException;
0033: import java.io.IOException;
0034: import java.io.InputStream;
0035: import java.io.OutputStream;
0036: import java.util.Collection;
0037: import java.util.HashMap;
0038: import java.util.HashSet;
0039: import java.util.Iterator;
0040: import java.util.Map;
0041: import java.util.NoSuchElementException;
0042: import java.util.Set;
0043: import java.util.logging.Level;
0044: import java.util.logging.Logger;
0045:
0046: import de.intarsys.pdf.cds.CDSDate;
0047: import de.intarsys.pdf.cos.COSCatalog;
0048: import de.intarsys.pdf.cos.COSDictionary;
0049: import de.intarsys.pdf.cos.COSDocument;
0050: import de.intarsys.pdf.cos.COSIndirectObject;
0051: import de.intarsys.pdf.cos.COSInfoDict;
0052: import de.intarsys.pdf.cos.COSName;
0053: import de.intarsys.pdf.cos.COSObject;
0054: import de.intarsys.pdf.cos.COSObjectKey;
0055: import de.intarsys.pdf.cos.COSObjectWalkerDeep;
0056: import de.intarsys.pdf.cos.COSRuntimeException;
0057: import de.intarsys.pdf.cos.COSTrailer;
0058: import de.intarsys.pdf.cos.COSVisitorException;
0059: import de.intarsys.pdf.crypt.COSEncryption;
0060: import de.intarsys.pdf.crypt.COSSecurityException;
0061: import de.intarsys.pdf.crypt.ISystemSecurityHandler;
0062: import de.intarsys.pdf.crypt.PasswordProvider;
0063: import de.intarsys.pdf.crypt.SystemSecurityHandler;
0064: import de.intarsys.pdf.parser.COSDocumentParser;
0065: import de.intarsys.pdf.parser.COSLoadError;
0066: import de.intarsys.pdf.parser.COSLoadException;
0067: import de.intarsys.pdf.writer.COSWriter;
0068: import de.intarsys.tools.locator.ILocator;
0069: import de.intarsys.tools.locator.ILocatorSupport;
0070: import de.intarsys.tools.locator.TransientLocator;
0071: import de.intarsys.tools.message.MessageBundle;
0072: import de.intarsys.tools.randomaccess.BufferedRandomAccess;
0073: import de.intarsys.tools.randomaccess.IRandomAccess;
0074: import de.intarsys.tools.stream.StreamTools;
0075:
0076: /**
0077: * The most physical abstraction of a PDF document. This object handles the
0078: * random access representation of the PDF file.
0079: * <p>
0080: * An STDocument manages the cross ref access to data stream positions from COS
0081: * level objects. As such the ST and the COS package are highly interdependent.
0082: */
0083: public class STDocument implements ILocatorSupport {
0084: /**
0085: * A counter for naming new documents
0086: */
0087: private static int COUNTER = 0;
0088:
0089: /** our current fdf version number * */
0090: public static final STDocType DOCTYPE_FDF = new STDocType(
0091: "FDF", "1.2"); //$NON-NLS-1$ //$NON-NLS-2$
0092:
0093: /** our current pdf version number * */
0094: public static final STDocType DOCTYPE_PDF = new STDocType(
0095: "PDF", "1.4"); //$NON-NLS-1$ //$NON-NLS-2$
0096:
0097: /** The logger to be used in this package */
0098: private static Logger Log = PACKAGE.Log;
0099:
0100: /**
0101: * NLS
0102: */
0103: private static final MessageBundle Msg = PACKAGE.Messages;
0104:
0105: public static final String OPTION_WRITEMODEHINT = "writeModeHint"; //$NON-NLS-1$
0106:
0107: /**
0108: * Create a new document representing the data referenced by locator.
0109: *
0110: * @param locator
0111: * The locator to the documents data
0112: *
0113: * @return A new document representing the data referenced by locator.
0114: * @throws IOException
0115: * @throws COSLoadException
0116: */
0117: public static STDocument createFromLocator(ILocator locator)
0118: throws IOException, COSLoadException {
0119: if (!locator.exists()) {
0120: throw new FileNotFoundException("'" + locator.getFullName() //$NON-NLS-1$
0121: + "' not found"); //$NON-NLS-1$
0122: }
0123: STDocument result = new STDocument(locator);
0124: result.initializeFromLocator();
0125: return result;
0126: }
0127:
0128: protected static String createName(String typeName) {
0129: COUNTER++;
0130: return Msg
0131: .getString(
0132: "STDocument.documentName.new", typeName, new Integer(COUNTER)); //$NON-NLS-1$
0133: }
0134:
0135: /**
0136: * create a new empty pdf document.
0137: *
0138: * @return A new empty pdf document
0139: */
0140: public static STDocument createNew() {
0141: return createNew(DOCTYPE_PDF);
0142: }
0143:
0144: /**
0145: * create a new empty document.
0146: *
0147: * @return A new empty document
0148: */
0149: public static STDocument createNew(STDocType docType) {
0150: STDocument doc = new STDocument();
0151: doc.initializeFromScratch(docType);
0152: return doc;
0153: }
0154:
0155: private Object accessLock = new Object();
0156:
0157: /**
0158: * The collection of changed objects within the document since last save
0159: */
0160: private Set changes = new HashSet();
0161:
0162: private boolean closed = false;
0163:
0164: /**
0165: * Flag if this document is changed
0166: */
0167: private boolean dirty = false;
0168:
0169: private COSDocument doc;
0170:
0171: /**
0172: * The document's doc type.
0173: *
0174: * <p>
0175: * This value is read from the file document header.
0176: * </p>
0177: */
0178: private STDocType docType;
0179:
0180: /**
0181: * A map of indirect objects in the document.
0182: */
0183: private Map keyToObject = new HashMap();
0184:
0185: /**
0186: * The locator for the document physics
0187: */
0188: private ILocator locator;
0189:
0190: /**
0191: * The next free COSObjectKey to use for a new indirect object
0192: */
0193: private COSObjectKey nextKey;
0194:
0195: /**
0196: * The parser used for this document
0197: */
0198: private COSDocumentParser parser;
0199:
0200: /**
0201: * The random access stream to read the documents data
0202: */
0203: private IRandomAccess randomAccess;
0204:
0205: /**
0206: * The security handler used for encrypting/decrypting this documents
0207: * content
0208: */
0209: private ISystemSecurityHandler systemSecurityHandler;
0210:
0211: private EnumWriteMode writeModeHint = (EnumWriteMode) EnumWriteMode.META
0212: .getDefault();
0213:
0214: /**
0215: * The most recent x reference section.
0216: * <p>
0217: * When a new document is created or initialized from a data stream, a new
0218: * empty XRef Section is always created for holding the changes to come.
0219: */
0220: private STXRefSection xRefSection;
0221:
0222: /**
0223: * A new empty document.
0224: * <p>
0225: * Use always the factory method, this is not completely initialized.
0226: */
0227: protected STDocument() {
0228: //
0229: }
0230:
0231: /**
0232: * A new document bound to a locator.
0233: *
0234: * @param locator
0235: * The locator to the documents data.
0236: */
0237: protected STDocument(ILocator locator) {
0238: setLocator(locator);
0239: }
0240:
0241: /**
0242: * Mark object as changed within this document.
0243: *
0244: * @param object
0245: * The object that is new or changed
0246: */
0247: public void addChangedReference(COSIndirectObject object) {
0248: setDirty(true);
0249: changes.add(object);
0250: }
0251:
0252: /**
0253: * Add another indirect object to the document.
0254: *
0255: * @param newRef
0256: * The new indirect object.
0257: */
0258: public void addObjectReference(COSIndirectObject newRef) {
0259: COSObjectKey key = newRef.getKey();
0260: getKeyToObject().put(key, newRef);
0261: }
0262:
0263: protected void checkConsistency() throws COSLoadError {
0264: if (getDocType() == null) {
0265: throw new COSLoadError("unknown document type"); //$NON-NLS-1$
0266: }
0267: if (getDocType().isPDF()) {
0268: if (getXRefSection() == null) {
0269: throw new COSLoadError("x ref section missing"); //$NON-NLS-1$
0270: }
0271: if (getXRefSection().cosGetDict() == null) {
0272: throw new COSLoadError("trailer missing"); //$NON-NLS-1$
0273: }
0274: }
0275: }
0276:
0277: /**
0278: * Close the document. Accessing a documents content is undefined after
0279: * <code>close</code>.
0280: *
0281: * @throws IOException
0282: */
0283: public void close() throws IOException {
0284: synchronized (getAccessLock()) {
0285: if (isClosed()) {
0286: return;
0287: }
0288: if (getRandomAccess() != null) {
0289: getRandomAccess().close();
0290: setClosed(true);
0291: setRandomAccess(null);
0292: }
0293: }
0294: }
0295:
0296: /**
0297: * Return a deep copy of the document. This will create a copy of the
0298: * documents content. The new documents location (random access) is
0299: * undefined. The objects will not preserve their key values.
0300: *
0301: * @return A deep copy of this.
0302: */
0303: public STDocument copyDeep() {
0304: STDocument result = STDocument.createNew();
0305: COSDictionary newTrailer = (COSDictionary) cosGetTrailer()
0306: .copyDeep();
0307: newTrailer.remove(COSTrailer.DK_Prev);
0308: newTrailer.remove(COSTrailer.DK_Size);
0309: newTrailer.remove(STXRefSection.DK_XRefStm);
0310: ((STTrailerXRefSection) result.getXRefSection())
0311: .cosSetDict(newTrailer);
0312: result.systemSecurityHandler = getSystemSecurityHandler();
0313: String name = Msg.getString(
0314: "STDocument.documentName.copyOf", getName()); //$NON-NLS-1$
0315: result.locator = new TransientLocator(name, getDocType()
0316: .getTypeName());
0317: return result;
0318: }
0319:
0320: /**
0321: * The documents trailer dictionary
0322: *
0323: * @return The documents trailer dictionary
0324: */
0325: public COSDictionary cosGetTrailer() {
0326: return getXRefSection().cosGetDict();
0327: }
0328:
0329: public STXRefSection createNewXRefSection() {
0330: if (getXRefSection().getOffset() != -1) {
0331: // create a new empty xref section for changes...
0332: return getXRefSection().createSuccessor();
0333: }
0334: return getXRefSection();
0335: }
0336:
0337: /**
0338: * Create a new valid key for use in the document.
0339: *
0340: * @return A new valid key for use in the document.
0341: */
0342: public COSObjectKey createObjectKey() {
0343: nextKey = nextKey.createNextKey();
0344: return nextKey;
0345: }
0346:
0347: /**
0348: * Create a new random access object for the document data.
0349: *
0350: * @param pLocator
0351: * The locator to the document data.
0352: * @return Create a new random access object for the document data.
0353: * @throws IOException
0354: */
0355: protected IRandomAccess createRandomAccess(ILocator pLocator)
0356: throws IOException {
0357: if (pLocator == null) {
0358: return null;
0359: }
0360: IRandomAccess baseAccess = pLocator.getRandomAccess();
0361:
0362: // return baseAccess;
0363: BufferedRandomAccess bufferedAccess = new BufferedRandomAccess(
0364: baseAccess, 4096);
0365: return bufferedAccess;
0366: }
0367:
0368: /**
0369: * Start a garbage collection for the receiver. In a garbage collection
0370: * every indirect object currently unused (unreachable from the catalog) is
0371: * removed.
0372: *
0373: */
0374: public void garbageCollect() {
0375: COSObjectWalkerDeep walker = new COSObjectWalkerDeep();
0376: try {
0377: cosGetTrailer().accept(walker);
0378: } catch (COSVisitorException e) {
0379: // won't happen
0380: }
0381:
0382: // prepare new empty x ref section
0383: STTrailerXRefSection emptyXRefSection = new STTrailerXRefSection(
0384: this );
0385: COSDictionary emptyTrailer = emptyXRefSection.cosGetDict();
0386: emptyTrailer.addAll(cosGetTrailer());
0387: emptyTrailer.remove(COSTrailer.DK_Prev);
0388: emptyTrailer.remove(COSTrailer.DK_Size);
0389: emptyTrailer.remove(STXRefSection.DK_XRefStm);
0390: setXRefSection(emptyXRefSection);
0391: // prepare new object collection
0392: getKeyToObject().clear();
0393: getChanges().clear();
0394: nextKey = new COSObjectKey(0, 0);
0395: for (Iterator i = walker.getVisited().iterator(); i.hasNext();) {
0396: COSIndirectObject o = (COSIndirectObject) i.next();
0397: // force new key
0398: o.setKey(null);
0399: addObjectReference(o);
0400: o.setDirty(true);
0401: }
0402: }
0403:
0404: public Object getAccessLock() {
0405: return accessLock;
0406: }
0407:
0408: public Collection getChanges() {
0409: return changes;
0410: }
0411:
0412: public COSDocument getDoc() {
0413: return doc;
0414: }
0415:
0416: public STDocType getDocType() {
0417: return docType;
0418: }
0419:
0420: public int getIncrementalCount() {
0421: return getXRefSection().getIncrementalCount();
0422: }
0423:
0424: /**
0425: * THe documents objects.
0426: *
0427: * @return THe documents objects.
0428: */
0429: protected Map getKeyToObject() {
0430: return keyToObject;
0431: }
0432:
0433: /**
0434: * THe locator for the document data.
0435: *
0436: * @return THe locator for the document data.
0437: */
0438: public ILocator getLocator() {
0439: return locator;
0440: }
0441:
0442: /**
0443: * A name for the document.
0444: * <p>
0445: * This is either a "local" name or the name of the locator reference if
0446: * present.
0447: *
0448: * @return A name for the document
0449: */
0450: public String getName() {
0451: return getLocator().getLocalName();
0452: }
0453:
0454: /**
0455: * The indirect object with object number objNum and generation number
0456: * genNum is looked up in the document. If the indirect object is not yet
0457: * available, it is created and registered.
0458: *
0459: * @param key
0460: *
0461: * @return The indirect object with object number objNum and generation
0462: * number genNum
0463: */
0464: public COSIndirectObject getObjectReference(COSObjectKey key) {
0465: COSIndirectObject result = (COSIndirectObject) getKeyToObject()
0466: .get(key);
0467: if (result == null) {
0468: result = COSIndirectObject.create(this , key);
0469: // todo 1 @mit this call should not be necessary
0470: addObjectReference(result);
0471: }
0472: return result;
0473: }
0474:
0475: /**
0476: * The parser used for decoding the document data stream.
0477: *
0478: * @return The parser used for decoding the document data stream.
0479: */
0480: public COSDocumentParser getParser() {
0481: return parser;
0482: }
0483:
0484: /**
0485: * The random access object for the documents data. Be aware that using the
0486: * IRandomAccess after it is closed will throw an IOException.
0487: *
0488: * @return The random access object for the documents data.
0489: */
0490: public IRandomAccess getRandomAccess() {
0491: return randomAccess;
0492: }
0493:
0494: /**
0495: * The documents security handler
0496: *
0497: * @return The documents security handler
0498: */
0499: public ISystemSecurityHandler getSystemSecurityHandler() {
0500: return systemSecurityHandler;
0501: }
0502:
0503: public COSTrailer getTrailer() {
0504: return (COSTrailer) COSTrailer.META
0505: .createFromCos(cosGetTrailer());
0506: }
0507:
0508: /**
0509: * The version of the PDF spec for this document
0510: *
0511: * @return The version of the PDF spec for this document
0512: */
0513: public String getVersion() {
0514: // todo 1 @mit fix version
0515: return getDocType().toString();
0516: }
0517:
0518: /**
0519: * The write mode to be used when the document is written the next time. If
0520: * defined this overrides any hint that is used when saving the document.
0521: * The write mode is reset after each "save".
0522: *
0523: * @return The write mode to be used when the document is written.
0524: */
0525: public EnumWriteMode getWriteModeHint() {
0526: return writeModeHint;
0527: }
0528:
0529: /**
0530: * The most recent STXrefSection of the document.
0531: *
0532: * @return The most recent STXrefSection of the document.
0533: */
0534: public STXRefSection getXRefSection() {
0535: return xRefSection;
0536: }
0537:
0538: public void incrementalGarbageCollect() {
0539: final Set unknown = new HashSet(getChanges());
0540: COSObjectWalkerDeep stripper = new COSObjectWalkerDeep(false) {
0541: public Object visitFromIndirectObject(COSIndirectObject io)
0542: throws COSVisitorException {
0543: unknown.remove(io);
0544: return super .visitFromIndirectObject(io);
0545: }
0546: };
0547: try {
0548: cosGetTrailer().accept(stripper);
0549: } catch (COSVisitorException e) {
0550: // won't happen
0551: }
0552: getChanges().removeAll(unknown);
0553: }
0554:
0555: /**
0556: * Load the encryption parameters and initialize the security handler
0557: * context.
0558: *
0559: * @throws IOException
0560: *
0561: */
0562: protected void initEncryption() throws IOException {
0563: systemSecurityHandler = null;
0564: COSEncryption encryption = COSEncryption
0565: .getEncryptionIn(getXRefSection());
0566: if (encryption != null) {
0567: systemSecurityHandler = SystemSecurityHandler
0568: .create(encryption);
0569: if (systemSecurityHandler == null) {
0570: throw new COSRuntimeException(
0571: "Unsupported encryption algorithm"); //$NON-NLS-1$
0572: }
0573: try {
0574: systemSecurityHandler.init(this , encryption);
0575: // force authentication early, /AuthEvent not yet supported
0576: systemSecurityHandler.authenticate(PasswordProvider
0577: .get());
0578: } catch (COSSecurityException e) {
0579: IOException ioe = new IOException(e.getMessage());
0580: ioe.initCause(e);
0581: throw ioe;
0582: }
0583: }
0584: }
0585:
0586: /**
0587: * Initialize the document from its data.
0588: *
0589: * @throws IOException
0590: * @throws COSLoadException
0591: */
0592: protected void initializeFromLocator() throws IOException,
0593: COSLoadException {
0594: parser = new COSDocumentParser(this );
0595: streamLoad();
0596: }
0597:
0598: /**
0599: * Initialize a new empty document
0600: */
0601: protected void initializeFromScratch(STDocType pDocType) {
0602: setDocType(pDocType);
0603: String name = createName(getDocType().getTypeName());
0604: locator = new TransientLocator(name, pDocType.getTypeName());
0605: parser = new COSDocumentParser(this );
0606: setXRefSection(new STTrailerXRefSection(this ));
0607: nextKey = new COSObjectKey(0, 0);
0608: cosGetTrailer().put(COSTrailer.DK_Root,
0609: COSCatalog.META.createNew().cosGetDict());
0610: setDirty(true);
0611: }
0612:
0613: public boolean isClosed() {
0614: return closed;
0615: }
0616:
0617: /**
0618: * <code>true</code> if this has been changed.
0619: *
0620: * @return <code>true</code> if this has been changed.
0621: */
0622: public boolean isDirty() {
0623: return dirty;
0624: }
0625:
0626: /**
0627: * @return if the document has an {@link ISystemSecurityHandler}
0628: */
0629: public boolean isEncrypted() {
0630: return getSystemSecurityHandler() != null;
0631: }
0632:
0633: public boolean isNew() {
0634: return (getXRefSection().getOffset() == -1)
0635: && (getXRefSection().getPrevious() == null);
0636: }
0637:
0638: /**
0639: * <code>true</code> if this is read only.
0640: *
0641: * @return <code>true</code> if this is read only.
0642: */
0643: public boolean isReadOnly() {
0644: return (getRandomAccess() == null)
0645: || getRandomAccess().isReadOnly();
0646: }
0647:
0648: /**
0649: * <code>true</code> if this has only streamed xref sections.
0650: *
0651: * @return <code>true</code> if this has only streamed xref sections.
0652: */
0653: public boolean isStreamed() {
0654: if (getXRefSection() != null) {
0655: return getXRefSection().isStreamed();
0656: }
0657: return false;
0658: }
0659:
0660: /**
0661: * Load a COSObject from the documents data.
0662: *
0663: * @param ref
0664: * The object reference to be loaded.
0665: * @throws IOException
0666: * @throws COSLoadException
0667: */
0668: public COSObject load(COSIndirectObject ref) throws IOException,
0669: COSLoadException {
0670: int objectNumber = ref.getKey().getObjectNumber();
0671: return load(objectNumber);
0672: }
0673:
0674: protected COSObject load(int objectNumber) throws IOException,
0675: COSLoadException {
0676: synchronized (getAccessLock()) {
0677: return getXRefSection().load(objectNumber,
0678: getSystemSecurityHandler());
0679: }
0680: }
0681:
0682: public void loadAll() throws IOException, COSLoadException {
0683: synchronized (getAccessLock()) {
0684: for (int i = 0; i < getXRefSection().getSize(); i++) {
0685: getXRefSection().load(i, getSystemSecurityHandler());
0686: }
0687: }
0688: }
0689:
0690: /**
0691: * The number of objects currently loaded.
0692: *
0693: * @return The number of objects currently loaded.
0694: */
0695: public int loadedSize() {
0696: int result = 0;
0697: for (Iterator it = getKeyToObject().values().iterator(); it
0698: .hasNext();) {
0699: COSIndirectObject ref = (COSIndirectObject) it.next();
0700: if (!ref.isSwapped()) {
0701: result++;
0702: }
0703: }
0704: return result;
0705: }
0706:
0707: /**
0708: * An iterator on the indirect objects of the storage layer document. This
0709: * includes garbage and purely technical objects like x ref streams.
0710: *
0711: * @return An iterator on the indirect objects of the storage layer
0712: * document. This includes garbage and purely technical objects like
0713: * x ref streams.
0714: */
0715: public Iterator objects() {
0716: return new Iterator() {
0717: int i = 1;
0718:
0719: public boolean hasNext() {
0720: return i < getXRefSection().getSize();
0721: }
0722:
0723: public Object next() {
0724: if (!hasNext()) {
0725: throw new NoSuchElementException(""); //$NON-NLS-1$
0726: }
0727: COSObjectKey key = new COSObjectKey(i++, 0);
0728: return getObjectReference(key);
0729: }
0730:
0731: public void remove() {
0732: throw new UnsupportedOperationException(
0733: "remove not supported"); //$NON-NLS-1$
0734: }
0735: };
0736: }
0737:
0738: /**
0739: * @throws IOException
0740: *
0741: */
0742: protected void open() throws IOException {
0743: synchronized (getAccessLock()) {
0744: if ((randomAccess != null) && !isClosed()) {
0745: throw new IllegalStateException(
0746: "can't open an open document"); //$NON-NLS-1$
0747: }
0748: setRandomAccess(createRandomAccess(getLocator()));
0749: }
0750: }
0751:
0752: /**
0753: * Reparses the XREF sections without actually instantiating. Used for
0754: * collecting errors on XREF level
0755: *
0756: * @throws IOException
0757: * @throws COSLoadException
0758: */
0759: public void reparseFromLocator() throws IOException,
0760: COSLoadException {
0761: synchronized (getAccessLock()) {
0762: int offset = getParser().searchLastStartXRef(
0763: getRandomAccess());
0764: AbstractXRefParser xRefParser;
0765: if (getParser().isTokenXRefAt(getRandomAccess(), offset)) {
0766: xRefParser = new XRefTrailerParser(this , getParser());
0767: } else {
0768: xRefParser = new XRefStreamParser(this , getParser());
0769: }
0770: getRandomAccess().seek(offset);
0771: xRefParser.parse(getRandomAccess());
0772: }
0773: }
0774:
0775: /**
0776: * Assign a new locator to the document.
0777: * <p>
0778: * The documents data is completely copied to the new location.
0779: *
0780: * @param newLocator
0781: * The new locator for the documents data.
0782: *
0783: * @throws IOException
0784: */
0785: protected void replaceLocator(ILocator newLocator)
0786: throws IOException {
0787: synchronized (getAccessLock()) {
0788: if (newLocator.equals(getLocator())) {
0789: return;
0790: }
0791: ILocator oldLocator = getLocator();
0792: IRandomAccess oldRandomAccess = getRandomAccess();
0793: try {
0794: setLocator(newLocator);
0795: setRandomAccess(null);
0796: open();
0797: IRandomAccess newRandomAccess = getRandomAccess();
0798: if (newRandomAccess.isReadOnly()) {
0799: throw new FileNotFoundException();
0800: }
0801: if (newRandomAccess.getLength() > 0) {
0802: newRandomAccess.setLength(0);
0803: }
0804: if (oldRandomAccess != null) {
0805: InputStream is = oldRandomAccess.asInputStream();
0806: OutputStream os = newRandomAccess.asOutputStream();
0807: oldRandomAccess.seek(0);
0808: StreamTools.copyStream(is, false, os, false);
0809: }
0810: StreamTools.close(oldRandomAccess);
0811: } catch (Exception e) {
0812: // undo changes
0813: StreamTools.close(getRandomAccess());
0814: setLocator(oldLocator);
0815: setRandomAccess(oldRandomAccess);
0816: }
0817: }
0818: }
0819:
0820: public void restore(ILocator newLocator) throws IOException,
0821: COSLoadException {
0822: synchronized (getAccessLock()) {
0823: if (newLocator.equals(getLocator())) {
0824: return;
0825: }
0826: IRandomAccess oldRandomAccess = getRandomAccess();
0827: StreamTools.close(oldRandomAccess);
0828: setRandomAccess(null);
0829: setLocator(newLocator);
0830: changes.clear();
0831: keyToObject.clear();
0832: closed = false;
0833: dirty = false;
0834: streamLoad();
0835: }
0836: getDoc().triggerChangedAll();
0837: }
0838:
0839: public void save() throws IOException {
0840: save(getLocator(), null);
0841: }
0842:
0843: public void save(ILocator pLocator) throws IOException {
0844: save(pLocator, null);
0845: }
0846:
0847: public void save(ILocator pLocator, Map options) throws IOException {
0848: // options could be null, when called from save(), even if a locator
0849: // exists.
0850: if (options == null) {
0851: options = new HashMap();
0852: }
0853: if ((pLocator != null) && (pLocator != getLocator())) {
0854: replaceLocator(pLocator);
0855: }
0856: boolean incremental = true;
0857: EnumWriteMode writeMode = doc.getWriteModeHint();
0858: // reset write mode
0859: doc.setWriteModeHint(EnumWriteMode.UNDEFINED);
0860: if (writeMode.isUndefined()) {
0861: Object tempHint = options.get(OPTION_WRITEMODEHINT);
0862: if (tempHint instanceof EnumWriteMode) {
0863: writeMode = (EnumWriteMode) tempHint;
0864: }
0865: }
0866: if (writeMode.isFull()) {
0867: incremental = false;
0868: }
0869: IRandomAccess tempRandomAccess = getRandomAccess();
0870: if (tempRandomAccess == null) {
0871: throw new IOException("nowhere to write to"); //$NON-NLS-1$
0872: }
0873: if (tempRandomAccess.isReadOnly()) {
0874: throw new FileNotFoundException("destination is read only"); //$NON-NLS-1$
0875: }
0876: COSWriter writer = new COSWriter(tempRandomAccess,
0877: getSystemSecurityHandler());
0878: writer.setIncremental(incremental);
0879: writer.writeDocument(this );
0880: }
0881:
0882: protected void setClosed(boolean closed) {
0883: this .closed = closed;
0884: }
0885:
0886: /**
0887: * Set the change flag of this.
0888: *
0889: * @param dirty
0890: * <code>true</code> if this should be marked as changed
0891: */
0892: public void setDirty(boolean dirty) {
0893: this .dirty = dirty;
0894: if (!dirty) {
0895: changes.clear();
0896: }
0897: }
0898:
0899: public void setDoc(COSDocument doc) {
0900: this .doc = doc;
0901: getXRefSection().setCOSDoc(getDoc());
0902: }
0903:
0904: protected void setDocType(STDocType docType) {
0905: this .docType = docType;
0906: }
0907:
0908: protected void setLocator(ILocator locator) {
0909: this .locator = locator;
0910: }
0911:
0912: /**
0913: * Rename the document locally.
0914: * <p>
0915: * This has no effect if a locator is present.
0916: *
0917: * @param name
0918: * The new local name of this
0919: */
0920: public void setName(String name) {
0921: if (getLocator() instanceof TransientLocator) {
0922: ((TransientLocator) getLocator()).setLocalName(name);
0923: }
0924: }
0925:
0926: /**
0927: * Assign the IRAndomAccess to the documetns data.
0928: *
0929: * @param randomAccess
0930: * the IRAndomAccess to the documetns data.
0931: */
0932: protected void setRandomAccess(IRandomAccess randomAccess) {
0933: this .randomAccess = randomAccess;
0934: }
0935:
0936: /**
0937: * The write mode to be used when the document is written the next time. If
0938: * defined this overrides any hint that is used when saving the document.
0939: * The write mode is reset after each "save".
0940: *
0941: * @param writeMode
0942: * The write mode to be used when the document is written.
0943: */
0944: public void setWriteModeHint(EnumWriteMode writeMode) {
0945: if (writeMode == null) {
0946: throw new IllegalArgumentException(
0947: "write mode can't be null"); //$NON-NLS-1$
0948: }
0949: this .writeModeHint = writeMode;
0950: }
0951:
0952: /**
0953: * Attach the most recent x ref section to the document.
0954: *
0955: * @param xRefSection
0956: * The x ref section representing the most recent document
0957: * changes.
0958: */
0959: public void setXRefSection(STXRefSection xRefSection) {
0960: this .xRefSection = xRefSection;
0961: if (getDoc() != null) {
0962: getXRefSection().setCOSDoc(getDoc());
0963: }
0964: }
0965:
0966: protected void streamLoad() throws IOException, COSLoadException {
0967: try {
0968: open();
0969: STXRefSection initialXRefSection;
0970: setDocType(getParser().parseHeader(getRandomAccess()));
0971: try {
0972: int offset = getParser().searchLastStartXRef(
0973: getRandomAccess());
0974: AbstractXRefParser xRefParser;
0975: if (getParser()
0976: .isTokenXRefAt(getRandomAccess(), offset)) {
0977: xRefParser = new XRefTrailerParser(this ,
0978: getParser());
0979: } else {
0980: xRefParser = new XRefStreamParser(this , getParser());
0981: }
0982: getRandomAccess().seek(offset);
0983: initialXRefSection = xRefParser
0984: .parse(getRandomAccess());
0985: } catch (Exception ex) {
0986: Log.log(Level.FINEST, "error parsing " //$NON-NLS-1$
0987: + getLocator().getFullName(), ex);
0988: // TODO 2 log warning, trailer can't be parsed
0989: initialXRefSection = new XRefFallbackParser(this ,
0990: getParser()).parse(getRandomAccess());
0991: }
0992: setXRefSection(initialXRefSection);
0993: nextKey = new COSObjectKey(
0994: initialXRefSection.getSize() - 1, 0);
0995: initEncryption();
0996: checkConsistency();
0997: } catch (IOException e) {
0998: try {
0999: close();
1000: } catch (IOException ce) {
1001: // ignore
1002: }
1003: throw e;
1004: } catch (COSLoadException e) {
1005: try {
1006: close();
1007: } catch (IOException ce) {
1008: // ignore
1009: }
1010: throw e;
1011: }
1012: }
1013:
1014: public void updateModificationDate() {
1015: COSDictionary infoDict = cosGetTrailer()
1016: .get(COSTrailer.DK_Info).asDictionary();
1017: if (infoDict == null) {
1018: return;
1019: }
1020: infoDict.put(COSInfoDict.DK_ModDate, new CDSDate()
1021: .cosGetObject());
1022: }
1023:
1024: /**
1025: * <code>true</code> if this document is linearized.
1026: * <p>
1027: * When linearized reading is truly implemented, this check should be made
1028: * using the document length instead for performance reasons.
1029: *
1030: * @return <code>true</code> if this document is linearized.
1031: */
1032: public boolean isLinearized() {
1033: return getLinearizedDict() != null;
1034: }
1035:
1036: /**
1037: * The /Linearized dictionary of the document. The /Linearized dictionary is
1038: * represented by the first entry in the (logically) first XRef section.
1039: * <p>
1040: * Note that this method may NOT return a dictionary even if the document
1041: * contains a /Linearized dictionary as the first object. This is the case
1042: * when the document was linearized and was written with an incremental
1043: * change so that the linearization is obsolete.
1044: *
1045: * @return The valid /Linearized dictionary of the document.
1046: */
1047: public COSDictionary getLinearizedDict() {
1048: int objectNumber = 0;
1049: Iterator it = getXRefSection().entryIterator();
1050: while (it.hasNext()) {
1051: STXRefEntry entry = (STXRefEntry) it.next();
1052: if (entry.getObjectNumber() != 0) {
1053: objectNumber = entry.getObjectNumber();
1054: break;
1055: }
1056: }
1057: try {
1058: COSDictionary result = load(objectNumber).asDictionary();
1059: if (result != null) {
1060: COSObject version = result.get(COSName
1061: .constant("Linearized"));
1062: if (!version.isNull()) {
1063: return result;
1064: }
1065: }
1066: } catch (Exception e) {
1067: // ignore
1068: }
1069: return null;
1070: }
1071: }
|