1/*
2 * Copyright (c) 2016, 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
24package selectionresolution;
25
26import java.util.ArrayList;
27import java.util.Iterator;
28
29import static jdk.internal.org.objectweb.asm.Opcodes.ACC_ABSTRACT;
30import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PUBLIC;
31import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PRIVATE;
32import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PROTECTED;
33import static jdk.internal.org.objectweb.asm.Opcodes.ACC_STATIC;
34
35/**
36 * Constructs classes and interfaces based on the information from a
37 * DefaultMethodTestCase
38 *
39 */
40public class ClassBuilder extends Builder {
41    private final ArrayList<ClassConstruct> classes;
42
43    // Add a class in every package to be able to instantiate package
44    // private classes from outside the package
45    private final Clazz[] helpers = new Clazz[4];
46    private ClassConstruct callsiteClass;
47
48    public enum ExecutionMode { DIRECT, INDY, MH_INVOKE_EXACT, MH_INVOKE_GENERIC}
49    private final ExecutionMode execMode;
50
51    public ClassBuilder(SelectionResolutionTestCase testcase,
52                        ExecutionMode execMode) {
53        super(testcase);
54        this.classes = new ArrayList<>();
55        this.execMode = execMode;
56    }
57
58    public ClassConstruct[] build() throws Exception {
59        buildClassConstructs();
60        return classes.toArray(new ClassConstruct[0]);
61    }
62
63    public ClassConstruct getCallsiteClass() {
64        return callsiteClass;
65    }
66
67    private void buildClassConstructs() throws Exception {
68        TestBuilder tb = new TestBuilder(testcase.methodref, testcase);
69
70        classes.add(new Clazz("Test", ACC_PUBLIC, -1));
71
72        for (int classId = 0; classId < classdata.size(); classId++) {
73            ClassConstruct C;
74            String[] interfaces = getInterfaces(classId);
75            ClassData data = classdata.get(classId);
76
77            if (isClass(classId)) {
78                C = new Clazz(getName(classId),
79                              getExtending(classId),
80                              getClassModifiers(data),
81                              classId,
82                              interfaces);
83
84                addHelperMethod(classId);
85
86            } else {
87                C = new Interface(getName(classId),
88                                  getAccessibility(data.access),
89                                  classId, interfaces);
90            }
91
92            // Add a method "m()LTestObject;" if applicable
93            if (containsMethod(data)) {
94                // Method will either be abstract or concrete depending on the
95                // abstract modifier
96                C.addTestMethod(getMethodModifiers(data));
97            }
98
99            if (classId == testcase.callsite) {
100                // Add test() method
101                tb.addTest(C, execMode);
102                callsiteClass = C;
103            }
104
105            classes.add(C);
106        }
107        classes.add(tb.getMainTestClass());
108
109    }
110
111    private void addHelperMethod(int classId) {
112        int packageId = classdata.get(classId).packageId.ordinal();
113        Clazz C = helpers[packageId];
114        if (C == null) {
115            C = new Clazz(getPackageName(packageId) + "Helper", -1, ACC_PUBLIC);
116            helpers[packageId] = C;
117            classes.add(C);
118        }
119
120        Method m = C.addMethod("get" + getClassName(classId),
121                               "()L" + getName(classId) + ";",
122                               ACC_PUBLIC + ACC_STATIC);
123        m.makeInstantiateMethod(getName(classId));
124    }
125
126    private String[] getInterfaces(int classId) {
127        ArrayList<String> interfaces = new ArrayList<>();
128
129        // Figure out if we're extending/implementing an interface
130        for (final int intf : hier.interfaces()) {
131            if (hier.inherits(classId, intf)) {
132                interfaces.add(getName(intf));
133            }
134        }
135        return interfaces.toArray(new String[0]);
136    }
137
138    private String getExtending(int classId) {
139        int extending = -1;
140
141        // See if we're extending another class
142        for (final int extendsClass : hier.classes()) {
143            if (hier.inherits(classId, extendsClass)) {
144                // Sanity check that we haven't already found an extending class
145                if (extending != -1) {
146                    throw new RuntimeException("Multiple extending classes");
147                }
148                extending = extendsClass;
149            }
150        }
151
152        return extending == -1 ? null : getName(extending);
153    }
154
155    /**
156     * Returns modifiers for a Class
157     * @param cd ClassData for the Class
158     * @return ASM modifiers for a Class
159     */
160    private int getClassModifiers(ClassData cd) {
161        // For Classes we only care about accessibility (public, private etc)
162        return getAccessibility(cd.access) | getAbstraction(cd.abstraction);
163    }
164
165    /**
166     * Returns modifiers for Method type
167     * @param cd ClassData for the Class or Interface where the Method resides
168     * @return ASM modifiers for the Method
169     */
170    private int getMethodModifiers(ClassData cd) {
171        int mod = 0;
172
173        // For methods we want everything
174        mod += getAccessibility(cd.methoddata.access);
175        mod += getAbstraction(cd.methoddata.context);
176        mod += getContext(cd.methoddata.context);
177        mod += getExtensibility();
178        return mod;
179    }
180
181
182    /**
183     * Convert ClassData access type to ASM
184     * @param access
185     * @return ASM version of accessibility (public / private / protected)
186     */
187    private int getAccessibility(MethodData.Access access) {
188        switch(access) {
189        case PACKAGE:
190            //TODO: Do I need to set this or will this be the default?
191            return 0;
192        case PRIVATE:
193            return ACC_PRIVATE;
194        case PROTECTED:
195            return ACC_PROTECTED;
196        case PUBLIC:
197            return ACC_PUBLIC;
198        default:
199            throw new RuntimeException("Illegal accessibility modifier: " + access);
200        }
201    }
202
203    /**
204     * Convert ClassData abstraction type to ASM
205     * @param abstraction
206     * @return ASM version of abstraction (abstract / non-abstract)
207     */
208    private int getAbstraction(MethodData.Context context) {
209        return context == MethodData.Context.ABSTRACT ? ACC_ABSTRACT : 0;
210    }
211
212    /**
213     * Convert ClassData context type to ASM
214     * @param context
215     * @return ASM version of context (static / non-static)
216     */
217    private int getContext(MethodData.Context context) {
218        return context == MethodData.Context.STATIC ? ACC_STATIC : 0;
219    }
220
221    /**
222     * Convert ClassData extensibility type to ASM
223     * @param extensibility
224     * @return ASM version of extensibility (final / non-final)
225     */
226    private int getExtensibility() {
227        return 0;
228    }
229
230    /**
231     * Determine if we need a method at all, abstraction is set to null if this
232     * Class/Interface should not have a test method
233     * @param cd
234     * @return
235     */
236    private boolean containsMethod(ClassData cd) {
237        return cd.methoddata != null;
238    }
239
240}
241