-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.java
More file actions
326 lines (272 loc) · 13.6 KB
/
main.java
File metadata and controls
326 lines (272 loc) · 13.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.nio.file.*;
import java.util.ArrayList;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
/**
* FIP-Kompressor/Dekompressor Applikation.
* Implementiert eine grafische Benutzeroberfläche (GUI) im Dark Mode unter Verwendung von Swing
* zur Komprimierung und Dekomprimierung von Dateien mit Run-Length Encoding (RLE).
*/
public class FipApp extends JFrame {
// --- GUI Komponenten ---
// Textbereich zur Anzeige von Statusmeldungen
private final JTextArea statusArea = new JTextArea("Bereit...");
// Dialog zur Dateiauswahl
private final JFileChooser fileChooser = new JFileChooser();
// --- Farben für Dark Mode ---
private static final Color DARK_BACKGROUND = new Color(30, 30, 30);
private static final Color MEDIUM_BACKGROUND = new Color(45, 45, 45);
private static final Color LIGHT_TEXT = new Color(240, 240, 240);
private static final Color BLUE_ACCENT = new Color(60, 130, 250); // Für Komprimierung
private static final Color GREEN_ACCENT = new Color(50, 200, 100); // Für Dekomprimierung
private static final Color RED_ERROR = new Color(255, 100, 100);
public FipApp() {
// Fenster-Setup
setTitle("FIP-Kompressor/Dekompressor (Java Dark Mode)");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(800, 500);
setLocationRelativeTo(null); // Zentrieren
getContentPane().setBackground(DARK_BACKGROUND);
// Styling (optional: System LookAndFeel versuchen)
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
// Standard-LookAndFeel verwenden
}
// Haupt-Panel für Komprimierung und Dekomprimierung (Grid Layout)
JPanel mainPanel = new JPanel(new GridLayout(1, 2, 20, 0));
mainPanel.setBackground(DARK_BACKGROUND);
mainPanel.setBorder(new EmptyBorder(20, 20, 20, 20));
// Header-Bereich
JLabel headerLabel = new JLabel("<html><h1 style='color: rgb(60, 130, 250); font-size: 24pt;'>FIP-Komprimierungs-Tool</h1><p style='color: #a0a0a0;'>Run-Length Encoding auf Byte-Ebene (RLE)</p></html>", SwingConstants.CENTER);
headerLabel.setBorder(new EmptyBorder(10, 0, 20, 0));
// Status- und Log-Bereich am unteren Rand
statusArea.setEditable(false);
statusArea.setBackground(MEDIUM_BACKGROUND);
statusArea.setForeground(LIGHT_TEXT);
statusArea.setBorder(BorderFactory.createLineBorder(BLUE_ACCENT.darker(), 1));
statusArea.setLineWrap(true);
statusArea.setWrapStyleWord(true);
statusArea.setFont(new Font("Monospaced", Font.PLAIN, 12));
JScrollPane scrollPane = new JScrollPane(statusArea);
scrollPane.setPreferredSize(new Dimension(800, 80));
// 1. Komprimierungs-Panel erstellen
JPanel compressPanel = createPanel("Komprimieren und .fip erstellen", BLUE_ACCENT, this::handleCompression);
mainPanel.add(compressPanel);
// 2. Dekomprimierungs-Panel erstellen
JPanel decompressPanel = createPanel("Dekomprimieren und Original wiederherstellen", GREEN_ACCENT, this::handleDecompression);
mainPanel.add(decompressPanel);
// Gesamt-Layout des Fensters
setLayout(new BorderLayout());
add(headerLabel, BorderLayout.NORTH);
add(mainPanel, BorderLayout.CENTER);
add(scrollPane, BorderLayout.SOUTH);
}
/**
* Erstellt ein stylisches Panel für eine der Funktionen.
*/
private JPanel createPanel(String buttonText, Color color, ActionListener listener) {
JPanel panel = new JPanel(new BorderLayout(10, 10));
panel.setBackground(MEDIUM_BACKGROUND);
panel.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(color.darker(), 2),
BorderFactory.createEmptyBorder(15, 15, 15, 15)
));
// Titel
JLabel title = new JLabel(buttonText.split(" und ")[0], SwingConstants.CENTER);
title.setForeground(color);
title.setFont(new Font("Inter", Font.BOLD, 18));
panel.add(title, BorderLayout.NORTH);
// Button
JButton button = new JButton(buttonText);
button.setBackground(color);
button.setForeground(Color.WHITE);
button.setFocusPainted(false);
button.setBorder(BorderFactory.createEmptyBorder(10, 20, 10, 20));
button.setFont(new Font("Inter", Font.BOLD, 14));
button.addActionListener(listener);
// Anweisung
JLabel instruction = new JLabel("<html><p style='text-align: center; color: #b0b0b0;'>Klicken Sie, um eine Datei auszuwählen.</p></html>", SwingConstants.CENTER);
panel.add(instruction, BorderLayout.CENTER);
panel.add(button, BorderLayout.SOUTH);
return panel;
}
/**
* Aktualisiert den Statusbereich mit einer Nachricht und optionaler Farbe.
*/
private void updateStatus(String message, Color color) {
statusArea.setText(message);
statusArea.setForeground(color);
}
// --- HANDLER FÜR KOMPRIMIERUNG UND DEKOMPRIMIERUNG ---
/**
* Wählt die Datei aus und startet die Komprimierung.
*/
private void handleCompression(ActionEvent e) {
updateStatus("Bitte wählen Sie die zu komprimierende Datei aus...", LIGHT_TEXT);
fileChooser.setDialogTitle("Datei zum Komprimieren auswählen");
fileChooser.setFileFilter(null); // Alle Dateien
if (fileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
File inputFile = fileChooser.getSelectedFile();
Path inputPath = inputFile.toPath();
updateStatus("Komprimiere " + inputFile.getName() + "...", BLUE_ACCENT);
try {
// 1. Datei lesen (als Byte-Array)
byte[] originalData = Files.readAllBytes(inputPath);
// 2. Daten komprimieren
byte[] compressedData = RLECompressor.compressData(originalData);
// 3. Zieldateipfad erstellen (.fip)
String originalName = inputFile.getName();
String baseName = originalName.contains(".")
? originalName.substring(0, originalName.lastIndexOf('.'))
: originalName;
String fipFileName = baseName + ".fip";
// Speichere die .fip-Datei im selben Verzeichnis wie die Originaldatei
Path outputPath = Paths.get(inputFile.getParent(), fipFileName);
// 4. Komprimierte Daten schreiben
Files.write(outputPath, compressedData);
double compressionRatio = (1.0 - (double)compressedData.length / originalData.length) * 100;
updateStatus(
String.format("Erfolgreich komprimiert! '%s' erstellt.\nOriginalgröße: %d Bytes, FIP-Größe: %d Bytes. (Ersparnis: %.2f%%)",
fipFileName,
originalData.length,
compressedData.length,
compressionRatio
),
GREEN_ACCENT
);
} catch (IOException ex) {
updateStatus("FEHLER beim Komprimieren (E/A-Fehler): " + ex.getMessage(), RED_ERROR);
ex.printStackTrace();
}
} else {
updateStatus("Komprimierung abgebrochen.", LIGHT_TEXT);
}
}
/**
* Wählt die .fip-Datei aus und startet die Dekomprimierung.
*/
private void handleDecompression(ActionEvent e) {
updateStatus("Bitte wählen Sie die zu dekomprimierende .fip-Datei aus...", LIGHT_TEXT);
fileChooser.setDialogTitle(".fip-Datei auswählen");
// Dateifilter nur für .fip
fileChooser.setFileFilter(new javax.swing.filechooser.FileNameExtensionFilter("FIP-Dateien (*.fip)", "fip"));
if (fileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
File fipFile = fileChooser.getSelectedFile();
Path fipPath = fipFile.toPath();
updateStatus("Dekomprimiere " + fipFile.getName() + "...", GREEN_ACCENT);
try {
// 1. .fip-Datei lesen (als Byte-Array)
byte[] compressedData = Files.readAllBytes(fipPath);
// 2. Daten dekomprimieren
byte[] decompressedData = RLECompressor.decompressData(compressedData);
// 3. Zieldateipfad erstellen (.orig)
String fipFileName = fipFile.getName();
// Ersetze das .fip-Endung und füge .orig hinzu, um Namenskonflikte zu vermeiden
String baseName = fipFileName.replaceFirst("(?i)\\.fip$", "");
String originalFileName = baseName + ".orig";
Path outputPath = Paths.get(fipFile.getParent(), originalFileName);
// 4. Dekomprimierte Daten schreiben
Files.write(outputPath, decompressedData);
updateStatus(
String.format("Erfolgreich dekomprimiert! '%s' erstellt.\nFIP-Größe: %d Bytes, Originalgröße: %d Bytes.",
originalFileName,
compressedData.length,
decompressedData.length
),
BLUE_ACCENT
);
} catch (IOException ex) {
updateStatus("FEHLER beim Dekomprimieren (E/A-Fehler): " + ex.getMessage(), RED_ERROR);
ex.printStackTrace();
} catch (Exception ex) {
updateStatus("FEHLER beim Dekomprimieren: " + ex.getMessage(), RED_ERROR);
ex.printStackTrace();
}
} else {
updateStatus("Dekomprimierung abgebrochen.", LIGHT_TEXT);
}
// Dateifilter zurücksetzen
fileChooser.setFileFilter(null);
}
// --- RLE-KOMPRESSIONS-/DEKOMPRESSIONS-LOGIK ---
/**
* Statische Klasse für die Run-Length Encoding Logik.
* Arbeitet direkt mit byte[].
*/
static class RLECompressor {
/**
* Komprimiert die Daten eines Byte-Arrays mithilfe einer einfachen Run-Length Encoding (RLE).
* Der maximale Zähler ist 127, da Java's byte-Typ vorzeichenbehaftet ist.
*/
public static byte[] compressData(byte[] data) {
if (data == null || data.length == 0) return new byte[0];
ArrayList<Byte> compressed = new ArrayList<>();
int i = 0;
// Maximale Lauflänge ist 127 (Maximalwert von signed byte)
final int MAX_RUN = 127;
while (i < data.length) {
byte currentByte = data[i];
int count = 0;
int j = i;
// Zähle, wie oft das aktuelle Byte wiederholt wird (maximal bis MAX_RUN)
while (j < data.length && data[j] == currentByte && count < MAX_RUN) {
count++;
j++;
}
// Füge das Paar [Zähler (Byte), Wert (Byte)] zum komprimierten Array hinzu
compressed.add((byte) count); // Zähler (von 1 bis 127)
compressed.add(currentByte); // Wert (der Byte-Wert selbst)
// Springe zur nächsten nicht verarbeiteten Position
i = j;
}
// Konvertiere ArrayList<Byte> zurück in byte[]
byte[] result = new byte[compressed.size()];
for (int k = 0; k < compressed.size(); k++) {
result[k] = compressed.get(k);
}
return result;
}
/**
* Dekomprimiert die RLE-Daten.
*/
public static byte[] decompressData(byte[] data) throws Exception {
if (data == null || data.length == 0) return new byte[0];
// Die Daten müssen eine gerade Anzahl von Bytes haben ([count, value]-Paare).
if (data.length % 2 != 0) {
throw new Exception("Ungültiges .fip-Format: Die komprimierte Datei ist beschädigt (ungerade Bytezahl).");
}
// Wir nutzen eine dynamische Liste, da wir die endgültige Größe nicht kennen
ArrayList<Byte> decompressed = new ArrayList<>();
// Durchlaufe die Daten in Paaren von 2 Bytes (Zähler und Wert)
for (int i = 0; i < data.length; i += 2) {
// Konvertiere das signed Java byte in einen positiven Zähler-Integer.
// Da wir in compressData auf 127 begrenzt haben, ist data[i] & 0xFF optional,
// aber eine gute Praxis, falls die RLE-Implementierung in Zukunft 0-255 unterstützt.
int count = data[i] & 0xFF;
byte value = data[i + 1];
// Wiederhole den Wert so oft, wie der Zähler angibt
for (int k = 0; k < count; k++) {
decompressed.add(value);
}
}
// Konvertiere ArrayList<Byte> zurück in byte[]
byte[] result = new byte[decompressed.size()];
for (int k = 0; k < decompressed.size(); k++) {
result[k] = decompressed.get(k);
}
return result;
}
}
// --- MAIN-METHODE ---
public static void main(String[] args) {
// Stellt sicher, dass die GUI im Event Dispatch Thread ausgeführt wird
SwingUtilities.invokeLater(() -> {
FipApp app = new FipApp();
app.setVisible(true);
});
}
}