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.MethodVisitor;
  import org.objectweb.asm.Opcodes;
  
  import static org.apache.cassandra.simulator.asm.Flag.GLOBAL_METHODS;
  import static org.apache.cassandra.simulator.asm.TransformationKind.CONCURRENT_HASH_MAP;
  import static org.apache.cassandra.simulator.asm.TransformationKind.GLOBAL_METHOD;
  import static org.apache.cassandra.simulator.asm.TransformationKind.IDENTITY_HASH_MAP;
  
  /**
   * Intercept factory methods in org.apache.concurrent.utils.concurrent, and redirect them to
   * {@link org.apache.cassandra.simulator.systems.InterceptorOfGlobalMethods}
   */
  class GlobalMethodTransformer extends MethodVisitor
  {
      private final ClassTransformer transformer;
      private final String methodName;
      private boolean globalMethods;
      private boolean globalClock;
      private boolean systemClock;
      private boolean lockSupport;
      private boolean deterministic;
      boolean hasSeenAnyMethodInsn;
  
      public GlobalMethodTransformer(EnumSet<Flag> flags, ClassTransformer transformer, int api, String methodName, MethodVisitor parent)
      {
          super(api, parent);
          this.globalMethods = flags.contains(GLOBAL_METHODS);
          this.globalClock = flags.contains(Flag.GLOBAL_CLOCK);
          this.systemClock = flags.contains(Flag.SYSTEM_CLOCK);
          this.lockSupport = flags.contains(Flag.LOCK_SUPPORT);
          this.deterministic = flags.contains(Flag.DETERMINISTIC);
          this.transformer = transformer;
          this.methodName = methodName;
      }
  
      @Override
      public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface)
      {
          boolean isFirstMethodInsn = !hasSeenAnyMethodInsn;
          hasSeenAnyMethodInsn = true;
  
          if (globalMethods && opcode == Opcodes.INVOKESTATIC && owner.startsWith("org/apache/cassandra/utils/") && (
                 (owner.equals("org/apache/cassandra/utils/concurrent/WaitQueue") && name.equals("newWaitQueue"))
              || (owner.equals("org/apache/cassandra/utils/concurrent/CountDownLatch") && name.equals("newCountDownLatch"))
              || (owner.equals("org/apache/cassandra/utils/concurrent/Condition") && name.equals("newOneTimeCondition"))
              || (owner.equals("org/apache/cassandra/utils/concurrent/BlockingQueues") && name.equals("newBlockingQueue"))
              || (owner.equals("org/apache/cassandra/utils/concurrent/Semaphore") && (name.equals("newSemaphore") || name.equals("newFairSemaphore")))
              ))
          {
              transformer.witness(GLOBAL_METHOD);
              super.visitMethodInsn(Opcodes.INVOKESTATIC, "org/apache/cassandra/simulator/systems/InterceptorOfGlobalMethods$Global", name, descriptor, false);
          }
          else if (globalMethods && ((opcode == Opcodes.INVOKEVIRTUAL && owner.equals("java/util/concurrent/TimeUnit") && name.equals("sleep"))))
          {
              transformer.witness(GLOBAL_METHOD);
              super.visitMethodInsn(Opcodes.INVOKESTATIC, "org/apache/cassandra/simulator/systems/InterceptorOfSystemMethods$Global", name, "(Ljava/lang/Object;J)V", false);
          }
          else if (globalMethods && ((opcode == Opcodes.INVOKESTATIC && (
                     owner.startsWith("org/apache/cassandra/utils/") && (
                          (owner.equals("org/apache/cassandra/utils/Clock") && name.equals("waitUntil"))
                       || (owner.equals("org/apache/cassandra/utils/concurrent/Awaitable$SyncAwaitable") && name.equals("waitUntil")))
                  || !deterministic && owner.equals("java/lang/System") && name.equals("identityHashCode")
                  || owner.equals("java/util/UUID") && name.equals("randomUUID")
                  || owner.equals("com/google/common/util/concurrent/Uninterruptibles") && name.equals("sleepUninterruptibly")
                  || owner.equals("sun/misc/Unsafe") && name.equals("getUnsafe"))))
          )
          {
              transformer.witness(GLOBAL_METHOD);
              super.visitMethodInsn(Opcodes.INVOKESTATIC, "org/apache/cassandra/simulator/systems/InterceptorOfSystemMethods$Global", name, descriptor, false);
          }
          else if (owner.equals("java/util/concurrent/TimeUnit") && name.equals("sleep"))
          {
              transformer.witness(GLOBAL_METHOD);
              super.visitMethodInsn(Opcodes.INVOKESTATIC, "org/apache/cassandra/simulator/systems/InterceptorOfSystemMethods$Global", "sleep", "(Ljava/util/concurrent/TimeUnit;J)V", false);
          }
          else if ((globalMethods || deterministic) && opcode == Opcodes.INVOKESTATIC &&
                   ((owner.equals("java/util/concurrent/ThreadLocalRandom") && (name.equals("getProbe") || name.equals("advanceProbe") || name.equals("localInit")))
                    || (owner.equals("java/util/concurrent/atomic/Striped64") && (name.equals("getProbe") || name.equals("advanceProbe"))))
          )
          {
              transformer.witness(GLOBAL_METHOD);
              // if we're in deterministic mode (i.e. for base ConcurrentHashMap) don't initialise ThreadLocalRandom
              if (name.equals("getProbe")) super.visitLdcInsn(0);
              else if (name.equals("advanceProbe")) super.visitMethodInsn(Opcodes.INVOKESTATIC, "org/apache/cassandra/simulator/systems/InterceptorOfSystemMethods$Global", name, descriptor, false);
          }
          else if (globalMethods && opcode == Opcodes.INVOKESPECIAL && owner.equals("java/util/IdentityHashMap") && name.equals("<init>"))
          {
              transformer.witness(IDENTITY_HASH_MAP);
              super.visitMethodInsn(opcode, "org/apache/cassandra/simulator/systems/InterceptedIdentityHashMap", name, descriptor, false);
          }
          else if (globalMethods && opcode == Opcodes.INVOKESPECIAL && owner.equals("java/util/concurrent/ConcurrentHashMap") && name.equals("<init>")
                   && !(transformer.className().equals("org/apache/cassandra/simulator/systems/InterceptibleConcurrentHashMap") && methodName.equals("<init>") && isFirstMethodInsn))
          {
              transformer.witness(CONCURRENT_HASH_MAP);
              super.visitMethodInsn(opcode, "org/apache/cassandra/simulator/systems/InterceptibleConcurrentHashMap", name, descriptor, false);
          }
          else if (lockSupport && opcode == Opcodes.INVOKESTATIC && owner.equals("java/util/concurrent/locks/LockSupport") && (name.startsWith("park") || name.equals("unpark")))
          {
              transformer.witness(TransformationKind.LOCK_SUPPORT);
              super.visitMethodInsn(Opcodes.INVOKESTATIC, "org/apache/cassandra/simulator/systems/InterceptorOfSystemMethods$Global", name, descriptor, false);
          }
          else if (globalClock && opcode == Opcodes.INVOKESTATIC && name.equals("timestampMicros") && owner.equals("org/apache/cassandra/utils/FBUtilities"))
          {
              transformer.witness(GLOBAL_METHOD);
              super.visitMethodInsn(Opcodes.INVOKESTATIC, "org/apache/cassandra/simulator/systems/SimulatedTime$Global", "nextGlobalMonotonicMicros", descriptor, false);
          }
          else if (systemClock && opcode == Opcodes.INVOKESTATIC && owner.equals("java/lang/System") && (name.equals("nanoTime") || name.equals("currentTimeMillis")))
          {
              transformer.witness(GLOBAL_METHOD);
              super.visitMethodInsn(Opcodes.INVOKESTATIC, "org/apache/cassandra/simulator/systems/InterceptorOfSystemMethods$Global", name, descriptor, false);
          }
          else
          {
              super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
          }
      }
  
      @Override
      public void visitTypeInsn(int opcode, String type)
      {
          if (globalMethods && opcode == Opcodes.NEW && type.equals("java/util/IdentityHashMap"))
          {
              super.visitTypeInsn(opcode, "org/apache/cassandra/simulator/systems/InterceptedIdentityHashMap");
          }
          else if (globalMethods && opcode == Opcodes.NEW && type.equals("java/util/concurrent/ConcurrentHashMap"))
          {
              super.visitTypeInsn(opcode, "org/apache/cassandra/simulator/systems/InterceptibleConcurrentHashMap");
          }
          else
          {
              super.visitTypeInsn(opcode, type);
          }
      }
  
      @Override
      public AnnotationVisitor visitAnnotation(String descriptor, boolean visible)
      {
          return Utils.checkForSimulationAnnotations(api, descriptor, super.visitAnnotation(descriptor, visible), (flag, add) -> {
              switch (flag)
              {
                  default: throw new AssertionError();
                  case GLOBAL_METHODS: globalMethods = add; break;
                  case GLOBAL_CLOCK: globalClock = add; break;
                  case SYSTEM_CLOCK: systemClock = add; break;
                  case LOCK_SUPPORT: lockSupport = add; break;
                  case DETERMINISTIC: deterministic = add; break;
                  case MONITORS: throw new UnsupportedOperationException("Cannot currently toggle MONITORS at the method level");
              }
          });
      }
  }
  