@SuppressWarnings("unchecked")
  public <T, I> T getImplementationClass( //
      QueryClassLoader classLoader, //
      TemplateClassDefinition<T> templateDefinition, //
      String entireClass, //
      String materializedClassName)
      throws ClassTransformationException {

    final ClassSet set =
        new ClassSet(null, templateDefinition.getTemplateClassName(), materializedClassName);

    try {
      final byte[][] implementationClasses =
          classLoader.getClassByteCode(set.generated.clazz, entireClass);

      Map<String, ClassNode> classesToMerge = Maps.newHashMap();
      for (byte[] clazz : implementationClasses) {
        ClassNode node = getClassNodeFromByteCode(clazz);
        classesToMerge.put(node.name, node);
      }

      LinkedList<ClassSet> names = Lists.newLinkedList();
      Set<ClassSet> namesCompleted = Sets.newHashSet();
      names.add(set);

      while (!names.isEmpty()) {
        final ClassSet nextSet = names.removeFirst();
        if (namesCompleted.contains(nextSet)) continue;
        final ClassNames nextPrecompiled = nextSet.precompiled;
        final byte[] precompiledBytes =
            byteCodeLoader.getClassByteCodeFromPath(nextPrecompiled.clazz);
        ClassNames nextGenerated = nextSet.generated;
        ClassNode generatedNode = classesToMerge.get(nextGenerated.slash);
        MergedClassResult result =
            MergeAdapter.getMergedClass(nextSet, precompiledBytes, generatedNode);

        for (String s : result.innerClasses) {
          s = s.replace(FileUtils.separatorChar, '.');
          names.add(nextSet.getChild(s));
        }
        classLoader.injectByteCode(nextGenerated.dot, result.bytes);
        namesCompleted.add(nextSet);
      }

      //      logger.debug(String.format("[Compile Time] Janino: %dms, Bytecode load and parse:
      // %dms, Class Merge: %dms, Subclass remap and load: %dms.",
      // t1.elapsed(TimeUnit.MILLISECONDS), t2.elapsed(TimeUnit.MILLISECONDS),
      // t3.elapsed(TimeUnit.MILLISECONDS), t4.elapsed(TimeUnit.MILLISECONDS)));

      Class<?> c = classLoader.findClass(set.generated.dot);
      if (templateDefinition.getExternalInterface().isAssignableFrom(c)) {
        return (T) c.newInstance();
      } else {
        throw new ClassTransformationException(
            "The requested class did not implement the expected interface.");
      }

    } catch (CompileException
        | IOException
        | ClassNotFoundException
        | InstantiationException
        | IllegalAccessException e) {
      throw new ClassTransformationException(
          String.format("Failure generating transformation classes for value: \n %s", entireClass),
          e);
    }
  }
  @SuppressWarnings("unchecked")
  public <T, I> T getImplementationClass( //
      QueryClassLoader classLoader, //
      TemplateClassDefinition<T> templateDefinition, //
      String entireClass, //
      String materializedClassName)
      throws ClassTransformationException {

    try {
      long t1 = System.nanoTime();
      final ClassSet set =
          new ClassSet(null, templateDefinition.getTemplateClassName(), materializedClassName);
      final byte[][] implementationClasses =
          classLoader.getClassByteCode(set.generated, entireClass);

      long totalBytecodeSize = 0;
      Map<String, ClassNode> classesToMerge = Maps.newHashMap();
      for (byte[] clazz : implementationClasses) {
        totalBytecodeSize += clazz.length;
        ClassNode node = getClassNodeFromByteCode(clazz);
        classesToMerge.put(node.name, node);
      }

      LinkedList<ClassSet> names = Lists.newLinkedList();
      Set<ClassSet> namesCompleted = Sets.newHashSet();
      names.add(set);

      while (!names.isEmpty()) {
        final ClassSet nextSet = names.removeFirst();
        if (namesCompleted.contains(nextSet)) continue;
        final ClassNames nextPrecompiled = nextSet.precompiled;
        final byte[] precompiledBytes =
            byteCodeLoader.getClassByteCodeFromPath(nextPrecompiled.clazz);
        ClassNames nextGenerated = nextSet.generated;
        ClassNode generatedNode = classesToMerge.get(nextGenerated.slash);
        MergedClassResult result =
            MergeAdapter.getMergedClass(nextSet, precompiledBytes, generatedNode);

        for (String s : result.innerClasses) {
          s = s.replace(FileUtils.separatorChar, '.');
          names.add(nextSet.getChild(s));
        }
        classLoader.injectByteCode(nextGenerated.dot, result.bytes);
        namesCompleted.add(nextSet);
      }

      Class<?> c = classLoader.findClass(set.generated.dot);
      if (templateDefinition.getExternalInterface().isAssignableFrom(c)) {
        T instance = (T) c.newInstance();
        logger.debug(
            "Done compiling (bytecode size={}, time:{} millis).",
            DrillStringUtils.readable(totalBytecodeSize),
            (System.nanoTime() - t1) / 1000000);
        return instance;
      } else {
        throw new ClassTransformationException(
            "The requested class did not implement the expected interface.");
      }
    } catch (CompileException
        | IOException
        | ClassNotFoundException
        | InstantiationException
        | IllegalAccessException e) {
      throw new ClassTransformationException(
          String.format("Failure generating transformation classes for value: \n %s", entireClass),
          e);
    }
  }