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 * https://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.lzma; 020 021import java.io.IOException; 022import java.io.InputStream; 023 024import org.apache.commons.compress.MemoryLimitException; 025import org.apache.commons.compress.compressors.CompressorInputStream; 026import org.apache.commons.compress.utils.InputStreamStatistics; 027import org.apache.commons.io.build.AbstractStreamBuilder; 028import org.apache.commons.io.input.BoundedInputStream; 029import org.tukaani.xz.LZMA2Options; 030import org.tukaani.xz.LZMAInputStream; 031 032/** 033 * LZMA decompressor. 034 * 035 * @since 1.6 036 */ 037public class LZMACompressorInputStream extends CompressorInputStream implements InputStreamStatistics { 038 039 // @formatter:off 040 /** 041 * Builds a new {@link LZMACompressorInputStream}. 042 * 043 * <p> 044 * For example: 045 * </p> 046 * <pre>{@code 047 * LZMACompressorOutputStream s = LZMACompressorInputStream.builder() 048 * .setPath(path) 049 * .get(); 050 * } 051 * </pre> 052 * 053 * @see #get() 054 * @see LZMA2Options 055 * @since 1.28.0 056 */ 057 // @formatter:on 058 public static class Builder extends AbstractStreamBuilder<LZMACompressorInputStream, Builder> { 059 060 private int memoryLimitKiB = -1; 061 062 @Override 063 public LZMACompressorInputStream get() throws IOException { 064 return new LZMACompressorInputStream(this); 065 } 066 067 /** 068 * Sets a working memory threshold in kibibytes (KiB). 069 * 070 * @param memoryLimitKiB Sets a working memory threshold in kibibytes (KiB). Processing throws MemoryLimitException if memory use is above this 071 * threshold. 072 * @return this instance. 073 */ 074 public Builder setMemoryLimitKiB(final int memoryLimitKiB) { 075 this.memoryLimitKiB = memoryLimitKiB; 076 return this; 077 } 078 } 079 080 /** 081 * Constructs a new builder of {@link LZMACompressorOutputStream}. 082 * 083 * @return a new builder of {@link LZMACompressorOutputStream}. 084 * @since 1.28.0 085 */ 086 public static Builder builder() { 087 return new Builder(); 088 } 089 090 /** 091 * Checks if the signature matches what is expected for an LZMA file. 092 * 093 * @param signature the bytes to check 094 * @param length the number of bytes to check 095 * @return true, if this stream is an LZMA compressed stream, false otherwise 096 * @since 1.10 097 */ 098 public static boolean matches(final byte[] signature, final int length) { 099 return signature != null && length >= 3 && signature[0] == 0x5d && signature[1] == 0 && signature[2] == 0; 100 } 101 102 private final BoundedInputStream countingStream; 103 104 private final InputStream in; 105 106 @SuppressWarnings("resource") // Caller closes 107 private LZMACompressorInputStream(final Builder builder) throws IOException { 108 try { 109 in = new LZMAInputStream(countingStream = BoundedInputStream.builder().setInputStream(builder.getInputStream()).get(), builder.memoryLimitKiB); 110 } catch (final org.tukaani.xz.MemoryLimitException e) { 111 // convert to Commons Compress exception 112 throw new MemoryLimitException(e.getMemoryNeeded(), e.getMemoryLimit(), (Throwable) e); 113 } 114 } 115 116 /** 117 * Creates a new input stream that decompresses LZMA-compressed data from the specified input stream. 118 * 119 * @param inputStream where to read the compressed data 120 * @throws IOException if the input is not in the .lzma format, the input is corrupt or truncated, the .lzma headers specify sizes that are not supported by 121 * this implementation, or the underlying {@code inputStream} throws an exception 122 */ 123 public LZMACompressorInputStream(final InputStream inputStream) throws IOException { 124 this(builder().setInputStream(inputStream)); 125 } 126 127 /** 128 * Creates a new input stream that decompresses LZMA-compressed data from the specified input stream. 129 * 130 * @param inputStream where to read the compressed data 131 * @param memoryLimitKiB Sets a working memory threshold in kibibytes (KiB). Processing throws MemoryLimitException if memory use is above this threshold. 132 * @throws IOException if the input is not in the .lzma format, the input is corrupt or truncated, the .lzma headers specify sizes that are not supported by 133 * this implementation, or the underlying {@code inputStream} throws an exception 134 * 135 * @since 1.14 136 * @deprecated Use {@link #builder()}. 137 */ 138 @Deprecated 139 public LZMACompressorInputStream(final InputStream inputStream, final int memoryLimitKiB) throws IOException { 140 this(builder().setInputStream(inputStream).setMemoryLimitKiB(memoryLimitKiB)); 141 } 142 143 /** {@inheritDoc} */ 144 @Override 145 public int available() throws IOException { 146 return in.available(); 147 } 148 149 /** {@inheritDoc} */ 150 @Override 151 public void close() throws IOException { 152 in.close(); 153 } 154 155 /** 156 * @since 1.17 157 */ 158 @Override 159 public long getCompressedCount() { 160 return countingStream.getCount(); 161 } 162 163 /** {@inheritDoc} */ 164 @Override 165 public int read() throws IOException { 166 final int ret = in.read(); 167 count(ret == -1 ? 0 : 1); 168 return ret; 169 } 170 171 /** {@inheritDoc} */ 172 @Override 173 public int read(final byte[] buf, final int off, final int len) throws IOException { 174 final int ret = in.read(buf, off, len); 175 count(ret); 176 return ret; 177 } 178 179 /** {@inheritDoc} */ 180 @Override 181 public long skip(final long n) throws IOException { 182 return org.apache.commons.io.IOUtils.skip(in, n); 183 } 184}