GordianLZMAInputStream.java
/*
* GordianKnot: Security Suite
* Copyright 2012-2026. Tony Washer
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package io.github.tonywasher.joceanus.gordianknot.impl.core.stream;
import SevenZip.Compression.LZMA.Decoder;
import SevenZip.Compression.LZMA.Encoder;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* Provides an LZMA decompression InputStream. Due to the design of the 7-Zip libraries the
* decompression must be performed on a separate thread. A thread is created to read the compressed
* data from the input stream, decompress the data and write the output to a PipeStream. This class
* works as the wrapper to read the decompressed data from the PipedStream.
*/
final class GordianLZMAInputStream
extends InputStream {
/**
* The sink stream to write to for the decoder thread.
*/
private final OutputStream theSink;
/**
* The source stream for the decoder thread.
*/
private final InputStream theInput;
/**
* The source stream to read from the decoder thread.
*/
private final InputStream theSource;
/**
* The decoder service.
*/
private final GordianDecoderService theService;
/**
* Constructor.
*
* @param pInput the input stream to wrap
*/
GordianLZMAInputStream(final InputStream pInput) {
/* Store the target */
theInput = pInput;
/* Create the piped stream */
final GordianPipedStream myPipe = new GordianPipedStream();
theSink = myPipe.getSink();
theSource = myPipe.getSource();
/* Create and run the decoder service */
theService = new GordianDecoderService();
theService.start();
}
@Override
public int read(final byte[] pBytes) throws IOException {
/* Read the bytes from the source */
final int myResult = read(pBytes, 0, pBytes.length);
/* Check for error */
theService.checkForError();
/* Read the bytes from the source */
return myResult;
}
@Override
public int read() throws IOException {
/* Read the bytes from the source */
final int myResult = theSource.read();
/* Check for error */
theService.checkForError();
/* Read the bytes from the source */
return myResult;
}
@Override
public int read(final byte[] pBuffer,
final int pOffset,
final int pLength) throws IOException {
/* Read the bytes from the source */
final int myResult = theSource.read(pBuffer, pOffset, pLength);
/* Check for error */
theService.checkForError();
/* Read the bytes from the source */
return myResult;
}
@Override
public void close() throws IOException {
/* Close the source */
theSource.close();
/* Wait for the Decoder service to stop */
try {
theService.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
/**
* Close the input.
*
* @throws IOException on error
*/
void closeInput() throws IOException {
theInput.close();
theSink.close();
}
/**
* Close the input.
*/
void closeInputOnError() {
try {
closeInput();
} catch (IOException e) {
/* Ignore!! */
}
}
/**
* The decoder service.
*/
private final class GordianDecoderService
extends Thread {
/**
* The decoder.
*/
private final Decoder theDecoder;
/**
* The error.
*/
private IOException theError;
/**
* Constructor.
*/
GordianDecoderService() {
/* Create the decoder */
theDecoder = new Decoder();
theError = null;
}
/**
* Check for error.
*
* @throws IOException on error
*/
void checkForError() throws IOException {
if (theError != null) {
throw theError;
}
}
@Override
public void run() {
try {
final byte[] myProperties = new byte[Encoder.kPropSize];
/* Read the decoder properties */
final int n = theInput.read(myProperties, 0, Encoder.kPropSize);
if (n != Encoder.kPropSize) {
theError = new IOException("input stream too short");
return;
}
/* Set the decoder properties */
theDecoder.SetDecoderProperties(myProperties);
/* Decode the stream */
theDecoder.Code(theInput, theSink, -1);
/* Close the input/output streams */
closeInput();
} catch (Exception e) {
theError = e instanceof IOException i ? i : new IOException(e);
closeInputOnError();
}
}
}
}