Skip to content

Commit 3beaf3e

Browse files
committed
chore: Refactor
1 parent da39456 commit 3beaf3e

File tree

11 files changed

+2248
-156
lines changed

11 files changed

+2248
-156
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,5 @@ target/
2222
*.war
2323
*.ear
2424
*.class
25+
26+
logs/file-compression.log

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ mvn exec:java
124124
### Using JAR directly
125125
After building with Maven, you can run the JAR:
126126
```bash
127-
java -jar target/file-compression-1.0-SNAPSHOT-jar-with-dependencies.jar
127+
java -jar target/file-compression-2.0-SNAPSHOT-jar-with-dependencies.jar
128128
```
129129

130130
![Outlook](/git_resource/outlook.png?raw=true "File Compression GUI")

logs/file-compression.log

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

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>prog</groupId>
88
<artifactId>file-compression</artifactId>
9-
<version>1.0-SNAPSHOT</version>
9+
<version>2.0-SNAPSHOT</version>
1010

1111
<properties>
1212
<maven.compiler.source>21</maven.compiler.source>

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: 43 additions & 40 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,24 +194,6 @@ 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);
182-
}
183-
184-
/**
185-
* Decompresses the file to a specific output path
186-
*
187-
* @param outputFilePath The path where the decompressed file will be saved
188-
* @throws RuntimeException if decompression fails
189-
* @throws IllegalArgumentException if outputFilePath is null or invalid
190-
*/
191-
@Override
192-
public void decompress(String outputFilePath) {
193-
FileUtils.validateOutputFilePath(outputFilePath);
194-
decompressFile(compressedFilePath, outputFilePath, huffmancodeToByteMap);
197+
decompressFile();
195198
}
196199
}

src/main/java/prog/huffman/HuffmanUtils.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,6 @@ public static String[] generateHuffmanCodes(HuffmanNode node) {
198198
* }</pre>
199199
*
200200
* @return An array of Constants.BYTE_VALUES_COUNT strings where index i contains the binary representation of byte value i
201-
* @since 1.0
202201
*/
203202
public static String[] createBinaryStringsForBytes() {
204203
String[] byteToBinaryStrings = new String[Constants.BYTE_VALUES_COUNT];

0 commit comments

Comments
 (0)