Last active
October 14, 2024 18:14
-
-
Save guaporocco/0a92b868027e43c79e608debd431cedb to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.io.BufferedInputStream; | |
import java.io.IOException; | |
import java.nio.file.Files; | |
import java.nio.file.Path; | |
import java.nio.file.Paths; | |
import java.nio.file.StandardCopyOption; | |
import java.util.Enumeration; | |
import java.util.Map; | |
import java.util.Set; | |
import java.util.jar.JarEntry; | |
import java.util.jar.JarFile; | |
import java.util.regex.Matcher; | |
import java.util.regex.Pattern; | |
import static java.util.stream.Collectors.*; | |
/** | |
* How to get the modern Intellij Look and Feels: | |
* <br/><br/> | |
* | |
* 1. Add the look and field class name to your application. For example, | |
* `UIManager.setLookAndFeel("com.intellij.ide.ui.laf.darcula.DarculaLaf");`, or | |
* `UIManager.setLookAndFeel("com.intellij.ide.ui.laf.IdeaLaf");`. | |
* <br/><br/> | |
* | |
* 2. Run your application with Intellij's `lib` directory on the classpath, and export | |
* the loaded class list to a file. Make sure to play around with your app so that every | |
* UI component that can be loaded makes it into the classlist<br/><br/> | |
* | |
* java -verbose:class -cp "/Applications/IntelliJ IDEA.app/Contents/lib/*" MyClass > classlist.txt | |
* <br/><br/> | |
* | |
* 3. Run this program and pass the classlist as an argument. | |
* <br/><br/> | |
* | |
* 4. A directory named `extracted` is created, with the minimal classes & resources | |
* extracted. You can now add this folder to your classpath to get the desired theme. | |
* NOTE: The Intellij icons jar must be manually added to your classpath. It is in the same | |
* Intellij instalation lib folder. And the applications must be run on Intellij Runtime | |
* (this is a best practice anyway, since IJR has many patches for swing performance improvements) | |
* <br/><br/> | |
* | |
* Unfortunately the Darcula (and Idea) themes have become very | |
* coupled to the Intellij codebase. Even a running ClassExtractor on a simple program will pull | |
* in 5+ MB of dependencies, including the Kotlin standard library. Hopefully Jetbrains | |
* takes some time to clean up their code, and let developers use their great themes! | |
* | |
*/ | |
public class ClassExtractor { | |
private static final class LoadedClass { | |
final Path jarPath; | |
final String cls; | |
LoadedClass(Path jarPath, String cls) { | |
this.jarPath = jarPath; | |
this.cls = cls; | |
} | |
Path getJarPath() { | |
return jarPath; | |
} | |
String getCls() { | |
return cls; | |
} | |
} | |
private static final Pattern reg = Pattern.compile("\\[Loaded (?<cls>\\S+) from (?<jar>.+/lib/.+)]"); | |
public static void main(String[] args) throws IOException { | |
Path classListPath = Paths.get(args[0]); | |
Matcher m = reg.matcher(""); | |
// Keys are the Jars in Intellij's lib directory, and | |
// Values are the set of classnames that your program depends on | |
// for the look and feel. | |
Map<Path, Set<String>> loadedclasses = Files.lines(classListPath) | |
.map(m::reset) | |
.filter(Matcher::matches) | |
.map(ClassExtractor::buildLoadedClass) | |
// ignore the jre stdlib jar | |
.filter(lc -> !lc.getJarPath().toString().contains("rt.jar")) | |
.collect(groupingBy(LoadedClass::getJarPath, mapping(LoadedClass::getCls, toSet()))); | |
Path base = Paths.get("extracted"); | |
Files.createDirectories(base); | |
// for each Jar that we depend on, open a JarFile | |
// and extract the contents that we need | |
for (Map.Entry<Path, Set<String>> entry : loadedclasses.entrySet()) { | |
Path jarPath = entry.getKey(); | |
Set<String> classNames = entry.getValue(); | |
try (JarFile jf = new JarFile(jarPath.toFile())) { | |
Enumeration<JarEntry> entries = jf.entries(); | |
while (entries.hasMoreElements()) { | |
JarEntry ze = entries.nextElement(); | |
String zeName = ze.getName(); | |
Path zePath = base.resolve(zeName); | |
if (ze.isDirectory() | |
|| (zeName.endsWith(".class") && !classNames.contains(zeName)) | |
|| !(zeName.contains("resources") || !zeName.contains("WEB_INF") || !zeName.contains("properties"))) | |
continue; | |
Files.createDirectories(zePath.getParent()); | |
try (BufferedInputStream is = new BufferedInputStream(jf.getInputStream(ze))) { | |
Files.copy(is, zePath, StandardCopyOption.REPLACE_EXISTING); | |
} | |
} | |
} | |
} | |
} | |
private static LoadedClass buildLoadedClass(Matcher m) { | |
String jar = m.group("jar"); | |
jar = jar.substring(jar.indexOf('/')); | |
jar = jar.replaceFirst("%20", " "); | |
Path jarPath = Paths.get(jar); | |
String cls = m.group("cls").replace('.', '/') + ".class"; | |
return new LoadedClass(jarPath, cls); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
java -verbose:class -cp "/Applications/IntelliJ IDEA.app/Content/lib/*" HelloWorldSwing > classlist.txt
Error: Could not find or load main class HelloWorldSwing
Caused by: java.lang.ClassNotFoundException: HelloWorldSwing