1/*
2 * Javassist, a Java-bytecode translator toolkit.
3 * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
4 *
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License.  Alternatively, the contents of this file may be used under
8 * the terms of the GNU Lesser General Public License Version 2.1 or later.
9 *
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
14 */
15
16package javassist;
17
18import javassist.bytecode.*;
19import javassist.compiler.Javac;
20import javassist.compiler.CompileError;
21
22/**
23 * An instance of CtConstructor represents a constructor.
24 * It may represent a static constructor
25 * (class initializer).  To distinguish a constructor and a class
26 * initializer, call <code>isClassInitializer()</code>.
27 *
28 * <p>See the super class <code>CtBehavior</code> as well since
29 * a number of useful methods are in <code>CtBehavior</code>.
30 *
31 * @see CtClass#getDeclaredConstructors()
32 * @see CtClass#getClassInitializer()
33 * @see CtNewConstructor
34 */
35public final class CtConstructor extends CtBehavior {
36    protected CtConstructor(MethodInfo minfo, CtClass declaring) {
37        super(declaring, minfo);
38    }
39
40    /**
41     * Creates a constructor with no constructor body.
42     * The created constructor
43     * must be added to a class with <code>CtClass.addConstructor()</code>.
44     *
45     * <p>The created constructor does not include a constructor body,
46     * which must be specified with <code>setBody()</code>.
47     *
48     * @param declaring         the class to which the created method is added.
49     * @param parameters        a list of the parameter types
50     *
51     * @see CtClass#addConstructor(CtConstructor)
52     * @see CtConstructor#setBody(String)
53     * @see CtConstructor#setBody(CtConstructor,ClassMap)
54     */
55    public CtConstructor(CtClass[] parameters, CtClass declaring) {
56        this((MethodInfo)null, declaring);
57        ConstPool cp = declaring.getClassFile2().getConstPool();
58        String desc = Descriptor.ofConstructor(parameters);
59        methodInfo = new MethodInfo(cp, "<init>", desc);
60        setModifiers(Modifier.PUBLIC);
61    }
62
63    /**
64     * Creates a copy of a <code>CtConstructor</code> object.
65     * The created constructor must be
66     * added to a class with <code>CtClass.addConstructor()</code>.
67     *
68     * <p>All occurrences of class names in the created constructor
69     * are replaced with names specified by
70     * <code>map</code> if <code>map</code> is not <code>null</code>.
71     *
72     * <p>By default, all the occurrences of the names of the class
73     * declaring <code>src</code> and the superclass are replaced
74     * with the name of the class and the superclass that
75     * the created constructor is added to.
76     * This is done whichever <code>map</code> is null or not.
77     * To prevent this replacement, call <code>ClassMap.fix()</code>
78     * or <code>put()</code> to explicitly specify replacement.
79     *
80     * <p><b>Note:</b> if the <code>.class</code> notation (for example,
81     * <code>String.class</code>) is included in an expression, the
82     * Javac compiler may produce a helper method.
83     * Since this constructor never
84     * copies this helper method, the programmers have the responsiblity of
85     * copying it.  Otherwise, use <code>Class.forName()</code> in the
86     * expression.
87     *
88     * @param src       the source method.
89     * @param declaring    the class to which the created method is added.
90     * @param map       the hashtable associating original class names
91     *                  with substituted names.
92     *                  It can be <code>null</code>.
93     *
94     * @see CtClass#addConstructor(CtConstructor)
95     * @see ClassMap#fix(String)
96     */
97    public CtConstructor(CtConstructor src, CtClass declaring, ClassMap map)
98        throws CannotCompileException
99    {
100        this((MethodInfo)null, declaring);
101        copy(src, true, map);
102    }
103
104    /**
105     * Returns true if this object represents a constructor.
106     */
107    public boolean isConstructor() {
108        return methodInfo.isConstructor();
109    }
110
111    /**
112     * Returns true if this object represents a static initializer.
113     */
114    public boolean isClassInitializer() {
115        return methodInfo.isStaticInitializer();
116    }
117
118    /**
119     * Returns the constructor name followed by parameter types
120     * such as <code>javassist.CtConstructor(CtClass[],CtClass)</code>.
121     *
122     * @since 3.5
123     */
124    public String getLongName() {
125        return getDeclaringClass().getName()
126               + (isConstructor() ? Descriptor.toString(getSignature())
127                                  : ("." + MethodInfo.nameClinit + "()"));
128    }
129
130    /**
131     * Obtains the name of this constructor.
132     * It is the same as the simple name of the class declaring this
133     * constructor.  If this object represents a class initializer,
134     * then this method returns <code>"&lt;clinit&gt;"</code>.
135     */
136    public String getName() {
137        if (methodInfo.isStaticInitializer())
138            return MethodInfo.nameClinit;
139        else
140            return declaringClass.getSimpleName();
141    }
142
143    /**
144     * Returns true if the constructor (or static initializer)
145     * is the default one.  This method returns true if the constructor
146     * takes some arguments but it does not perform anything except
147     * calling <code>super()</code> (the no-argument constructor of
148     * the super class).
149     */
150    public boolean isEmpty() {
151        CodeAttribute ca = getMethodInfo2().getCodeAttribute();
152        if (ca == null)
153            return false;       // native or abstract??
154                                // they are not allowed, though.
155
156        ConstPool cp = ca.getConstPool();
157        CodeIterator it = ca.iterator();
158        try {
159            int pos, desc;
160            int op0 = it.byteAt(it.next());
161            return op0 == Opcode.RETURN     // empty static initializer
162                || (op0 == Opcode.ALOAD_0
163                    && it.byteAt(pos = it.next()) == Opcode.INVOKESPECIAL
164                    && (desc = cp.isConstructor(getSuperclassName(),
165                                                it.u16bitAt(pos + 1))) != 0
166                    && "()V".equals(cp.getUtf8Info(desc))
167                    && it.byteAt(it.next()) == Opcode.RETURN
168                    && !it.hasNext());
169        }
170        catch (BadBytecode e) {}
171        return false;
172    }
173
174    private String getSuperclassName() {
175        ClassFile cf = declaringClass.getClassFile2();
176        return cf.getSuperclass();
177    }
178
179    /**
180     * Returns true if this constructor calls a constructor
181     * of the super class.  This method returns false if it
182     * calls another constructor of this class by <code>this()</code>.
183     */
184    public boolean callsSuper() throws CannotCompileException {
185        CodeAttribute codeAttr = methodInfo.getCodeAttribute();
186        if (codeAttr != null) {
187            CodeIterator it = codeAttr.iterator();
188            try {
189                int index = it.skipSuperConstructor();
190                return index >= 0;
191            }
192            catch (BadBytecode e) {
193                throw new CannotCompileException(e);
194            }
195        }
196
197        return false;
198    }
199
200    /**
201     * Sets a constructor body.
202     *
203     * @param src       the source code representing the constructor body.
204     *                  It must be a single statement or block.
205     *                  If it is <code>null</code>, the substituted
206     *                  constructor body does nothing except calling
207     *                  <code>super()</code>.
208     */
209    public void setBody(String src) throws CannotCompileException {
210        if (src == null)
211            if (isClassInitializer())
212                src = ";";
213            else
214                src = "super();";
215
216        super.setBody(src);
217    }
218
219    /**
220     * Copies a constructor body from another constructor.
221     *
222     * <p>All occurrences of the class names in the copied body
223     * are replaced with the names specified by
224     * <code>map</code> if <code>map</code> is not <code>null</code>.
225     *
226     * @param src       the method that the body is copied from.
227     * @param map       the hashtable associating original class names
228     *                  with substituted names.
229     *                  It can be <code>null</code>.
230     */
231    public void setBody(CtConstructor src, ClassMap map)
232        throws CannotCompileException
233    {
234        setBody0(src.declaringClass, src.methodInfo,
235                 declaringClass, methodInfo, map);
236    }
237
238    /**
239     * Inserts bytecode just after another constructor in the super class
240     * or this class is called.
241     * It does not work if this object represents a class initializer.
242     *
243     * @param src       the source code representing the inserted bytecode.
244     *                  It must be a single statement or block.
245     */
246    public void insertBeforeBody(String src) throws CannotCompileException {
247        CtClass cc = declaringClass;
248        cc.checkModify();
249        if (isClassInitializer())
250            throw new CannotCompileException("class initializer");
251
252        CodeAttribute ca = methodInfo.getCodeAttribute();
253        CodeIterator iterator = ca.iterator();
254        Bytecode b = new Bytecode(methodInfo.getConstPool(),
255                                  ca.getMaxStack(), ca.getMaxLocals());
256        b.setStackDepth(ca.getMaxStack());
257        Javac jv = new Javac(b, cc);
258        try {
259            jv.recordParams(getParameterTypes(), false);
260            jv.compileStmnt(src);
261            ca.setMaxStack(b.getMaxStack());
262            ca.setMaxLocals(b.getMaxLocals());
263            iterator.skipConstructor();
264            int pos = iterator.insertEx(b.get());
265            iterator.insert(b.getExceptionTable(), pos);
266            methodInfo.rebuildStackMapIf6(cc.getClassPool(), cc.getClassFile2());
267        }
268        catch (NotFoundException e) {
269            throw new CannotCompileException(e);
270        }
271        catch (CompileError e) {
272            throw new CannotCompileException(e);
273        }
274        catch (BadBytecode e) {
275            throw new CannotCompileException(e);
276        }
277    }
278
279    /* This method is called by addCatch() in CtBehavior.
280     * super() and this() must not be in a try statement.
281     */
282    int getStartPosOfBody(CodeAttribute ca) throws CannotCompileException {
283        CodeIterator ci = ca.iterator();
284        try {
285            ci.skipConstructor();
286            return ci.next();
287        }
288        catch (BadBytecode e) {
289            throw new CannotCompileException(e);
290        }
291    }
292
293    /**
294     * Makes a copy of this constructor and converts it into a method.
295     * The signature of the mehtod is the same as the that of this constructor.
296     * The return type is <code>void</code>.  The resulting method must be
297     * appended to the class specified by <code>declaring</code>.
298     * If this constructor is a static initializer, the resulting method takes
299     * no parameter.
300     *
301     * <p>An occurrence of another constructor call <code>this()</code>
302     * or a super constructor call <code>super()</code> is
303     * eliminated from the resulting method.
304     *
305     * <p>The immediate super class of the class declaring this constructor
306     * must be also a super class of the class declaring the resulting method.
307     * If the constructor accesses a field, the class declaring the resulting method
308     * must also declare a field with the same name and type.
309     *
310     * @param name              the name of the resulting method.
311     * @param declaring         the class declaring the resulting method.
312     */
313    public CtMethod toMethod(String name, CtClass declaring)
314        throws CannotCompileException
315    {
316        return toMethod(name, declaring, null);
317    }
318
319    /**
320     * Makes a copy of this constructor and converts it into a method.
321     * The signature of the method is the same as the that of this constructor.
322     * The return type is <code>void</code>.  The resulting method must be
323     * appended to the class specified by <code>declaring</code>.
324     * If this constructor is a static initializer, the resulting method takes
325     * no parameter.
326     *
327     * <p>An occurrence of another constructor call <code>this()</code>
328     * or a super constructor call <code>super()</code> is
329     * eliminated from the resulting method.
330     *
331     * <p>The immediate super class of the class declaring this constructor
332     * must be also a super class of the class declaring the resulting method
333     * (this is obviously true if the second parameter <code>declaring</code> is
334     * the same as the class declaring this constructor).
335     * If the constructor accesses a field, the class declaring the resulting method
336     * must also declare a field with the same name and type.
337     *
338     * @param name              the name of the resulting method.
339     * @param declaring         the class declaring the resulting method.
340     *                          It is normally the same as the class declaring this
341     *                          constructor.
342     * @param map       the hash table associating original class names
343     *                  with substituted names.  The original class names will be
344     *                  replaced while making a copy.
345     *                  <code>map</code> can be <code>null</code>.
346     */
347    public CtMethod toMethod(String name, CtClass declaring, ClassMap map)
348        throws CannotCompileException
349    {
350        CtMethod method = new CtMethod(null, declaring);
351        method.copy(this, false, map);
352        if (isConstructor()) {
353            MethodInfo minfo = method.getMethodInfo2();
354            CodeAttribute ca = minfo.getCodeAttribute();
355            if (ca != null) {
356                removeConsCall(ca);
357                try {
358                    methodInfo.rebuildStackMapIf6(declaring.getClassPool(),
359                                                  declaring.getClassFile2());
360                }
361                catch (BadBytecode e) {
362                    throw new CannotCompileException(e);
363                }
364            }
365        }
366
367        method.setName(name);
368        return method;
369    }
370
371    private static void removeConsCall(CodeAttribute ca)
372        throws CannotCompileException
373    {
374        CodeIterator iterator = ca.iterator();
375        try {
376            int pos = iterator.skipConstructor();
377            if (pos >= 0) {
378                int mref = iterator.u16bitAt(pos + 1);
379                String desc = ca.getConstPool().getMethodrefType(mref);
380                int num = Descriptor.numOfParameters(desc) + 1;
381                if (num > 3)
382                    pos = iterator.insertGapAt(pos, num - 3, false).position;
383
384                iterator.writeByte(Opcode.POP, pos++);  // this
385                iterator.writeByte(Opcode.NOP, pos);
386                iterator.writeByte(Opcode.NOP, pos + 1);
387                Descriptor.Iterator it = new Descriptor.Iterator(desc);
388                while (true) {
389                    it.next();
390                    if (it.isParameter())
391                        iterator.writeByte(it.is2byte() ? Opcode.POP2 : Opcode.POP,
392                                           pos++);
393                    else
394                        break;
395                }
396            }
397        }
398        catch (BadBytecode e) {
399            throw new CannotCompileException(e);
400        }
401    }
402}
403