/*
Copyright 2006-2010 Stefano Chizzolini. http://www.pdfclown.org
Contributors:
* Stefano Chizzolini (original code developer, http://www.stefanochizzolini.it)
This file should be part of the source code distribution of "PDF Clown library"
(the Program): see the accompanying README files for more info.
This Program is free software; you can redistribute it and/or modify it under the terms
of the GNU Lesser General Public License as published by the Free Software Foundation;
either version 3 of the License, or (at your option) any later version.
This Program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY,
either expressed or implied; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the License for more details.
You should have received a copy of the GNU Lesser General Public License along with this
Program (see README files); if not, go to the GNU website (http://www.gnu.org/licenses/).
Redistribution and use, with or without modification, are permitted provided that such
redistributions retain the above copyright notice, license and disclaimer, along with
this list of conditions.
*/
package org.pdfclown.files;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.NoSuchElementException;
import java.util.SortedMap;
import java.util.TreeMap;
import org.pdfclown.objects.PdfDataObject;
import org.pdfclown.objects.PdfIndirectObject;
import org.pdfclown.tokens.XRefEntry;
import org.pdfclown.util.NotImplementedException;
/**
Collection of the <b>alive indirect objects</b> available inside the file.
<h3>Remarks</h3>
<p>According to the PDF spec, <i>indirect object entries may be free
(no data object associated) or in-use (data object associated)</i>.</p>
<p>We can effectively subdivide indirect objects in two possibly-overriding
collections: the <b>original indirect objects</b> (coming from the associated
preexisting file) and the <b>newly-registered indirect objects</b> (coming
from new data objects or original indirect objects manipulated during the
current session).</p>
<p><i>To ensure that the modifications applied to an original indirect object
are committed to being persistent</i> is critical that the modified original
indirect object is newly-registered (practically overriding the original
indirect object).</p>
<p><b>Alive indirect objects</b> encompass all the newly-registered ones plus
not-overridden original ones.</p>
@author Stefano Chizzolini (http://www.stefanochizzolini.it)
@since 0.0.0
@version 0.1.0
*/
public final class IndirectObjects
implements List<PdfIndirectObject>
{
// <class>
// <dynamic>
// <fields>
/**
Associated file.
*/
private File file;
/**
Map of matching references of imported indirect objects.
<p>This collection is used to prevent duplications among imported indirect
objects.</p>
<p><code>Key</code> is the external indirect object hashcode, <code>Value</code> is the
matching internal indirect object.</p>
*/
private Hashtable<Integer,PdfIndirectObject> importedObjects = new Hashtable<Integer,PdfIndirectObject>();
/**
Collection of newly-registered indirect objects.
*/
private TreeMap<Integer,PdfIndirectObject> modifiedObjects = new TreeMap<Integer,PdfIndirectObject>();
/**
Collection of instantiated original indirect objects.
<p>This collection is used as a cache to avoid unconsistent parsing duplications.</p>
*/
private TreeMap<Integer,PdfIndirectObject> wokenObjects = new TreeMap<Integer,PdfIndirectObject>();
/**
Object counter.
*/
private int lastObjectNumber;
/**
Offsets of the original indirect objects inside the associated file (to say:
implicit collection of the original indirect objects).
<p>This information is vital to randomly retrieve the indirect-object persistent
representation inside the associated file.</p>
*/
private SortedMap<Integer,XRefEntry> xrefEntries;
private UpdateModeEnum updateMode = UpdateModeEnum.Manual;
// </fields>
// <constructors>
IndirectObjects(
File file,
SortedMap<Integer,XRefEntry> xrefEntries
)
{
this.file = file;
this.xrefEntries = xrefEntries;
if(this.xrefEntries == null) // No original indirect objects.
{
// Register the leading free-object!
/*
NOTE: Mandatory head of the linked list of free objects
at object number 0 [PDF:1.6:3.4.3].
*/
lastObjectNumber = 0;
modifiedObjects.put(
lastObjectNumber,
new PdfIndirectObject(
this.file,
null,
new XRefEntry(
lastObjectNumber,
XRefEntry.GenerationUnreusable,
0,
XRefEntry.UsageEnum.Free
)
)
);
}
else
{
// Adjust the object counter!
lastObjectNumber = xrefEntries.lastKey();
}
}
// </constructors>
// <interface>
// <public>
public File getFile(
)
{return file;}
/**
Register an <b>internal data object</b>.
<h3>Remarks</h3>
<p>Alternatives:<ul>
<li>To register a modified internal indirect object, use
{@link #set(int,PdfIndirectObject) set(int,PdfIndirectObject)}.</li>
<li>To register an external indirect object, use
{@link #addExternal(PdfIndirectObject) addExternal(PdfIndirectObject)}.</li>
</ul></p>
*/
public PdfIndirectObject add(
PdfDataObject object
)
{
// Wrap the data object inside a new indirect object!
lastObjectNumber++;
PdfIndirectObject indirectObject = new PdfIndirectObject(
file,
object,
new XRefEntry(
lastObjectNumber,
0,
0,
XRefEntry.UsageEnum.InUse
)
);
// Register the object!
modifiedObjects.put(lastObjectNumber,indirectObject);
return indirectObject;
}
/**
Registers and gets an <b>external indirect object</b>.
<h3>Remarks</h3>
<p>External indirect objects come from alien PDF files. <i>This is a powerful
way to import the content of one file into another</i>.</p>
<p>Alternatives:<ul>
<li>To register a modified internal indirect object, use
{@link #set(int,PdfIndirectObject) set(int,PdfIndirectObject)}.</li>
<li>To register an internal data object, use
{@link #add(PdfDataObject) add(PdfDataObject)}.</li></ul></p>
*/
public PdfIndirectObject addExternal(
PdfIndirectObject object
)
{
PdfIndirectObject indirectObject = importedObjects.get(object.hashCode());
// Hasn't the external indirect object been imported yet?
if(indirectObject == null)
{
// Register the clone of the data object corresponding to the external indirect object!
indirectObject = add((PdfDataObject)object.getDataObject().clone(file));
// Keep track of the imported indirect object!
importedObjects.put(object.hashCode(),indirectObject);
}
return indirectObject;
}
public Collection<? extends PdfIndirectObject> addAllExternal(
Collection<? extends PdfIndirectObject> objects
)
{
ArrayList<PdfIndirectObject> addedObjects = new ArrayList<PdfIndirectObject>(objects.size());
for(PdfIndirectObject object : objects)
{addedObjects.add(addExternal(object));}
return addedObjects;
}
// <List>
@Override
public void add(
int index,
PdfIndirectObject object
)
{throw new UnsupportedOperationException();}
@Override
public boolean addAll(
int index,
Collection<? extends PdfIndirectObject> objects
)
{throw new UnsupportedOperationException();}
/**
Gets an indirect object with the specified object number.
@param index Object number of the indirect object to get.
@return Indirect object corresponding to the specified object number.
*/
@Override
public PdfIndirectObject get(
int index
)
{
PdfIndirectObject object = modifiedObjects.get(index);
if(object == null)
{
object = wokenObjects.get(index);
if(object == null)
{
XRefEntry xrefEntry = xrefEntries.get(index);
if(xrefEntry == null)
{
if(index > lastObjectNumber)
return null;
/*
NOTE: The cross-reference table (comprising the original cross-reference section and all update sections)
MUST contain one entry for each object number from 0 to the maximum object number used in the file, even
if one or more of the object numbers in this range do not actually occur in the file.
However, for resilience purposes missing entries are treated as free ones.
*/
xrefEntries.put(
index,
xrefEntry = new XRefEntry(
index,
XRefEntry.GenerationUnreusable,
0,
XRefEntry.UsageEnum.Free
)
);
}
// Awake the object!
/*
NOTE: This operation allows to keep a consistent state across the whole session,
avoiding multiple incoherent instantiations of the same original indirect object.
*/
wokenObjects.put(index, object = new PdfIndirectObject(file, null, xrefEntry));
// Early registration?
if(updateMode == UpdateModeEnum.Automatic)
{update(object);}
}
}
return object;
}
@Override
public int indexOf(
Object object
)
{
// Is this indirect object associated to this file?
if(((PdfIndirectObject)object).getFile() != file)
return -1;
return ((PdfIndirectObject)object).getReference().getObjectNumber();
}
@Override
public int lastIndexOf(
Object object
)
{
/*
NOTE: By definition, there's a bijective relation between indirect objects
and their indices.
*/
return indexOf(object);
}
@Override
public ListIterator<PdfIndirectObject> listIterator(
)
{throw new NotImplementedException();}
@Override
public ListIterator<PdfIndirectObject> listIterator(
int index
)
{throw new NotImplementedException();}
@Override
public PdfIndirectObject remove(
int index
)
{
/*
NOTE: Acrobat 6.0 and later (PDF 1.5+) DO NOT use the free list to recycle object numbers;
new objects are assigned new numbers [PDF:1.6:H.3:16].
According to such an implementation note, we simply mark the removed object as 'not-reusable'
newly-freed entry, neglecting both to add it to the linked list of free entries
and to increment by 1 its generation number.
*/
return update(
new PdfIndirectObject(
file,
null,
new XRefEntry(
index,
XRefEntry.GenerationUnreusable,
0,
XRefEntry.UsageEnum.Free
)
)
);
}
/**
Sets an indirect object with the specified object number.
<h3>Contract</h3>
<ul>
<li>Preconditions:
<ol>
<li><code>index</code> value MUST be between 1 and (size() - 1); index 0
is reserved to the mandatory head of the linked list of free objects [PDF:1.6:3.4.3].</li>
</ol>
</li>
<li>Postconditions:<ol><li>(none).</li></ol></li>
<li>Invariants:<ol><li>(none).</li></ol></li>
<li>Side-effects:<ol><li>(none).</li></ol></li>
</ul>
<h3>Remarks</h3>
<p>This method is currently limited to <b>internal indirect
objects</b>: <i>use it to register modified internal indirect objects only</i>.
If you need to register alternative-type objects, consider the following
methods:</p>
<ul>
<li>to register an <b>external indirect object</b>, use
{@link #addExternal(PdfIndirectObject) addExternal(PdfIndirectObject)}.</li>
<li>to register an <b>internal data object</b>, use
{@link #add(PdfDataObject) add(PdfDataObject)}.</li>
</ul>
@param index Object number of the indirect object to set.
@param object Indirect object to set.
@return Replaced indirect object.
*/
@Override
public PdfIndirectObject set(
int index,
PdfIndirectObject object
)
{throw new UnsupportedOperationException();}
@Override
public List<PdfIndirectObject> subList(
int fromIndex,
int toIndex
)
{throw new NotImplementedException();}
// <Collection>
/**
Registers an <b>external indirect object</b>.
<h3>Remarks</h3>
<p>External indirect objects come from alien PDF files. <i>This is a powerful
way to import the content of one file into another</i>.</p>
<p>Alternatives:<ul>
<li>To register and get an external indirect object, use
{@link #addExternal(PdfIndirectObject) addExternal(PdfIndirectObject)}.</li>
</ul></p>
*/
@Override
public boolean add(
PdfIndirectObject object
)
{
boolean changed = (addExternal(object) != null);
return changed;
}
/**
Registers <b>external indirect objects</b>.
<h3>Remarks</h3>
<p>External indirect objects come from alien PDF files. <i>This is a powerful
way to import the content of one file into another</i>.</p>
<p>Alternatives:<ul>
<li>To register and get external indirect object, use
{@link #addAllExternal(Collection<? extends PdfIndirectObject>) addAllExternal(Collection<? extends PdfIndirectObject>)}.</li>
</ul></p>
*/
@Override
public boolean addAll(
Collection<? extends PdfIndirectObject> objects
)
{
boolean changed = false;
for(PdfIndirectObject object : objects)
{
changed |= (addExternal(object) != null);
}
return changed;
}
@Override
public void clear(
)
{throw new UnsupportedOperationException();}
@Override
public boolean contains(
Object object
)
{throw new NotImplementedException();}
@Override
public boolean containsAll(
Collection<?> objects
)
{throw new NotImplementedException();}
@Override
public boolean equals(
Object object
)
{throw new NotImplementedException();}
@Override
public int hashCode(
)
{throw new NotImplementedException();}
@Override
public boolean isEmpty(
)
{
/*
NOTE: Semantics of the indirect objects collection imply that the collection is considered
empty in any case no in-use object is available.
*/
for(PdfIndirectObject object : this)
{
if(object.isInUse())
return false;
}
return true;
}
@Override
public boolean remove(
Object object
)
{
return (remove(
((PdfIndirectObject)object).getReference().getObjectNumber()
) != null);
}
@Override
public boolean removeAll(
Collection<?> objects
)
{throw new NotImplementedException();}
@Override
public boolean retainAll(
Collection<?> objects
)
{throw new UnsupportedOperationException();}
/**
Gets the number of entries available (both in-use and free) in the
collection.
@return The number of entries available in the collection.
*/
@Override
public int size(
)
{return (lastObjectNumber + 1);}
@Override
public PdfIndirectObject[] toArray(
)
{throw new NotImplementedException();}
@Override
public <T> T[] toArray(
T[] objects
)
{throw new NotImplementedException();}
// <Iterable>
@Override
public Iterator<PdfIndirectObject> iterator(
)
{
return new Iterator<PdfIndirectObject>()
{
// <class>
// <dynamic>
// <fields>
/** Index of the next item. */
private int index = 0;
// </fields>
// <interface>
// <public>
// <Iterator>
@Override
public boolean hasNext(
)
{return (index < size());}
@Override
public PdfIndirectObject next(
)
{
if(!hasNext())
throw new NoSuchElementException();
return get(index++);
}
@Override
public void remove(
)
{throw new UnsupportedOperationException();}
// </Iterator>
// </public>
// </interface>
// </dynamic>
// </class>
};
}
// </Iterable>
// </Collection>
// </List>
// </public>
// <internal>
/**
For internal use only.
*/
public TreeMap<Integer,PdfIndirectObject> getModifiedObjects(
)
{return modifiedObjects;}
/**
For internal use only.
*/
public PdfIndirectObject update(
PdfIndirectObject object
)
{
int index = object.getReference().getObjectNumber();
// Get the old indirect object to be replaced!
PdfIndirectObject old = get(index);
if(old != object)
{old.dropFile();} // Disconnects the old indirect object.
// Insert the new indirect object into the modified objects collection!
modifiedObjects.put(index,object);
// Remove old indirect object from cache!
wokenObjects.remove(index);
// Mark the new indirect object as modified!
object.dropOriginal();
return old;
}
// </internal>
// </interface>
// </dynamic>
// </class>
}