// Copyright 2016 The Bazel Authors. All rights reserved.
//
// 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.google.devtools.build.android.desugar;

import java.io.IOError;
import java.io.IOException;
import java.io.InputStream;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

/**
 * Class loader that can "load" classes from header Jars. This class loader stubs in missing code
 * attributes on the fly to make {@link ClassLoader#defineClass} happy.  Classes loaded are unusable
 * other than to resolve method references, so this class loader should only be used to process
 * or inspect classes, not to execute their code.  Also note that the resulting classes may be
 * missing private members, which header Jars may omit.
 *
 * @see java.net.URLClassLoader
 */
class HeaderClassLoader extends ClassLoader {

  private final IndexedInputs indexedInputs;
  private final CoreLibraryRewriter rewriter;

  public HeaderClassLoader(
      IndexedInputs indexedInputs, CoreLibraryRewriter rewriter, ClassLoader parent) {
    super(parent);
    this.rewriter = rewriter;
    this.indexedInputs = indexedInputs;
  }

  @Override
  protected Class<?> findClass(String name) throws ClassNotFoundException {
    String filename = rewriter.unprefix(name.replace('.', '/') + ".class");
    InputFileProvider inputFileProvider = indexedInputs.getInputFileProvider(filename);
    if (inputFileProvider == null) {
      throw new ClassNotFoundException();
    }
    byte[] bytecode;
    try (InputStream content = inputFileProvider.getInputStream(filename)) {
      ClassReader reader = rewriter.reader(content);
      // Have ASM compute maxs so we don't need to figure out how many formal parameters there are
      ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
      reader.accept(new CodeStubber(writer), 0);
      bytecode = writer.toByteArray();
    } catch (IOException e) {
      throw new IOError(e);
    }
    return defineClass(name, bytecode, 0, bytecode.length);
  }

  /** Class visitor that stubs in missing code attributes. */
  private static class CodeStubber extends ClassVisitor {

    public CodeStubber(ClassVisitor cv) {
      super(Opcodes.ASM5, cv);
    }

    @Override
    public MethodVisitor visitMethod(
        int access, String name, String desc, String signature, String[] exceptions) {
      MethodVisitor dest = super.visitMethod(access, name, desc, signature, exceptions);
      if ((access & (Opcodes.ACC_ABSTRACT | Opcodes.ACC_NATIVE)) != 0) {
        // No need to stub out abstract or native methods
        return dest;
      }
      return new BodyStubber(dest);
    }

  }

  /** Method visitor used by {@link CodeStubber} to put code into methods without code. */
  private static class BodyStubber extends MethodVisitor {

    private static final String EXCEPTION_INTERNAL_NAME = "java/lang/UnsupportedOperationException";

    private boolean hasCode = false;

    public BodyStubber(MethodVisitor mv) {
      super(Opcodes.ASM5, mv);
    }

    @Override
    public void visitCode() {
      hasCode = true;
      super.visitCode();
    }

    @Override
    public void visitEnd() {
      if (!hasCode) {
        super.visitTypeInsn(Opcodes.NEW, EXCEPTION_INTERNAL_NAME);
        super.visitInsn(Opcodes.DUP);
        super.visitMethodInsn(
            Opcodes.INVOKESPECIAL, EXCEPTION_INTERNAL_NAME, "<init>", "()V", /*itf*/ false);
        super.visitInsn(Opcodes.ATHROW);
        super.visitMaxs(0, 0); // triggers computation of the actual max's
      }
      super.visitEnd();
    }
  }
}

@@ -2095,16 +2095,46 @@
 ception(
+%22Class %22 + name + %22 not found%22
 );%0A    %7D

Add name of missing class to ClassNotFoundException