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 
16 package javassist;
17 
18 import javassist.bytecode.*;
19 import javassist.compiler.Javac;
20 import 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  */
35 public final class CtConstructor extends CtBehavior {
CtConstructor(MethodInfo minfo, CtClass declaring)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      */
CtConstructor(CtClass[] parameters, CtClass declaring)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      */
CtConstructor(CtConstructor src, CtClass declaring, ClassMap map)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      */
isConstructor()107     public boolean isConstructor() {
108         return methodInfo.isConstructor();
109     }
110 
111     /**
112      * Returns true if this object represents a static initializer.
113      */
isClassInitializer()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      */
getLongName()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      */
getName()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      */
isEmpty()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 
getSuperclassName()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      */
callsSuper()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      */
setBody(String src)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      */
setBody(CtConstructor src, ClassMap map)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      */
insertBeforeBody(String src)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      */
getStartPosOfBody(CodeAttribute ca)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      */
toMethod(String name, CtClass declaring)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      */
toMethod(String name, CtClass declaring, ClassMap map)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 
removeConsCall(CodeAttribute ca)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