001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.commons.compress.compressors.xz; 020 021import java.io.IOException; 022import java.io.InputStream; 023 024import org.tukaani.xz.XZ; 025import org.tukaani.xz.SingleXZInputStream; 026import org.tukaani.xz.XZInputStream; 027 028import org.apache.commons.compress.MemoryLimitException; 029import org.apache.commons.compress.compressors.CompressorInputStream; 030import org.apache.commons.compress.utils.CountingInputStream; 031import org.apache.commons.compress.utils.IOUtils; 032import org.apache.commons.compress.utils.InputStreamStatistics; 033 034/** 035 * XZ decompressor. 036 * @since 1.4 037 */ 038public class XZCompressorInputStream extends CompressorInputStream 039 implements InputStreamStatistics { 040 041 private final CountingInputStream countingStream; 042 private final InputStream in; 043 044 /** 045 * Checks if the signature matches what is expected for a .xz file. 046 * 047 * @param signature the bytes to check 048 * @param length the number of bytes to check 049 * @return true if signature matches the .xz magic bytes, false otherwise 050 */ 051 public static boolean matches(final byte[] signature, final int length) { 052 if (length < XZ.HEADER_MAGIC.length) { 053 return false; 054 } 055 056 for (int i = 0; i < XZ.HEADER_MAGIC.length; ++i) { 057 if (signature[i] != XZ.HEADER_MAGIC[i]) { 058 return false; 059 } 060 } 061 062 return true; 063 } 064 065 /** 066 * Creates a new input stream that decompresses XZ-compressed data 067 * from the specified input stream. This doesn't support 068 * concatenated .xz files. 069 * 070 * @param inputStream where to read the compressed data 071 * 072 * @throws IOException if the input is not in the .xz format, 073 * the input is corrupt or truncated, the .xz 074 * headers specify options that are not supported 075 * by this implementation, or the underlying 076 * <code>inputStream</code> throws an exception 077 */ 078 public XZCompressorInputStream(final InputStream inputStream) 079 throws IOException { 080 this(inputStream, false); 081 } 082 083 /** 084 * Creates a new input stream that decompresses XZ-compressed data 085 * from the specified input stream. 086 * 087 * @param inputStream where to read the compressed data 088 * @param decompressConcatenated 089 * if true, decompress until the end of the 090 * input; if false, stop after the first .xz 091 * stream and leave the input position to point 092 * to the next byte after the .xz stream 093 * 094 * @throws IOException if the input is not in the .xz format, 095 * the input is corrupt or truncated, the .xz 096 * headers specify options that are not supported 097 * by this implementation, or the underlying 098 * <code>inputStream</code> throws an exception 099 */ 100 public XZCompressorInputStream(final InputStream inputStream, 101 final boolean decompressConcatenated) 102 throws IOException { 103 this(inputStream, decompressConcatenated, -1); 104 } 105 106 /** 107 * Creates a new input stream that decompresses XZ-compressed data 108 * from the specified input stream. 109 * 110 * @param inputStream where to read the compressed data 111 * @param decompressConcatenated 112 * if true, decompress until the end of the 113 * input; if false, stop after the first .xz 114 * stream and leave the input position to point 115 * to the next byte after the .xz stream 116 * @param memoryLimitInKb memory limit used when reading blocks. If 117 * the estimated memory limit is exceeded on {@link #read()}, 118 * a {@link MemoryLimitException} is thrown. 119 * 120 * @throws IOException if the input is not in the .xz format, 121 * the input is corrupt or truncated, the .xz 122 * headers specify options that are not supported 123 * by this implementation, 124 * or the underlying <code>inputStream</code> throws an exception 125 * 126 * @since 1.14 127 */ 128 public XZCompressorInputStream(InputStream inputStream, 129 boolean decompressConcatenated, final int memoryLimitInKb) 130 throws IOException { 131 countingStream = new CountingInputStream(inputStream); 132 if (decompressConcatenated) { 133 in = new XZInputStream(countingStream, memoryLimitInKb); 134 } else { 135 in = new SingleXZInputStream(countingStream, memoryLimitInKb); 136 } 137 } 138 139 @Override 140 public int read() throws IOException { 141 try { 142 final int ret = in.read(); 143 count(ret == -1 ? -1 : 1); 144 return ret; 145 } catch (org.tukaani.xz.MemoryLimitException e) { 146 throw new MemoryLimitException(e.getMemoryNeeded(), e.getMemoryLimit(), e); 147 } 148 } 149 150 @Override 151 public int read(final byte[] buf, final int off, final int len) throws IOException { 152 try { 153 final int ret = in.read(buf, off, len); 154 count(ret); 155 return ret; 156 } catch (org.tukaani.xz.MemoryLimitException e) { 157 //convert to commons-compress MemoryLimtException 158 throw new MemoryLimitException(e.getMemoryNeeded(), e.getMemoryLimit(), e); 159 } 160 } 161 162 @Override 163 public long skip(final long n) throws IOException { 164 try { 165 return IOUtils.skip(in, n); 166 } catch (org.tukaani.xz.MemoryLimitException e) { 167 //convert to commons-compress MemoryLimtException 168 throw new MemoryLimitException(e.getMemoryNeeded(), e.getMemoryLimit(), e); 169 } 170 } 171 172 @Override 173 public int available() throws IOException { 174 return in.available(); 175 } 176 177 @Override 178 public void close() throws IOException { 179 in.close(); 180 } 181 182 /** 183 * @since 1.17 184 */ 185 @Override 186 public long getCompressedCount() { 187 return countingStream.getBytesRead(); 188 } 189}