docs: public Class<? extends T> buildProxyClass() throws IOException {
          ClassLoader requestedClassloader;
          if (sharedClassLoader) {
              requestedClassloader = baseClass.getClassLoader();
          } else {
              requestedClassloader = parentClassLoader;
          }
  
          // try the cache to see if we've generated this one before
          // we only populate the map with matching types
          ProxiedClass<T> cacheKey =
                  new ProxiedClass<>(baseClass, interfaces, requestedClassloader, sharedClassLoader);
          @SuppressWarnings("unchecked")
          Class<? extends T> proxyClass = (Class) generatedProxyClasses.get(cacheKey);
          if (proxyClass != null) {
              return proxyClass; // cache hit!
          }
  
          // the cache missed; generate the class
          DexMaker dexMaker = new DexMaker();
          String generatedName = getMethodNameForProxyOf(baseClass, interfaces);
          TypeId<? extends T> generatedType = TypeId.get("L" + generatedName + ";");
          TypeId<T> superType = TypeId.get(baseClass);
          generateConstructorsAndFields(dexMaker, generatedType, superType, baseClass);
  
          Method[] methodsToProxy;
          if (methods == null) {
              methodsToProxy = getMethodsToProxyRecursive();
          } else {
              methodsToProxy = methods;
          }
  
          // Sort the results array so that they are in a deterministic fashion.
          //
          // We use the same parameters to sort as used in {@link MethodId#hashCode}. This is needed
          // as e.g. making a method "public" instead of "protected" should not change the id's of the
          // methods. If the id's would change the classes loaded from the cache would be incorrect.
          Arrays.sort(methodsToProxy, new Comparator<Method>() {
              @Override
              public int compare(Method method1, Method method2) {
                  String m1Signature = method1.getDeclaringClass() + method1.getName() + Arrays.toString(method1.getParameterTypes()) + method1.getReturnType();
                  String m2Signature = method2.getDeclaringClass() + method2.getName() + Arrays.toString(method2.getParameterTypes()) + method2.getReturnType();
  
                  return m1Signature.compareTo(m2Signature);
              }
          });
  
          generateCodeForAllMethods(dexMaker, generatedType, methodsToProxy, superType);
          dexMaker.declare(generatedType, generatedName + ".generated", PUBLIC, superType, getInterfacesAsTypeIds());
          if (sharedClassLoader) {
              dexMaker.setSharedClassLoader(requestedClassloader);
          }
          if (markTrusted) {
              // The proxied class might have blacklisted methods. Blacklisting methods (and fields)
              // is a new feature of Android P:
              //
              // https://android-developers.googleblog.com/2018/02/
              // improving-stability-by-reducing-usage.html
              //
              // The newly generated class might not be allowed to call methods of the proxied class
              // if it is not trusted. As it is not clear which classes have blacklisted methods, mark
              // all generated classes as trusted.
              dexMaker.markAsTrusted();
          }
          ClassLoader classLoader;
          if (sharedClassLoader) {
              classLoader = dexMaker.generateAndLoad(null, dexCache);
          } else {
              classLoader = dexMaker.generateAndLoad(parentClassLoader, dexCache);
          }
          try {
              proxyClass = loadClass(classLoader, generatedName);
          } catch (IllegalAccessError e) {
              // Thrown when the base class is not accessible.
              throw new UnsupportedOperationException(
                      "cannot proxy inaccessible class " + baseClass, e);
          } catch (ClassNotFoundException e) {
              // Should not be thrown, we're sure to have generated this class.
              throw new AssertionError(e);
          }
          setMethodsStaticField(proxyClass, methodsToProxy);
          generatedProxyClasses.put(cacheKey, proxyClass);
          return proxyClass;
      }