// Copyright 2019 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.nest;

import static com.google.devtools.build.android.desugar.langmodel.LangModelConstants.NEST_COMPANION_CLASS_SIMPLE_NAME;
import static org.objectweb.asm.Opcodes.ACC_ABSTRACT;
import static org.objectweb.asm.Opcodes.ACC_INTERFACE;
import static org.objectweb.asm.Opcodes.ACC_SYNTHETIC;

import com.google.devtools.build.android.desugar.langmodel.ClassName;
import com.google.devtools.build.android.desugar.langmodel.FieldKey;
import com.google.devtools.build.android.desugar.langmodel.MethodDeclInfo;
import com.google.devtools.build.android.desugar.langmodel.MethodKey;
import javax.annotation.Nullable;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

/**
 * A class visitor that rewrites visited classes and interfaces with nested-based access [JEP 181].
 * For any private class member with access from another class or interface within the same nest,
 * The visitor generates visibility-broadened bridge methods and update all call sites. For any
 * private interface member within access from another class or interface within the same nest, the
 * visitor broadens the visibility of private interface methods, renames these interface members and
 * update their call sites.
 */
public final class NestDesugaring extends ClassVisitor {

  private static final int NEST_COMPANION_CLASS_ACCESS_CODE =
      Opcodes.ACC_SYNTHETIC | Opcodes.ACC_ABSTRACT | Opcodes.ACC_STATIC;

  private final NestDigest nestDigest;

  private FieldAccessBridgeEmitter fieldAccessBridgeEmitter;
  private MethodAccessorEmitter methodAccessorEmitter;

  private ClassName className;
  private int classAccess;
  @Nullable private ClassVisitor nestCompanionVisitor;

  /** Whether the class being visited is a nest host with with a nest companion class. */
  private boolean isNestHostWithNestCompanion;

  public NestDesugaring(ClassVisitor classVisitor, NestDigest nestDigest) {
    super(Opcodes.ASM8, classVisitor);
    this.nestDigest = nestDigest;
  }

  @Override
  public void visit(
      int version,
      int access,
      String name,
      String signature,
      String superName,
      String[] interfaces) {
    className = ClassName.create(name);
    classAccess = access;
    nestCompanionVisitor = nestDigest.getCompanionClassWriter(className);
    isNestHostWithNestCompanion =
        (nestCompanionVisitor != null) && className.equals(nestDigest.nestHost(className).get());
    fieldAccessBridgeEmitter = new FieldAccessBridgeEmitter();
    methodAccessorEmitter = new MethodAccessorEmitter(nestDigest);
    super.visit(
        Math.min(version, NestDesugarConstants.MIN_VERSION),
        access,
        name,
        signature,
        superName,
        interfaces);
    if (isNestHostWithNestCompanion) {
      nestCompanionVisitor.visit(
          Math.min(version, NestDesugarConstants.MIN_VERSION),
          ACC_SYNTHETIC | ACC_ABSTRACT,
          nestDigest.nestCompanion(className).binaryName(),
          /* signature= */ null,
          /* superName= */ "java/lang/Object",
          /* interfaces= */ new String[0]);
    }
  }

  @Override
  public FieldVisitor visitField(
      int access, String name, String descriptor, String signature, Object value) {
    FieldKey fieldKey = FieldKey.create(className, name, descriptor);
    // Generates necessary bridge methods for this field, including field getter methods and field
    // setter methods. See FieldAccessBridgeEmitter.
    nestDigest
        .findAllMemberUseKinds(fieldKey)
        .forEach(useKind -> fieldKey.accept(useKind, fieldAccessBridgeEmitter, cv));
    return super.visitField(access, name, descriptor, signature, value);
  }

  @Override
  public MethodVisitor visitMethod(
      int access, String name, String descriptor, String signature, String[] exceptions) {
    MethodKey methodKey = MethodKey.create(className, name, descriptor);
    if (nestDigest.hasAnyTrackingReason(methodKey)) {
      MethodDeclInfo methodDeclInfo =
          MethodDeclInfo.create(methodKey, classAccess, access, signature, exceptions);
      // For interfaces, the following .accept converts the original method into a
      // visibility-broadened renamed method in-place while preserving the method body
      // implementation; for classes, the following .accept generates synthetic bridge methods and
      // preserve the original methods. The returned method visitor is subject to further desugar
      // operations for interfaces. See {@link MethodAccessorEmitter}
      MethodVisitor targetMethodVisitor = methodDeclInfo.accept(methodAccessorEmitter, cv);

      // For classes, we continue to desugar the code of the bridge's originating method. For
      // interfaces, we continue to desugar the header-modified methods. The code of the bridge
      // method is one simple delegation call to its originating method without any code subject to
      // desugar.
      MethodVisitor primaryMethodVisitor =
          (classAccess & ACC_INTERFACE) != 0
              ? targetMethodVisitor
              : super.visitMethod(access, name, descriptor, signature, exceptions);
      return new NestBridgeRefConverter(primaryMethodVisitor, methodKey, nestDigest);
    }
    // In absence of method invocation record, fallback to the delegate method visitor.
    MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
    return mv == null ? null : new NestBridgeRefConverter(mv, methodKey, nestDigest);
  }

  @Override
  public void visitNestHost(String nestHost) {
    // Intentionally omit super call to avoid writing NestHost attribute to the class file.
  }

  @Override
  public void visitNestMember(String nestMember) {
    // Intentionally omit super call to avoid writing the NestMembers attribute to the class file.
  }

  @Override
  public void visitSource(String source, String debug) {
    super.visitSource(source, debug);
    if (isNestHostWithNestCompanion) {
      nestCompanionVisitor.visitSource(source, debug);
    }
  }

  @Override
  public void visitEnd() {
    if (isNestHostWithNestCompanion) {
      ClassName nestCompanion = nestDigest.nestCompanion(className);
      // In the nest companion class, marks its outer class as the nest host.
      nestCompanionVisitor.visitInnerClass(
          nestCompanion.binaryName(),
          className.binaryName(),
          NEST_COMPANION_CLASS_SIMPLE_NAME,
          NEST_COMPANION_CLASS_ACCESS_CODE);
      // In the nest host class, marks the nest companion as one of its inner classes.
      cv.visitInnerClass(
          nestCompanion.binaryName(),
          className.binaryName(),
          NEST_COMPANION_CLASS_SIMPLE_NAME,
          NEST_COMPANION_CLASS_ACCESS_CODE);
    }
    super.visitEnd();
  }
}
