/*
* Copyright 2006-2017 ICEsoft Technologies Canada Corp.
*
* 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.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* @author Mark Collette
* @since 2.0
*/
public class CrossReference {
private static final Logger logger =
Logger.getLogger(CrossReference.class.toString());
public static final Name SIZE_KEY = new Name("Size");
public static final Name INDEX_KEY = new Name("Index");
public static final Name W_KEY = new Name("W");
/**
* Map of all the objects in reference by the CrossReference table. Ojbects
* are retrieved by object number.
*/
private ConcurrentHashMap<Number, Entry> 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 xrefPrevious != null, Then just use it
* If xrefPrevious == null And pTrailer == null,
* Then we can't do anything
* If xrefPrevious == null And pTrailer != null,
* Then use pTrailer to setup xrefPrevious
*/
private PTrailer pTrailer;
private CrossReference xrefPrevious;
private CrossReference xrefPeer;
//
private boolean bIsCrossReferenceTable;
private boolean bHaveTriedLoadingPrevious;
private boolean bHaveTriedLoadingPeer;
// offset error for simple file error issue.
protected int offset;
public CrossReference() {
hObjectNumber2Entry = new ConcurrentHashMap<Number, Entry>(4096);
}
public void setTrailer(PTrailer trailer) {
pTrailer = trailer;
}
/**
* Starts the parsing of an xRef table entries as found when using the
* Parser to Parse out an object via Parser.getObject().
* <br>
* All entries are taken into consideration except for ones that are marked
* free.
*
* @param parser content parser
*/
public void addXRefTableEntries(Parser parser) {
bIsCrossReferenceTable = true;
try {
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 filePosition = 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, filePosition, generationNum);
}
// ignore any free entries.
else if (usedOrFree == 'f') { // Free
// check for the first entry 0000000000 65535 f and
// a object range where the first entry isn't zero. The
// code below will treat the first entry as zero and then
// start counting.
if (startingObjectNumber > 0 &&
filePosition == 0 && generationNum == 65535) {
// offset the count so we start counting after the zeroed entry
currNumber--;
}
// addFreeEntry(currNumber, (int) filePosition, generationNum);
}
currNumber++;
}
}
} catch (IOException e) {
logger.log(Level.SEVERE, "Error parsing xRef table entries.", e);
}
}
/**
* Once a XRef stream is found, the decoded streamInput is itereated over
* to build out the Xref structure.
*
* @param library The Document's Library
* @param xrefStreamHash Dictionary for XRef stream
* @param streamInput Decoded stream bytes for XRef stream
*/
@SuppressWarnings("unchecked")
public void addXRefStreamEntries(Library library, HashMap xrefStreamHash, InputStream streamInput) {
try {
// number +1 represented the highest object number.
int size = library.getInt(xrefStreamHash, SIZE_KEY);
// pair of integers for each subsection in this section. The first
// int is the first object number in this section and the second
// is the number of entries.
List<Number> objNumAndEntriesCountPairs =
(List) library.getObject(xrefStreamHash, INDEX_KEY);
if (objNumAndEntriesCountPairs == null) {
objNumAndEntriesCountPairs = new ArrayList<Number>(2);
objNumAndEntriesCountPairs.add(0);
objNumAndEntriesCountPairs.add(size);
}
// three int's: field values, x,y and z bytes in length.
List fieldSizesVec = (List) library.getObject(xrefStreamHash, W_KEY);
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();
}
// not doing anything with PREV.
int fieldTypeSize = fieldSizes[0];
int fieldTwoSize = fieldSizes[1];
int fieldThreeSize = fieldSizes[2];
// parse out the object data.
for (int xrefSubsection = 0; xrefSubsection < objNumAndEntriesCountPairs.size(); xrefSubsection += 2) {
int startingObjectNumber = objNumAndEntriesCountPairs.get(xrefSubsection).intValue();
int entriesCount = 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);
// used object but not compressed
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);
}
// entries define compress objects.
else if (entryType == Entry.TYPE_COMPRESSED) {
int objectNumberOfContainingObjectStream = Utils.readIntWithVaryingBytesBE(
streamInput, fieldTwoSize);
int indexWithinObjectStream = Utils.readIntWithVaryingBytesBE(
streamInput, fieldThreeSize);
addCompressedEntry(
objectNumber, objectNumberOfContainingObjectStream, indexWithinObjectStream);
}
// free objects, no used.
else if (entryType == Entry.TYPE_FREE) {
// we do nothing but we still need to move the cursor.
Utils.readIntWithVaryingBytesBE(
streamInput, fieldTwoSize);
Utils.readIntWithVaryingBytesBE(
streamInput, fieldThreeSize);
}
}
}
} catch (IOException e) {
logger.log(Level.SEVERE, "Error parsing xRef stream entries.", e);
}
}
public Entry getEntryForObject(Integer objectNumber) {
Entry entry = hObjectNumber2Entry.get(objectNumber);
if (entry != null)
return entry;
/// fall back code to look for another xref table.
if (bIsCrossReferenceTable && !bHaveTriedLoadingPeer &&
xrefPeer == null && pTrailer != null) {
// Lazily load xrefPeer, using pTrailer
pTrailer.loadXRefStmIfApplicable();
xrefPeer = pTrailer.getCrossReferenceStream();
bHaveTriedLoadingPeer = true;
}
if (xrefPeer != null) {
entry = xrefPeer.getEntryForObject(objectNumber);
if (entry != null)
return entry;
}
if (!bHaveTriedLoadingPrevious &&
xrefPrevious == null && pTrailer != null) {
// Lazily load xrefPrevious, using pTrailer
pTrailer.onDemandLoadAndSetupPreviousTrailer();
bHaveTriedLoadingPrevious = true;
}
if (xrefPrevious != null) {
entry = xrefPrevious.getEntryForObject(objectNumber);
if (entry != null)
return entry;
}
return entry;
}
public void addToEndOfChainOfPreviousXRefs(CrossReference prev) {
if (xrefPrevious == null)
xrefPrevious = prev;
else
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);
hObjectNumber2Entry.put(objectNumber, entry);
}
protected void addCompressedEntry(int objectNumber, int objectNumberOfContainingObjectStream, int indexWithinObjectStream) {
CompressedEntry entry = new CompressedEntry(objectNumber, objectNumberOfContainingObjectStream, indexWithinObjectStream);
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 Type;
private int objectNumber;
Entry(int type, int objectNumber) {
Type = type;
this.objectNumber = objectNumber;
}
int getType() {
return Type;
}
int getObjectNumber() {
return objectNumber;
}
}
public static class FreeEntry extends Entry {
private int nextFreeObjectNumber;
private int generationNumberIfReused;
FreeEntry(int objectNumber, int nextFreeObjectNumber, int generationNumberIfReused) {
super(TYPE_FREE, objectNumber);
this.nextFreeObjectNumber = nextFreeObjectNumber;
this.generationNumberIfReused = generationNumberIfReused;
}
public int getNextFreeObjectNumber() {
return nextFreeObjectNumber;
}
public int getGenerationNumberIfReused() {
return generationNumberIfReused;
}
}
public class UsedEntry extends Entry {
private long filePositionOfObject;
private int generationNumber;
UsedEntry(int objectNumber, long filePositionOfObject, int generationNumber) {
super(TYPE_USED, objectNumber);
this.filePositionOfObject = filePositionOfObject;
this.generationNumber = generationNumber;
}
public long getFilePositionOfObject() {
return filePositionOfObject + offset;
}
public int getGenerationNumber() {
return generationNumber;
}
public void setFilePositionOfObject(long filePositionOfObject) {
this.filePositionOfObject = filePositionOfObject;
}
}
public static class CompressedEntry extends Entry {
private int objectNumberOfContainingObjectStream;
private int indexWithinObjectStream;
CompressedEntry(int objectNumber, int objectNumberOfContainingObjectStream, int indexWithinObjectStream) {
super(TYPE_COMPRESSED, objectNumber);
this.objectNumberOfContainingObjectStream = objectNumberOfContainingObjectStream;
this.indexWithinObjectStream = indexWithinObjectStream;
}
public int getObjectNumberOfContainingObjectStream() {
return objectNumberOfContainingObjectStream;
}
public int getIndexWithinObjectStream() {
return indexWithinObjectStream;
}
}
public void setOffset(int offset) {
this.offset = offset;
}
}