View Javadoc
1   /*
2    * Copyright (c) 2006 JMockit developers
3    * This file is subject to the terms of the MIT license (see LICENSE.txt).
4    */
5   package mockit.internal.classGeneration;
6   
7   import static java.util.Arrays.asList;
8   
9   import static mockit.asm.jvmConstants.Opcodes.ALOAD;
10  import static mockit.asm.jvmConstants.Opcodes.INVOKESPECIAL;
11  
12  import edu.umd.cs.findbugs.annotations.NonNull;
13  import edu.umd.cs.findbugs.annotations.Nullable;
14  
15  import java.lang.reflect.Method;
16  import java.lang.reflect.Type;
17  import java.util.ArrayList;
18  import java.util.HashSet;
19  import java.util.List;
20  import java.util.Set;
21  
22  import mockit.asm.classes.ClassInfo;
23  import mockit.asm.classes.ClassReader;
24  import mockit.asm.fields.FieldVisitor;
25  import mockit.asm.jvmConstants.Access;
26  import mockit.asm.metadata.ClassMetadataReader;
27  import mockit.asm.metadata.ClassMetadataReader.MethodInfo;
28  import mockit.asm.methods.MethodVisitor;
29  import mockit.asm.types.JavaType;
30  import mockit.internal.BaseClassModifier;
31  import mockit.internal.ClassFile;
32  import mockit.internal.util.TypeDescriptor;
33  
34  public class BaseSubclassGenerator extends BaseClassModifier {
35      private static final int CLASS_ACCESS_MASK = 0xFFFF - Access.ABSTRACT;
36      private static final int CONSTRUCTOR_ACCESS_MASK = Access.PUBLIC + Access.PROTECTED;
37  
38      // Fixed initial state:
39      @NonNull
40      Class<?> baseClass;
41      @NonNull
42      private final String subclassName;
43      @Nullable
44      protected final MockedTypeInfo mockedTypeInfo;
45      private final boolean copyConstructors;
46  
47      // Helper fields for mutable state:
48      @NonNull
49      private final List<String> implementedMethods;
50      @Nullable
51      private String superClassOfSuperClass;
52      private Set<String> superInterfaces;
53  
54      protected BaseSubclassGenerator(@NonNull Class<?> baseClass, @NonNull ClassReader cr,
55              @Nullable Type genericMockedType, @NonNull String subclassName, boolean copyConstructors) {
56          super(cr);
57          this.baseClass = baseClass;
58          this.subclassName = subclassName.replace('.', '/');
59          mockedTypeInfo = genericMockedType == null ? null : new MockedTypeInfo(genericMockedType);
60          this.copyConstructors = copyConstructors;
61          implementedMethods = new ArrayList<>();
62      }
63  
64      @Override
65      public void visit(int version, int access, @NonNull String name, @NonNull ClassInfo additionalInfo) {
66          ClassInfo subClassInfo = new ClassInfo();
67          subClassInfo.superName = name;
68          subClassInfo.signature = mockedTypeInfo == null ? additionalInfo.signature
69                  : mockedTypeInfo.implementationSignature;
70          int subclassAccess = access & CLASS_ACCESS_MASK | Access.FINAL;
71  
72          super.visit(version, subclassAccess, subclassName, subClassInfo);
73  
74          superClassOfSuperClass = additionalInfo.superName;
75          superInterfaces = new HashSet<>();
76  
77          String[] interfaces = additionalInfo.interfaces;
78  
79          if (interfaces.length > 0) {
80              superInterfaces.addAll(asList(interfaces));
81          }
82      }
83  
84      @Override
85      public final void visitInnerClass(@NonNull String name, @Nullable String outerName, @Nullable String innerName,
86              int access) {
87      }
88  
89      @Override
90      @Nullable
91      public final FieldVisitor visitField(int access, @NonNull String name, @NonNull String desc,
92              @Nullable String signature, @Nullable Object value) {
93          return null;
94      }
95  
96      @Override
97      @Nullable
98      public MethodVisitor visitMethod(int access, @NonNull String name, @NonNull String desc, @Nullable String signature,
99              @Nullable String[] exceptions) {
100         if (copyConstructors && "<init>".equals(name)) {
101             if ((access & CONSTRUCTOR_ACCESS_MASK) != 0) {
102                 generateConstructorDelegatingToSuper(desc, signature, exceptions);
103             }
104         } else {
105             // Inherits from super-class when non-abstract; otherwise, creates implementation for abstract method.
106             generateImplementationIfAbstractMethod(superClassName, access, name, desc, signature, exceptions);
107         }
108 
109         return null;
110     }
111 
112     private void generateConstructorDelegatingToSuper(@NonNull String desc, @Nullable String signature,
113             @Nullable String[] exceptions) {
114         mw = cw.visitMethod(Access.PUBLIC, "<init>", desc, signature, exceptions);
115         mw.visitVarInsn(ALOAD, 0);
116         int varIndex = 1;
117 
118         for (JavaType paramType : JavaType.getArgumentTypes(desc)) {
119             int loadOpcode = paramType.getLoadOpcode();
120             mw.visitVarInsn(loadOpcode, varIndex);
121             varIndex++;
122         }
123 
124         mw.visitMethodInsn(INVOKESPECIAL, superClassName, "<init>", desc, false);
125         generateEmptyImplementation();
126     }
127 
128     private void generateImplementationIfAbstractMethod(String className, int access, @NonNull String name,
129             @NonNull String desc, @Nullable String signature, @Nullable String[] exceptions) {
130         if (!"<init>".equals(name)) {
131             String methodNameAndDesc = name + desc;
132 
133             if (!implementedMethods.contains(methodNameAndDesc)) {
134                 if ((access & Access.ABSTRACT) != 0) {
135                     generateMethodImplementation(className, access, name, desc, signature, exceptions);
136                 }
137 
138                 implementedMethods.add(methodNameAndDesc);
139             }
140         }
141     }
142 
143     protected void generateMethodImplementation(String className, int access, @NonNull String name,
144             @NonNull String desc, @Nullable String signature, @Nullable String[] exceptions) {
145     }
146 
147     @Override
148     public void visitEnd() {
149         generateImplementationsForInheritedAbstractMethods(superClassOfSuperClass);
150 
151         while (!superInterfaces.isEmpty()) {
152             String superInterface = superInterfaces.iterator().next();
153             generateImplementationsForAbstractMethods(superInterface, false);
154             superInterfaces.remove(superInterface);
155         }
156     }
157 
158     private void generateImplementationsForInheritedAbstractMethods(@Nullable String superName) {
159         if (superName != null) {
160             generateImplementationsForAbstractMethods(superName, true);
161         }
162     }
163 
164     private void generateImplementationsForAbstractMethods(@NonNull String typeName, boolean abstractClass) {
165         if (!"java/lang/Object".equals(typeName)) {
166             byte[] typeBytecode = ClassFile.getClassFile(typeName);
167             ClassMetadataReader cmr = new ClassMetadataReader(typeBytecode);
168             String[] interfaces = cmr.getInterfaces();
169 
170             if (interfaces != null) {
171                 superInterfaces.addAll(asList(interfaces));
172             }
173 
174             for (MethodInfo method : cmr.getMethods()) {
175                 if (abstractClass) {
176                     generateImplementationIfAbstractMethod(typeName, method.accessFlags, method.name, method.desc, null,
177                             null);
178                 } else if (method.isAbstract()) {
179                     generateImplementationForInterfaceMethodIfMissing(typeName, method);
180                 }
181             }
182 
183             if (abstractClass) {
184                 String superClass = cmr.getSuperClass();
185                 generateImplementationsForInheritedAbstractMethods(superClass);
186             }
187         }
188     }
189 
190     private void generateImplementationForInterfaceMethodIfMissing(@NonNull String typeName,
191             @NonNull MethodInfo method) {
192         String name = method.name;
193         String desc = method.desc;
194         String methodNameAndDesc = name + desc;
195 
196         if (!implementedMethods.contains(methodNameAndDesc)) {
197             if (!hasMethodImplementation(name, desc)) {
198                 generateMethodImplementation(typeName, method.accessFlags, name, desc, null, null);
199             }
200 
201             implementedMethods.add(methodNameAndDesc);
202         }
203     }
204 
205     private boolean hasMethodImplementation(@NonNull String name, @NonNull String desc) {
206         Class<?>[] paramTypes = TypeDescriptor.getParameterTypes(desc);
207 
208         try {
209             Method method = baseClass.getMethod(name, paramTypes);
210             return !method.getDeclaringClass().isInterface();
211         } catch (NoSuchMethodException ignore) {
212             return false;
213         }
214     }
215 }