All Downloads are FREE. Search and download functionalities are using the official Maven repository.

mockit.internal.expectations.mocking.SubclassGenerationModifier Maven / Gradle / Ivy

/*
 * Copyright (c) 2006-2012 Rogério Liesenfeld
 * This file is subject to the terms of the MIT license (see LICENSE.txt).
 */
package mockit.internal.expectations.mocking;

import java.lang.reflect.*;
import java.lang.reflect.Type;
import java.util.*;

import static java.util.Arrays.*;
import static mockit.external.asm4.Opcodes.*;

import mockit.external.asm4.*;
import mockit.internal.*;
import mockit.internal.filtering.*;
import mockit.internal.util.*;

public final class SubclassGenerationModifier extends MockedTypeModifier
{
   private static final int CLASS_ACCESS_MASK = 0xFFFF - ACC_ABSTRACT;
   private static final int CONSTRUCTOR_ACCESS_MASK = ACC_PUBLIC + ACC_PROTECTED;

   // Fixed initial state:
   private final MockingConfiguration mockingCfg;
   private final Class abstractClass;
   private final String subclassName;
   private boolean copyConstructors;

   // Helper fields for mutable state:
   private String superClassOfSuperClass;
   private Set superInterfaces;
   private final List implementedMethods;

   public SubclassGenerationModifier(Type mockedType, ClassReader classReader, String subclassName)
   {
      this(null, mockedType, classReader, subclassName);
      copyConstructors = true;
   }

   SubclassGenerationModifier(
      MockingConfiguration mockingConfiguration, Type mockedType, ClassReader classReader, String subclassName)
   {
      super(classReader, mockedType);
      mockingCfg = mockingConfiguration;
      abstractClass = Utilities.getClassType(mockedType);
      this.subclassName = subclassName.replace('.', '/');
      implementedMethods = new ArrayList();
      implementationSignature = 'L' + abstractClass.getName().replace('.', '/') + implementationSignature;
   }

   @Override
   public void visit(int version, int access, String name, String signature, String superName, String[] interfaces)
   {
      int subclassAccess = access & CLASS_ACCESS_MASK | ACC_FINAL;
      super.visit(version, subclassAccess, subclassName, implementationSignature, name, null);
      superClassOfSuperClass = superName;
      superInterfaces = new HashSet(asList(interfaces));
   }

   @Override
   public void visitInnerClass(String name, String outerName, String innerName, int access) {}

   @Override
   public void visitOuterClass(String owner, String name, String desc) {}

   @Override
   public void visitAttribute(Attribute attr) {}

   @Override
   public void visitSource(String file, String debug) {}

   @Override
   public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { return null; }

   @Override
   public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions)
   {
      if (copyConstructors && "".equals(name)) {
         if ((access & CONSTRUCTOR_ACCESS_MASK) != 0) {
            generateConstructorDelegatingToSuper(desc, signature, exceptions);
         }
      }
      else {
         // Inherits from super-class when non-abstract.
         // Otherwise, creates implementation for abstract method with call to "recordOrReplay".
         generateImplementationIfAbstractMethod(superClassName, access, name, desc, signature, exceptions);
      }

      return null;
   }

   private void generateConstructorDelegatingToSuper(String desc, String signature, String[] exceptions)
   {
      mw = super.visitMethod(ACC_PUBLIC, "", desc, signature, exceptions);
      mw.visitVarInsn(ALOAD, 0);
      int var = 1;

      for (mockit.external.asm4.Type paramType : mockit.external.asm4.Type.getArgumentTypes(desc)) {
         int loadOpcode = getLoadOpcodeForParameterType(paramType.getSort());
         mw.visitVarInsn(loadOpcode, var);
         var++;
      }

      mw.visitMethodInsn(INVOKESPECIAL, superClassName, "", desc);
      generateEmptyImplementation();
   }

   private int getLoadOpcodeForParameterType(int paramType)
   {
      if (paramType <= mockit.external.asm4.Type.INT) {
         return ILOAD;
      }

      switch (paramType) {
         case mockit.external.asm4.Type.FLOAT:  return FLOAD;
         case mockit.external.asm4.Type.LONG:   return LLOAD;
         case mockit.external.asm4.Type.DOUBLE: return DLOAD;
         default: return ALOAD;
      }
   }

   private void generateImplementationIfAbstractMethod(
      String className, int access, String name, String desc, String signature, String[] exceptions)
   {
      if (!"".equals(name)) {
         String methodNameAndDesc = name + desc;

         if (!implementedMethods.contains(methodNameAndDesc)) {
            if ((access & ACC_ABSTRACT) != 0) {
               generateMethodImplementation(className, access, name, desc, signature, exceptions);
            }

            implementedMethods.add(methodNameAndDesc);
         }
      }
   }

   @SuppressWarnings("AssignmentToMethodParameter")
   private void generateMethodImplementation(
      String className, int access, String name, String desc, String signature, String[] exceptions)
   {
      if (signature != null) {
         signature = genericTypeMap.resolveReturnType(signature);
      }

      mw = super.visitMethod(ACC_PUBLIC, name, desc, signature, exceptions);

      boolean noFiltersToMatch = mockingCfg == null;

      if (
         noFiltersToMatch && !isMethodFromObject(name, desc) ||
         !noFiltersToMatch && mockingCfg.matchesFilters(name, desc)
      ) {
         generateDirectCallToHandler(className, access, name, desc, signature, exceptions, 0);
         generateReturnWithObjectAtTopOfTheStack(desc);
         mw.visitMaxs(1, 0);
      }
      else {
         generateEmptyImplementation(desc);
      }
   }

   @Override
   public void visitEnd()
   {
      generateImplementationsForInheritedAbstractMethods(superClassOfSuperClass);

      while (!superInterfaces.isEmpty()) {
         String superInterface = superInterfaces.iterator().next();
         generateImplementationsForInterfaceMethods(superInterface);
         superInterfaces.remove(superInterface);
      }
   }

   private void generateImplementationsForInheritedAbstractMethods(String superName)
   {
      if (!"java/lang/Object".equals(superName)) {
         new MethodModifierForSuperclass(superName);
      }
   }

   private void generateImplementationsForInterfaceMethods(String superName)
   {
      if (!"java/lang/Object".equals(superName)) {
         new MethodModifierForImplementedInterface(superName);
      }
   }

   private class BaseMethodModifier extends ClassVisitor
   {
      final String typeName;

      BaseMethodModifier(String typeName)
      {
         this.typeName = typeName;
         ClassFile.visitClass(typeName, this);
      }

      @Override
      public FieldVisitor visitField(int access, String name, String desc, String signature, Object value)
      {
         return null;
      }
   }

   private final class MethodModifierForSuperclass extends BaseMethodModifier
   {
      String superName;

      MethodModifierForSuperclass(String className) { super(className); }

      @Override
      public void visit(int version, int access, String name, String signature, String superName, String[] interfaces)
      {
         this.superName = superName;

         if (interfaces != null) {
            superInterfaces.addAll(asList(interfaces));
         }
      }

      @Override
      public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions)
      {
         generateImplementationIfAbstractMethod(typeName, access, name, desc, signature, exceptions);
         return null;
      }

      @Override
      public void visitEnd()
      {
         generateImplementationsForInheritedAbstractMethods(superName);
      }
   }

   private final class MethodModifierForImplementedInterface extends BaseMethodModifier
   {
      MethodModifierForImplementedInterface(String interfaceName) { super(interfaceName); }

      @Override
      public void visit(int version, int access, String name, String signature, String superName, String[] interfaces)
      {
         superInterfaces.addAll(asList(interfaces));
      }

      @Override
      public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions)
      {
         generateImplementationForInterfaceMethodIfMissing(access, name, desc, signature, exceptions);
         return null;
      }

      private void generateImplementationForInterfaceMethodIfMissing(
         int access, String name, String desc, String signature, String[] exceptions)
      {
         String methodNameAndDesc = name + desc;

         if (!implementedMethods.contains(methodNameAndDesc)) {
            if (!hasMethodImplementation(name, desc)) {
               generateMethodImplementation(typeName, access, name, desc, signature, exceptions);
            }

            implementedMethods.add(methodNameAndDesc);
         }
      }

      private boolean hasMethodImplementation(String name, String desc)
      {
         Class[] paramTypes = TypeDescriptor.getParameterTypes(desc);

         try {
            Method method = abstractClass.getMethod(name, paramTypes);
            return !method.getDeclaringClass().isInterface();
         }
         catch (NoSuchMethodException ignore) {
            return false;
         }
      }
   }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy