001/*
002 * This file is part of lzo-java, an implementation of LZO in Java.
003 * https://github.com/shevek/lzo-java
004 *
005 * The Java portion of this library is:
006 * Copyright (C) 2011 Shevek <shevek@anarres.org>
007 * All Rights Reserved.
008 *
009 * The preprocessed C portion of this library is:
010 * Copyright (C) 2006-2011 Markus Franz Xaver Johannes Oberhumer
011 * All Rights Reserved.
012 *
013 * This library is free software; you can redistribute it and/or
014 * modify it under the terms of the GNU General Public License
015 * as published by the Free Software Foundation; either version
016 * 2 of the License, or (at your option) any later version.
017 *
018 * This library is distributed in the hope that it will be useful,
019 * but WITHOUT ANY WARRANTY; without even the implied warranty
020 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
021 * See the GNU General Public License for more details.
022 *
023 * You should have received a copy of the GNU General Public
024 * License along with the LZO library; see the file COPYING.
025 * If not, see <http://www.gnu.org/licenses/> or write to the
026 * Free Software Foundation, Inc., 51 Franklin Street, Fifth
027 * Floor, Boston, MA 02110-1301, USA.
028
029 * As a special exception, the copyright holders of this file
030 * give you permission to link this file with independent
031 * modules to produce an executable, regardless of the license
032 * terms of these independent modules, and to copy and distribute
033 * the resulting executable under terms of your choice, provided
034 * that you also meet, for each linked independent module,
035 * the terms and conditions of the license of that module. An
036 * independent module is a module which is not derived from or
037 * based on this library or file. If you modify this file, you may
038 * extend this exception to your version of the file, but
039 * you are not obligated to do so. If you do not wish to do so,
040 * delete this exception statement from your version.
041 */
042package org.anarres.lzo;
043
044import java.io.ByteArrayOutputStream;
045import java.io.DataOutputStream;
046import java.io.IOException;
047import java.io.OutputStream;
048import java.util.Arrays;
049import java.util.zip.Adler32;
050import java.util.zip.CRC32;
051import java.util.zip.Checksum;
052import javax.annotation.CheckForNull;
053import javax.annotation.CheckForSigned;
054import javax.annotation.Nonnegative;
055import javax.annotation.Nonnull;
056
057import org.apache.commons.logging.Log;
058import org.apache.commons.logging.LogFactory;
059
060/**
061 * @author shevek
062 */
063public class LzopOutputStream extends LzoOutputStream {
064
065    private static final Log LOG = LogFactory.getLog(LzopOutputStream.class);
066    private final long flags;
067    private final CRC32 c_crc32_c;
068    private final CRC32 c_crc32_d;
069    private final Adler32 c_adler32_c;
070    private final Adler32 c_adler32_d;
071    private boolean closed = false;
072
073    /**
074     * Constructs a new LzopOutputStream.
075     * <p/>
076     * I recommend limiting flags to the following unless you REALLY know what
077     * you are doing:
078     * <ul>
079     * <li>{@link LzopConstants#F_ADLER32_C}</li>
080     * <li>{@link LzopConstants#F_ADLER32_D}</li>
081     * <li>{@link LzopConstants#F_CRC32_C}</li>
082     * <li>{@link LzopConstants#F_CRC32_D}</li>
083     * </ul>
084     */
085    public LzopOutputStream(OutputStream out, LzoCompressor compressor, @CheckForSigned int inputBufferSize, long flags) throws IOException {
086        super(out, compressor, inputBufferSize);
087        this.flags = flags;
088        this.c_crc32_c = ((flags & LzopConstants.F_CRC32_C) == 0) ? null : new CRC32();
089        this.c_crc32_d = ((flags & LzopConstants.F_CRC32_D) == 0) ? null : new CRC32();
090        this.c_adler32_c = ((flags & LzopConstants.F_ADLER32_C) == 0) ? null : new Adler32();
091        this.c_adler32_d = ((flags & LzopConstants.F_ADLER32_D) == 0) ? null : new Adler32();
092        writeLzopHeader();
093    }
094
095    public LzopOutputStream(@Nonnull OutputStream out, @Nonnull LzoCompressor compressor, @CheckForSigned int inputBufferSize) throws IOException {
096        this(out, compressor, inputBufferSize, 0L);
097    }
098
099    public LzopOutputStream(@Nonnull OutputStream out, @Nonnull LzoCompressor compressor) throws IOException {
100        this(out, compressor, 256 * 1024);
101    }
102
103    /**
104     * Writes an lzop-compatible header to the OutputStream provided.
105     */
106    protected void writeLzopHeader() throws IOException {
107        ByteArrayOutputStream bos = new ByteArrayOutputStream();
108        DataOutputStream dob = new DataOutputStream(bos);
109        try {
110            dob.writeShort(LzopConstants.LZOP_VERSION);
111            dob.writeShort(LzoVersion.LZO_LIBRARY_VERSION);
112            dob.writeShort(LzopConstants.LZOP_COMPAT_VERSION);
113            switch (getAlgorithm()) {
114                case LZO1X:
115                    LzoConstraint[] constraints = getConstraints();
116
117                    if (constraints.length == 0) {
118                        dob.writeByte(LzopConstants.M_LZO1X_1);
119                        dob.writeByte(5);
120                    } else if (constraints.length == 1 && constraints[0] == LzoConstraint.COMPRESSION) {
121                        dob.writeByte(LzopConstants.M_LZO1X_999);
122                        dob.writeByte(getCompressor().getCompressionLevel());
123                    } else
124                        throw new IOException("Unsupported constraint combination for LZO1X: " + Arrays.toString(constraints));
125
126                    break;
127                /*
128                 case LZO1X_15:
129                 dob.writeByte(LzopConstants.M_LZO1X_1_15);
130                 dob.writeByte(1);
131                 break;
132                 */
133                default:
134                    throw new IOException("Incompatible lzop algorithm " + getAlgorithm());
135            }
136            long mask = LzopConstants.F_ADLER32_C | LzopConstants.F_ADLER32_D;
137            mask = mask | LzopConstants.F_CRC32_C | LzopConstants.F_CRC32_D;
138            dob.writeInt((int) (flags & mask & 0xFFFFFFFF)); // all flags 0
139            dob.writeInt(33188); // mode
140            dob.writeInt((int) (System.currentTimeMillis() / 1000)); // mtime
141            dob.writeInt(0); // gmtdiff ignored
142            dob.writeByte(0); // no filename
143            Adler32 headerChecksum = new Adler32();
144            headerChecksum.update(bos.toByteArray());
145            // headerChecksum.update(dob.getData(), 0, dob.getLength());
146            int hc = (int) headerChecksum.getValue();
147            dob.writeInt(hc);
148            out.write(LzopConstants.LZOP_MAGIC);
149            // out.write(dob.getData(), 0, dob.getLength());
150            out.write(bos.toByteArray());
151        } finally {
152            dob.close();
153        }
154    }
155
156    private void writeChecksum(@CheckForNull Checksum csum, @Nonnull byte[] data, @Nonnegative int off, @Nonnegative int len) throws IOException {
157        if (csum == null)
158            return;
159        csum.reset();
160        csum.update(data, off, len);
161        long value = csum.getValue();
162        // LOG.info("Writing checksum " + csum);
163        writeInt((int) (value & 0xFFFFFFFF));
164    }
165
166    @Override
167    protected void writeBlock(@Nonnull byte[] inputData, @Nonnegative int inputPos, @Nonnegative int inputLen, @Nonnull byte[] outputData, @Nonnegative int outputPos, @Nonnegative int outputLen) throws IOException {
168        // LOG.info("inputLen=" + inputLen + "; outputLen=" + outputLen);
169        writeInt(inputLen);
170        if (outputLen < inputLen)
171            writeInt(outputLen);
172        else
173            writeInt(inputLen);
174
175        // This is where we put checksums, if any.
176        writeChecksum(c_adler32_d, inputData, inputPos, inputLen);
177        writeChecksum(c_crc32_d, inputData, inputPos, inputLen);
178        if (outputLen < inputLen) {
179            writeChecksum(c_adler32_c, outputData, outputPos, outputLen);
180            writeChecksum(c_crc32_c, outputData, outputPos, outputLen);
181        }
182
183        if (outputLen < inputLen)
184            out.write(outputData, outputPos, outputLen);
185        else
186            out.write(inputData, inputPos, inputLen);
187    }
188
189    /**
190     * Writes a null word to the underlying output stream, then closes it.
191     */
192    @Override
193    public void close() throws IOException {
194        if (!closed) {
195            flush();
196            out.write(new byte[]{0, 0, 0, 0});
197            super.close();
198            closed = true;
199        }
200    }
201}