package com.njlabs.showjava.processor;
import com.crashlytics.android.Crashlytics;
import com.njlabs.showjava.utils.SourceInfo;
import com.njlabs.showjava.utils.ZipUtils;
import com.njlabs.showjava.utils.logging.Ln;
import org.benf.cfr.reader.Main;
import org.benf.cfr.reader.state.ClassFileSourceImpl;
import org.benf.cfr.reader.state.DCCommonState;
import org.benf.cfr.reader.util.getopt.GetOptParser;
import org.benf.cfr.reader.util.getopt.Options;
import org.benf.cfr.reader.util.getopt.OptionsImpl;
import org.jetbrains.java.decompiler.main.decompiler.ConsoleDecompiler;
import org.jetbrains.java.decompiler.main.decompiler.PrintStreamLogger;
import java.io.File;
import java.io.PrintStream;
import java.util.HashMap;
import java.util.Map;
import jadx.api.JadxDecompiler;
/**
* Created by Niranjan on 29-05-2015.
*/
public class JavaExtractor extends ProcessServiceHelper {
public JavaExtractor(ProcessService processService) {
this.processService = processService;
this.UIHandler = processService.UIHandler;
this.packageFilePath = processService.packageFilePath;
this.packageName = processService.packageName;
this.exceptionHandler = processService.exceptionHandler;
this.sourceOutputDir = processService.sourceOutputDir;
this.javaSourceOutputDir = processService.javaSourceOutputDir;
if(printStream == null) {
printStream = new PrintStream(new ProgressStream());
System.setErr(printStream);
System.setOut(printStream);
}
}
public void extract() {
broadcastStatus("jar2java");
File dexInputFile = new File(sourceOutputDir + "/optimised_classes.dex");
File jarInputFile = new File(sourceOutputDir + "/" + packageName + ".jar");
final File javaOutputDir = new File(javaSourceOutputDir);
if (!javaOutputDir.isDirectory()) {
javaOutputDir.mkdirs();
}
if(!processService.decompilerToUse.equals("jadx")){
if(dexInputFile.exists() && dexInputFile.isFile()){
dexInputFile.delete();
}
}
switch (processService.decompilerToUse) {
case "jadx":
decompileWithJaDX(dexInputFile, javaOutputDir);
break;
case "cfr":
decompileWithCFR(jarInputFile, javaOutputDir);
break;
case "fernflower":
decompileWithFernFlower(jarInputFile, javaOutputDir);
break;
}
}
private void decompileWithCFR(File jarInputFile, File javaOutputDir){
String[] args = {jarInputFile.toString(), "--outputdir", javaOutputDir.toString()};
GetOptParser getOptParser = new GetOptParser();
Options options = null;
try {
options = getOptParser.parse(args, OptionsImpl.getFactory());
if(!options.optionIsSet(OptionsImpl.HELP) && options.getOption(OptionsImpl.FILENAME) != null) {
ClassFileSourceImpl classFileSource = new ClassFileSourceImpl(options);
final DCCommonState dcCommonState = new DCCommonState(options, classFileSource);
final String path = options.getOption(OptionsImpl.FILENAME);
ThreadGroup group = new ThreadGroup("Jar 2 Java Group");
Thread javaExtractionThread = new Thread(group, new Runnable() {
@Override
public void run() {
boolean javaError = false;
try {
Main.doJar(dcCommonState, path);
} catch (Exception | StackOverflowError e) {
Ln.e(e);
javaError = true;
}
startXMLExtractor(!javaError);
}
}, "Jar to Java Thread", processService.STACK_SIZE);
javaExtractionThread.setPriority(Thread.MAX_PRIORITY);
javaExtractionThread.setUncaughtExceptionHandler(exceptionHandler);
javaExtractionThread.start();
} else {
broadcastStatus("exit_process_on_error");
}
} catch (Exception e) {
Crashlytics.logException(e);
broadcastStatus("exit_process_on_error");
}
}
private void decompileWithJaDX(final File dexInputFile, final File javaOutputDir){
ThreadGroup group = new ThreadGroup("Jar 2 Java Group");
Thread javaExtractionThread = new Thread(group, new Runnable() {
@Override
public void run() {
boolean javaError = false;
try {
JadxDecompiler jadx = new JadxDecompiler();
jadx.setOutputDir(javaOutputDir);
jadx.loadFile(dexInputFile);
jadx.saveSources();
} catch (Exception | StackOverflowError e) {
Ln.e(e);
javaError = true;
}
if(dexInputFile.exists() && dexInputFile.isFile()){
dexInputFile.delete();
}
startXMLExtractor(!javaError);
}
}, "Jar to Java Thread", processService.STACK_SIZE);
javaExtractionThread.setPriority(Thread.MAX_PRIORITY);
javaExtractionThread.setUncaughtExceptionHandler(exceptionHandler);
javaExtractionThread.start();
}
private void decompileWithFernFlower(final File jarInputFile, final File javaOutputDir){
ThreadGroup group = new ThreadGroup("Jar 2 Java Group");
Thread javaExtractionThread = new Thread(group, new Runnable() {
@Override
public void run() {
boolean javaError = false;
try {
final Map<String, Object> mapOptions = new HashMap<>();
PrintStreamLogger logger = new PrintStreamLogger(printStream);
ConsoleDecompiler decompiler = new ConsoleDecompiler(javaOutputDir, mapOptions, logger);
decompiler.addSpace(jarInputFile, true);
decompiler.decompileContext();
File decompiledJarFile = new File(javaOutputDir + "/" + packageName +".jar");
if(decompiledJarFile.exists()) {
ZipUtils.unzip(decompiledJarFile, javaOutputDir, printStream);
decompiledJarFile.delete();
} else {
javaError = true;
}
} catch (Exception | StackOverflowError e) {
Ln.e(e);
javaError = true;
}
startXMLExtractor(!javaError);
}
}, "Jar to Java Thread", processService.STACK_SIZE);
javaExtractionThread.setPriority(Thread.MAX_PRIORITY);
javaExtractionThread.setUncaughtExceptionHandler(exceptionHandler);
javaExtractionThread.start();
}
private void startXMLExtractor(boolean hasJava) {
SourceInfo.setjavaSourceStatus(processService, hasJava);
((new ResourcesExtractor(processService))).extract();
}
}