# COSArray.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.util.ArrayList;
    import java.util.Collection;
    import java.util.Iterator;
    import java.util.List;
    import java.util.stream.Collectors;
    import java.util.stream.StreamSupport;

    import org.apache.pdfbox.pdmodel.common.COSObjectable;

    /**
     * An array of PDFBase objects as part of the PDF document.
     *
     * @author Ben Litchfield
     */
    public class COSArray extends COSBase implements Iterable<COSBase>, COSUpdateInfo
    {
        private final List<COSBase> objects = new ArrayList<>();
        private final COSUpdateState updateState;

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

        /**
         * Use the given list to initialize the COSArray.
         *
         * @param cosObjectables the initial list of COSObjectables
         */
        public COSArray(List<? extends COSObjectable> cosObjectables)
        {
            if (cosObjectables == null)
            {
                throw new IllegalArgumentException("List of COSObjectables cannot be null");
            }
            updateState = new COSUpdateState(this);
            cosObjectables.forEach(cosObjectable ->
                objects.add(cosObjectable != null ? cosObjectable.getCOSObject() : null));
        }

        /**
         * This will add an object to the array.
         *
         * @param object The object to add to the array.
         */
        public void add( COSBase object )
        {
            objects.add( object );
            getUpdateState().update(object);
        }

        /**
         * This will add an object to the array.
         *
         * @param object The object to add to the array.
         */
        public void add( COSObjectable object )
        {
            objects.add( object.getCOSObject() );
            getUpdateState().update(object.getCOSObject());
        }

        /**
         * Add the specified object at the ith location and push the rest to the
         * right.
         *
         * @param i The index to add at.
         * @param object The object to add at that index.
         */
        public void add( int i, COSBase object)
        {
            objects.add( i, object );
            getUpdateState().update(object);
        }

        /**
         * This will remove all of the objects in the collection.
         */
        public void clear()
        {
            objects.clear();
            getUpdateState().update();
        }

        /**
         * This will remove all of the objects in the collection.
         *
         * @param objectsList The list of objects to remove from the collection.
         */
        public void removeAll( Collection<COSBase> objectsList )
        {
            objects.removeAll( objectsList );
            getUpdateState().update();
        }

        /**
         * This will retain all of the objects in the collection.
         *
         * @param objectsList The list of objects to retain from the collection.
         */
        public void retainAll( Collection<COSBase> objectsList )
        {
            objects.retainAll( objectsList );
            getUpdateState().update();
        }

        /**
         * This will add an object to the array.
         *
         * @param objectsList The object to add to the array.
         */
        public void addAll( Collection<COSBase> objectsList )
        {
            objects.addAll( objectsList );
            getUpdateState().update(objectsList);
        }

        /**
         * This will add all objects to this array.
         *
         * @param objectList The list of objects to add.
         */
        public void addAll( COSArray objectList )
        {
            if( objectList != null )
            {
                objects.addAll( objectList.objects );
                getUpdateState().update(objectList);
            }
        }

        /**
         * Add the specified object at the ith location and push the rest to the
         * right.
         *
         * @param i The index to add at.
         * @param objectList The object to add at that index.
         */
        public void addAll( int i, Collection<COSBase> objectList )
        {
            objects.addAll( i, objectList );
            getUpdateState().update(objectList);
        }

        /**
         * This will set an object at a specific index.
         *
         * @param index zero based index into array.
         * @param object The object to set.
         */
        public void set( int index, COSBase object )
        {
            objects.set( index, object );
            getUpdateState().update(object);
        }

        /**
         * This will set an object at a specific index.
         *
         * @param index zero based index into array.
         * @param intVal The object to set.
         */
        public void set( int index, int intVal )
        {
            objects.set( index, COSInteger.get(intVal));
            getUpdateState().update();
        }

        /**
         * This will set an object at a specific index.
         *
         * @param index zero based index into array.
         * @param object The object to set.
         */
        public void set( int index, COSObjectable object )
        {
            COSBase base = null;
            if( object != null )
            {
                base = object.getCOSObject();
            }
            objects.set( index, base );
            getUpdateState().update(base);
        }

        /**
         * This will get an object from the array.  This will dereference the object.
         * If the object is COSNull then null will be returned.
         *
         * @param index The index into the array to get the object.
         *
         * @return The object at the requested index.
         */
        public COSBase getObject( int index )
        {
            COSBase obj = objects.get( index );
            if( obj instanceof COSObject )
            {
                obj = ((COSObject)obj).getObject();
            }
            if (obj instanceof COSNull)
            {
                obj = null;
            }
            return obj;
        }

        /**
         * This will get an object from the array.  This will NOT dereference
         * the COS object.
         *
         * @param index The index into the array to get the object.
         *
         * @return The object at the requested index.
         */
        public COSBase get( int index )
        {
            return objects.get( index );
        }

        /**
         * Get the value of the array as an integer.
         *
         * @param index The index into the list.
         *
         * @return The value at that index or -1 if does not exist.
         */
        public int getInt( int index )
        {
            return getInt( index, -1 );
        }

        /**
         * Get the value of the array as an integer, return the default if it does not exist.
         *
         * @param index The value of the array.
         * @param defaultValue The value to return if the value is null.
         * @return The value at the index or the defaultValue.
         */
        public int getInt( int index, int defaultValue )
        {
            int retval = defaultValue;
            if ( index < size() )
            {
                Object obj = objects.get( index );
                if( obj instanceof COSNumber )
                {
                    retval = ((COSNumber)obj).intValue();
                }
            }
            return retval;
        }

        /**
         * Set the value in the array as an integer.
         *
         * @param index The index into the array.
         * @param value The value to set.
         */
        public void setInt( int index, int value )
        {
            set( index, COSInteger.get( value ) );
        }

        /**
         * Set the value in the array as a name.
         * @param index The index into the array.
         * @param name The name to set in the array.
         */
        public void setName( int index, String name )
        {
            set( index, COSName.getPDFName( name ) );
        }

        /**
         * Get the value of the array as a string.
         *
         * @param index The index into the array.
         * @return The name converted to a string or null if it does not exist.
         */
        public String getName( int index )
        {
            return getName( index, null );
        }

        /**
         * Get an entry in the array that is expected to be a COSName.
         * @param index The index into the array.
         * @param defaultValue The value to return if it is null.
         * @return The value at the index or defaultValue if none is found.
         */
        public String getName( int index, String defaultValue )
        {
            String retval = defaultValue;
            if( index < size() )
            {
                Object obj = objects.get( index );
                if( obj instanceof COSName )
                {
                    retval = ((COSName)obj).getName();
                }
            }
            return retval;
        }

        /**
         * Set the value in the array as a string.
         * @param index The index into the array.
         * @param string The string to set in the array.
         */
        public void setString( int index, String string )
        {
            if ( string != null )
            {
                set( index, new COSString( string ) );
            }
            else
            {
                set( index, null );
            }
        }

        /**
         * Get the value of the array as a string.
         *
         * @param index The index into the array.
         * @return The string or null if it does not exist.
         */
        public String getString( int index )
        {
            return getString( index, null );
        }

        /**
         * Get an entry in the array that is expected to be a COSName.
         * @param index The index into the array.
         * @param defaultValue The value to return if it is null.
         * @return The value at the index or defaultValue if none is found.
         */
        public String getString( int index, String defaultValue )
        {
            String retval = defaultValue;
            if( index < size() )
            {
                Object obj = objects.get( index );
                if( obj instanceof COSString )
                {
                    retval = ((COSString)obj).getString();
                }
            }
            return retval;
        }

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

        /**
         * This will remove an element from the array.
         *
         * @param i The index of the object to remove.
         *
         * @return The object that was removed.
         */
        public COSBase remove( int i )
        {
            COSBase removedEntry = objects.remove( i );
            getUpdateState().update();
            return removedEntry;
        }

        /**
         * This will remove an element from the array.
         *
         * @param o The object to remove.
         *
         * @return <code>true</code> if the object was removed, <code>false</code>
         *  otherwise
         */
        public boolean remove( COSBase o )
        {
            boolean removed = objects.remove(o);
            if (removed)
            {
                getUpdateState().update();
            }
            return removed;
        }

        /**
         * This will remove an element from the array.
         * This method will also remove a reference to the object.
         *
         * @param o The object to remove.
         * @return <code>true</code> if the object was removed, <code>false</code>
         *  otherwise
         */
        public boolean removeObject(COSBase o)
        {
            boolean removed = this.remove(o);
            if (!removed)
            {
                for (int i = 0; i < this.size(); i++)
                {
                    COSBase entry = this.get(i);
                    if (entry instanceof COSObject)
                    {
                        COSObject objEntry = (COSObject) entry;
                        if (objEntry.getObject().equals(o))
                        {
                            return this.remove(entry);
                        }
                    }
                }
            }
            return removed;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public String toString()
        {
            return "COSArray{" + objects + "}";
        }

        /**
         * Get access to the list.
         *
         * @return an iterator over the array elements
         */
        @Override
        public Iterator<COSBase> iterator()
        {
            return objects.iterator();
        }

        /**
         * This will return the index of the entry or -1 if it is not found.
         *
         * @param object The object to search for.
         * @return The index of the object or -1.
         */
        public int indexOf(COSBase object)
        {
            for (int i = 0; i < size(); i++)
            {
                COSBase item = get(i);
                if (item == null)
                {
                    if (object == null)
                    {
                        return i;
                    }
                }
                else if (item.equals(object))
                {
                    return i;
                }
            }
            return -1;
        }

        /**
         * This will return the index of the entry or -1 if it is not found.
         * This method will also find references to indirect objects.
         *
         * @param object The object to search for.
         * @return The index of the object or -1.
         */
        public int indexOfObject(COSBase object)
        {
            for (int i = 0; i < this.size(); i++)
            {
                COSBase item = this.get(i);
                if (item == null)
                {
                    if (item == object)
                    {
                        return i;
                    }
                }
                else if (item.equals(object)
                        || item instanceof COSObject && ((COSObject) item).getObject().equals(object))
                {
                    return i;
                }
            }
            return -1;
        }

        /**
         * This will add null values until the size of the array is at least
         * as large as the parameter.  If the array is already larger than the
         * parameter then nothing is done.
         *
         * @param size The desired size of the array.
         */
        public void growToSize( int size )
        {
            growToSize( size, null );
        }

        /**
         * This will add the object until the size of the array is at least
         * as large as the parameter.  If the array is already larger than the
         * parameter then nothing is done.
         *
         * @param size The desired size of the array.
         * @param object The object to fill the array with.
         */
        public void growToSize( int size, COSBase object )
        {
            while( size() < size )
            {
                add( object );
                getUpdateState().update(object);
            }
            getUpdateState().update();
        }

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

        /**
         * This will take an COSArray of numbers and convert it to a float[].
         *
         * @return This COSArray as an array of float numbers.
         */
        public float[] toFloatArray()
        {
            float[] retval = new float[size()];
            for (int i = 0; i < retval.length; i++)
            {
                COSBase base = getObject(i);
                retval[i] = base instanceof COSNumber ? ((COSNumber) base).floatValue() : 0;
            }
            return retval;
        }

        /**
         * Clear the current contents of the COSArray and set it with the float[].
         *
         * @param value The new value of the float array.
         */
        public void setFloatArray( float[] value )
        {
            this.clear();
            for (float aValue : value)
            {
                add(new COSFloat(aValue));
            }
        }

        /**
         *  Return contents of COSArray as a Java List.
         *
         *  @return the COSArray as List
         */
        public List<? extends COSBase> toList()
        {
            return new ArrayList<>(objects);
        }

        /**
         * This will return a list of names if the COSArray consists of COSNames only.
         *
         * @return the list of names of the COSArray of COSNames
         */
        public List<String> toCOSNameStringList()
        {
            return StreamSupport.stream(objects.spliterator(), false) //
                    .map(o -> ((COSName) o).getName()) //
                    .collect(Collectors.toList());
        }

        /**
         * This will return a list of names if the COSArray consists of COSStrings only.
         *
         * @return the list of names of the COSArray of COSStrings
         */
        public List<String> toCOSStringStringList()
        {
            return StreamSupport.stream(objects.spliterator(), false) //
                    .map(o -> ((COSString) o).getString()) //
                    .collect(Collectors.toList());
        }

        /**
         * This will return a list of float values if the COSArray consists of COSNumbers only.
         *
         * @return the list of float values of the COSArray of COSNumbers
         */
        public List<Float> toCOSNumberFloatList()
        {
            List<Float> numbers = new ArrayList<>(size());
            for (int i = 0; i < size(); i++)
            {
                COSBase num = getObject(i);
                if (num instanceof COSNumber)
                {
                    numbers.add(((COSNumber) num).floatValue());
                }
                else
                {
                    numbers.add(null);
                }
            }
            return numbers;
        }

        /**
         * This will return a list of int values if the COSArray consists of COSNumbers only.
         *
         * @return the list of int values of the COSArray of COSNumbers
         */
        public List<Integer> toCOSNumberIntegerList()
        {
            List<Integer> numbers = new ArrayList<>(size());
            for (int i = 0; i < size(); i++)
            {
                COSBase num = getObject(i);
                if (num instanceof COSNumber)
                {
                    numbers.add(((COSNumber) num).intValue());
                }
                else
                {
                    numbers.add(null);
                }
            }
            return numbers;
        }

        /**
         * This will take a list of integer objects and return a COSArray of COSInteger objects.
         *
         * @param integer A list of integers
         *
         * @return An array of COSInteger objects
         */
        public static COSArray ofCOSIntegers(List<Integer> integer)
        {
            COSArray retval = new COSArray();
            integer.forEach(s -> retval.add(COSInteger.get(s.longValue())));
            return retval;
        }

        /**
         * This will take a list of string objects and return a COSArray of COSName objects.
         *
         * @param strings A list of strings
         *
         * @return An array of COSName objects
         */
        public static COSArray ofCOSNames(List<String> strings)
        {
            COSArray retval = new COSArray();
            strings.forEach(s -> retval.add(COSName.getPDFName(s)));
            return retval;
        }

        /**
         * This will take a list of string objects and return a COSArray of COSName objects.
         *
         * @param strings A list of strings
         *
         * @return An array of COSName objects
         */
        public static COSArray ofCOSStrings(List<String> strings)
        {
            COSArray retval = new COSArray();
            strings.forEach(s -> retval.add(new COSString(s)));
            return retval;
        }

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

    }