/* This code is part of Freenet. It is distributed under the GNU General * Public License, version 2 (or at your option any later version). See * http://www.gnu.org/ for further details of the GPL. */ package freenet.support.compress; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import freenet.support.api.Bucket; import freenet.support.api.BucketFactory; /** * A data compressor. Contains methods to get all data compressors. * This is for single-file compression (gzip, bzip2) as opposed to archives. */ public interface Compressor { public static final String DEFAULT_COMPRESSORDESCRIPTOR = null; public enum COMPRESSOR_TYPE implements Compressor { // WARNING: Changing non-transient members on classes that are Serializable can result in // restarting downloads or losing uploads. // Codecs will be tried in order: put the less resource consuming first GZIP("GZIP", new GzipCompressor(), (short) 0), BZIP2("BZIP2", new Bzip2Compressor(), (short) 1), LZMA("LZMA", new OldLZMACompressor(), (short)2), LZMA_NEW("LZMA_NEW", new NewLZMACompressor(), (short)3); public final String name; public final Compressor compressor; public final short metadataID; /** cached values(). Never modify or pass this array to outside code! */ private final static COMPRESSOR_TYPE[] values = values(); COMPRESSOR_TYPE(String name, Compressor c, short metadataID) { this.name = name; this.compressor = c; this.metadataID = metadataID; } public static COMPRESSOR_TYPE getCompressorByMetadataID(short id) { for(COMPRESSOR_TYPE current : values) if(current.metadataID == id) return current; return null; } public static COMPRESSOR_TYPE getCompressorByName(String name) { for(COMPRESSOR_TYPE current : values) if(current.name.equals(name)) return current; return null; } public static String getHelloCompressorDescriptor() { StringBuilder sb = new StringBuilder(); sb.append(values.length); sb.append(" - "); getCompressorDescriptor(sb); return sb.toString(); } public static String getCompressorDescriptor() { StringBuilder sb = new StringBuilder(); getCompressorDescriptor(sb); return sb.toString(); } public static void getCompressorDescriptor(StringBuilder sb) { boolean isfirst = true; for(COMPRESSOR_TYPE current : values) { if (isfirst) isfirst = false; else sb.append(", "); sb.append(current.name); sb.append('('); sb.append(current.metadataID); sb.append(')'); } } /** * make a COMPRESSOR_TYPE[] from a descriptor string<BR> * the descriptor string is a comma separated list of numbers or names(can be mixed)<BR> * it is better to store the string in db4o instead of the compressors?<BR> * if the string is null/empty, it returns COMPRESSOR_TYPE.values() as default * @param compressordescriptor * @return * @throws InvalidCompressionCodecException */ public static COMPRESSOR_TYPE[] getCompressorsArray(String compressordescriptor, boolean pre1254) throws InvalidCompressionCodecException { COMPRESSOR_TYPE[] result = getCompressorsArrayNoDefault(compressordescriptor); if (result == null) { COMPRESSOR_TYPE[] ret = new COMPRESSOR_TYPE[values.length-1]; int x = 0; for(COMPRESSOR_TYPE v: values) { if((v == LZMA) && !pre1254) continue; if((v == LZMA_NEW) && pre1254) continue; ret[x++] = v; } result = ret; } return result; } public static COMPRESSOR_TYPE[] getCompressorsArrayNoDefault(String compressordescriptor) throws InvalidCompressionCodecException { if (compressordescriptor == null) return null; if (compressordescriptor.trim().length() == 0) return null; String[] codecs = compressordescriptor.split(","); ArrayList<COMPRESSOR_TYPE> result = new ArrayList<COMPRESSOR_TYPE>(codecs.length); for (String codec : codecs) { codec = codec.trim(); COMPRESSOR_TYPE ct = getCompressorByName(codec); if (ct == null) { try { ct = getCompressorByMetadataID(Short.parseShort(codec)); } catch (NumberFormatException nfe) { } } if (ct == null) { throw new InvalidCompressionCodecException("Unknown compression codec identifier: '"+codec+"'"); } if (result.contains(ct)) { throw new InvalidCompressionCodecException("Duplicate compression codec identifier: '"+codec+"'"); } result.add(ct); } return result.toArray(new COMPRESSOR_TYPE[result.size()]); } @Override public Bucket compress(Bucket data, BucketFactory bf, long maxReadLength, long maxWriteLength) throws IOException, CompressionOutputSizeException { return compressor.compress(data, bf, maxReadLength, maxWriteLength); } @Override public long compress(InputStream is, OutputStream os, long maxReadLength, long maxWriteLength) throws IOException, CompressionOutputSizeException { return compressor.compress(is, os, maxReadLength, maxWriteLength); } @Override public long decompress(InputStream input, OutputStream output, long maxLength, long maxEstimateSizeLength) throws IOException, CompressionOutputSizeException { return compressor.decompress(input, output, maxLength, maxEstimateSizeLength); } @Override public int decompress(byte[] dbuf, int i, int j, byte[] output) throws CompressionOutputSizeException { return compressor.decompress(dbuf, i, j, output); } public static int countCompressors() { return values.length; } } /** * Compress the data. * @param data The bucket to read from. * @param bf The means to create a new bucket. * @param maxReadLength The maximum number of bytes to read from the input bucket. * @param maxWriteLength The maximum number of bytes to write to the output bucket. If this is exceeded, throw a CompressionOutputSizeException. * @return The compressed data. * @throws IOException If an error occurs reading or writing data. * @throws CompressionOutputSizeException If the compressed data is larger than maxWriteLength. */ public abstract Bucket compress(Bucket data, BucketFactory bf, long maxReadLength, long maxWriteLength) throws IOException, CompressionOutputSizeException; /** * Compress the data. * @param input The InputStream to read from. * @param output The OutputStream to write to. * @param maxReadLength The maximum number of bytes to read from the input bucket. * @param maxWriteLength The maximum number of bytes to write to the output bucket. If this is exceeded, throw a CompressionOutputSizeException. * @return The compressed data. * @throws IOException If an error occurs reading or writing data. * @throws CompressionOutputSizeException If the compressed data is larger than maxWriteLength. */ public abstract long compress(InputStream input, OutputStream output, long maxReadLength, long maxWriteLength) throws IOException, CompressionOutputSizeException; /** * Decompress data. * @param input Where to read the data to decompress from * @param output Where to write the final product to * @param maxLength The maximum length to decompress (we throw if more is present). * @param maxEstimateSizeLength If the data is too big, and this is >0, read up to this many bytes in order to try to get the data size. * @return Number of bytes copied * @throws IOException * @throws CompressionOutputSizeException */ public abstract long decompress(InputStream input, OutputStream output, long maxLength, long maxEstimateSizeLength) throws IOException, CompressionOutputSizeException; /** Decompress in RAM only. * @param dbuf Input buffer. * @param i Offset to start reading from. * @param j Number of bytes to read. * @param output Output buffer. * @throws DecompressException * @throws CompressionOutputSizeException * @returns The number of bytes actually written. */ public abstract int decompress(byte[] dbuf, int i, int j, byte[] output) throws CompressionOutputSizeException; }