content: /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you 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 org.apache.cassandra.simulator.asm;
  
  import java.util.EnumSet;
  
  import org.objectweb.asm.AnnotationVisitor;
  import org.objectweb.asm.FieldVisitor;
  import org.objectweb.asm.Handle;
  import org.objectweb.asm.Label;
  import org.objectweb.asm.MethodVisitor;
  import org.objectweb.asm.Type;
  import org.objectweb.asm.TypePath;
  
  /**
   * A SORT OF general purpose facility for creating a copy of a system class that we want to transform
   * and use in place of the system class without transforming the system class itself.
   *
   * NOTE that this does not implement this translation perfectly, so care must be taken when extending its usage
   * Some things not handled:
   *   - generic type signatures in class files
   *   - 
   *
   * While it is possible and safe in principle to modify ConcurrentHashMap in particular, in practice it messes
   * with class loading, as ConcurrentHashMap is used widely within the JDK, including for things like class loaders
   * and method handle caching. It seemed altogether more tractable and safe to selectively replace ConcurrentHashMap
   * with a shadowed variety.
   *
   * This approach makes some rough assumptions, namely that any public method on the root class should accept the
   * shadowed type, but that any inner class may safely use the shadow type.
   */
  public class ShadowingTransformer extends ClassTransformer
  {
      final String originalType;
      final String originalRootType;
      final String shadowRootType;
      final String originalOuterTypePrefix;
      final String shadowOuterTypePrefix;
      String originalSuperName;
  
      ShadowingTransformer(int api, String originalType, String shadowType, String originalRootType, String shadowRootType, String originalOuterTypePrefix, String shadowOuterTypePrefix, EnumSet<Flag> flags, ChanceSupplier monitorDelayChance, NemesisGenerator nemesis, NemesisFieldKind.Selector nemesisFieldSelector, Hashcode insertHashcode)
      {
          super(api, shadowType, flags, monitorDelayChance, nemesis, nemesisFieldSelector, insertHashcode, null);
          this.originalType = originalType;
          this.originalRootType = originalRootType;
          this.shadowRootType = shadowRootType;
          this.originalOuterTypePrefix = originalOuterTypePrefix;
          this.shadowOuterTypePrefix = shadowOuterTypePrefix;
      }
  
      private String toShadowType(String type)
      {
          if (type.startsWith("["))
              return toShadowTypeDescriptor(type);
          else if (type.equals(originalRootType))
              type = shadowRootType;
          else if (type.startsWith(originalOuterTypePrefix))
              type = shadowOuterTypePrefix + type.substring(originalOuterTypePrefix.length());
          else
              return type;
  
          witness(TransformationKind.SHADOW);
          return type;
      }
  
      private String toShadowTypeDescriptor(String owner)
      {
          return toShadowTypeDescriptor(owner, false);
      }
  
      private String toShadowTypeDescriptor(String desc, boolean innerTypeOnly)
      {
          int i = 0;
          while (i < desc.length() && desc.charAt(i) == '[') ++i;
          if (desc.charAt(i) != 'L')
              return desc;
          ++i;
  
          if (!innerTypeOnly && desc.regionMatches(i, originalRootType, 0, originalRootType.length()) && desc.length() == originalRootType.length() + 1 + i && desc.charAt(i + originalRootType.length()) == ';')
              desc = desc.substring(0, i) + shadowRootType + ';';
          else if (desc.regionMatches(i, originalOuterTypePrefix, 0, originalOuterTypePrefix.length()))
              desc = desc.substring(0, i) + shadowOuterTypePrefix + desc.substring(i + originalOuterTypePrefix.length());
          else
              return desc;
  
          witness(TransformationKind.SHADOW);
          return desc;
      }
  
      private Type toShadowTypeDescriptor(Type type)
      {
          String in = type.getDescriptor();
          String out = toShadowTypeDescriptor(in, false);
          if (in == out) return type;
          return Type.getType(out);
      }
  
      private Type toShadowInnerTypeDescriptor(Type type)
      {
          String in = type.getDescriptor();
          String out = toShadowTypeDescriptor(in, true);
          if (in == out) return type;
          return Type.getType(out);
      }
  
      Object[] toShadowTypes(Object[] in)
      {
          Object[] out = null;
          for (int i = 0 ; i < in.length ; ++i)
          {
              if (in[i] instanceof String)
              {
                  // TODO (broader correctness): in some cases we want the original type, and others the new type
                  String inv = (String) in[i];
                  String outv = toShadowType(inv);
                  if (inv != outv)
                  {
                      if (out == null)
                      {
                          out = new Object[in.length];
                          System.arraycopy(in, 0, out, 0, i);
                      }
                      out[i] = outv;
                      continue;
                  }
              }
  
              if (out != null)
                  out[i] = in[i];
          }
          return out != null ? out : in;
      }
  
      String methodDescriptorToShadowInnerArgumentTypes(String descriptor)
      {
          Type ret = toShadowTypeDescriptor(Type.getReturnType(descriptor));
          Type[] args = Type.getArgumentTypes(descriptor);
          for (int i = 0 ; i < args.length ; ++i)
              args[i] = toShadowInnerTypeDescriptor(args[i]);
          return Type.getMethodDescriptor(ret, args);
      }
  
      String methodDescriptorToShadowTypes(String descriptor)
      {
          Type ret = toShadowTypeDescriptor(Type.getReturnType(descriptor));
          Type[] args = Type.getArgumentTypes(descriptor);
          for (int i = 0 ; i < args.length ; ++i)
              args[i] = toShadowTypeDescriptor(args[i]);
          return Type.getMethodDescriptor(ret, args);
      }
  
      class ShadowingMethodVisitor extends MethodVisitor
      {
          final boolean isConstructor;
          public ShadowingMethodVisitor(int api, boolean isConstructor, MethodVisitor methodVisitor)
          {
              super(api, methodVisitor);
              this.isConstructor = isConstructor;
          }
  
          @Override
          public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible)
          {
              return super.visitTypeAnnotation(typeRef, typePath, descriptor, visible);
          }
  
          @Override
          public void visitFieldInsn(int opcode, String owner, String name, String descriptor)
          {
              super.visitFieldInsn(opcode, toShadowType(owner), name, toShadowTypeDescriptor(descriptor));
          }
  
          @Override
          public void visitTypeInsn(int opcode, String type)
          {
              // TODO (broader correctness): in some cases we want the original type, and others the new type
              super.visitTypeInsn(opcode, toShadowType(type));
          }
  
          @Override
          public void visitLocalVariable(String name, String descriptor, String signature, Label start, Label end, int index)
          {
              super.visitLocalVariable(name, toShadowTypeDescriptor(descriptor), signature, start, end, index);
          }
  
          @Override
          public void visitFrame(int type, int numLocal, Object[] local, int numStack, Object[] stack)
          {
              super.visitFrame(type, numLocal, toShadowTypes(local), numStack, toShadowTypes(stack));
          }
  
          @Override
          public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface)
          {
              // TODO (broader correctness): this is incorrect, but will do for ConcurrentHashMap (no general guarantee of same constructors)
              if (owner.equals(originalRootType)) descriptor = methodDescriptorToShadowInnerArgumentTypes(descriptor);
              else descriptor = methodDescriptorToShadowTypes(descriptor);
              if (isConstructor && name.equals("<init>") && owner.equals(originalSuperName) && originalType.equals(originalRootType)) owner = originalRootType;
              else owner = toShadowType(owner);
              super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
          }
  
          @Override
          public void visitInvokeDynamicInsn(String name, String descriptor, Handle bootstrapMethodHandle, Object... bootstrapMethodArguments)
          {
              if (bootstrapMethodHandle.getOwner().startsWith(originalRootType))
              {
                  bootstrapMethodHandle = new Handle(bootstrapMethodHandle.getTag(), toShadowType(bootstrapMethodHandle.getOwner()),
                                                     bootstrapMethodHandle.getName(), bootstrapMethodHandle.getDesc(),
                                                     bootstrapMethodHandle.isInterface());
              }
              super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments);
          }
  
          @Override
          public void visitLdcInsn(Object value)
          {
              if (value instanceof Type)
                  value = toShadowTypeDescriptor((Type) value);
              super.visitLdcInsn(value);
          }
      }
  
      @Override
      public void visitInnerClass(String name, String outerName, String innerName, int access)
      {
          super.visitInnerClass(name, toShadowType(outerName), innerName, access);
      }
  
      @Override
      public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value)
      {
          return super.visitField(access, name, toShadowTypeDescriptor(descriptor), signature, value);
      }
  
      @Override
      public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions)
      {
          if (originalType.equals(originalRootType)) descriptor = methodDescriptorToShadowInnerArgumentTypes(descriptor);
          else descriptor = methodDescriptorToShadowTypes(descriptor);
          return new ShadowingMethodVisitor(api, name.equals("<init>"), super.visitMethod(access, name, descriptor, signature, exceptions));
      }
  
      @Override
      public void visit(int version, int access, String name, String signature, String superName, String[] interfaces)
      {
          originalSuperName = superName;
          if (originalType.equals(originalRootType))
          {
              superName = name;
              name = shadowRootType;
          }
          else
          {
              name = toShadowType(name);
              superName = toShadowType(superName);
          }
  
          super.visit(version, access, name, signature, superName, interfaces);
      }
  
  }
  