package de.is24.deadcode4j.analyzer.javassist;

import com.google.common.collect.Lists;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;

import static com.google.common.collect.Sets.newHashSet;
import static java.util.Arrays.asList;
import static org.slf4j.LoggerFactory.getLogger;

/**
 * Provides convenience methods with which to analyze instances of {@link javassist.CtClass}.
 *
 * @since 1.6
 */
public final class CtClasses {

    private CtClasses() {
    }


    /**
     * Retrieves the specified class.
     * This method swallows class loading issues, returning only those classes that are accessible by the
     * {@link javassist.CtClass#getClassPool() class pool}.
     *
     * @since 1.6
     */
    @Nullable
    public static CtClass getCtClass(@Nonnull ClassPool classPool, @Nonnull String className) {
        CtClass clazz = classPool.getOrNull(className);
        if (clazz == null) {
            handleMissingClass(className);
        }
        return clazz;
    }

    /**
     * Returns {@code true} if the specified class refers to {@link java.lang.Object}.
     *
     * @since 1.6
     */
    public static boolean isJavaLangObject(@Nullable CtClass loopClass) {
        return loopClass != null && "java.lang.Object".equals(loopClass.getName());
    }

    /**
     * Retrieves all interfaces a class implements - either directly, via superclass or via interface inheritance.
     *
     * @throws java.lang.RuntimeException if an implemented interface or super class cannot be loaded
     * @see #getInterfacesOf(javassist.CtClass)
     * @since 1.6
     */
    @Nonnull
    public static Set<String> getAllImplementedInterfaces(@Nonnull final CtClass clazz) {
        Set<String> interfaces = newHashSet();
        CtClass loopClass = clazz;
        do {
            for (CtClass anInterface : getInterfacesOf(loopClass)) {
                interfaces.add(anInterface.getName());
                interfaces.addAll(getAllImplementedInterfaces(anInterface));
            }
            loopClass = getSuperclassOf(loopClass);
        } while (loopClass != null && !isJavaLangObject(loopClass));
        return interfaces;
    }

    /**
     * Retrieves all interfaces a class directly implements.
     * This method swallows class loading issues, returning only those classes that are accessible by the
     * {@link javassist.CtClass#getClassPool() class pool}.
     *
     * @see #getAllImplementedInterfaces(javassist.CtClass)
     * @since 1.6
     */
    @Nonnull
    public static Iterable<CtClass> getInterfacesOf(@Nonnull CtClass clazz) {
        String[] interfaceNames = clazz.getClassFile2().getInterfaces();
        Collection<CtClass> interfaces = Lists.newArrayListWithCapacity(interfaceNames.length);
        for (String nameOfInterface : interfaceNames) {
            CtClass interfaze = getCtClass(clazz, nameOfInterface);
            if (interfaze != null) {
                interfaces.add(interfaze);
            }
        }
        return interfaces;
    }

    /**
     * Retrieves the superclass.
     * This method swallows class loading issues, returning only those classes that are accessible by the
     * {@link javassist.CtClass#getClassPool() class pool}.
     *
     * @since 1.6
     */
    @Nullable
    public static CtClass getSuperclassOf(@Nonnull CtClass clazz) {
        String nameOfSuperclass = clazz.getClassFile2().getSuperclass();
        return getCtClass(clazz, nameOfSuperclass);
    }


    /**
     * Retrieves the nested classes.
     * This method swallows class loading issues, returning only those classes that are accessible by the
     * {@link javassist.CtClass#getClassPool() class pool}.
     *
     * @since 1.6
     */
    @Nonnull
    public static Iterable<CtClass> getNestedClassesOf(@Nonnull CtClass clazz) {
        try {
            return asList(clazz.getNestedClasses());
        } catch (NotFoundException e) {
            handleMissingClass(e.getMessage());
            return Collections.emptyList();
        }
    }

    private static void handleMissingClass(@Nonnull String className) {
        getLogger(CtClasses.class).warn("The class path is not correctly set up; could not load {}!", className);
    }

    @Nullable
    private static CtClass getCtClass(@Nonnull CtClass classProvidingPool, @Nonnull String className) {
        return getCtClass(classProvidingPool.getClassPool(), className);
    }

}

@@ -170,16 +170,73 @@
 ception;
+%0Aimport org.slf4j.Logger;%0Aimport org.slf4j.LoggerFactory;
 %0A%0Aimport
@@ -477,57 +477,8 @@
 ist;
-%0Aimport static org.slf4j.LoggerFactory.getLogger;
 %0A%0A/*
@@ -626,16 +626,98 @@
 sses %7B%0A%0A
+    private static boolean issuedWarningForJavaLangAnnotationRepeatable = false;%0A%0A
     priv
@@ -734,24 +734,23 @@
 ses() %7B%0A
+
     %7D%0A%0A
-%0A
     /**%0A
@@ -4312,94 +4312,558 @@
 tic 
-void handleMissingClass(@Nonnull String className) %7B%0A        getLogger(CtClasses.class
+Logger getLogger() %7B%0A        return LoggerFactory.getLogger(CtClasses.class);%0A    %7D%0A%0A    private static void handleMissingClass(@Nonnull String className) %7B%0A        if (%22java.lang.annotation.Repeatable%22.equals(className)) %7B%0A            if (issuedWarningForJavaLangAnnotationRepeatable) %7B%0A                return;%0A            %7D%0A            getLogger().warn(%22Running with JDK %3C 8, but classes or libraries refer to JDK8 annotation %7B%7D.%22, className);%0A            issuedWarningForJavaLangAnnotationRepeatable = true;%0A        %7D else %7B%0A            getLogger(
 ).wa
@@ -4935,24 +4935,34 @@
 className);%0A
+        %7D%0A
     %7D%0A%0A    @

handle JDK mismatch