com.samczsun.helios.Helios.java Source code

Java tutorial

Introduction

Here is the source code for com.samczsun.helios.Helios.java

Source

/*
 * Copyright 2016 Sam Sun <me@samczsun.com>
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package com.samczsun.helios;

import com.eclipsesource.json.JsonArray;
import com.eclipsesource.json.JsonValue;
import com.google.common.collect.Sets;
import com.samczsun.helios.api.events.Events;
import com.samczsun.helios.api.events.requests.RecentFileRequest;
import com.samczsun.helios.api.events.requests.TreeUpdateRequest;
import com.samczsun.helios.bootloader.BootSequence;
import com.samczsun.helios.bootloader.Splash;
import com.samczsun.helios.gui.BackgroundTaskGui;
import com.samczsun.helios.gui.GUI;
import com.samczsun.helios.handler.BackgroundTaskHandler;
import com.samczsun.helios.handler.ExceptionHandler;
import com.samczsun.helios.handler.addons.AddonHandler;
import com.samczsun.helios.tasks.AddFilesTask;
import com.samczsun.helios.utils.FileChooserUtil;
import com.samczsun.helios.utils.SWTUtil;
import org.apache.commons.io.IOUtils;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.ShellAdapter;
import org.eclipse.swt.events.ShellEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.objectweb.asm.tree.ClassNode;

import javax.swing.UIManager;
import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.InetAddress;
import java.net.URISyntaxException;
import java.security.Permission;
import java.util.*;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

public class Helios {
    private static final Map<String, LoadedFile> files = new HashMap<>();
    private static final List<Process> processes = new ArrayList<>();
    private static Boolean python2Verified = null;
    private static Boolean python3Verified = null;
    private static Boolean javaRtVerified = null;
    private static GUI gui;
    private static BackgroundTaskHandler backgroundTaskHandler;
    private static BackgroundTaskGui backgroundTaskGui;

    private static volatile Map<String, LoadedFile> path = new HashMap<>();

    public static void main(String[] args, Shell shell, Splash splashScreen) {
        System.setSecurityManager(new SecurityManager() {
            @Override
            public void checkPermission(Permission perm) {
            }

            @Override
            public void checkPermission(Permission perm, Object context) {
            }

            @Override
            public void checkCreateClassLoader() {
            }

            @Override
            public void checkAccess(Thread t) {
            }

            @Override
            public void checkAccess(ThreadGroup g) {
            }

            @Override
            public void checkExit(int status) {
                if (!getClassContext()[3].getCanonicalName().startsWith("com.samczsun")) {
                    throw new SecurityException(); //Baksmali
                }
            }

            @Override
            public void checkExec(String cmd) {
            }

            @Override
            public void checkLink(String lib) {
            }

            @Override
            public void checkRead(FileDescriptor fd) {
            }

            @Override
            public void checkRead(String file) {
            }

            @Override
            public void checkRead(String file, Object context) {
            }

            @Override
            public void checkWrite(FileDescriptor fd) {
            }

            @Override
            public void checkWrite(String file) {
            }

            @Override
            public void checkDelete(String file) {
            }

            @Override
            public void checkConnect(String host, int port) {
            }

            @Override
            public void checkConnect(String host, int port, Object context) {
            }

            @Override
            public void checkListen(int port) {
            }

            @Override
            public void checkAccept(String host, int port) {
            }

            @Override
            public void checkMulticast(InetAddress maddr) {
            }

            @Override
            public void checkMulticast(InetAddress maddr, byte ttl) {
            }

            @Override
            public void checkPropertiesAccess() {
            }

            @Override
            public void checkPropertyAccess(String key) {
            }

            @Override
            public boolean checkTopLevelWindow(Object window) {
                return true;
            }

            @Override
            public void checkPrintJobAccess() {
            }

            @Override
            public void checkSystemClipboardAccess() {
            }

            @Override
            public void checkAwtEventQueueAccess() {
            }

            @Override
            public void checkPackageAccess(String pkg) {
            }

            @Override
            public void checkPackageDefinition(String pkg) {
            }

            @Override
            public void checkSetFactory() {
            }

            @Override
            public void checkMemberAccess(Class<?> clazz, int which) {
            }

            @Override
            public void checkSecurityAccess(String target) {
            }
        });
        try {
            if (!System.getProperty("os.name").toLowerCase().contains("linux")) {
                UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            }
        } catch (Exception exception) { //Not important. No point notifying the user
        }
        splashScreen.updateState(BootSequence.LOADING_SETTINGS);
        Settings.loadSettings();
        backgroundTaskGui = new BackgroundTaskGui();
        backgroundTaskHandler = new BackgroundTaskHandler();
        splashScreen.updateState(BootSequence.LOADING_ADDONS);
        AddonHandler.registerPreloadedAddons();
        for (File file : Constants.ADDONS_DIR.listFiles()) {
            AddonHandler.getAllHandlers().stream().filter(handler -> handler.accept(file)).findFirst()
                    .ifPresent(handler -> {
                        handler.run(file);
                    });
        }
        splashScreen.updateState(BootSequence.SETTING_UP_GUI);
        gui = new GUI(shell);
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            Settings.saveSettings();
            getBackgroundTaskHandler().shutdown();
            processes.forEach(Process::destroy);
        }));
        splashScreen.updateState(BootSequence.COMPLETE);
        while (!splashScreen.isDisposed())
            ;
        Display.getDefault().syncExec(() -> getGui().getShell().open());

        List<File> open = new ArrayList<>();

        for (String name : args) {
            File file = new File(name);
            if (file.exists()) {
                open.add(file);
            }
        }

        submitBackgroundTask(() -> {
            Map<String, LoadedFile> newPath = new HashMap<>();
            for (String strFile : Sets.newHashSet(Settings.PATH.get().asString().split(";"))) {
                File file = new File(strFile);
                if (file.exists()) {
                    try {
                        LoadedFile loadedFile = new LoadedFile(file);
                        newPath.put(loadedFile.getName(), loadedFile);
                    } catch (IOException e1) {
                        ExceptionHandler.handle(e1);
                    }
                }
            }
            synchronized (Helios.class) {
                path.clear();
                path.putAll(newPath);
            }
        });

        if (open.size() > 0) {
            openFiles(open.toArray(new File[open.size()]), true);
        }
    }

    public static List<LoadedFile> getFilesForName(String fileName) {
        return files.values().stream().filter(loadedFile -> loadedFile.getFiles().containsKey(fileName))
                .collect(Collectors.toList());
    }

    public static void loadFile(File fileToLoad) throws IOException {
        LoadedFile loadedFile = new LoadedFile(fileToLoad);
        files.put(loadedFile.getName(), loadedFile);
    }

    public static List<ClassNode> loadAllClasses() {
        List<ClassNode> classNodes = new ArrayList<>();
        for (LoadedFile loadedFile : files.values()) {
            for (String s : loadedFile.getFiles().keySet()) {
                ClassNode loaded = loadedFile.getClassNode(s);
                if (loaded != null) {
                    classNodes.add(loaded);
                }
            }
        }
        return classNodes;
    }

    public static void openFiles(final File[] files, final boolean recentFiles) {
        submitBackgroundTask(new AddFilesTask(files, recentFiles));
    }

    public static void resetWorkSpace(boolean ask) {
        if (ask && !SWTUtil.promptForYesNo(Constants.REPO_NAME + " - New Workspace",
                "You have not yet saved your workspace. Are you sure you wish to create a new one?")) {
            return;
        }
        files.clear();
        getGui().getTreeManager().reset();
        getGui().getClassManager().reset();
        processes.forEach(Process::destroyForcibly);
        processes.clear();
    }

    public static Process launchProcess(ProcessBuilder launch) throws IOException {
        Process process = launch.start();
        processes.add(process);
        submitBackgroundTask(() -> {
            try {
                process.waitFor();
                if (!process.isAlive()) {
                    processes.remove(process);
                }
            } catch (InterruptedException e) {
                ExceptionHandler.handle(e);
            }
        });
        return process;
    }

    public static void addRecentFile(File f) {
        if (!f.getAbsolutePath().isEmpty()) {
            JsonArray array = Settings.RECENT_FILES.get().asArray();
            array.add(f.getAbsolutePath());
            while (array.size() > Settings.MAX_RECENTFILES.get().asInt()) {
                array.remove(0);
            }
            updateRecentFiles();
        }
    }

    public static void updateRecentFiles() {
        Events.callEvent(new RecentFileRequest(Settings.RECENT_FILES.get().asArray().values().stream()
                .map(JsonValue::asString).collect(Collectors.toList())));
    }

    public static void promptForFilesToOpen() {
        submitBackgroundTask(() -> {
            List<String> validExtensions = Arrays.asList("jar", "zip", "class", "apk", "dex");
            List<File> files1 = FileChooserUtil.chooseFiles(Settings.LAST_DIRECTORY.get().asString(),
                    validExtensions, true);
            if (files1.size() > 0) {
                Settings.LAST_DIRECTORY.set(files1.get(0).getAbsolutePath());
                try {
                    Helios.openFiles(files1.toArray(new File[files1.size()]), true);
                } catch (Exception e1) {
                    ExceptionHandler.handle(e1);
                }
            }
        });
    }

    public static void promptForRefresh() {
        if (SWTUtil.promptForYesNo(Constants.REPO_NAME + " - Refresh", "Are you sure you wish to refresh?")) {
            for (LoadedFile loadedFile : Helios.files.values()) {
                try {
                    loadedFile.reset();
                } catch (IOException e) {
                    ExceptionHandler.handle(e);
                }
            }
            Events.callEvent(new TreeUpdateRequest());
        }
    }

    private static volatile Shell customPathShell;

    public static void promptForCustomPath() {
        synchronized (Helios.class) {
            if (customPathShell != null) {
                customPathShell.setFocus();
                return;
            }
        }
        Display.getDefault().asyncExec(() -> {
            synchronized (Helios.class) {
                Display display = Display.getDefault();
                Shell shell = new Shell(display);
                shell.setLayout(new GridLayout());
                shell.setImage(Resources.ICON.getImage());
                shell.setText("Set your PATH variable");
                final Text text = new Text(shell, SWT.BORDER | SWT.MULTI | SWT.WRAP);
                text.setText(Settings.PATH.get().asString());
                GridData gridData = new GridData(GridData.FILL_HORIZONTAL);
                gridData.heightHint = text.getLineHeight();
                gridData.widthHint = 512;
                text.setLayoutData(gridData);
                shell.addShellListener(new ShellAdapter() {
                    @Override
                    public void shellClosed(ShellEvent e) {
                        synchronized (Helios.class) {
                            customPathShell = null;
                        }
                        String oldPath = Settings.PATH.get().asString();
                        if (!oldPath.equals(text.getText())) {
                            Settings.PATH.set(text.getText());
                            submitBackgroundTask(() -> {
                                Map<String, LoadedFile> newPath = new HashMap<>();
                                for (String strFile : Sets.newHashSet(Settings.PATH.get().asString().split(";"))) {
                                    File file = new File(strFile);
                                    if (file.exists()) {
                                        try {
                                            LoadedFile loadedFile = new LoadedFile(file);
                                            newPath.put(loadedFile.getName(), loadedFile);
                                        } catch (IOException e1) {
                                            ExceptionHandler.handle(e1);
                                        }
                                    }
                                }
                                synchronized (Helios.class) {
                                    path.clear();
                                    path.putAll(newPath);
                                }
                            });
                        }
                    }
                });
                text.addKeyListener(new KeyAdapter() {
                    @Override
                    public void keyPressed(KeyEvent e) {
                        if (e.keyCode == SWT.ESC || SWTUtil.isEnter(e.keyCode)) {
                            shell.close();
                        } else if (e.keyCode == 'a' && SWTUtil.isCtrl(e.stateMask)) {
                            text.selectAll();
                            e.doit = false;
                        }
                    }
                });
                shell.pack();
                SWTUtil.center(shell);
                shell.open();
                customPathShell = shell;
            }
        });
    }

    public static synchronized Map<String, LoadedFile> getPathFiles() {
        return path;
    }

    public static boolean ensurePython3Set() {
        return ensurePython3Set0(false);
    }

    public static boolean ensurePython2Set() {
        return ensurePython2Set0(false);
    }

    public static boolean ensureJavaRtSet() {
        return ensureJavaRtSet0(false);
    }

    private static boolean ensurePython3Set0(boolean forceCheck) {
        String python3Location = Settings.PYTHON3_LOCATION.get().asString();
        if (python3Location.isEmpty()) {
            SWTUtil.showMessage("You need to set the location of the Python/PyPy 3.x executable", true);
            setLocationOf(Settings.PYTHON3_LOCATION);
            python3Location = Settings.PYTHON3_LOCATION.get().asString();
        }
        if (python3Location.isEmpty()) {
            return false;
        }
        if (python3Verified == null || forceCheck) {
            try {
                Process process = new ProcessBuilder(python3Location, "-V").start();
                String result = IOUtils.toString(process.getInputStream());
                String error = IOUtils.toString(process.getErrorStream());
                python3Verified = error.startsWith("Python 3") || result.startsWith("Python 3");
            } catch (Throwable t) {
                t.printStackTrace();
                StringWriter sw = new StringWriter();
                t.printStackTrace(new PrintWriter(sw));
                SWTUtil.showMessage("The Python 3.x executable is invalid." + Constants.NEWLINE + Constants.NEWLINE
                        + sw.toString());
                python3Verified = false;
            }
        }
        return python3Verified;
    }

    private static boolean ensurePython2Set0(boolean forceCheck) {
        String python2Location = Settings.PYTHON2_LOCATION.get().asString();
        if (python2Location.isEmpty()) {
            SWTUtil.showMessage("You need to set the location of the Python/PyPy 2.x executable", true);
            setLocationOf(Settings.PYTHON2_LOCATION);
            python2Location = Settings.PYTHON2_LOCATION.get().asString();
        }
        if (python2Location.isEmpty()) {
            return false;
        }
        if (python2Verified == null || forceCheck) {
            try {
                Process process = new ProcessBuilder(python2Location, "-V").start();
                String result = IOUtils.toString(process.getInputStream());
                String error = IOUtils.toString(process.getErrorStream());
                python2Verified = error.startsWith("Python 2") || result.startsWith("Python 2");
            } catch (Throwable t) {
                StringWriter sw = new StringWriter();
                t.printStackTrace(new PrintWriter(sw));
                SWTUtil.showMessage("The Python 2.x executable is invalid." + Constants.NEWLINE + Constants.NEWLINE
                        + sw.toString());
                t.printStackTrace();
                python2Verified = false;
            }
        }
        return python2Verified;
    }

    private static boolean ensureJavaRtSet0(boolean forceCheck) {
        String javaRtLocation = Settings.RT_LOCATION.get().asString();
        if (javaRtLocation.isEmpty()) {
            SWTUtil.showMessage("You need to set the location of Java's rt.jar", true);
            setLocationOf(Settings.RT_LOCATION);
            javaRtLocation = Settings.RT_LOCATION.get().asString();
        }
        if (javaRtLocation.isEmpty()) {
            return false;
        }
        if (javaRtVerified == null || forceCheck) {
            ZipFile zipFile = null;
            try {
                File rtjar = new File(javaRtLocation);
                if (rtjar.exists()) {
                    zipFile = new ZipFile(rtjar);
                    ZipEntry object = zipFile.getEntry("java/lang/Object.class");
                    if (object != null) {
                        javaRtVerified = true;
                    }
                }
            } catch (Throwable t) {
                StringWriter sw = new StringWriter();
                t.printStackTrace(new PrintWriter(sw));
                SWTUtil.showMessage("The selected Java rt.jar is invalid." + Constants.NEWLINE + Constants.NEWLINE
                        + sw.toString());
                t.printStackTrace();
                javaRtVerified = false;
            } finally {
                IOUtils.closeQuietly(zipFile);
                if (javaRtVerified == null) {
                    javaRtVerified = false;
                }
            }
        }
        return javaRtVerified;
    }

    public static void checkHotKey(Event e) {
        if (e.doit) {
            if ((e.stateMask & SWT.CTRL) == SWT.CTRL) {
                if (e.keyCode == 'o') {
                    promptForFilesToOpen();
                    e.doit = false;
                } else if (e.keyCode == 'n') {
                    resetWorkSpace(true);
                    e.doit = false;
                } else if (e.keyCode == 't') {
                    getGui().getClassManager().handleNewTabRequest();
                    e.doit = false;
                } else if (e.keyCode == 'w') {
                    if ((e.stateMask & SWT.SHIFT) == SWT.SHIFT) {
                        getGui().getClassManager().closeCurrentInnerTab();
                    } else {
                        getGui().getClassManager().closeCurrentTab();
                    }
                    e.doit = false;
                } else if (e.keyCode == 'f') {
                    getGui().getSearchPopup().open();
                    e.doit = false;
                }
            } else {
                if (e.keyCode == SWT.F5) {
                    if ((e.stateMask & SWT.SHIFT) == SWT.SHIFT) {
                        getGui().getClassManager().refreshCurrentView();
                    } else {
                        promptForRefresh();
                    }
                    e.doit = false;
                }
            }
        }
    }

    public static LoadedFile getLoadedFile(String file) {
        return files.containsKey(file) ? files.get(file) : getPathFiles().get(file);
    }

    public static void submitBackgroundTask(Runnable runnable) {
        getBackgroundTaskHandler().submit(runnable);
    }

    public static Collection<LoadedFile> getAllFiles() {
        return Collections.unmodifiableCollection(files.values());
    }

    public static Map<String, byte[]> getAllLoadedData() {
        Map<String, byte[]> data = new HashMap<>();
        for (LoadedFile loadedFile : files.values()) {
            data.putAll(loadedFile.getData());
        }
        for (LoadedFile loadedFile : path.values()) {
            data.putAll(loadedFile.getData());
        }
        return data;
    }

    public static BackgroundTaskHandler getBackgroundTaskHandler() {
        return backgroundTaskHandler;
    }

    /*
    These are to somewhat separate GUI and logic
     */

    public static GUI getGui() {
        return gui;
    }

    public static void setLocationOf(Settings setting) {
        String temp = setting.get().asString();
        String currentFile = temp == null || temp.isEmpty() ? System.getProperty("user.home") : temp;
        List<File> files = FileChooserUtil.chooseFiles(currentFile, Collections.emptyList(), false);
        if (!files.isEmpty()) {
            setting.set(files.get(0).getAbsolutePath());
            if (setting == Settings.PYTHON2_LOCATION) {
                ensurePython2Set0(true);
            } else if (setting == Settings.PYTHON3_LOCATION) {
                ensurePython3Set0(true);
            } else if (setting == Settings.RT_LOCATION) {
                ensureJavaRtSet0(true);
            }
        }
    }

    public static void addToContextMenu() {
        try {
            if (System.getProperty("os.name").toLowerCase().contains("win")) {
                Process process = Runtime.getRuntime().exec("reg add HKCR\\*\\shell\\helios\\command /f");
                process.waitFor();
                if (process.exitValue() == 0) {
                    process = Runtime.getRuntime()
                            .exec("reg add HKCR\\*\\shell\\helios /ve /d \"Open with Helios\" /f");
                    process.waitFor();
                    if (process.exitValue() == 0) {
                        File currentJarLocation = getJarLocation();
                        if (currentJarLocation != null) {
                            File javaw = getJavawLocation();
                            if (javaw != null) {
                                process = Runtime.getRuntime()
                                        .exec("reg add HKCR\\*\\shell\\helios\\command /ve /d \"\\\""
                                                + javaw.getAbsolutePath() + "\\\" -jar \\\""
                                                + currentJarLocation.getAbsolutePath() + "\\\" \\\"%1\\\"\" /f");
                                process.waitFor();
                                if (process.exitValue() == 0) {
                                    SWTUtil.showMessage("Done");
                                } else {
                                    SWTUtil.showMessage("Failed to set context menu");
                                }
                            } else {
                                SWTUtil.showMessage("Could not set context menu - unable to find javaw.exe");
                            }
                        } else {
                            SWTUtil.showMessage("Could not set context menu - unable to find Helios.jar");
                        }
                    } else {
                        SWTUtil.showMessage("Failed to set context menu");
                    }
                } else {
                    if (SWTUtil.promptForYesNo("UAC",
                            "Helios must be run as an administrator to do this. Relaunch as administrator?")) {
                        relaunchAsAdmin();
                    }
                }
            } else {
                SWTUtil.showMessage("You may only do this on Windows");
            }
        } catch (Throwable t) {
            ExceptionHandler.handle(t);
        }
    }

    public static void relaunchAsAdmin() throws IOException, InterruptedException, URISyntaxException {
        File currentJarLocation = getJarLocation();
        File javawLocation = getJavawLocation();
        if (currentJarLocation == null) {
            SWTUtil.showMessage("Could not relaunch as admin - unable to find Helios.jar");
            return;
        }
        if (javawLocation == null) {
            SWTUtil.showMessage("Could not relaunch as admin - unable to find javaw.exe");
            return;
        }
        File tempVBSFile = File.createTempFile("tmpvbs", ".vbs");
        PrintWriter writer = new PrintWriter(tempVBSFile);
        writer.println("Set objShell = CreateObject(\"Wscript.Shell\")");
        writer.println("strPath = Wscript.ScriptFullName");
        writer.println("Set objFSO = CreateObject(\"Scripting.FileSystemObject\")");
        writer.println("Set objFile = objFSO.GetFile(strPath)");
        writer.println("strFolder = objFSO.GetParentFolderName(objFile)");
        writer.println("Set UAC = CreateObject(\"Shell.Application\")");
        writer.println("UAC.ShellExecute \"\"\"" + javawLocation.getAbsolutePath() + "\"\"\", \"-jar \"\""
                + currentJarLocation.getAbsolutePath() + "\"\"\", strFolder, \"runas\", 1");
        writer.println("WScript.Quit 0");
        writer.close();

        Process process = Runtime.getRuntime().exec("cscript " + tempVBSFile.getAbsolutePath());
        process.waitFor();
        System.exit(process.exitValue());
    }

    private static File getJarLocation() throws URISyntaxException {
        File currentJarLocation = new File(
                Helios.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath());
        while (!currentJarLocation.exists() || !currentJarLocation.isFile()) {
            SWTUtil.showMessage("Could not determine location of Helios. Please select the JAR file", true);
            List<File> chosen = FileChooserUtil.chooseFiles(".", Arrays.asList("jar"), false);
            if (chosen.size() == 0) {
                return null;
            }
            currentJarLocation = chosen.get(0);
        }
        return currentJarLocation;
    }

    private static File getJavawLocation() {
        File javaw = new File(
                System.getProperty("java.home") + File.separator + "bin" + File.separator + "javaw.exe");
        while (!javaw.exists() || !javaw.isFile()) {
            SWTUtil.showMessage(
                    "Could not determine location of javaw. Please select the location of the executable", true);
            List<File> chosen = FileChooserUtil.chooseFiles(System.getProperty("java.home"), Arrays.asList("exe"),
                    false);
            if (chosen.size() == 0) {
                return null;
            }
            javaw = chosen.get(0);
        }
        return javaw;
    }
}