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}