# COSDictionary.java

    /*
     * Licensed to the Apache Software Foundation (ASF) under one or more
     * contributor license agreements.  See the NOTICE file distributed with
     * this work for additional information regarding copyright ownership.
     * The ASF licenses this file to You 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.apache.pdfbox.cos;

    import java.io.IOException;
    import java.io.InputStream;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Calendar;
    import java.util.Collection;
    import java.util.List;
    import java.util.Map;
    import java.util.Set;
    import java.util.function.BiConsumer;

    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;

    import org.apache.pdfbox.io.IOUtils;
    import org.apache.pdfbox.pdmodel.common.COSObjectable;
    import org.apache.pdfbox.util.DateConverter;
    import org.apache.pdfbox.util.SmallMap;

    /**
     * This class represents a dictionary where name/value pairs reside.
     *
     * @author Ben Litchfield
     *
     */
    public class COSDictionary extends COSBase implements COSUpdateInfo
    {

        /**
         * Log instance.
         */
        private static final Log LOG = LogFactory.getLog(COSDictionary.class);

        private static final String PATH_SEPARATOR = "/";

        /**
         * The name-value pairs of this dictionary. The pairs are kept in the order they were added to the dictionary.
         */
        protected Map<COSName, COSBase> items = new SmallMap<>();
        private final COSUpdateState updateState;

        /**
         * Constructor.
         */
        public COSDictionary()
        {
            updateState = new COSUpdateState(this);
        }

        /**
         * Copy Constructor. This will make a shallow copy of this dictionary.
         *
         * @param dict The dictionary to copy.
         */
        public COSDictionary(COSDictionary dict)
        {
            updateState = new COSUpdateState(this);
            items.putAll(dict.items);
        }

        /**
         * @see java.util.Map#containsValue(java.lang.Object)
         *
         * @param value The value to find in the map.
         *
         * @return true if the map contains this value.
         */
        public boolean containsValue(Object value)
        {
            boolean contains = items.containsValue(value);
            if (!contains && value instanceof COSObject)
            {
                contains = items.containsValue(((COSObject) value).getObject());
            }
            return contains;
        }

        /**
         * Search in the map for the value that matches the parameter and return the first key that maps to that value.
         *
         * @param value The value to search for in the map.
         * @return The key for the value in the map or null if it does not exist.
         */
        public COSName getKeyForValue(Object value)
        {
            for (Map.Entry<COSName, COSBase> entry : items.entrySet())
            {
                Object nextValue = entry.getValue();
                if (nextValue.equals(value)
                        || (nextValue instanceof COSObject && ((COSObject) nextValue).getObject()
                                .equals(value)))
                {
                    return entry.getKey();
                }
            }
            return null;
        }

        /**
         * This will return the number of elements in this dictionary.
         *
         * @return The number of elements in the dictionary.
         */
        public int size()
        {
            return items.size();
        }

        /**
         * This will clear all items in the map.
         */
        public void clear()
        {
            items.clear();
            getUpdateState().update();
        }

        /**
         * This will get an object from this dictionary. If the object is a reference then it will dereference it and get it
         * from the document. If the object is COSNull then null will be returned.
         *
         * @param key The key to the object that we are getting.
         *
         * @return The object that matches the key.
         */
        public COSBase getDictionaryObject(String key)
        {
            return getDictionaryObject(COSName.getPDFName(key));
        }

        /**
         * This is a special case of getDictionaryObject that takes multiple keys, it will handle the situation where
         * multiple keys could get the same value, ie if either CS or ColorSpace is used to get the colorspace. This will
         * get an object from this dictionary. If the object is a reference then it will dereference it and get it from the
         * document. If the object is COSNull then null will be returned.
         *
         * @param firstKey The first key to try.
         * @param secondKey The second key to try.
         *
         * @return The object that matches the key.
         */
        public COSBase getDictionaryObject(COSName firstKey, COSName secondKey)
        {
            COSBase retval = getDictionaryObject(firstKey);
            if (retval == null && secondKey != null)
            {
                retval = getDictionaryObject(secondKey);
            }
            return retval;
        }

        /**
         * This will get an object from this dictionary. If the object is a reference then it will dereference it and get it
         * from the document. If the object is COSNull then null will be returned.
         *
         * @param key The key to the object that we are getting.
         *
         * @return The object that matches the key.
         */
        public COSBase getDictionaryObject(COSName key)
        {
            COSBase retval = items.get(key);
            if (retval instanceof COSObject)
            {
                retval = ((COSObject) retval).getObject();
            }
            if (retval instanceof COSNull)
            {
                retval = null;
            }
            return retval;
        }

        /**
         * This will set an item in the dictionary. If value is null then the result will be the same as removeItem( key ).
         *
         * @param key The key to the dictionary object.
         * @param value The value to the dictionary object.
         */
        public void setItem(COSName key, COSBase value)
        {
            if (value == null)
            {
                removeItem(key);
            }
            else
            {
                items.put(key, value);
                getUpdateState().update(value);
            }
        }

        /**
         * This will set an item in the dictionary. If value is null then the result will be the same as removeItem( key ).
         *
         * @param key The key to the dictionary object.
         * @param value The value to the dictionary object.
         */
        public void setItem(COSName key, COSObjectable value)
        {
            COSBase base = null;
            if (value != null)
            {
                base = value.getCOSObject();
            }
            setItem(key, base);
        }

        /**
         * This will set an item in the dictionary. If value is null then the result will be the same as removeItem( key ).
         *
         * @param key The key to the dictionary object.
         * @param value The value to the dictionary object.
         */
        public void setItem(String key, COSObjectable value)
        {
            setItem(COSName.getPDFName(key), value);
        }

        /**
         * This will set an item in the dictionary.
         *
         * @param key The key to the dictionary object.
         * @param value The value to the dictionary object.
         */
        public void setBoolean(String key, boolean value)
        {
            setItem(COSName.getPDFName(key), COSBoolean.getBoolean(value));
        }

        /**
         * This will set an item in the dictionary.
         *
         * @param key The key to the dictionary object.
         * @param value The value to the dictionary object.
         */
        public void setBoolean(COSName key, boolean value)
        {
            setItem(key, COSBoolean.getBoolean(value));
        }

        /**
         * This will set an item in the dictionary. If value is null then the result will be the same as removeItem( key ).
         *
         * @param key The key to the dictionary object.
         * @param value The value to the dictionary object.
         */
        public void setItem(String key, COSBase value)
        {
            setItem(COSName.getPDFName(key), value);
        }

        /**
         * This is a convenience method that will convert the value to a COSName object. If it is null then the object will
         * be removed.
         *
         * @param key The key to the object,
         * @param value The string value for the name.
         */
        public void setName(String key, String value)
        {
            setName(COSName.getPDFName(key), value);
        }

        /**
         * This is a convenience method that will convert the value to a COSName object. If it is null then the object will
         * be removed.
         *
         * @param key The key to the object,
         * @param value The string value for the name.
         */
        public void setName(COSName key, String value)
        {
            COSName name = null;
            if (value != null)
            {
                name = COSName.getPDFName(value);
            }
            setItem(key, name);
        }

        /**
         * Set the value of a date entry in the dictionary.
         *
         * @param key The key to the date value.
         * @param date The date value.
         */
        public void setDate(String key, Calendar date)
        {
            setDate(COSName.getPDFName(key), date);
        }

        /**
         * Set the date object.
         *
         * @param key The key to the date.
         * @param date The date to set.
         */
        public void setDate(COSName key, Calendar date)
        {
            setString(key, DateConverter.toString(date));
        }

        /**
         * Set the date object.
         *
         * @param embedded The embedded dictionary.
         * @param key The key to the date.
         * @param date The date to set.
         */
        public void setEmbeddedDate(COSName embedded, COSName key, Calendar date)
        {
            COSDictionary dic = getCOSDictionary(embedded);
            if (dic == null && date != null)
            {
                dic = new COSDictionary();
                setItem(embedded, dic);
            }
            if (dic != null)
            {
                dic.setDate(key, date);
            }
        }

        /**
         * This is a convenience method that will convert the value to a COSString object. If it is null then the object
         * will be removed.
         *
         * @param key The key to the object,
         * @param value The string value for the name.
         */
        public void setString(String key, String value)
        {
            setString(COSName.getPDFName(key), value);
        }

        /**
         * This is a convenience method that will convert the value to a COSString object. If it is null then the object
         * will be removed.
         *
         * @param key The key to the object,
         * @param value The string value for the name.
         */
        public void setString(COSName key, String value)
        {
            COSString name = null;
            if (value != null)
            {
                name = new COSString(value);
            }
            setItem(key, name);
        }

        /**
         * This is a convenience method that will convert the value to a COSString object. If it is null then the object
         * will be removed.
         *
         * @param embedded The embedded dictionary to set the item in.
         * @param key The key to the object,
         * @param value The string value for the name.
         */
        public void setEmbeddedString(COSName embedded, COSName key, String value)
        {
            COSDictionary dic = getCOSDictionary(embedded);
            if (dic == null && value != null)
            {
                dic = new COSDictionary();
                setItem(embedded, dic);
            }
            if (dic != null)
            {
                dic.setString(key, value);
            }
        }

        /**
         * This is a convenience method that will convert the value to a COSInteger object.
         *
         * @param key The key to the object,
         * @param value The int value for the name.
         */
        public void setInt(String key, int value)
        {
            setInt(COSName.getPDFName(key), value);
        }

        /**
         * This is a convenience method that will convert the value to a COSInteger object.
         *
         * @param key The key to the object,
         * @param value The int value for the name.
         */
        public void setInt(COSName key, int value)
        {
            setItem(key, COSInteger.get(value));
        }

        /**
         * This is a convenience method that will convert the value to a COSInteger object.
         *
         * @param key The key to the object,
         * @param value The int value for the name.
         */
        public void setLong(String key, long value)
        {
            setLong(COSName.getPDFName(key), value);
        }

        /**
         * This is a convenience method that will convert the value to a COSInteger object.
         *
         * @param key The key to the object,
         * @param value The int value for the name.
         */
        public void setLong(COSName key, long value)
        {
            COSInteger intVal = COSInteger.get(value);
            setItem(key, intVal);
        }

        /**
         * This is a convenience method that will convert the value to a COSInteger object.
         *
         * @param embeddedDictionary The embedded dictionary.
         * @param key The key to the object,
         * @param value The int value for the name.
         */
        public void setEmbeddedInt(COSName embeddedDictionary, COSName key, int value)
        {
            COSDictionary embedded = getCOSDictionary(embeddedDictionary);
            if (embedded == null)
            {
                embedded = new COSDictionary();
                setItem(embeddedDictionary, embedded);
            }
            embedded.setInt(key, value);
        }

        /**
         * This is a convenience method that will convert the value to a COSFloat object.
         *
         * @param key The key to the object,
         * @param value The int value for the name.
         */
        public void setFloat(String key, float value)
        {
            setFloat(COSName.getPDFName(key), value);
        }

        /**
         * This is a convenience method that will convert the value to a COSFloat object.
         *
         * @param key The key to the object,
         * @param value The int value for the name.
         */
        public void setFloat(COSName key, float value)
        {
            COSFloat fltVal = new COSFloat(value);
            setItem(key, fltVal);
        }

        /**
         * Sets the given boolean value at bitPos in the flags.
         *
         * @param field The COSName of the field to set the value into.
         * @param bitFlag the bit position to set the value in.
         * @param value the value the bit position should have.
         */
        public void setFlag(COSName field, int bitFlag, boolean value)
        {
            int currentFlags = getInt(field, 0);
            if (value)
            {
                currentFlags = currentFlags | bitFlag;
            }
            else
            {
                currentFlags &= ~bitFlag;
            }
            setInt(field, currentFlags);
        }

        /**
         * This is a convenience method that will get the dictionary object that is expected to be a name. Null is returned
         * if the entry does not exist in the dictionary.
         *
         * @param key The key to the item in the dictionary.
         * @return The COS name.
         */
        public COSName getCOSName(COSName key)
        {
            COSBase name = getDictionaryObject(key);
            if (name instanceof COSName)
            {
                return (COSName) name;
            }
            return null;
        }

        /**
         * This is a convenience method that will get the dictionary object that is expected to be a COSObject. Null is
         * returned if the entry does not exist in the dictionary.
         *
         * @param key The key to the item in the dictionary.
         * @return The COSObject.
         */
        public COSObject getCOSObject(COSName key)
        {
            COSBase object = getItem(key);
            if (object instanceof COSObject)
            {
                return (COSObject) object;
            }
            return null;
        }

        /**
         * This is a convenience method that will get the dictionary object that is expected to be a COSDictionary. Null is
         * returned if the entry does not exist in the dictionary.
         *
         * @param key The key to the item in the dictionary.
         * @return The COSDictionary.
         */
        public COSDictionary getCOSDictionary(COSName key)
        {
            COSBase dictionary = getDictionaryObject(key);
            if (dictionary instanceof COSDictionary)
            {
                return (COSDictionary) dictionary;
            }
            return null;
        }

        /**
         * This is a convenience method that will get the dictionary object that is expected to be a COSDictionary. Null is
         * returned if the entry does not exist in the dictionary.
         *
         * @param firstKey The first key to the item in the dictionary.
         * @param secondKey The second key to the item in the dictionary.
         * @return The COSDictionary.
         */
        public COSDictionary getCOSDictionary(COSName firstKey, COSName secondKey)
        {
            COSBase dictionary = getDictionaryObject(firstKey, secondKey);
            if (dictionary instanceof COSDictionary)
            {
                return (COSDictionary) dictionary;
            }
            return null;
        }

        /**
         * This is a convenience method that will get the dictionary object that is expected to be a COSStream. Null is
         * returned if the entry does not exist in the dictionary.
         *
         * @param key The key to the item in the dictionary.
         * @return The COSStream.
         */
        public COSStream getCOSStream(COSName key)
        {
            COSBase base = getDictionaryObject(key);
            if (base instanceof COSStream)
            {
                return (COSStream) base;
            }
            return null;
        }

        /**
         * This is a convenience method that will get the dictionary object that is expected to be a COSArray. Null is
         * returned if the entry does not exist in the dictionary.
         *
         * @param key The key to the item in the dictionary.
         * @return The COSArray.
         */
        public COSArray getCOSArray(COSName key)
        {
            COSBase array = getDictionaryObject(key);
            if (array instanceof COSArray)
            {
                return (COSArray) array;
            }
            return null;
        }

        /**
         * This is a convenience method that will get the dictionary object that is expected to be a name. Default is
         * returned if the entry does not exist in the dictionary.
         *
         * @param key The key to the item in the dictionary.
         * @param defaultValue The value to return if the dictionary item is null.
         * @return The COS name.
         */
        public COSName getCOSName(COSName key, COSName defaultValue)
        {
            COSBase name = getDictionaryObject(key);
            if (name instanceof COSName)
            {
                return (COSName) name;
            }
            return defaultValue;
        }

        /**
         * This is a convenience method that will get the dictionary object that is expected to be a name and convert it to
         * a string. Null is returned if the entry does not exist in the dictionary.
         *
         * @param key The key to the item in the dictionary.
         * @return The name converted to a string.
         */
        public String getNameAsString(String key)
        {
            return getNameAsString(COSName.getPDFName(key));
        }

        /**
         * This is a convenience method that will get the dictionary object that is expected to be a name and convert it to
         * a string. Null is returned if the entry does not exist in the dictionary.
         *
         * @param key The key to the item in the dictionary.
         * @return The name converted to a string.
         */
        public String getNameAsString(COSName key)
        {
            String retval = null;
            COSBase name = getDictionaryObject(key);
            if (name instanceof COSName)
            {
                retval = ((COSName) name).getName();
            }
            else if (name instanceof COSString)
            {
                retval = ((COSString) name).getString();
            }
            return retval;
        }

        /**
         * This is a convenience method that will get the dictionary object that is expected to be a name and convert it to
         * a string.
         *
         * @param key The key to the item in the dictionary.
         * @param defaultValue The value to return if the dictionary item is null.
         * @return The name converted to a string.
         */
        public String getNameAsString(String key, String defaultValue)
        {
            return getNameAsString(COSName.getPDFName(key), defaultValue);
        }

        /**
         * This is a convenience method that will get the dictionary object that is expected to be a name and convert it to
         * a string.
         *
         * @param key The key to the item in the dictionary.
         * @param defaultValue The value to return if the dictionary item is null.
         * @return The name converted to a string.
         */
        public String getNameAsString(COSName key, String defaultValue)
        {
            String retval = getNameAsString(key);
            if (retval == null)
            {
                retval = defaultValue;
            }
            return retval;
        }

        /**
         * This is a convenience method that will get the dictionary object that is expected to be a
         * string. Null is returned if the entry does not exist in the dictionary.
         *
         * @param key The key to the item in the dictionary.
         * @return The name converted to a string.
         */
        public String getString(String key)
        {
            return getString(COSName.getPDFName(key));
        }

        /**
         * This is a convenience method that will get the dictionary object that is expected to be a
         * string. Null is returned if the entry does not exist in the dictionary.
         *
         * @param key The key to the item in the dictionary.
         * @return The name converted to a string.
         */
        public String getString(COSName key)
        {
            String retval = null;
            COSBase value = getDictionaryObject(key);
            if (value instanceof COSString)
            {
                retval = ((COSString) value).getString();
            }
            return retval;
        }

        /**
         * This is a convenience method that will get the dictionary object that is expected to be a
         * string.
         *
         * @param key The key to the item in the dictionary.
         * @param defaultValue The default value to return.
         * @return The name converted to a string.
         */
        public String getString(String key, String defaultValue)
        {
            return getString(COSName.getPDFName(key), defaultValue);
        }

        /**
         * This is a convenience method that will get the dictionary object that is expected to be a
         * string.
         *
         * @param key The key to the item in the dictionary.
         * @param defaultValue The default value to return.
         * @return The name converted to a string.
         */
        public String getString(COSName key, String defaultValue)
        {
            String retval = getString(key);
            if (retval == null)
            {
                retval = defaultValue;
            }
            return retval;
        }

        /**
         * This is a convenience method that will get the dictionary object that is expected to be a name and convert it to
         * a string. Null is returned if the entry does not exist in the dictionary.
         *
         * @param embedded The embedded dictionary.
         * @param key The key to the item in the dictionary.
         * @return The name converted to a string.
         */
        public String getEmbeddedString(COSName embedded, COSName key)
        {
            return getEmbeddedString(embedded, key, null);
        }

        /**
         * This is a convenience method that will get the dictionary object that is expected to be a name and convert it to
         * a string.
         *
         * @param embedded The embedded dictionary.
         * @param key The key to the item in the dictionary.
         * @param defaultValue The default value to return.
         * @return The name converted to a string.
         */
        public String getEmbeddedString(COSName embedded, COSName key, String defaultValue)
        {
            COSDictionary eDic = getCOSDictionary(embedded);
            return eDic != null ? eDic.getString(key, defaultValue) : defaultValue;
        }

        /**
         * This is a convenience method that will get the dictionary object that is expected to be a name and convert it to
         * a string. Null is returned if the entry does not exist in the dictionary or if the date was invalid.
         *
         * @param key The key to the item in the dictionary.
         * @return The name converted to a date.
         */
        public Calendar getDate(String key)
        {
            return getDate(COSName.getPDFName(key));
        }

        /**
         * This is a convenience method that will get the dictionary object that is expected to be a name and convert it to
         * a string. Null is returned if the entry does not exist in the dictionary or if the date was invalid.
         *
         * @param key The key to the item in the dictionary.
         * @return The name converted to a date.
         */
        public Calendar getDate(COSName key)
        {
            COSBase base = getDictionaryObject(key);
            if (base instanceof COSString)
            {
                return DateConverter.toCalendar((COSString) base);
            }
            return null;
        }

        /**
         * This is a convenience method that will get the dictionary object that is expected to be a date.
         *
         * @param key The key to the item in the dictionary.
         * @param defaultValue The default value to return if the entry does not exist in the dictionary or if the date was invalid.
         * @return The name converted to a date.
         */
        public Calendar getDate(String key, Calendar defaultValue)
        {
            return getDate(COSName.getPDFName(key), defaultValue);
        }

        /**
         * This is a convenience method that will get the dictionary object that is expected to be a date.
         *
         * @param key The key to the item in the dictionary.
         * @param defaultValue The default value to return if the entry does not exist in the dictionary or if the date was invalid.
         * @return The name converted to a date.
         */
        public Calendar getDate(COSName key, Calendar defaultValue)
        {
            Calendar retval = getDate(key);
            if (retval == null)
            {
                retval = defaultValue;
            }
            return retval;
        }

        /**
         * This is a convenience method that will get the dictionary object that is expected to be a name and convert it to
         * a string. Null is returned if the entry does not exist in the dictionary.
         *
         * @param embedded The embedded dictionary to get.
         * @param key The key to the item in the dictionary.
         * @return The name converted to a string.
         */
        public Calendar getEmbeddedDate(COSName embedded, COSName key)
        {
            return getEmbeddedDate(embedded, key, null);
        }

        /**
         * This is a convenience method that will get the dictionary object that is expected to be a date.
         *
         * @param embedded The embedded dictionary to get.
         * @param key The key to the item in the dictionary.
         * @param defaultValue The default value to return if the entry does not exist in the dictionary or if the date was
         * invalid.
         * @return The name converted to a string.
         */
        public Calendar getEmbeddedDate(COSName embedded, COSName key, Calendar defaultValue)
        {
            COSDictionary eDic = getCOSDictionary(embedded);
            return eDic != null ? eDic.getDate(key, defaultValue) : defaultValue;
        }

        /**
         * This is a convenience method that will get the dictionary object that is expected to be a cos boolean and convert
         * it to a primitive boolean.
         *
         * @param key The key to the item in the dictionary.
         * @param defaultValue The value returned if the entry is null.
         *
         * @return The value converted to a boolean.
         */
        public boolean getBoolean(String key, boolean defaultValue)
        {
            return getBoolean(COSName.getPDFName(key), defaultValue);
        }

        /**
         * This is a convenience method that will get the dictionary object that is expected to be a COSBoolean and convert
         * it to a primitive boolean.
         *
         * @param key The key to the item in the dictionary.
         * @param defaultValue The value returned if the entry is null.
         *
         * @return The entry converted to a boolean.
         */
        public boolean getBoolean(COSName key, boolean defaultValue)
        {
            return getBoolean(key, null, defaultValue);
        }

        /**
         * This is a convenience method that will get the dictionary object that is expected to be a COSBoolean and convert
         * it to a primitive boolean.
         *
         * @param firstKey The first key to the item in the dictionary.
         * @param secondKey The second key to the item in the dictionary.
         * @param defaultValue The value returned if the entry is null.
         *
         * @return The entry converted to a boolean.
         */
        public boolean getBoolean(COSName firstKey, COSName secondKey, boolean defaultValue)
        {
            boolean retval = defaultValue;
            COSBase bool = getDictionaryObject(firstKey, secondKey);
            if (bool instanceof COSBoolean)
            {
                retval = bool == COSBoolean.TRUE;
            }
            return retval;
        }

        /**
         * Get an integer from an embedded dictionary. Useful for 1-1 mappings. default:-1
         *
         * @param embeddedDictionary The name of the embedded dictionary.
         * @param key The key in the embedded dictionary.
         *
         * @return The value of the embedded integer.
         */
        public int getEmbeddedInt(COSName embeddedDictionary, COSName key)
        {
            return getEmbeddedInt(embeddedDictionary, key, -1);
        }

        /**
         * Get an integer from an embedded dictionary. Useful for 1-1 mappings.
         *
         * @param embeddedDictionary The name of the embedded dictionary.
         * @param key The key in the embedded dictionary.
         * @param defaultValue The value if there is no embedded dictionary or it does not contain the key.
         *
         * @return The value of the embedded integer.
         */
        public int getEmbeddedInt(COSName embeddedDictionary, COSName key, int defaultValue)
        {
            COSDictionary embedded = getCOSDictionary(embeddedDictionary);
            return embedded != null ? embedded.getInt(key, defaultValue) : defaultValue;
        }

        /**
         * This is a convenience method that will get the dictionary object that is expected to be an int. -1 is returned if
         * there is no value.
         *
         * @param key The key to the item in the dictionary.
         * @return The integer value.
         */
        public int getInt(String key)
        {
            return getInt(COSName.getPDFName(key), -1);
        }

        /**
         * This is a convenience method that will get the dictionary object that is expected to be an int. -1 is returned if
         * there is no value.
         *
         * @param key The key to the item in the dictionary.
         * @return The integer value..
         */
        public int getInt(COSName key)
        {
            return getInt(key, -1);
        }

        /**
         * This is a convenience method that will get the dictionary object that is expected to be an integer. If the
         * dictionary value is null then the default value will be returned.
         *
         * @param key The key to the item in the dictionary.
         * @param defaultValue The value to return if the dictionary item is null.
         * @return The integer value.
         */
        public int getInt(String key, int defaultValue)
        {
            return getInt(COSName.getPDFName(key), defaultValue);
        }

        /**
         * This is a convenience method that will get the dictionary object that is expected to be an integer. If the
         * dictionary value is null then the defaultvalue will be returned.
         *
         * @param key The key to the item in the dictionary.
         * @param defaultValue The value to return if the dictionary item is null.
         * @return The integer value.
         */
        public int getInt(COSName key, int defaultValue)
        {
            return getInt(key, null, defaultValue);
        }

        /**
         * This is a convenience method that will get the dictionary object that is expected to be an integer. If the
         * dictionary value is null then the default value -1 will be returned.
         *
         * @param firstKey The first key to the item in the dictionary.
         * @param secondKey The second key to the item in the dictionary.
         * @return The integer value.
         */
        public int getInt(COSName firstKey, COSName secondKey)
        {
            return getInt(firstKey, secondKey, -1);
        }

        /**
         * This is a convenience method that will get the dictionary object that is expected to be an integer. If the
         * dictionary value is null then the default value will be returned.
         *
         * @param firstKey The first key to the item in the dictionary.
         * @param secondKey The second key to the item in the dictionary.
         * @param defaultValue The value to return if the dictionary item is null.
         * @return The integer value.
         */
        public int getInt(COSName firstKey, COSName secondKey, int defaultValue)
        {
            int retval = defaultValue;
            COSBase obj = getDictionaryObject(firstKey, secondKey);
            if (obj instanceof COSNumber)
            {
                retval = ((COSNumber) obj).intValue();
            }
            return retval;
        }

        /**
         * This is a convenience method that will get the dictionary object that is expected to be an long. -1 is returned
         * if there is no value.
         *
         * @param key The key to the item in the dictionary.
         *
         * @return The long value.
         */
        public long getLong(String key)
        {
            return getLong(COSName.getPDFName(key), -1L);
        }

        /**
         * This is a convenience method that will get the dictionary object that is expected to be an long. -1 is returned
         * if there is no value.
         *
         * @param key The key to the item in the dictionary.
         * @return The long value.
         */
        public long getLong(COSName key)
        {
            return getLong(key, -1L);
        }

        /**
         * This is a convenience method that will get the dictionary object that is expected to be an integer. If the
         * dictionary value is null then the default value will be returned.
         *
         * @param key The key to the item in the dictionary.
         * @param defaultValue The value to return if the dictionary item is null.
         * @return The integer value.
         */
        public long getLong(String key, long defaultValue)
        {
            return getLong(COSName.getPDFName(key), defaultValue);
        }

        /**
         * This is a convenience method that will get the dictionary object that is expected to be an integer. If the
         * dictionary value is null then the default value will be returned.
         *
         * @param key The key to the item in the dictionary.
         * @param defaultValue The value to return if the dictionary item is null.
         * @return The integer value.
         */
        public long getLong(COSName key, long defaultValue)
        {
            long retval = defaultValue;
            COSBase obj = getDictionaryObject(key);
            if (obj instanceof COSNumber)
            {
                retval = ((COSNumber) obj).longValue();
            }
            return retval;
        }

        /**
         * This is a convenience method that will get the dictionary object that is expected to be an float. -1 is returned
         * if there is no value.
         *
         * @param key The key to the item in the dictionary.
         * @return The float value.
         */
        public float getFloat(String key)
        {
            return getFloat(COSName.getPDFName(key), -1);
        }

        /**
         * This is a convenience method that will get the dictionary object that is expected to be an float. -1 is returned
         * if there is no value.
         *
         * @param key The key to the item in the dictionary.
         * @return The float value.
         */
        public float getFloat(COSName key)
        {
            return getFloat(key, -1);
        }

        /**
         * This is a convenience method that will get the dictionary object that is expected to be a float. If the
         * dictionary value is null then the default value will be returned.
         *
         * @param key The key to the item in the dictionary.
         * @param defaultValue The value to return if the dictionary item is null.
         * @return The float value.
         */
        public float getFloat(String key, float defaultValue)
        {
            return getFloat(COSName.getPDFName(key), defaultValue);
        }

        /**
         * This is a convenience method that will get the dictionary object that is expected to be an float. If the
         * dictionary value is null then the default value will be returned.
         *
         * @param key The key to the item in the dictionary.
         * @param defaultValue The value to return if the dictionary item is null.
         * @return The float value.
         */
        public float getFloat(COSName key, float defaultValue)
        {
            float retval = defaultValue;
            COSBase obj = getDictionaryObject(key);
            if (obj instanceof COSNumber)
            {
                retval = ((COSNumber) obj).floatValue();
            }
            return retval;
        }

        /**
         * Gets the boolean value from the flags at the given bit position.
         *
         * @param field The COSName of the field to get the flag from.
         * @param bitFlag the bitPosition to get the value from.
         *
         * @return true if the number at bitPos is '1'
         */
        public boolean getFlag(COSName field, int bitFlag)
        {
            int ff = getInt(field, 0);
            return (ff & bitFlag) == bitFlag;
        }

        /**
         * This will remove an item for the dictionary. This will do nothing of the object does not exist.
         *
         * @param key The key to the item to remove from the dictionary.
         */
        public void removeItem(COSName key)
        {
            items.remove(key);
            getUpdateState().update();
        }

        /**
         * This will do a lookup into the dictionary.
         *
         * @param key The key to the object.
         *
         * @return The item that matches the key.
         */
        public COSBase getItem(COSName key)
        {
            return items.get(key);
        }

        /**
         * This will do a lookup into the dictionary.
         *
         * @param key The key to the object.
         *
         * @return The item that matches the key.
         */
        public COSBase getItem(String key)
        {
            return getItem(COSName.getPDFName(key));
        }

        /**
         * This is a special case of getItem that takes multiple keys, it will handle the situation
         * where multiple keys could get the same value, ie if either CS or ColorSpace is used to get
         * the colorspace. This will get an object from this dictionary.
         *
         * @param firstKey The first key to try.
         * @param secondKey The second key to try.
         *
         * @return The object that matches the key.
         */
        public COSBase getItem(COSName firstKey, COSName secondKey)
        {
            COSBase retval = getItem(firstKey);
            if (retval == null && secondKey != null)
            {
                retval = getItem(secondKey);
            }
            return retval;
        }

        /**
         * Returns the names of the entries in this dictionary. The returned set is in the order the entries were added to
         * the dictionary.
         *
         * @since Apache PDFBox 1.1.0
         * @return names of the entries in this dictionary
         */
        public Set<COSName> keySet()
        {
            return items.keySet();
        }

        /**
         * Returns the name-value entries in this dictionary. The returned set is in the order the entries were added to the
         * dictionary.
         *
         * @since Apache PDFBox 1.1.0
         * @return name-value entries in this dictionary
         */
        public Set<Map.Entry<COSName, COSBase>> entrySet()
        {
            return items.entrySet();
        }

        /**
         * Convenience method that calls
         * {@link Map#forEach(java.util.function.BiConsumer) Map.forEach(BiConsumer)}.
         *
         * @param action
         */
        public void forEach(BiConsumer<? super COSName, ? super COSBase> action)
        {
            items.forEach(action);
        }

        /**
         * This will get all of the values for the dictionary.
         *
         * @return All the values for the dictionary.
         */
        public Collection<COSBase> getValues()
        {
            return items.values();
        }

        /**
         * visitor pattern double dispatch method.
         *
         * @param visitor The object to notify when visiting this object.
         * @throws IOException If there is an error visiting this object.
         */
        @Override
        public void accept(ICOSVisitor visitor) throws IOException
        {
            visitor.visitFromDictionary(this);
        }

        /**
         * This will add all of the dictionaries keys/values to this dictionary. Existing key/value pairs will be
         * overwritten.
         *
         * @param dic The dictionaries to get the key/value pairs from.
         */
        public void addAll(COSDictionary dic)
        {
            dic.forEach(this::setItem);
        }

        /**
         * @see java.util.Map#containsKey(Object)
         *
         * @param name The key to find in the map.
         * @return true if the map contains this key.
         */
        public boolean containsKey(COSName name)
        {
            return this.items.containsKey(name);
        }

        /**
         * @see java.util.Map#containsKey(Object)
         *
         * @param name The key to find in the map.
         * @return true if the map contains this key.
         */
        public boolean containsKey(String name)
        {
            return containsKey(COSName.getPDFName(name));
        }

        /**
         * Nice method, gives you every object you want Arrays works properly too. Try "P/Annots/[k]/Rect" where k means the
         * index of the Annots array.
         *
         * @param objPath the relative path to the object.
         * @return the object
         */
        public COSBase getObjectFromPath(String objPath)
        {
            String[] path = objPath.split(PATH_SEPARATOR);
            COSBase retval = this;
            for (String pathString : path)
            {
                if (retval instanceof COSArray)
                {
                    int idx = Integer.parseInt(pathString.replace("\\[", "").replace("\\]", ""));
                    retval = ((COSArray) retval).getObject(idx);
                }
                else if (retval instanceof COSDictionary)
                {
                    retval = ((COSDictionary) retval).getDictionaryObject(pathString);
                }
            }
            return retval;
        }

        /**
         * Returns an unmodifiable view of this dictionary.
         *
         * @return an unmodifiable view of this dictionary
         */
        public COSDictionary asUnmodifiableDictionary()
        {
            return new UnmodifiableCOSDictionary(this);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public String toString()
        {
            try
            {
                return getDictionaryString(this, new ArrayList<>());
            }
            catch (IOException e)
            {
                LOG.debug("An exception occurred trying - returning error message instead", e);
                return "COSDictionary{" + e.getMessage() + "}";
            }
        }

        private static String getDictionaryString(COSBase base, List<COSBase> objs) throws IOException
        {
            if (base == null)
            {
                return "null";
            }
            if (objs.contains(base))
            {
                // avoid endless recursion
                return String.valueOf(base.hashCode());
            }
            objs.add(base);
            if (base instanceof COSDictionary)
            {
                StringBuilder sb = new StringBuilder("COSDictionary{");
                for (Map.Entry<COSName, COSBase> x : ((COSDictionary) base).entrySet())
                {
                    sb.append(x.getKey());
                    sb.append(":");
                    sb.append(getDictionaryString(x.getValue(), objs));
                    sb.append(";");
                }
                sb.append("}");
                if (base instanceof COSStream)
                {
                    try (InputStream stream = ((COSStream) base).createRawInputStream())
                    {
                        byte[] b = IOUtils.toByteArray(stream);
                        sb.append("COSStream{").append(Arrays.hashCode(b)).append("}");
                    }
                }
                return sb.toString();
            }
            if (base instanceof COSArray)
            {
                StringBuilder sb = new StringBuilder("COSArray{");
                for (COSBase x : (COSArray) base)
                {
                    sb.append(getDictionaryString(x, objs));
                    sb.append(";");
                }
                sb.append("}");
                return sb.toString();
            }
            if (base instanceof COSObject)
            {
                COSObject obj = (COSObject) base;
                return "COSObject{"
                        + getDictionaryString(
                                obj.isObjectNull() ? COSNull.NULL : obj.getObject(), objs)
                        + "}";
            }
            return base.toString();
        }

        /**
         * Returns the current {@link COSUpdateState} of this {@link COSDictionary}.
         *
         * @return The current {@link COSUpdateState} of this {@link COSDictionary}.
         * @see COSUpdateState
         */
        @Override
        public COSUpdateState getUpdateState()
        {
            return updateState;
        }

    }