1 /*
   2  * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  *
  23  */
  24 
  25 import static jdk.internal.org.objectweb.asm.Opcodes.*;
  26 
  27 import java.util.ArrayList;
  28 import java.util.Iterator;
  29 
  30 /**
  31  * Constructs classes and interfaces based on the information from a
  32  * DefaultMethodTestCase
  33  *
  34  */
  35 public class ClassBuilder extends Builder {
  36     private final ArrayList<ClassConstruct> classes;
  37 
  38     // Add a class in every package to be able to instantiate package
  39     // private classes from outside the package
  40     private final Clazz[] helpers = new Clazz[4];
  41     private ClassConstruct callsiteClass;
  42 
  43     public enum ExecutionMode { DIRECT, INDY, MH_INVOKE_EXACT, MH_INVOKE_GENERIC}
  44     private final ExecutionMode execMode;
  45 
  46     public ClassBuilder(SelectionResolutionTestCase testcase,
  47                         ExecutionMode execMode) {
  48         super(testcase);
  49         this.classes = new ArrayList<>();
  50         this.execMode = execMode;
  51     }
  52 
  53     public ClassConstruct[] build() throws Exception {
  54         buildClassConstructs();
  55         return classes.toArray(new ClassConstruct[0]);
  56     }
  57 
  58     public ClassConstruct getCallsiteClass() {
  59         return callsiteClass;
  60     }
  61 
  62     private void buildClassConstructs() throws Exception {
  63         TestBuilder tb = new TestBuilder(testcase.methodref, testcase);
  64 
  65         classes.add(new Clazz("Test", ACC_PUBLIC, -1));
  66 
  67         for (int classId = 0; classId < classdata.size(); classId++) {
  68             ClassConstruct C;
  69             String[] interfaces = getInterfaces(classId);
  70             ClassData data = classdata.get(classId);
  71 
  72             if (isClass(classId)) {
  73                 C = new Clazz(getName(classId),
  74                               getExtending(classId),
  75                               getClassModifiers(data),
  76                               classId,
  77                               interfaces);
  78 
  79                 addHelperMethod(classId);
  80 
  81             } else {
  82                 C = new Interface(getName(classId),
  83                                   getAccessibility(data.access),
  84                                   classId, interfaces);
  85             }
  86 
  87             // Add a method "m()LTestObject;" if applicable
  88             if (containsMethod(data)) {
  89                 // Method will either be abstract or concrete depending on the
  90                 // abstract modifier
  91                 C.addTestMethod(getMethodModifiers(data));
  92             }
  93 
  94             if (classId == testcase.callsite) {
  95                 // Add test() method
  96                 tb.addTest(C, execMode);
  97                 callsiteClass = C;
  98             }
  99 
 100             classes.add(C);
 101         }
 102         classes.add(tb.getMainTestClass());
 103 
 104     }
 105 
 106     private void addHelperMethod(int classId) {
 107         int packageId = classdata.get(classId).packageId.ordinal();
 108         Clazz C = helpers[packageId];
 109         if (C == null) {
 110             C = new Clazz(getPackageName(packageId) + "Helper", -1, ACC_PUBLIC);
 111             helpers[packageId] = C;
 112             classes.add(C);
 113         }
 114 
 115         Method m = C.addMethod("get" + getClassName(classId),
 116                                "()L" + getName(classId) + ";",
 117                                ACC_PUBLIC + ACC_STATIC);
 118         m.makeInstantiateMethod(getName(classId));
 119     }
 120 
 121     private String[] getInterfaces(int classId) {
 122         ArrayList<String> interfaces = new ArrayList<>();
 123 
 124         // Figure out if we're extending/implementing an interface
 125         for (final int intf : hier.interfaces()) {
 126             if (hier.inherits(classId, intf)) {
 127                 interfaces.add(getName(intf));
 128             }
 129         }
 130         return interfaces.toArray(new String[0]);
 131     }
 132 
 133     private String getExtending(int classId) {
 134         int extending = -1;
 135 
 136         // See if we're extending another class
 137         for (final int extendsClass : hier.classes()) {
 138             if (hier.inherits(classId, extendsClass)) {
 139                 // Sanity check that we haven't already found an extending class
 140                 if (extending != -1) {
 141                     throw new RuntimeException("Multiple extending classes");
 142                 }
 143                 extending = extendsClass;
 144             }
 145         }
 146 
 147         return extending == -1 ? null : getName(extending);
 148     }
 149 
 150     /**
 151      * Returns modifiers for a Class
 152      * @param cd ClassData for the Class
 153      * @return ASM modifiers for a Class
 154      */
 155     private int getClassModifiers(ClassData cd) {
 156         // For Classes we only care about accessibility (public, private etc)
 157         return getAccessibility(cd.access) | getAbstraction(cd.abstraction);
 158     }
 159 
 160     /**
 161      * Returns modifiers for Method type
 162      * @param cd ClassData for the Class or Interface where the Method resides
 163      * @return ASM modifiers for the Method
 164      */
 165     private int getMethodModifiers(ClassData cd) {
 166         int mod = 0;
 167 
 168         // For methods we want everything
 169         mod += getAccessibility(cd.methoddata.access);
 170         mod += getAbstraction(cd.methoddata.context);
 171         mod += getContext(cd.methoddata.context);
 172         mod += getExtensibility();
 173         return mod;
 174     }
 175 
 176 
 177     /**
 178      * Convert ClassData access type to ASM
 179      * @param access
 180      * @return ASM version of accessibility (public / private / protected)
 181      */
 182     private int getAccessibility(MethodData.Access access) {
 183         switch(access) {
 184         case PACKAGE:
 185             //TODO: Do I need to set this or will this be the default?
 186             return 0;
 187         case PRIVATE:
 188             return ACC_PRIVATE;
 189         case PROTECTED:
 190             return ACC_PROTECTED;
 191         case PUBLIC:
 192             return ACC_PUBLIC;
 193         default:
 194             throw new RuntimeException("Illegal accessibility modifier: " + access);
 195         }
 196     }
 197 
 198     /**
 199      * Convert ClassData abstraction type to ASM
 200      * @param abstraction
 201      * @return ASM version of abstraction (abstract / non-abstract)
 202      */
 203     private int getAbstraction(MethodData.Context context) {
 204         return context == MethodData.Context.ABSTRACT ? ACC_ABSTRACT : 0;
 205     }
 206 
 207     /**
 208      * Convert ClassData context type to ASM
 209      * @param context
 210      * @return ASM version of context (static / non-static)
 211      */
 212     private int getContext(MethodData.Context context) {
 213         return context == MethodData.Context.STATIC ? ACC_STATIC : 0;
 214     }
 215 
 216     /**
 217      * Convert ClassData extensibility type to ASM
 218      * @param extensibility
 219      * @return ASM version of extensibility (final / non-final)
 220      */
 221     private int getExtensibility() {
 222         return 0;
 223     }
 224 
 225     /**
 226      * Determine if we need a method at all, abstraction is set to null if this
 227      * Class/Interface should not have a test method
 228      * @param cd
 229      * @return
 230      */
 231     private boolean containsMethod(ClassData cd) {
 232         return cd.methoddata != null;
 233     }
 234 
 235 }