package decompsource; import installer.Utils; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.Reader; import java.io.Writer; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.URLClassLoader; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Enumeration; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; import javax.lang.model.element.Modifier; import javax.lang.model.element.NestingKind; import javax.tools.FileObject; import javax.tools.JavaCompiler; import javax.tools.JavaFileManager; import javax.tools.JavaFileObject; import javax.tools.JavaCompiler.CompilationTask; import javax.tools.JavaFileObject.Kind; import javax.tools.StandardLocation; import javax.tools.ToolProvider; public class CompileZip { static ZipOutputStream zipOut; static class ZipFileManager implements JavaFileManager { ZipFile zf; Collection<ZipFile> others = new ArrayList<>(); List<ZipEntry> entries = new ArrayList<>(); List<URL> otherURLs = new ArrayList<>(); List<File> otherFiles = new ArrayList<>(); JavaFileManager parent; ZipFileManager(File file, File libsDir, JavaFileManager parent, List<File> additionalClasspathFiles) throws IOException { zf = new ZipFile(file); for(Enumeration<? extends ZipEntry> entries_enum = zf.entries(); entries_enum.hasMoreElements();) entries.add(entries_enum.nextElement()); this.parent = parent; if(libsDir != null) walkLibs(libsDir); otherFiles.addAll(additionalClasspathFiles); if(!otherFiles.isEmpty()) { String s=""; for(File f : otherFiles) s += ";" + f.getAbsolutePath(); if(!parent.handleOption("-cp", Arrays.asList(s.substring(1)).iterator())) throw new RuntimeException(s); } } private void walkLibs(File dir) throws IOException { if(dir.isFile()) { if(!dir.getName().endsWith(".jar")) return; //System.err.println(dir); others.add(new ZipFile(dir)); otherURLs.add(dir.toURI().toURL()); otherFiles.add(dir); } else for(File sub : dir.listFiles()) walkLibs(sub); } static class OutputClassJFO implements JavaFileObject { String className; public OutputClassJFO(String name) { this.className = name; } @Override public URI toUri() { try { return new URI("CompileZipOutput:"+className); } catch (URISyntaxException e) { throw new AssertionError(e); } } @Override public String getName() { throw new RuntimeException("getName "+className); } @Override public InputStream openInputStream() throws IOException { throw new RuntimeException("openInputStream "+className); } @Override public OutputStream openOutputStream() throws IOException { return new ByteArrayOutputStream() { @Override public void close() throws IOException { super.close(); System.err.println("Writing "+className+"..."); zipOut.putNextEntry(new ZipEntry(className.replace(".","/")+".class")); zipOut.write(toByteArray()); zipOut.closeEntry(); } }; } @Override public Reader openReader(boolean ignoreEncodingErrors) throws IOException { throw new RuntimeException("openReader "+className); } @Override public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { throw new RuntimeException("getCharContent "+className); } @Override public Writer openWriter() throws IOException { throw new RuntimeException("openWriter "+className); } @Override public long getLastModified() { throw new RuntimeException("getLastModified "+className); } @Override public boolean delete() { return false; } @Override public Kind getKind() { return Kind.CLASS; } @Override public boolean isNameCompatible(String simpleName, Kind kind) { return true; } @Override public NestingKind getNestingKind() { throw new RuntimeException("getNestingKind "+className); } @Override public Modifier getAccessLevel() { throw new RuntimeException("getAccessLevel "+className); } } static class ZipFileObject implements JavaFileObject, FileObject { ZipFile zf; ZipEntry ze; Kind kind; public ZipFileObject(ZipFile zf, String path, Kind kind) { this.zf = zf; this.kind = kind; ze = zf.getEntry(path); if(ze == null) throw new RuntimeException(path+" not found"); } @Override public URI toUri() { try { return new URI("ZipFileObject:///"+ze.getName()); } catch(URISyntaxException e) { throw new AssertionError(e); } } @Override public String getName() { return ze.getName(); } @Override public InputStream openInputStream() throws IOException { return zf.getInputStream(ze); } @Override public OutputStream openOutputStream() throws IOException { throw new RuntimeException("openOutputStream"); } @Override public Reader openReader(boolean ignoreEncodingErrors) throws IOException { // ignoreEncodingErrors ignored return new InputStreamReader(openInputStream(), StandardCharsets.UTF_8); } @Override public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { StringBuilder rv = new StringBuilder(); char[] buffer = new char[32768]; try (Reader r = openReader(ignoreEncodingErrors)) { while(true) { int read = r.read(buffer); if(read < 0) break; rv.append(buffer, 0, read); } } return rv; } @Override public Writer openWriter() throws IOException { throw new RuntimeException("openWriter"); } @Override public long getLastModified() { throw new RuntimeException("getLastModified"); } @Override public boolean delete() { throw new RuntimeException("delete"); } @Override public Kind getKind() { return kind; } @Override public boolean isNameCompatible(String simpleName, Kind kind) { if(kind != this.kind) return false; String name = ze.getName(); if(name.contains("/")) name = name.substring(name.lastIndexOf('/')+1); if(name.contains(".")) name = name.substring(0, name.lastIndexOf('.')); return name.equals(simpleName); } @Override public NestingKind getNestingKind() { throw new RuntimeException("getNestingKind"); } @Override public Modifier getAccessLevel() { throw new RuntimeException("getAccessLevel"); } @Override public String toString() { return ze.getName(); } } @Override public int isSupportedOption(String option) { return parent.isSupportedOption(option); } @Override public ClassLoader getClassLoader(Location location) { //System.err.println("getClassLoader "+location); return new URLClassLoader(new URL[0], parent.getClassLoader(location)) {{ for(URL url : otherURLs) addURL(url); }}; } @Override public Iterable<JavaFileObject> list(Location location, String packageName, Set<Kind> kinds, boolean recurse) throws IOException { //System.err.println("list "+location+" "+packageName+" "+kinds+" "+recurse); List<JavaFileObject> result = new ArrayList<>(); for(JavaFileObject pJFO : parent.list(location, packageName, kinds, recurse)) result.add(pJFO); packageName = packageName.replace(".", "/"); if(kinds.contains(Kind.CLASS)) { for(ZipFile zf : others) { for(Enumeration<? extends ZipEntry> entries_enum = zf.entries(); entries_enum.hasMoreElements();) { ZipEntry ze = entries_enum.nextElement(); if(ze.getName().startsWith(packageName) && ze.getName().endsWith(".class")) if(recurse || ze.getName().indexOf('/', packageName.length()+1) < 0) result.add(new ZipFileObject(zf, ze.getName(), Kind.CLASS)); } } } if(kinds.contains(Kind.SOURCE)) { for(Enumeration<? extends ZipEntry> entries_enum = zf.entries(); entries_enum.hasMoreElements();) { ZipEntry ze = entries_enum.nextElement(); if(ze.getName().startsWith(packageName) && ze.getName().endsWith(".java")) if(recurse || ze.getName().indexOf('/', packageName.length()+1) < 0) result.add(new ZipFileObject(zf, ze.getName(), Kind.SOURCE)); } } //if(packageName.equals("java")) //throw new RuntimeException(result.toString()); return result; } @Override public String inferBinaryName(Location location, JavaFileObject file) { if(file instanceof ZipFileObject) { String result = ((ZipFileObject)file).ze.getName().replaceFirst("\\.class$", "").replaceFirst("\\.java$", ""); //if(result.contains("java")) // throw new RuntimeException(result); return result; } //throw new RuntimeException("inferBinaryName"); return parent.inferBinaryName(location, file); } @Override public boolean isSameFile(FileObject a, FileObject b) { if(a instanceof ZipFileObject) if(b instanceof ZipFileObject) return ((ZipFileObject) a).ze == ((ZipFileObject) b).ze; else return false; if(b instanceof ZipFileObject) return false; return parent.isSameFile(a, b); } @Override public boolean handleOption(String current, Iterator<String> remaining) { //throw new RuntimeException("handleOption "+current); return parent.handleOption(current, remaining); } @Override public boolean hasLocation(Location location) { return location == StandardLocation.SOURCE_PATH || parent.hasLocation(location); } @Override public JavaFileObject getJavaFileForInput(Location location, String className, Kind kind) throws IOException { if(kind != Kind.SOURCE) return parent.getJavaFileForInput(location, className, kind); return (JavaFileObject)getFileForInput(location, "", className.replace(".", "/")+".java"); } @Override public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling) throws IOException { if(kind != Kind.CLASS || location != StandardLocation.CLASS_OUTPUT) return null; return new OutputClassJFO(className); } @Override public FileObject getFileForInput(Location location, String packageName, String relativeName) throws IOException { if(location != StandardLocation.SOURCE_PATH) return null; return new ZipFileObject(zf, packageName.equals("") ? relativeName : packageName.replace(".","/")+"/"+relativeName, Kind.SOURCE); } @Override public FileObject getFileForOutput(Location location, String packageName, String relativeName, FileObject sibling) throws IOException { throw new RuntimeException("gffo"); } @Override public void flush() throws IOException { parent.flush(); } @Override public void close() throws IOException { parent.close(); zf.close(); } } public static void main(String[] args) { if(args.length < 2 || (args.length & 1) != 0) { System.err.println("Usage: java CompileDirTree sources.zip libsDir [-cp file.jar | -x extraOption]... > output.jar"); System.exit(1); } List<File> additionalClasspathFiles = new ArrayList<>(); List<String> compilerOptions = new ArrayList<>(); for(int k = 0; k < args.length; k += 2) { String opt = args[k]; String val = args[k+1]; if(opt.equals("-cp")) additionalClasspathFiles.add(new File(val)); else if(opt.equals("-x")) compilerOptions.add(val); } if(!compilerOptions.contains("-source")) { compilerOptions.add("-source"); compilerOptions.add("1.6"); compilerOptions.add("-target"); compilerOptions.add("1.6"); } try { JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); JavaFileManager standardFM = compiler.getStandardFileManager(null, null, StandardCharsets.UTF_8); ZipFileManager fileManager = new ZipFileManager(new File(args[0]), args[1].equals("-") ? null : new File(args[1]), standardFM, additionalClasspathFiles); List<JavaFileObject> compilationFileObjects = new ArrayList<>(); for(ZipEntry ze : fileManager.entries) { if(ze.getName().endsWith(".java")) { String name = ze.getName(); name = name.substring(0, name.length() - 5); name = name.replace("/", "."); compilationFileObjects.add(fileManager.getJavaFileForInput(StandardLocation.SOURCE_PATH, name, Kind.SOURCE)); } } try (ZipOutputStream zipOut = new ZipOutputStream(System.out)) { CompileZip.zipOut = zipOut; for(ZipEntry ze : fileManager.entries) { if(!ze.getName().endsWith(".java")) { try (InputStream in = fileManager.zf.getInputStream(ze)) { zipOut.putNextEntry(new ZipEntry(ze.getName())); Utils.copyStream(in, zipOut); zipOut.closeEntry(); } } } CompilationTask task = compiler.getTask(null, fileManager, null, compilerOptions, null, compilationFileObjects); if(!task.call()) { System.err.println("Compilation failed"); System.exit(1); } } } catch(Throwable t) { t.printStackTrace(); System.exit(1); } } }