All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.cesecore.util.CompressedCollection Maven / Gradle / Ivy

/*************************************************************************
 *                                                                       *
 *  CESeCore: CE Security Core                                           *
 *                                                                       *
 *  This software is free software; you can redistribute it and/or       *
 *  modify it under the terms of the GNU Lesser General Public           *
 *  License as published by the Free Software Foundation; either         *
 *  version 2.1 of the License, or any later version.                    *
 *                                                                       *
 *  See terms of license at gnu.org.                                     *
 *                                                                       *
 *************************************************************************/
package org.cesecore.util;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterInputStream;

import org.apache.log4j.Logger;

/**
 * Special Collection that compresses all the added objects to a in memory byte array.
 * 
 * Objects are read from the collection by using a iterator over a decompression InputStream.
 * To avoid memory leaks, the .clear() call should be used when the collection is no longer needed.
 * 
 * The implementation is not thread safe.
 * 
 * Example use-case: a RevokedCertInfo takes 248 bytes in serialized form, but averages at only 48
 * bytes in compressed serialized form.
 * 
 * @version $Id: CompressedCollection.java 22565 2016-01-12 16:50:15Z jeklund $
 */
public class CompressedCollection implements Collection , Serializable {

    private static final long serialVersionUID = 1L;
    private static final Logger log = Logger.getLogger(CompressedCollection.class);

    private ByteArrayOutputStream baos = null;
    private ObjectOutputStream oos = null;
    private byte[] compressedData = null;
    private int size = 0;
    private final List oiss = new ArrayList();

    public CompressedCollection() {
        clear();
    }
    
    @Override
    public boolean add(final T object) {
        if (compressedData!=null) {
            throw new IllegalStateException("closeForWrite() has alread been called without clear() for this CompressedCollection.");
        }
        boolean ret = false;
        if (object!=null) {
            try {
                getObjectOutputStream().writeObject(object);
                ret = true;
                size++;
            } catch (IOException e) {
                throw new IllegalStateException(e);
            }
        }
        return ret;
    }

    /** Lazy initialization of our in memory object storage */
    private ObjectOutputStream getObjectOutputStream() throws IOException {
        if (oos==null) {
            baos = new ByteArrayOutputStream();
            final DeflaterOutputStream dos = new DeflaterOutputStream(baos, new Deflater(Deflater.BEST_COMPRESSION));
            oos = new ObjectOutputStream(dos);
        }
        return oos;
    }

    @Override
    public boolean addAll(final Collection objects) {
        for (final T object : objects) {
            add(object);
        }
        return objects.size()!=0;
    }

    @Override
    public void clear() {
        if (oos!=null) {
            // Clean up OutputStream, unless this has already been done
            try {
                oos.close();
            } catch (IOException e) {
                log.error(e.getMessage(), e);
            }
            oos = null;
        }
        size = 0;
        compressedData = null;
        // Clean up all InputStreams, unless this has already been done
        for (final ObjectInputStream ois : oiss) {
            try {
                ois.close();
            } catch (IOException e) {
                log.error(e.getMessage(), e);
            }
        }
        oiss.clear();
    }

    @Override
    public boolean contains(Object object) {
        final Iterator iterator = iterator();
        while (iterator.hasNext()) {
            final T t = iterator.next();
            if (t.equals(object)) {
                try {
                    oiss.get(oiss.size()-1).close();
                } catch (IOException e) {
                    throw new IllegalStateException(e);
                }
                oiss.remove(oiss.size()-1);
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean containsAll(final Collection objects) {
        throw new UnsupportedOperationException();  // This is not really an optional operation for a Collection
    }

    @Override
    public boolean isEmpty() {
        return size==0;
    }

    /** Signal that no more data will be added to this collection. Call before Serialization. */
    public void closeForWrite() {
        if (compressedData==null) {
            if (oos==null) {
                // Nothing was added
                compressedData = new byte[0];
            } else {
                // Clean up outputstream now when we are about to read the data
                try {
                    oos.flush();
                    oos.close();
                    oos = null;
                    compressedData = baos.toByteArray();
                    baos = null;
                    if (log.isDebugEnabled()) {
                        log.debug("Compressed data of " + size + " entries to " + compressedData.length + " bytes.");
                    }
                } catch (IOException e) {
                    log.error(e.getMessage(), e);
                }
            }
        }
    }

    @Override
    public Iterator iterator() {
        closeForWrite();
        // Create a new decompression stream over the data that belongs to the Iterator
        final ByteArrayInputStream bais = new ByteArrayInputStream(compressedData);
        final InflaterInputStream iis = new InflaterInputStream(bais);
        final ObjectInputStream ois;
        if (compressedData.length==0) {
            ois = null;
        } else {
            try {
                ois = new ObjectInputStream(iis);
            } catch (IOException e) {
                throw new IllegalStateException(e);
            }
            oiss.add(ois);
        }
        return new Iterator() {
            private T next = null;
            
            @Override
            public boolean hasNext() {
                try {
                    next = readNext();
                } catch (NoSuchElementException e) {
                    return false;
                }
                return true;
            }

            @Override
            public T next() {
                if (next==null) {
                    return readNext();
                }
                T ret = next;
                next = null;
                return ret;
            }

            @Override
            public void remove() {
                throw new UnsupportedOperationException();
            }

            @SuppressWarnings("unchecked")
            private T readNext() {
                if (ois==null) {
                    throw new NoSuchElementException();
                }
                try {
                    return (T) ois.readObject();
                } catch (IOException e) {
                    cleanUp();
                    throw new NoSuchElementException();
                } catch (ClassNotFoundException e) {
                    cleanUp();
                    throw new NoSuchElementException();
                }
            }

            /** Clean up InputStream right away if we reached the last entry in the stream */
            private void cleanUp() {
                oiss.remove(ois);
                try {
                    ois.close();
                } catch (IOException e) {
                    log.error(e.getMessage(), e);
                }
            }
        };
    }

    @Override
    public boolean remove(Object arg0) {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean removeAll(Collection arg0) {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean retainAll(Collection arg0) {
        throw new UnsupportedOperationException();
    }

    @Override
    public int size() {
        return size;
    }

    @Override
    public Object[] toArray() {
        throw new UnsupportedOperationException();  // This is not really an optional operation for a Collection
    }

    @Override
    public  T2[] toArray(final T2[] arg0) {
        throw new UnsupportedOperationException();  // This is not really an optional operation for a Collection
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy