# COSObjectPool.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.pdfwriter.compress;

    import org.apache.pdfbox.cos.COSBase;
    import org.apache.pdfbox.cos.COSObject;
    import org.apache.pdfbox.cos.COSObjectKey;

    import java.util.HashMap;
    import java.util.Map;

    /**
     * An instance of this class maps {@link COSBase} instances to {@link COSObjectKey}s and allows for a bidirectional
     * lookup.
     *
     * @author Christian Appl
     */
    public class COSObjectPool
    {
        private final Map<COSObjectKey, COSBase> keyPool = new HashMap<>();
        private final Map<COSBase, COSObjectKey> objectPool = new HashMap<>();

        private long highestXRefObjectNumber = 0;

        /**
         * Creates a map of {@link COSBase} instances to {@link COSObjectKey}s, allowing bidirectional lookups. This
         * constructor can be used for pre - initialized structures to start the assignment of new object numbers starting
         * from the hereby given offset.
         *
         * @param highestXRefObjectNumber The highest known object number.
         */
        public COSObjectPool(long highestXRefObjectNumber)
        {
            this.highestXRefObjectNumber = Math.max(this.highestXRefObjectNumber,
                    highestXRefObjectNumber);
        }

        /**
         * Update the key and object maps.
         *
         * @param key The key, that shall be added.
         * @param object The object, that shall be added.
         * @return The actual key, the object has been added for.
         */
        public COSObjectKey put(COSObjectKey key, COSBase object)
        {
            // to avoid to mixup indirect COSInteger objects holding the same value we have to check
            // if the given key is the same than the key which is stored for the "same" base object wihtin the object pool
            // the same is always true for COSFloat, COSBoolean and COSName and under certain circumstances for the remainig
            // types as well
            if (object == null || (contains(object) && getKey(object).equals(key)))
            {
                return null;
            }
            COSObjectKey actualKey = key;
            if (actualKey == null || contains(actualKey))
            {
                highestXRefObjectNumber++;
                actualKey = new COSObjectKey(highestXRefObjectNumber, 0);
                object.setKey(actualKey);
            }
            else
            {
                highestXRefObjectNumber = Math.max(key.getNumber(), highestXRefObjectNumber);
            }
            keyPool.put(actualKey, object);
            objectPool.put(object, actualKey);
            return actualKey;
        }

        /**
         * Returns the {@link COSObjectKey} for a given registered {@link COSBase}. Returns null if such an object is not
         * registered.
         *
         * @param object The {@link COSBase} a {@link COSObjectKey} shall be determined for.
         * @return key The {@link COSObjectKey}, that matches the registered {@link COSBase}, or null if such an object is
         * not registered.
         */
        public COSObjectKey getKey(COSBase object)
        {
            COSObjectKey key = null;
            if (object instanceof COSObject)
            {
                key = objectPool.get(((COSObject) object).getObject());
            }
            if (key == null)
            {
                return objectPool.get(object);
            }
            return key;
        }

        /**
         * Returns true, if a {@link COSBase} is registered for the given {@link COSObjectKey}.
         *
         * @param key The {@link COSObjectKey} that shall be checked for a registered {@link COSBase}.
         * @return True, if a {@link COSBase} is registered for the given {@link COSObjectKey}.
         */
        public boolean contains(COSObjectKey key)
        {
            return keyPool.containsKey(key);
        }

        /**
         * Returns the {@link COSBase}, that is registered for the given {@link COSObjectKey}, or null if no object is
         * registered for that key.
         *
         * @param key The {@link COSObjectKey} a registered {@link COSBase} shall be found for.
         * @return The {@link COSBase}, that is registered for the given {@link COSObjectKey}, or null if no object is
         * registered for that key.
         */
        public COSBase getObject(COSObjectKey key)
        {
            return keyPool.get(key);
        }

        /**
         * Returns true, if the given {@link COSBase} is a registered object of this pool.
         *
         * @param object The {@link COSBase} that shall be checked.
         * @return True, if such a {@link COSBase} is registered in this pool.
         */
        public boolean contains(COSBase object)
        {
            return (object instanceof COSObject
                    && objectPool.containsKey(((COSObject) object).getObject()))
                    || objectPool.containsKey(object);
        }

        /**
         * Returns the highest known object number (see: {@link COSObjectKey} for further information), that is currently
         * registered in this pool.
         *
         * @return The highest known object number (see: {@link COSObjectKey} for further information), that is currently
         * registered in this pool.
         */
        public long getHighestXRefObjectNumber()
        {
            return highestXRefObjectNumber;
        }
    }