Skip to content

Commit 56af7b8

Browse files
vogellaclaude
andcommitted
Add script to extract traditional Eclipse icons for backup/restore
ExtractTraditionalIcons.java reads icon-mapping.json and extracts every referenced icon from an existing Eclipse installation (JAR and directory plugins) into iconpacks/eclipse-traditional-icons/, preserving the plugin-relative path structure. It also writes icon-restore-mapping.json so a restore script knows which dual-tone icon replaced each original. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 061d7e1 commit 56af7b8

2 files changed

Lines changed: 288 additions & 0 deletions

File tree

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
import java.io.*;
2+
import java.nio.file.*;
3+
import java.util.*;
4+
import java.util.jar.*;
5+
import java.util.regex.*;
6+
7+
/**
8+
* Extracts the original (traditional) Eclipse icons referenced in
9+
* iconpacks/eclipse-dual-tone/icon-mapping.json from an existing Eclipse
10+
* installation, storing them under iconpacks/eclipse-traditional-icons/.
11+
*
12+
* Also produces icon-restore-mapping.json in that folder, which maps every
13+
* Eclipse-relative icon path back to the dual-tone icon name that replaced it.
14+
* A restore script can use that file to undo the dual-tone replacement.
15+
*
16+
* Usage:
17+
* java ExtractTraditionalIcons.java <eclipse-dir> [icon-mapping.json] [output-dir]
18+
*
19+
* Defaults:
20+
* icon-mapping.json -> iconpacks/eclipse-dual-tone/icon-mapping.json
21+
* output-dir -> iconpacks/eclipse-traditional-icons
22+
*/
23+
public class ExtractTraditionalIcons {
24+
25+
public static void main(String[] args) throws Exception {
26+
if (args.length < 1) {
27+
System.err.println("Usage: java ExtractTraditionalIcons.java <eclipse-dir> [icon-mapping.json] [output-dir]");
28+
System.exit(1);
29+
}
30+
31+
Path eclipseDir = Path.of(args[0]);
32+
Path mappingFile = Path.of(args.length > 1 ? args[1] : "iconpacks/eclipse-dual-tone/icon-mapping.json");
33+
Path outputDir = Path.of(args.length > 2 ? args[2] : "iconpacks/eclipse-traditional-icons");
34+
Path pluginsDir = eclipseDir.resolve("plugins");
35+
36+
if (!Files.isDirectory(pluginsDir)) {
37+
System.err.println("Eclipse plugins directory not found: " + pluginsDir);
38+
System.exit(1);
39+
}
40+
41+
System.out.println("Eclipse dir : " + eclipseDir);
42+
System.out.println("Mapping file : " + mappingFile);
43+
System.out.println("Output dir : " + outputDir);
44+
System.out.println();
45+
46+
Map<String, List<String>> iconMapping = parseIconMapping(mappingFile);
47+
48+
// Collect all unique Eclipse icon paths (values in the mapping).
49+
// Preserve insertion order so the output mapping stays readable.
50+
Set<String> allEclipsePaths = new LinkedHashSet<>();
51+
for (List<String> paths : iconMapping.values()) {
52+
allEclipsePaths.addAll(paths);
53+
}
54+
55+
Files.createDirectories(outputDir);
56+
57+
// Index: pluginId -> matching path (dir or jar) in the Eclipse plugins folder.
58+
// Built lazily and cached to avoid repeated directory scans.
59+
Map<String, Path> pluginCache = new HashMap<>();
60+
List<Path> pluginEntries = Files.list(pluginsDir).toList();
61+
62+
int extracted = 0;
63+
int missing = 0;
64+
65+
// restoreMapping: eclipsePath -> dualToneIconName
66+
Map<String, String> restoreMapping = new LinkedHashMap<>();
67+
68+
// Build the dual-tone lookup once (eclipse path -> dual-tone key)
69+
Map<String, String> eclipsePathToDualTone = new LinkedHashMap<>();
70+
for (Map.Entry<String, List<String>> entry : iconMapping.entrySet()) {
71+
for (String eclipsePath : entry.getValue()) {
72+
eclipsePathToDualTone.put(eclipsePath, entry.getKey());
73+
}
74+
}
75+
76+
for (String eclipsePath : allEclipsePaths) {
77+
int slash = eclipsePath.indexOf('/');
78+
if (slash < 0) {
79+
System.err.println("Skipping malformed path: " + eclipsePath);
80+
continue;
81+
}
82+
String pluginId = eclipsePath.substring(0, slash);
83+
String iconRelPath = eclipsePath.substring(slash + 1);
84+
85+
Path pluginEntry = pluginCache.computeIfAbsent(pluginId,
86+
id -> findPlugin(pluginEntries, id));
87+
88+
if (pluginEntry == null) {
89+
System.err.println(" MISSING plugin : " + pluginId);
90+
missing++;
91+
continue;
92+
}
93+
94+
Path dest = outputDir.resolve(eclipsePath);
95+
boolean ok;
96+
if (Files.isDirectory(pluginEntry)) {
97+
ok = extractFromDirectory(pluginEntry, iconRelPath, dest);
98+
} else {
99+
ok = extractFromJar(pluginEntry, iconRelPath, dest);
100+
}
101+
102+
if (ok) {
103+
restoreMapping.put(eclipsePath, eclipsePathToDualTone.get(eclipsePath));
104+
System.out.println(" OK " + eclipsePath);
105+
extracted++;
106+
} else {
107+
System.err.println(" MISSING icon : " + eclipsePath + " (in " + pluginEntry.getFileName() + ")");
108+
missing++;
109+
}
110+
}
111+
112+
Path restoreMappingFile = outputDir.resolve("icon-restore-mapping.json");
113+
writeRestoreMapping(restoreMappingFile, restoreMapping);
114+
115+
System.out.println();
116+
System.out.println("Extracted : " + extracted + " / " + allEclipsePaths.size());
117+
System.out.println("Missing : " + missing);
118+
System.out.println("Restore mapping -> " + restoreMappingFile);
119+
}
120+
121+
// -------------------------------------------------------------------------
122+
// Plugin lookup
123+
// -------------------------------------------------------------------------
124+
125+
/** Finds the first directory or JAR in {@code entries} whose name matches {@code pluginId}. */
126+
private static Path findPlugin(List<Path> entries, String pluginId) {
127+
for (Path entry : entries) {
128+
String name = entry.getFileName().toString();
129+
// Matches: "org.eclipse.foo", "org.eclipse.foo_1.2.3.jar", "org.eclipse.foo_1.2.3/"
130+
if (name.equals(pluginId)
131+
|| name.startsWith(pluginId + "_")
132+
|| name.equals(pluginId + ".jar")) {
133+
return entry;
134+
}
135+
}
136+
return null;
137+
}
138+
139+
// -------------------------------------------------------------------------
140+
// Extraction helpers
141+
// -------------------------------------------------------------------------
142+
143+
private static boolean extractFromDirectory(Path pluginDir, String iconRelPath, Path dest) throws IOException {
144+
Path source = pluginDir.resolve(iconRelPath);
145+
if (!Files.exists(source)) return false;
146+
Files.createDirectories(dest.getParent());
147+
Files.copy(source, dest, StandardCopyOption.REPLACE_EXISTING);
148+
return true;
149+
}
150+
151+
private static boolean extractFromJar(Path jarPath, String iconRelPath, Path dest) throws IOException {
152+
try (JarFile jar = new JarFile(jarPath.toFile())) {
153+
JarEntry entry = jar.getJarEntry(iconRelPath);
154+
if (entry == null) return false;
155+
Files.createDirectories(dest.getParent());
156+
try (InputStream is = jar.getInputStream(entry)) {
157+
Files.copy(is, dest, StandardCopyOption.REPLACE_EXISTING);
158+
}
159+
return true;
160+
}
161+
}
162+
163+
// -------------------------------------------------------------------------
164+
// JSON parsing (no external library required)
165+
// -------------------------------------------------------------------------
166+
167+
/**
168+
* Parses the icon-mapping.json format:
169+
* <pre>
170+
* {
171+
* "key": ["path/a.svg", "path/b.svg"],
172+
* ...
173+
* }
174+
* </pre>
175+
*/
176+
private static Map<String, List<String>> parseIconMapping(Path file) throws IOException {
177+
String content = Files.readString(file);
178+
Map<String, List<String>> result = new LinkedHashMap<>();
179+
180+
// Match each "key": [...] block
181+
Pattern blockPattern = Pattern.compile(
182+
"\"([^\"]+)\"\\s*:\\s*\\[([^\\]]*?)\\]",
183+
Pattern.DOTALL);
184+
Pattern stringPattern = Pattern.compile("\"([^\"]+)\"");
185+
186+
Matcher blockMatcher = blockPattern.matcher(content);
187+
while (blockMatcher.find()) {
188+
String key = blockMatcher.group(1);
189+
String arrayStr = blockMatcher.group(2);
190+
List<String> values = new ArrayList<>();
191+
Matcher valMatcher = stringPattern.matcher(arrayStr);
192+
while (valMatcher.find()) {
193+
values.add(valMatcher.group(1));
194+
}
195+
result.put(key, values);
196+
}
197+
return result;
198+
}
199+
200+
// -------------------------------------------------------------------------
201+
// Output mapping
202+
// -------------------------------------------------------------------------
203+
204+
/**
205+
* Writes icon-restore-mapping.json.
206+
* Format: { "eclipse/plugin/path/icon.svg": "dual-tone-icon-name.svg", ... }
207+
*
208+
* A restore script reads this file and, for each entry:
209+
* - copies eclipse-traditional-icons/<key>
210+
* - back to Eclipse plugins/<pluginId_version>/<iconRelPath>
211+
*/
212+
private static void writeRestoreMapping(Path outputFile, Map<String, String> mapping) throws IOException {
213+
StringBuilder sb = new StringBuilder("{\n");
214+
int i = 0;
215+
for (Map.Entry<String, String> entry : mapping.entrySet()) {
216+
if (i++ > 0) sb.append(",\n");
217+
sb.append(" \"").append(entry.getKey()).append("\": \"").append(entry.getValue()).append("\"");
218+
}
219+
sb.append("\n}\n");
220+
Files.writeString(outputFile, sb.toString());
221+
}
222+
}

scripts/README.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Scripts
2+
3+
## ExtractTraditionalIcons.java
4+
5+
Extracts the original Eclipse icons referenced in `iconpacks/eclipse-dual-tone/icon-mapping.json`
6+
from an existing Eclipse installation and saves them into `iconpacks/eclipse-traditional-icons/`.
7+
It also produces an `icon-restore-mapping.json` that maps every extracted icon path back to the
8+
dual-tone icon name that replaced it, so a restore script can undo the dual-tone replacement.
9+
10+
### Requirements
11+
12+
- Java 11 or later (single-file launch via `java SourceFile.java`)
13+
- No external dependencies
14+
15+
### Usage
16+
17+
Run from the repository root:
18+
19+
```bash
20+
java scripts/ExtractTraditionalIcons.java <eclipse-dir> [icon-mapping.json] [output-dir]
21+
```
22+
23+
| Argument | Default |
24+
|---|---|
25+
| `eclipse-dir` | *(required)* path to the Eclipse installation |
26+
| `icon-mapping.json` | `iconpacks/eclipse-dual-tone/icon-mapping.json` |
27+
| `output-dir` | `iconpacks/eclipse-traditional-icons` |
28+
29+
### Example
30+
31+
```bash
32+
java scripts/ExtractTraditionalIcons.java /opt/eclipse
33+
```
34+
35+
### Output
36+
37+
```
38+
iconpacks/eclipse-traditional-icons/
39+
├── icon-restore-mapping.json # maps each Eclipse path → dual-tone icon name
40+
├── org.eclipse.debug.ui/
41+
│ └── icons/full/elcl16/
42+
│ ├── hierarchicalLayout.svg
43+
│ └── ...
44+
├── org.eclipse.jdt.ui/
45+
│ └── ...
46+
└── ...
47+
```
48+
49+
**`icon-restore-mapping.json`** has the following format:
50+
51+
```json
52+
{
53+
"org.eclipse.ui/icons/full/etool16/undo_edit.svg": "undo.svg",
54+
"org.eclipse.search/icons/full/elcl16/flatLayout.svg": "flat_layout.svg",
55+
...
56+
}
57+
```
58+
59+
Each key is the Eclipse-relative path of the backed-up icon (mirrored under `eclipse-traditional-icons/`).
60+
Each value is the dual-tone icon name from `icon-mapping.json` that replaced it.
61+
62+
### Notes
63+
64+
- Plugin JARs (e.g. `org.eclipse.debug.ui_3.x.x.jar`) and expanded plugin directories are both supported.
65+
- Icons from optional plugins not present in the target installation are skipped and reported as missing.
66+
- The script is idempotent: re-running it overwrites previously extracted files.

0 commit comments

Comments
 (0)