/*
* Copyright 2006-2012 ICEsoft Technologies Inc.
*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 org.icepdf.core.pobjects;
import org.icepdf.core.util.Library;
import org.icepdf.core.util.Parser;
import org.icepdf.core.util.Utils;
import java.io.IOException;
import java.io.InputStream;
import java.util.Hashtable;
import java.util.Vector;
import java.util.logging.Logger;
import java.util.logging.Level;
/**
* @author Mark Collette
* @since 2.0
*/
public class CrossReference {
private static final Logger logger =
Logger.getLogger(CrossReference.class.toString());
// private Vector<Entry> m_vXRefEntries;
private Hashtable<Number, Entry> m_hObjectNumber2Entry;
/**
* In a Linearized PDF, we don't want to load all Trailers and their XRefs
* upfront, but would rather load the first upfront, and then lazily load
* the rest.
* If m_xrefPrevious != null, Then just use it
* If m_xrefPrevious == null And m_PTrailer == null,
* Then we can't do anything
* If m_xrefPrevious == null And m_PTrailer != null,
* Then use m_PTrailer to setup m_xrefPrevious
*/
private PTrailer m_PTrailer;
private CrossReference m_xrefPrevious;
private CrossReference m_xrefPeer;
private boolean m_bIsCrossReferenceTable;
private boolean m_bHaveTriedLoadingPrevious;
private boolean m_bHaveTriedLoadingPeer;
public CrossReference() {
// m_vXRefEntries = new Vector<Entry>(4096);
m_hObjectNumber2Entry = new Hashtable<Number, Entry>(4096);
}
public void setTrailer(PTrailer trailer) {
m_PTrailer = trailer;
}
public void addXRefTableEntries(Parser parser) {
m_bIsCrossReferenceTable = true;
try {
//System.out.println("Starting to read xref table : " + (new java.util.Date()));
//int countOfAllEntries = 0;
while (true) {
Object startingObjectNumberOrTrailer = parser.getNumberOrStringWithMark(16);
if (!(startingObjectNumberOrTrailer instanceof Number)) {
parser.ungetNumberOrStringWithReset();
break;
}
int startingObjectNumber = ((Number) startingObjectNumberOrTrailer).intValue();
int numEntries = ((Number) parser.getToken()).intValue();
int currNumber = startingObjectNumber;
for (int i = 0; i < numEntries; i++) {
long tenDigitNum = parser.getIntSurroundedByWhitespace(); // ( (Number) getToken() ).longValue();
int generationNum = parser.getIntSurroundedByWhitespace(); // ( (Number) getToken() ).intValue();
char usedOrFree = parser.getCharSurroundedByWhitespace(); // ( (String) getToken() ).charAt( 0 );
if (usedOrFree == 'n') // Used
addUsedEntry(currNumber, tenDigitNum, generationNum);
else if (usedOrFree == 'f') // Free
addFreeEntry(currNumber, (int) tenDigitNum, generationNum);
//System.out.println("xref entry " + (i+1) + " of " + numEntries + " tenDigitNum: " + tenDigitNum + ", generationNum: " + generationNum + ", usedOrFree: " + usedOrFree + ", objNum: " + currNumber);
currNumber++;
//countOfAllEntries++;
}
}
//System.out.println("Done reading xref table entries : " + (new java.util.Date()) + " count: " + countOfAllEntries);
}
catch (IOException e) {
logger.log(Level.SEVERE, "Error parsing xRef table entries.", e);
}
}
/**
* @param library The Document's Library
* @param xrefStreamHash Dictionary for XRef stream
* @param streamInput Decoded stream bytes for XRef stream
*/
public void addXRefStreamEntries(Library library, Hashtable xrefStreamHash, InputStream streamInput) {
//System.out.println("Starting to read xref stream : " + (new java.util.Date()));
//int countOfAllEntries = 0;
try {
int size = library.getInt(xrefStreamHash, "Size");
Vector<Number> objNumAndEntriesCountPairs =
(Vector) library.getObject(xrefStreamHash, "Index");
if (objNumAndEntriesCountPairs == null) {
objNumAndEntriesCountPairs = new Vector<Number>(2);
objNumAndEntriesCountPairs.add(0);
objNumAndEntriesCountPairs.add(size);
}
Vector fieldSizesVec = (Vector) library.getObject(xrefStreamHash, "W");
int[] fieldSizes = null;
if (fieldSizesVec != null) {
fieldSizes = new int[fieldSizesVec.size()];
for (int i = 0; i < fieldSizesVec.size(); i++)
fieldSizes[i] = ((Number) fieldSizesVec.get(i)).intValue();
}
int fieldTypeSize = fieldSizes[0];
int fieldTwoSize = fieldSizes[1];
int fieldThreeSize = fieldSizes[2];
for (int xrefSubsection = 0; xrefSubsection < objNumAndEntriesCountPairs.size(); xrefSubsection += 2) {
int startingObjectNumber = ((Number) objNumAndEntriesCountPairs.get(xrefSubsection)).intValue();
int entriesCount = ((Number) objNumAndEntriesCountPairs.get(xrefSubsection + 1)).intValue();
int afterObjectNumber = startingObjectNumber + entriesCount;
for (int objectNumber = startingObjectNumber; objectNumber < afterObjectNumber; objectNumber++) {
int entryType = Entry.TYPE_USED; // Default value is 1
if (fieldTypeSize > 0)
entryType = Utils.readIntWithVaryingBytesBE(streamInput, fieldTypeSize);
if (entryType == Entry.TYPE_FREE) {
int nextFreeObjectNumber = Utils.readIntWithVaryingBytesBE(
streamInput, fieldTwoSize);
int generationNumberIfReused = Utils.readIntWithVaryingBytesBE(
streamInput, fieldThreeSize);
addFreeEntry(objectNumber, nextFreeObjectNumber, generationNumberIfReused);
} else if (entryType == Entry.TYPE_USED) {
long filePositionOfObject = Utils.readLongWithVaryingBytesBE(
streamInput, fieldTwoSize);
int generationNumber = 0; // Default value is 0
if (fieldThreeSize > 0) {
generationNumber = Utils.readIntWithVaryingBytesBE(
streamInput, fieldThreeSize);
}
addUsedEntry(objectNumber, filePositionOfObject, generationNumber);
} else if (entryType == Entry.TYPE_COMPRESSED) {
int objectNumberOfContainingObjectStream = Utils.readIntWithVaryingBytesBE(
streamInput, fieldTwoSize);
int indexWithinObjectStream = Utils.readIntWithVaryingBytesBE(
streamInput, fieldThreeSize);
addCompressedEntry(
objectNumber, objectNumberOfContainingObjectStream, indexWithinObjectStream);
}
//countOfAllEntries++;
}
}
}
catch (IOException e) {
logger.log(Level.SEVERE, "Error parsing xRef stream entries.", e);
}
//System.out.println("Done reading xref stream entries : " + (new java.util.Date()) + " count: " + countOfAllEntries);
}
public Entry getEntryForObject(Integer objectNumber) {
Entry entry = m_hObjectNumber2Entry.get(objectNumber);
if (entry != null)
return entry;
if (m_bIsCrossReferenceTable && !m_bHaveTriedLoadingPeer &&
m_xrefPeer == null && m_PTrailer != null) {
// Lazily load m_xrefPeer, using m_PTrailer
m_PTrailer.loadXRefStmIfApplicable();
m_xrefPeer = m_PTrailer.getCrossReferenceStream();
m_bHaveTriedLoadingPeer = true;
}
if (m_xrefPeer != null) {
entry = m_xrefPeer.getEntryForObject(objectNumber);
if (entry != null)
return entry;
}
if (!m_bHaveTriedLoadingPrevious &&
m_xrefPrevious == null && m_PTrailer != null) {
// Lazily load m_xrefPrevious, using m_PTrailer
m_PTrailer.onDemandLoadAndSetupPreviousTrailer();
m_bHaveTriedLoadingPrevious = true;
}
if (m_xrefPrevious != null) {
entry = m_xrefPrevious.getEntryForObject(objectNumber);
if (entry != null)
return entry;
}
return entry;
}
public void addToEndOfChainOfPreviousXRefs(CrossReference prev) {
if (m_xrefPrevious == null)
m_xrefPrevious = prev;
else
m_xrefPrevious.addToEndOfChainOfPreviousXRefs(prev);
}
protected void addFreeEntry(int objectNumber, int nextFreeObjectNumber, int generationNumberIfReused) {
FreeEntry entry = new FreeEntry(objectNumber, nextFreeObjectNumber, generationNumberIfReused);
// m_vXRefEntries.add(entry);
}
protected void addUsedEntry(int objectNumber, long filePositionOfObject, int generationNumber) {
UsedEntry entry = new UsedEntry(objectNumber, filePositionOfObject, generationNumber);
// m_vXRefEntries.add(entry);
m_hObjectNumber2Entry.put(objectNumber, entry);
}
protected void addCompressedEntry(int objectNumber, int objectNumberOfContainingObjectStream, int indexWithinObjectStream) {
CompressedEntry entry = new CompressedEntry(objectNumber, objectNumberOfContainingObjectStream, indexWithinObjectStream);
// m_vXRefEntries.add(entry);
m_hObjectNumber2Entry.put(objectNumber, entry);
}
public static class Entry {
public static final int TYPE_FREE = 0;
public static final int TYPE_USED = 1;
public static final int TYPE_COMPRESSED = 2;
private int m_iType;
private int m_iObjectNumber;
Entry(int type, int objectNumber) {
m_iType = type;
m_iObjectNumber = objectNumber;
}
int getType() {
return m_iType;
}
int getObjectNumber() {
return m_iObjectNumber;
}
}
public static class FreeEntry extends Entry {
private int m_iNextFreeObjectNumber;
private int m_iGenerationNumberIfReused;
FreeEntry(int objectNumber, int nextFreeObjectNumber, int generationNumberIfReused) {
super(TYPE_FREE, objectNumber);
m_iNextFreeObjectNumber = nextFreeObjectNumber;
m_iGenerationNumberIfReused = generationNumberIfReused;
}
public int getNextFreeObjectNumber() {
return m_iNextFreeObjectNumber;
}
public int getGenerationNumberIfReused() {
return m_iGenerationNumberIfReused;
}
}
public static class UsedEntry extends Entry {
private long m_lFilePositionOfObject;
private int m_iGenerationNumber;
UsedEntry(int objectNumber, long filePositionOfObject, int generationNumber) {
super(TYPE_USED, objectNumber);
m_lFilePositionOfObject = filePositionOfObject;
m_iGenerationNumber = generationNumber;
}
public long getFilePositionOfObject() {
return m_lFilePositionOfObject;
}
public int getGenerationNumber() {
return m_iGenerationNumber;
}
}
public static class CompressedEntry extends Entry {
private int m_iObjectNumberOfContainingObjectStream;
private int m_iIndexWithinObjectStream;
CompressedEntry(int objectNumber, int objectNumberOfContainingObjectStream, int indexWithinObjectStream) {
super(TYPE_COMPRESSED, objectNumber);
m_iObjectNumberOfContainingObjectStream = objectNumberOfContainingObjectStream;
m_iIndexWithinObjectStream = indexWithinObjectStream;
}
public int getObjectNumberOfContainingObjectStream() {
return m_iObjectNumberOfContainingObjectStream;
}
public int getIndexWithinObjectStream() {
return m_iIndexWithinObjectStream;
}
}
}