/*
/*
* 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.util;
import org.icepdf.core.io.SeekableInput;
import org.icepdf.core.pobjects.*;
import org.icepdf.core.pobjects.acroform.InteractiveForm;
import org.icepdf.core.pobjects.acroform.SignatureHandler;
import org.icepdf.core.pobjects.fonts.Font;
import org.icepdf.core.pobjects.fonts.FontDescriptor;
import org.icepdf.core.pobjects.graphics.ICCBased;
import org.icepdf.core.pobjects.graphics.ImagePool;
import org.icepdf.core.pobjects.security.SecurityManager;
import java.awt.geom.Rectangle2D;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.concurrent.*;
import java.util.logging.Logger;
/**
* <p>The <code>Library</code> class acts a central repository for the access
* of PDF objects in a document. The Library class has many utility methods
* which are designed to access PDF objects as easily as possible. The
* <code>Library</code> class has direct access to the PDF file and loads the
* needed objects from the file system when needed. </p>
*
* @since 1.0
*/
public class Library {
private static final Logger log =
Logger.getLogger(Library.class.toString());
protected static ThreadPoolExecutor commonThreadPool;
protected static ThreadPoolExecutor imageThreadPool;
protected static int commonPoolThreads;
protected static int imagePoolThreads;
private static final long KEEP_ALIVE_TIME = 90;
static {
try {
commonPoolThreads =
Defs.intProperty("org.icepdf.core.library.threadPoolSize", 2);
if (commonPoolThreads < 1) {
commonPoolThreads = 2;
}
} catch (NumberFormatException e) {
log.warning("Error reading buffered scale factor");
}
try {
// todo make ImageReference call interruptible and then we can get rid of this pool.
imagePoolThreads =
Defs.intProperty("org.icepdf.core.library.imageThreadPoolSize", 2);
if (imagePoolThreads < 1) {
imagePoolThreads = 2;
}
} catch (NumberFormatException e) {
log.warning("Error reading buffered scale factor");
}
log.fine("Starting ICEpdf Thread Pools: " +
(commonPoolThreads + imagePoolThreads) +
" threads.");
initializeThreadPool();
}
// new incremental file loader class.
private LazyObjectLoader lazyObjectLoader;
private ConcurrentHashMap<Reference, WeakReference<Object>> refs =
new ConcurrentHashMap<Reference, WeakReference<Object>>(1024);
private ConcurrentHashMap<Reference, WeakReference<ICCBased>> lookupReference2ICCBased =
new ConcurrentHashMap<Reference, WeakReference<ICCBased>>(256);
// Instead of keeping Names names, Dictionary dests, we keep
// a reference to the Catalog, which actually owns them
private Catalog catalog;
private SecurityManager securityManager;
// handles signature validation and signing.
private SignatureHandler signatureHandler;
// signature permissions
private Permissions permissions;
private SeekableInput documentInput;
// state manager reference needed by most classes to properly managed state
// changes and new object creation
public StateManager stateManager;
private boolean isEncrypted;
private boolean isLinearTraversal;
private ImagePool imagePool;
/**
* Sets a document loader for the library.
*
* @param lol loader object.
*/
public void setLazyObjectLoader(LazyObjectLoader lol) {
lazyObjectLoader = lol;
}
/**
* Gets the document's trailer.
*
* @param position byte offset of the trailer in the PDF file.
* @return trailer dictionary
*/
public PTrailer getTrailerByFilePosition(long position) {
if (lazyObjectLoader == null)
return null;
return lazyObjectLoader.loadTrailer(position);
}
/**
* Gets the object specified by the reference.
*
* @param reference reference to a PDF object in the document structure.
* @return PDF object dictionary that the reference refers to. Null if the
* object reference can not be found.
*/
public Object getObject(Reference reference) {
Object ob;
while (true) {
WeakReference<Object> obRef = refs.get(reference);
// check stateManager first to allow for annotations to be injected
// from a separate file.
if (stateManager != null) {
if (stateManager.contains(reference)) {
ob = stateManager.getChange(reference);
if (ob instanceof PObject){
return ((PObject) ob).getObject();
}
return ob;
}
}
ob = obRef != null ? obRef.get() : null;
if (ob == null && lazyObjectLoader != null) {
ob = lazyObjectLoader.loadObject(reference);
}
if (ob instanceof PObject) {
return ((PObject) ob).getObject();
} else if (ob instanceof Reference) {
reference = (Reference) ob;
} else {
break;
}
}
return ob;
}
/**
* Utility method for displaying debug info related to PDF object loading.
*
* @param ob object to show debug information for
*/
private void printObjectDebug(Object ob) {
if (ob == null) {
log.finer("null object found");
} else if (ob instanceof PObject) {
PObject tmp = (PObject) ob;
log.finer(tmp.getReference() + " " + tmp.toString());
} else if (ob instanceof Dictionary) {
Dictionary tmp = (Dictionary) ob;
log.finer(tmp.getPObjectReference() + " " + tmp.toString());
} else {
log.finer(ob.getClass() + " " + ob.toString());
}
}
/**
* Gets the PDF object specified by the <code>key</code> in the dictionary
* entries. If the key value is a reference, the object that the reference
* points to is returned.
*
* @param dictionaryEntries the dictionary entries to look up the key in.
* @param key string value representing the dictionary key.
* @return PDF object that the key references.
* @see #getObjectReference(java.util.HashMap, Name)
*/
public Object getObject(HashMap dictionaryEntries, Name key) {
if (dictionaryEntries == null) {
return null;
}
Object o = dictionaryEntries.get(key);
if (o == null)
return null;
if (o instanceof Reference) {
o = getObject((Reference) o);
}
return o;
}
/**
* Test to see if the given key is a reference and not an inline dictinary
*
* @param dictionaryEntries dictionary to test
* @param key dictionary key
* @return true if the key value exists and is a reference, false if the
* dictionaryEntries are null or the key references an inline dictionary
*/
public boolean isReference(HashMap dictionaryEntries, Name key) {
return dictionaryEntries != null &&
dictionaryEntries.get(key) instanceof Reference;
}
/**
* Gets the reference association of the key if any. This method is usual
* used in combination with #isReference to get and assign the Reference
* for a given PObject.
*
* @param dictionaryEntries dictionary to search in.
* @param key key to search for in dictionary.
* @return reference of the object that key points if any. Null if the key
* points to an inline dictionary and not a reference.
*/
public Reference getReference(HashMap dictionaryEntries, Name key) {
Object ref = dictionaryEntries.get(key);
if (ref instanceof Reference) {
return (Reference) ref;
} else {
return null;
}
}
/**
* Gets the state manager class which keeps track of changes PDF objects.
*
* @return document state manager
*/
public StateManager getStateManager() {
return stateManager;
}
/**
* Sets the document state manager so that all object can access the
* state manager via the central library instance.
*
* @param stateManager reference to the state change class
*/
public void setStateManager(StateManager stateManager) {
this.stateManager = stateManager;
}
/**
* Gets the PDF object that the <code>referenceObject</code> references.
*
* @param referenceObject reference object.
* @return PDF object that <code>referenceObject</code> references. If
* <code>referenceObject</code> is not an instance of a Reference, the
* origional <code>referenceObject</code> is returned.
*/
public Object getObject(Object referenceObject) {
if (referenceObject instanceof Reference) {
return getObject((Reference) referenceObject);
}
return referenceObject;
}
/**
* Tests if the given key will return a non-null PDF object from the
* specified dictionary entries. A null PDF object would result if no
* PDF object could be found with the specified key.
*
* @param dictionaryEntries dictionary entries
* @param key dictionary key
* @return true, if the key's value is non-null PDF object; false, otherwise.
*/
public boolean isValidEntry(HashMap dictionaryEntries, Name key) {
if (dictionaryEntries == null) {
return false;
}
Object o = dictionaryEntries.get(key);
return o != null && (!(o instanceof Reference) || isValidEntry((Reference) o));
}
/**
* Tests if there exists a cross-reference entry for this reference.
*
* @param reference reference to a PDF object in the document structure.
* @return true, if a cross-reference entry exists for this reference; false, otherwise.
*/
public boolean isValidEntry(Reference reference) {
WeakReference<Object> ob = refs.get(reference);
return (ob != null && ob.get() != null) ||
lazyObjectLoader != null &&
lazyObjectLoader.haveEntry(reference);
}
/**
* Gets a Number specified by the <code>key</code> in the dictionary
* entries. If the key value is a reference, the Number object that the
* reference points to is returned.
*
* @param dictionaryEntries the dictionary entries to look up the key in.
* @param key string value representing the dictionary key.
* @return Number object if a valid key; null, if the key does not point
* to Number or is invalid.
*/
public Number getNumber(HashMap dictionaryEntries, Name key) {
Object o = getObject(dictionaryEntries, key);
if (o instanceof Number)
return (Number) o;
return null;
}
/**
* Gets a Boolean specified by the <code>key</code> in the dictionary
* entries. If the key value is a reference, the Boolean object that the
* reference points to is returned.
*
* @param dictionaryEntries the dictionary entries to look up the key in.
* @param key string value representing the dictionary key.
* @return Number object if a valid key; null, if the key does not point
* to Number or is invalid.
*/
public Boolean getBoolean(HashMap dictionaryEntries, Name key) {
Object o = getObject(dictionaryEntries, key);
if (o instanceof String)
return Boolean.valueOf((String) o);
else return o instanceof Boolean && (Boolean) o;
}
/**
* Gets a float specified by the <code>key</code> in the dictionary
* entries. If the key value is a reference, the object that the
* reference points to is returned.
*
* @param dictionaryEntries the dictionary entries to look up the key in.
* @param key string value representing the dictionary key.
* @return float value if a valid key; null, if the key does not point
* to a float or is invalid.
*/
public float getFloat(HashMap dictionaryEntries, Name key) {
Number n = getNumber(dictionaryEntries, key);
return (n != null) ? n.floatValue() : 0.0f;
}
/**
* Gets an int specified by the <code>key</code> in the dictionary
* entries. If the key value is a reference, the object that the
* reference points to is returned.
*
* @param dictionaryEntries the dictionary entries to look up the key in.
* @param key string value representing the dictionary key.
* @return int value if a valid key, null if the key does not point
* to an int or is invalid.
*/
public int getInt(HashMap dictionaryEntries, Name key) {
Number n = getNumber(dictionaryEntries, key);
return (n != null) ? n.intValue() : 0;
}
/**
* Gets a float specified by the <code>key</code> in the dictionary
* entries. If the key value is a reference, the object that the
* reference points to is returned.
*
* @param dictionaryEntries the dictionary entries to look up the key in.
* @param key string value representing the dictionary key.
* @return float value if a valid key; null, if the key does not point
* to a float or is invalid.
*/
public long getLong(HashMap dictionaryEntries, Name key) {
Number n = getNumber(dictionaryEntries, key);
return (n != null) ? n.longValue() : 0L;
}
/**
* Gets a Name specified by the <code>key</code> in the dictionary
* entries. If the key value is a reference, the Name object that the
* reference points to is returned.
*
* @param dictionaryEntries the dictionary entries to look up the key in.
* @param key string value representing the dictionary key.
* @return Name object if a valid key; null, if the key does not point
* to Name or is invalid.
*/
public Name getName(HashMap dictionaryEntries, Name key) {
Object o = getObject(dictionaryEntries, key);
if (o != null) {
if (o instanceof Name) {
return (Name) o;
}
}
return null;
}
/**
* Gets a text string specified by the <code>key</code> in the dictionary
* entries. If the key value is a reference, the string object that the
* reference points to is returned.
*
* @param dictionaryEntries the dictionary entries to look up the key in.
* @param key string value representing the dictionary key.
* @return string object if a valid key; null, if the key does not point
* to Name or is invalid.
*/
public String getString(HashMap dictionaryEntries, Name key) {
Object o = getObject(dictionaryEntries, key);
if (o != null) {
if (o instanceof String) {
return ((String) o);
} else if (o instanceof StringObject) {
return ((StringObject) o).getDecryptedLiteralString(securityManager);
} else if (o instanceof Name) {
return ((Name) o).getName();
}
}
return null;
}
/**
* Gets a dictionary specified by the <code>key</code> in the dictionary
* entries. If the key value is a reference, the dictionary object that the
* reference points to is returned.
*
* @param dictionaryEntries the dictionary entries to look up the key in.
* @param key string value representing the dictionary key.
* @return dictionary object if a valid key; null, if the key does not point
* to dictionary or is invalid.
*/
@SuppressWarnings("unchecked")
public HashMap getDictionary(HashMap dictionaryEntries, Name key) {
Object o = getObject(dictionaryEntries, key);
if (o instanceof HashMap) {
return (HashMap) o;
} else if (o instanceof List) {
List v = (List) o;
HashMap h1 = new HashMap();
for (Object o1 : v) {
if (o1 instanceof HashMap) {
h1.putAll((HashMap) o1);
}
}
return h1;
}
return null;
}
public List getArray(HashMap dictionaryEntries, Name key) {
Object o = getObject(dictionaryEntries, key);
if (o instanceof List) {
return (List) o;
}
return null;
}
/**
* Gets a rectangle specified by the key. The rectangle is already
* in the coordinate system of Java2D, and thus must be used carefully.
*
* @param dictionaryEntries the dictionary entries to look up the key in.
* @param key string value representing the dictionary key.
* @return rectangle in Java2D coordinate system.
*/
public Rectangle2D.Float getRectangle(HashMap dictionaryEntries, Name key) {
List v = (List) getObject(dictionaryEntries, key);
if (v != null) {
// s by default contains data in the Cartesian plain.
return new PRectangle(v).toJava2dCoordinates();
} else {
return null;
}
}
/**
* The Reference is to the Stream from which the ICC color space data
* is to be parsed. So, without this method, we would be making and
* initializing a new ICCBased object every time one was needed, since
* the Reference is not for the ICCBased object itself.
*
* @param ref Reference to Stream containing ICC color space data
* @return ICCBased color model object for the given reference
*/
public ICCBased getICCBased(Reference ref) {
ICCBased cs = null;
WeakReference<ICCBased> csRef = lookupReference2ICCBased.get(ref);
if (csRef != null) {
cs = csRef.get();
}
if (cs == null) {
Object obj = getObject(ref);
if (obj instanceof Stream) {
Stream stream = (Stream) obj;
cs = new ICCBased(this, stream);
lookupReference2ICCBased.put(ref, new WeakReference<ICCBased>(cs));
}
}
return cs;
}
@SuppressWarnings("unchecked")
public Resources getResources(HashMap dictionaryEntries, Name key) {
if (dictionaryEntries == null)
return null;
Object ob = dictionaryEntries.get(key);
if (ob == null)
return null;
else if (ob instanceof Resources)
return (Resources) ob;
else if (ob instanceof Reference) {
Reference reference = (Reference) ob;
return getResources(reference);
} else if (ob instanceof HashMap) {
HashMap ht = (HashMap) ob;
Resources resources = new Resources(this, ht);
dictionaryEntries.put(key, resources);
return resources;
}
return null;
}
public Resources getResources(Reference reference) {
Object object = getObject(reference);
if (object instanceof Resources) {
return (Resources) object;
} else if (object instanceof HashMap) {
HashMap ht = (HashMap) object;
Resources resources = new Resources(this, ht);
addObject(resources, reference);
return resources;
}
return null;
}
/**
* Adds a PDF object and its respective object reference to the library.
*
* @param object PDF object to add.
* @param objectReference PDF object reference object.
*/
public void addObject(Object object, Reference objectReference) {
refs.put(objectReference, new WeakReference<Object>(object));
}
/**
* Removes an object from from the library.
*
* @param objetReference object reference to remove to library
*/
public void removeObject(Reference objetReference) {
if (objetReference != null) {
refs.remove(objetReference);
}
}
/**
* Creates a new instance of a Library.
*/
public Library() {
// set Catalog memory Manager and cache manager.
imagePool = new ImagePool();
signatureHandler = new SignatureHandler();
}
/**
* Sets a pointer to the orginal document input stream
*
* @param documentInput seekable inputstream.
*/
public void setDocumentInput(SeekableInput documentInput) {
this.documentInput = documentInput;
}
/**
* Gets the SeekableInput of the document underlying bytes.
*
* @return document bytes.
*/
public SeekableInput getDocumentInput() {
return documentInput;
}
/**
* Gets the PDF object specified by the <code>key</code> in the dictionary
* entries. If the key value is a reference it is returned.
*
* @param dictionaryEntries the dictionary entries to look up the key in.
* @param key string value representing the dictionary key.
* @return the Reference specified by the PDF key. If the key is invalid
* or does not reference a Reference object, null is returned.
* @see #getObject(java.util.HashMap, Name)
*/
public Reference getObjectReference(HashMap dictionaryEntries,
Name key) {
if (dictionaryEntries == null) {
return null;
}
Object o = dictionaryEntries.get(key);
if (o == null)
return null;
Reference currentRef = null;
while (o != null && (o instanceof Reference)) {
currentRef = (Reference) o;
o = getObject(currentRef);
}
return currentRef;
}
/**
* Indicates that document is encrypted using Adobe Standard Encryption.
*
* @return true if the document is encrypted, false otherwise.
*/
public boolean isEncrypted() {
return isEncrypted;
}
/**
* Gets the document's security manger.
*
* @return document's security manager if the document is encrypted, null
* otherwise.
*/
public SecurityManager getSecurityManager() {
return securityManager;
}
public void setSecurityManager(SecurityManager securityManager) {
this.securityManager = securityManager;
}
public SignatureHandler getSignatureHandler() {
return signatureHandler;
}
/**
* Set a documents permissions for a given certificate of signature, optional.
* The permission should also be used with the encryption permissions if present
* to configure the viewer permissions.
*
* @return permission object if present, otherwise null.
*/
public Permissions getPermissions() {
return permissions;
}
public void setPermissions(Permissions permissions) {
this.permissions = permissions;
}
/**
* Set the document is encrypted flag.
*
* @param flag true, if the document is encrypted; false, otherwize.
*/
public void setEncrypted(boolean flag) {
isEncrypted = flag;
}
/**
* When we fail to load the required xref tables or streams that are
* needed to access the objects in the PDF, then we simply go to the
* beginning of the file and read in all of the objects into memory,
* which we call linear traversal.
*/
public void setLinearTraversal() {
isLinearTraversal = true;
}
/**
* There are several implications from using linear traversal, which
* affect how we parse the PDF objects, and maintain them in memory,
* so those sections of code need to check this flag here.
*
* @return If PDF was parsed via linear traversal
*/
public boolean isLinearTraversal() {
return isLinearTraversal;
}
/**
* Gets the document's catalog.
*
* @return document's catalog.
*/
public Catalog getCatalog() {
return catalog;
}
/**
* Sets the document's catalog. Normally only accessed by the document's parser.
*
* @param c document catalog object.
*/
public void setCatalog(Catalog c) {
catalog = c;
}
/**
* Checks the Catalog for an interactive Forms dictionary and if found the resources object
* is used for a font lookup.
*
* @param fontName font name to look for.
* @return font font, null otherwise.
*/
public Font getInteractiveFormFont(String fontName) {
InteractiveForm form = getCatalog().getInteractiveForm();
if (form != null && form.getResources() != null) {
Resources resources = form.getResources();
return resources.getFont(new Name(fontName));
}
return null;
}
/**
* Utility/demo functionality to clear all font and font descriptor
* resources. The library will re-fetch the font resources in question
* when needed again.
*/
public void disposeFontResources() {
Set<Reference> test = refs.keySet();
for (Reference ref : test) {
WeakReference<Object> reference = refs.get(ref);
Object tmp = reference != null ? reference.get() : null;
if (tmp instanceof Font || tmp instanceof FontDescriptor) {
refs.remove(ref);
}
}
}
public ImagePool getImagePool() {
return imagePool;
}
public static void initializeThreadPool() {
log.fine("Starting ICEpdf Thread Pool: " + commonPoolThreads + " threads.");
if (commonThreadPool == null || commonThreadPool.isShutdown()) {
commonThreadPool = new ThreadPoolExecutor(
commonPoolThreads, commonPoolThreads, KEEP_ALIVE_TIME, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>());
// set a lower thread priority
commonThreadPool.setThreadFactory(new ThreadFactory() {
public Thread newThread(java.lang.Runnable command) {
Thread newThread = new Thread(command);
newThread.setName("ICEpdf-thread-pool");
newThread.setPriority(Thread.NORM_PRIORITY);
newThread.setDaemon(true);
return newThread;
}
});
}
if (imageThreadPool == null || imageThreadPool.isShutdown()) {
imageThreadPool = new ThreadPoolExecutor(
imagePoolThreads, imagePoolThreads, KEEP_ALIVE_TIME, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>());
// set a lower thread priority
imageThreadPool.setThreadFactory(new ThreadFactory() {
public Thread newThread(java.lang.Runnable command) {
Thread newThread = new Thread(command);
newThread.setName("ICEpdf-thread-image-pool");
newThread.setPriority(Thread.NORM_PRIORITY);
newThread.setDaemon(true);
return newThread;
}
});
}
}
public static void shutdownThreadPool() {
// do a little clean up.
commonThreadPool.purge();
commonThreadPool.shutdownNow();
imageThreadPool.purge();
imageThreadPool.shutdownNow();
}
public static void execute(Runnable runnable) {
try {
if (commonThreadPool.isShutdown()) {
initializeThreadPool();
}
commonThreadPool.execute(runnable);
} catch (RejectedExecutionException e) {
log.severe("ICEpdf Common Thread Pool was shutdown!");
}
}
public static void executeImage(FutureTask callable) {
try {
if (imageThreadPool.isShutdown()) {
initializeThreadPool();
}
imageThreadPool.execute(callable);
} catch (RejectedExecutionException e) {
log.severe("ICEpdf Common Thread Pool was shutdown!");
}
}
}