ExceptionUtil.java

package com.jsql.util;

import com.jsql.model.InjectionModel;
import com.jsql.util.bruter.HashUtil;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import javax.swing.*;
import java.lang.reflect.InvocationTargetException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * Utility class managing exception reporting to GitHub.
 */
public class ExceptionUtil {
    
    private static final Logger LOGGER = LogManager.getRootLogger();
    
    private final InjectionModel injectionModel;
    
    private final Set<String> exceptionsMd5Cached = new CopyOnWriteArraySet<>();
    
    public ExceptionUtil(InjectionModel injectionModel) {
        
        this.injectionModel = injectionModel;
    }
    
    /**
     * Handler class processing errors on top of the JVM in order to send
     * a report to GitHub automatically.
     */
    public class ExceptionHandler implements Thread.UncaughtExceptionHandler {

        @Override
        public void uncaughtException(Thread thread, Throwable throwable) {
            
            LOGGER.log(
                LogLevelUtil.CONSOLE_JAVA,
                () -> String.format("Unhandled Exception on %s", thread.getName()),
                throwable
            );

            // Display to stdout
            LOGGER.log(
                Level.ERROR,
                () -> String.format("Unhandled Exception on %s", thread.getName()),
                throwable
            );

            // Report #214: ignore if OutOfMemoryError: Java heap space
            if (
                ExceptionUtil.this.injectionModel.getMediatorUtils().getPreferencesUtil().isReportingBugs()
                && ExceptionUtils.getStackTrace(throwable).contains("com.jsql")
                && !(throwable instanceof OutOfMemoryError)
                && !ExceptionUtils.getStackTrace(throwable).contains("OutOfMemoryError")  // when implicit
            ) {

                if (ExceptionUtils.getStackTrace(throwable).contains("Could not initialize class java.awt.Toolkit")) {

                    LOGGER.log(LogLevelUtil.CONSOLE_JAVA, "System libraries are missing, please use a proper Java runtime instead of headless runtime");
                    return;

                } else if (ExceptionUtils.getStackTrace(throwable).contains("Could not initialize class sun.awt.X11.XToolkit")) {

                    LOGGER.log(LogLevelUtil.CONSOLE_JAVA, "System libraries are missing or wrong DISPLAY variable, please verify your settings");
                    return;
                }

                try {
                    var md = MessageDigest.getInstance("Md5");

                    String stackTrace = ExceptionUtils.getStackTrace(throwable).trim();
                    var passwordString = String.valueOf(stackTrace.toCharArray());

                    byte[] passwordByte = passwordString.getBytes(StandardCharsets.UTF_8);
                    md.update(passwordByte, 0, passwordByte.length);

                    byte[] encodedPassword = md.digest();

                    var md5Exception = HashUtil.digestToHexString(encodedPassword);

                    if (!ExceptionUtil.this.exceptionsMd5Cached.contains(md5Exception)) {

                        ExceptionUtil.this.exceptionsMd5Cached.add(md5Exception);
                        ExceptionUtil.this.injectionModel.getMediatorUtils().getGitUtil().sendUnhandledException(
                            thread.getName(),
                            throwable
                        );
                    }
                } catch (NoSuchAlgorithmException e) {
                    LOGGER.log(LogLevelUtil.IGNORE, e);
                }
            }
        }
    }
    
    /**
     * Add the error reporting mechanism on top of the JVM in order to
     * intercept and process the error to GitHub.
     */
    public void setUncaughtExceptionHandler() {
        
        // Regular Exception
        Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler());

        // Event dispatching thread Exception
        try {
            SwingUtilities.invokeAndWait(() ->
                // We are in the event dispatching thread
                Thread.currentThread().setUncaughtExceptionHandler(new ExceptionHandler())
            );
        } catch (InvocationTargetException | InterruptedException e) {
            
            LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
            Thread.currentThread().interrupt();
        }
    }
}