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

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

The newest version!
/*
 * JMockit Expectations & Verifications
 * Copyright (c) 2006-2010 Rogério Liesenfeld
 * All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package mockit.internal.expectations.mocking;

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

import static mockit.external.asm.Opcodes.*;

import mockit.external.asm.*;
import mockit.external.asm.Type;
import mockit.external.asm.commons.*;
import mockit.internal.*;
import mockit.internal.filtering.*;
import mockit.internal.util.*;

final class SubclassGenerationModifier extends BaseClassModifier
{
   private static final int CLASS_ACCESS_MASK = 0xFFFF - ACC_ABSTRACT;

   private final MockingConfiguration mockingConfiguration;
   private final Class abstractClass;
   private final String subclassName;
   private String superClassName;
   private String superClassOfSuperClass;
   private String[] initialSuperInterfaces;
   private final MockConstructorInfo mockConstructorInfo;
   private boolean defaultConstructorCreated;
   private final List implementedMethods;

   SubclassGenerationModifier(
      MockConstructorInfo mockConstructorInfo, MockingConfiguration mockingConfiguration,
      Class abstractClass, ClassReader classReader, String subclassName)
   {
      super(classReader);
      this.mockingConfiguration = mockingConfiguration;
      this.abstractClass = abstractClass;
      this.subclassName = subclassName.replace('.', '/');
      this.mockConstructorInfo = mockConstructorInfo;
      implementedMethods = new ArrayList();
   }

   @Override
   public void visit(int version, int access, String name, String signature, String superName, String[] interfaces)
   {
      super.visit(version, access & CLASS_ACCESS_MASK, subclassName, signature, name, null);
      superClassName = name;
      superClassOfSuperClass = superName;
      initialSuperInterfaces = 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 ("".equals(name)) {
         if (!defaultConstructorCreated) {
            generateConstructor(desc);
            defaultConstructorCreated = true;
         }
      }
      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 generateConstructor(String candidateSuperConstructor)
   {
      boolean withSuperConstructor = mockConstructorInfo != null && mockConstructorInfo.isWithSuperConstructor();
      String newConstructorDesc = withSuperConstructor ? "([Ljava/lang/Object;)V" : "()V";

      mw = super.visitMethod(ACC_PUBLIC, "", newConstructorDesc, null, null);
      mw.visitVarInsn(ALOAD, 0);

      String superConstructorDesc;

      if (withSuperConstructor) {
         superConstructorDesc = generateSuperConstructorArguments();
      }
      else {
         pushDefaultValuesForParameterTypes(candidateSuperConstructor);
         superConstructorDesc = candidateSuperConstructor;
      }

      mw.visitMethodInsn(INVOKESPECIAL, superClassName, "", superConstructorDesc);
      mw.visitInsn(RETURN);
      mw.visitMaxs(1, 0);
   }

   private String generateSuperConstructorArguments()
   {
      mw.visitVarInsn(ALOAD, 1);

      GeneratorAdapter generator = new GeneratorAdapter(mw, ACC_PUBLIC, "()V");
      Type[] paramTypes = mockConstructorInfo.getParameterTypesForSuperConstructor();
      int paramIndex = 0;

      for (Type paramType : paramTypes) {
         generator.push(paramIndex++);
         generator.arrayLoad(paramType);
         generator.unbox(paramType);
      }

      return Type.getMethodDescriptor(Type.VOID_TYPE, paramTypes);
   }

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

      if (!implementedMethods.contains(methodNameAndDesc)) {
         if (Modifier.isAbstract(access)) {
            generateMethodImplementation(className, access, name, desc, signature, exceptions);
         }

         implementedMethods.add(methodNameAndDesc);
      }
   }

   private void generateMethodImplementation(
      String className, int access, String name, String desc, String signature, String[] exceptions)
   {
      mw = super.visitMethod(ACC_PUBLIC, name, desc, signature, exceptions);

      boolean noFiltersToMatch = mockingConfiguration == null || mockingConfiguration.isEmpty();

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

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

      for (String superInterface : initialSuperInterfaces) {
         generateImplementationsForInterfaceMethods(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 EmptyVisitor
   {
      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;
      }

      @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
   {
      String[] superInterfaces;

      MethodModifierForImplementedInterface(String interfaceName)
      {
         super(interfaceName);
      }

      @Override
      public void visit(int version, int access, String name, String signature, String superName, String[] interfaces)
      {
         superInterfaces = 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 = Utilities.getParameterTypes(desc);

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

      @Override
      public void visitEnd()
      {
         for (String superName : superInterfaces) {
            generateImplementationsForInterfaceMethods(superName);
         }
      }
   }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy