/* * 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.util; import org.icepdf.core.pobjects.*; import org.icepdf.core.pobjects.Dictionary; 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.security.SecurityManager; import java.awt.geom.Rectangle2D; import java.util.*; import java.util.logging.Logger; import java.util.concurrent.ConcurrentHashMap; /** * <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()); // new incremental file loader class. private LazyObjectLoader m_LazyObjectLoader; private ConcurrentHashMap<Reference, Object> refs = new ConcurrentHashMap<Reference, Object>(1024); private ConcurrentHashMap<Reference, ICCBased> lookupReference2ICCBased = new ConcurrentHashMap<Reference, ICCBased>(256); // Instead of keeping Names names, Dictionary dests, we keep // a reference to the Catalog, which actually owns them private Catalog catalog; // centrally locate these objects for later clean up. public MemoryManager memoryManager; public CacheManager cacheManager; public SecurityManager securityManager; // 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; /** * Sets a document loader for the library. * * @param lol loader object. */ public void setLazyObjectLoader(LazyObjectLoader lol) { m_LazyObjectLoader = lol; if (m_LazyObjectLoader != null) memoryManager.registerMemoryManagerDelegate(m_LazyObjectLoader); } /** * 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 (m_LazyObjectLoader == null) return null; return m_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) { ob = refs.get(reference); if (ob == null && m_LazyObjectLoader != null) { if ( m_LazyObjectLoader.loadObject(reference)) { ob = refs.get(reference); // printObjectDebug(ob); } } if (ob == null) break; else if (!(ob instanceof Reference)) break; reference = (Reference) ob; } 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.Hashtable, String) */ public Object getObject(Hashtable dictionaryEntries, String 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(Hashtable dictionaryEntries, String 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(Hashtable dictionaryEntries, String 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 */ 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(Hashtable dictionaryEntries, String 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) { Object ob = refs.get(reference); return ob != null || m_LazyObjectLoader != null && m_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(Hashtable dictionaryEntries, String 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(Hashtable dictionaryEntries, String 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(Hashtable dictionaryEntries, String 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(Hashtable dictionaryEntries, String 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(Hashtable dictionaryEntries, String 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 String getName(Hashtable dictionaryEntries, String key) { Object o = getObject(dictionaryEntries, key); if (o != null) { 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. */ public Hashtable getDictionary(Hashtable dictionaryEntries, String key) { Object o = getObject(dictionaryEntries, key); if (o instanceof Hashtable) { return (Hashtable) o; } else if (o instanceof Vector) { Vector v = (Vector) o; Hashtable h1 = new Hashtable(); for (Enumeration e = v.elements(); e.hasMoreElements();) { Object o1 = e.nextElement(); if (o1 instanceof Map) { h1.putAll((Map) o1); } } return h1; } 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(Hashtable dictionaryEntries, String key) { Vector v = (Vector) 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 = lookupReference2ICCBased.get(ref); if (cs == null) { Object obj = getObject(ref); if (obj instanceof Stream) { Stream stream = (Stream) obj; cs = new ICCBased(this, stream); lookupReference2ICCBased.put(ref, cs); } } return cs; } public Resources getResources(Hashtable dictionaryEntries, String 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 Hashtable) { Hashtable ht = (Hashtable) ob; Resources resources = new Resources(this, ht); dictionaryEntries.put(key, resources); return resources; } return null; } public Resources getResources(Reference reference) { while (true) { Object ob = refs.get(reference); if (ob == null && m_LazyObjectLoader != null) { if (m_LazyObjectLoader.loadObject(reference)) { ob = refs.get(reference); } } if (ob == null) return null; else if (ob instanceof Resources) return (Resources) ob; else if (ob instanceof Reference) { reference = (Reference) ob; continue; } else if (ob instanceof Hashtable) { Hashtable ht = (Hashtable) ob; Resources resources = new Resources(this, ht); addObject(resources, reference); return resources; } break; } 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, object); } /** * Removes an object from from the library. * * @param objetReference */ 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. memoryManager = MemoryManager.getInstance(); cacheManager = new CacheManager(); } /** * 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.Hashtable, String) */ public Reference getObjectReference(Hashtable dictionaryEntries, String 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; } /** * 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 library cache manager. * * @return cache manager used by library and document. */ public CacheManager getCacheManager() { return cacheManager; } /** * 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; } /** * 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(); Object tmp; for (Reference ref:test) { tmp = refs.get(ref); if (tmp instanceof Font || tmp instanceof FontDescriptor) { refs.remove(ref); } } } /** * Dispose the library's resources. */ public void dispose() { if (memoryManager != null) { memoryManager.releaseAllByLibrary(this); } if (cacheManager != null) { cacheManager.dispose(); } if (refs != null) { refs.clear(); } if (lookupReference2ICCBased != null) { lookupReference2ICCBased.clear(); lookupReference2ICCBased = null; } if (m_LazyObjectLoader != null) { m_LazyObjectLoader.dispose(); } } }