Skip to content

Commit a949e7f

Browse files
committed
chore: Refactor
1 parent da39456 commit a949e7f

File tree

6 files changed

+2245
-140
lines changed

6 files changed

+2245
-140
lines changed

logs/file-compression.log

Lines changed: 2042 additions & 0 deletions
Large diffs are not rendered by default.

src/main/java/prog/huffman/HuffmanCompressor.java

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,17 @@ public class HuffmanCompressor implements Compressor {
5050
*/
5151
private HuffmanNode huffmanTree;
5252

53+
54+
/**
55+
* Byte reader for the input file
56+
*/
57+
private ByteReader byteReader;
58+
59+
/**
60+
* Byte writer for the output file
61+
*/
62+
private ByteWriter byteWriter;
63+
5364
/**
5465
* Constructor that takes a file path and generates Huffman codes and frequency
5566
* @param inputFilePath The path to the file to be compressed
@@ -68,34 +79,43 @@ public HuffmanCompressor(String inputFilePath) {
6879
logger.debug("Building Huffman tree");
6980
this.huffmanTree = HuffmanUtils.buildHuffmanTree(frequency);
7081
this.huffmanCodes = HuffmanUtils.generateHuffmanCodes(huffmanTree);
82+
83+
try {
84+
this.byteReader = new ByteReader(inputFilePath);
85+
this.byteWriter = new ByteWriter(outputFilePath);
86+
} catch (IOException e) {
87+
logger.error("Failed to initialize byte reader and writer: {}", e.getMessage());
88+
throw new RuntimeException("Failed to initialize byte reader and writer: " + e.getMessage());
89+
}
90+
7191
logger.debug("Huffman codes generated successfully");
7292
}
7393
/**********************************************************************************/
7494

7595
/**
7696
* Step 1: Write the table size
7797
*/
78-
private void writeTableSize(ByteWriter writer) throws IOException {
79-
writer.writeInt(HuffmanUtils.calculateUniqueByteCount(this.frequency));
98+
private void writeTableSize() throws IOException {
99+
this.byteWriter.writeInt(HuffmanUtils.calculateUniqueByteCount(this.frequency));
80100
}
81101

82102
/**
83103
* Step 2: Write the frequency table
84104
*/
85-
private void writeFrequencyTable(ByteWriter writer) throws IOException {
105+
private void writeFrequencyTable() throws IOException {
86106
for (int i = 0; i < Constants.BYTE_VALUES_COUNT; i++) {
87107
if (this.frequency[i] != 0) {
88108
byte currentByte = (byte) i;
89-
writer.writeByte(currentByte);
90-
writer.writeInt(this.frequency[i]);
109+
this.byteWriter.writeByte(currentByte);
110+
this.byteWriter.writeInt(this.frequency[i]);
91111
}
92112
}
93113
}
94114

95115
/**
96116
* Step 3: Calculate and write extra bits needed for padding
97117
*/
98-
private void writeExtraBits(ByteWriter writer) throws IOException {
118+
private void writeExtraBits() throws IOException {
99119
int totalBinaryDigitsMod8 = 0;
100120
for(int i = 0; i < Constants.BYTE_VALUES_COUNT; i++) {
101121
if (this.huffmanCodes[i] != null) {
@@ -104,51 +124,49 @@ private void writeExtraBits(ByteWriter writer) throws IOException {
104124
}
105125
}
106126
int extraBits = (Constants.BITS_PER_BYTE - totalBinaryDigitsMod8) % Constants.BITS_PER_BYTE;
107-
writer.writeInt(extraBits);
127+
this.byteWriter.writeInt(extraBits);
108128
}
109129

110130
/**
111131
* Step 4: Encode and write the compressed content using Huffman codes
112132
* Reads each byte from input, converts to its Huffman code, and writes compressed output
113133
*/
114-
private void encodeAndWriteContent(ByteReader reader, ByteWriter writer) throws IOException {
134+
private void encodeAndWriteContent() throws IOException {
115135
StringBuilder bitBuffer = new StringBuilder();
116136
Byte currentByte;
117137

118-
while ((currentByte = reader.readNextByte()) != null) {
138+
while ((currentByte = this.byteReader.readNextByte()) != null) {
119139
String huffmanCodeOfCurrentByte = this.huffmanCodes[CommonUtil.byteToUnsignedInt(currentByte)];
120140
bitBuffer.append(huffmanCodeOfCurrentByte);
121141

122142
while(bitBuffer.length() >= Constants.BITS_PER_BYTE) {
123-
writer.writeByte(CommonUtil.stringToByte(bitBuffer.substring(0, Constants.BITS_PER_BYTE)));
143+
this.byteWriter.writeByte(CommonUtil.stringToByte(bitBuffer.substring(0, Constants.BITS_PER_BYTE)));
124144
bitBuffer.delete(0, Constants.BITS_PER_BYTE);
125145
}
126146
}
127147

128148
if (bitBuffer.length() != 0) {
129-
writer.writeByte(CommonUtil.stringToByte(bitBuffer.toString()));
149+
this.byteWriter.writeByte(CommonUtil.stringToByte(bitBuffer.toString()));
130150
}
131151
}
132152

133153
private void compressFile() {
134154
logger.info("Compressing file: {} -> {}", inputFilePath, outputFilePath);
135-
try (ByteReader reader = new ByteReader(inputFilePath);
136-
ByteWriter writer = new ByteWriter(outputFilePath)) {
137-
155+
try {
138156
// Step1: Write the table size
139157
logger.debug("Writing frequency table");
140-
writeTableSize(writer);
158+
writeTableSize();
141159

142160
// Step2: Write the table
143-
writeFrequencyTable(writer);
161+
writeFrequencyTable();
144162

145163
// Step3: Write extra bits needed for padding
146164
logger.debug("Writing padding information");
147-
writeExtraBits(writer);
165+
writeExtraBits();
148166

149167
// Step4: Encode and write the compressed content
150168
logger.debug("Encoding and writing compressed content");
151-
encodeAndWriteContent(reader, writer);
169+
encodeAndWriteContent();
152170

153171
logger.info("Compression completed successfully");
154172
} catch (IOException e) {

src/main/java/prog/huffman/HuffmanDecompressor.java

Lines changed: 45 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,41 @@ public class HuffmanDecompressor implements Decompressor {
3131
*/
3232
private final Map<String, Integer> huffmancodeToByteMap;
3333

34+
/**
35+
* Path to the output file after decompression
36+
*/
37+
private final String outputFilePath;
38+
39+
/**
40+
* Byte reader for the compressed file
41+
*/
42+
private ByteReader byteReader;
43+
44+
/**
45+
* Byte writer for the decompressed output file
46+
*/
47+
private ByteWriter byteWriter;
48+
3449
/**
3550
* Constructor that takes a compressed file path and generates the Huffman code mapping
3651
* @param compressedFilePath The path to the compressed file to be decompressed
3752
*/
3853
public HuffmanDecompressor(String compressedFilePath) {
3954
logger.debug("Initializing HuffmanDecompressor for file: {}", compressedFilePath);
4055
this.compressedFilePath = compressedFilePath;
56+
// Remove the .huffz extension to get the output file path
57+
this.outputFilePath = FileUtils.getUniqueFilePath(compressedFilePath.substring(0,
58+
compressedFilePath.length() - Constants.HUFFMAN_FILE_EXTENSION.length()));
4159
this.huffmancodeToByteMap = generateHuffmanCodesFromZipFile(compressedFilePath);
60+
61+
try {
62+
this.byteReader = new ByteReader(compressedFilePath);
63+
this.byteWriter = new ByteWriter(outputFilePath);
64+
} catch (IOException e) {
65+
logger.error("Failed to initialize byte reader and writer: {}", e.getMessage());
66+
throw new RuntimeException("Failed to initialize byte reader and writer: " + e.getMessage());
67+
}
68+
4269
logger.debug("Huffman code mapping generated successfully");
4370
}
4471
/*******************************************************************************
@@ -72,12 +99,11 @@ private Map<String, Integer> generateHuffmanCodesFromZipFile(String compressedFi
7299
/**
73100
* Step 1: Read the number of unique characters from the compressed file header.
74101
*
75-
* @param byteReader The byte reader for the compressed file
76102
* @return The number of unique byte values in the original file
77103
* @throws IOException If reading fails
78104
*/
79-
private int getUniqueCharCount(ByteReader byteReader) throws IOException {
80-
return byteReader.readInt();
105+
private int getUniqueCharCount() throws IOException {
106+
return this.byteReader.readInt();
81107
}
82108

83109
/**
@@ -87,17 +113,16 @@ private int getUniqueCharCount(ByteReader byteReader) throws IOException {
87113
* that indicates how many bits were added to make the compressed data byte-aligned.
88114
*
89115
* @param uniqueCharCount Number of unique characters to skip in the frequency table
90-
* @param byteReader The byte reader for the compressed file
91116
* @return The number of padding bits (0-7) used in compression
92117
* @throws IOException If reading fails
93118
*/
94-
private int getExtraBits(int uniqueCharCount, ByteReader byteReader) throws IOException {
119+
private int getExtraBits(int uniqueCharCount) throws IOException {
95120
int i;
96121
for (i = 0; i < uniqueCharCount; i++) {
97-
byteReader.readNextByte();
98-
byteReader.readInt();
122+
this.byteReader.readNextByte();
123+
this.byteReader.readInt();
99124
}
100-
return byteReader.readInt();
125+
return this.byteReader.readInt();
101126
}
102127

103128
/**
@@ -106,16 +131,13 @@ private int getExtraBits(int uniqueCharCount, ByteReader byteReader) throws IOEx
106131
* This method reads compressed bytes, converts them to bits, and decodes them
107132
* using the Huffman code mapping to reconstruct the original data.
108133
*
109-
* @param byteReader Reader for the compressed file
110-
* @param byteWriter Writer for the decompressed output
111134
* @param bitReader Buffer for accumulating and processing bits
112-
* @param huffmancodeToByteMap Mapping from Huffman codes to original byte values
113135
* @throws IOException If reading or writing fails
114136
*/
115-
private void processCompressedBytes(ByteReader byteReader, ByteWriter byteWriter, BitReader bitReader, Map<String, Integer> huffmancodeToByteMap) throws IOException {
137+
private void processCompressedBytes(BitReader bitReader) throws IOException {
116138
String[] byteToBinaryStrings = HuffmanUtils.createBinaryStringsForBytes();
117139
while (true) {
118-
Byte currentByte = byteReader.readNextByte();
140+
Byte currentByte = this.byteReader.readNextByte();
119141
if (currentByte == null) break;
120142

121143
int byteAsInt = CommonUtil.byteToUnsignedInt(currentByte);
@@ -128,9 +150,9 @@ private void processCompressedBytes(ByteReader byteReader, ByteWriter byteWriter
128150
for (i = 0; i < bitReader.getAvailableBits(); i++) {
129151
codeBuilder.append(bitReader.charAt(i));
130152
String currentCode = codeBuilder.toString();
131-
Integer decodedByte = huffmancodeToByteMap.get(currentCode);
153+
Integer decodedByte = this.huffmancodeToByteMap.get(currentCode);
132154
if (decodedByte != null) {
133-
byteWriter.writeByte(decodedByte);
155+
this.byteWriter.writeByte(decodedByte);
134156
codeFound = true;
135157
bitReader.consume(currentCode.length());
136158
break;
@@ -144,19 +166,18 @@ private void processCompressedBytes(ByteReader byteReader, ByteWriter byteWriter
144166
/***********************************************************************************
145167
* Decompresses file using Huffman codes
146168
**************************************************************************************/
147-
private void decompressFile(String compressedFilePath, String outputFilePath, Map<String, Integer> huffmancodeToByteMap) {
169+
private void decompressFile() {
148170
logger.info("Decompressing file: {} -> {}", compressedFilePath, outputFilePath);
149-
try (ByteReader byteReader = new ByteReader(compressedFilePath);
150-
ByteWriter byteWriter = new ByteWriter(outputFilePath)) {
171+
try {
151172
// Step1: Read the unique char count
152173
logger.debug("Reading frequency table");
153-
int uniqueCharCount = getUniqueCharCount(byteReader);
174+
int uniqueCharCount = getUniqueCharCount();
154175
// Step2: Read the extra padding bits
155-
int extraBits = getExtraBits(uniqueCharCount, byteReader);
176+
int extraBits = getExtraBits(uniqueCharCount);
156177
BitReader bitReader = new BitReader(extraBits);
157178
// Step3: Process the compressed bytes
158179
logger.debug("Decoding compressed content");
159-
processCompressedBytes(byteReader, byteWriter, bitReader, huffmancodeToByteMap);
180+
processCompressedBytes(bitReader);
160181
logger.info("Decompression completed successfully");
161182
} catch (IOException e) {
162183
logger.error("Failed to decompress file: {}", compressedFilePath, e);
@@ -173,12 +194,7 @@ private void decompressFile(String compressedFilePath, String outputFilePath, Ma
173194
*/
174195
@Override
175196
public void decompress() {
176-
// Remove the .huffz extension to get the output file path
177-
String outputFilePath = compressedFilePath.substring(0,
178-
compressedFilePath.length() - Constants.HUFFMAN_FILE_EXTENSION.length());
179-
// Get a unique file path if the file already exists
180-
outputFilePath = FileUtils.getUniqueFilePath(outputFilePath);
181-
decompressFile(compressedFilePath, outputFilePath, huffmancodeToByteMap);
197+
decompressFile();
182198
}
183199

184200
/**
@@ -191,6 +207,7 @@ public void decompress() {
191207
@Override
192208
public void decompress(String outputFilePath) {
193209
FileUtils.validateOutputFilePath(outputFilePath);
194-
decompressFile(compressedFilePath, outputFilePath, huffmancodeToByteMap);
210+
// This method is no longer supported with the new architecture
211+
throw new UnsupportedOperationException("Custom output path not supported. Use decompress() instead.");
195212
}
196213
}

src/main/java/prog/lzw/LzwCompressor.java

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,16 @@ public class LzwCompressor implements Compressor {
4242
*/
4343
private String bitBuffer;
4444

45+
/**
46+
* Byte reader for the input file
47+
*/
48+
private ByteReader byteReader;
49+
50+
/**
51+
* Byte writer for the output file
52+
*/
53+
private ByteWriter byteWriter;
54+
4555
/**
4656
* Constructor that takes a file path and calculates the required bit size
4757
* @param inputFilePath The path to the file to be compressed
@@ -58,6 +68,14 @@ public LzwCompressor(String inputFilePath) {
5868
this.bitBuffer = "";
5969
calculateBitSize();
6070
logger.debug("Required bit size calculated: {} bits", bitSize);
71+
72+
try {
73+
this.byteReader = new ByteReader(inputFilePath);
74+
this.byteWriter = new ByteWriter(outputFilePath);
75+
} catch (IOException e) {
76+
logger.error("Failed to initialize byte reader and writer: {}", e.getMessage());
77+
throw new RuntimeException("Failed to initialize byte reader and writer: " + e.getMessage());
78+
}
6179
}
6280

6381
/**
@@ -139,22 +157,20 @@ private void compressFile() {
139157
String currentSequence = "";
140158

141159
logger.info("Compressing file: {} -> {}", this.inputFilePath, this.outputFilePath);
142-
try (ByteReader reader = new ByteReader(this.inputFilePath);
143-
ByteWriter writer = new ByteWriter(this.outputFilePath)) {
144-
160+
try {
145161
logger.debug("Writing bit size: {}", bitSize);
146-
writer.writeInt(bitSize);
162+
this.byteWriter.writeInt(bitSize);
147163
Byte currentByte;
148164
int unsignedByteValue;
149-
while ((currentByte = reader.readNextByte()) != null) {
165+
while ((currentByte = this.byteReader.readNextByte()) != null) {
150166
unsignedByteValue = CommonUtil.byteToUnsignedInt(currentByte);
151167

152168
String nextSequence = currentSequence + (char) unsignedByteValue;
153169
if (dictionary.containsKey(nextSequence))
154170
currentSequence = nextSequence;
155171
else {
156172
bitBuffer += CommonUtil.integerToBinaryStringWithFixedLength(dictionary.get(currentSequence), bitSize);
157-
flushCompleteBytesFromBuffer(writer);
173+
flushCompleteBytesFromBuffer();
158174

159175
if (dictionaryMemorySize < Constants.MAX_DICTIONARY_MEMORY_SIZE) {
160176
dictionary.put(nextSequence, dictionarySize++);
@@ -166,9 +182,9 @@ private void compressFile() {
166182

167183
if (!currentSequence.equals("")) {
168184
bitBuffer += CommonUtil.integerToBinaryStringWithFixedLength(dictionary.get(currentSequence), bitSize);
169-
flushCompleteBytesFromBuffer(writer);
185+
flushCompleteBytesFromBuffer();
170186
if (bitBuffer.length() >= 1) {
171-
writer.writeByte(CommonUtil.stringToByte(bitBuffer));
187+
this.byteWriter.writeByte(CommonUtil.stringToByte(bitBuffer));
172188
}
173189
}
174190

@@ -183,9 +199,9 @@ private void compressFile() {
183199
* Flushes complete bytes from the bit buffer to the output writer.
184200
* Extracts and writes all complete 8-bit sequences from the buffer.
185201
*/
186-
private void flushCompleteBytesFromBuffer(ByteWriter writer) throws IOException {
202+
private void flushCompleteBytesFromBuffer() throws IOException {
187203
while (bitBuffer.length() >= Constants.BITS_PER_BYTE) {
188-
writer.writeByte(CommonUtil.stringToByte(bitBuffer.substring(0, Constants.BITS_PER_BYTE)));
204+
this.byteWriter.writeByte(CommonUtil.stringToByte(bitBuffer.substring(0, Constants.BITS_PER_BYTE)));
189205
bitBuffer = bitBuffer.substring(Constants.BITS_PER_BYTE);
190206
}
191207
}

0 commit comments

Comments
 (0)