Add javassist to external
We are going to add android-mock to external, which uses javassist.
Change-Id: Ibc17ea987c3a8cebb0a30e56d3574c3bacd589b2
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..b5d12be
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,30 @@
+#
+# Copyright (C) 2011 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+# We only want this apk build for tests.
+LOCAL_MODULE_TAGS := tests
+
+# We need classes from the tools dir in the JDK
+LOCAL_CLASSPATH := $(HOST_JDK_TOOLS_JAR)
+
+LOCAL_MODULE := javassistlib
+
+LOCAL_SRC_FILES := $(call all-java-files-under,src/main)
+
+include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/HOWTO.txt b/HOWTO.txt
new file mode 100644
index 0000000..91f9992
--- /dev/null
+++ b/HOWTO.txt
@@ -0,0 +1,4 @@
+In order to update the code, simply export it from svn with the regenerate_from_source.sh.
+If you want to run the tests, remember to do it from master or a branch that
+includes junit.jar and that has been compiled. The command is:
+ant -propertyfile build.properties test
diff --git a/License.html b/License.html
new file mode 100644
index 0000000..bec1335
--- /dev/null
+++ b/License.html
@@ -0,0 +1,372 @@
+<HTML>
+<HEAD>
+<TITLE>Javassist License</TITLE>
+<META http-equiv=Content-Type content="text/html; charset=iso-8859-1">
+<META content="MSHTML 5.50.4916.2300" name=GENERATOR></HEAD>
+
+<BODY text=#000000 vLink=#551a8b aLink=#ff0000 link=#0000ee bgColor=#ffffff>
+<CENTER><B><FONT size=+2>MOZILLA PUBLIC LICENSE</FONT></B> <BR><B>Version
+1.1</B>
+<P>
+<HR width="20%">
+</CENTER>
+<P><B>1. Definitions.</B>
+<UL><B>1.0.1. "Commercial Use" </B>means distribution or otherwise making the
+ Covered Code available to a third party.
+ <P><B>1.1. ''Contributor''</B> means each entity that creates or contributes
+ to the creation of Modifications.
+ <P><B>1.2. ''Contributor Version''</B> means the combination of the Original
+ Code, prior Modifications used by a Contributor, and the Modifications made by
+ that particular Contributor.
+ <P><B>1.3. ''Covered Code''</B> means the Original Code or Modifications or
+ the combination of the Original Code and Modifications, in each case including
+ portions thereof<B>.</B>
+ <P><B>1.4. ''Electronic Distribution Mechanism''</B> means a mechanism
+ generally accepted in the software development community for the electronic
+ transfer of data.
+ <P><B>1.5. ''Executable''</B> means Covered Code in any form other than Source
+ Code.
+ <P><B>1.6. ''Initial Developer''</B> means the individual or entity identified
+ as the Initial Developer in the Source Code notice required by <B>Exhibit
+ A</B>.
+ <P><B>1.7. ''Larger Work''</B> means a work which combines Covered Code or
+ portions thereof with code not governed by the terms of this License.
+ <P><B>1.8. ''License''</B> means this document.
+ <P><B>1.8.1. "Licensable"</B> means having the right to grant, to the maximum
+ extent possible, whether at the time of the initial grant or subsequently
+ acquired, any and all of the rights conveyed herein.
+ <P><B>1.9. ''Modifications''</B> means any addition to or deletion from the
+ substance or structure of either the Original Code or any previous
+ Modifications. When Covered Code is released as a series of files, a
+ Modification is:
+ <UL><B>A.</B> Any addition to or deletion from the contents of a file
+ containing Original Code or previous Modifications.
+ <P><B>B.</B> Any new file that contains any part of the Original Code or
+ previous Modifications. <BR> </P></UL><B>1.10. ''Original Code''</B>
+ means Source Code of computer software code which is described in the Source
+ Code notice required by <B>Exhibit A</B> as Original Code, and which, at the
+ time of its release under this License is not already Covered Code governed by
+ this License.
+ <P><B>1.10.1. "Patent Claims"</B> means any patent claim(s), now owned or
+ hereafter acquired, including without limitation, method, process, and
+ apparatus claims, in any patent Licensable by grantor.
+ <P><B>1.11. ''Source Code''</B> means the preferred form of the Covered Code
+ for making modifications to it, including all modules it contains, plus any
+ associated interface definition files, scripts used to control compilation and
+ installation of an Executable, or source code differential comparisons against
+ either the Original Code or another well known, available Covered Code of the
+ Contributor's choice. The Source Code can be in a compressed or archival form,
+ provided the appropriate decompression or de-archiving software is widely
+ available for no charge.
+ <P><B>1.12. "You'' (or "Your") </B> means an individual or a legal entity
+ exercising rights under, and complying with all of the terms of, this License
+ or a future version of this License issued under Section 6.1. For legal
+ entities, "You'' includes any entity which controls, is controlled by, or is
+ under common control with You. For purposes of this definition, "control''
+ means (a) the power, direct or indirect, to cause the direction or management
+ of such entity, whether by contract or otherwise, or (b) ownership of more
+ than fifty percent (50%) of the outstanding shares or beneficial ownership of
+ such entity.</P></UL><B>2. Source Code License.</B>
+<UL><B>2.1. The Initial Developer Grant.</B> <BR>The Initial Developer hereby
+ grants You a world-wide, royalty-free, non-exclusive license, subject to third
+ party intellectual property claims:
+ <UL><B>(a)</B> <B> </B>under intellectual property rights (other than
+ patent or trademark) Licensable by Initial Developer to use, reproduce,
+ modify, display, perform, sublicense and distribute the Original Code (or
+ portions thereof) with or without Modifications, and/or as part of a Larger
+ Work; and
+ <P><B>(b)</B> under Patents Claims infringed by the making, using or selling
+ of Original Code, to make, have made, use, practice, sell, and offer for
+ sale, and/or otherwise dispose of the Original Code (or portions thereof).
+ <UL>
+ <UL></UL></UL><B>(c) </B>the licenses granted in this Section 2.1(a) and (b)
+ are effective on the date Initial Developer first distributes Original Code
+ under the terms of this License.
+ <P><B>(d) </B>Notwithstanding Section 2.1(b) above, no patent license is
+ granted: 1) for code that You delete from the Original Code; 2) separate
+ from the Original Code; or 3) for infringements caused by: i) the
+ modification of the Original Code or ii) the combination of the Original
+ Code with other software or devices. <BR> </P></UL><B>2.2. Contributor
+ Grant.</B> <BR>Subject to third party intellectual property claims, each
+ Contributor hereby grants You a world-wide, royalty-free, non-exclusive
+ license
+ <UL> <BR><B>(a)</B> <B> </B>under intellectual property rights (other
+ than patent or trademark) Licensable by Contributor, to use, reproduce,
+ modify, display, perform, sublicense and distribute the Modifications
+ created by such Contributor (or portions thereof) either on an unmodified
+ basis, with other Modifications, as Covered Code and/or as part of a Larger
+ Work; and
+ <P><B>(b)</B> under Patent Claims infringed by the making, using, or selling
+ of Modifications made by that Contributor either alone and/or in<FONT
+ color=#000000> combination with its Contributor Version (or portions of such
+ combination), to make, use, sell, offer for sale, have made, and/or
+ otherwise dispose of: 1) Modifications made by that Contributor (or portions
+ thereof); and 2) the combination of Modifications made by that
+ Contributor with its Contributor Version (or portions of such
+ combination).</FONT>
+ <P><B>(c) </B>the licenses granted in Sections 2.2(a) and 2.2(b) are
+ effective on the date Contributor first makes Commercial Use of the Covered
+ Code.
+ <P><B>(d) </B> Notwithstanding Section 2.2(b) above, no
+ patent license is granted: 1) for any code that Contributor has deleted from
+ the Contributor Version; 2) separate from the Contributor
+ Version; 3) for infringements caused by: i) third party
+ modifications of Contributor Version or ii) the combination of
+ Modifications made by that Contributor with other software (except as
+ part of the Contributor Version) or other devices; or 4) under Patent Claims
+ infringed by Covered Code in the absence of Modifications made by that
+ Contributor.</P></UL></UL>
+<P><BR><B>3. Distribution Obligations.</B>
+<UL><B>3.1. Application of License.</B> <BR>The Modifications which You create
+ or to which You contribute are governed by the terms of this License,
+ including without limitation Section <B>2.2</B>. The Source Code version of
+ Covered Code may be distributed only under the terms of this License or a
+ future version of this License released under Section <B>6.1</B>, and You must
+ include a copy of this License with every copy of the Source Code You
+ distribute. You may not offer or impose any terms on any Source Code version
+ that alters or restricts the applicable version of this License or the
+ recipients' rights hereunder. However, You may include an additional document
+ offering the additional rights described in Section <B>3.5</B>.
+ <P><B>3.2. Availability of Source Code.</B> <BR>Any Modification which You
+ create or to which You contribute must be made available in Source Code form
+ under the terms of this License either on the same media as an Executable
+ version or via an accepted Electronic Distribution Mechanism to anyone to whom
+ you made an Executable version available; and if made available via Electronic
+ Distribution Mechanism, must remain available for at least twelve (12) months
+ after the date it initially became available, or at least six (6) months after
+ a subsequent version of that particular Modification has been made available
+ to such recipients. You are responsible for ensuring that the Source Code
+ version remains available even if the Electronic Distribution Mechanism is
+ maintained by a third party.
+ <P><B>3.3. Description of Modifications.</B> <BR>You must cause all Covered
+ Code to which You contribute to contain a file documenting the changes You
+ made to create that Covered Code and the date of any change. You must include
+ a prominent statement that the Modification is derived, directly or
+ indirectly, from Original Code provided by the Initial Developer and including
+ the name of the Initial Developer in (a) the Source Code, and (b) in any
+ notice in an Executable version or related documentation in which You describe
+ the origin or ownership of the Covered Code.
+ <P><B>3.4. Intellectual Property Matters</B>
+ <UL><B>(a) Third Party Claims</B>. <BR>If Contributor has knowledge that a
+ license under a third party's intellectual property rights is required to
+ exercise the rights granted by such Contributor under Sections 2.1 or 2.2,
+ Contributor must include a text file with the Source Code distribution
+ titled "LEGAL'' which describes the claim and the party making the claim in
+ sufficient detail that a recipient will know whom to contact. If Contributor
+ obtains such knowledge after the Modification is made available as described
+ in Section 3.2, Contributor shall promptly modify the LEGAL file in all
+ copies Contributor makes available thereafter and shall take other steps
+ (such as notifying appropriate mailing lists or newsgroups) reasonably
+ calculated to inform those who received the Covered Code that new knowledge
+ has been obtained.
+ <P><B>(b) Contributor APIs</B>. <BR>If Contributor's Modifications include
+ an application programming interface and Contributor has knowledge of patent
+ licenses which are reasonably necessary to implement that API, Contributor
+ must also include this information in the LEGAL file.
+ <BR> </P></UL>
+ <B>(c) Representations.</B>
+ <UL>Contributor represents that, except as disclosed pursuant to Section
+ 3.4(a) above, Contributor believes that Contributor's Modifications are
+ Contributor's original creation(s) and/or Contributor has sufficient rights
+ to grant the rights conveyed by this License.</UL>
+ <P><BR><B>3.5. Required Notices.</B> <BR>You must duplicate the notice in
+ <B>Exhibit A</B> in each file of the Source Code. If it is not possible
+ to put such notice in a particular Source Code file due to its structure, then
+ You must include such notice in a location (such as a relevant directory)
+ where a user would be likely to look for such a notice. If You created
+ one or more Modification(s) You may add your name as a Contributor to the
+ notice described in <B>Exhibit A</B>. You must also duplicate this
+ License in any documentation for the Source Code where You describe
+ recipients' rights or ownership rights relating to Covered Code. You may
+ choose to offer, and to charge a fee for, warranty, support, indemnity or
+ liability obligations to one or more recipients of Covered Code. However, You
+ may do so only on Your own behalf, and not on behalf of the Initial Developer
+ or any Contributor. You must make it absolutely clear than any such warranty,
+ support, indemnity or liability obligation is offered by You alone, and You
+ hereby agree to indemnify the Initial Developer and every Contributor for any
+ liability incurred by the Initial Developer or such Contributor as a result of
+ warranty, support, indemnity or liability terms You offer.
+ <P><B>3.6. Distribution of Executable Versions.</B> <BR>You may distribute
+ Covered Code in Executable form only if the requirements of Section
+ <B>3.1-3.5</B> have been met for that Covered Code, and if You include a
+ notice stating that the Source Code version of the Covered Code is available
+ under the terms of this License, including a description of how and where You
+ have fulfilled the obligations of Section <B>3.2</B>. The notice must be
+ conspicuously included in any notice in an Executable version, related
+ documentation or collateral in which You describe recipients' rights relating
+ to the Covered Code. You may distribute the Executable version of Covered Code
+ or ownership rights under a license of Your choice, which may contain terms
+ different from this License, provided that You are in compliance with the
+ terms of this License and that the license for the Executable version does not
+ attempt to limit or alter the recipient's rights in the Source Code version
+ from the rights set forth in this License. If You distribute the Executable
+ version under a different license You must make it absolutely clear that any
+ terms which differ from this License are offered by You alone, not by the
+ Initial Developer or any Contributor. You hereby agree to indemnify the
+ Initial Developer and every Contributor for any liability incurred by the
+ Initial Developer or such Contributor as a result of any such terms You offer.
+
+ <P><B>3.7. Larger Works.</B> <BR>You may create a Larger Work by combining
+ Covered Code with other code not governed by the terms of this License and
+ distribute the Larger Work as a single product. In such a case, You must make
+ sure the requirements of this License are fulfilled for the Covered
+Code.</P></UL><B>4. Inability to Comply Due to Statute or Regulation.</B>
+<UL>If it is impossible for You to comply with any of the terms of this
+ License with respect to some or all of the Covered Code due to statute,
+ judicial order, or regulation then You must: (a) comply with the terms of this
+ License to the maximum extent possible; and (b) describe the limitations and
+ the code they affect. Such description must be included in the LEGAL file
+ described in Section <B>3.4</B> and must be included with all distributions of
+ the Source Code. Except to the extent prohibited by statute or regulation,
+ such description must be sufficiently detailed for a recipient of ordinary
+ skill to be able to understand it.</UL><B>5. Application of this License.</B>
+<UL>This License applies to code to which the Initial Developer has attached
+ the notice in <B>Exhibit A</B> and to related Covered Code.</UL><B>6. Versions
+of the License.</B>
+<UL><B>6.1. New Versions</B>. <BR>Netscape Communications Corporation
+ (''Netscape'') may publish revised and/or new versions of the License from
+ time to time. Each version will be given a distinguishing version number.
+ <P><B>6.2. Effect of New Versions</B>. <BR>Once Covered Code has been
+ published under a particular version of the License, You may always continue
+ to use it under the terms of that version. You may also choose to use such
+ Covered Code under the terms of any subsequent version of the License
+ published by Netscape. No one other than Netscape has the right to modify the
+ terms applicable to Covered Code created under this License.
+ <P><B>6.3. Derivative Works</B>. <BR>If You create or use a modified version
+ of this License (which you may only do in order to apply it to code which is
+ not already Covered Code governed by this License), You must (a) rename Your
+ license so that the phrases ''Mozilla'', ''MOZILLAPL'', ''MOZPL'',
+ ''Netscape'', "MPL", ''NPL'' or any confusingly similar phrase do not appear
+ in your license (except to note that your license differs from this License)
+ and (b) otherwise make it clear that Your version of the license contains
+ terms which differ from the Mozilla Public License and Netscape Public
+ License. (Filling in the name of the Initial Developer, Original Code or
+ Contributor in the notice described in <B>Exhibit A</B> shall not of
+ themselves be deemed to be modifications of this License.)</P></UL><B>7.
+DISCLAIMER OF WARRANTY.</B>
+<UL>COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS'' BASIS, WITHOUT
+ WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT
+ LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE,
+ FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. THE ENTIRE RISK AS TO THE
+ QUALITY AND PERFORMANCE OF THE COVERED CODE IS WITH YOU. SHOULD ANY COVERED
+ CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE INITIAL DEVELOPER OR ANY
+ OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY SERVICING, REPAIR OR
+ CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS
+ LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS
+ DISCLAIMER.</UL><B>8. TERMINATION.</B>
+<UL><B>8.1. </B>This License and the rights granted hereunder will
+ terminate automatically if You fail to comply with terms herein and fail to
+ cure such breach within 30 days of becoming aware of the breach. All
+ sublicenses to the Covered Code which are properly granted shall survive any
+ termination of this License. Provisions which, by their nature, must remain in
+ effect beyond the termination of this License shall survive.
+ <P><B>8.2. </B>If You initiate litigation by asserting a patent
+ infringement claim (excluding declatory judgment actions) against Initial
+ Developer or a Contributor (the Initial Developer or Contributor against whom
+ You file such action is referred to as "Participant") alleging that:
+ <P><B>(a) </B>such Participant's Contributor Version directly or
+ indirectly infringes any patent, then any and all rights granted by such
+ Participant to You under Sections 2.1 and/or 2.2 of this License shall, upon
+ 60 days notice from Participant terminate prospectively, unless if within 60
+ days after receipt of notice You either: (i) agree in writing to pay
+ Participant a mutually agreeable reasonable royalty for Your past and future
+ use of Modifications made by such Participant, or (ii) withdraw Your
+ litigation claim with respect to the Contributor Version against such
+ Participant. If within 60 days of notice, a reasonable royalty and
+ payment arrangement are not mutually agreed upon in writing by the parties or
+ the litigation claim is not withdrawn, the rights granted by Participant to
+ You under Sections 2.1 and/or 2.2 automatically terminate at the expiration of
+ the 60 day notice period specified above.
+ <P><B>(b)</B> any software, hardware, or device, other than such
+ Participant's Contributor Version, directly or indirectly infringes any
+ patent, then any rights granted to You by such Participant under Sections
+ 2.1(b) and 2.2(b) are revoked effective as of the date You first made, used,
+ sold, distributed, or had made, Modifications made by that Participant.
+ <P><B>8.3. </B>If You assert a patent infringement claim against
+ Participant alleging that such Participant's Contributor Version directly or
+ indirectly infringes any patent where such claim is resolved (such as by
+ license or settlement) prior to the initiation of patent infringement
+ litigation, then the reasonable value of the licenses granted by such
+ Participant under Sections 2.1 or 2.2 shall be taken into account in
+ determining the amount or value of any payment or license.
+ <P><B>8.4.</B> In the event of termination under Sections 8.1 or 8.2
+ above, all end user license agreements (excluding distributors and
+ resellers) which have been validly granted by You or any distributor hereunder
+ prior to termination shall survive termination.</P></UL><B>9. LIMITATION OF
+LIABILITY.</B>
+<UL>UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT (INCLUDING
+ NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL DEVELOPER, ANY
+ OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE, OR ANY SUPPLIER OF ANY
+ OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR ANY INDIRECT, SPECIAL,
+ INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT
+ LIMITATION, DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
+ MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH
+ PARTY SHALL HAVE BEEN INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS
+ LIMITATION OF LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL
+ INJURY RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW
+ PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR
+ LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS EXCLUSION AND
+ LIMITATION MAY NOT APPLY TO YOU.</UL><B>10. U.S. GOVERNMENT END USERS.</B>
+<UL>The Covered Code is a ''commercial item,'' as that term is defined in 48
+ C.F.R. 2.101 (Oct. 1995), consisting of ''commercial computer software'' and
+ ''commercial computer software documentation,'' as such terms are used in 48
+ C.F.R. 12.212 (Sept. 1995). Consistent with 48 C.F.R. 12.212 and 48 C.F.R.
+ 227.7202-1 through 227.7202-4 (June 1995), all U.S. Government End Users
+ acquire Covered Code with only those rights set forth herein.</UL><B>11.
+MISCELLANEOUS.</B>
+<UL>This License represents the complete agreement concerning subject matter
+ hereof. If any provision of this License is held to be unenforceable, such
+ provision shall be reformed only to the extent necessary to make it
+ enforceable. This License shall be governed by California law provisions
+ (except to the extent applicable law, if any, provides otherwise), excluding
+ its conflict-of-law provisions. With respect to disputes in which at least one
+ party is a citizen of, or an entity chartered or registered to do business in
+ the United States of America, any litigation relating to this License shall be
+ subject to the jurisdiction of the Federal Courts of the Northern District of
+ California, with venue lying in Santa Clara County, California, with the
+ losing party responsible for costs, including without limitation, court costs
+ and reasonable attorneys' fees and expenses. The application of the United
+ Nations Convention on Contracts for the International Sale of Goods is
+ expressly excluded. Any law or regulation which provides that the language of
+ a contract shall be construed against the drafter shall not apply to this
+ License.</UL><B>12. RESPONSIBILITY FOR CLAIMS.</B>
+<UL>As between Initial Developer and the Contributors, each party is
+ responsible for claims and damages arising, directly or indirectly, out of its
+ utilization of rights under this License and You agree to work with Initial
+ Developer and Contributors to distribute such responsibility on an equitable
+ basis. Nothing herein is intended or shall be deemed to constitute any
+ admission of liability.</UL><B>13. MULTIPLE-LICENSED CODE.</B>
+<UL>Initial Developer may designate portions of the Covered Code as
+ �Multiple-Licensed?. �Multiple-Licensed? means that the Initial
+ Developer permits you to utilize portions of the Covered Code under Your
+ choice of the MPL or the alternative licenses, if any, specified by the
+ Initial Developer in the file described in Exhibit A.</UL>
+<P><BR><B>EXHIBIT A -Mozilla Public License.</B>
+<UL>The contents of this file are subject to the Mozilla Public License
+ Version 1.1 (the "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+ <BR>http://www.mozilla.org/MPL/
+ <P>Software distributed under the License is distributed on an "AS IS" basis,
+ WITHOUT WARRANTY OF <BR>ANY KIND, either express or implied. See the License
+ for the specific language governing rights and <BR>limitations under the
+ License.
+ <P>The Original Code is Javassist.
+ <P>The Initial Developer of the Original Code is Shigeru Chiba.
+ Portions created by the Initial Developer are<BR>
+ Copyright (C) 1999-2010 Shigeru Chiba. All Rights Reserved.
+ <P>Contributor(s): ______________________________________.
+
+ <P>Alternatively, the contents of this file may be used under the terms of
+ the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ in which case the provisions of the LGPL are applicable instead
+ of those above. If you wish to allow use of your version of this file only
+ under the terms of the LGPL, and not to allow others to
+ use your version of this file under the terms of the MPL, indicate your
+ decision by deleting the provisions above and replace them with the notice
+ and other provisions required by the LGPL. If you do not delete
+ the provisions above, a recipient may use your version of this file under
+ the terms of either the MPL or the LGPL.
+
+ <P></P></UL>
+</BODY>
+</HTML>
diff --git a/Readme.html b/Readme.html
new file mode 100644
index 0000000..f6429fe
--- /dev/null
+++ b/Readme.html
@@ -0,0 +1,800 @@
+<html>
+<HEAD>
+ <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1">
+ <TITLE>Read Me First</TITLE>
+</HEAD>
+<body>
+
+<h1>Javassist version 3</h1>
+
+<h3>Copyright (C) 1999-2010 by Shigeru Chiba, All rights reserved.</h3>
+
+<p><br></p>
+
+<p>Javassist (JAVA programming ASSISTant) makes Java bytecode manipulation
+simple. It is a class library for editing bytecodes in Java;
+it enables Java programs to define a new class at runtime and to
+modify a class file when the JVM loads it. Unlike other similar
+bytecode editors, Javassist provides two levels of API: source level
+and bytecode level. If the users use the source-level API, they can
+edit a class file without knowledge of the specifications of the Java
+bytecode. The whole API is designed with only the vocabulary of the
+Java language. You can even specify inserted bytecode in the form of
+source text; Javassist compiles it on the fly. On the other hand, the
+bytecode-level API allows the users to directly edit a class file as
+other editors.
+
+<p><br>
+
+<h2>Files</h2>
+
+<ul>
+<table>
+<tr>
+<td><li><tt><a href="License.html">License.html</a></tt></td>
+<td>License file
+(Also see the <a href="#copyright">copyright notices</a> below)</td>
+</tr>
+
+<tr>
+<td><li><tt><a href="tutorial/tutorial.html">tutorial/tutorial.html</a></tt></td>
+<td>Tutorial</td>
+</tr>
+
+<tr>
+<td><li><tt>./javassist.jar</tt></td>
+<td>The Javassist jar file (class files)</td>
+</tr>
+
+<tr>
+<td><li><tt>./src/main</tt></td>
+<td>The source files</td>
+</tr>
+
+<tr>
+<td><li><tt><a href="html/index.html">html/index.html</a></tt></td>
+<td>The top page of the Javassist API document.</td>
+</tr>
+
+<tr>
+<td><li><tt>./sample/</tt></td>
+<td>Sample programs</td>
+</tr>
+</table>
+</ul>
+
+<p><br>
+
+<h2>How to run sample programs</h2>
+
+<p>JDK 1.4 or later is needed.
+
+<h3>0. If you have Apache Ant</h3>
+
+<p>Run the sample-all task.
+Otherwise, follow the instructions below.
+
+<h3>1. Move to the directory where this Readme.html file is located.</h3>
+
+<p>In the following instructions, we assume that the javassist.jar
+file is included in the class path.
+For example, the javac and java commands must receive
+the following <code>classpath</code> option:
+
+<ul><pre>
+-classpath ".:javassist.jar"
+</pre></ul>
+
+<p>If the operating system is Windows, the path
+separator must be not <code>:</code> (colon) but
+<code>;</code> (semicolon). The java command can receive
+the <code>-cp</code> option
+as well as <code>-classpath</code>.
+
+<p>If you don't want to use the class-path option, you can make
+<code>javassist.jar</code> included in the <code>CLASSPATH</code>
+environment:
+
+<ul><pre>
+export CLASSPATH=.:javassist.jar
+</pre></ul>
+
+<p>or if the operating system is Windows:
+
+<ul><pre>
+set CLASSPATH=.;javassist.jar
+</pre></ul>
+
+
+<p>Otherwise, you can copy <tt>javassist.jar</tt> to the directory
+
+<ul><<i>java-home</i>><tt>/jre/lib/ext</tt>.</ul>
+
+<p><<i>java-home</i>> depends on the system. It is usually
+<tt>/usr/local/java</tt>, <tt>c:\j2sdk1.4\</tt>, etc.
+
+<h3>2. sample/Test.java</h3>
+
+<p> This is a very simple program using Javassist.
+
+<p> To run, type the commands:
+
+<ul><pre>
+% javac sample/Test.java
+% java sample.Test
+</pre></ul>
+
+<p> For more details, see <a type="text/plain" href="sample/Test.java">sample/Test.java</a>
+
+<h3>3. sample/reflect/*.java</h3>
+
+<p> This is the "verbose metaobject" example well known in reflective
+ programming. This program dynamically attaches a metaobject to
+ a Person object. The metaobject prints a message if a method
+ is called on the Person object.
+
+<p> To run, type the commands:
+
+<ul><pre>
+% javac sample/reflect/*.java
+% java javassist.tools.reflect.Loader sample.reflect.Main Joe
+</pre></ul>
+
+<p>Compare this result with that of the regular execution without reflection:
+
+<ul><pre>% java sample.reflect.Person Joe</pre></ul>
+
+<p> For more details, see <a type="text/plain" href="sample/reflect/Main.java">sample/reflect/Main.java</a>
+
+<p> Furthermore, the Person class can be statically modified so that
+ all the Person objects become reflective without sample.reflect.Main.
+ To do this, type the commands:
+
+<ul><pre>
+% java javassist.tools.reflect.Compiler sample.reflect.Person -m sample.reflect.VerboseMetaobj
+</pre></ul>
+
+<p> Then,
+<ul><pre>
+% java sample.reflect.Person Joe
+</pre></ul>
+
+<h3>4. sample/duplicate/*.java</h3>
+
+<p> This is another example of reflective programming.
+
+<p> To run, type the commands:
+
+<ul><pre>
+% javac sample/duplicate/*.java
+% java sample.duplicate.Main
+</pre></ul>
+
+<p>Compare this result with that of the regular execution without reflection:
+
+<ul><pre>% java sample.duplicate.Viewer</pre></ul>
+
+<p>For more details, see
+<a type="text/plain" href="sample/duplicate/Main.java">sample/duplicate/Main.java</a>
+
+<h3>5. sample/vector/*.java</h3>
+
+<p>This example shows the use of Javassit for producing a class representing
+a vector of a given type at compile time.
+
+<p> To run, type the commands:
+<ul><pre>
+% javac sample/vector/*.java
+% java sample.preproc.Compiler sample/vector/Test.j
+% javac sample/vector/Test.java
+% java sample.vector.Test
+</pre></ul>
+
+<p>Note: <code>javassist.jar</code> is unnecessary to compile and execute
+<code>sample/vector/Test.java</code>.
+
+For more details, see
+<a type="text/plain" href="sample/vector/Test.j">sample/vector/Test.j</a>
+and <a type="text/plain" href="sample/vector/VectorAssistant.java">sample/vector/VectorAssistant.java</a>
+
+<h3>6. sample/rmi/*.java</h3>
+
+<p> This demonstrates the javassist.rmi package.
+
+<p> To run, type the commands:
+<ul><pre>
+% javac sample/rmi/*.java
+% java sample.rmi.Counter 5001
+</pre></ul>
+
+<p> The second line starts a web server listening to port 5001.
+
+<p> Then, open <a href="sample/rmi/webdemo.html">sample/rmi/webdemo.html</a>
+with a web browser running
+ on the local host. (<tt>webdemo.html</tt> trys to fetch an applet from
+ <tt>http://localhost:5001/</tt>, which is the web server we started above.)
+
+<p> Otherwise, run sample.rmi.CountApplet as an application:
+
+<ul><pre>
+% java javassist.web.Viewer localhost 5001 sample.rmi.CountApplet
+</pre></ul>
+
+<h3>7. sample/evolve/*.java</h3>
+
+<p> This is a demonstration of the class evolution mechanism implemented
+ with Javassist. This mechanism enables a Java program to reload an
+ existing class file under some restriction.
+
+<p> To run, type the commands:
+<ul><pre>
+% javac sample/evolve/*.java
+% java sample.evolve.DemoLoader 5003
+</pre></ul>
+
+<p> The second line starts a class loader DemoLoader, which runs a web
+ server DemoServer listening to port 5003.
+
+<p> Then, open <a href="http://localhost:5003/demo.html">http://localhost:5003/demo.html</a> with a web browser running
+ on the local host.
+(Or, see <a href="sample/evolve/start.html">sample/evolve/start.html</a>.)
+
+<h3>8. sample/hotswap/*.java</h3>
+
+<p>This shows dynamic class reloading by the JPDA. It needs JDK 1.4 or later.
+To run, first type the following commands:
+
+<ul><pre>
+% cd sample/hotswap
+% javac *.java
+% cd logging
+% javac *.java
+% cd ..
+</pre></ul>
+
+<p>If your Java is 1.4, then type:
+
+<ul><pre>
+% java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000 Test
+</pre></ul>
+
+<p>If you are using Java 5, then type:
+
+<ul><pre>
+% java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000 Test
+</pre></ul>
+
+<p>Note that the class path must include <code>JAVA_HOME/lib/tools.jar</code>.
+
+<h2>Hints</h2>
+
+<p>To know the version number, type this command:
+
+<ul><pre>
+% java -jar javassist.jar
+</pre></ul>
+
+<p>Javassist provides a class file viewer for debugging. For more details,
+see javassist.Dump.
+
+<p><br>
+
+<h2>Changes</h2>
+
+<p>-version 3.14 on October 5, 2010
+
+<ul>
+ <li>JIRA JASSIST-121, 123, 128, 129, 130, 131, 132.
+</ul>
+
+<p>-version 3.13 on July 19, 2010
+
+<ul>
+ <li>JIRA JASSIST-118, 119, 122, 124, 125.
+</ul>
+
+<p>-version 3.12.1 on June 10, 2010
+
+<p>-version 3.12 on April 16, 2010
+
+<p>-version 3.11 on July 3, 2009
+<ul>
+ <li>JIRA JASSIST-67, 68, 74, 75, 76, 77, 81, 83, 84, 85, 86, 87 were fixed.
+ <li>Now javassist.bytecode.CodeIterator can insert a gap into
+ a large method body more than 32KB. (JIRA JASSIST-79, 80)
+</ul>
+
+<p>-version 3.10 on March 5, 2009
+
+<ul>
+ <li>JIRA JASSIST-69, 70, 71 were fixed.
+</ul>
+
+<p>-version 3.9 on October 9, 2008
+<ul>
+ <li>ClassPool.makeClassIfNew(InputStream) was implemented.
+ <li>CtNewMethod.wrapped(..) and CtNewConstructor.wrapped(..)
+ implicitly append a method like _added_m$0.
+ This method now has a synthetic attribute.
+ <li>JIRA JASSIST-66 has been fixed.
+</ul>
+
+<p>-version 3.8.1 on July 17, 2008
+<ul>
+ <li>CtClass.rebuildClassFile() has been added.
+ <li>A few bugs of javassist.bytecode.analysis have been fixed.
+ 3.8.0 could not correctly deal with one letter class name
+ such as I and J.
+</ul>
+
+<p>-version 3.8.0 on June 13, 2008
+<ul>
+ <li>javassist.bytecode.analysis was implemented.
+ <li>JASSIST-45, 47, 51, 54-57, 60, 62 were fixed.
+</ul>
+
+<p>-version 3.7.1 on March 10, 2008
+<ul>
+ <li>a bug of javassist.util.proxy has been fixed.
+</ul>
+
+<p>-version 3.7 on January 20, 2008
+<ul>
+ <li>Several minor bugs have been fixed.
+</ul>
+
+<p>-version 3.6.0 on September 13, 2007
+
+<p>-version 3.6.0.CR1 on July 27, 2007
+
+<ul>
+ <li>The stack map table introduced since Java 6 has been supported.
+ <li>CtClass#getDeclaredBehaviors() now returns a class initializer
+ as well as methods and constructors.
+ <li>The default status of automatic pruning was made off.
+ Instead of pruning, this version of Javassist compresses
+ the data structure of a class file after toBytecode() is called.
+ The compressed class file is automatically decompressed when needed.
+ This saves memory space better than pruning.
+ <li><a href="http://jira.jboss.com/jira/browse/JASSIST-33">JIRA JASSIST-33</a> has been fixed.
+</ul>
+
+<p>-version 3.5 on April 29, 2007
+<ul>
+ <li>Various minor updates.
+</ul>
+
+<p>-version 3.4 on November 17, 2006
+<ul>
+ <li>A bug in CodeConverter#replaceFieldRead() and CodeConverter#replaceFieldWrite()
+ was fixed. <a href="http://jira.jboss.com/jira/browse/JBAOP-284">JBAOP-284</a>.
+
+ <li>A synchronization bug and a performance bug in <code>javassist.util.proxy</code>
+ have been fixed
+ (<a href="http://jira.jboss.com/jira/browse/JASSIST-28">JASSIST-28</a>).
+ Now generated proxy classes are cached. To turn the caching off,
+ set <code>ProxyFactory.useCache</code> to <code>false</code>.
+</ul>
+
+<p>-version 3.3 on August 17, 2006
+<ul>
+ <li>CtClass#toClass() and ClassPool#toClass() were modified to accept a
+ <code>ProtectionDomain</code>
+ (<a href="http://jira.jboss.com/jira/browse/JASSIST-23">JASSIST-23</a>).
+ Now ClassPool#toClass(CtClass, ClassLoader) should not be overridden. All
+ subclasses of ClassPool must override toClass(CtClass, ClassLoader,
+ ProtectionDomain).
+
+ <li>CtClass#getAvailableAnnotations() etc. have been implemented.
+
+ <li>A bug related to a way of dealing with a bridge method was fixed
+ (<a href="http://jira.jboss.com/jira/browse/HIBERNATE-37">HIBERNATE-37</a>).
+
+ <li>javassist.scopedpool package was added.
+</ul>
+
+<p>-version 3.2 on June 21, 2006
+
+<ul>
+ <li>The behavior of CtBehavior#getParameterAnnotations() has been changed.
+ It is now compatible to Java Reflection API
+ (<a href="http://jira.jboss.com/jira/browse/JASSIST-19">JASSIST-19</a>).
+</ul>
+
+<p>- version 3.2.0.CR2 on May 9, 2006
+<ul>
+ <li>A bug of replace(String,ExprEditor) in javassist.expr.Expr has been fixed.
+ <li>Updated ProxyFactory getClassLoader to choose the javassit class loader
+ when the proxy superclass has a null class loader (a jdk/endorsed class)
+ (<a href='http://jira.jboss.com/jira/browse/JASSIST-18'>JASSIST-18</a>).
+ <li>Updated the throws clause of the javassist.util.proxy.MethodHandler
+ to be Throwable rather than Exception
+ (<a href='http://jira.jboss.com/jira/browse/JASSIST-16'>JASSIST-16</a>).
+</ul>
+
+<p>- version 3.2.0.CR1 on March 18, 2006
+<ul>
+ <li>Annotations enhancements to javassist.bytecode.MethodInfo.
+ <li>Allow a ClassPool to override the "guess" at the classloader to use.
+</ul>
+
+<p>- version 3.1 on February 23, 2006
+
+<ul>
+ <li>getFields(), getMethods(), and getConstructors() in CtClass
+ were changed to return non-private memebers instead of only
+ public members.
+ <li>getEnclosingClass() in javassist.CtClass was renamed
+ to getEnclosingMethod().
+ <li>getModifiers() was extended to return Modifier.STATIC if the class
+ is a static inner class.
+ <li>The return type of CtClass.stopPruning() was changed from void
+ to boolean.
+ <li>toMethod() in javassist.CtConstructor has been implemented.
+ <li>It includes new javassist.util.proxy package
+ similar to Enhancer of CGLIB.
+ <p>
+ <li>The subpackages of Javassist were restructured.
+ <ul>
+ <li>javassist.tool package was renamed to javassist.tools.
+ <li>HotSwapper was moved to javassist.util.
+ <li>Several subpackages were moved to javassist.tools.
+ <li>javassist.preproc package was elminated and the source was
+ moved to the sample directory.
+ </ul>
+</ul>
+
+<p>- version 3.1 RC2 on September 7, 2005
+
+<ul>
+ <li>RC2 is released mainly for an administrative reason.
+ <li>A few bugs have been fixed.
+</ul>
+
+<p>- version 3.1 RC1 on August 29, 2005
+
+<ul>
+ <li>Better annotation supports. See <code>CtClass.getAnnotations()</code>
+ <li>javassist.tool.HotSwapper was added.
+ <li>javassist.ClassPool.importPackage() was added.
+ <li>The compiler now accepts array initializers
+ (only one dimensional arrays).
+ <li>javassist.Dump was moved to javassist.tool.Dump.
+ <li>Many bugs were fixed.
+</ul>
+
+<p>- version 3.0 on January 18, 2005
+
+<ul>
+ <li>The compiler now supports synchronized statements and finally
+ clauses.
+ <li>You can now remove a method and a field.
+</ul>
+
+<p>- version 3.0 RC1 on September 13, 2004.
+
+<ul>
+ <li>CtClass.toClass() has been reimplemented. The behavior has been
+ changed.
+ <li>javassist.expr.NewArray has been implemented. It enables modifying
+ an expression for array creation.
+ <li><code>.class</code> notation has been supported. The modified class
+ file needs javassist.runtime.DotClass at runtime.
+ <li>a bug in <code>CtClass.getMethods()</code> has been fixed.
+ <li>The compiler supports a switch statement.
+</ul>
+
+<p>- version 3.0 beta on May 18th, 2004.
+
+<ul>
+ <li>The ClassPool framework has been redesigned.
+ <ul>
+ <li>writeFile(), write(), ... in ClassPool have been moved to CtClass.
+ <li>The design of javassist.Translator has been changed.
+ </ul>
+
+ <li>javassist.bytecode.annotation has been added for meta tags.
+ <li>CtClass.makeNestedClass() has been added.
+ <li>The methods declared in javassist.bytecode.InnerClassesAttribute
+ have been renamed a bit.
+ <li>Now local variables were made available in the source text passed to
+ CtBehavior.insertBefore(), MethodCall.replace(), etc.
+ <li>CtClass.main(), which prints the version number, has been added.
+ <li>ClassPool.SimpleLoader has been public.
+ <li>javassist.bytecode.DeprecatedAttribute has been added.
+ <li>javassist.bytecode.LocalVariableAttribute has been added.
+ <li>CtClass.getURL() and javassist.ClassPath.find() has been added.
+ <li>CtBehavior.insertAt() has been added.
+ <li>CtClass.detach() has been added.
+ <li>CodeAttribute.computeMaxStack() has been added.
+</ul>
+
+<p>- version 2.6 in August, 2003.
+
+<ul>
+ <li>The behavior of CtClass.setSuperclass() was changed.
+ To obtain the previous behavior, call CtClass.replaceClassName().
+ <li>CtConstructor.setBody() now works for class initializers.
+ <li>CtNewMethod.delegator() now works for static methods.
+ <li>javassist.expr.Expr.indexOfBytecode() has been added.
+ <li>javassist.Loader has been modified so that getPackage() returns
+ a package object.
+ <li>Now, the compiler can correctly compile a try statement and an
+ infinite while-loop.
+</ul>
+
+<p>- version 2.5.1 in May, 2003.
+<br>Simple changes for integration with JBoss AOP
+<ul>
+ <li>Made ClassPool.get0 protected so that subclasses of ClassPool can call it.
+ <li>Moved all access to the class cache (the field ClassPool.classes) to a method called getCached(String classname). This is so subclasses of ClassPool can override this behavior.
+</ul>
+
+<p>- version 2.5 in May, 2003.
+<br>From this version, Javassist is part of the JBoss project.
+<ul>
+ <li>The license was changed from MPL to MPL/LGPL dual.
+ <li>ClassPool.removeClassPath() and ClassPath.close() have been added.
+ <li>ClassPool.makeClass(InputStream) has been added.
+ <li>CtClass.makeClassInitializer() has been added.
+ <li>javassist.expr.Expr has been changed to a public class.
+ <li>javassist.expr.Handler has been added.
+ <li>javassist.expr.MethodCall.isSuper() has been added.
+ <li>CtMethod.isEmpty() and CtConstructor.isEmpty() have been added.
+ <li>LoaderClassPath has been implemented.
+</ul>
+
+<p>- version 2.4 in February, 2003.
+<ul>
+ <li>The compiler included in Javassist did not correctly work with
+ interface methods. This bug was fixed.
+ <li>Now javassist.bytecode.Bytecode allows more than 255 local
+ variables in the same method.
+ <li>javassist.expr.Instanceof and Cast have been added.
+ <li>javassist.expr.{MethodCall,NewExpr,FieldAccess,Instanceof,Cast}.where()
+ have been added. They return the caller-side method surrounding the
+ expression.
+ <li>javassist.expr.{MethodCall,NewExpr,FieldAccess,Instanceof,Cast}.mayThrow()
+ have been added.
+ <li>$class has been introduced.
+ <li>The parameters to replaceFieldRead(), replaceFieldWrite(),
+ and redirectFieldAccess() in javassist.CodeConverter are changed.
+ <li>The compiler could not correctly handle a try-catch statement.
+ This bug has been fixed.
+</ul>
+
+<p>- version 2.3 in December, 2002.
+<ul>
+ <li>The tutorial has been revised a bit.
+ <li>SerialVersionUID class was donated by Bob Lee. Thanks.
+ <li>CtMethod.setBody() and CtConstructor.setBody() have been added.
+ <li>javassist.reflect.ClassMetaobject.useContextClassLoader has been added.
+ If true, the reflection package does not use Class.forName() but uses
+ a context class loader specified by the user.
+ <li>$sig and $type are now available.
+ <li>Bugs in Bytecode.write() and read() have been fixed.
+</ul>
+
+<p>- version 2.2 in October, 2002.
+<ul>
+ <li>The tutorial has been revised.
+ <li>A new package <code>javassist.expr</code> has been added.
+ This is replacement of classic <code>CodeConverter</code>.
+ <li>javassist.ConstParameter was changed into
+ javassist.CtMethod.ConstParameter.
+ <li>javassist.FieldInitializer was renamed into
+ javassist.CtField.Initializer.
+ <li>A bug in javassist.bytecode.Bytecode.addInvokeinterface() has been
+ fixed.
+ <li>In javassist.bytecode.Bytecode, addGetfield(), addGetstatic(),
+ addInvokespecial(), addInvokestatic(), addInvokevirtual(),
+ and addInvokeinterface()
+ have been modified to update the current statck depth.
+</ul>
+
+<p>- version 2.1 in July, 2002.
+<ul>
+ <li>javassist.CtMember and javassist.CtBehavior have been added.
+ <li>javassist.CtClass.toBytecode() has been added.
+ <li>javassist.CtClass.toClass() and javassist.ClassPool.writeAsClass()
+ has been added.
+ <li>javassist.ByteArrayClassPath has been added.
+ <li>javassist.bytecode.Mnemonic has been added.
+ <li>Several bugs have been fixed.
+</ul>
+
+<p>- version 2.0 (major update) in November, 2001.
+<ul>
+ <li>The javassist.bytecode package has been provided. It is a
+ lower-level API for directly modifying a class file although
+ the users must have detailed knowledge of the Java bytecode.
+
+ <li>The mechanism for creating CtClass objects have been changed.
+
+ <li>javassist.tool.Dump moves to the javassist package.
+</ul>
+
+<p>version 1.0 in July, 2001.
+<ul>
+ <li>javassist.reflect.Metaobject and ClassMetaobject was changed.
+ Now they throw the same exception that they receive from a
+ base-level object.
+</ul>
+
+<p>- version 0.8
+<ul>
+ <li>javassist.tool.Dump was added. It is a class file viewer.
+
+ <li>javassist.FiledInitializer.byNewArray() was added. It is for
+ initializing a field with an array object.
+
+ <li>javassist.CodeConverter.redirectMethodCall() was added.
+
+ <li>javassist.Run was added.
+</ul>
+
+<p>- version 0.7
+<ul>
+ <li>javassit.Loader was largely modified. javassist.UserLoader was
+ deleted. Instead, Codebase was renamed to ClassPath
+ and UserClassPath was added. Now programmers who want to
+ customize Loader must write a class implementing UserClassPath
+ instead of UserLoader. This change is for sharing class search paths
+ between Loader and CtClass.CtClass(String).
+
+ <li>CtClass.addField(), addMethod(), addConstructor(), addWrapper() were
+ also largely modified so that it receives CtNewMethod, CtNewConstructor,
+ or CtNewField. The static methods for creating these objects were
+ added to the API.
+
+ <li>Constructors are now represented by CtConstructor objects.
+ CtConstructor is a subclass of CtMethod.
+
+ <li>CtClass.getUserAttribute() was removed. Use CtClass.getAttribute().
+
+ <li>javassist.rmi.RmiLoader was added.
+
+ <li>javassist.reflect.Metalevel._setMetaobject() was added. Now
+ metaobjects can be replaced at runtime.
+</ul>
+
+<p>- version 0.6
+<ul>
+ <li>Javassist was modified to correctly deal with array types appearing
+ in signatures.
+
+ <li>A bug crashed resulting bytecode if a class includes a private static
+ filed. It has been fixed.
+
+ <li>javassist.CtNewInterface was added.
+
+ <li>javassist.Loader.recordClass() was renamed into makeClass().
+
+ <li>javassist.UserLoader.loadClass() was changed to take the second
+ parameter.
+</ul>
+
+<p>- version 0.5
+<ul>
+ <li>a bug-fix version.
+</ul>
+
+<p>- version 0.4
+<ul>
+ <li>Major update again. Many classes and methods were changed.
+ Most of methods taking java.lang.Class have been changed to
+ take javassist.CtClass.
+</ul>
+
+<p>- version 0.3
+<ul>
+ <li>Major update. Many classes and methods were changed.
+</ul>
+
+<p>- version 0.2
+<ul>
+ <li>Jar/zip files are supported.
+</ul>
+
+<p>-version 0.1 on April 16, 1999.
+<ul>
+ <li>The first release.
+</ul>
+
+<p><br>
+
+<h2>Bug reports etc.</h2>
+
+<dl>
+<dt>Bug reports:
+<dd>Post your reports at <a href="http://www.jboss.org/jive.jsp">Forums</a>
+or directly send an email to:
+<br>
+<tt><a href="mailto:chiba@acm.org">chiba@acm.org</a></tt>
+or
+<tt><a href="mailto:chiba@is.titech.ac.jp">chiba@is.titech.ac.jp</a></tt>
+<br>
+
+<p><dt>The home page of Javassist:
+<dd>Visit <a href="http://www.javassist.org"><tt>www.javassist.org</tt></a>
+and <a href="http://www.jboss.org/index.html?module=html&op=userdisplay&id=developers/projects/javassist"><tt>www.jboss.org</tt></a>
+
+</dl>
+
+<p><br>
+
+<a name="copyright">
+<h2>Copyright notices</h2>
+
+<p>Javassist, a Java-bytecode translator toolkit.
+<br>Copyright (C) 1999-2010 Shigeru Chiba. All Rights Reserved.
+
+<p>The contents of this software, Javassist, are subject to
+the Mozilla Public License Version 1.1 (the "License");<br>
+you may not use this software except in compliance
+with the License. You may obtain a copy of the License at
+<br>http://www.mozilla.org/MPL/
+
+<p>Software distributed under the License is distributed on an "AS IS"
+basis, WITHOUT WARRANTY OF <br>ANY KIND, either express or implied.
+See the License for the specific language governing rights and
+<br>limitations under the License.
+
+<p>The Original Code is Javassist.
+
+<p>The Initial Developer of the Original Code is Shigeru Chiba.
+Portions created by the Initial Developer are<br>
+Copyright (C) 1999-2010 Shigeru Chiba. All Rights Reserved.
+<p>Contributor(s): ______________________________________.
+
+<p>Alternatively, the contents of this software may be used under the
+terms of the GNU Lesser General Public License Version 2.1 or later
+(the "LGPL"), in which case the provisions of the LGPL are applicable
+instead of those above. If you wish to allow use of your version of
+this software only under the terms of the LGPL, and not to allow others to
+use your version of this software under the terms of the MPL, indicate
+your decision by deleting the provisions above and replace them with
+the notice and other provisions required by the LGPL. If you do not
+delete the provisions above, a recipient may use your version of this
+software under the terms of either the MPL or the LGPL.
+
+<p>If you obtain this software as part of JBoss, the contents of this
+software may be used under only the terms of the LGPL. To use them
+under the MPL, you must obtain a separate package including only
+Javassist but not the other part of JBoss.
+
+<p>All the contributors to the original source tree must agree to
+the original license term described above.
+
+<p><br>
+
+<h2>Acknowledgments</h2>
+
+<p>The development of this software is sponsored in part by the PRESTO
+and CREST programs of <a href="http://www.jst.go.jp/">Japan
+Science and Technology Corporation</a>.
+
+<p>I'd like to thank Michiaki Tatsubori, Johan Cloetens,
+Philip Tomlinson, Alex Villazon, Pascal Rapicault, Dan HE, Eric Tanter,
+Michael Haupt, Toshiyuki Sasaki, Renaud Pawlak, Luc Bourlier,
+Eric Bui, Lewis Stiller, Susumu Yamazaki, Rodrigo Teruo Tomita,
+Marc Segura-Devillechaise, Jan Baudisch, Julien Blass, Yoshiki Sato,
+Fabian Crabus, Bo Norregaard Jorgensen, Bob Lee, Bill Burke,
+Remy Sanlaville, Muga Nishizawa, Alexey Loubyansky, Saori Oki,
+Andreas Salathe, Dante Torres estrada, S. Pam, Nuno Santos,
+Denis Taye, Colin Sampaleanu, Robert Bialek, Asato Shimotaki,
+Howard Lewis Ship, Richard Jones, Marjan Sterjev,
+Bruce McDonald, Mark Brennan, Vlad Skarzhevskyy,
+Brett Randall, Tsuyoshi Murakami, Nathan Meyers, Yoshiyuki Usui
+Yutaka Sunaga, Arjan van der Meer, Bruce Eckel, Guillaume Pothier,
+Kumar Matcha, Andreas Salathe, Renat Zubairov, Armin Haaf,
+Emmanuel Bernard
+and all other contributors for their contributions.
+
+<p><br>
+
+<hr>
+<a href="http://www.is.titech.ac.jp/~chiba">Shigeru Chiba</a>
+(Email: <tt>chiba@acm.org</tt>)
+<br>Dept. of Math. and Computing Sciences,
+<a href="http://www.titech.ac.jp">Tokyo Institute of Technology</a>
diff --git a/build.properties b/build.properties
new file mode 100644
index 0000000..42e774e
--- /dev/null
+++ b/build.properties
@@ -0,0 +1,4 @@
+# We only use junit, so we set the lib.dir to the junit out dir; this
+# works only on full builds that include master and in unix (though
+# you can change the path in your local repository).
+lib.dir=../../out/host/linux-x86/framework
diff --git a/build.xml b/build.xml
new file mode 100644
index 0000000..9d4c329
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,298 @@
+<?xml version="1.0"?>
+
+<!-- =================================================================== -->
+<!-- JBoss build file -->
+<!-- =================================================================== -->
+
+<project name="javassist" default="jar" basedir=".">
+
+ <property name="dist-version" value="javassist-3.14.0-GA"/>
+
+ <property environment="env"/>
+ <property name="target.jar" value="javassist.jar"/>
+ <property name="target-src.jar" value="javassist-src.jar"/>
+ <property name="lib.dir" value="${basedir}/lib"/>
+ <property name="src.dir" value="${basedir}/src/main"/>
+ <property name="build.dir" value="${basedir}/build"/>
+ <property name="build.classes.dir" value="${build.dir}/classes"/>
+ <property name="test.src.dir" value="${basedir}/src/test"/>
+ <property name="test.build.dir" value="${basedir}/build/test-classes"/>
+ <property name="test.reports.dir" value = "${basedir}/build/test-output"/>
+
+ <property name="run.dir" value="${build.classes.dir}"/>
+
+ <!-- Build classpath -->
+ <path id="classpath">
+ <pathelement location="${build.classes.dir}"/>
+ </path>
+
+ <property name="build.classpath" refid="classpath"/>
+
+ <path id="test.compile.classpath">
+ <pathelement location="${build.classes.dir}"/>
+ <pathelement location="${lib.dir}/junit.jar"/>
+ </path>
+
+ <property name="test.compile.classpath" refid="test.compile.classpath"/>
+
+ <path id="test.classpath">
+ <pathelement location="${test.build.dir}"/>
+ <pathelement location="${lib.dir}/junit.jar"/>
+ <pathelement location="${build.classes.dir}"/>
+ </path>
+
+ <property name="test.classpath" refid="test.classpath"/>
+
+ <!-- =================================================================== -->
+ <!-- Prepares the build directory -->
+ <!-- =================================================================== -->
+ <target name="prepare" >
+ <mkdir dir="${build.dir}"/>
+ <mkdir dir="${build.classes.dir}"/>
+ <mkdir dir="${test.build.dir}"/>
+ <mkdir dir="${test.reports.dir}"/>
+ </target>
+
+ <!-- =================================================================== -->
+ <!-- Compiles the source code -->
+ <!-- =================================================================== -->
+ <target name="compile" depends="prepare">
+ <javac srcdir="${src.dir}"
+ destdir="${build.classes.dir}"
+ debug="on"
+ deprecation="on"
+ optimize="off"
+ includes="**">
+ <classpath refid="classpath"/>
+ </javac>
+ </target>
+
+ <target name="compile14" depends="prepare">
+ <javac srcdir="${src.dir}"
+ destdir="${build.classes.dir}"
+ debug="on"
+ deprecation="on"
+ source="1.4"
+ target="1.4"
+ optimize="off"
+ includes="**">
+ <classpath refid="classpath"/>
+ </javac>
+ </target>
+
+ <target name="test-compile" depends="compile">
+ <javac srcdir="${test.src.dir}"
+ destdir="${test.build.dir}"
+ debug="on"
+ deprecation="on"
+ optimize="off"
+ includes="**">
+ <classpath refid="test.compile.classpath"/>
+ </javac>
+ </target>
+
+ <target name="test" depends="test-compile">
+ <junit fork="true" printsummary="true">
+ <classpath refid="test.classpath"/>
+ <formatter type="plain"/>
+ <formatter type="xml"/>
+ <batchtest todir="${test.reports.dir}">
+ <fileset dir="${test.build.dir}">
+ <include name="**/*Test.*"/>
+ </fileset>
+ </batchtest>
+ </junit>
+ </target>
+
+ <target name="sample" depends="compile">
+ <javac srcdir="${basedir}"
+ destdir="${build.classes.dir}"
+ debug="on"
+ deprecation="on"
+ optimize="off"
+ includes="sample/**"
+ excludes="sample/hotswap/**,sample/evolve/sample/**">
+ <classpath refid="classpath"/>
+ </javac>
+
+ <copy file="sample/vector/Test.j"
+ todir="${build.classes.dir}/sample/vector"/>
+
+ <javac srcdir="${basedir}/sample/evolve"
+ destdir="${build.classes.dir}/sample/evolve/"
+ debug="on"
+ deprecation="on"
+ optimize="off"
+ includes="sample/**">
+ <classpath refid="classpath"/>
+ </javac>
+ <copy todir="${build.classes.dir}/sample/evolve">
+ <fileset dir="sample/evolve"/>
+ </copy>
+ <copy file="${build.classes.dir}/sample/evolve/WebPage.class"
+ tofile="${build.classes.dir}/sample/evolve/WebPage.class.0"/>
+ <copy file="${build.classes.dir}/sample/evolve/sample/evolve/WebPage.class"
+ tofile="${build.classes.dir}/sample/evolve/WebPage.class.1"/>
+
+ <javac srcdir="${basedir}/sample/hotswap"
+ destdir="${build.classes.dir}"
+ debug="on"
+ deprecation="on"
+ optimize="off"
+ includes="*">
+ <classpath refid="classpath"/>
+ </javac>
+ <mkdir dir="${build.classes.dir}/logging"/>
+ <javac srcdir="${basedir}/sample/hotswap/logging"
+ destdir="${build.classes.dir}/logging"
+ debug="on"
+ deprecation="on"
+ optimize="off"
+ includes="*">
+ <classpath refid="classpath"/>
+ </javac>
+ <echo>To run the sample programs without ant, change the current directory
+to ${build.classes.dir}.</echo>
+ </target>
+
+ <target name="jar" depends="compile14">
+ <jar jarfile="${target.jar}" manifest="${src.dir}/META-INF/MANIFEST.MF">
+ <fileset dir="${build.classes.dir}">
+ <include name="**/*.class"/>
+ </fileset>
+ </jar>
+ <jar jarfile="${target-src.jar}" manifest="${src.dir}/META-INF/MANIFEST.MF">
+ <fileset dir="${src.dir}">
+ <include name="javassist/**"/>
+ </fileset>
+ </jar>
+ </target>
+
+ <target name="javadocs">
+ <mkdir dir="html"/>
+ <javadoc
+ Locale="en_US"
+ packagenames="javassist.*"
+ excludepackagenames="javassist.compiler.*,javassist.convert.*,javassist.scopedpool.*,javassist.bytecode.stackmap.*"
+ sourcepath="src/main"
+ defaultexcludes="yes"
+ destdir="html"
+ author="true"
+ version="true"
+ use="true"
+ public="true"
+ nohelp="true"
+ windowtitle="Javassist API">
+ <doctitle><![CDATA[<h1>Javassist</h1>]]></doctitle>
+ <bottom><![CDATA[<i>Javassist, a Java-bytecode translator toolkit.<br>
+Copyright (C) 1999-2010 Shigeru Chiba. All Rights Reserved.</i>]]></bottom>
+ </javadoc>
+ </target>
+
+ <target name="dist" depends="jar,javadocs">
+ <delete file="${dist-version}.zip"/>
+ <zip zipfile="${dist-version}.zip">
+ <zipfileset dir="${basedir}" prefix="${dist-version}">
+ <include name="html/**"/>
+ <include name="sample/**"/>
+ <include name="src/main/**"/>
+ <include name="tutorial/**"/>
+ <include name="*.html"/>
+ <include name="*.xml"/>
+ <include name="${target.jar}"/>
+ </zipfileset>
+ </zip>
+ </target>
+
+ <target name="clean">
+ <delete dir="build"/>
+ <delete dir="html"/>
+ <delete file="${target.jar}"/>
+ <delete file="${dist-version}.zip"/>
+ </target>
+
+ <!-- =================================================================== -->
+ <!-- Run samples -->
+ <!-- =================================================================== -->
+
+ <target name = "sample-all"
+ depends="sample-test,sample-reflect,sample-duplicate,sample-vector">
+ <echo>** please run sample-rmi, sample-evolve, and</echo>
+ <echo> sample-hotswap (or -hotswap5) separately **</echo>
+ </target>
+
+ <target name = "sample-test" depends="sample" >
+ <java fork="true" dir="${run.dir}" classname="sample.Test">
+ <classpath refid="classpath"/>
+ </java>
+ </target>
+
+ <target name = "sample-reflect" depends="sample" >
+ <java fork="true" dir="${run.dir}" classname="javassist.tools.reflect.Loader">
+ <classpath refid="classpath"/>
+ <arg line="sample.reflect.Main Joe" />
+ </java>
+ </target>
+
+ <target name = "sample-duplicate" depends="sample" >
+ <echo>run sample.duplicate.Viewer without reflection</echo>
+ <java fork="true" dir="${run.dir}" classname="sample.duplicate.Viewer">
+ <classpath refid="classpath"/>
+ </java>
+ <echo>run sample.duplicate.Viewer with reflection</echo>
+ <java fork="true" dir="${run.dir}" classname="sample.duplicate.Main">
+ <classpath refid="classpath"/>
+ </java>
+ </target>
+
+ <target name = "sample-vector" depends="sample" >
+ <echo>sample.preproc.Compiler sample/vector/Test.j</echo>
+ <java fork="true" dir="${run.dir}" classname="sample.preproc.Compiler">
+ <classpath refid="classpath"/>
+ <arg line="sample/vector/Test.j"/>
+ </java>
+ <echo>javac sample/vector/Test.java</echo>
+ <javac srcdir="${build.classes.dir}"
+ destdir="${build.classes.dir}"
+ includes="sample/vector/Test.java">
+ <classpath refid="classpath"/>
+ </javac>
+ <java fork="true" dir="${run.dir}" classname="sample.vector.Test" />
+ </target>
+
+ <target name = "sample-rmi" depends="sample" >
+ <echo>** Please open sample/rmi/webdemo.html with your browser **</echo>
+ <java fork="true" dir="${run.dir}" classname="sample.rmi.Counter">
+ <classpath refid="classpath"/>
+ <arg value="5001" />
+ </java>
+ </target>
+
+ <target name = "sample-evolve" depends="sample" >
+ <echo>** Please open http://localhost:5003/demo.html with your browser **</echo>
+ <java fork="true" dir="${run.dir}" classname="sample.evolve.DemoLoader">
+ <classpath refid="classpath"/>
+ <arg value="5003" />
+ </java>
+ </target>
+
+ <!-- for JDK 1.4 -->
+ <target name = "sample-hotswap" depends="sample">
+ <echo>** JAVA_HOME/lib/tools.jar must be included in CLASS_PATH</echo>
+ <echo>** for JDK 1.4</echo>
+ <java fork="true" dir="${run.dir}" classname="Test">
+ <jvmarg line="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000" />
+ <classpath refid="classpath"/>
+ </java>
+ </target>
+
+ <!-- for Java 5 -->
+ <target name = "sample-hotswap5" depends="sample">
+ <echo>** JAVA_HOME/lib/tools.jar must be included in CLASS_PATH</echo>
+ <echo>** for JDK 1.5 or later</echo>
+ <java fork="true" dir="${run.dir}" classname="Test">
+ <jvmarg line="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000" />
+ <classpath refid="classpath"/>
+ </java>
+ </target>
+</project>
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..0b8c335
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,262 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.javassist</groupId>
+ <artifactId>javassist</artifactId>
+ <packaging>jar</packaging>
+ <description>Javassist (JAVA programming ASSISTant) makes Java bytecode manipulation
+ simple. It is a class library for editing bytecodes in Java.
+ </description>
+ <version>3.14.0-GA</version>
+ <name>Javassist</name>
+ <url>http://www.javassist.org/</url>
+
+ <issueManagement>
+ <system>JIRA</system>
+ <url>https://jira.jboss.org/jira/browse/JASSIST/</url>
+ </issueManagement>
+ <licenses>
+ <!-- this is the license under which javassist is usually distributed
+ -->
+ <license>
+ <name>MPL 1.1</name>
+ <url>http://www.mozilla.org/MPL/MPL-1.1.html</url>
+ </license>
+ <!-- this is the license under which javassist is distributed when
+ it is bundled with JBoss
+ -->
+ <license>
+ <name>LGPL 2.1</name>
+ <url>http://www.gnu.org/licenses/lgpl-2.1.html</url>
+ </license>
+ </licenses>
+
+ <scm>
+ <connection>scm:svn:http://anonsvn.jboss.org/repos/javassist/</connection>
+ <developerConnection>scm:svn:https://svn.jboss.org/repos/javassist/</developerConnection>
+ <url>http://fisheye.jboss.org/browse/javassist/</url>
+ </scm>
+
+ <developers>
+ <developer>
+ <id>chiba</id>
+ <name>Shigeru Chiba</name>
+ <email>chiba@acm.org</email>
+ <organization>Tokyo Institute of Technology</organization>
+ <organizationUrl>http://www.javassist.org/</organizationUrl>
+ <roles>
+ <role>project lead</role>
+ </roles>
+ <timezone>8</timezone>
+ </developer>
+
+ <developer>
+ <id>adinn</id>
+ <name>Andrew Dinn</name>
+ <email>adinn@redhat.com</email>
+ <organization>JBoss</organization>
+ <organizationUrl>http://www.jboss.org/</organizationUrl>
+ <roles>
+ <role>contributing developer</role>
+ </roles>
+ <timezone>0</timezone>
+ </developer>
+
+ <developer>
+ <id>kabir.khan@jboss.com</id>
+ <name>Kabir Khan</name>
+ <email>kabir.khan@jboss.com</email>
+ <organization>JBoss</organization>
+ <organizationUrl>http://www.jboss.org/</organizationUrl>
+ <roles>
+ <role>contributing developer</role>
+ </roles>
+ <timezone>0</timezone>
+ </developer>
+ </developers>
+
+ <distributionManagement>
+ <!--
+ You need entries in your .m2/settings.xml like this:
+ <servers>
+ <server>
+ <id>jboss-releases-repository</id>
+ <username>your_jboss.org_username</username>
+ <password>password</password>
+ </server>
+ <server>
+ <id>jboss-snapshots-repository</id>
+ <username>your_jboss.org_username</username>
+ <password>password</password>
+ </server>
+ </servers>
+
+ To deploy a snapshot, you need to run
+
+ mvn deploy -Dversion=3.x.y-SNAPSHOT
+
+ To deploy a release you need to change the version to 3.x.y.GA and run
+
+ mvn deploy
+ -->
+ <repository>
+ <id>jboss-releases-repository</id>
+ <name>JBoss Releases Repository</name>
+ <url>https://repository.jboss.org/nexus/service/local/staging/deploy/maven2/</url>
+ </repository>
+ <snapshotRepository>
+ <id>jboss-snapshots-repository</id>
+ <name>JBoss Snapshots Repository</name>
+ <url>https://repository.jboss.org/nexus/content/repositories/snapshots/</url>
+ </snapshotRepository>
+ </distributionManagement>
+ <build>
+ <sourceDirectory>src/main/</sourceDirectory>
+ <testSourceDirectory>src/test/</testSourceDirectory>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.4</source>
+ <target>1.4</target>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <configuration>
+ <archive>
+ <manifestFile>${project.build.sourceDirectory}/META-INF/MANIFEST.MF</manifestFile>
+ </archive>
+ </configuration>
+ </plugin>
+ <plugin>
+ <artifactId>maven-source-plugin</artifactId>
+ <version>2.0.3</version>
+ <executions>
+ <execution>
+ <id>attach-sources</id>
+ <goals>
+ <goal>jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ <inherited>true</inherited>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <version>2.7</version>
+ <configuration>
+ <attach>true</attach>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <profiles>
+ <!-- profile for releasing to sonatype repo
+ exercise with mvn -PcentralRelease
+ -->
+ <profile>
+ <id>centralRelease</id>
+ <!-- obviously we need to use the Sonatype staging repo for upload -->
+ <distributionManagement>
+ <repository>
+ <id>sonatype-releases-repository</id>
+ <name>Sonatype Releases Repository</name>
+ <url>https://oss.sonatype.org/service/local/staging/deploy/maven2</url>
+ </repository>
+ </distributionManagement>
+ <!-- we need to be able to sign the jars we install -->
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-gpg-plugin</artifactId>
+ <configuration>
+ <passphrase>${gpg.passphrase}</passphrase>
+ <useAgent>${gpg.useAgent}</useAgent>
+ </configuration>
+ <executions>
+ <execution>
+ <id>sign-artifacts</id>
+ <phase>verify</phase>
+ <goals>
+ <goal>sign</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ <!-- profiles to add tools jar containing com.sun.jdi code
+ needed by sample code
+ -->
+ <profile>
+ <id>jdk14</id>
+ <activation>
+ <jdk>1.4</jdk>
+ <property>
+ <name>!no.tools</name>
+ </property>
+ </activation>
+ <dependencies>
+ <dependency>
+ <groupId>com.sun</groupId>
+ <artifactId>tools</artifactId>
+ <version>1.4</version>
+ <scope>system</scope>
+ <optional>true</optional>
+ <systemPath>${java.home}/../lib/tools.jar</systemPath>
+ </dependency>
+ </dependencies>
+ </profile>
+ <profile>
+ <id>jdk15</id>
+ <activation>
+ <jdk>1.5</jdk>
+ <property>
+ <name>!no.tools</name>
+ </property>
+ </activation>
+ <dependencies>
+ <dependency>
+ <groupId>com.sun</groupId>
+ <artifactId>tools</artifactId>
+ <version>1.5</version>
+ <scope>system</scope>
+ <optional>true</optional>
+ <systemPath>${java.home}/../lib/tools.jar</systemPath>
+ </dependency>
+ </dependencies>
+ </profile>
+ <profile>
+ <id>jdk16</id>
+ <activation>
+ <jdk>1.6</jdk>
+ <property>
+ <name>!no.tools</name>
+ </property>
+ </activation>
+ <dependencies>
+ <dependency>
+ <groupId>com.sun</groupId>
+ <artifactId>tools</artifactId>
+ <version>1.6</version>
+ <scope>system</scope>
+ <optional>true</optional>
+ <systemPath>${java.home}/../lib/tools.jar</systemPath>
+ </dependency>
+ </dependencies>
+ </profile>
+ </profiles>
+ <dependencies>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>3.8.1</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/regenerate_from_source.sh b/regenerate_from_source.sh
new file mode 100644
index 0000000..2f744e1
--- /dev/null
+++ b/regenerate_from_source.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+#
+# Copyright (C) 2011 The Android Open Source Project.
+# This script imports the src, test, etc. from javassist. It DOESN'T commit
+# to git giving you a chance to review the changes. Remember that changes in
+# bin are normally ignored by git, but we need to force them this case.
+#
+# This script doesn't take any parameter.
+
+svn export --force http://anonsvn.jboss.org/repos/javassist/trunk .
+rm lib/junit.jar
+# I don't check if there is something on lib on purpose; that way, if
+# anything new is added, we will get a visible error.
+rmdir lib
diff --git a/sample/Test.java b/sample/Test.java
new file mode 100644
index 0000000..1e2b49c
--- /dev/null
+++ b/sample/Test.java
@@ -0,0 +1,44 @@
+package sample;
+
+import javassist.*;
+
+/*
+ A very simple sample program
+
+ This program overwrites sample/Test.class (the class file of this
+ class itself) for adding a method g(). If the method g() is not
+ defined in class Test, then this program adds a copy of
+ f() to the class Test with name g(). Otherwise, this program does
+ not modify sample/Test.class at all.
+
+ To see the modified class definition, execute:
+
+ % javap sample.Test
+
+ after running this program.
+*/
+public class Test {
+ public int f(int i) {
+ return i + 1;
+ }
+
+ public static void main(String[] args) throws Exception {
+ ClassPool pool = ClassPool.getDefault();
+
+ CtClass cc = pool.get("sample.Test");
+ try {
+ cc.getDeclaredMethod("g");
+ System.out.println("g() is already defined in sample.Test.");
+ }
+ catch (NotFoundException e) {
+ /* getDeclaredMethod() throws an exception if g()
+ * is not defined in sample.Test.
+ */
+ CtMethod fMethod = cc.getDeclaredMethod("f");
+ CtMethod gMethod = CtNewMethod.copy(fMethod, "g", cc, null);
+ cc.addMethod(gMethod);
+ cc.writeFile(); // update the class file
+ System.out.println("g() was added.");
+ }
+ }
+}
diff --git a/sample/duplicate/Ball.java b/sample/duplicate/Ball.java
new file mode 100644
index 0000000..21d6e1c
--- /dev/null
+++ b/sample/duplicate/Ball.java
@@ -0,0 +1,44 @@
+package sample.duplicate;
+
+import java.awt.Graphics;
+import java.awt.Color;
+
+public class Ball {
+ private int x, y;
+ private Color color;
+ private int radius = 30;
+ private boolean isBackup = false;
+
+ public Ball(int x, int y) {
+ move(x, y);
+ changeColor(Color.orange);
+ }
+
+ // This constructor is for a backup object.
+ public Ball(Ball b) {
+ isBackup = true;
+ }
+
+ // Adjust the position so that the backup object is visible.
+ private void adjust() {
+ if (isBackup) {
+ this.x += 50;
+ this.y += 50;
+ }
+ }
+
+ public void paint(Graphics g) {
+ g.setColor(color);
+ g.fillOval(x, y, radius, radius);
+ }
+
+ public void move(int x, int y) {
+ this.x = x;
+ this.y = y;
+ adjust();
+ }
+
+ public void changeColor(Color color) {
+ this.color = color;
+ }
+}
diff --git a/sample/duplicate/DuplicatedObject.java b/sample/duplicate/DuplicatedObject.java
new file mode 100644
index 0000000..7161493
--- /dev/null
+++ b/sample/duplicate/DuplicatedObject.java
@@ -0,0 +1,38 @@
+package sample.duplicate;
+
+import javassist.tools.reflect.*;
+
+public class DuplicatedObject extends Metaobject {
+ private DuplicatedObject backup;
+
+ // if a base-level object is created, this metaobject creates
+ // a copy of the base-level object.
+
+ public DuplicatedObject(Object self, Object[] args) {
+ super(self, args);
+ ClassMetaobject clazz = getClassMetaobject();
+ if (clazz.isInstance(args[0]))
+ backup = null; // self is a backup object.
+ else {
+ Object[] args2 = new Object[1];
+ args2[0] = self;
+ try {
+ Metalevel m = (Metalevel)clazz.newInstance(args2);
+ backup = (DuplicatedObject)m._getMetaobject();
+ }
+ catch (CannotCreateException e) {
+ backup = null;
+ }
+ }
+ }
+
+ public Object trapMethodcall(int identifier, Object[] args)
+ throws Throwable
+ {
+ Object obj = super.trapMethodcall(identifier, args);
+ if (backup != null)
+ backup.trapMethodcall(identifier, args);
+
+ return obj;
+ }
+}
diff --git a/sample/duplicate/Main.java b/sample/duplicate/Main.java
new file mode 100644
index 0000000..064f13c
--- /dev/null
+++ b/sample/duplicate/Main.java
@@ -0,0 +1,44 @@
+package sample.duplicate;
+
+/*
+ Runtime metaobject (JDK 1.2 or later only).
+
+ With the javassist.tools.reflect package, the users can attach a metaobject
+ to an object. The metaobject can control the behavior of the object.
+ For example, you can implement fault tolerancy with this ability. One
+ of the implementation techniques of fault tolernacy is to make a copy
+ of every object containing important data and maintain it as a backup.
+ If the machine running the object becomes down, the backup object on a
+ different machine is used to continue the execution.
+
+ To make the copy of the object a real backup, all the method calls to
+ the object must be also sent to that copy. The metaobject is needed
+ for this duplication of the method calls. It traps every method call
+ and invoke the same method on the copy of the object so that the
+ internal state of the copy is kept equivalent to that of the original
+ object.
+
+ First, run sample.duplicate.Viewer without a metaobject.
+
+ % java sample.duplicate.Viewer
+
+ This program shows a ball in a window.
+
+ Then, run the same program with a metaobject, which is an instance
+ of sample.duplicate.DuplicatedObject.
+
+ % java sample.duplicate.Main
+
+ You would see two balls in a window. This is because
+ sample.duplicate.Viewer is loaded by javassist.tools.reflect.Loader so that
+ a metaobject would be attached.
+*/
+public class Main {
+ public static void main(String[] args) throws Throwable {
+ javassist.tools.reflect.Loader cl = new javassist.tools.reflect.Loader();
+ cl.makeReflective("sample.duplicate.Ball",
+ "sample.duplicate.DuplicatedObject",
+ "javassist.tools.reflect.ClassMetaobject");
+ cl.run("sample.duplicate.Viewer", args);
+ }
+}
diff --git a/sample/duplicate/Viewer.java b/sample/duplicate/Viewer.java
new file mode 100644
index 0000000..aec13f6
--- /dev/null
+++ b/sample/duplicate/Viewer.java
@@ -0,0 +1,78 @@
+package sample.duplicate;
+
+import java.applet.*;
+import java.awt.*;
+import java.awt.event.*;
+
+public class Viewer extends Applet
+ implements MouseListener, ActionListener, WindowListener
+{
+ private static final Color[] colorList = {
+ Color.orange, Color.pink, Color.green, Color.blue };
+
+ private Ball ball;
+ private int colorNo;
+
+ public void init() {
+ colorNo = 0;
+ Button b = new Button("change");
+ b.addActionListener(this);
+ add(b);
+
+ addMouseListener(this);
+ }
+
+ public void start() {
+ ball = new Ball(50, 50);
+ ball.changeColor(colorList[0]);
+ }
+
+ public void paint(Graphics g) {
+ ball.paint(g);
+ }
+
+ public void mouseClicked(MouseEvent ev) {
+ ball.move(ev.getX(), ev.getY());
+ repaint();
+ }
+
+ public void mouseEntered(MouseEvent ev) {}
+
+ public void mouseExited(MouseEvent ev) {}
+
+ public void mousePressed(MouseEvent ev) {}
+
+ public void mouseReleased(MouseEvent ev) {}
+
+ public void actionPerformed(ActionEvent e) {
+ ball.changeColor(colorList[++colorNo % colorList.length]);
+ repaint();
+ }
+
+ public void windowOpened(WindowEvent e) {}
+
+ public void windowClosing(WindowEvent e) {
+ System.exit(0);
+ }
+
+ public void windowClosed(WindowEvent e) {}
+
+ public void windowIconified(WindowEvent e) {}
+
+ public void windowDeiconified(WindowEvent e) {}
+
+ public void windowActivated(WindowEvent e) {}
+
+ public void windowDeactivated(WindowEvent e) {}
+
+ public static void main(String[] args) {
+ Frame f = new Frame("Viewer");
+ Viewer view = new Viewer();
+ f.addWindowListener(view);
+ f.add(view);
+ f.setSize(300, 300);
+ view.init();
+ view.start();
+ f.setVisible(true);
+ }
+}
diff --git a/sample/evolve/CannotCreateException.java b/sample/evolve/CannotCreateException.java
new file mode 100644
index 0000000..828afb0
--- /dev/null
+++ b/sample/evolve/CannotCreateException.java
@@ -0,0 +1,14 @@
+package sample.evolve;
+
+/**
+ * Signals that VersionManager.newInstance() fails.
+ */
+public class CannotCreateException extends RuntimeException {
+ public CannotCreateException(String s) {
+ super(s);
+ }
+
+ public CannotCreateException(Exception e) {
+ super("by " + e.toString());
+ }
+}
diff --git a/sample/evolve/CannotUpdateException.java b/sample/evolve/CannotUpdateException.java
new file mode 100644
index 0000000..bd28bba
--- /dev/null
+++ b/sample/evolve/CannotUpdateException.java
@@ -0,0 +1,14 @@
+package sample.evolve;
+
+/**
+ * Signals that VersionManager.update() fails.
+ */
+public class CannotUpdateException extends Exception {
+ public CannotUpdateException(String msg) {
+ super(msg);
+ }
+
+ public CannotUpdateException(Exception e) {
+ super("by " + e.toString());
+ }
+}
diff --git a/sample/evolve/DemoLoader.java b/sample/evolve/DemoLoader.java
new file mode 100644
index 0000000..7c770bd
--- /dev/null
+++ b/sample/evolve/DemoLoader.java
@@ -0,0 +1,38 @@
+package sample.evolve;
+
+import javassist.*;
+
+/**
+ * DemoLoader is a class loader for running a program including
+ * an updatable class. This simple loader allows only a single
+ * class to be updatable. (Extending it for supporting multiple
+ * updatable classes is easy.)
+ *
+ * To run, type as follows:
+ *
+ * % java sample.evolve.DemoLoader <port number>
+ *
+ * Then DemoLoader launches sample.evolve.DemoServer with <port number>.
+ * It also translates sample.evolve.WebPage, which sample.evolve.DemoServer
+ * uses, so that it is an updable class.
+ *
+ * Note: JDK 1.2 or later only.
+ */
+public class DemoLoader {
+ private static final int initialVersion = 0;
+ private String updatableClassName = null;
+ private CtClass updatableClass = null;
+
+ /* Creates a <code>DemoLoader</code> for making class WebPage
+ * updatable. Then it runs main() in sample.evolve.DemoServer.
+ */
+ public static void main(String[] args) throws Throwable {
+ Evolution translator = new Evolution();
+ ClassPool cp = ClassPool.getDefault();
+ Loader cl = new Loader();
+ cl.addTranslator(cp, translator);
+
+ translator.makeUpdatable("sample.evolve.WebPage");
+ cl.run("sample.evolve.DemoServer", args);
+ }
+}
diff --git a/sample/evolve/DemoServer.java b/sample/evolve/DemoServer.java
new file mode 100644
index 0000000..b334bcc
--- /dev/null
+++ b/sample/evolve/DemoServer.java
@@ -0,0 +1,102 @@
+package sample.evolve;
+
+import javassist.tools.web.*;
+import java.io.*;
+
+/**
+ * A web server for demonstrating class evolution. It must be
+ * run with a DemoLoader.
+ *
+ * If a html file /java.html is requested, this web server calls
+ * WebPage.show() for constructing the contents of that html file
+ * So if a DemoLoader changes the definition of WebPage, then
+ * the image of /java.html is also changed.
+ * Note that WebPage is not an applet. It is rather
+ * similar to a CGI script or a servlet. The web server never
+ * sends the class file of WebPage to web browsers.
+ *
+ * Furthermore, if a html file /update.html is requested, this web
+ * server overwrites WebPage.class (class file) and calls update()
+ * in VersionManager so that WebPage.class is loaded into the JVM
+ * again. The new contents of WebPage.class are copied from
+ * either sample/evolve/WebPage.class
+ * or sample/evolve/sample/evolve/WebPage.class.
+ */
+public class DemoServer extends Webserver {
+
+ public static void main(String[] args) throws IOException
+ {
+ if (args.length == 1) {
+ DemoServer web = new DemoServer(Integer.parseInt(args[0]));
+ web.run();
+ }
+ else
+ System.err.println(
+ "Usage: java sample.evolve.DemoServer <port number>");
+ }
+
+ public DemoServer(int port) throws IOException {
+ super(port);
+ htmlfileBase = "sample/evolve/";
+ }
+
+ private static final String ver0 = "sample/evolve/WebPage.class.0";
+ private static final String ver1 = "sample/evolve/WebPage.class.1";
+ private String currentVersion = ver0;
+
+ public void doReply(InputStream in, OutputStream out, String cmd)
+ throws IOException, BadHttpRequest
+ {
+ if (cmd.startsWith("GET /java.html ")) {
+ runJava(out);
+ return;
+ }
+ else if (cmd.startsWith("GET /update.html ")) {
+ try {
+ if (currentVersion == ver0)
+ currentVersion = ver1;
+ else
+ currentVersion = ver0;
+
+ updateClassfile(currentVersion);
+ VersionManager.update("sample.evolve.WebPage");
+ }
+ catch (CannotUpdateException e) {
+ logging(e.toString());
+ }
+ catch (FileNotFoundException e) {
+ logging(e.toString());
+ }
+ }
+
+ super.doReply(in, out, cmd);
+ }
+
+ private void runJava(OutputStream outs) throws IOException {
+ OutputStreamWriter out = new OutputStreamWriter(outs);
+ out.write("HTTP/1.0 200 OK\r\n\r\n");
+ WebPage page = new WebPage();
+ page.show(out);
+ out.close();
+ }
+
+ /* updateClassfile() copies the specified file to WebPage.class.
+ */
+ private void updateClassfile(String filename)
+ throws IOException, FileNotFoundException
+ {
+ byte[] buf = new byte[1024];
+
+ FileInputStream fin
+ = new FileInputStream(filename);
+ FileOutputStream fout
+ = new FileOutputStream("sample/evolve/WebPage.class");
+ for (;;) {
+ int len = fin.read(buf);
+ if (len >= 0)
+ fout.write(buf, 0, len);
+ else
+ break;
+ }
+ }
+}
diff --git a/sample/evolve/Evolution.java b/sample/evolve/Evolution.java
new file mode 100644
index 0000000..e804ff4
--- /dev/null
+++ b/sample/evolve/Evolution.java
@@ -0,0 +1,189 @@
+package sample.evolve;
+
+import javassist.*;
+
+/**
+ * Evolution provides a set of methods for instrumenting bytecodes.
+ *
+ * For class evolution, updatable class A is renamed to B. Then an abstract
+ * class named A is produced as the super class of B. If the original class A
+ * has a public method m(), then the abstract class A has an abstract method
+ * m().
+ *
+ * abstract class A abstract m() _makeInstance() | class A --------> class B m()
+ * m()
+ *
+ * Also, all the other classes are translated so that "new A(i)" in the methods
+ * is replaced with "_makeInstance(i)". This makes it possible to change the
+ * behavior of the instantiation of the class A.
+ */
+public class Evolution implements Translator {
+ public final static String handlerMethod = "_makeInstance";
+
+ public final static String latestVersionField = VersionManager.latestVersionField;
+
+ public final static String versionManagerMethod = "initialVersion";
+
+ private static CtMethod trapMethod;
+
+ private static final int initialVersion = 0;
+
+ private ClassPool pool;
+
+ private String updatableClassName = null;
+
+ private CtClass updatableClass = null;
+
+ public void start(ClassPool _pool) throws NotFoundException {
+ pool = _pool;
+
+ // Get the definition of Sample.make() and store it into trapMethod
+ // for later use.
+ trapMethod = _pool.getMethod("sample.evolve.Sample", "make");
+ }
+
+ public void onLoad(ClassPool _pool, String classname)
+ throws NotFoundException, CannotCompileException {
+ onLoadUpdatable(classname);
+
+ /*
+ * Replaces all the occurrences of the new operator with a call to
+ * _makeInstance().
+ */
+ CtClass clazz = _pool.get(classname);
+ CtClass absClass = updatableClass;
+ CodeConverter converter = new CodeConverter();
+ converter.replaceNew(absClass, absClass, handlerMethod);
+ clazz.instrument(converter);
+ }
+
+ private void onLoadUpdatable(String classname) throws NotFoundException,
+ CannotCompileException {
+ // if the class is a concrete class,
+ // classname is <updatableClassName>$$<version>.
+
+ int i = classname.lastIndexOf("$$");
+ if (i <= 0)
+ return;
+
+ String orgname = classname.substring(0, i);
+ if (!orgname.equals(updatableClassName))
+ return;
+
+ int version;
+ try {
+ version = Integer.parseInt(classname.substring(i + 2));
+ }
+ catch (NumberFormatException e) {
+ throw new NotFoundException(classname, e);
+ }
+
+ CtClass clazz = pool.getAndRename(orgname, classname);
+ makeConcreteClass(clazz, updatableClass, version);
+ }
+
+ /*
+ * Register an updatable class.
+ */
+ public void makeUpdatable(String classname) throws NotFoundException,
+ CannotCompileException {
+ if (pool == null)
+ throw new RuntimeException(
+ "Evolution has not been linked to ClassPool.");
+
+ CtClass c = pool.get(classname);
+ updatableClassName = classname;
+ updatableClass = makeAbstractClass(c);
+ }
+
+ /**
+ * Produces an abstract class.
+ */
+ protected CtClass makeAbstractClass(CtClass clazz)
+ throws CannotCompileException, NotFoundException {
+ int i;
+
+ CtClass absClass = pool.makeClass(clazz.getName());
+ absClass.setModifiers(Modifier.PUBLIC | Modifier.ABSTRACT);
+ absClass.setSuperclass(clazz.getSuperclass());
+ absClass.setInterfaces(clazz.getInterfaces());
+
+ // absClass.inheritAllConstructors();
+
+ CtField fld = new CtField(pool.get("java.lang.Class"),
+ latestVersionField, absClass);
+ fld.setModifiers(Modifier.PUBLIC | Modifier.STATIC);
+
+ CtField.Initializer finit = CtField.Initializer.byCall(pool
+ .get("sample.evolve.VersionManager"), versionManagerMethod,
+ new String[] { clazz.getName() });
+ absClass.addField(fld, finit);
+
+ CtField[] fs = clazz.getDeclaredFields();
+ for (i = 0; i < fs.length; ++i) {
+ CtField f = fs[i];
+ if (Modifier.isPublic(f.getModifiers()))
+ absClass.addField(new CtField(f.getType(), f.getName(),
+ absClass));
+ }
+
+ CtConstructor[] cs = clazz.getDeclaredConstructors();
+ for (i = 0; i < cs.length; ++i) {
+ CtConstructor c = cs[i];
+ int mod = c.getModifiers();
+ if (Modifier.isPublic(mod)) {
+ CtMethod wm = CtNewMethod.wrapped(absClass, handlerMethod, c
+ .getParameterTypes(), c.getExceptionTypes(),
+ trapMethod, null, absClass);
+ wm.setModifiers(Modifier.PUBLIC | Modifier.STATIC);
+ absClass.addMethod(wm);
+ }
+ }
+
+ CtMethod[] ms = clazz.getDeclaredMethods();
+ for (i = 0; i < ms.length; ++i) {
+ CtMethod m = ms[i];
+ int mod = m.getModifiers();
+ if (Modifier.isPublic(mod))
+ if (Modifier.isStatic(mod))
+ throw new CannotCompileException(
+ "static methods are not supported.");
+ else {
+ CtMethod m2 = CtNewMethod.abstractMethod(m.getReturnType(),
+ m.getName(), m.getParameterTypes(), m
+ .getExceptionTypes(), absClass);
+ absClass.addMethod(m2);
+ }
+ }
+
+ return absClass;
+ }
+
+ /**
+ * Modifies the given class file so that it is a subclass of the abstract
+ * class produced by makeAbstractClass().
+ *
+ * Note: the naming convention must be consistent with
+ * VersionManager.update().
+ */
+ protected void makeConcreteClass(CtClass clazz, CtClass abstractClass,
+ int version) throws CannotCompileException, NotFoundException {
+ int i;
+ clazz.setSuperclass(abstractClass);
+ CodeConverter converter = new CodeConverter();
+ CtField[] fs = clazz.getDeclaredFields();
+ for (i = 0; i < fs.length; ++i) {
+ CtField f = fs[i];
+ if (Modifier.isPublic(f.getModifiers()))
+ converter.redirectFieldAccess(f, abstractClass, f.getName());
+ }
+
+ CtConstructor[] cs = clazz.getDeclaredConstructors();
+ for (i = 0; i < cs.length; ++i)
+ cs[i].instrument(converter);
+
+ CtMethod[] ms = clazz.getDeclaredMethods();
+ for (i = 0; i < ms.length; ++i)
+ ms[i].instrument(converter);
+ }
+}
diff --git a/sample/evolve/Sample.java b/sample/evolve/Sample.java
new file mode 100644
index 0000000..c147c96
--- /dev/null
+++ b/sample/evolve/Sample.java
@@ -0,0 +1,12 @@
+package sample.evolve;
+
+/**
+ * This is a sample class used by Transformer.
+ */
+public class Sample {
+ public static Class _version;
+
+ public static Object make(Object[] args) {
+ return VersionManager.make(_version, args);
+ }
+}
diff --git a/sample/evolve/VersionManager.java b/sample/evolve/VersionManager.java
new file mode 100644
index 0000000..efecee1
--- /dev/null
+++ b/sample/evolve/VersionManager.java
@@ -0,0 +1,90 @@
+package sample.evolve;
+
+import java.util.Hashtable;
+import java.lang.reflect.*;
+
+/**
+ * Runtime system for class evolution
+ */
+public class VersionManager {
+ private static Hashtable versionNo = new Hashtable();
+
+ public final static String latestVersionField = "_version";
+
+ /**
+ * For updating the definition of class my.X, say:
+ *
+ * VersionManager.update("my.X");
+ */
+ public static void update(String qualifiedClassname)
+ throws CannotUpdateException {
+ try {
+ Class c = getUpdatedClass(qualifiedClassname);
+ Field f = c.getField(latestVersionField);
+ f.set(null, c);
+ }
+ catch (ClassNotFoundException e) {
+ throw new CannotUpdateException("cannot update class: "
+ + qualifiedClassname);
+ }
+ catch (Exception e) {
+ throw new CannotUpdateException(e);
+ }
+ }
+
+ private static Class getUpdatedClass(String qualifiedClassname)
+ throws ClassNotFoundException {
+ int version;
+ Object found = versionNo.get(qualifiedClassname);
+ if (found == null)
+ version = 0;
+ else
+ version = ((Integer)found).intValue() + 1;
+
+ Class c = Class.forName(qualifiedClassname + "$$" + version);
+ versionNo.put(qualifiedClassname, new Integer(version));
+ return c;
+ }
+
+ /*
+ * initiaVersion() is used to initialize the _version field of the updatable
+ * classes.
+ */
+ public static Class initialVersion(String[] params) {
+ try {
+ return getUpdatedClass(params[0]);
+ }
+ catch (ClassNotFoundException e) {
+ throw new RuntimeException("cannot initialize " + params[0]);
+ }
+ }
+
+ /**
+ * make() performs the object creation of the updatable classes. The
+ * expression "new <updatable class>" is replaced with a call to this
+ * method.
+ */
+ public static Object make(Class clazz, Object[] args) {
+ Constructor[] constructors = clazz.getConstructors();
+ int n = constructors.length;
+ for (int i = 0; i < n; ++i) {
+ try {
+ return constructors[i].newInstance(args);
+ }
+ catch (IllegalArgumentException e) {
+ // try again
+ }
+ catch (InstantiationException e) {
+ throw new CannotCreateException(e);
+ }
+ catch (IllegalAccessException e) {
+ throw new CannotCreateException(e);
+ }
+ catch (InvocationTargetException e) {
+ throw new CannotCreateException(e);
+ }
+ }
+
+ throw new CannotCreateException("no constructor matches");
+ }
+}
diff --git a/sample/evolve/WebPage.java b/sample/evolve/WebPage.java
new file mode 100644
index 0000000..7d420fe
--- /dev/null
+++ b/sample/evolve/WebPage.java
@@ -0,0 +1,17 @@
+package sample.evolve;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * Updatable class. DemoServer instantiates this class and calls
+ * show() on the created object.
+ */
+
+public class WebPage {
+ public void show(OutputStreamWriter out) throws IOException {
+ Calendar c = new GregorianCalendar();
+ out.write(c.getTime().toString());
+ out.write("<P><A HREF=\"demo.html\">Return to the home page.</A>");
+ }
+}
diff --git a/sample/evolve/demo.html b/sample/evolve/demo.html
new file mode 100644
index 0000000..3eedf3d
--- /dev/null
+++ b/sample/evolve/demo.html
@@ -0,0 +1,39 @@
+<H2>Class Evolution</H2>
+
+<P>This is a demonstration of the class evolution mechanism
+implemented with Javassist. This mechanism enables a Java program to
+reload an existing class file. Although the reloaded class file is
+not applied to exiting objects (the old class file is used for those
+objects), it is effective in newly created objects.
+
+<P>Since the reloading is transparently executed, no programming
+convention is needed. However, there are some limitations on possible
+changes of the class definition. For example, the new class definition
+must have the same set of methods as the old one. These limitations are
+necessary for keeping the type system consistent.
+
+
+<H3><a href="java.html">Run WebPage.show()</a></H3>
+
+<P>The web server creates a new <code>WebPage</code> object and
+calls <code>show()</code> on that object. This method works as
+if it is a CGI script or a servlet and you will see the html file
+produced by this method on your browser.
+
+<H3><a href="update.html">Change WebPage.class</a></H3>
+
+<P>The web server overwrites class file <code>WebPage.class</code>
+on the local disk. Then it signals that <code>WebPage.class</code>
+must be reloaded into the JVM. If you run <code>WebPage.show()</code>
+again, you will see a different page on your browser.
+
+<H3>Source files</H3>
+
+<P>Web server: <A HREF="DemoServer.java"><code>DemoServer.java</code></A>
+
+<P>WebPage: <A HREF="WebPage.java"><code>WebPage.java</code></A> and
+another <A HREF="sample/evolve/WebPage.java"><code>WebPage.java</code></A>
+
+<P>Class loader: <A HREF="DemoLoader.java"><code>DemoLoader.java</code></A>,
+ <A HREF="Evolution.java"><code>Evolution.java</code></A>, and
+ <A HREF="VersionManager.java"><code>VersionManager.java</code></A>.
diff --git a/sample/evolve/sample/evolve/WebPage.java b/sample/evolve/sample/evolve/WebPage.java
new file mode 100644
index 0000000..507b956
--- /dev/null
+++ b/sample/evolve/sample/evolve/WebPage.java
@@ -0,0 +1,20 @@
+package sample.evolve;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * Updatable class. DemoServer instantiates this class and calls
+ * show() on the created object.
+ */
+
+public class WebPage {
+ public void show(OutputStreamWriter out) throws IOException {
+ out.write("<H2>Current Time:</H2>");
+ Calendar c = new GregorianCalendar();
+ out.write("<CENTER><H3><FONT color=\"blue\">");
+ out.write(c.getTime().toString());
+ out.write("</FONT></H3></CENTER><HR>");
+ out.write("<P><A HREF=\"demo.html\">Return to the home page.</A>");
+ }
+}
diff --git a/sample/evolve/start.html b/sample/evolve/start.html
new file mode 100644
index 0000000..8ab3f94
--- /dev/null
+++ b/sample/evolve/start.html
@@ -0,0 +1,23 @@
+<h2>Instructions</h2>
+
+<p>1. Compile <code>sample/evolve/*.java</code>.
+
+<p>2. change the current directory to <code>sample/evolve</code><br>
+and compile there <code>sample/evolve/WebPage.java</code><br>
+(i.e. compile <code>sample/evolve/sample/evolve/WebPage.java</code>).
+
+<p>The two versions of <code>WebPage.class</code> are used<br>
+for changing the contents of <code>WebPage.class</code> during
+the demo.
+
+<p>3. Run the server on the local host (where your web browser is running):
+
+<ul>
+<code>% java sample.evolve.DemoLoader 5003
+</code></ul>
+
+<p>4. Click below:
+
+<ul><h2><a href="http://localhost:5003/demo.html">
+Start!
+</a></h2></ul>
diff --git a/sample/evolve/update.html b/sample/evolve/update.html
new file mode 100644
index 0000000..8255165
--- /dev/null
+++ b/sample/evolve/update.html
@@ -0,0 +1,3 @@
+<h2><code>WebPage.class</code> has been changed.</h2>
+
+<a href="demo.html">Return to the home page.</a>
diff --git a/sample/hotswap/HelloWorld.java b/sample/hotswap/HelloWorld.java
new file mode 100644
index 0000000..51d8fcd
--- /dev/null
+++ b/sample/hotswap/HelloWorld.java
@@ -0,0 +1,5 @@
+public class HelloWorld {
+ public void print() {
+ System.out.println("hello world");
+ }
+}
diff --git a/sample/hotswap/Test.java b/sample/hotswap/Test.java
new file mode 100644
index 0000000..651d218
--- /dev/null
+++ b/sample/hotswap/Test.java
@@ -0,0 +1,25 @@
+import java.io.*;
+import javassist.util.HotSwapper;
+
+public class Test {
+ public static void main(String[] args) throws Exception {
+ HotSwapper hs = new HotSwapper(8000);
+ new HelloWorld().print();
+
+ File newfile = new File("logging/HelloWorld.class");
+ byte[] bytes = new byte[(int)newfile.length()];
+ new FileInputStream(newfile).read(bytes);
+ System.out.println("** reload a logging version");
+
+ hs.reload("HelloWorld", bytes);
+ new HelloWorld().print();
+
+ newfile = new File("HelloWorld.class");
+ bytes = new byte[(int)newfile.length()];
+ new FileInputStream(newfile).read(bytes);
+ System.out.println("** reload the original version");
+
+ hs.reload("HelloWorld", bytes);
+ new HelloWorld().print();
+ }
+}
diff --git a/sample/hotswap/logging/HelloWorld.java b/sample/hotswap/logging/HelloWorld.java
new file mode 100644
index 0000000..88f0866
--- /dev/null
+++ b/sample/hotswap/logging/HelloWorld.java
@@ -0,0 +1,6 @@
+public class HelloWorld {
+ public void print() {
+ System.out.println("** HelloWorld.print()");
+ System.out.println("hello world");
+ }
+}
diff --git a/sample/preproc/Assistant.java b/sample/preproc/Assistant.java
new file mode 100644
index 0000000..81b93b2
--- /dev/null
+++ b/sample/preproc/Assistant.java
@@ -0,0 +1,53 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2005 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package sample.preproc;
+
+import javassist.CtClass;
+import javassist.CannotCompileException;
+import javassist.ClassPool;
+
+/**
+ * This is an interface for objects invoked by the
+ * Javassist preprocessor when the preprocessor encounters an annotated
+ * import declaration.
+ *
+ * @see sample.preproc.Compiler
+ */
+public interface Assistant {
+ /**
+ * Is called when the Javassist preprocessor encounters an
+ * import declaration annotated with the "by" keyword.
+ *
+ * <p>The original import declaration is replaced with new import
+ * declarations of classes returned by this method. For example,
+ * the following implementation does not change the original
+ * declaration:
+ *
+ * <ul><pre>
+ * public CtClass[] assist(ClassPool cp, String importname, String[] args) {
+ * return new CtClass[] { cp.get(importname) };
+ * }
+ * </pre></uL>
+ *
+ * @param cp class pool
+ * @param importname the class imported by the declaration
+ * @param args the parameters specified by the annotation
+ * @return the classes imported in the java source
+ * program produced by the preprocessor.
+ */
+ public CtClass[] assist(ClassPool cp, String importname,
+ String[] args) throws CannotCompileException;
+}
diff --git a/sample/preproc/Compiler.java b/sample/preproc/Compiler.java
new file mode 100644
index 0000000..5481c0f
--- /dev/null
+++ b/sample/preproc/Compiler.java
@@ -0,0 +1,352 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2005 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package sample.preproc;
+
+import java.io.IOException;
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.BufferedWriter;
+import java.io.FileWriter;
+import java.util.Vector;
+import javassist.CannotCompileException;
+import javassist.CtClass;
+import javassist.ClassPool;
+
+/**
+ * This is a preprocessor for Java source programs using annotated
+ * import declarations.
+ *
+ * <ul><pre>
+ * import <i>class-name</i> by <i>assistant-name</i> [(<i>arg1, arg2, ...</i>)]
+ * </pre></ul>
+ *
+ * <p>To process this annotation, run this class as follows:
+ *
+ * <ul><pre>
+ * java sample.preproc.Compiler sample.j
+ * </pre></ul>
+ *
+ * <p>This command produces <code>sample.java</code>, which only includes
+ * regular import declarations. Also, the Javassist program
+ * specified by <i>assistant-name</i> is executed so that it produces
+ * class files under the <code>./tmpjvst</code> directory. The class
+ * specified by <i>assistant-name</i> must implement
+ * <code>sample.preproc.Assistant</code>.
+ *
+ * @see sample.preproc.Assistant
+ */
+
+public class Compiler {
+ protected BufferedReader input;
+ protected BufferedWriter output;
+ protected ClassPool classPool;
+
+ /**
+ * Constructs a <code>Compiler</code> with a source file.
+ *
+ * @param inputname the name of the source file.
+ */
+ public Compiler(String inputname) throws CannotCompileException {
+ try {
+ input = new BufferedReader(new FileReader(inputname));
+ }
+ catch (IOException e) {
+ throw new CannotCompileException("cannot open: " + inputname);
+ }
+
+ String outputname = getOutputFilename(inputname);
+ if (outputname.equals(inputname))
+ throw new CannotCompileException("invalid source name: "
+ + inputname);
+
+ try {
+ output = new BufferedWriter(new FileWriter(outputname));
+ }
+ catch (IOException e) {
+ throw new CannotCompileException("cannot open: " + outputname);
+ }
+
+ classPool = ClassPool.getDefault();
+ }
+
+ /**
+ * Starts preprocessing.
+ */
+ public void process() throws IOException, CannotCompileException {
+ int c;
+ CommentSkipper reader = new CommentSkipper(input, output);
+ while ((c = reader.read()) != -1) {
+ output.write(c);
+ if (c == 'p') {
+ if (skipPackage(reader))
+ break;
+ }
+ else if (c == 'i')
+ readImport(reader);
+ else if (c != ' ' && c != '\t' && c != '\n' && c != '\r')
+ break;
+ }
+
+ while ((c = input.read()) != -1)
+ output.write(c);
+
+ input.close();
+ output.close();
+ }
+
+ private boolean skipPackage(CommentSkipper reader) throws IOException {
+ int c;
+ c = reader.read();
+ output.write(c);
+ if (c != 'a')
+ return true;
+
+ while ((c = reader.read()) != -1) {
+ output.write(c);
+ if (c == ';')
+ break;
+ }
+
+ return false;
+ }
+
+ private void readImport(CommentSkipper reader)
+ throws IOException, CannotCompileException
+ {
+ int word[] = new int[5];
+ int c;
+ for (int i = 0; i < 5; ++i) {
+ word[i] = reader.read();
+ output.write(word[i]);
+ }
+
+ if (word[0] != 'm' || word[1] != 'p' || word[2] != 'o'
+ || word[3] != 'r' || word[4] != 't')
+ return; // syntax error?
+
+ c = skipSpaces(reader, ' ');
+ StringBuffer classbuf = new StringBuffer();
+ while (c != ' ' && c != '\t' && c != '\n' && c != '\r'
+ && c != ';' && c != -1) {
+ classbuf.append((char)c);
+ c = reader.read();
+ }
+
+ String importclass = classbuf.toString();
+ c = skipSpaces(reader, c);
+ if (c == ';') {
+ output.write(importclass);
+ output.write(';');
+ return;
+ }
+ if (c != 'b')
+ syntaxError(importclass);
+
+ reader.read(); // skip 'y'
+
+ StringBuffer assistant = new StringBuffer();
+ Vector args = new Vector();
+ c = readAssistant(reader, importclass, assistant, args);
+ c = skipSpaces(reader, c);
+ if (c != ';')
+ syntaxError(importclass);
+
+ runAssistant(importclass, assistant.toString(), args);
+ }
+
+ void syntaxError(String importclass) throws CannotCompileException {
+ throw new CannotCompileException("Syntax error. Cannot import "
+ + importclass);
+ }
+
+ int readAssistant(CommentSkipper reader, String importclass,
+ StringBuffer assistant, Vector args)
+ throws IOException, CannotCompileException
+ {
+ int c = readArgument(reader, assistant);
+ c = skipSpaces(reader, c);
+ if (c == '(') {
+ do {
+ StringBuffer arg = new StringBuffer();
+ c = readArgument(reader, arg);
+ args.addElement(arg.toString());
+ c = skipSpaces(reader, c);
+ } while (c == ',');
+
+ if (c != ')')
+ syntaxError(importclass);
+
+ return reader.read();
+ }
+
+ return c;
+ }
+
+ int readArgument(CommentSkipper reader, StringBuffer buf)
+ throws IOException
+ {
+ int c = skipSpaces(reader, ' ');
+ while ('A' <= c && c <= 'Z' || 'a' <= c && c <= 'z'
+ || '0' <= c && c <= '9' || c == '.' || c == '_') {
+ buf.append((char)c);
+ c = reader.read();
+ }
+
+ return c;
+ }
+
+ int skipSpaces(CommentSkipper reader, int c) throws IOException {
+ while (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
+ if (c == '\n' || c == '\r')
+ output.write(c);
+
+ c = reader.read();
+ }
+
+ return c;
+ }
+
+ /**
+ * Is invoked if this compiler encoutenrs:
+ *
+ * <ul><pre>
+ * import <i>class name</i> by <i>assistant</i> (<i>args1</i>, <i>args2</i>, ...);
+ * </pre></ul>
+ *
+ * @param classname class name
+ * @param assistantname assistant
+ * @param argv args1, args2, ...
+ */
+ private void runAssistant(String importname, String assistantname,
+ Vector argv)
+ throws IOException, CannotCompileException
+ {
+ Class assistant;
+ Assistant a;
+ int s = argv.size();
+ String[] args = new String[s];
+ for (int i = 0; i < s; ++i)
+ args[i] = (String)argv.elementAt(i);
+
+ try {
+ assistant = Class.forName(assistantname);
+ }
+ catch (ClassNotFoundException e) {
+ throw new CannotCompileException("Cannot find " + assistantname);
+ }
+
+ try {
+ a = (Assistant)assistant.newInstance();
+ }
+ catch (Exception e) {
+ throw new CannotCompileException(e);
+ }
+
+ CtClass[] imports = a.assist(classPool, importname, args);
+ s = imports.length;
+ if (s < 1)
+ output.write(" java.lang.Object;");
+ else {
+ output.write(' ');
+ output.write(imports[0].getName());
+ output.write(';');
+ for (int i = 1; i < s; ++i) {
+ output.write(" import ");
+ output.write(imports[1].getName());
+ output.write(';');
+ }
+ }
+ }
+
+ private String getOutputFilename(String input) {
+ int i = input.lastIndexOf('.');
+ if (i < 0)
+ i = input.length();
+
+ return input.substring(0, i) + ".java";
+ }
+
+ public static void main(String[] args) {
+ if (args.length > 0)
+ try {
+ Compiler c = new Compiler(args[0]);
+ c.process();
+ }
+ catch (IOException e) {
+ System.err.println(e);
+ }
+ catch (CannotCompileException e) {
+ System.err.println(e);
+ }
+ else {
+ System.err.println("Javassist version " + CtClass.version);
+ System.err.println("No source file is specified.");
+ }
+ }
+}
+
+class CommentSkipper {
+ private BufferedReader input;
+ private BufferedWriter output;
+
+ public CommentSkipper(BufferedReader reader, BufferedWriter writer) {
+ input = reader;
+ output = writer;
+ }
+
+ public int read() throws IOException {
+ int c;
+ while ((c = input.read()) != -1)
+ if (c != '/')
+ return c;
+ else {
+ c = input.read();
+ if (c == '/')
+ skipCxxComments();
+ else if (c == '*')
+ skipCComments();
+ else
+ output.write('/');
+ }
+
+ return c;
+ }
+
+ private void skipCxxComments() throws IOException {
+ int c;
+ output.write("//");
+ while ((c = input.read()) != -1) {
+ output.write(c);
+ if (c == '\n' || c == '\r')
+ break;
+ }
+ }
+
+ private void skipCComments() throws IOException {
+ int c;
+ boolean star = false;
+ output.write("/*");
+ while ((c = input.read()) != -1) {
+ output.write(c);
+ if (c == '*')
+ star = true;
+ else if(star && c == '/')
+ break;
+ else
+ star = false;
+ }
+ }
+}
diff --git a/sample/preproc/package.html b/sample/preproc/package.html
new file mode 100644
index 0000000..dee15e0
--- /dev/null
+++ b/sample/preproc/package.html
@@ -0,0 +1,14 @@
+<html>
+<body>
+A sample preprocessor.
+
+<p>The preprocessor for running Javassist at compile time.
+The produced class files are saved on a local disk.
+
+<p>This package is provided as a sample implementation of the
+preprocessor using Javassist. All the programs in this package
+uses only the regular Javassist API; they never call any hidden
+methods.
+
+</body>
+</html>
diff --git a/sample/reflect/Main.java b/sample/reflect/Main.java
new file mode 100644
index 0000000..972e896
--- /dev/null
+++ b/sample/reflect/Main.java
@@ -0,0 +1,32 @@
+package sample.reflect;
+
+import javassist.tools.reflect.Loader;
+
+/*
+ The "verbose metaobject" example (JDK 1.2 or later only).
+
+ Since this program registers class Person as a reflective class
+ (in a more realistic demonstration, what classes are reflective
+ would be specified by some configuration file), the class loader
+ modifies Person.class when loading into the JVM so that the class
+ Person is changed into a reflective class and a Person object is
+ controlled by a VerboseMetaobj.
+
+ To run,
+
+ % java javassist.tools.reflect.Loader sample.reflect.Main Joe
+
+ Compare this result with that of the regular execution without reflection:
+
+ % java sample.reflect.Person Joe
+*/
+public class Main {
+ public static void main(String[] args) throws Throwable {
+ Loader cl = (Loader)Main.class.getClassLoader();
+ cl.makeReflective("sample.reflect.Person",
+ "sample.reflect.VerboseMetaobj",
+ "javassist.tools.reflect.ClassMetaobject");
+
+ cl.run("sample.reflect.Person", args);
+ }
+}
diff --git a/sample/reflect/Person.java b/sample/reflect/Person.java
new file mode 100644
index 0000000..90ccb18
--- /dev/null
+++ b/sample/reflect/Person.java
@@ -0,0 +1,53 @@
+/*
+ * A base-level class controlled by VerboseMetaobj.
+ */
+
+package sample.reflect;
+
+import javassist.tools.reflect.Metalevel;
+import javassist.tools.reflect.Metaobject;
+
+public class Person {
+ public String name;
+
+ public static int birth = 3;
+
+ public static final String defaultName = "John";
+
+ public Person(String name, int birthYear) {
+ if (name == null)
+ this.name = defaultName;
+ else
+ this.name = name;
+
+ birth = birthYear;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public int getAge(int year) {
+ return year - birth;
+ }
+
+ public static void main(String[] args) {
+ String name;
+ if (args.length > 0)
+ name = args[0];
+ else
+ name = "Bill";
+
+ Person p = new Person(name, 1960);
+ System.out.println("name: " + p.getName());
+ System.out.println("object: " + p.toString());
+
+ // change the metaobject of p.
+ if (p instanceof Metalevel) {
+ ((Metalevel)p)._setMetaobject(new Metaobject(p, null));
+ System.out.println("<< the metaobject was changed.>>");
+ }
+
+ System.out.println("age: " + p.getAge(1999));
+ }
+}
diff --git a/sample/reflect/VerboseMetaobj.java b/sample/reflect/VerboseMetaobj.java
new file mode 100644
index 0000000..cc47999
--- /dev/null
+++ b/sample/reflect/VerboseMetaobj.java
@@ -0,0 +1,27 @@
+package sample.reflect;
+
+import javassist.tools.reflect.*;
+
+public class VerboseMetaobj extends Metaobject {
+ public VerboseMetaobj(Object self, Object[] args) {
+ super(self, args);
+ System.out.println("** constructed: " + self.getClass().getName());
+ }
+
+ public Object trapFieldRead(String name) {
+ System.out.println("** field read: " + name);
+ return super.trapFieldRead(name);
+ }
+
+ public void trapFieldWrite(String name, Object value) {
+ System.out.println("** field write: " + name);
+ super.trapFieldWrite(name, value);
+ }
+
+ public Object trapMethodcall(int identifier, Object[] args)
+ throws Throwable {
+ System.out.println("** trap: " + getMethodName(identifier) + "() in "
+ + getClassMetaobject().getName());
+ return super.trapMethodcall(identifier, args);
+ }
+}
diff --git a/sample/rmi/AlertDialog.java b/sample/rmi/AlertDialog.java
new file mode 100644
index 0000000..99fae5c
--- /dev/null
+++ b/sample/rmi/AlertDialog.java
@@ -0,0 +1,30 @@
+package sample.rmi;
+
+import java.awt.*;
+import java.awt.event.*;
+
+public class AlertDialog extends Frame implements ActionListener {
+ private Label label;
+
+ public AlertDialog() {
+ super("Alert");
+ setSize(200, 100);
+ setLocation(100, 100);
+ label = new Label();
+ Button b = new Button("OK");
+ b.addActionListener(this);
+ Panel p = new Panel();
+ p.add(b);
+ add("North", label);
+ add("South", p);
+ }
+
+ public void show(String message) {
+ label.setText(message);
+ setVisible(true);
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ setVisible(false);
+ }
+}
diff --git a/sample/rmi/CountApplet.java b/sample/rmi/CountApplet.java
new file mode 100644
index 0000000..e4ee0ee
--- /dev/null
+++ b/sample/rmi/CountApplet.java
@@ -0,0 +1,83 @@
+package sample.rmi;
+
+import java.applet.*;
+import java.awt.*;
+import java.awt.event.*;
+import javassist.tools.rmi.ObjectImporter;
+import javassist.tools.rmi.ObjectNotFoundException;
+import javassist.tools.web.Viewer;
+
+public class CountApplet extends Applet implements ActionListener {
+ private Font font;
+ private ObjectImporter importer;
+ private Counter counter;
+ private AlertDialog dialog;
+ private String message;
+
+ private String paramButton;
+ private String paramName;
+
+ public void init() {
+ paramButton = getParameter("button");
+ paramName = getParameter("name");
+ importer = new ObjectImporter(this);
+ commonInit();
+ }
+
+ /* call this method instead of init() if this program is not run
+ * as an applet.
+ */
+ public void applicationInit() {
+ paramButton = "OK";
+ paramName = "counter";
+ Viewer cl = (Viewer)getClass().getClassLoader();
+ importer = new ObjectImporter(cl.getServer(), cl.getPort());
+ commonInit();
+ }
+
+ private void commonInit() {
+ font = new Font("SansSerif", Font.ITALIC, 40);
+ Button b = new Button(paramButton);
+ b.addActionListener(this);
+ add(b);
+ dialog = new AlertDialog();
+ message = "???";
+ }
+
+ public void destroy() {
+ dialog.dispose();
+ }
+
+ public void start() {
+ try {
+ counter = (Counter)importer.lookupObject(paramName);
+ message = Integer.toString(counter.get());
+ }
+ catch (ObjectNotFoundException e) {
+ dialog.show(e.toString());
+ }
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ counter.increase();
+ message = Integer.toString(counter.get());
+ repaint();
+ }
+
+ public void paint(Graphics g) {
+ g.setFont(font);
+ g.drawRect(50, 50, 100, 100);
+ g.setColor(Color.blue);
+ g.drawString(message, 60, 120);
+ }
+
+ public static void main(String[] args) {
+ Frame f = new Frame("CountApplet");
+ CountApplet ca = new CountApplet();
+ f.add(ca);
+ f.setSize(300, 300);
+ ca.applicationInit();
+ ca.start();
+ f.setVisible(true);
+ }
+}
diff --git a/sample/rmi/Counter.java b/sample/rmi/Counter.java
new file mode 100644
index 0000000..0920ca7
--- /dev/null
+++ b/sample/rmi/Counter.java
@@ -0,0 +1,32 @@
+package sample.rmi;
+
+import javassist.tools.rmi.AppletServer;
+import java.io.IOException;
+import javassist.CannotCompileException;
+import javassist.NotFoundException;
+
+public class Counter {
+ private int count = 0;
+
+ public int get() {
+ return count;
+ }
+
+ synchronized public int increase() {
+ count += 1;
+ return count;
+ }
+
+ public static void main(String[] args)
+ throws IOException, NotFoundException, CannotCompileException
+ {
+ if (args.length == 1) {
+ AppletServer web = new AppletServer(args[0]);
+ web.exportObject("counter", new Counter());
+ web.run();
+ }
+ else
+ System.err.println(
+ "Usage: java sample.rmi.Counter <port number>");
+ }
+}
diff --git a/sample/rmi/inside.gif b/sample/rmi/inside.gif
new file mode 100644
index 0000000..c69c8ee
--- /dev/null
+++ b/sample/rmi/inside.gif
Binary files differ
diff --git a/sample/rmi/start.html b/sample/rmi/start.html
new file mode 100644
index 0000000..33321ad
--- /dev/null
+++ b/sample/rmi/start.html
@@ -0,0 +1,15 @@
+<h2>Instructions</h2>
+
+<p>1. Run the server on the local host (where your web browser is running):
+
+<ul>% java sample.rmi.Counter 5001</ul>
+
+<p>2. Click below:
+
+<h2><a href="webdemo.html">
+Start!
+</a></h2>
+
+<p>If you don't want to use a web browser, do as follows:
+
+<ul><pre>% java javassist.tools.web.Viewer localhost 5001 sample.rmi.CountApplet</pre></ul>
diff --git a/sample/rmi/webdemo.html b/sample/rmi/webdemo.html
new file mode 100644
index 0000000..a2b595c
--- /dev/null
+++ b/sample/rmi/webdemo.html
@@ -0,0 +1,203 @@
+<html>
+<body>
+<h2>Remote Method Invocation</h2>
+
+<P>Javassist enables an applet to access a remote object as if it is a
+local object. The applet can communicate through a socket with the
+host that executes the web server distributing that applet. However,
+the applet cannot directly call a method on an object if the object is
+on a remote host. The <code>javassist.tools.rmi</code> package provides
+a mechanism for the applet to transparently access the remote object.
+The rules that the applet must be subject to are simpler than the
+standard Java RMI.
+
+<h3>1. Sample applet</h3>
+
+<P>The applet showing below is a simple number counter.
+If you press the button, the number is increased by one.
+An interesting feature of this applet is that the object
+recording the current number is contained by the web server
+written in Java. The applet must access the object through a socket
+to obtain the current number.
+
+<p><center>
+<applet codebase="http://localhost:5001"
+code="sample.rmi.CountApplet" width=200 height=200>
+<param name=name value="counter">
+<param name=button value="+1">
+</applet>
+</center>
+
+<p>However, the program of the applet does not need to directly handle
+a socket. The <code>ObjectImporter</code> provided by Javassist deals
+with all the awkward programming.
+Look at the lines shown with red:
+
+<p><b>Figure 1: Applet</b>
+
+<pre>
+<font color="red">import javassist.tools.rmi.ObjectImporter;</font>
+
+public class CountApplet extends Applet implements ActionListener {
+ private Font font;
+ <font color="red">private ObjectImporter importer;
+ private Counter counter;</font>
+ private AlertDialog dialog;
+ private String message;
+
+ public void init() {
+ font = new Font("SansSerif", Font.ITALIC, 40);
+ Button b = new Button(getParameter("button"));
+ b.addActionListener(this);
+ add(b);
+ <font color="red">importer = new ObjectImporter(this);</font>
+ dialog = new AlertDialog();
+ message = "???";
+ }
+
+ public void start() {
+ String counterName = getParameter("name");
+ <font color="red">counter = (Counter)importer.getObject(counterName);</font>
+ message = Integer.toString(<font color="red">counter.get()</font>);
+ }
+
+ /* The method called when the button is pressed.
+ */
+ public void actionPerformed(ActionEvent e) {
+ message = Integer.toString(<font color="red">counter.increase()</font>);
+ repaint();
+ }
+
+ public void paint(Graphics g) {
+ g.setFont(font);
+ g.drawRect(50, 50, 100, 100);
+ g.setColor(Color.blue);
+ g.drawString(message, 60, 120);
+ }
+}
+</pre>
+
+<p>A <code>Counter</code> object running on a remote host
+maintains the counter number. To access this object, the applet first
+calls <code>getObject()</code> on an <code>ObjectImporter</code>
+to obtain a reference to the object. The parameter is the name associated
+with the object by the web server. Once the reference is obtained, it
+is delt with as if it is a reference to a local object.
+For example, <code>counter.get()</code> and <code>counter.increase()</code>
+call methods on the remote object.
+
+<p>The definition of the <code>Counter</code> class is also
+straightforward:
+
+<p><b>Figure 2: Remote object</b>
+
+<pre>
+public class Counter {
+ private int count = 0;
+
+ public int get() {
+ return count;
+ }
+
+ public int increase() {
+ count += 1;
+ return count;
+ }
+}
+</pre>
+
+<p>Note that the <code>javassist.tools.rmi</code> package does not require
+the <code>Counter</code> class to be an interface unlike the Java RMI,
+with which <code>Counter</code> must be an interface and it must be
+implemented by another class.
+
+<p>To make the <code>Counter</code> object available from the applet,
+it must be registered with the web server. A <code>AppletServer</code>
+object is a simple webserver that can distribute <code>.html</code> files
+and <code>.class</code> files (Java applets).
+
+<p><b>Figure 3: Server-side program</b>
+
+<pre>
+public class MyWebServer {
+ public static void main(String[] args) throws IOException, CannotCompileException
+ {
+ AppletServer web = new AppletServer(args[0]);
+ <font color="red">web.exportObject("counter", new Counter());</font>
+ web.run();
+ }
+}
+</pre>
+
+<p>The <code>exportObject()</code> method registers a remote object
+with the <code>AppletServer</code> object. In the example above,
+a <code>Counter</code> object is registered. The applet can access
+the object with the name "counter". The web server starts the service
+if the <code>run()</code> method is called.
+
+<p><br>
+
+<h3>2. Features</h3>
+
+The remote method invocation mechanism provided by Javassist has the
+following features:
+
+<ul>
+<li><b>Regular Java syntax:</b><br>
+ The applet can call a method on a remote object with regular
+ Java syntax.
+<p>
+
+<li><b>No special naming convention:</b><br>
+ The applet can use the same class name as the server-side program.
+ The reference object to a remote <code>Foo</code> object is
+ also represented by the class <code>Foo</code>.
+ Unlike other similar
+ systems, it is not represented by a different class such as
+ <code>ProxyFoo</code> or an interface implemented by
+ <code>Foo</code>.
+<p>
+
+<li><b>No extra compiler:</b><br>
+ All the programs, both the applet and the server-side program,
+ are compiled by the regular Java compiler. No external compiler
+ is needed.
+</ul>
+
+<p> With the Java RMI or Voyager, the applet programmer must define
+an interface for every remote object class and access the remote object
+through that interface.
+On the other hand, the <code>javassist.tools.rmi</code> package does not
+require the programmer to follow that programming convention.
+It is suitable for writing simple distributed programs like applets.
+
+<p><br>
+
+<h3>3. Inside of the system</h3>
+
+<p>A key idea of the implementation is that the applet and the server-side
+program must use different versions of the class <code>Counter</code>.
+The <code>Counter</code> object in the applet must work as a proxy
+object, which transfers the method invocations to the <code>Counter</code>
+object in the server-side program.
+
+<p>With other systems like the Java RMI, the class of this proxy object is
+produced by a special compiler such as <code>rmic</code>.
+It must be manually maintained by the programmer.
+
+<center><img src="inside.gif"></center>
+
+<p>However, Javassist automatically generates the proxy class at
+runtime so that the programmer does not have to be concerned about the
+maintenance of the proxy class.
+If the web browser running the applet
+requests to load the <code>Counter</code> class, which is the class
+of an exported object,
+then the web server
+transfers the version of <code>Counter</code> that Javassist generates
+as a proxy class.
+
+<p><br>
+
+</body>
+</html>
diff --git a/sample/vector/Sample.java b/sample/vector/Sample.java
new file mode 100644
index 0000000..7a47aad
--- /dev/null
+++ b/sample/vector/Sample.java
@@ -0,0 +1,14 @@
+package sample.vector;
+
+public class Sample extends java.util.Vector {
+ public void add(X e) {
+ super.addElement(e);
+ }
+
+ public X at(int i) {
+ return (X)super.elementAt(i);
+ }
+}
+
+class X {
+}
diff --git a/sample/vector/Sample2.java b/sample/vector/Sample2.java
new file mode 100644
index 0000000..dd5c965
--- /dev/null
+++ b/sample/vector/Sample2.java
@@ -0,0 +1,13 @@
+package sample.vector;
+
+public class Sample2 extends java.util.Vector {
+ public Object add(Object[] args) {
+ super.addElement(args[0]);
+ return null;
+ }
+
+ public Object at(Object[] args) {
+ int i = ((Integer)args[0]).intValue();
+ return super.elementAt(i);
+ }
+}
diff --git a/sample/vector/Test.j b/sample/vector/Test.j
new file mode 100644
index 0000000..4ae7f6b
--- /dev/null
+++ b/sample/vector/Test.j
@@ -0,0 +1,38 @@
+/*
+ A sample program using sample.vector.VectorAssistant
+ and the sample.preproc package.
+
+ This automatically produces the classes representing vectors of integer
+ and vectors of java.lang.String.
+
+ To compile and run this program, do as follows:
+
+ % java sample.preproc.Compiler sample/vector/Test.j
+ % javac sample/vector/Test.java
+ % java sample.vector.Test
+
+ The first line produces one source file (sample/Test.java) and
+ two class files (sample/vector/intVector.class and
+ sample/vector/StringVector.class).
+*/
+
+package sample.vector;
+
+import java.util.Vector by sample.vector.VectorAssistant(java.lang.String);
+import java.util.Vector by sample.vector.VectorAssistant(int);
+
+public class Test {
+ public static void main(String[] args) {
+ intVector iv = new intVector();
+ iv.add(3);
+ iv.add(4);
+ for (int i = 0; i < iv.size(); ++i)
+ System.out.println(iv.at(i));
+
+ StringVector sv = new StringVector();
+ sv.add("foo");
+ sv.add("bar");
+ for (int i = 0; i < sv.size(); ++i)
+ System.out.println(sv.at(i));
+ }
+}
diff --git a/sample/vector/VectorAssistant.java b/sample/vector/VectorAssistant.java
new file mode 100644
index 0000000..8b721db
--- /dev/null
+++ b/sample/vector/VectorAssistant.java
@@ -0,0 +1,135 @@
+package sample.vector;
+
+import java.io.IOException;
+import javassist.*;
+import sample.preproc.Assistant;
+
+/**
+ * This is a Javassist program which produce a new class representing
+ * vectors of a given type. For example,
+ *
+ * <ul>import java.util.Vector by sample.vector.VectorAssistant(int)</ul>
+ *
+ * <p>requests the Javassist preprocessor to substitute the following
+ * lines for the original import declaration:
+ *
+ * <ul><pre>
+ * import java.util.Vector;
+ * import sample.vector.intVector;
+ * </pre></ul>
+ *
+ * <p>The Javassist preprocessor calls <code>VectorAssistant.assist()</code>
+ * and produces class <code>intVector</code> equivalent to:
+ *
+ * <ul><pre>
+ * package sample.vector;
+ *
+ * public class intVector extends Vector {
+ * pubilc void add(int value) {
+ * addElement(new Integer(value));
+ * }
+ *
+ * public int at(int index) {
+ * return elementAt(index).intValue();
+ * }
+ * }
+ * </pre></ul>
+ *
+ * <p><code>VectorAssistant.assist()</code> uses
+ * <code>sample.vector.Sample</code> and <code>sample.vector.Sample2</code>
+ * as a template to produce the methods <code>add()</code> and
+ * <code>at()</code>.
+ */
+public class VectorAssistant implements Assistant {
+ public final String packageName = "sample.vector.";
+
+ /**
+ * Calls <code>makeSubclass()</code> and produces a new vector class.
+ * This method is called by a <code>sample.preproc.Compiler</code>.
+ *
+ * @see sample.preproc.Compiler
+ */
+ public CtClass[] assist(ClassPool pool, String vec, String[] args)
+ throws CannotCompileException
+ {
+ if (args.length != 1)
+ throw new CannotCompileException(
+ "VectorAssistant receives a single argument.");
+
+ try {
+ CtClass subclass;
+ CtClass elementType = pool.get(args[0]);
+ if (elementType.isPrimitive())
+ subclass = makeSubclass2(pool, elementType);
+ else
+ subclass = makeSubclass(pool, elementType);
+
+ CtClass[] results = { subclass, pool.get(vec) };
+ return results;
+ }
+ catch (NotFoundException e) {
+ throw new CannotCompileException(e);
+ }
+ catch (IOException e) {
+ throw new CannotCompileException(e);
+ }
+ }
+
+ /**
+ * Produces a new vector class. This method does not work if
+ * the element type is a primitive type.
+ *
+ * @param type the type of elements
+ */
+ public CtClass makeSubclass(ClassPool pool, CtClass type)
+ throws CannotCompileException, NotFoundException, IOException
+ {
+ CtClass vec = pool.makeClass(makeClassName(type));
+ vec.setSuperclass(pool.get("java.util.Vector"));
+
+ CtClass c = pool.get("sample.vector.Sample");
+ CtMethod addmethod = c.getDeclaredMethod("add");
+ CtMethod atmethod = c.getDeclaredMethod("at");
+
+ ClassMap map = new ClassMap();
+ map.put("sample.vector.X", type.getName());
+
+ vec.addMethod(CtNewMethod.copy(addmethod, "add", vec, map));
+ vec.addMethod(CtNewMethod.copy(atmethod, "at", vec, map));
+ vec.writeFile();
+ return vec;
+ }
+
+ /**
+ * Produces a new vector class. This uses wrapped methods so that
+ * the element type can be a primitive type.
+ *
+ * @param type the type of elements
+ */
+ public CtClass makeSubclass2(ClassPool pool, CtClass type)
+ throws CannotCompileException, NotFoundException, IOException
+ {
+ CtClass vec = pool.makeClass(makeClassName(type));
+ vec.setSuperclass(pool.get("java.util.Vector"));
+
+ CtClass c = pool.get("sample.vector.Sample2");
+ CtMethod addmethod = c.getDeclaredMethod("add");
+ CtMethod atmethod = c.getDeclaredMethod("at");
+
+ CtClass[] args1 = { type };
+ CtClass[] args2 = { CtClass.intType };
+ CtMethod m
+ = CtNewMethod.wrapped(CtClass.voidType, "add", args1,
+ null, addmethod, null, vec);
+ vec.addMethod(m);
+ m = CtNewMethod.wrapped(type, "at", args2,
+ null, atmethod, null, vec);
+ vec.addMethod(m);
+ vec.writeFile();
+ return vec;
+ }
+
+ private String makeClassName(CtClass type) {
+ return packageName + type.getSimpleName() + "Vector";
+ }
+}
diff --git a/src/main/META-INF/MANIFEST.MF b/src/main/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..02ac494
--- /dev/null
+++ b/src/main/META-INF/MANIFEST.MF
@@ -0,0 +1,8 @@
+Manifest-Version: 1.1
+Specification-Title: Javassist
+Created-By: Shigeru Chiba, Tokyo Institute of Technology
+Specification-Vendor: Shigeru Chiba, Tokyo Institute of Technology
+Specification-Version: 3.14.0.GA
+Main-Class: javassist.CtClass
+
+Name: javassist/
diff --git a/src/main/javassist/ByteArrayClassPath.java b/src/main/javassist/ByteArrayClassPath.java
new file mode 100644
index 0000000..f09f81f
--- /dev/null
+++ b/src/main/javassist/ByteArrayClassPath.java
@@ -0,0 +1,98 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist;
+
+import java.io.*;
+import java.net.URL;
+import java.net.MalformedURLException;
+
+/**
+ * A <code>ByteArrayClassPath</code> contains bytes that is served as
+ * a class file to a <code>ClassPool</code>. It is useful to convert
+ * a byte array to a <code>CtClass</code> object.
+ *
+ * <p>For example, if you want to convert a byte array <code>b</code>
+ * into a <code>CtClass</code> object representing the class with a name
+ * <code>classname</code>, then do as following:
+ *
+ * <ul><pre>
+ * ClassPool cp = ClassPool.getDefault();
+ * cp.insertClassPath(new ByteArrayClassPath(classname, b));
+ * CtClass cc = cp.get(classname);
+ * </pre></ul>
+ *
+ * <p>The <code>ClassPool</code> object <code>cp</code> uses the created
+ * <code>ByteArrayClassPath</code> object as the source of the class file.
+ *
+ * <p>A <code>ByteArrayClassPath</code> must be instantiated for every
+ * class. It contains only a single class file.
+ *
+ * @see javassist.ClassPath
+ * @see ClassPool#insertClassPath(ClassPath)
+ * @see ClassPool#appendClassPath(ClassPath)
+ * @see ClassPool#makeClass(InputStream)
+ */
+public class ByteArrayClassPath implements ClassPath {
+ protected String classname;
+ protected byte[] classfile;
+
+ /*
+ * Creates a <code>ByteArrayClassPath</code> containing the given
+ * bytes.
+ *
+ * @param name a fully qualified class name
+ * @param classfile the contents of a class file.
+ */
+ public ByteArrayClassPath(String name, byte[] classfile) {
+ this.classname = name;
+ this.classfile = classfile;
+ }
+
+ /**
+ * Closes this class path.
+ */
+ public void close() {}
+
+ public String toString() {
+ return "byte[]:" + classname;
+ }
+
+ /**
+ * Opens the class file.
+ */
+ public InputStream openClassfile(String classname) {
+ if(this.classname.equals(classname))
+ return new ByteArrayInputStream(classfile);
+ else
+ return null;
+ }
+
+ /**
+ * Obtains the URL.
+ */
+ public URL find(String classname) {
+ if(this.classname.equals(classname)) {
+ String cname = classname.replace('.', '/') + ".class";
+ try {
+ // return new File(cname).toURL();
+ return new URL("file:/ByteArrayClassPath/" + cname);
+ }
+ catch (MalformedURLException e) {}
+ }
+
+ return null;
+ }
+}
diff --git a/src/main/javassist/CannotCompileException.java b/src/main/javassist/CannotCompileException.java
new file mode 100644
index 0000000..35ad6e7
--- /dev/null
+++ b/src/main/javassist/CannotCompileException.java
@@ -0,0 +1,119 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist;
+
+import javassist.compiler.CompileError;
+
+/**
+ * Thrown when bytecode transformation has failed.
+ */
+public class CannotCompileException extends Exception {
+ private Throwable myCause;
+
+ /**
+ * Gets the cause of this throwable.
+ * It is for JDK 1.3 compatibility.
+ */
+ public Throwable getCause() {
+ return (myCause == this ? null : myCause);
+ }
+
+ /**
+ * Initializes the cause of this throwable.
+ * It is for JDK 1.3 compatibility.
+ */
+ public synchronized Throwable initCause(Throwable cause) {
+ myCause = cause;
+ return this;
+ }
+
+ private String message;
+
+ /**
+ * Gets a long message if it is available.
+ */
+ public String getReason() {
+ if (message != null)
+ return message;
+ else
+ return this.toString();
+ }
+
+ /**
+ * Constructs a CannotCompileException with a message.
+ *
+ * @param msg the message.
+ */
+ public CannotCompileException(String msg) {
+ super(msg);
+ message = msg;
+ initCause(null);
+ }
+
+ /**
+ * Constructs a CannotCompileException with an <code>Exception</code>
+ * representing the cause.
+ *
+ * @param e the cause.
+ */
+ public CannotCompileException(Throwable e) {
+ super("by " + e.toString());
+ message = null;
+ initCause(e);
+ }
+
+ /**
+ * Constructs a CannotCompileException with a detailed message
+ * and an <code>Exception</code> representing the cause.
+ *
+ * @param msg the message.
+ * @param e the cause.
+ */
+ public CannotCompileException(String msg, Throwable e) {
+ this(msg);
+ initCause(e);
+ }
+
+ /**
+ * Constructs a CannotCompileException with a
+ * <code>NotFoundException</code>.
+ */
+ public CannotCompileException(NotFoundException e) {
+ this("cannot find " + e.getMessage(), e);
+ }
+
+ /**
+ * Constructs a CannotCompileException with an <code>CompileError</code>.
+ */
+ public CannotCompileException(CompileError e) {
+ this("[source error] " + e.getMessage(), e);
+ }
+
+ /**
+ * Constructs a CannotCompileException
+ * with a <code>ClassNotFoundException</code>.
+ */
+ public CannotCompileException(ClassNotFoundException e, String name) {
+ this("cannot find " + name, e);
+ }
+
+ /**
+ * Constructs a CannotCompileException with a ClassFormatError.
+ */
+ public CannotCompileException(ClassFormatError e, String name) {
+ this("invalid class format: " + name, e);
+ }
+}
diff --git a/src/main/javassist/ClassClassPath.java b/src/main/javassist/ClassClassPath.java
new file mode 100644
index 0000000..c9925d8
--- /dev/null
+++ b/src/main/javassist/ClassClassPath.java
@@ -0,0 +1,96 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist;
+
+import java.io.InputStream;
+import java.net.URL;
+
+/**
+ * A search-path for obtaining a class file
+ * by <code>getResourceAsStream()</code> in <code>java.lang.Class</code>.
+ *
+ * <p>Try adding a <code>ClassClassPath</code> when a program is running
+ * with a user-defined class loader and any class files are not found with
+ * the default <code>ClassPool</code>. For example,
+ *
+ * <ul><pre>
+ * ClassPool cp = ClassPool.getDefault();
+ * cp.insertClassPath(new ClassClassPath(this.getClass()));
+ * </pre></ul>
+ *
+ * This code snippet permanently adds a <code>ClassClassPath</code>
+ * to the default <code>ClassPool</code>. Note that the default
+ * <code>ClassPool</code> is a singleton. The added
+ * <code>ClassClassPath</code> uses a class object representing
+ * the class including the code snippet above.
+ *
+ * @see ClassPool#insertClassPath(ClassPath)
+ * @see ClassPool#appendClassPath(ClassPath)
+ * @see LoaderClassPath
+ */
+public class ClassClassPath implements ClassPath {
+ private Class thisClass;
+
+ /** Creates a search path.
+ *
+ * @param c the <code>Class</code> object used to obtain a class
+ * file. <code>getResourceAsStream()</code> is called on
+ * this object.
+ */
+ public ClassClassPath(Class c) {
+ thisClass = c;
+ }
+
+ ClassClassPath() {
+ /* The value of thisClass was this.getClass() in early versions:
+ *
+ * thisClass = this.getClass();
+ *
+ * However, this made openClassfile() not search all the system
+ * class paths if javassist.jar is put in jre/lib/ext/
+ * (with JDK1.4).
+ */
+ this(java.lang.Object.class);
+ }
+
+ /**
+ * Obtains a class file by <code>getResourceAsStream()</code>.
+ */
+ public InputStream openClassfile(String classname) {
+ String jarname = "/" + classname.replace('.', '/') + ".class";
+ return thisClass.getResourceAsStream(jarname);
+ }
+
+ /**
+ * Obtains the URL of the specified class file.
+ *
+ * @return null if the class file could not be found.
+ */
+ public URL find(String classname) {
+ String jarname = "/" + classname.replace('.', '/') + ".class";
+ return thisClass.getResource(jarname);
+ }
+
+ /**
+ * Does nothing.
+ */
+ public void close() {
+ }
+
+ public String toString() {
+ return thisClass.getName() + ".class";
+ }
+}
diff --git a/src/main/javassist/ClassMap.java b/src/main/javassist/ClassMap.java
new file mode 100644
index 0000000..e923a38
--- /dev/null
+++ b/src/main/javassist/ClassMap.java
@@ -0,0 +1,170 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist;
+
+import javassist.bytecode.Descriptor;
+
+/**
+ * A hash table associating class names with different names.
+ *
+ * <p>This hashtable is used for replacing class names in a class
+ * definition or a method body. Define a subclass of this class
+ * if a more complex mapping algorithm is needed. For example,
+ *
+ * <ul><pre>class MyClassMap extends ClassMap {
+ * public Object get(Object jvmClassName) {
+ * String name = toJavaName((String)jvmClassName);
+ * if (name.startsWith("java."))
+ * return toJvmName("java2." + name.substring(5));
+ * else
+ * return super.get(jvmClassName);
+ * }
+ * }</pre></ul>
+ *
+ * <p>This subclass maps <code>java.lang.String</code> to
+ * <code>java2.lang.String</code>. Note that <code>get()</code>
+ * receives and returns the internal representation of a class name.
+ * For example, the internal representation of <code>java.lang.String</code>
+ * is <code>java/lang/String</code>.
+ *
+ * @see #get(Object)
+ * @see CtClass#replaceClassName(ClassMap)
+ * @see CtNewMethod#copy(CtMethod,String,CtClass,ClassMap)
+ */
+public class ClassMap extends java.util.HashMap {
+ private ClassMap parent;
+
+ /**
+ * Constructs a hash table.
+ */
+ public ClassMap() { parent = null; }
+
+ ClassMap(ClassMap map) { parent = map; }
+
+ /**
+ * Maps a class name to another name in this hashtable.
+ * The names are obtained with calling <code>Class.getName()</code>.
+ * This method translates the given class names into the
+ * internal form used in the JVM before putting it in
+ * the hashtable.
+ *
+ * @param oldname the original class name
+ * @param newname the substituted class name.
+ */
+ public void put(CtClass oldname, CtClass newname) {
+ put(oldname.getName(), newname.getName());
+ }
+
+ /**
+ * Maps a class name to another name in this hashtable.
+ * If the hashtable contains another mapping from the same
+ * class name, the old mapping is replaced.
+ * This method translates the given class names into the
+ * internal form used in the JVM before putting it in
+ * the hashtable.
+ *
+ * <p>If <code>oldname</code> is identical to
+ * <code>newname</code>, then this method does not
+ * perform anything; it does not record the mapping from
+ * <code>oldname</code> to <code>newname</code>. See
+ * <code>fix</code> method.
+ *
+ * @param oldname the original class name.
+ * @param newname the substituted class name.
+ * @see #fix(String)
+ */
+ public void put(String oldname, String newname) {
+ if (oldname == newname)
+ return;
+
+ String oldname2 = toJvmName(oldname);
+ String s = (String)get(oldname2);
+ if (s == null || !s.equals(oldname2))
+ super.put(oldname2, toJvmName(newname));
+ }
+
+ /**
+ * Is equivalent to <code>put()</code> except that
+ * the given mapping is not recorded into the hashtable
+ * if another mapping from <code>oldname</code> is
+ * already included.
+ *
+ * @param oldname the original class name.
+ * @param newname the substituted class name.
+ */
+ public void putIfNone(String oldname, String newname) {
+ if (oldname == newname)
+ return;
+
+ String oldname2 = toJvmName(oldname);
+ String s = (String)get(oldname2);
+ if (s == null)
+ super.put(oldname2, toJvmName(newname));
+ }
+
+ protected final void put0(Object oldname, Object newname) {
+ super.put(oldname, newname);
+ }
+
+ /**
+ * Returns the class name to wihch the given <code>jvmClassName</code>
+ * is mapped. A subclass of this class should override this method.
+ *
+ * <p>This method receives and returns the internal representation of
+ * class name used in the JVM.
+ *
+ * @see #toJvmName(String)
+ * @see #toJavaName(String)
+ */
+ public Object get(Object jvmClassName) {
+ Object found = super.get(jvmClassName);
+ if (found == null && parent != null)
+ return parent.get(jvmClassName);
+ else
+ return found;
+ }
+
+ /**
+ * Prevents a mapping from the specified class name to another name.
+ */
+ public void fix(CtClass clazz) {
+ fix(clazz.getName());
+ }
+
+ /**
+ * Prevents a mapping from the specified class name to another name.
+ */
+ public void fix(String name) {
+ String name2 = toJvmName(name);
+ super.put(name2, name2);
+ }
+
+ /**
+ * Converts a class name into the internal representation used in
+ * the JVM.
+ */
+ public static String toJvmName(String classname) {
+ return Descriptor.toJvmName(classname);
+ }
+
+ /**
+ * Converts a class name from the internal representation used in
+ * the JVM to the normal one used in Java.
+ */
+ public static String toJavaName(String classname) {
+ return Descriptor.toJavaName(classname);
+ }
+}
diff --git a/src/main/javassist/ClassPath.java b/src/main/javassist/ClassPath.java
new file mode 100644
index 0000000..d34b279
--- /dev/null
+++ b/src/main/javassist/ClassPath.java
@@ -0,0 +1,67 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist;
+
+import java.io.InputStream;
+import java.net.URL;
+
+/**
+ * <code>ClassPath</code> is an interface implemented by objects
+ * representing a class search path.
+ * <code>ClassPool</code> uses those objects for reading class files.
+ *
+ * <p>The users can define a class implementing this interface so that
+ * a class file is obtained from a non-standard source.
+ *
+ * @see ClassPool#insertClassPath(ClassPath)
+ * @see ClassPool#appendClassPath(ClassPath)
+ * @see ClassPool#removeClassPath(ClassPath)
+ */
+public interface ClassPath {
+ /**
+ * Opens a class file.
+ * This method may be called just to examine whether the class file
+ * exists as well as to read the contents of the file.
+ *
+ * <p>This method can return null if the specified class file is not
+ * found. If null is returned, the next search path is examined.
+ * However, if an error happens, this method must throw an exception
+ * so that the search will be terminated.
+ *
+ * <p>This method should not modify the contents of the class file.
+ *
+ * @param classname a fully-qualified class name
+ * @return the input stream for reading a class file
+ * @see javassist.Translator
+ */
+ InputStream openClassfile(String classname) throws NotFoundException;
+
+ /**
+ * Returns the uniform resource locator (URL) of the class file
+ * with the specified name.
+ *
+ * @param classname a fully-qualified class name.
+ * @return null if the specified class file could not be found.
+ */
+ URL find(String classname);
+
+ /**
+ * This method is invoked when the <code>ClassPath</code> object is
+ * detached from the search path. It will be an empty method in most of
+ * classes.
+ */
+ void close();
+}
diff --git a/src/main/javassist/ClassPool.java b/src/main/javassist/ClassPool.java
new file mode 100644
index 0000000..b75bfb6
--- /dev/null
+++ b/src/main/javassist/ClassPool.java
@@ -0,0 +1,1107 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.security.ProtectionDomain;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import javassist.bytecode.Descriptor;
+
+/**
+ * A container of <code>CtClass</code> objects.
+ * A <code>CtClass</code> object must be obtained from this object.
+ * If <code>get()</code> is called on this object,
+ * it searches various sources represented by <code>ClassPath</code>
+ * to find a class file and then it creates a <code>CtClass</code> object
+ * representing that class file. The created object is returned to the
+ * caller.
+ *
+ * <p><b>Memory consumption memo:</b>
+ *
+ * <p><code>ClassPool</code> objects hold all the <code>CtClass</code>es
+ * that have been created so that the consistency among modified classes
+ * can be guaranteed. Thus if a large number of <code>CtClass</code>es
+ * are processed, the <code>ClassPool</code> will consume a huge amount
+ * of memory. To avoid this, a <code>ClassPool</code> object
+ * should be recreated, for example, every hundred classes processed.
+ * Note that <code>getDefault()</code> is a singleton factory.
+ * Otherwise, <code>detach()</code> in <code>CtClass</code> should be used
+ * to avoid huge memory consumption.
+ *
+ * <p><b><code>ClassPool</code> hierarchy:</b>
+ *
+ * <p><code>ClassPool</code>s can make a parent-child hierarchy as
+ * <code>java.lang.ClassLoader</code>s. If a <code>ClassPool</code> has
+ * a parent pool, <code>get()</code> first asks the parent pool to find
+ * a class file. Only if the parent could not find the class file,
+ * <code>get()</code> searches the <code>ClassPath</code>s of
+ * the child <code>ClassPool</code>. This search order is reversed if
+ * <code>ClassPath.childFirstLookup</code> is <code>true</code>.
+ *
+ * @see javassist.CtClass
+ * @see javassist.ClassPath
+ */
+public class ClassPool {
+ // used by toClass().
+ private static java.lang.reflect.Method defineClass1, defineClass2;
+
+ static {
+ try {
+ AccessController.doPrivileged(new PrivilegedExceptionAction(){
+ public Object run() throws Exception{
+ Class cl = Class.forName("java.lang.ClassLoader");
+ defineClass1 = cl.getDeclaredMethod("defineClass",
+ new Class[] { String.class, byte[].class,
+ int.class, int.class });
+
+ defineClass2 = cl.getDeclaredMethod("defineClass",
+ new Class[] { String.class, byte[].class,
+ int.class, int.class, ProtectionDomain.class });
+ return null;
+ }
+ });
+ }
+ catch (PrivilegedActionException pae) {
+ throw new RuntimeException("cannot initialize ClassPool", pae.getException());
+ }
+ }
+
+ /**
+ * Determines the search order.
+ *
+ * <p>If this field is true, <code>get()</code> first searches the
+ * class path associated to this <code>ClassPool</code> and then
+ * the class path associated with the parent <code>ClassPool</code>.
+ * Otherwise, the class path associated with the parent is searched
+ * first.
+ *
+ * <p>The default value is false.
+ */
+ public boolean childFirstLookup = false;
+
+ /**
+ * Turning the automatic pruning on/off.
+ *
+ * <p>If this field is true, <code>CtClass</code> objects are
+ * automatically pruned by default when <code>toBytecode()</code> etc.
+ * are called. The automatic pruning can be turned on/off individually
+ * for each <code>CtClass</code> object.
+ *
+ * <p>The initial value is false.
+ *
+ * @see CtClass#prune()
+ * @see CtClass#stopPruning(boolean)
+ * @see CtClass#detach()
+ */
+ public static boolean doPruning = false;
+
+ private int compressCount;
+ private static final int COMPRESS_THRESHOLD = 100;
+
+ /* releaseUnmodifiedClassFile was introduced for avoiding a bug
+ of JBoss AOP. So the value should be true except for JBoss AOP.
+ */
+
+ /**
+ * If true, unmodified and not-recently-used class files are
+ * periodically released for saving memory.
+ *
+ * <p>The initial value is true.
+ */
+ public static boolean releaseUnmodifiedClassFile = true;
+
+ protected ClassPoolTail source;
+ protected ClassPool parent;
+ protected Hashtable classes; // should be synchronous
+
+ /**
+ * Table of registered cflow variables.
+ */
+ private Hashtable cflow = null; // should be synchronous.
+
+ private static final int INIT_HASH_SIZE = 191;
+
+ private ArrayList importedPackages;
+
+ /**
+ * Creates a root class pool. No parent class pool is specified.
+ */
+ public ClassPool() {
+ this(null);
+ }
+
+ /**
+ * Creates a root class pool. If <code>useDefaultPath</code> is
+ * true, <code>appendSystemPath()</code> is called. Otherwise,
+ * this constructor is equivalent to the constructor taking no
+ * parameter.
+ *
+ * @param useDefaultPath true if the system search path is
+ * appended.
+ */
+ public ClassPool(boolean useDefaultPath) {
+ this(null);
+ if (useDefaultPath)
+ appendSystemPath();
+ }
+
+ /**
+ * Creates a class pool.
+ *
+ * @param parent the parent of this class pool. If this is a root
+ * class pool, this parameter must be <code>null</code>.
+ * @see javassist.ClassPool#getDefault()
+ */
+ public ClassPool(ClassPool parent) {
+ this.classes = new Hashtable(INIT_HASH_SIZE);
+ this.source = new ClassPoolTail();
+ this.parent = parent;
+ if (parent == null) {
+ CtClass[] pt = CtClass.primitiveTypes;
+ for (int i = 0; i < pt.length; ++i)
+ classes.put(pt[i].getName(), pt[i]);
+ }
+
+ this.cflow = null;
+ this.compressCount = 0;
+ clearImportedPackages();
+ }
+
+ /**
+ * Returns the default class pool.
+ * The returned object is always identical since this method is
+ * a singleton factory.
+ *
+ * <p>The default class pool searches the system search path,
+ * which usually includes the platform library, extension
+ * libraries, and the search path specified by the
+ * <code>-classpath</code> option or the <code>CLASSPATH</code>
+ * environment variable.
+ *
+ * <p>When this method is called for the first time, the default
+ * class pool is created with the following code snippet:
+ *
+ * <ul><code>ClassPool cp = new ClassPool();
+ * cp.appendSystemPath();
+ * </code></ul>
+ *
+ * <p>If the default class pool cannot find any class files,
+ * try <code>ClassClassPath</code> and <code>LoaderClassPath</code>.
+ *
+ * @see ClassClassPath
+ * @see LoaderClassPath
+ */
+ public static synchronized ClassPool getDefault() {
+ if (defaultPool == null) {
+ defaultPool = new ClassPool(null);
+ defaultPool.appendSystemPath();
+ }
+
+ return defaultPool;
+ }
+
+ private static ClassPool defaultPool = null;
+
+ /**
+ * Provide a hook so that subclasses can do their own
+ * caching of classes.
+ *
+ * @see #cacheCtClass(String,CtClass,boolean)
+ * @see #removeCached(String)
+ */
+ protected CtClass getCached(String classname) {
+ return (CtClass)classes.get(classname);
+ }
+
+ /**
+ * Provides a hook so that subclasses can do their own
+ * caching of classes.
+ *
+ * @see #getCached(String)
+ * @see #removeCached(String,CtClass)
+ */
+ protected void cacheCtClass(String classname, CtClass c, boolean dynamic) {
+ classes.put(classname, c);
+ }
+
+ /**
+ * Provide a hook so that subclasses can do their own
+ * caching of classes.
+ *
+ * @see #getCached(String)
+ * @see #cacheCtClass(String,CtClass,boolean)
+ */
+ protected CtClass removeCached(String classname) {
+ return (CtClass)classes.remove(classname);
+ }
+
+ /**
+ * Returns the class search path.
+ */
+ public String toString() {
+ return source.toString();
+ }
+
+ /**
+ * This method is periodically invoked so that memory
+ * footprint will be minimized.
+ */
+ void compress() {
+ if (compressCount++ > COMPRESS_THRESHOLD) {
+ compressCount = 0;
+ Enumeration e = classes.elements();
+ while (e.hasMoreElements())
+ ((CtClass)e.nextElement()).compress();
+ }
+ }
+
+ /**
+ * Record a package name so that the Javassist compiler searches
+ * the package to resolve a class name.
+ * Don't record the <code>java.lang</code> package, which has
+ * been implicitly recorded by default.
+ *
+ * <p>Since version 3.14, <code>packageName</code> can be a
+ * fully-qualified class name.
+ *
+ * <p>Note that <code>get()</code> in <code>ClassPool</code> does
+ * not search the recorded package. Only the compiler searches it.
+ *
+ * @param packageName the package name.
+ * It must not include the last '.' (dot).
+ * For example, "java.util" is valid but "java.util." is wrong.
+ * @since 3.1
+ */
+ public void importPackage(String packageName) {
+ importedPackages.add(packageName);
+ }
+
+ /**
+ * Clear all the package names recorded by <code>importPackage()</code>.
+ * The <code>java.lang</code> package is not removed.
+ *
+ * @see #importPackage(String)
+ * @since 3.1
+ */
+ public void clearImportedPackages() {
+ importedPackages = new ArrayList();
+ importedPackages.add("java.lang");
+ }
+
+ /**
+ * Returns all the package names recorded by <code>importPackage()</code>.
+ *
+ * @see #importPackage(String)
+ * @since 3.1
+ */
+ public Iterator getImportedPackages() {
+ return importedPackages.iterator();
+ }
+
+ /**
+ * Records a name that never exists.
+ * For example, a package name can be recorded by this method.
+ * This would improve execution performance
+ * since <code>get()</code> does not search the class path at all
+ * if the given name is an invalid name recorded by this method.
+ * Note that searching the class path takes relatively long time.
+ *
+ * @param name a class name (separeted by dot).
+ */
+ public void recordInvalidClassName(String name) {
+ source.recordInvalidClassName(name);
+ }
+
+ /**
+ * Records the <code>$cflow</code> variable for the field specified
+ * by <code>cname</code> and <code>fname</code>.
+ *
+ * @param name variable name
+ * @param cname class name
+ * @param fname field name
+ */
+ void recordCflow(String name, String cname, String fname) {
+ if (cflow == null)
+ cflow = new Hashtable();
+
+ cflow.put(name, new Object[] { cname, fname });
+ }
+
+ /**
+ * Undocumented method. Do not use; internal-use only.
+ *
+ * @param name the name of <code>$cflow</code> variable
+ */
+ public Object[] lookupCflow(String name) {
+ if (cflow == null)
+ cflow = new Hashtable();
+
+ return (Object[])cflow.get(name);
+ }
+
+ /**
+ * Reads a class file and constructs a <code>CtClass</code>
+ * object with a new name.
+ * This method is useful if you want to generate a new class as a copy
+ * of another class (except the class name). For example,
+ *
+ * <ul><pre>
+ * getAndRename("Point", "Pair")
+ * </pre></ul>
+ *
+ * returns a <code>CtClass</code> object representing <code>Pair</code>
+ * class. The definition of <code>Pair</code> is the same as that of
+ * <code>Point</code> class except the class name since <code>Pair</code>
+ * is defined by reading <code>Point.class</code>.
+ *
+ * @param orgName the original (fully-qualified) class name
+ * @param newName the new class name
+ */
+ public CtClass getAndRename(String orgName, String newName)
+ throws NotFoundException
+ {
+ CtClass clazz = get0(orgName, false);
+ if (clazz == null)
+ throw new NotFoundException(orgName);
+
+ if (clazz instanceof CtClassType)
+ ((CtClassType)clazz).setClassPool(this);
+
+ clazz.setName(newName); // indirectly calls
+ // classNameChanged() in this class
+ return clazz;
+ }
+
+ /*
+ * This method is invoked by CtClassType.setName(). It removes a
+ * CtClass object from the hash table and inserts it with the new
+ * name. Don't delegate to the parent.
+ */
+ synchronized void classNameChanged(String oldname, CtClass clazz) {
+ CtClass c = (CtClass)getCached(oldname);
+ if (c == clazz) // must check this equation.
+ removeCached(oldname); // see getAndRename().
+
+ String newName = clazz.getName();
+ checkNotFrozen(newName);
+ cacheCtClass(newName, clazz, false);
+ }
+
+ /**
+ * Reads a class file from the source and returns a reference
+ * to the <code>CtClass</code>
+ * object representing that class file. If that class file has been
+ * already read, this method returns a reference to the
+ * <code>CtClass</code> created when that class file was read at the
+ * first time.
+ *
+ * <p>If <code>classname</code> ends with "[]", then this method
+ * returns a <code>CtClass</code> object for that array type.
+ *
+ * <p>To obtain an inner class, use "$" instead of "." for separating
+ * the enclosing class name and the inner class name.
+ *
+ * @param classname a fully-qualified class name.
+ */
+ public CtClass get(String classname) throws NotFoundException {
+ CtClass clazz;
+ if (classname == null)
+ clazz = null;
+ else
+ clazz = get0(classname, true);
+
+ if (clazz == null)
+ throw new NotFoundException(classname);
+ else {
+ clazz.incGetCounter();
+ return clazz;
+ }
+ }
+
+ /**
+ * Reads a class file from the source and returns a reference
+ * to the <code>CtClass</code>
+ * object representing that class file.
+ * This method is equivalent to <code>get</code> except
+ * that it returns <code>null</code> when a class file is
+ * not found and it never throws an exception.
+ *
+ * @param classname a fully-qualified class name.
+ * @return a <code>CtClass</code> object or <code>null</code>.
+ * @see #get(String)
+ * @see #find(String)
+ * @since 3.13
+ */
+ public CtClass getOrNull(String classname) {
+ CtClass clazz = null;
+ if (classname == null)
+ clazz = null;
+ else
+ try {
+ /* ClassPool.get0() never throws an exception
+ but its subclass may implement get0 that
+ may throw an exception.
+ */
+ clazz = get0(classname, true);
+ }
+ catch (NotFoundException e){}
+
+ if (clazz != null)
+ clazz.incGetCounter();
+
+ return clazz;
+ }
+
+ /**
+ * Returns a <code>CtClass</code> object with the given name.
+ * This is almost equivalent to <code>get(String)</code> except
+ * that classname can be an array-type "descriptor" (an encoded
+ * type name) such as <code>[Ljava/lang/Object;</code>.
+ *
+ * <p>Using this method is not recommended; this method should be
+ * used only to obtain the <code>CtClass</code> object
+ * with a name returned from <code>getClassInfo</code> in
+ * <code>javassist.bytecode.ClassPool</code>. <code>getClassInfo</code>
+ * returns a fully-qualified class name but, if the class is an array
+ * type, it returns a descriptor.
+ *
+ * @param classname a fully-qualified class name or a descriptor
+ * representing an array type.
+ * @see #get(String)
+ * @see javassist.bytecode.ConstPool#getClassInfo(int)
+ * @see javassist.bytecode.Descriptor#toCtClass(String, ClassPool)
+ * @since 3.8.1
+ */
+ public CtClass getCtClass(String classname) throws NotFoundException {
+ if (classname.charAt(0) == '[')
+ return Descriptor.toCtClass(classname, this);
+ else
+ return get(classname);
+ }
+
+ /**
+ * @param useCache false if the cached CtClass must be ignored.
+ * @param searchParent false if the parent class pool is not searched.
+ * @return null if the class could not be found.
+ */
+ protected synchronized CtClass get0(String classname, boolean useCache)
+ throws NotFoundException
+ {
+ CtClass clazz = null;
+ if (useCache) {
+ clazz = getCached(classname);
+ if (clazz != null)
+ return clazz;
+ }
+
+ if (!childFirstLookup && parent != null) {
+ clazz = parent.get0(classname, useCache);
+ if (clazz != null)
+ return clazz;
+ }
+
+ clazz = createCtClass(classname, useCache);
+ if (clazz != null) {
+ // clazz.getName() != classname if classname is "[L<name>;".
+ if (useCache)
+ cacheCtClass(clazz.getName(), clazz, false);
+
+ return clazz;
+ }
+
+ if (childFirstLookup && parent != null)
+ clazz = parent.get0(classname, useCache);
+
+ return clazz;
+ }
+
+ /**
+ * Creates a CtClass object representing the specified class.
+ * It first examines whether or not the corresponding class
+ * file exists. If yes, it creates a CtClass object.
+ *
+ * @return null if the class file could not be found.
+ */
+ protected CtClass createCtClass(String classname, boolean useCache) {
+ // accept "[L<class name>;" as a class name.
+ if (classname.charAt(0) == '[')
+ classname = Descriptor.toClassName(classname);
+
+ if (classname.endsWith("[]")) {
+ String base = classname.substring(0, classname.indexOf('['));
+ if ((!useCache || getCached(base) == null) && find(base) == null)
+ return null;
+ else
+ return new CtArray(classname, this);
+ }
+ else
+ if (find(classname) == null)
+ return null;
+ else
+ return new CtClassType(classname, this);
+ }
+
+ /**
+ * Searches the class path to obtain the URL of the class file
+ * specified by classname. It is also used to determine whether
+ * the class file exists.
+ *
+ * @param classname a fully-qualified class name.
+ * @return null if the class file could not be found.
+ * @see CtClass#getURL()
+ */
+ public URL find(String classname) {
+ return source.find(classname);
+ }
+
+ /*
+ * Is invoked by CtClassType.setName() and methods in this class.
+ * This method throws an exception if the class is already frozen or
+ * if this class pool cannot edit the class since it is in a parent
+ * class pool.
+ *
+ * @see checkNotExists(String)
+ */
+ void checkNotFrozen(String classname) throws RuntimeException {
+ CtClass clazz = getCached(classname);
+ if (clazz == null) {
+ if (!childFirstLookup && parent != null) {
+ try {
+ clazz = parent.get0(classname, true);
+ }
+ catch (NotFoundException e) {}
+ if (clazz != null)
+ throw new RuntimeException(classname
+ + " is in a parent ClassPool. Use the parent.");
+ }
+ }
+ else
+ if (clazz.isFrozen())
+ throw new RuntimeException(classname
+ + ": frozen class (cannot edit)");
+ }
+
+ /*
+ * This method returns null if this or its parent class pool does
+ * not contain a CtClass object with the class name.
+ *
+ * @see checkNotFrozen(String)
+ */
+ CtClass checkNotExists(String classname) {
+ CtClass clazz = getCached(classname);
+ if (clazz == null)
+ if (!childFirstLookup && parent != null) {
+ try {
+ clazz = parent.get0(classname, true);
+ }
+ catch (NotFoundException e) {}
+ }
+
+ return clazz;
+ }
+
+ /* for CtClassType.getClassFile2(). Don't delegate to the parent.
+ */
+ InputStream openClassfile(String classname) throws NotFoundException {
+ return source.openClassfile(classname);
+ }
+
+ void writeClassfile(String classname, OutputStream out)
+ throws NotFoundException, IOException, CannotCompileException
+ {
+ source.writeClassfile(classname, out);
+ }
+
+ /**
+ * Reads class files from the source and returns an array of
+ * <code>CtClass</code>
+ * objects representing those class files.
+ *
+ * <p>If an element of <code>classnames</code> ends with "[]",
+ * then this method
+ * returns a <code>CtClass</code> object for that array type.
+ *
+ * @param classnames an array of fully-qualified class name.
+ */
+ public CtClass[] get(String[] classnames) throws NotFoundException {
+ if (classnames == null)
+ return new CtClass[0];
+
+ int num = classnames.length;
+ CtClass[] result = new CtClass[num];
+ for (int i = 0; i < num; ++i)
+ result[i] = get(classnames[i]);
+
+ return result;
+ }
+
+ /**
+ * Reads a class file and obtains a compile-time method.
+ *
+ * @param classname the class name
+ * @param methodname the method name
+ * @see CtClass#getDeclaredMethod(String)
+ */
+ public CtMethod getMethod(String classname, String methodname)
+ throws NotFoundException
+ {
+ CtClass c = get(classname);
+ return c.getDeclaredMethod(methodname);
+ }
+
+ /**
+ * Creates a new class (or interface) from the given class file.
+ * If there already exists a class with the same name, the new class
+ * overwrites that previous class.
+ *
+ * <p>This method is used for creating a <code>CtClass</code> object
+ * directly from a class file. The qualified class name is obtained
+ * from the class file; you do not have to explicitly give the name.
+ *
+ * @param classfile class file.
+ * @throws RuntimeException if there is a frozen class with the
+ * the same name.
+ * @see #makeClassIfNew(InputStream)
+ * @see javassist.ByteArrayClassPath
+ */
+ public CtClass makeClass(InputStream classfile)
+ throws IOException, RuntimeException
+ {
+ return makeClass(classfile, true);
+ }
+
+ /**
+ * Creates a new class (or interface) from the given class file.
+ * If there already exists a class with the same name, the new class
+ * overwrites that previous class.
+ *
+ * <p>This method is used for creating a <code>CtClass</code> object
+ * directly from a class file. The qualified class name is obtained
+ * from the class file; you do not have to explicitly give the name.
+ *
+ * @param classfile class file.
+ * @param ifNotFrozen throws a RuntimeException if this parameter is true
+ * and there is a frozen class with the same name.
+ * @see javassist.ByteArrayClassPath
+ */
+ public CtClass makeClass(InputStream classfile, boolean ifNotFrozen)
+ throws IOException, RuntimeException
+ {
+ compress();
+ classfile = new BufferedInputStream(classfile);
+ CtClass clazz = new CtClassType(classfile, this);
+ clazz.checkModify();
+ String classname = clazz.getName();
+ if (ifNotFrozen)
+ checkNotFrozen(classname);
+
+ cacheCtClass(classname, clazz, true);
+ return clazz;
+ }
+
+ /**
+ * Creates a new class (or interface) from the given class file.
+ * If there already exists a class with the same name, this method
+ * returns the existing class; a new class is never created from
+ * the given class file.
+ *
+ * <p>This method is used for creating a <code>CtClass</code> object
+ * directly from a class file. The qualified class name is obtained
+ * from the class file; you do not have to explicitly give the name.
+ *
+ * @param classfile the class file.
+ * @see #makeClass(InputStream)
+ * @see javassist.ByteArrayClassPath
+ * @since 3.9
+ */
+ public CtClass makeClassIfNew(InputStream classfile)
+ throws IOException, RuntimeException
+ {
+ compress();
+ classfile = new BufferedInputStream(classfile);
+ CtClass clazz = new CtClassType(classfile, this);
+ clazz.checkModify();
+ String classname = clazz.getName();
+ CtClass found = checkNotExists(classname);
+ if (found != null)
+ return found;
+ else {
+ cacheCtClass(classname, clazz, true);
+ return clazz;
+ }
+ }
+
+ /**
+ * Creates a new public class.
+ * If there already exists a class with the same name, the new class
+ * overwrites that previous class.
+ *
+ * <p>If no constructor is explicitly added to the created new
+ * class, Javassist generates constructors and adds it when
+ * the class file is generated. It generates a new constructor
+ * for each constructor of the super class. The new constructor
+ * takes the same set of parameters and invokes the
+ * corresponding constructor of the super class. All the received
+ * parameters are passed to it.
+ *
+ * @param classname a fully-qualified class name.
+ * @throws RuntimeException if the existing class is frozen.
+ */
+ public CtClass makeClass(String classname) throws RuntimeException {
+ return makeClass(classname, null);
+ }
+
+ /**
+ * Creates a new public class.
+ * If there already exists a class/interface with the same name,
+ * the new class overwrites that previous class.
+ *
+ * <p>If no constructor is explicitly added to the created new
+ * class, Javassist generates constructors and adds it when
+ * the class file is generated. It generates a new constructor
+ * for each constructor of the super class. The new constructor
+ * takes the same set of parameters and invokes the
+ * corresponding constructor of the super class. All the received
+ * parameters are passed to it.
+ *
+ * @param classname a fully-qualified class name.
+ * @param superclass the super class.
+ * @throws RuntimeException if the existing class is frozen.
+ */
+ public synchronized CtClass makeClass(String classname, CtClass superclass)
+ throws RuntimeException
+ {
+ checkNotFrozen(classname);
+ CtClass clazz = new CtNewClass(classname, this, false, superclass);
+ cacheCtClass(classname, clazz, true);
+ return clazz;
+ }
+
+ /**
+ * Creates a new public nested class.
+ * This method is called by CtClassType.makeNestedClass().
+ *
+ * @param classname a fully-qualified class name.
+ * @return the nested class.
+ */
+ synchronized CtClass makeNestedClass(String classname) {
+ checkNotFrozen(classname);
+ CtClass clazz = new CtNewNestedClass(classname, this, false, null);
+ cacheCtClass(classname, clazz, true);
+ return clazz;
+ }
+
+ /**
+ * Creates a new public interface.
+ * If there already exists a class/interface with the same name,
+ * the new interface overwrites that previous one.
+ *
+ * @param name a fully-qualified interface name.
+ * @throws RuntimeException if the existing interface is frozen.
+ */
+ public CtClass makeInterface(String name) throws RuntimeException {
+ return makeInterface(name, null);
+ }
+
+ /**
+ * Creates a new public interface.
+ * If there already exists a class/interface with the same name,
+ * the new interface overwrites that previous one.
+ *
+ * @param name a fully-qualified interface name.
+ * @param superclass the super interface.
+ * @throws RuntimeException if the existing interface is frozen.
+ */
+ public synchronized CtClass makeInterface(String name, CtClass superclass)
+ throws RuntimeException
+ {
+ checkNotFrozen(name);
+ CtClass clazz = new CtNewClass(name, this, true, superclass);
+ cacheCtClass(name, clazz, true);
+ return clazz;
+ }
+
+ /**
+ * Appends the system search path to the end of the
+ * search path. The system search path
+ * usually includes the platform library, extension
+ * libraries, and the search path specified by the
+ * <code>-classpath</code> option or the <code>CLASSPATH</code>
+ * environment variable.
+ *
+ * @return the appended class path.
+ */
+ public ClassPath appendSystemPath() {
+ return source.appendSystemPath();
+ }
+
+ /**
+ * Insert a <code>ClassPath</code> object at the head of the
+ * search path.
+ *
+ * @return the inserted class path.
+ * @see javassist.ClassPath
+ * @see javassist.URLClassPath
+ * @see javassist.ByteArrayClassPath
+ */
+ public ClassPath insertClassPath(ClassPath cp) {
+ return source.insertClassPath(cp);
+ }
+
+ /**
+ * Appends a <code>ClassPath</code> object to the end of the
+ * search path.
+ *
+ * @return the appended class path.
+ * @see javassist.ClassPath
+ * @see javassist.URLClassPath
+ * @see javassist.ByteArrayClassPath
+ */
+ public ClassPath appendClassPath(ClassPath cp) {
+ return source.appendClassPath(cp);
+ }
+
+ /**
+ * Inserts a directory or a jar (or zip) file at the head of the
+ * search path.
+ *
+ * @param pathname the path name of the directory or jar file.
+ * It must not end with a path separator ("/").
+ * If the path name ends with "/*", then all the
+ * jar files matching the path name are inserted.
+ *
+ * @return the inserted class path.
+ * @throws NotFoundException if the jar file is not found.
+ */
+ public ClassPath insertClassPath(String pathname)
+ throws NotFoundException
+ {
+ return source.insertClassPath(pathname);
+ }
+
+ /**
+ * Appends a directory or a jar (or zip) file to the end of the
+ * search path.
+ *
+ * @param pathname the path name of the directory or jar file.
+ * It must not end with a path separator ("/").
+ * If the path name ends with "/*", then all the
+ * jar files matching the path name are appended.
+ *
+ * @return the appended class path.
+ * @throws NotFoundException if the jar file is not found.
+ */
+ public ClassPath appendClassPath(String pathname)
+ throws NotFoundException
+ {
+ return source.appendClassPath(pathname);
+ }
+
+ /**
+ * Detatches the <code>ClassPath</code> object from the search path.
+ * The detached <code>ClassPath</code> object cannot be added
+ * to the pathagain.
+ */
+ public void removeClassPath(ClassPath cp) {
+ source.removeClassPath(cp);
+ }
+
+ /**
+ * Appends directories and jar files for search.
+ *
+ * <p>The elements of the given path list must be separated by colons
+ * in Unix or semi-colons in Windows.
+ *
+ * @param pathlist a (semi)colon-separated list of
+ * the path names of directories and jar files.
+ * The directory name must not end with a path
+ * separator ("/").
+ * @throws NotFoundException if a jar file is not found.
+ */
+ public void appendPathList(String pathlist) throws NotFoundException {
+ char sep = File.pathSeparatorChar;
+ int i = 0;
+ for (;;) {
+ int j = pathlist.indexOf(sep, i);
+ if (j < 0) {
+ appendClassPath(pathlist.substring(i));
+ break;
+ }
+ else {
+ appendClassPath(pathlist.substring(i, j));
+ i = j + 1;
+ }
+ }
+ }
+
+ /**
+ * Converts the given class to a <code>java.lang.Class</code> object.
+ * Once this method is called, further modifications are not
+ * allowed any more.
+ * To load the class, this method uses the context class loader
+ * of the current thread. It is obtained by calling
+ * <code>getClassLoader()</code>.
+ *
+ * <p>This behavior can be changed by subclassing the pool and changing
+ * the <code>getClassLoader()</code> method.
+ * If the program is running on some application
+ * server, the context class loader might be inappropriate to load the
+ * class.
+ *
+ * <p>This method is provided for convenience. If you need more
+ * complex functionality, you should write your own class loader.
+ *
+ * <p><b>Warining:</b> A Class object returned by this method may not
+ * work with a security manager or a signed jar file because a
+ * protection domain is not specified.
+ *
+ * @see #toClass(CtClass, java.lang.ClassLoader, ProtectionDomain)
+ * @see #getClassLoader()
+ */
+ public Class toClass(CtClass clazz) throws CannotCompileException {
+ // Some subclasses of ClassPool may override toClass(CtClass,ClassLoader).
+ // So we should call that method instead of toClass(.., ProtectionDomain).
+ return toClass(clazz, getClassLoader());
+ }
+
+ /**
+ * Get the classloader for <code>toClass()</code>, <code>getAnnotations()</code> in
+ * <code>CtClass</code>, etc.
+ *
+ * <p>The default is the context class loader.
+ *
+ * @return the classloader for the pool
+ * @see #toClass(CtClass)
+ * @see CtClass#getAnnotations()
+ */
+ public ClassLoader getClassLoader() {
+ return getContextClassLoader();
+ }
+
+ /**
+ * Obtains a class loader that seems appropriate to look up a class
+ * by name.
+ */
+ static ClassLoader getContextClassLoader() {
+ return Thread.currentThread().getContextClassLoader();
+ }
+
+ /**
+ * Converts the class to a <code>java.lang.Class</code> object.
+ * Do not override this method any more at a subclass because
+ * <code>toClass(CtClass)</code> never calls this method.
+ *
+ * <p><b>Warining:</b> A Class object returned by this method may not
+ * work with a security manager or a signed jar file because a
+ * protection domain is not specified.
+ *
+ * @deprecated Replaced by {@link #toClass(CtClass,ClassLoader,ProtectionDomain)}.
+ * A subclass of <code>ClassPool</code> that has been
+ * overriding this method should be modified. It should override
+ * {@link #toClass(CtClass,ClassLoader,ProtectionDomain)}.
+ */
+ public Class toClass(CtClass ct, ClassLoader loader)
+ throws CannotCompileException
+ {
+ return toClass(ct, loader, null);
+ }
+
+ /**
+ * Converts the class to a <code>java.lang.Class</code> object.
+ * Once this method is called, further modifications are not allowed
+ * any more.
+ *
+ * <p>The class file represented by the given <code>CtClass</code> is
+ * loaded by the given class loader to construct a
+ * <code>java.lang.Class</code> object. Since a private method
+ * on the class loader is invoked through the reflection API,
+ * the caller must have permissions to do that.
+ *
+ * <p>An easy way to obtain <code>ProtectionDomain</code> object is
+ * to call <code>getProtectionDomain()</code>
+ * in <code>java.lang.Class</code>. It returns the domain that the
+ * class belongs to.
+ *
+ * <p>This method is provided for convenience. If you need more
+ * complex functionality, you should write your own class loader.
+ *
+ * @param loader the class loader used to load this class.
+ * For example, the loader returned by
+ * <code>getClassLoader()</code> can be used
+ * for this parameter.
+ * @param domain the protection domain for the class.
+ * If it is null, the default domain created
+ * by <code>java.lang.ClassLoader</code> is used.
+ *
+ * @see #getClassLoader()
+ * @since 3.3
+ */
+ public Class toClass(CtClass ct, ClassLoader loader, ProtectionDomain domain)
+ throws CannotCompileException
+ {
+ try {
+ byte[] b = ct.toBytecode();
+ java.lang.reflect.Method method;
+ Object[] args;
+ if (domain == null) {
+ method = defineClass1;
+ args = new Object[] { ct.getName(), b, new Integer(0),
+ new Integer(b.length)};
+ }
+ else {
+ method = defineClass2;
+ args = new Object[] { ct.getName(), b, new Integer(0),
+ new Integer(b.length), domain};
+ }
+
+ return toClass2(method, loader, args);
+ }
+ catch (RuntimeException e) {
+ throw e;
+ }
+ catch (java.lang.reflect.InvocationTargetException e) {
+ throw new CannotCompileException(e.getTargetException());
+ }
+ catch (Exception e) {
+ throw new CannotCompileException(e);
+ }
+ }
+
+ private static synchronized Class toClass2(Method method,
+ ClassLoader loader, Object[] args)
+ throws Exception
+ {
+ method.setAccessible(true);
+ try {
+ return (Class)method.invoke(loader, args);
+ }
+ finally {
+ method.setAccessible(false);
+ }
+ }
+}
diff --git a/src/main/javassist/ClassPoolTail.java b/src/main/javassist/ClassPoolTail.java
new file mode 100644
index 0000000..aa1aefe
--- /dev/null
+++ b/src/main/javassist/ClassPoolTail.java
@@ -0,0 +1,442 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist;
+
+import java.io.*;
+import java.util.jar.*;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Hashtable;
+
+final class ClassPathList {
+ ClassPathList next;
+ ClassPath path;
+
+ ClassPathList(ClassPath p, ClassPathList n) {
+ next = n;
+ path = p;
+ }
+}
+
+final class DirClassPath implements ClassPath {
+ String directory;
+
+ DirClassPath(String dirName) {
+ directory = dirName;
+ }
+
+ public InputStream openClassfile(String classname) {
+ try {
+ char sep = File.separatorChar;
+ String filename = directory + sep
+ + classname.replace('.', sep) + ".class";
+ return new FileInputStream(filename.toString());
+ }
+ catch (FileNotFoundException e) {}
+ catch (SecurityException e) {}
+ return null;
+ }
+
+ public URL find(String classname) {
+ char sep = File.separatorChar;
+ String filename = directory + sep
+ + classname.replace('.', sep) + ".class";
+ File f = new File(filename);
+ if (f.exists())
+ try {
+ return f.getCanonicalFile().toURI().toURL();
+ }
+ catch (MalformedURLException e) {}
+ catch (IOException e) {}
+
+ return null;
+ }
+
+ public void close() {}
+
+ public String toString() {
+ return directory;
+ }
+}
+
+final class JarDirClassPath implements ClassPath {
+ JarClassPath[] jars;
+
+ JarDirClassPath(String dirName) throws NotFoundException {
+ File[] files = new File(dirName).listFiles(new FilenameFilter() {
+ public boolean accept(File dir, String name) {
+ name = name.toLowerCase();
+ return name.endsWith(".jar") || name.endsWith(".zip");
+ }
+ });
+
+ if (files != null) {
+ jars = new JarClassPath[files.length];
+ for (int i = 0; i < files.length; i++)
+ jars[i] = new JarClassPath(files[i].getPath());
+ }
+ }
+
+ public InputStream openClassfile(String classname) throws NotFoundException {
+ if (jars != null)
+ for (int i = 0; i < jars.length; i++) {
+ InputStream is = jars[i].openClassfile(classname);
+ if (is != null)
+ return is;
+ }
+
+ return null; // not found
+ }
+
+ public URL find(String classname) {
+ if (jars != null)
+ for (int i = 0; i < jars.length; i++) {
+ URL url = jars[i].find(classname);
+ if (url != null)
+ return url;
+ }
+
+ return null; // not found
+ }
+
+ public void close() {
+ if (jars != null)
+ for (int i = 0; i < jars.length; i++)
+ jars[i].close();
+ }
+}
+
+final class JarClassPath implements ClassPath {
+ JarFile jarfile;
+ String jarfileURL;
+
+ JarClassPath(String pathname) throws NotFoundException {
+ try {
+ jarfile = new JarFile(pathname);
+ jarfileURL = new File(pathname).getCanonicalFile()
+ .toURI().toURL().toString();
+ return;
+ }
+ catch (IOException e) {}
+ throw new NotFoundException(pathname);
+ }
+
+ public InputStream openClassfile(String classname)
+ throws NotFoundException
+ {
+ try {
+ String jarname = classname.replace('.', '/') + ".class";
+ JarEntry je = jarfile.getJarEntry(jarname);
+ if (je != null)
+ return jarfile.getInputStream(je);
+ else
+ return null; // not found
+ }
+ catch (IOException e) {}
+ throw new NotFoundException("broken jar file?: "
+ + jarfile.getName());
+ }
+
+ public URL find(String classname) {
+ String jarname = classname.replace('.', '/') + ".class";
+ JarEntry je = jarfile.getJarEntry(jarname);
+ if (je != null)
+ try {
+ return new URL("jar:" + jarfileURL + "!/" + jarname);
+ }
+ catch (MalformedURLException e) {}
+
+ return null; // not found
+ }
+
+ public void close() {
+ try {
+ jarfile.close();
+ jarfile = null;
+ }
+ catch (IOException e) {}
+ }
+
+ public String toString() {
+ return jarfile == null ? "<null>" : jarfile.toString();
+ }
+}
+
+final class ClassPoolTail {
+ protected ClassPathList pathList;
+ private Hashtable packages; // should be synchronized.
+
+ public ClassPoolTail() {
+ pathList = null;
+ packages = new Hashtable();
+ }
+
+ public String toString() {
+ StringBuffer buf = new StringBuffer();
+ buf.append("[class path: ");
+ ClassPathList list = pathList;
+ while (list != null) {
+ buf.append(list.path.toString());
+ buf.append(File.pathSeparatorChar);
+ list = list.next;
+ }
+
+ buf.append(']');
+ return buf.toString();
+ }
+
+ public synchronized ClassPath insertClassPath(ClassPath cp) {
+ pathList = new ClassPathList(cp, pathList);
+ return cp;
+ }
+
+ public synchronized ClassPath appendClassPath(ClassPath cp) {
+ ClassPathList tail = new ClassPathList(cp, null);
+ ClassPathList list = pathList;
+ if (list == null)
+ pathList = tail;
+ else {
+ while (list.next != null)
+ list = list.next;
+
+ list.next = tail;
+ }
+
+ return cp;
+ }
+
+ public synchronized void removeClassPath(ClassPath cp) {
+ ClassPathList list = pathList;
+ if (list != null)
+ if (list.path == cp)
+ pathList = list.next;
+ else {
+ while (list.next != null)
+ if (list.next.path == cp)
+ list.next = list.next.next;
+ else
+ list = list.next;
+ }
+
+ cp.close();
+ }
+
+ public ClassPath appendSystemPath() {
+ return appendClassPath(new ClassClassPath());
+ }
+
+ public ClassPath insertClassPath(String pathname)
+ throws NotFoundException
+ {
+ return insertClassPath(makePathObject(pathname));
+ }
+
+ public ClassPath appendClassPath(String pathname)
+ throws NotFoundException
+ {
+ return appendClassPath(makePathObject(pathname));
+ }
+
+ private static ClassPath makePathObject(String pathname)
+ throws NotFoundException
+ {
+ String lower = pathname.toLowerCase();
+ if (lower.endsWith(".jar") || lower.endsWith(".zip"))
+ return new JarClassPath(pathname);
+
+ int len = pathname.length();
+ if (len > 2 && pathname.charAt(len - 1) == '*'
+ && (pathname.charAt(len - 2) == '/'
+ || pathname.charAt(len - 2) == File.separatorChar)) {
+ String dir = pathname.substring(0, len - 2);
+ return new JarDirClassPath(dir);
+ }
+
+ return new DirClassPath(pathname);
+ }
+
+ /**
+ * You can record "System" so that java.lang.System can be quickly
+ * found although "System" is not a package name.
+ */
+ public void recordInvalidClassName(String name) {
+ packages.put(name, name);
+ }
+
+ /**
+ * This method does not close the output stream.
+ */
+ void writeClassfile(String classname, OutputStream out)
+ throws NotFoundException, IOException, CannotCompileException
+ {
+ InputStream fin = openClassfile(classname);
+ if (fin == null)
+ throw new NotFoundException(classname);
+
+ try {
+ copyStream(fin, out);
+ }
+ finally {
+ fin.close();
+ }
+ }
+
+ /*
+ -- faster version --
+ void checkClassName(String classname) throws NotFoundException {
+ if (find(classname) == null)
+ throw new NotFoundException(classname);
+ }
+
+ -- slower version --
+
+ void checkClassName(String classname) throws NotFoundException {
+ InputStream fin = openClassfile(classname);
+ try {
+ fin.close();
+ }
+ catch (IOException e) {}
+ }
+ */
+
+
+ /**
+ * Opens the class file for the class specified by
+ * <code>classname</code>.
+ *
+ * @param classname a fully-qualified class name
+ * @return null if the file has not been found.
+ * @throws NotFoundException if any error is reported by ClassPath.
+ */
+ InputStream openClassfile(String classname)
+ throws NotFoundException
+ {
+ if (packages.get(classname) != null)
+ return null; // not found
+
+ ClassPathList list = pathList;
+ InputStream ins = null;
+ NotFoundException error = null;
+ while (list != null) {
+ try {
+ ins = list.path.openClassfile(classname);
+ }
+ catch (NotFoundException e) {
+ if (error == null)
+ error = e;
+ }
+
+ if (ins == null)
+ list = list.next;
+ else
+ return ins;
+ }
+
+ if (error != null)
+ throw error;
+ else
+ return null; // not found
+ }
+
+ /**
+ * Searches the class path to obtain the URL of the class file
+ * specified by classname. It is also used to determine whether
+ * the class file exists.
+ *
+ * @param classname a fully-qualified class name.
+ * @return null if the class file could not be found.
+ */
+ public URL find(String classname) {
+ if (packages.get(classname) != null)
+ return null;
+
+ ClassPathList list = pathList;
+ URL url = null;
+ while (list != null) {
+ url = list.path.find(classname);
+ if (url == null)
+ list = list.next;
+ else
+ return url;
+ }
+
+ return null;
+ }
+
+ /**
+ * Reads from an input stream until it reaches the end.
+ *
+ * @return the contents of that input stream
+ */
+ public static byte[] readStream(InputStream fin) throws IOException {
+ byte[][] bufs = new byte[8][];
+ int bufsize = 4096;
+
+ for (int i = 0; i < 8; ++i) {
+ bufs[i] = new byte[bufsize];
+ int size = 0;
+ int len = 0;
+ do {
+ len = fin.read(bufs[i], size, bufsize - size);
+ if (len >= 0)
+ size += len;
+ else {
+ byte[] result = new byte[bufsize - 4096 + size];
+ int s = 0;
+ for (int j = 0; j < i; ++j) {
+ System.arraycopy(bufs[j], 0, result, s, s + 4096);
+ s = s + s + 4096;
+ }
+
+ System.arraycopy(bufs[i], 0, result, s, size);
+ return result;
+ }
+ } while (size < bufsize);
+ bufsize *= 2;
+ }
+
+ throw new IOException("too much data");
+ }
+
+ /**
+ * Reads from an input stream and write to an output stream
+ * until it reaches the end. This method does not close the
+ * streams.
+ */
+ public static void copyStream(InputStream fin, OutputStream fout)
+ throws IOException
+ {
+ int bufsize = 4096;
+ for (int i = 0; i < 8; ++i) {
+ byte[] buf = new byte[bufsize];
+ int size = 0;
+ int len = 0;
+ do {
+ len = fin.read(buf, size, bufsize - size);
+ if (len >= 0)
+ size += len;
+ else {
+ fout.write(buf, 0, size);
+ return;
+ }
+ } while (size < bufsize);
+ fout.write(buf);
+ bufsize *= 2;
+ }
+
+ throw new IOException("too much data");
+ }
+}
diff --git a/src/main/javassist/CodeConverter.java b/src/main/javassist/CodeConverter.java
new file mode 100644
index 0000000..44881d5
--- /dev/null
+++ b/src/main/javassist/CodeConverter.java
@@ -0,0 +1,799 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist;
+
+import javassist.bytecode.*;
+import javassist.convert.*;
+
+/**
+ * Simple translator of method bodies
+ * (also see the <code>javassist.expr</code> package).
+ *
+ * <p>Instances of this class specifies how to instrument of the
+ * bytecodes representing a method body. They are passed to
+ * <code>CtClass.instrument()</code> or
+ * <code>CtMethod.instrument()</code> as a parameter.
+ *
+ * <p>Example:
+ * <ul><pre>
+ * ClassPool cp = ClassPool.getDefault();
+ * CtClass point = cp.get("Point");
+ * CtClass singleton = cp.get("Singleton");
+ * CtClass client = cp.get("Client");
+ * CodeConverter conv = new CodeConverter();
+ * conv.replaceNew(point, singleton, "makePoint");
+ * client.instrument(conv);
+ * </pre></ul>
+ *
+ * <p>This program substitutes "<code>Singleton.makePoint()</code>"
+ * for all occurrences of "<code>new Point()</code>"
+ * appearing in methods declared in a <code>Client</code> class.
+ *
+ * @see javassist.CtClass#instrument(CodeConverter)
+ * @see javassist.CtMethod#instrument(CodeConverter)
+ * @see javassist.expr.ExprEditor
+ */
+public class CodeConverter {
+ protected Transformer transformers = null;
+
+ /**
+ * Modify a method body so that instantiation of the specified class
+ * is replaced with a call to the specified static method. For example,
+ * <code>replaceNew(ctPoint, ctSingleton, "createPoint")</code>
+ * (where <code>ctPoint</code> and <code>ctSingleton</code> are
+ * compile-time classes for class <code>Point</code> and class
+ * <code>Singleton</code>, respectively)
+ * replaces all occurrences of:
+ *
+ * <ul><code>new Point(x, y)</code></ul>
+ *
+ * in the method body with:
+ *
+ * <ul><code>Singleton.createPoint(x, y)</code></ul>
+ *
+ * <p>This enables to intercept instantiation of <code>Point</code>
+ * and change the samentics. For example, the following
+ * <code>createPoint()</code> implements the singleton pattern:
+ *
+ * <ul><pre>public static Point createPoint(int x, int y) {
+ * if (aPoint == null)
+ * aPoint = new Point(x, y);
+ * return aPoint;
+ * }
+ * </pre></ul>
+ *
+ * <p>The static method call substituted for the original <code>new</code>
+ * expression must be
+ * able to receive the same set of parameters as the original
+ * constructor. If there are multiple constructors with different
+ * parameter types, then there must be multiple static methods
+ * with the same name but different parameter types.
+ *
+ * <p>The return type of the substituted static method must be
+ * the exactly same as the type of the instantiated class specified by
+ * <code>newClass</code>.
+ *
+ * @param newClass the instantiated class.
+ * @param calledClass the class in which the static method is
+ * declared.
+ * @param calledMethod the name of the static method.
+ */
+ public void replaceNew(CtClass newClass,
+ CtClass calledClass, String calledMethod) {
+ transformers = new TransformNew(transformers, newClass.getName(),
+ calledClass.getName(), calledMethod);
+ }
+
+ /**
+ * Modify a method body so that instantiation of the class
+ * specified by <code>oldClass</code>
+ * is replaced with instantiation of another class <code>newClass</code>.
+ * For example,
+ * <code>replaceNew(ctPoint, ctPoint2)</code>
+ * (where <code>ctPoint</code> and <code>ctPoint2</code> are
+ * compile-time classes for class <code>Point</code> and class
+ * <code>Point2</code>, respectively)
+ * replaces all occurrences of:
+ *
+ * <ul><code>new Point(x, y)</code></ul>
+ *
+ * in the method body with:
+ *
+ * <ul><code>new Point2(x, y)</code></ul>
+ *
+ * <p>Note that <code>Point2</code> must be type-compatible with <code>Point</code>.
+ * It must have the same set of methods, fields, and constructors as the
+ * replaced class.
+ */
+ public void replaceNew(CtClass oldClass, CtClass newClass) {
+ transformers = new TransformNewClass(transformers, oldClass.getName(),
+ newClass.getName());
+ }
+
+ /**
+ * Modify a method body so that field read/write expressions access
+ * a different field from the original one.
+ *
+ * <p>Note that this method changes only the filed name and the class
+ * declaring the field; the type of the target object does not change.
+ * Therefore, the substituted field must be declared in the same class
+ * or a superclass of the original class.
+ *
+ * <p>Also, <code>clazz</code> and <code>newClass</code> must specify
+ * the class directly declaring the field. They must not specify
+ * a subclass of that class.
+ *
+ * @param field the originally accessed field.
+ * @param newClass the class declaring the substituted field.
+ * @param newFieldname the name of the substituted field.
+ */
+ public void redirectFieldAccess(CtField field,
+ CtClass newClass, String newFieldname) {
+ transformers = new TransformFieldAccess(transformers, field,
+ newClass.getName(),
+ newFieldname);
+ }
+
+ /**
+ * Modify a method body so that an expression reading the specified
+ * field is replaced with a call to the specified <i>static</i> method.
+ * This static method receives the target object of the original
+ * read expression as a parameter. It must return a value of
+ * the same type as the field.
+ *
+ * <p>For example, the program below
+ *
+ * <ul><pre>Point p = new Point();
+ * int newX = p.x + 3;</pre></ul>
+ *
+ * <p>can be translated into:
+ *
+ * <ul><pre>Point p = new Point();
+ * int newX = Accessor.readX(p) + 3;</pre></ul>
+ *
+ * <p>where
+ *
+ * <ul><pre>public class Accessor {
+ * public static int readX(Object target) { ... }
+ * }</pre></ul>
+ *
+ * <p>The type of the parameter of <code>readX()</code> must
+ * be <code>java.lang.Object</code> independently of the actual
+ * type of <code>target</code>. The return type must be the same
+ * as the field type.
+ *
+ * @param field the field.
+ * @param calledClass the class in which the static method is
+ * declared.
+ * @param calledMethod the name of the static method.
+ */
+ public void replaceFieldRead(CtField field,
+ CtClass calledClass, String calledMethod) {
+ transformers = new TransformReadField(transformers, field,
+ calledClass.getName(),
+ calledMethod);
+ }
+
+ /**
+ * Modify a method body so that an expression writing the specified
+ * field is replaced with a call to the specified static method.
+ * This static method receives two parameters: the target object of
+ * the original
+ * write expression and the assigned value. The return type of the
+ * static method is <code>void</code>.
+ *
+ * <p>For example, the program below
+ *
+ * <ul><pre>Point p = new Point();
+ * p.x = 3;</pre></ul>
+ *
+ * <p>can be translated into:
+ *
+ * <ul><pre>Point p = new Point();
+ * Accessor.writeX(3);</pre></ul>
+ *
+ * <p>where
+ *
+ * <ul><pre>public class Accessor {
+ * public static void writeX(Object target, int value) { ... }
+ * }</pre></ul>
+ *
+ * <p>The type of the first parameter of <code>writeX()</code> must
+ * be <code>java.lang.Object</code> independently of the actual
+ * type of <code>target</code>. The type of the second parameter
+ * is the same as the field type.
+ *
+ * @param field the field.
+ * @param calledClass the class in which the static method is
+ * declared.
+ * @param calledMethod the name of the static method.
+ */
+ public void replaceFieldWrite(CtField field,
+ CtClass calledClass, String calledMethod) {
+ transformers = new TransformWriteField(transformers, field,
+ calledClass.getName(),
+ calledMethod);
+ }
+
+ /**
+ * Modify a method body, so that ALL accesses to an array are replaced with
+ * calls to static methods within another class. In the case of reading an
+ * element from the array, this is replaced with a call to a static method with
+ * the array and the index as arguments, the return value is the value read from
+ * the array. If writing to an array, this is replaced with a call to a static
+ * method with the array, index and new value as parameters, the return value of
+ * the static method is <code>void</code>.
+ *
+ * <p>The <code>calledClass</code> parameter is the class containing the static methods to be used
+ * for array replacement. The <code>names</code> parameter points to an implementation of
+ * <code>ArrayAccessReplacementMethodNames</code> which specifies the names of the method to be
+ * used for access for each type of array. For example reading from an <code>int[]</code> will
+ * require a different method than if writing to an <code>int[]</code>, and writing to a <code>long[]</code>
+ * will require a different method than if writing to a <code>byte[]</code>. If the implementation
+ * of <code>ArrayAccessReplacementMethodNames</code> does not contain the name for access for a
+ * type of array, that access is not replaced.
+ *
+ * <p>A default implementation of <code>ArrayAccessReplacementMethodNames</code> called
+ * <code>DefaultArrayAccessReplacementMethodNames</code> has been provided and is what is used in the
+ * following example. This also assumes that <code>'foo.ArrayAdvisor'</code> is the name of the
+ * <code>CtClass</code> passed in.
+ *
+ * <p>If we have the following class:
+ * <pre>class POJO{
+ * int[] ints = new int[]{1, 2, 3, 4, 5};
+ * long[] longs = new int[]{10, 20, 30};
+ * Object objects = new Object[]{true, false};
+ * Integer[] integers = new Integer[]{new Integer(10)};
+ * }
+ * </pre>
+ * and this is accessed as:
+ * <pre>POJO p = new POJO();
+ *
+ * //Write to int array
+ * p.ints[2] = 7;
+ *
+ * //Read from int array
+ * int i = p.ints[2];
+ *
+ * //Write to long array
+ * p.longs[2] = 1000L;
+ *
+ * //Read from long array
+ * long l = p.longs[2];
+ *
+ * //Write to Object array
+ * p.objects[2] = "Hello";
+ *
+ * //Read from Object array
+ * Object o = p.objects[2];
+ *
+ * //Write to Integer array
+ * Integer integer = new Integer(5);
+ * p.integers[0] = integer;
+ *
+ * //Read from Object array
+ * integer = p.integers[0];
+ * </pre>
+ *
+ * Following instrumentation we will have
+ * <pre>POJO p = new POJO();
+ *
+ * //Write to int array
+ * ArrayAdvisor.arrayWriteInt(p.ints, 2, 7);
+ *
+ * //Read from int array
+ * int i = ArrayAdvisor.arrayReadInt(p.ints, 2);
+ *
+ * //Write to long array
+ * ArrayAdvisor.arrayWriteLong(p.longs, 2, 1000L);
+ *
+ * //Read from long array
+ * long l = ArrayAdvisor.arrayReadLong(p.longs, 2);
+ *
+ * //Write to Object array
+ * ArrayAdvisor.arrayWriteObject(p.objects, 2, "Hello");
+ *
+ * //Read from Object array
+ * Object o = ArrayAdvisor.arrayReadObject(p.objects, 2);
+ *
+ * //Write to Integer array
+ * Integer integer = new Integer(5);
+ * ArrayAdvisor.arrayWriteObject(p.integers, 0, integer);
+ *
+ * //Read from Object array
+ * integer = ArrayAdvisor.arrayWriteObject(p.integers, 0);
+ * </pre>
+ *
+ * @see DefaultArrayAccessReplacementMethodNames
+ *
+ * @param calledClass the class containing the static methods.
+ * @param names contains the names of the methods to replace
+ * the different kinds of array access with.
+ */
+ public void replaceArrayAccess(CtClass calledClass, ArrayAccessReplacementMethodNames names)
+ throws NotFoundException
+ {
+ transformers = new TransformAccessArrayField(transformers, calledClass.getName(), names);
+ }
+
+ /**
+ * Modify method invocations in a method body so that a different
+ * method will be invoked.
+ *
+ * <p>Note that the target object, the parameters, or
+ * the type of invocation
+ * (static method call, interface call, or private method call)
+ * are not modified. Only the method name is changed. The substituted
+ * method must have the same signature that the original one has.
+ * If the original method is a static method, the substituted method
+ * must be static.
+ *
+ * @param origMethod original method
+ * @param substMethod substituted method
+ */
+ public void redirectMethodCall(CtMethod origMethod,
+ CtMethod substMethod)
+ throws CannotCompileException
+ {
+ String d1 = origMethod.getMethodInfo2().getDescriptor();
+ String d2 = substMethod.getMethodInfo2().getDescriptor();
+ if (!d1.equals(d2))
+ throw new CannotCompileException("signature mismatch: "
+ + substMethod.getLongName());
+
+ int mod1 = origMethod.getModifiers();
+ int mod2 = substMethod.getModifiers();
+ if (Modifier.isStatic(mod1) != Modifier.isStatic(mod2)
+ || (Modifier.isPrivate(mod1) && !Modifier.isPrivate(mod2))
+ || origMethod.getDeclaringClass().isInterface()
+ != substMethod.getDeclaringClass().isInterface())
+ throw new CannotCompileException("invoke-type mismatch "
+ + substMethod.getLongName());
+
+ transformers = new TransformCall(transformers, origMethod,
+ substMethod);
+ }
+
+ /**
+ * Correct invocations to a method that has been renamed.
+ * If a method is renamed, calls to that method must be also
+ * modified so that the method with the new name will be called.
+ *
+ * <p>The method must be declared in the same class before and
+ * after it is renamed.
+ *
+ * <p>Note that the target object, the parameters, or
+ * the type of invocation
+ * (static method call, interface call, or private method call)
+ * are not modified. Only the method name is changed.
+ *
+ * @param oldMethodName the old name of the method.
+ * @param newMethod the method with the new name.
+ * @see javassist.CtMethod#setName(String)
+ */
+ public void redirectMethodCall(String oldMethodName,
+ CtMethod newMethod)
+ throws CannotCompileException
+ {
+ transformers
+ = new TransformCall(transformers, oldMethodName, newMethod);
+ }
+
+ /**
+ * Insert a call to another method before an existing method call.
+ * That "before" method must be static. The return type must be
+ * <code>void</code>. As parameters, the before method receives
+ * the target object and all the parameters to the originally invoked
+ * method. For example, if the originally invoked method is
+ * <code>move()</code>:
+ *
+ * <ul><pre>class Point {
+ * Point move(int x, int y) { ... }
+ * }</pre></ul>
+ *
+ * <p>Then the before method must be something like this:
+ *
+ * <ul><pre>class Verbose {
+ * static void print(Point target, int x, int y) { ... }
+ * }</pre></ul>
+ *
+ * <p>The <code>CodeConverter</code> would translate bytecode
+ * equivalent to:
+ *
+ * <ul><pre>Point p2 = p.move(x + y, 0);</pre></ul>
+ *
+ * <p>into the bytecode equivalent to:
+ *
+ * <ul><pre>int tmp1 = x + y;
+ * int tmp2 = 0;
+ * Verbose.print(p, tmp1, tmp2);
+ * Point p2 = p.move(tmp1, tmp2);</pre></ul>
+ *
+ * @param origMethod the method originally invoked.
+ * @param beforeMethod the method invoked before
+ * <code>origMethod</code>.
+ */
+ public void insertBeforeMethod(CtMethod origMethod,
+ CtMethod beforeMethod)
+ throws CannotCompileException
+ {
+ try {
+ transformers = new TransformBefore(transformers, origMethod,
+ beforeMethod);
+ }
+ catch (NotFoundException e) {
+ throw new CannotCompileException(e);
+ }
+ }
+
+ /**
+ * Inserts a call to another method after an existing method call.
+ * That "after" method must be static. The return type must be
+ * <code>void</code>. As parameters, the after method receives
+ * the target object and all the parameters to the originally invoked
+ * method. For example, if the originally invoked method is
+ * <code>move()</code>:
+ *
+ * <ul><pre>class Point {
+ * Point move(int x, int y) { ... }
+ * }</pre></ul>
+ *
+ * <p>Then the after method must be something like this:
+ *
+ * <ul><pre>class Verbose {
+ * static void print(Point target, int x, int y) { ... }
+ * }</pre></ul>
+ *
+ * <p>The <code>CodeConverter</code> would translate bytecode
+ * equivalent to:
+ *
+ * <ul><pre>Point p2 = p.move(x + y, 0);</pre></ul>
+ *
+ * <p>into the bytecode equivalent to:
+ *
+ * <ul><pre>int tmp1 = x + y;
+ * int tmp2 = 0;
+ * Point p2 = p.move(tmp1, tmp2);
+ * Verbose.print(p, tmp1, tmp2);</pre></ul>
+ *
+ * @param origMethod the method originally invoked.
+ * @param afterMethod the method invoked after
+ * <code>origMethod</code>.
+ */
+ public void insertAfterMethod(CtMethod origMethod,
+ CtMethod afterMethod)
+ throws CannotCompileException
+ {
+ try {
+ transformers = new TransformAfter(transformers, origMethod,
+ afterMethod);
+ }
+ catch (NotFoundException e) {
+ throw new CannotCompileException(e);
+ }
+ }
+
+ /**
+ * Performs code conversion.
+ */
+ protected void doit(CtClass clazz, MethodInfo minfo, ConstPool cp)
+ throws CannotCompileException
+ {
+ Transformer t;
+ CodeAttribute codeAttr = minfo.getCodeAttribute();
+ if (codeAttr == null || transformers == null)
+ return;
+ for (t = transformers; t != null; t = t.getNext())
+ t.initialize(cp, clazz, minfo);
+
+ CodeIterator iterator = codeAttr.iterator();
+ while (iterator.hasNext()) {
+ try {
+ int pos = iterator.next();
+ for (t = transformers; t != null; t = t.getNext())
+ pos = t.transform(clazz, pos, iterator, cp);
+ }
+ catch (BadBytecode e) {
+ throw new CannotCompileException(e);
+ }
+ }
+
+ int locals = 0;
+ int stack = 0;
+ for (t = transformers; t != null; t = t.getNext()) {
+ int s = t.extraLocals();
+ if (s > locals)
+ locals = s;
+
+ s = t.extraStack();
+ if (s > stack)
+ stack = s;
+ }
+
+ for (t = transformers; t != null; t = t.getNext())
+ t.clean();
+
+ if (locals > 0)
+ codeAttr.setMaxLocals(codeAttr.getMaxLocals() + locals);
+
+ if (stack > 0)
+ codeAttr.setMaxStack(codeAttr.getMaxStack() + stack);
+ }
+
+ /**
+ * Interface containing the method names to be used
+ * as array access replacements.
+ *
+ * @author <a href="kabir.khan@jboss.com">Kabir Khan</a>
+ * @version $Revision: 1.16 $
+ */
+ public interface ArrayAccessReplacementMethodNames
+ {
+ /**
+ * Returns the name of a static method with the signature
+ * <code>(Ljava/lang/Object;I)B</code> to replace reading from a byte[].
+ */
+ String byteOrBooleanRead();
+
+ /**
+ * Returns the name of a static method with the signature
+ * <code>(Ljava/lang/Object;IB)V</code> to replace writing to a byte[].
+ */
+ String byteOrBooleanWrite();
+
+ /**
+ * @return the name of a static method with the signature
+ * <code>(Ljava/lang/Object;I)C</code> to replace reading from a char[].
+ */
+ String charRead();
+
+ /**
+ * Returns the name of a static method with the signature
+ * <code>(Ljava/lang/Object;IC)V</code> to replace writing to a byte[].
+ */
+ String charWrite();
+
+ /**
+ * Returns the name of a static method with the signature
+ * <code>(Ljava/lang/Object;I)D</code> to replace reading from a double[].
+ */
+ String doubleRead();
+
+ /**
+ * Returns the name of a static method with the signature
+ * <code>(Ljava/lang/Object;ID)V</code> to replace writing to a double[].
+ */
+ String doubleWrite();
+
+ /**
+ * Returns the name of a static method with the signature
+ * <code>(Ljava/lang/Object;I)F</code> to replace reading from a float[].
+ */
+ String floatRead();
+
+ /**
+ * Returns the name of a static method with the signature
+ * <code>(Ljava/lang/Object;IF)V</code> to replace writing to a float[].
+ */
+ String floatWrite();
+
+ /**
+ * Returns the name of a static method with the signature
+ * <code>(Ljava/lang/Object;I)I</code> to replace reading from a int[].
+ */
+ String intRead();
+
+ /**
+ * Returns the name of a static method with the signature
+ * <code>(Ljava/lang/Object;II)V</code> to replace writing to a int[].
+ */
+ String intWrite();
+
+ /**
+ * Returns the name of a static method with the signature
+ * <code>(Ljava/lang/Object;I)J</code> to replace reading from a long[].
+ */
+ String longRead();
+
+ /**
+ * Returns the name of a static method with the signature
+ * <code>(Ljava/lang/Object;IJ)V</code> to replace writing to a long[].
+ */
+ String longWrite();
+
+ /**
+ * Returns the name of a static method with the signature
+ * <code>(Ljava/lang/Object;I)Ljava/lang/Object;</code>
+ * to replace reading from a Object[] (or any subclass of object).
+ */
+ String objectRead();
+
+ /**
+ * Returns the name of a static method with the signature
+ * <code>(Ljava/lang/Object;ILjava/lang/Object;)V</code>
+ * to replace writing to a Object[] (or any subclass of object).
+ */
+ String objectWrite();
+
+ /**
+ * Returns the name of a static method with the signature
+ * <code>(Ljava/lang/Object;I)S</code> to replace reading from a short[].
+ */
+ String shortRead();
+
+ /**
+ * Returns the name of a static method with the signature
+ * <code>(Ljava/lang/Object;IS)V</code> to replace writing to a short[].
+ */
+ String shortWrite();
+ }
+
+ /**
+ * Default implementation of the <code>ArrayAccessReplacementMethodNames</code>
+ * interface giving default values for method names to be used for replacing
+ * accesses to array elements.
+ *
+ * @author <a href="kabir.khan@jboss.com">Kabir Khan</a>
+ * @version $Revision: 1.16 $
+ */
+ public static class DefaultArrayAccessReplacementMethodNames
+ implements ArrayAccessReplacementMethodNames
+ {
+ /**
+ * Returns "arrayReadByteOrBoolean" as the name of the static method with the signature
+ * (Ljava/lang/Object;I)B to replace reading from a byte[].
+ */
+ public String byteOrBooleanRead()
+ {
+ return "arrayReadByteOrBoolean";
+ }
+
+ /**
+ * Returns "arrayWriteByteOrBoolean" as the name of the static method with the signature
+ * (Ljava/lang/Object;IB)V to replace writing to a byte[].
+ */
+ public String byteOrBooleanWrite()
+ {
+ return "arrayWriteByteOrBoolean";
+ }
+
+ /**
+ * Returns "arrayReadChar" as the name of the static method with the signature
+ * (Ljava/lang/Object;I)C to replace reading from a char[].
+ */
+ public String charRead()
+ {
+ return "arrayReadChar";
+ }
+
+ /**
+ * Returns "arrayWriteChar" as the name of the static method with the signature
+ * (Ljava/lang/Object;IC)V to replace writing to a byte[].
+ */
+ public String charWrite()
+ {
+ return "arrayWriteChar";
+ }
+
+ /**
+ * Returns "arrayReadDouble" as the name of the static method with the signature
+ * (Ljava/lang/Object;I)D to replace reading from a double[].
+ */
+ public String doubleRead()
+ {
+ return "arrayReadDouble";
+ }
+
+ /**
+ * Returns "arrayWriteDouble" as the name of the static method with the signature
+ * (Ljava/lang/Object;ID)V to replace writing to a double[].
+ */
+ public String doubleWrite()
+ {
+ return "arrayWriteDouble";
+ }
+
+ /**
+ * Returns "arrayReadFloat" as the name of the static method with the signature
+ * (Ljava/lang/Object;I)F to replace reading from a float[].
+ */
+ public String floatRead()
+ {
+ return "arrayReadFloat";
+ }
+
+ /**
+ * Returns "arrayWriteFloat" as the name of the static method with the signature
+ * (Ljava/lang/Object;IF)V to replace writing to a float[].
+ */
+ public String floatWrite()
+ {
+ return "arrayWriteFloat";
+ }
+
+ /**
+ * Returns "arrayReadInt" as the name of the static method with the signature
+ * (Ljava/lang/Object;I)I to replace reading from a int[].
+ */
+ public String intRead()
+ {
+ return "arrayReadInt";
+ }
+
+ /**
+ * Returns "arrayWriteInt" as the name of the static method with the signature
+ * (Ljava/lang/Object;II)V to replace writing to a int[].
+ */
+ public String intWrite()
+ {
+ return "arrayWriteInt";
+ }
+
+ /**
+ * Returns "arrayReadLong" as the name of the static method with the signature
+ * (Ljava/lang/Object;I)J to replace reading from a long[].
+ */
+ public String longRead()
+ {
+ return "arrayReadLong";
+ }
+
+ /**
+ * Returns "arrayWriteLong" as the name of the static method with the signature
+ * (Ljava/lang/Object;IJ)V to replace writing to a long[].
+ */
+ public String longWrite()
+ {
+ return "arrayWriteLong";
+ }
+
+ /**
+ * Returns "arrayReadObject" as the name of the static method with the signature
+ * (Ljava/lang/Object;I)Ljava/lang/Object; to replace reading from a Object[] (or any subclass of object).
+ */
+ public String objectRead()
+ {
+ return "arrayReadObject";
+ }
+
+ /**
+ * Returns "arrayWriteObject" as the name of the static method with the signature
+ * (Ljava/lang/Object;ILjava/lang/Object;)V to replace writing to a Object[] (or any subclass of object).
+ */
+ public String objectWrite()
+ {
+ return "arrayWriteObject";
+ }
+
+ /**
+ * Returns "arrayReadShort" as the name of the static method with the signature
+ * (Ljava/lang/Object;I)S to replace reading from a short[].
+ */
+ public String shortRead()
+ {
+ return "arrayReadShort";
+ }
+
+ /**
+ * Returns "arrayWriteShort" as the name of the static method with the signature
+ * (Ljava/lang/Object;IS)V to replace writing to a short[].
+ */
+ public String shortWrite()
+ {
+ return "arrayWriteShort";
+ }
+ }
+}
diff --git a/src/main/javassist/CtArray.java b/src/main/javassist/CtArray.java
new file mode 100644
index 0000000..42fe609
--- /dev/null
+++ b/src/main/javassist/CtArray.java
@@ -0,0 +1,112 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist;
+
+/**
+ * Array types.
+ */
+final class CtArray extends CtClass {
+ protected ClassPool pool;
+
+ // the name of array type ends with "[]".
+ CtArray(String name, ClassPool cp) {
+ super(name);
+ pool = cp;
+ }
+
+ public ClassPool getClassPool() {
+ return pool;
+ }
+
+ public boolean isArray() {
+ return true;
+ }
+
+ private CtClass[] interfaces = null;
+
+ public int getModifiers() {
+ int mod = Modifier.FINAL;
+ try {
+ mod |= getComponentType().getModifiers()
+ & (Modifier.PROTECTED | Modifier.PUBLIC | Modifier.PRIVATE);
+ }
+ catch (NotFoundException e) {}
+ return mod;
+ }
+
+ public CtClass[] getInterfaces() throws NotFoundException {
+ if (interfaces == null) {
+ Class[] intfs = Object[].class.getInterfaces();
+ // java.lang.Cloneable and java.io.Serializable.
+ // If the JVM is CLDC, intfs is empty.
+ interfaces = new CtClass[intfs.length];
+ for (int i = 0; i < intfs.length; i++)
+ interfaces[i] = pool.get(intfs[i].getName());
+ }
+
+ return interfaces;
+ }
+
+ public boolean subtypeOf(CtClass clazz) throws NotFoundException {
+ if (super.subtypeOf(clazz))
+ return true;
+
+ String cname = clazz.getName();
+ if (cname.equals(javaLangObject))
+ return true;
+
+ CtClass[] intfs = getInterfaces();
+ for (int i = 0; i < intfs.length; i++)
+ if (intfs[i].subtypeOf(clazz))
+ return true;
+
+ return clazz.isArray()
+ && getComponentType().subtypeOf(clazz.getComponentType());
+ }
+
+ public CtClass getComponentType() throws NotFoundException {
+ String name = getName();
+ return pool.get(name.substring(0, name.length() - 2));
+ }
+
+ public CtClass getSuperclass() throws NotFoundException {
+ return pool.get(javaLangObject);
+ }
+
+ public CtMethod[] getMethods() {
+ try {
+ return getSuperclass().getMethods();
+ }
+ catch (NotFoundException e) {
+ return super.getMethods();
+ }
+ }
+
+ public CtMethod getMethod(String name, String desc)
+ throws NotFoundException
+ {
+ return getSuperclass().getMethod(name, desc);
+ }
+
+ public CtConstructor[] getConstructors() {
+ try {
+ return getSuperclass().getConstructors();
+ }
+ catch (NotFoundException e) {
+ return super.getConstructors();
+ }
+ }
+}
diff --git a/src/main/javassist/CtBehavior.java b/src/main/javassist/CtBehavior.java
new file mode 100644
index 0000000..1414d05
--- /dev/null
+++ b/src/main/javassist/CtBehavior.java
@@ -0,0 +1,1152 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist;
+
+import javassist.bytecode.*;
+import javassist.compiler.Javac;
+import javassist.compiler.CompileError;
+import javassist.expr.ExprEditor;
+
+/**
+ * <code>CtBehavior</code> represents a method, a constructor,
+ * or a static constructor (class initializer).
+ * It is the abstract super class of
+ * <code>CtMethod</code> and <code>CtConstructor</code>.
+ */
+public abstract class CtBehavior extends CtMember {
+ protected MethodInfo methodInfo;
+
+ protected CtBehavior(CtClass clazz, MethodInfo minfo) {
+ super(clazz);
+ methodInfo = minfo;
+ }
+
+ /**
+ * @param isCons true if this is a constructor.
+ */
+ void copy(CtBehavior src, boolean isCons, ClassMap map)
+ throws CannotCompileException
+ {
+ CtClass declaring = declaringClass;
+ MethodInfo srcInfo = src.methodInfo;
+ CtClass srcClass = src.getDeclaringClass();
+ ConstPool cp = declaring.getClassFile2().getConstPool();
+
+ map = new ClassMap(map);
+ map.put(srcClass.getName(), declaring.getName());
+ try {
+ boolean patch = false;
+ CtClass srcSuper = srcClass.getSuperclass();
+ CtClass destSuper = declaring.getSuperclass();
+ String destSuperName = null;
+ if (srcSuper != null && destSuper != null) {
+ String srcSuperName = srcSuper.getName();
+ destSuperName = destSuper.getName();
+ if (!srcSuperName.equals(destSuperName))
+ if (srcSuperName.equals(CtClass.javaLangObject))
+ patch = true;
+ else
+ map.putIfNone(srcSuperName, destSuperName);
+ }
+
+ // a stack map table is copied from srcInfo.
+ methodInfo = new MethodInfo(cp, srcInfo.getName(), srcInfo, map);
+ if (isCons && patch)
+ methodInfo.setSuperclass(destSuperName);
+ }
+ catch (NotFoundException e) {
+ throw new CannotCompileException(e);
+ }
+ catch (BadBytecode e) {
+ throw new CannotCompileException(e);
+ }
+ }
+
+ protected void extendToString(StringBuffer buffer) {
+ buffer.append(' ');
+ buffer.append(getName());
+ buffer.append(' ');
+ buffer.append(methodInfo.getDescriptor());
+ }
+
+ /**
+ * Returns the method or constructor name followed by parameter types
+ * such as <code>javassist.CtBehavior.stBody(String)</code>.
+ *
+ * @since 3.5
+ */
+ public abstract String getLongName();
+
+ /**
+ * Returns the MethodInfo representing this method/constructor in the
+ * class file.
+ */
+ public MethodInfo getMethodInfo() {
+ declaringClass.checkModify();
+ return methodInfo;
+ }
+
+ /**
+ * Returns the MethodInfo representing the method/constructor in the
+ * class file (read only).
+ * Normal applications do not need calling this method. Use
+ * <code>getMethodInfo()</code>.
+ *
+ * <p>The <code>MethodInfo</code> object obtained by this method
+ * is read only. Changes to this object might not be reflected
+ * on a class file generated by <code>toBytecode()</code>,
+ * <code>toClass()</code>, etc in <code>CtClass</code>.
+ *
+ * <p>This method is available even if the <code>CtClass</code>
+ * containing this method is frozen. However, if the class is
+ * frozen, the <code>MethodInfo</code> might be also pruned.
+ *
+ * @see #getMethodInfo()
+ * @see CtClass#isFrozen()
+ * @see CtClass#prune()
+ */
+ public MethodInfo getMethodInfo2() { return methodInfo; }
+
+ /**
+ * Obtains the modifiers of the method/constructor.
+ *
+ * @return modifiers encoded with
+ * <code>javassist.Modifier</code>.
+ * @see Modifier
+ */
+ public int getModifiers() {
+ return AccessFlag.toModifier(methodInfo.getAccessFlags());
+ }
+
+ /**
+ * Sets the encoded modifiers of the method/constructor.
+ *
+ * <p>Changing the modifiers may cause a problem.
+ * For example, if a non-static method is changed to static,
+ * the method will be rejected by the bytecode verifier.
+ *
+ * @see Modifier
+ */
+ public void setModifiers(int mod) {
+ declaringClass.checkModify();
+ methodInfo.setAccessFlags(AccessFlag.of(mod));
+ }
+
+ /**
+ * Returns true if the class has the specified annotation class.
+ *
+ * @param clz the annotation class.
+ * @return <code>true</code> if the annotation is found,
+ * otherwise <code>false</code>.
+ * @since 3.11
+ */
+ public boolean hasAnnotation(Class clz) {
+ MethodInfo mi = getMethodInfo2();
+ AnnotationsAttribute ainfo = (AnnotationsAttribute)
+ mi.getAttribute(AnnotationsAttribute.invisibleTag);
+ AnnotationsAttribute ainfo2 = (AnnotationsAttribute)
+ mi.getAttribute(AnnotationsAttribute.visibleTag);
+ return CtClassType.hasAnnotationType(clz,
+ getDeclaringClass().getClassPool(),
+ ainfo, ainfo2);
+ }
+
+ /**
+ * Returns the annotation if the class has the specified annotation class.
+ * For example, if an annotation <code>@Author</code> is associated
+ * with this method/constructor, an <code>Author</code> object is returned.
+ * The member values can be obtained by calling methods on
+ * the <code>Author</code> object.
+ *
+ * @param clz the annotation class.
+ * @return the annotation if found, otherwise <code>null</code>.
+ * @since 3.11
+ */
+ public Object getAnnotation(Class clz) throws ClassNotFoundException {
+ MethodInfo mi = getMethodInfo2();
+ AnnotationsAttribute ainfo = (AnnotationsAttribute)
+ mi.getAttribute(AnnotationsAttribute.invisibleTag);
+ AnnotationsAttribute ainfo2 = (AnnotationsAttribute)
+ mi.getAttribute(AnnotationsAttribute.visibleTag);
+ return CtClassType.getAnnotationType(clz,
+ getDeclaringClass().getClassPool(),
+ ainfo, ainfo2);
+ }
+
+ /**
+ * Returns the annotations associated with this method or constructor.
+ *
+ * @return an array of annotation-type objects.
+ * @see #getAvailableAnnotations()
+ * @since 3.1
+ */
+ public Object[] getAnnotations() throws ClassNotFoundException {
+ return getAnnotations(false);
+ }
+
+ /**
+ * Returns the annotations associated with this method or constructor.
+ * If any annotations are not on the classpath, they are not included
+ * in the returned array.
+ *
+ * @return an array of annotation-type objects.
+ * @see #getAnnotations()
+ * @since 3.3
+ */
+ public Object[] getAvailableAnnotations(){
+ try{
+ return getAnnotations(true);
+ }
+ catch (ClassNotFoundException e){
+ throw new RuntimeException("Unexpected exception", e);
+ }
+ }
+
+ private Object[] getAnnotations(boolean ignoreNotFound)
+ throws ClassNotFoundException
+ {
+ MethodInfo mi = getMethodInfo2();
+ AnnotationsAttribute ainfo = (AnnotationsAttribute)
+ mi.getAttribute(AnnotationsAttribute.invisibleTag);
+ AnnotationsAttribute ainfo2 = (AnnotationsAttribute)
+ mi.getAttribute(AnnotationsAttribute.visibleTag);
+ return CtClassType.toAnnotationType(ignoreNotFound,
+ getDeclaringClass().getClassPool(),
+ ainfo, ainfo2);
+ }
+
+ /**
+ * Returns the parameter annotations associated with this method or constructor.
+ *
+ * @return an array of annotation-type objects. The length of the returned array is
+ * equal to the number of the formal parameters. If each parameter has no
+ * annotation, the elements of the returned array are empty arrays.
+ *
+ * @see #getAvailableParameterAnnotations()
+ * @see #getAnnotations()
+ * @since 3.1
+ */
+ public Object[][] getParameterAnnotations() throws ClassNotFoundException {
+ return getParameterAnnotations(false);
+ }
+
+ /**
+ * Returns the parameter annotations associated with this method or constructor.
+ * If any annotations are not on the classpath, they are not included in the
+ * returned array.
+ *
+ * @return an array of annotation-type objects. The length of the returned array is
+ * equal to the number of the formal parameters. If each parameter has no
+ * annotation, the elements of the returned array are empty arrays.
+ *
+ * @see #getParameterAnnotations()
+ * @see #getAvailableAnnotations()
+ * @since 3.3
+ */
+ public Object[][] getAvailableParameterAnnotations(){
+ try {
+ return getParameterAnnotations(true);
+ }
+ catch(ClassNotFoundException e) {
+ throw new RuntimeException("Unexpected exception", e);
+ }
+ }
+
+ Object[][] getParameterAnnotations(boolean ignoreNotFound)
+ throws ClassNotFoundException
+ {
+ MethodInfo mi = getMethodInfo2();
+ ParameterAnnotationsAttribute ainfo = (ParameterAnnotationsAttribute)
+ mi.getAttribute(ParameterAnnotationsAttribute.invisibleTag);
+ ParameterAnnotationsAttribute ainfo2 = (ParameterAnnotationsAttribute)
+ mi.getAttribute(ParameterAnnotationsAttribute.visibleTag);
+ return CtClassType.toAnnotationType(ignoreNotFound,
+ getDeclaringClass().getClassPool(),
+ ainfo, ainfo2, mi);
+ }
+
+ /**
+ * Obtains parameter types of this method/constructor.
+ */
+ public CtClass[] getParameterTypes() throws NotFoundException {
+ return Descriptor.getParameterTypes(methodInfo.getDescriptor(),
+ declaringClass.getClassPool());
+ }
+
+ /**
+ * Obtains the type of the returned value.
+ */
+ CtClass getReturnType0() throws NotFoundException {
+ return Descriptor.getReturnType(methodInfo.getDescriptor(),
+ declaringClass.getClassPool());
+ }
+
+ /**
+ * Returns the method signature (the parameter types
+ * and the return type).
+ * The method signature is represented by a character string
+ * called method descriptor, which is defined in the JVM specification.
+ * If two methods/constructors have
+ * the same parameter types
+ * and the return type, <code>getSignature()</code> returns the
+ * same string (the return type of constructors is <code>void</code>).
+ *
+ * <p>Note that the returned string is not the type signature
+ * contained in the <code>SignatureAttirbute</code>. It is
+ * a descriptor. To obtain a type signature, call the following
+ * methods:
+ *
+ * <ul><pre>getMethodInfo().getAttribute(SignatureAttribute.tag)
+ * </pre></ul>
+ *
+ * @see javassist.bytecode.Descriptor
+ * @see javassist.bytecode.SignatureAttribute
+ */
+ public String getSignature() {
+ return methodInfo.getDescriptor();
+ }
+
+ /**
+ * Obtains exceptions that this method/constructor may throw.
+ *
+ * @return a zero-length array if there is no throws clause.
+ */
+ public CtClass[] getExceptionTypes() throws NotFoundException {
+ String[] exceptions;
+ ExceptionsAttribute ea = methodInfo.getExceptionsAttribute();
+ if (ea == null)
+ exceptions = null;
+ else
+ exceptions = ea.getExceptions();
+
+ return declaringClass.getClassPool().get(exceptions);
+ }
+
+ /**
+ * Sets exceptions that this method/constructor may throw.
+ */
+ public void setExceptionTypes(CtClass[] types) throws NotFoundException {
+ declaringClass.checkModify();
+ if (types == null || types.length == 0) {
+ methodInfo.removeExceptionsAttribute();
+ return;
+ }
+
+ String[] names = new String[types.length];
+ for (int i = 0; i < types.length; ++i)
+ names[i] = types[i].getName();
+
+ ExceptionsAttribute ea = methodInfo.getExceptionsAttribute();
+ if (ea == null) {
+ ea = new ExceptionsAttribute(methodInfo.getConstPool());
+ methodInfo.setExceptionsAttribute(ea);
+ }
+
+ ea.setExceptions(names);
+ }
+
+ /**
+ * Returns true if the body is empty.
+ */
+ public abstract boolean isEmpty();
+
+ /**
+ * Sets a method/constructor body.
+ *
+ * @param src the source code representing the body.
+ * It must be a single statement or block.
+ * If it is <code>null</code>, the substituted
+ * body does nothing except returning zero or null.
+ */
+ public void setBody(String src) throws CannotCompileException {
+ setBody(src, null, null);
+ }
+
+ /**
+ * Sets a method/constructor body.
+ *
+ * @param src the source code representing the body.
+ * It must be a single statement or block.
+ * If it is <code>null</code>, the substituted
+ * body does nothing except returning zero or null.
+ * @param delegateObj the source text specifying the object
+ * that is called on by <code>$proceed()</code>.
+ * @param delegateMethod the name of the method
+ * that is called by <code>$proceed()</code>.
+ */
+ public void setBody(String src,
+ String delegateObj, String delegateMethod)
+ throws CannotCompileException
+ {
+ CtClass cc = declaringClass;
+ cc.checkModify();
+ try {
+ Javac jv = new Javac(cc);
+ if (delegateMethod != null)
+ jv.recordProceed(delegateObj, delegateMethod);
+
+ Bytecode b = jv.compileBody(this, src);
+ methodInfo.setCodeAttribute(b.toCodeAttribute());
+ methodInfo.setAccessFlags(methodInfo.getAccessFlags()
+ & ~AccessFlag.ABSTRACT);
+ methodInfo.rebuildStackMapIf6(cc.getClassPool(), cc.getClassFile2());
+ declaringClass.rebuildClassFile();
+ }
+ catch (CompileError e) {
+ throw new CannotCompileException(e);
+ } catch (BadBytecode e) {
+ throw new CannotCompileException(e);
+ }
+ }
+
+ static void setBody0(CtClass srcClass, MethodInfo srcInfo,
+ CtClass destClass, MethodInfo destInfo,
+ ClassMap map)
+ throws CannotCompileException
+ {
+ destClass.checkModify();
+
+ map = new ClassMap(map);
+ map.put(srcClass.getName(), destClass.getName());
+ try {
+ CodeAttribute cattr = srcInfo.getCodeAttribute();
+ if (cattr != null) {
+ ConstPool cp = destInfo.getConstPool();
+ CodeAttribute ca = (CodeAttribute)cattr.copy(cp, map);
+ destInfo.setCodeAttribute(ca);
+ // a stack map table is copied to destInfo.
+ }
+ }
+ catch (CodeAttribute.RuntimeCopyException e) {
+ /* the exception may be thrown by copy() in CodeAttribute.
+ */
+ throw new CannotCompileException(e);
+ }
+
+ destInfo.setAccessFlags(destInfo.getAccessFlags()
+ & ~AccessFlag.ABSTRACT);
+ destClass.rebuildClassFile();
+ }
+
+ /**
+ * Obtains an attribute with the given name.
+ * If that attribute is not found in the class file, this
+ * method returns null.
+ *
+ * <p>Note that an attribute is a data block specified by
+ * the class file format. It is not an annotation.
+ * See {@link javassist.bytecode.AttributeInfo}.
+ *
+ * @param name attribute name
+ */
+ public byte[] getAttribute(String name) {
+ AttributeInfo ai = methodInfo.getAttribute(name);
+ if (ai == null)
+ return null;
+ else
+ return ai.get();
+ }
+
+ /**
+ * Adds an attribute. The attribute is saved in the class file.
+ *
+ * <p>Note that an attribute is a data block specified by
+ * the class file format. It is not an annotation.
+ * See {@link javassist.bytecode.AttributeInfo}.
+ *
+ * @param name attribute name
+ * @param data attribute value
+ */
+ public void setAttribute(String name, byte[] data) {
+ declaringClass.checkModify();
+ methodInfo.addAttribute(new AttributeInfo(methodInfo.getConstPool(),
+ name, data));
+ }
+
+ /**
+ * Declares to use <code>$cflow</code> for this method/constructor.
+ * If <code>$cflow</code> is used, the class files modified
+ * with Javassist requires a support class
+ * <code>javassist.runtime.Cflow</code> at runtime
+ * (other Javassist classes are not required at runtime).
+ *
+ * <p>Every <code>$cflow</code> variable is given a unique name.
+ * For example, if the given name is <code>"Point.paint"</code>,
+ * then the variable is indicated by <code>$cflow(Point.paint)</code>.
+ *
+ * @param name <code>$cflow</code> name. It can include
+ * alphabets, numbers, <code>_</code>,
+ * <code>$</code>, and <code>.</code> (dot).
+ *
+ * @see javassist.runtime.Cflow
+ */
+ public void useCflow(String name) throws CannotCompileException {
+ CtClass cc = declaringClass;
+ cc.checkModify();
+ ClassPool pool = cc.getClassPool();
+ String fname;
+ int i = 0;
+ while (true) {
+ fname = "_cflow$" + i++;
+ try {
+ cc.getDeclaredField(fname);
+ }
+ catch(NotFoundException e) {
+ break;
+ }
+ }
+
+ pool.recordCflow(name, declaringClass.getName(), fname);
+ try {
+ CtClass type = pool.get("javassist.runtime.Cflow");
+ CtField field = new CtField(type, fname, cc);
+ field.setModifiers(Modifier.PUBLIC | Modifier.STATIC);
+ cc.addField(field, CtField.Initializer.byNew(type));
+ insertBefore(fname + ".enter();", false);
+ String src = fname + ".exit();";
+ insertAfter(src, true);
+ }
+ catch (NotFoundException e) {
+ throw new CannotCompileException(e);
+ }
+ }
+
+ /**
+ * Declares a new local variable. The scope of this variable is the
+ * whole method body. The initial value of that variable is not set.
+ * The declared variable can be accessed in the code snippet inserted
+ * by <code>insertBefore()</code>, <code>insertAfter()</code>, etc.
+ *
+ * <p>If the second parameter <code>asFinally</code> to
+ * <code>insertAfter()</code> is true, the declared local variable
+ * is not visible from the code inserted by <code>insertAfter()</code>.
+ *
+ * @param name the name of the variable
+ * @param type the type of the variable
+ * @see #insertBefore(String)
+ * @see #insertAfter(String)
+ */
+ public void addLocalVariable(String name, CtClass type)
+ throws CannotCompileException
+ {
+ declaringClass.checkModify();
+ ConstPool cp = methodInfo.getConstPool();
+ CodeAttribute ca = methodInfo.getCodeAttribute();
+ if (ca == null)
+ throw new CannotCompileException("no method body");
+
+ LocalVariableAttribute va = (LocalVariableAttribute)ca.getAttribute(
+ LocalVariableAttribute.tag);
+ if (va == null) {
+ va = new LocalVariableAttribute(cp);
+ ca.getAttributes().add(va);
+ }
+
+ int maxLocals = ca.getMaxLocals();
+ String desc = Descriptor.of(type);
+ va.addEntry(0, ca.getCodeLength(),
+ cp.addUtf8Info(name), cp.addUtf8Info(desc), maxLocals);
+ ca.setMaxLocals(maxLocals + Descriptor.dataSize(desc));
+ }
+
+ /**
+ * Inserts a new parameter, which becomes the first parameter.
+ */
+ public void insertParameter(CtClass type)
+ throws CannotCompileException
+ {
+ declaringClass.checkModify();
+ String desc = methodInfo.getDescriptor();
+ String desc2 = Descriptor.insertParameter(type, desc);
+ try {
+ addParameter2(Modifier.isStatic(getModifiers()) ? 0 : 1, type, desc);
+ }
+ catch (BadBytecode e) {
+ throw new CannotCompileException(e);
+ }
+
+ methodInfo.setDescriptor(desc2);
+ }
+
+ /**
+ * Appends a new parameter, which becomes the last parameter.
+ */
+ public void addParameter(CtClass type)
+ throws CannotCompileException
+ {
+ declaringClass.checkModify();
+ String desc = methodInfo.getDescriptor();
+ String desc2 = Descriptor.appendParameter(type, desc);
+ int offset = Modifier.isStatic(getModifiers()) ? 0 : 1;
+ try {
+ addParameter2(offset + Descriptor.paramSize(desc), type, desc);
+ }
+ catch (BadBytecode e) {
+ throw new CannotCompileException(e);
+ }
+
+ methodInfo.setDescriptor(desc2);
+ }
+
+ private void addParameter2(int where, CtClass type, String desc)
+ throws BadBytecode
+ {
+ CodeAttribute ca = methodInfo.getCodeAttribute();
+ if (ca != null) {
+ int size = 1;
+ char typeDesc = 'L';
+ int classInfo = 0;
+ if (type.isPrimitive()) {
+ CtPrimitiveType cpt = (CtPrimitiveType)type;
+ size = cpt.getDataSize();
+ typeDesc = cpt.getDescriptor();
+ }
+ else
+ classInfo = methodInfo.getConstPool().addClassInfo(type);
+
+ ca.insertLocalVar(where, size);
+ LocalVariableAttribute va
+ = (LocalVariableAttribute)
+ ca.getAttribute(LocalVariableAttribute.tag);
+ if (va != null)
+ va.shiftIndex(where, size);
+
+ StackMapTable smt = (StackMapTable)ca.getAttribute(StackMapTable.tag);
+ if (smt != null)
+ smt.insertLocal(where, StackMapTable.typeTagOf(typeDesc), classInfo);
+
+ StackMap sm = (StackMap)ca.getAttribute(StackMap.tag);
+ if (sm != null)
+ sm.insertLocal(where, StackMapTable.typeTagOf(typeDesc), classInfo);
+ }
+ }
+
+ /**
+ * Modifies the method/constructor body.
+ *
+ * @param converter specifies how to modify.
+ */
+ public void instrument(CodeConverter converter)
+ throws CannotCompileException
+ {
+ declaringClass.checkModify();
+ ConstPool cp = methodInfo.getConstPool();
+ converter.doit(getDeclaringClass(), methodInfo, cp);
+ }
+
+ /**
+ * Modifies the method/constructor body.
+ *
+ * @param editor specifies how to modify.
+ */
+ public void instrument(ExprEditor editor)
+ throws CannotCompileException
+ {
+ // if the class is not frozen,
+ // does not turn the modified flag on.
+ if (declaringClass.isFrozen())
+ declaringClass.checkModify();
+
+ if (editor.doit(declaringClass, methodInfo))
+ declaringClass.checkModify();
+ }
+
+ /**
+ * Inserts bytecode at the beginning of the body.
+ *
+ * <p>If this object represents a constructor,
+ * the bytecode is inserted before
+ * a constructor in the super class or this class is called.
+ * Therefore, the inserted bytecode is subject to constraints described
+ * in Section 4.8.2 of The Java Virtual Machine Specification (2nd ed).
+ * For example, it cannot access instance fields or methods although
+ * it may assign a value to an instance field directly declared in this
+ * class. Accessing static fields and methods is allowed.
+ * Use <code>insertBeforeBody()</code> in <code>CtConstructor</code>.
+ *
+ * @param src the source code representing the inserted bytecode.
+ * It must be a single statement or block.
+ * @see CtConstructor#insertBeforeBody(String)
+ */
+ public void insertBefore(String src) throws CannotCompileException {
+ insertBefore(src, true);
+ }
+
+ private void insertBefore(String src, boolean rebuild)
+ throws CannotCompileException
+ {
+ CtClass cc = declaringClass;
+ cc.checkModify();
+ CodeAttribute ca = methodInfo.getCodeAttribute();
+ if (ca == null)
+ throw new CannotCompileException("no method body");
+
+ CodeIterator iterator = ca.iterator();
+ Javac jv = new Javac(cc);
+ try {
+ int nvars = jv.recordParams(getParameterTypes(),
+ Modifier.isStatic(getModifiers()));
+ jv.recordParamNames(ca, nvars);
+ jv.recordLocalVariables(ca, 0);
+ jv.recordType(getReturnType0());
+ jv.compileStmnt(src);
+ Bytecode b = jv.getBytecode();
+ int stack = b.getMaxStack();
+ int locals = b.getMaxLocals();
+
+ if (stack > ca.getMaxStack())
+ ca.setMaxStack(stack);
+
+ if (locals > ca.getMaxLocals())
+ ca.setMaxLocals(locals);
+
+ int pos = iterator.insertEx(b.get());
+ iterator.insert(b.getExceptionTable(), pos);
+ if (rebuild)
+ methodInfo.rebuildStackMapIf6(cc.getClassPool(), cc.getClassFile2());
+ }
+ catch (NotFoundException e) {
+ throw new CannotCompileException(e);
+ }
+ catch (CompileError e) {
+ throw new CannotCompileException(e);
+ }
+ catch (BadBytecode e) {
+ throw new CannotCompileException(e);
+ }
+ }
+
+ /**
+ * Inserts bytecode at the end of the body.
+ * The bytecode is inserted just before every return insturction.
+ * It is not executed when an exception is thrown.
+ *
+ * @param src the source code representing the inserted bytecode.
+ * It must be a single statement or block.
+ */
+ public void insertAfter(String src)
+ throws CannotCompileException
+ {
+ insertAfter(src, false);
+ }
+
+ /**
+ * Inserts bytecode at the end of the body.
+ * The bytecode is inserted just before every return insturction.
+ *
+ * @param src the source code representing the inserted bytecode.
+ * It must be a single statement or block.
+ * @param asFinally true if the inserted bytecode is executed
+ * not only when the control normally returns
+ * but also when an exception is thrown.
+ * If this parameter is true, the inserted code cannot
+ * access local variables.
+ */
+ public void insertAfter(String src, boolean asFinally)
+ throws CannotCompileException
+ {
+ CtClass cc = declaringClass;
+ cc.checkModify();
+ ConstPool pool = methodInfo.getConstPool();
+ CodeAttribute ca = methodInfo.getCodeAttribute();
+ if (ca == null)
+ throw new CannotCompileException("no method body");
+
+ CodeIterator iterator = ca.iterator();
+ int retAddr = ca.getMaxLocals();
+ Bytecode b = new Bytecode(pool, 0, retAddr + 1);
+ b.setStackDepth(ca.getMaxStack() + 1);
+ Javac jv = new Javac(b, cc);
+ try {
+ int nvars = jv.recordParams(getParameterTypes(),
+ Modifier.isStatic(getModifiers()));
+ jv.recordParamNames(ca, nvars);
+ CtClass rtype = getReturnType0();
+ int varNo = jv.recordReturnType(rtype, true);
+ jv.recordLocalVariables(ca, 0);
+
+ // finally clause for exceptions
+ int handlerLen = insertAfterHandler(asFinally, b, rtype, varNo,
+ jv, src);
+ // finally clause for normal termination
+ insertAfterAdvice(b, jv, src, pool, rtype, varNo);
+
+ ca.setMaxStack(b.getMaxStack());
+ ca.setMaxLocals(b.getMaxLocals());
+
+ int gapPos = iterator.append(b.get());
+ iterator.append(b.getExceptionTable(), gapPos);
+
+ if (asFinally)
+ ca.getExceptionTable().add(getStartPosOfBody(ca), gapPos, gapPos, 0);
+
+ int gapLen = iterator.getCodeLength() - gapPos - handlerLen;
+ int subr = iterator.getCodeLength() - gapLen;
+
+ while (iterator.hasNext()) {
+ int pos = iterator.next();
+ if (pos >= subr)
+ break;
+
+ int c = iterator.byteAt(pos);
+ if (c == Opcode.ARETURN || c == Opcode.IRETURN
+ || c == Opcode.FRETURN || c == Opcode.LRETURN
+ || c == Opcode.DRETURN || c == Opcode.RETURN) {
+ insertGoto(iterator, subr, pos);
+ subr = iterator.getCodeLength() - gapLen;
+ }
+ }
+
+ methodInfo.rebuildStackMapIf6(cc.getClassPool(), cc.getClassFile2());
+ }
+ catch (NotFoundException e) {
+ throw new CannotCompileException(e);
+ }
+ catch (CompileError e) {
+ throw new CannotCompileException(e);
+ }
+ catch (BadBytecode e) {
+ throw new CannotCompileException(e);
+ }
+ }
+
+ private void insertAfterAdvice(Bytecode code, Javac jv, String src,
+ ConstPool cp, CtClass rtype, int varNo)
+ throws CompileError
+ {
+ if (rtype == CtClass.voidType) {
+ code.addOpcode(Opcode.ACONST_NULL);
+ code.addAstore(varNo);
+ jv.compileStmnt(src);
+ code.addOpcode(Opcode.RETURN);
+ if (code.getMaxLocals() < 1)
+ code.setMaxLocals(1);
+ }
+ else {
+ code.addStore(varNo, rtype);
+ jv.compileStmnt(src);
+ code.addLoad(varNo, rtype);
+ if (rtype.isPrimitive())
+ code.addOpcode(((CtPrimitiveType)rtype).getReturnOp());
+ else
+ code.addOpcode(Opcode.ARETURN);
+ }
+ }
+
+ /*
+ * assert subr > pos
+ */
+ private void insertGoto(CodeIterator iterator, int subr, int pos)
+ throws BadBytecode
+ {
+ iterator.setMark(subr);
+ // the gap length might be a multiple of 4.
+ iterator.writeByte(Opcode.NOP, pos);
+ boolean wide = subr + 2 - pos > Short.MAX_VALUE;
+ pos = iterator.insertGapAt(pos, wide ? 4 : 2, false).position;
+ int offset = iterator.getMark() - pos;
+ if (wide) {
+ iterator.writeByte(Opcode.GOTO_W, pos);
+ iterator.write32bit(offset, pos + 1);
+ }
+ else if (offset <= Short.MAX_VALUE) {
+ iterator.writeByte(Opcode.GOTO, pos);
+ iterator.write16bit(offset, pos + 1);
+ }
+ else {
+ pos = iterator.insertGapAt(pos, 2, false).position;
+ iterator.writeByte(Opcode.GOTO_W, pos);
+ iterator.write32bit(iterator.getMark() - pos, pos + 1);
+ }
+ }
+
+ /* insert a finally clause
+ */
+ private int insertAfterHandler(boolean asFinally, Bytecode b,
+ CtClass rtype, int returnVarNo,
+ Javac javac, String src)
+ throws CompileError
+ {
+ if (!asFinally)
+ return 0;
+
+ int var = b.getMaxLocals();
+ b.incMaxLocals(1);
+ int pc = b.currentPc();
+ b.addAstore(var); // store an exception
+ if (rtype.isPrimitive()) {
+ char c = ((CtPrimitiveType)rtype).getDescriptor();
+ if (c == 'D') {
+ b.addDconst(0.0);
+ b.addDstore(returnVarNo);
+ }
+ else if (c == 'F') {
+ b.addFconst(0);
+ b.addFstore(returnVarNo);
+ }
+ else if (c == 'J') {
+ b.addLconst(0);
+ b.addLstore(returnVarNo);
+ }
+ else if (c == 'V') {
+ b.addOpcode(Opcode.ACONST_NULL);
+ b.addAstore(returnVarNo);
+ }
+ else { // int, boolean, char, short, ...
+ b.addIconst(0);
+ b.addIstore(returnVarNo);
+ }
+ }
+ else {
+ b.addOpcode(Opcode.ACONST_NULL);
+ b.addAstore(returnVarNo);
+ }
+
+ javac.compileStmnt(src);
+ b.addAload(var);
+ b.addOpcode(Opcode.ATHROW);
+ return b.currentPc() - pc;
+ }
+
+ /* -- OLD version --
+
+ public void insertAfter(String src) throws CannotCompileException {
+ declaringClass.checkModify();
+ CodeAttribute ca = methodInfo.getCodeAttribute();
+ CodeIterator iterator = ca.iterator();
+ Bytecode b = new Bytecode(methodInfo.getConstPool(),
+ ca.getMaxStack(), ca.getMaxLocals());
+ b.setStackDepth(ca.getMaxStack());
+ Javac jv = new Javac(b, declaringClass);
+ try {
+ jv.recordParams(getParameterTypes(),
+ Modifier.isStatic(getModifiers()));
+ CtClass rtype = getReturnType0();
+ int varNo = jv.recordReturnType(rtype, true);
+ boolean isVoid = rtype == CtClass.voidType;
+ if (isVoid) {
+ b.addOpcode(Opcode.ACONST_NULL);
+ b.addAstore(varNo);
+ jv.compileStmnt(src);
+ }
+ else {
+ b.addStore(varNo, rtype);
+ jv.compileStmnt(src);
+ b.addLoad(varNo, rtype);
+ }
+
+ byte[] code = b.get();
+ ca.setMaxStack(b.getMaxStack());
+ ca.setMaxLocals(b.getMaxLocals());
+ while (iterator.hasNext()) {
+ int pos = iterator.next();
+ int c = iterator.byteAt(pos);
+ if (c == Opcode.ARETURN || c == Opcode.IRETURN
+ || c == Opcode.FRETURN || c == Opcode.LRETURN
+ || c == Opcode.DRETURN || c == Opcode.RETURN)
+ iterator.insert(pos, code);
+ }
+ }
+ catch (NotFoundException e) {
+ throw new CannotCompileException(e);
+ }
+ catch (CompileError e) {
+ throw new CannotCompileException(e);
+ }
+ catch (BadBytecode e) {
+ throw new CannotCompileException(e);
+ }
+ }
+ */
+
+ /**
+ * Adds a catch clause that handles an exception thrown in the
+ * body. The catch clause must end with a return or throw statement.
+ *
+ * @param src the source code representing the catch clause.
+ * It must be a single statement or block.
+ * @param exceptionType the type of the exception handled by the
+ * catch clause.
+ */
+ public void addCatch(String src, CtClass exceptionType)
+ throws CannotCompileException
+ {
+ addCatch(src, exceptionType, "$e");
+ }
+
+ /**
+ * Adds a catch clause that handles an exception thrown in the
+ * body. The catch clause must end with a return or throw statement.
+ *
+ * @param src the source code representing the catch clause.
+ * It must be a single statement or block.
+ * @param exceptionType the type of the exception handled by the
+ * catch clause.
+ * @param exceptionName the name of the variable containing the
+ * caught exception, for example,
+ * <code>$e</code>.
+ */
+ public void addCatch(String src, CtClass exceptionType,
+ String exceptionName)
+ throws CannotCompileException
+ {
+ CtClass cc = declaringClass;
+ cc.checkModify();
+ ConstPool cp = methodInfo.getConstPool();
+ CodeAttribute ca = methodInfo.getCodeAttribute();
+ CodeIterator iterator = ca.iterator();
+ Bytecode b = new Bytecode(cp, ca.getMaxStack(), ca.getMaxLocals());
+ b.setStackDepth(1);
+ Javac jv = new Javac(b, cc);
+ try {
+ jv.recordParams(getParameterTypes(),
+ Modifier.isStatic(getModifiers()));
+ int var = jv.recordVariable(exceptionType, exceptionName);
+ b.addAstore(var);
+ jv.compileStmnt(src);
+
+ int stack = b.getMaxStack();
+ int locals = b.getMaxLocals();
+
+ if (stack > ca.getMaxStack())
+ ca.setMaxStack(stack);
+
+ if (locals > ca.getMaxLocals())
+ ca.setMaxLocals(locals);
+
+ int len = iterator.getCodeLength();
+ int pos = iterator.append(b.get());
+ ca.getExceptionTable().add(getStartPosOfBody(ca), len, len,
+ cp.addClassInfo(exceptionType));
+ iterator.append(b.getExceptionTable(), pos);
+ methodInfo.rebuildStackMapIf6(cc.getClassPool(), cc.getClassFile2());
+ }
+ catch (NotFoundException e) {
+ throw new CannotCompileException(e);
+ }
+ catch (CompileError e) {
+ throw new CannotCompileException(e);
+ } catch (BadBytecode e) {
+ throw new CannotCompileException(e);
+ }
+ }
+
+ /* CtConstructor overrides this method.
+ */
+ int getStartPosOfBody(CodeAttribute ca) throws CannotCompileException {
+ return 0;
+ }
+
+ /**
+ * Inserts bytecode at the specified line in the body.
+ * It is equivalent to:
+ *
+ * <br><code>insertAt(lineNum, true, src)</code>
+ *
+ * <br>See this method as well.
+ *
+ * @param lineNum the line number. The bytecode is inserted at the
+ * beginning of the code at the line specified by this
+ * line number.
+ * @param src the source code representing the inserted bytecode.
+ * It must be a single statement or block.
+ * @return the line number at which the bytecode has been inserted.
+ *
+ * @see CtBehavior#insertAt(int,boolean,String)
+ */
+ public int insertAt(int lineNum, String src)
+ throws CannotCompileException
+ {
+ return insertAt(lineNum, true, src);
+ }
+
+ /**
+ * Inserts bytecode at the specified line in the body.
+ *
+ * <p>If there is not
+ * a statement at the specified line, the bytecode might be inserted
+ * at the line including the first statement after that line specified.
+ * For example, if there is only a closing brace at that line, the
+ * bytecode would be inserted at another line below.
+ * To know exactly where the bytecode will be inserted, call with
+ * <code>modify</code> set to <code>false</code>.
+ *
+ * @param lineNum the line number. The bytecode is inserted at the
+ * beginning of the code at the line specified by this
+ * line number.
+ * @param modify if false, this method does not insert the bytecode.
+ * It instead only returns the line number at which
+ * the bytecode would be inserted.
+ * @param src the source code representing the inserted bytecode.
+ * It must be a single statement or block.
+ * If modify is false, the value of src can be null.
+ * @return the line number at which the bytecode has been inserted.
+ */
+ public int insertAt(int lineNum, boolean modify, String src)
+ throws CannotCompileException
+ {
+ CodeAttribute ca = methodInfo.getCodeAttribute();
+ if (ca == null)
+ throw new CannotCompileException("no method body");
+
+ LineNumberAttribute ainfo
+ = (LineNumberAttribute)ca.getAttribute(LineNumberAttribute.tag);
+ if (ainfo == null)
+ throw new CannotCompileException("no line number info");
+
+ LineNumberAttribute.Pc pc = ainfo.toNearPc(lineNum);
+ lineNum = pc.line;
+ int index = pc.index;
+ if (!modify)
+ return lineNum;
+
+ CtClass cc = declaringClass;
+ cc.checkModify();
+ CodeIterator iterator = ca.iterator();
+ Javac jv = new Javac(cc);
+ try {
+ jv.recordLocalVariables(ca, index);
+ jv.recordParams(getParameterTypes(),
+ Modifier.isStatic(getModifiers()));
+ jv.setMaxLocals(ca.getMaxLocals());
+ jv.compileStmnt(src);
+ Bytecode b = jv.getBytecode();
+ int locals = b.getMaxLocals();
+ int stack = b.getMaxStack();
+ ca.setMaxLocals(locals);
+
+ /* We assume that there is no values in the operand stack
+ * at the position where the bytecode is inserted.
+ */
+ if (stack > ca.getMaxStack())
+ ca.setMaxStack(stack);
+
+ index = iterator.insertAt(index, b.get());
+ iterator.insert(b.getExceptionTable(), index);
+ methodInfo.rebuildStackMapIf6(cc.getClassPool(), cc.getClassFile2());
+ return lineNum;
+ }
+ catch (NotFoundException e) {
+ throw new CannotCompileException(e);
+ }
+ catch (CompileError e) {
+ throw new CannotCompileException(e);
+ }
+ catch (BadBytecode e) {
+ throw new CannotCompileException(e);
+ }
+ }
+}
diff --git a/src/main/javassist/CtClass.java b/src/main/javassist/CtClass.java
new file mode 100644
index 0000000..e805c20
--- /dev/null
+++ b/src/main/javassist/CtClass.java
@@ -0,0 +1,1440 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist;
+
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URL;
+import java.security.ProtectionDomain;
+import java.util.Collection;
+import javassist.bytecode.ClassFile;
+import javassist.bytecode.Descriptor;
+import javassist.bytecode.Opcode;
+import javassist.expr.ExprEditor;
+
+/* Note:
+ *
+ * This class is an abstract class and several methods just return null
+ * or throw an exception. Those methods are overridden in subclasses
+ * of this class. Read the source code of CtClassType if you are
+ * interested in the implementation of Javassist.
+ *
+ * Subclasses of CtClass are CtClassType, CtPrimitiveType, and CtArray.
+ */
+
+/**
+ * An instance of <code>CtClass</code> represents a class.
+ * It is obtained from <code>ClassPool</code>.
+ *
+ * @see ClassPool#get(String)
+ */
+public abstract class CtClass {
+ protected String qualifiedName;
+
+ /**
+ * The version number of this release.
+ */
+ public static final String version = "3.14.0.GA";
+
+ /**
+ * Prints the version number and the copyright notice.
+ *
+ * <p>The following command invokes this method:
+ *
+ * <ul><pre>java -jar javassist.jar</pre></ul>
+ */
+ public static void main(String[] args) {
+ System.out.println("Javassist version " + CtClass.version);
+ System.out.println("Copyright (C) 1999-2010 Shigeru Chiba."
+ + " All Rights Reserved.");
+ }
+
+ static final String javaLangObject = "java.lang.Object";
+
+ /**
+ * The <code>CtClass</code> object representing
+ * the <code>boolean</code> type.
+ */
+ public static CtClass booleanType;
+
+ /**
+ * The <code>CtClass</code> object representing
+ * the <code>char</code> type.
+ */
+ public static CtClass charType;
+
+ /**
+ * The <code>CtClass</code> object representing
+ * the <code>byte</code> type.
+ */
+ public static CtClass byteType;
+
+ /**
+ * The <code>CtClass</code> object representing
+ * the <code>short</code> type.
+ */
+ public static CtClass shortType;
+
+ /**
+ * The <code>CtClass</code> object representing
+ * the <code>int</code> type.
+ */
+ public static CtClass intType;
+
+ /**
+ * The <code>CtClass</code> object representing
+ * the <code>long</code> type.
+ */
+ public static CtClass longType;
+
+ /**
+ * The <code>CtClass</code> object representing
+ * the <code>float</code> type.
+ */
+ public static CtClass floatType;
+
+ /**
+ * The <code>CtClass</code> object representing
+ * the <code>double</code> type.
+ */
+ public static CtClass doubleType;
+
+ /**
+ * The <code>CtClass</code> object representing
+ * the <code>void</code> type.
+ */
+ public static CtClass voidType;
+
+ static CtClass[] primitiveTypes;
+
+ static {
+ primitiveTypes = new CtClass[9];
+
+ booleanType =
+ new CtPrimitiveType("boolean", 'Z', "java.lang.Boolean",
+ "booleanValue", "()Z", Opcode.IRETURN,
+ Opcode.T_BOOLEAN, 1);
+ primitiveTypes[0] = booleanType;
+
+ charType = new CtPrimitiveType("char", 'C', "java.lang.Character",
+ "charValue", "()C", Opcode.IRETURN,
+ Opcode.T_CHAR, 1);
+ primitiveTypes[1] = charType;
+
+ byteType = new CtPrimitiveType("byte", 'B', "java.lang.Byte",
+ "byteValue", "()B", Opcode.IRETURN,
+ Opcode.T_BYTE, 1);
+ primitiveTypes[2] = byteType;
+
+ shortType = new CtPrimitiveType("short", 'S', "java.lang.Short",
+ "shortValue", "()S", Opcode.IRETURN,
+ Opcode.T_SHORT, 1);
+ primitiveTypes[3] = shortType;
+
+ intType = new CtPrimitiveType("int", 'I', "java.lang.Integer",
+ "intValue", "()I", Opcode.IRETURN,
+ Opcode.T_INT, 1);
+ primitiveTypes[4] = intType;
+
+ longType = new CtPrimitiveType("long", 'J', "java.lang.Long",
+ "longValue", "()J", Opcode.LRETURN,
+ Opcode.T_LONG, 2);
+ primitiveTypes[5] = longType;
+
+ floatType = new CtPrimitiveType("float", 'F', "java.lang.Float",
+ "floatValue", "()F", Opcode.FRETURN,
+ Opcode.T_FLOAT, 1);
+ primitiveTypes[6] = floatType;
+
+ doubleType = new CtPrimitiveType("double", 'D', "java.lang.Double",
+ "doubleValue", "()D", Opcode.DRETURN,
+ Opcode.T_DOUBLE, 2);
+ primitiveTypes[7] = doubleType;
+
+ voidType = new CtPrimitiveType("void", 'V', "java.lang.Void",
+ null, null, Opcode.RETURN, 0, 0);
+ primitiveTypes[8] = voidType;
+ }
+
+ protected CtClass(String name) {
+ qualifiedName = name;
+ }
+
+ /**
+ * Converts the object to a string.
+ */
+ public String toString() {
+ StringBuffer buf = new StringBuffer(getClass().getName());
+ buf.append("@");
+ buf.append(Integer.toHexString(hashCode()));
+ buf.append("[");
+ extendToString(buf);
+ buf.append("]");
+ return buf.toString();
+ }
+
+ /**
+ * Implemented in subclasses to add to the {@link #toString()} result.
+ * Subclasses should put a space before each token added to the buffer.
+ */
+ protected void extendToString(StringBuffer buffer) {
+ buffer.append(getName());
+ }
+
+ /**
+ * Returns a <code>ClassPool</code> for this class.
+ */
+ public ClassPool getClassPool() { return null; }
+
+ /**
+ * Returns a class file for this class.
+ *
+ * <p>This method is not available if <code>isFrozen()</code>
+ * is true.
+ */
+ public ClassFile getClassFile() {
+ checkModify();
+ return getClassFile2();
+ }
+
+ /**
+ * Returns a class file for this class (read only).
+ * Normal applications do not need calling this method. Use
+ * <code>getClassFile()</code>.
+ *
+ * <p>The <code>ClassFile</code> object obtained by this method
+ * is read only. Changes to this object might not be reflected
+ * on a class file generated by <code>toBytecode()</code>,
+ * <code>toClass()</code>, etc.
+ *
+ * <p>This method is available even if <code>isFrozen()</code>
+ * is true. However, if the class is frozen, it might be also
+ * pruned.
+ *
+ * @see CtClass#getClassFile()
+ * @see CtClass#isFrozen()
+ * @see CtClass#prune()
+ */
+ public ClassFile getClassFile2() { return null; }
+
+ /**
+ * Undocumented method. Do not use; internal-use only.
+ */
+ public javassist.compiler.AccessorMaker getAccessorMaker() {
+ return null;
+ }
+
+ /**
+ * Returns the uniform resource locator (URL) of the class file.
+ */
+ public URL getURL() throws NotFoundException {
+ throw new NotFoundException(getName());
+ }
+
+ /**
+ * Returns true if the definition of the class has been modified.
+ */
+ public boolean isModified() { return false; }
+
+ /**
+ * Returns true if the class has been loaded or written out
+ * and thus it cannot be modified any more.
+ *
+ * @see #defrost()
+ * @see #detach()
+ */
+ public boolean isFrozen() { return true; }
+
+ /**
+ * Makes the class frozen.
+ *
+ * @see #isFrozen()
+ * @see #defrost()
+ * @since 3.6
+ */
+ public void freeze() {}
+
+ /* Note: this method is overridden by CtClassType
+ */
+ void checkModify() throws RuntimeException {
+ if (isFrozen())
+ throw new RuntimeException(getName() + " class is frozen");
+
+ // isModified() must return true after this method is invoked.
+ }
+
+ /**
+ * Defrosts the class so that the class can be modified again.
+ *
+ * <p>To avoid changes that will be never reflected,
+ * the class is frozen to be unmodifiable if it is loaded or
+ * written out. This method should be called only in a case
+ * that the class will be reloaded or written out later again.
+ *
+ * <p>If <code>defrost()</code> will be called later, pruning
+ * must be disallowed in advance.
+ *
+ * @see #isFrozen()
+ * @see #stopPruning(boolean)
+ * @see #detach()
+ */
+ public void defrost() {
+ throw new RuntimeException("cannot defrost " + getName());
+ }
+
+ /**
+ * Returns <code>true</code> if this object represents a primitive
+ * Java type: boolean, byte, char, short, int, long, float, double,
+ * or void.
+ */
+ public boolean isPrimitive() { return false; }
+
+ /**
+ * Returns <code>true</code> if this object represents an array type.
+ */
+ public boolean isArray() {
+ return false;
+ }
+
+ /**
+ * If this object represents an array, this method returns the component
+ * type of the array. Otherwise, it returns <code>null</code>.
+ */
+ public CtClass getComponentType() throws NotFoundException {
+ return null;
+ }
+
+ /**
+ * Returns <code>true</code> if this class extends or implements
+ * <code>clazz</code>. It also returns <code>true</code> if
+ * this class is the same as <code>clazz</code>.
+ */
+ public boolean subtypeOf(CtClass clazz) throws NotFoundException {
+ return this == clazz || getName().equals(clazz.getName());
+ }
+
+ /**
+ * Obtains the fully-qualified name of the class.
+ */
+ public String getName() { return qualifiedName; }
+
+ /**
+ * Obtains the not-qualified class name.
+ */
+ public final String getSimpleName() {
+ String qname = qualifiedName;
+ int index = qname.lastIndexOf('.');
+ if (index < 0)
+ return qname;
+ else
+ return qname.substring(index + 1);
+ }
+
+ /**
+ * Obtains the package name. It may be <code>null</code>.
+ */
+ public final String getPackageName() {
+ String qname = qualifiedName;
+ int index = qname.lastIndexOf('.');
+ if (index < 0)
+ return null;
+ else
+ return qname.substring(0, index);
+ }
+
+ /**
+ * Sets the class name
+ *
+ * @param name fully-qualified name
+ */
+ public void setName(String name) {
+ checkModify();
+ if (name != null)
+ qualifiedName = name;
+ }
+
+ /**
+ * Substitutes <code>newName</code> for all occurrences of a class
+ * name <code>oldName</code> in the class file.
+ *
+ * @param oldName replaced class name
+ * @param newName substituted class name
+ */
+ public void replaceClassName(String oldName, String newName) {
+ checkModify();
+ }
+
+ /**
+ * Changes class names appearing in the class file according to the
+ * given <code>map</code>.
+ *
+ * <p>All the class names appearing in the class file are tested
+ * with <code>map</code> to determine whether each class name is
+ * replaced or not. Thus this method can be used for collecting
+ * all the class names in the class file. To do that, first define
+ * a subclass of <code>ClassMap</code> so that <code>get()</code>
+ * records all the given parameters. Then, make an instance of
+ * that subclass as an empty hash-table. Finally, pass that instance
+ * to this method. After this method finishes, that instance would
+ * contain all the class names appearing in the class file.
+ *
+ * @param map the hashtable associating replaced class names
+ * with substituted names.
+ */
+ public void replaceClassName(ClassMap map) {
+ checkModify();
+ }
+
+ /**
+ * Returns a collection of the names of all the classes
+ * referenced in this class.
+ * That collection includes the name of this class.
+ *
+ * <p>This method may return <code>null</code>.
+ *
+ * @return a <code>Collection<String></code> object.
+ */
+ public synchronized Collection getRefClasses() {
+ ClassFile cf = getClassFile2();
+ if (cf != null) {
+ ClassMap cm = new ClassMap() {
+ public void put(String oldname, String newname) {
+ put0(oldname, newname);
+ }
+
+ public Object get(Object jvmClassName) {
+ String n = toJavaName((String)jvmClassName);
+ put0(n, n);
+ return null;
+ }
+
+ public void fix(String name) {}
+ };
+ cf.getRefClasses(cm);
+ return cm.values();
+ }
+ else
+ return null;
+ }
+
+ /**
+ * Determines whether this object represents a class or an interface.
+ * It returns <code>true</code> if this object represents an interface.
+ */
+ public boolean isInterface() {
+ return false;
+ }
+
+ /**
+ * Determines whether this object represents an annotation type.
+ * It returns <code>true</code> if this object represents an annotation type.
+ *
+ * @since 3.2
+ */
+ public boolean isAnnotation() {
+ return false;
+ }
+
+ /**
+ * Determines whether this object represents an enum.
+ * It returns <code>true</code> if this object represents an enum.
+ *
+ * @since 3.2
+ */
+ public boolean isEnum() {
+ return false;
+ }
+
+ /**
+ * Returns the modifiers for this class, encoded in an integer.
+ * For decoding, use <code>javassist.Modifier</code>.
+ *
+ * <p>If the class is a static nested class (a.k.a. static inner class),
+ * the returned modifiers include <code>Modifier.STATIC</code>.
+ *
+ * @see Modifier
+ */
+ public int getModifiers() {
+ return 0;
+ }
+
+ /**
+ * Returns true if the class has the specified annotation class.
+ *
+ * @param clz the annotation class.
+ * @return <code>true</code> if the annotation is found, otherwise <code>false</code>.
+ * @since 3.11
+ */
+ public boolean hasAnnotation(Class clz) {
+ return false;
+ }
+
+ /**
+ * Returns the annotation if the class has the specified annotation class.
+ * For example, if an annotation <code>@Author</code> is associated
+ * with this class, an <code>Author</code> object is returned.
+ * The member values can be obtained by calling methods on
+ * the <code>Author</code> object.
+ *
+ * @param clz the annotation class.
+ * @return the annotation if found, otherwise <code>null</code>.
+ * @since 3.11
+ */
+ public Object getAnnotation(Class clz) throws ClassNotFoundException {
+ return null;
+ }
+
+ /**
+ * Returns the annotations associated with this class.
+ * For example, if an annotation <code>@Author</code> is associated
+ * with this class, the returned array contains an <code>Author</code>
+ * object. The member values can be obtained by calling methods on
+ * the <code>Author</code> object.
+ *
+ * @return an array of annotation-type objects.
+ * @see CtMember#getAnnotations()
+ * @since 3.1
+ */
+ public Object[] getAnnotations() throws ClassNotFoundException {
+ return new Object[0];
+ }
+
+ /**
+ * Returns the annotations associated with this class.
+ * This method is equivalent to <code>getAnnotations()</code>
+ * except that, if any annotations are not on the classpath,
+ * they are not included in the returned array.
+ *
+ * @return an array of annotation-type objects.
+ * @see #getAnnotations()
+ * @see CtMember#getAvailableAnnotations()
+ * @since 3.3
+ */
+ public Object[] getAvailableAnnotations(){
+ return new Object[0];
+ }
+
+ /**
+ * Returns an array of nested classes declared in the class.
+ * Nested classes are inner classes, anonymous classes, local classes,
+ * and static nested classes.
+ *
+ * @since 3.2
+ */
+ public CtClass[] getNestedClasses() throws NotFoundException {
+ return new CtClass[0];
+ }
+
+ /**
+ * Sets the modifiers.
+ *
+ * <p>If the class is a nested class, this method also modifies
+ * the class declaring that nested class (i.e. the enclosing
+ * class is modified).
+ *
+ * @param mod modifiers encoded by
+ * <code>javassist.Modifier</code>
+ * @see Modifier
+ */
+ public void setModifiers(int mod) {
+ checkModify();
+ }
+
+ /**
+ * Determines whether the class directly or indirectly extends
+ * the given class. If this class extends a class A and
+ * the class A extends a class B, then subclassof(B) returns true.
+ *
+ * <p>This method returns true if the given class is identical to
+ * the class represented by this object.
+ */
+ public boolean subclassOf(CtClass superclass) {
+ return false;
+ }
+
+ /**
+ * Obtains the class object representing the superclass of the
+ * class.
+ * It returns null if this object represents the
+ * <code>java.lang.Object</code> class and thus it does not have
+ * the super class.
+ *
+ * <p>If this object represents an interface, this method
+ * always returns the <code>java.lang.Object</code> class.
+ * To obtain the super interfaces
+ * extended by that interface, call <code>getInterfaces()</code>.
+ */
+ public CtClass getSuperclass() throws NotFoundException {
+ return null;
+ }
+
+ /**
+ * Changes a super class unless this object represents an interface.
+ * The new super class must be compatible with the old one; for example,
+ * it should inherit from the old super class.
+ *
+ * <p>If this object represents an interface, this method is equivalent
+ * to <code>addInterface()</code>; it appends <code>clazz</code> to
+ * the list of the super interfaces extended by that interface.
+ * Note that an interface can extend multiple super interfaces.
+ *
+ * @see #replaceClassName(String, String)
+ * @see #replaceClassName(ClassMap)
+ */
+ public void setSuperclass(CtClass clazz) throws CannotCompileException {
+ checkModify();
+ }
+
+ /**
+ * Obtains the class objects representing the interfaces implemented
+ * by the class or, if this object represents an interface, the interfaces
+ * extended by that interface.
+ */
+ public CtClass[] getInterfaces() throws NotFoundException {
+ return new CtClass[0];
+ }
+
+ /**
+ * Sets implemented interfaces. If this object represents an interface,
+ * this method sets the interfaces extended by that interface.
+ *
+ * @param list a list of the <code>CtClass</code> objects
+ * representing interfaces, or
+ * <code>null</code> if the class implements
+ * no interfaces.
+ */
+ public void setInterfaces(CtClass[] list) {
+ checkModify();
+ }
+
+ /**
+ * Adds an interface.
+ *
+ * @param anInterface the added interface.
+ */
+ public void addInterface(CtClass anInterface) {
+ checkModify();
+ }
+
+ /**
+ * If this class is a member class or interface of another class,
+ * then the class enclosing this class is returned.
+ *
+ * @return null if this class is a top-level class or an anonymous class.
+ */
+ public CtClass getDeclaringClass() throws NotFoundException {
+ return null;
+ }
+
+ /**
+ * Returns the immediately enclosing method of this class.
+ * This method works only with JDK 1.5 or later.
+ *
+ * @return null if this class is not a local class or an anonymous
+ * class.
+ */
+ public CtMethod getEnclosingMethod() throws NotFoundException {
+ return null;
+ }
+
+ /**
+ * Makes a new public nested class. If this method is called,
+ * the <code>CtClass</code>, which encloses the nested class, is modified
+ * since a class file includes a list of nested classes.
+ *
+ * <p>The current implementation only supports a static nested class.
+ * <code>isStatic</code> must be true.
+ *
+ * @param name the simple name of the nested class.
+ * @param isStatic true if the nested class is static.
+ */
+ public CtClass makeNestedClass(String name, boolean isStatic) {
+ throw new RuntimeException(getName() + " is not a class");
+ }
+
+ /**
+ * Returns an array containing <code>CtField</code> objects
+ * representing all the non-private fields of the class.
+ * That array includes non-private fields inherited from the
+ * superclasses.
+ */
+ public CtField[] getFields() { return new CtField[0]; }
+
+ /**
+ * Returns the field with the specified name. The returned field
+ * may be a private field declared in a super class or interface.
+ */
+ public CtField getField(String name) throws NotFoundException {
+ return getField(name, null);
+ }
+
+ /**
+ * Returns the field with the specified name and type. The returned field
+ * may be a private field declared in a super class or interface.
+ * Unlike Java, the JVM allows a class to have
+ * multiple fields with the same name but different types.
+ *
+ * @param name the field name.
+ * @param desc the type descriptor of the field. It is available by
+ * {@link CtField#getSignature()}.
+ * @see CtField#getSignature()
+ */
+ public CtField getField(String name, String desc) throws NotFoundException {
+ throw new NotFoundException(name);
+ }
+
+ /**
+ * @return null if the specified field is not found.
+ */
+ CtField getField2(String name, String desc) { return null; }
+
+ /**
+ * Gets all the fields declared in the class. The inherited fields
+ * are not included.
+ *
+ * <p>Note: the result does not include inherited fields.
+ */
+ public CtField[] getDeclaredFields() { return new CtField[0]; }
+
+ /**
+ * Retrieves the field with the specified name among the fields
+ * declared in the class.
+ *
+ * <p>Note: this method does not search the super classes.
+ */
+ public CtField getDeclaredField(String name) throws NotFoundException {
+ throw new NotFoundException(name);
+ }
+
+ /**
+ * Retrieves the field with the specified name and type among the fields
+ * declared in the class. Unlike Java, the JVM allows a class to have
+ * multiple fields with the same name but different types.
+ *
+ * <p>Note: this method does not search the super classes.
+ *
+ * @param name the field name.
+ * @param desc the type descriptor of the field. It is available by
+ * {@link CtField#getSignature()}.
+ * @see CtField#getSignature()
+ */
+ public CtField getDeclaredField(String name, String desc) throws NotFoundException {
+ throw new NotFoundException(name);
+ }
+
+ /**
+ * Gets all the constructors and methods declared in the class.
+ */
+ public CtBehavior[] getDeclaredBehaviors() {
+ return new CtBehavior[0];
+ }
+
+ /**
+ * Returns an array containing <code>CtConstructor</code> objects
+ * representing all the non-private constructors of the class.
+ */
+ public CtConstructor[] getConstructors() {
+ return new CtConstructor[0];
+ }
+
+ /**
+ * Returns the constructor with the given signature,
+ * which is represented by a character string
+ * called method descriptor.
+ * For details of the method descriptor, see the JVM specification
+ * or <code>javassist.bytecode.Descriptor</code>.
+ *
+ * @param desc method descriptor
+ * @see javassist.bytecode.Descriptor
+ */
+ public CtConstructor getConstructor(String desc)
+ throws NotFoundException
+ {
+ throw new NotFoundException("no such constructor");
+ }
+
+ /**
+ * Gets all the constructors declared in the class.
+ *
+ * @see javassist.CtConstructor
+ */
+ public CtConstructor[] getDeclaredConstructors() {
+ return new CtConstructor[0];
+ }
+
+ /**
+ * Returns a constructor receiving the specified parameters.
+ *
+ * @param params parameter types.
+ */
+ public CtConstructor getDeclaredConstructor(CtClass[] params)
+ throws NotFoundException
+ {
+ String desc = Descriptor.ofConstructor(params);
+ return getConstructor(desc);
+ }
+
+ /**
+ * Gets the class initializer (static constructor)
+ * declared in the class.
+ * This method returns <code>null</code> if
+ * no class initializer is not declared.
+ *
+ * @see #makeClassInitializer()
+ * @see javassist.CtConstructor
+ */
+ public CtConstructor getClassInitializer() {
+ return null;
+ }
+
+ /**
+ * Returns an array containing <code>CtMethod</code> objects
+ * representing all the non-private methods of the class.
+ * That array includes non-private methods inherited from the
+ * superclasses.
+ */
+ public CtMethod[] getMethods() {
+ return new CtMethod[0];
+ }
+
+ /**
+ * Returns the method with the given name and signature.
+ * The returned method may be declared in a super class.
+ * The method signature is represented by a character string
+ * called method descriptor,
+ * which is defined in the JVM specification.
+ *
+ * @param name method name
+ * @param desc method descriptor
+ * @see CtBehavior#getSignature()
+ * @see javassist.bytecode.Descriptor
+ */
+ public CtMethod getMethod(String name, String desc)
+ throws NotFoundException
+ {
+ throw new NotFoundException(name);
+ }
+
+ /**
+ * Gets all methods declared in the class. The inherited methods
+ * are not included.
+ *
+ * @see javassist.CtMethod
+ */
+ public CtMethod[] getDeclaredMethods() {
+ return new CtMethod[0];
+ }
+
+ /**
+ * Retrieves the method with the specified name and parameter types
+ * among the methods declared in the class.
+ *
+ * <p>Note: this method does not search the superclasses.
+ *
+ * @param name method name
+ * @param params parameter types
+ * @see javassist.CtMethod
+ */
+ public CtMethod getDeclaredMethod(String name, CtClass[] params)
+ throws NotFoundException
+ {
+ throw new NotFoundException(name);
+ }
+
+ /**
+ * Retrieves the method with the specified name among the methods
+ * declared in the class. If there are multiple methods with
+ * the specified name, then this method returns one of them.
+ *
+ * <p>Note: this method does not search the superclasses.
+ *
+ * @see javassist.CtMethod
+ */
+ public CtMethod getDeclaredMethod(String name) throws NotFoundException {
+ throw new NotFoundException(name);
+ }
+
+ /**
+ * Makes an empty class initializer (static constructor).
+ * If the class already includes a class initializer,
+ * this method returns it.
+ *
+ * @see #getClassInitializer()
+ */
+ public CtConstructor makeClassInitializer()
+ throws CannotCompileException
+ {
+ throw new CannotCompileException("not a class");
+ }
+
+ /**
+ * Adds a constructor. To add a class initializer (static constructor),
+ * call <code>makeClassInitializer()</code>.
+ *
+ * @see #makeClassInitializer()
+ */
+ public void addConstructor(CtConstructor c)
+ throws CannotCompileException
+ {
+ checkModify();
+ }
+
+ /**
+ * Removes a constructor declared in this class.
+ *
+ * @param c removed constructor.
+ * @throws NotFoundException if the constructor is not found.
+ */
+ public void removeConstructor(CtConstructor c) throws NotFoundException {
+ checkModify();
+ }
+
+ /**
+ * Adds a method.
+ */
+ public void addMethod(CtMethod m) throws CannotCompileException {
+ checkModify();
+ }
+
+ /**
+ * Removes a method declared in this class.
+ *
+ * @param m removed method.
+ * @throws NotFoundException if the method is not found.
+ */
+ public void removeMethod(CtMethod m) throws NotFoundException {
+ checkModify();
+ }
+
+ /**
+ * Adds a field.
+ *
+ * <p>The <code>CtField</code> belonging to another
+ * <code>CtClass</code> cannot be directly added to this class.
+ * Only a field created for this class can be added.
+ *
+ * @see javassist.CtField#CtField(CtField,CtClass)
+ */
+ public void addField(CtField f) throws CannotCompileException {
+ addField(f, (CtField.Initializer)null);
+ }
+
+ /**
+ * Adds a field with an initial value.
+ *
+ * <p>The <code>CtField</code> belonging to another
+ * <code>CtClass</code> cannot be directly added to this class.
+ * Only a field created for this class can be added.
+ *
+ * <p>The initial value is given as an expression written in Java.
+ * Any regular Java expression can be used for specifying the initial
+ * value. The followings are examples.
+ *
+ * <ul><pre>
+ * cc.addField(f, "0") // the initial value is 0.
+ * cc.addField(f, "i + 1") // i + 1.
+ * cc.addField(f, "new Point()"); // a Point object.
+ * </pre></ul>
+ *
+ * <p>Here, the type of variable <code>cc</code> is <code>CtClass</code>.
+ * The type of <code>f</code> is <code>CtField</code>.
+ *
+ * <p>Note: do not change the modifier of the field
+ * (in particular, do not add or remove <code>static</code>
+ * to/from the modifier)
+ * after it is added to the class by <code>addField()</code>.
+ *
+ * @param init an expression for the initial value.
+ *
+ * @see javassist.CtField.Initializer#byExpr(String)
+ * @see javassist.CtField#CtField(CtField,CtClass)
+ */
+ public void addField(CtField f, String init)
+ throws CannotCompileException
+ {
+ checkModify();
+ }
+
+ /**
+ * Adds a field with an initial value.
+ *
+ * <p>The <code>CtField</code> belonging to another
+ * <code>CtClass</code> cannot be directly added to this class.
+ * Only a field created for this class can be added.
+ *
+ * <p>For example,
+ *
+ * <ul><pre>
+ * CtClass cc = ...;
+ * addField(new CtField(CtClass.intType, "i", cc),
+ * CtField.Initializer.constant(1));
+ * </pre></ul>
+ *
+ * <p>This code adds an <code>int</code> field named "i". The
+ * initial value of this field is 1.
+ *
+ * @param init specifies the initial value of the field.
+ *
+ * @see javassist.CtField#CtField(CtField,CtClass)
+ */
+ public void addField(CtField f, CtField.Initializer init)
+ throws CannotCompileException
+ {
+ checkModify();
+ }
+
+ /**
+ * Removes a field declared in this class.
+ *
+ * @param f removed field.
+ * @throws NotFoundException if the field is not found.
+ */
+ public void removeField(CtField f) throws NotFoundException {
+ checkModify();
+ }
+
+ /**
+ * Obtains an attribute with the given name.
+ * If that attribute is not found in the class file, this
+ * method returns null.
+ *
+ * <p>This is a convenient method mainly for obtaining
+ * a user-defined attribute. For dealing with attributes, see the
+ * <code>javassist.bytecode</code> package. For example, the following
+ * expression returns all the attributes of a class file.
+ *
+ * <ul><pre>
+ * getClassFile().getAttributes()
+ * </pre></ul>
+ *
+ * @param name attribute name
+ * @see javassist.bytecode.AttributeInfo
+ */
+ public byte[] getAttribute(String name) {
+ return null;
+ }
+
+ /**
+ * Adds a named attribute.
+ * An arbitrary data (smaller than 64Kb) can be saved in the class
+ * file. Some attribute name are reserved by the JVM.
+ * The attributes with the non-reserved names are ignored when a
+ * class file is loaded into the JVM.
+ * If there is already an attribute with
+ * the same name, this method substitutes the new one for it.
+ *
+ * <p>This is a convenient method mainly for adding
+ * a user-defined attribute. For dealing with attributes, see the
+ * <code>javassist.bytecode</code> package. For example, the following
+ * expression adds an attribute <code>info</code> to a class file.
+ *
+ * <ul><pre>
+ * getClassFile().addAttribute(info)
+ * </pre></ul>
+ *
+ * @param name attribute name
+ * @param data attribute value
+ * @see javassist.bytecode.AttributeInfo
+ */
+ public void setAttribute(String name, byte[] data) {
+ checkModify();
+ }
+
+ /**
+ * Applies the given converter to all methods and constructors
+ * declared in the class. This method calls <code>instrument()</code>
+ * on every <code>CtMethod</code> and <code>CtConstructor</code> object
+ * in the class.
+ *
+ * @param converter specifies how to modify.
+ */
+ public void instrument(CodeConverter converter)
+ throws CannotCompileException
+ {
+ checkModify();
+ }
+
+ /**
+ * Modifies the bodies of all methods and constructors
+ * declared in the class. This method calls <code>instrument()</code>
+ * on every <code>CtMethod</code> and <code>CtConstructor</code> object
+ * in the class.
+ *
+ * @param editor specifies how to modify.
+ */
+ public void instrument(ExprEditor editor)
+ throws CannotCompileException
+ {
+ checkModify();
+ }
+
+ /**
+ * Converts this class to a <code>java.lang.Class</code> object.
+ * Once this method is called, further modifications are not
+ * allowed any more.
+ * To load the class, this method uses the context class loader
+ * of the current thread. If the program is running on some application
+ * server, the context class loader might be inappropriate to load the
+ * class.
+ *
+ * <p>This method is provided for convenience. If you need more
+ * complex functionality, you should write your own class loader.
+ *
+ * <p>Note: this method calls <code>toClass()</code>
+ * in <code>ClassPool</code>.
+ *
+ * <p><b>Warining:</b> A Class object returned by this method may not
+ * work with a security manager or a signed jar file because a
+ * protection domain is not specified.
+ *
+ * @see #toClass(java.lang.ClassLoader,ProtectionDomain)
+ * @see ClassPool#toClass(CtClass)
+ */
+ public Class toClass() throws CannotCompileException {
+ return getClassPool().toClass(this);
+ }
+
+ /**
+ * Converts this class to a <code>java.lang.Class</code> object.
+ * Once this method is called, further modifications are not allowed
+ * any more.
+ *
+ * <p>The class file represented by this <code>CtClass</code> is
+ * loaded by the given class loader to construct a
+ * <code>java.lang.Class</code> object. Since a private method
+ * on the class loader is invoked through the reflection API,
+ * the caller must have permissions to do that.
+ *
+ * <p>An easy way to obtain <code>ProtectionDomain</code> object is
+ * to call <code>getProtectionDomain()</code>
+ * in <code>java.lang.Class</code>. It returns the domain that
+ * the class belongs to.
+ *
+ * <p>This method is provided for convenience. If you need more
+ * complex functionality, you should write your own class loader.
+ *
+ * <p>Note: this method calls <code>toClass()</code>
+ * in <code>ClassPool</code>.
+ *
+ * @param loader the class loader used to load this class.
+ * If it is null, the class loader returned by
+ * {@link ClassPool#getClassLoader()} is used.
+ * @param domain the protection domain that the class belongs to.
+ * If it is null, the default domain created
+ * by <code>java.lang.ClassLoader</code> is used.
+ * @see ClassPool#toClass(CtClass,java.lang.ClassLoader)
+ * @since 3.3
+ */
+ public Class toClass(ClassLoader loader, ProtectionDomain domain)
+ throws CannotCompileException
+ {
+ ClassPool cp = getClassPool();
+ if (loader == null)
+ loader = cp.getClassLoader();
+
+ return cp.toClass(this, loader, domain);
+ }
+
+ /**
+ * Converts this class to a <code>java.lang.Class</code> object.
+ *
+ * <p><b>Warining:</b> A Class object returned by this method may not
+ * work with a security manager or a signed jar file because a
+ * protection domain is not specified.
+ *
+ * @deprecated Replaced by {@link #toClass(ClassLoader,ProtectionDomain)}
+ */
+ public final Class toClass(ClassLoader loader)
+ throws CannotCompileException
+ {
+ return getClassPool().toClass(this, loader);
+ }
+
+ /**
+ * Removes this <code>CtClass</code> object from the
+ * <code>ClassPool</code>.
+ * After this method is called, any method cannot be called on the
+ * removed <code>CtClass</code> object.
+ *
+ * <p>If <code>get()</code> in <code>ClassPool</code> is called
+ * with the name of the removed method,
+ * the <code>ClassPool</code> will read the class file again
+ * and constructs another <code>CtClass</code> object representing
+ * the same class.
+ */
+ public void detach() {
+ ClassPool cp = getClassPool();
+ CtClass obj = cp.removeCached(getName());
+ if (obj != this)
+ cp.cacheCtClass(getName(), obj, false);
+ }
+
+ /**
+ * Disallows (or allows) automatically pruning this <code>CtClass</code>
+ * object.
+ *
+ * <p>
+ * Javassist can automatically prune a <code>CtClass</code> object
+ * when <code>toBytecode()</code> (or <code>toClass()</code>,
+ * <code>writeFile()</code>) is called.
+ * Since a <code>ClassPool</code> holds all instances of <code>CtClass</code>
+ * even after <code>toBytecode()</code> (or <code>toClass()</code>,
+ * <code>writeFile()</code>) is called, pruning may significantly
+ * save memory consumption.
+ *
+ * <p>If <code>ClassPool.doPruning</code> is true, the automatic pruning
+ * is on by default. Otherwise, it is off. The default value of
+ * <code>ClassPool.doPruning</code> is false.
+ *
+ * @param stop disallow pruning if true. Otherwise, allow.
+ * @return the previous status of pruning. true if pruning is already stopped.
+ *
+ * @see #detach()
+ * @see #prune()
+ * @see ClassPool#doPruning
+ */
+ public boolean stopPruning(boolean stop) { return true; }
+
+ /**
+ * Discards unnecessary attributes, in particular,
+ * <code>CodeAttribute</code>s (method bodies) of the class,
+ * to minimize the memory footprint.
+ * After calling this method, the class is read only.
+ * It cannot be modified any more.
+ * Furthermore, <code>toBytecode()</code>,
+ * <code>writeFile()</code>, <code>toClass()</code>,
+ * or <code>instrument()</code> cannot be called.
+ * However, the method names and signatures in the class etc.
+ * are still accessible.
+ *
+ * <p><code>toBytecode()</code>, <code>writeFile()</code>, and
+ * <code>toClass()</code> internally call this method if
+ * automatic pruning is on.
+ *
+ * <p>According to some experiments, pruning does not really reduce
+ * memory consumption. Only about 20%. Since pruning takes time,
+ * it might not pay off. So the automatic pruning is off by default.
+ *
+ * @see #stopPruning(boolean)
+ * @see #detach()
+ * @see ClassPool#doPruning
+ *
+ * @see #toBytecode()
+ * @see #toClass()
+ * @see #writeFile()
+ * @see #instrument(CodeConverter)
+ * @see #instrument(ExprEditor)
+ */
+ public void prune() {}
+
+ /* Called by get() in ClassPool.
+ * CtClassType overrides this method.
+ */
+ void incGetCounter() {}
+
+ /**
+ * If this method is called, the class file will be
+ * rebuilt when it is finally generated by
+ * <code>toBytecode()</code> and <code>writeFile()</code>.
+ * For a performance reason, the symbol table of the
+ * class file may contain unused entries, for example,
+ * after a method or a filed is deleted.
+ * This method
+ * removes those unused entries. This removal will
+ * minimize the size of the class file.
+ *
+ * @since 3.8.1
+ */
+ public void rebuildClassFile() {}
+
+ /**
+ * Converts this class to a class file.
+ * Once this method is called, further modifications are not
+ * possible any more.
+ *
+ * @return the contents of the class file.
+ */
+ public byte[] toBytecode() throws IOException, CannotCompileException {
+ ByteArrayOutputStream barray = new ByteArrayOutputStream();
+ DataOutputStream out = new DataOutputStream(barray);
+ try {
+ toBytecode(out);
+ }
+ finally {
+ out.close();
+ }
+
+ return barray.toByteArray();
+ }
+
+ /**
+ * Writes a class file represented by this <code>CtClass</code>
+ * object in the current directory.
+ * Once this method is called, further modifications are not
+ * possible any more.
+ *
+ * @see #debugWriteFile()
+ */
+ public void writeFile()
+ throws NotFoundException, IOException, CannotCompileException
+ {
+ writeFile(".");
+ }
+
+ /**
+ * Writes a class file represented by this <code>CtClass</code>
+ * object on a local disk.
+ * Once this method is called, further modifications are not
+ * possible any more.
+ *
+ * @param directoryName it must end without a directory separator.
+ * @see #debugWriteFile(String)
+ */
+ public void writeFile(String directoryName)
+ throws CannotCompileException, IOException
+ {
+ String classname = getName();
+ String filename = directoryName + File.separatorChar
+ + classname.replace('.', File.separatorChar) + ".class";
+ int pos = filename.lastIndexOf(File.separatorChar);
+ if (pos > 0) {
+ String dir = filename.substring(0, pos);
+ if (!dir.equals("."))
+ new File(dir).mkdirs();
+ }
+
+ DataOutputStream out
+ = new DataOutputStream(new BufferedOutputStream(
+ new DelayedFileOutputStream(filename)));
+ try {
+ toBytecode(out);
+ }
+ finally {
+ out.close();
+ }
+ }
+
+ /**
+ * Writes a class file as <code>writeFile()</code> does although this
+ * method does not prune or freeze the class after writing the class
+ * file. Note that, once <code>writeFile()</code> or <code>toBytecode()</code>
+ * is called, it cannot be called again since the class is pruned and frozen.
+ * This method would be useful for debugging.
+ */
+ public void debugWriteFile() {
+ debugWriteFile(".");
+ }
+
+ /**
+ * Writes a class file as <code>writeFile()</code> does although this
+ * method does not prune or freeze the class after writing the class
+ * file. Note that, once <code>writeFile()</code> or <code>toBytecode()</code>
+ * is called, it cannot be called again since the class is pruned and frozen.
+ * This method would be useful for debugging.
+ *
+ * @param directoryName it must end without a directory separator.
+ */
+ public void debugWriteFile(String directoryName) {
+ try {
+ boolean p = stopPruning(true);
+ writeFile(directoryName);
+ defrost();
+ stopPruning(p);
+ }
+ catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ static class DelayedFileOutputStream extends OutputStream {
+ private FileOutputStream file;
+ private String filename;
+
+ DelayedFileOutputStream(String name) {
+ file = null;
+ filename = name;
+ }
+
+ private void init() throws IOException {
+ if (file == null)
+ file = new FileOutputStream(filename);
+ }
+
+ public void write(int b) throws IOException {
+ init();
+ file.write(b);
+ }
+
+ public void write(byte[] b) throws IOException {
+ init();
+ file.write(b);
+ }
+
+ public void write(byte[] b, int off, int len) throws IOException {
+ init();
+ file.write(b, off, len);
+
+ }
+
+ public void flush() throws IOException {
+ init();
+ file.flush();
+ }
+
+ public void close() throws IOException {
+ init();
+ file.close();
+ }
+ }
+
+ /**
+ * Converts this class to a class file.
+ * Once this method is called, further modifications are not
+ * possible any more.
+ *
+ * <p>This method dose not close the output stream in the end.
+ *
+ * @param out the output stream that a class file is written to.
+ */
+ public void toBytecode(DataOutputStream out)
+ throws CannotCompileException, IOException
+ {
+ throw new CannotCompileException("not a class");
+ }
+
+ /**
+ * Makes a unique member name. This method guarantees that
+ * the returned name is not used as a prefix of any methods
+ * or fields visible in this class.
+ * If the returned name is XYZ, then any method or field names
+ * in this class do not start with XYZ.
+ *
+ * @param prefix the prefix of the member name.
+ */
+ public String makeUniqueName(String prefix) {
+ throw new RuntimeException("not available in " + getName());
+ }
+
+ /* Invoked from ClassPool#compress().
+ * This method is overridden by CtClassType.
+ */
+ void compress() {}
+}
diff --git a/src/main/javassist/CtClassType.java b/src/main/javassist/CtClassType.java
new file mode 100644
index 0000000..dad5db7
--- /dev/null
+++ b/src/main/javassist/CtClassType.java
@@ -0,0 +1,1695 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist;
+
+import java.lang.ref.WeakReference;
+import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Set;
+
+import javassist.bytecode.AccessFlag;
+import javassist.bytecode.AttributeInfo;
+import javassist.bytecode.AnnotationsAttribute;
+import javassist.bytecode.BadBytecode;
+import javassist.bytecode.Bytecode;
+import javassist.bytecode.ClassFile;
+import javassist.bytecode.CodeAttribute;
+import javassist.bytecode.ConstantAttribute;
+import javassist.bytecode.CodeIterator;
+import javassist.bytecode.ConstPool;
+import javassist.bytecode.Descriptor;
+import javassist.bytecode.EnclosingMethodAttribute;
+import javassist.bytecode.FieldInfo;
+import javassist.bytecode.InnerClassesAttribute;
+import javassist.bytecode.MethodInfo;
+import javassist.bytecode.ParameterAnnotationsAttribute;
+import javassist.bytecode.annotation.Annotation;
+import javassist.compiler.AccessorMaker;
+import javassist.compiler.CompileError;
+import javassist.compiler.Javac;
+import javassist.expr.ExprEditor;
+
+/**
+ * Class types.
+ */
+class CtClassType extends CtClass {
+ ClassPool classPool;
+ boolean wasChanged;
+ private boolean wasFrozen;
+ boolean wasPruned;
+ boolean gcConstPool; // if true, the constant pool entries will be garbage collected.
+ ClassFile classfile;
+ byte[] rawClassfile; // backup storage
+
+ private WeakReference memberCache;
+ private AccessorMaker accessors;
+
+ private FieldInitLink fieldInitializers;
+ private Hashtable hiddenMethods; // must be synchronous
+ private int uniqueNumberSeed;
+
+ private boolean doPruning = ClassPool.doPruning;
+ private int getCount;
+ private static final int GET_THRESHOLD = 2; // see compress()
+
+ CtClassType(String name, ClassPool cp) {
+ super(name);
+ classPool = cp;
+ wasChanged = wasFrozen = wasPruned = gcConstPool = false;
+ classfile = null;
+ rawClassfile = null;
+ memberCache = null;
+ accessors = null;
+ fieldInitializers = null;
+ hiddenMethods = null;
+ uniqueNumberSeed = 0;
+ getCount = 0;
+ }
+
+ CtClassType(InputStream ins, ClassPool cp) throws IOException {
+ this((String)null, cp);
+ classfile = new ClassFile(new DataInputStream(ins));
+ qualifiedName = classfile.getName();
+ }
+
+ protected void extendToString(StringBuffer buffer) {
+ if (wasChanged)
+ buffer.append("changed ");
+
+ if (wasFrozen)
+ buffer.append("frozen ");
+
+ if (wasPruned)
+ buffer.append("pruned ");
+
+ buffer.append(Modifier.toString(getModifiers()));
+ buffer.append(" class ");
+ buffer.append(getName());
+
+ try {
+ CtClass ext = getSuperclass();
+ if (ext != null) {
+ String name = ext.getName();
+ if (!name.equals("java.lang.Object"))
+ buffer.append(" extends " + ext.getName());
+ }
+ }
+ catch (NotFoundException e) {
+ buffer.append(" extends ??");
+ }
+
+ try {
+ CtClass[] intf = getInterfaces();
+ if (intf.length > 0)
+ buffer.append(" implements ");
+
+ for (int i = 0; i < intf.length; ++i) {
+ buffer.append(intf[i].getName());
+ buffer.append(", ");
+ }
+ }
+ catch (NotFoundException e) {
+ buffer.append(" extends ??");
+ }
+
+ CtMember.Cache memCache = getMembers();
+ exToString(buffer, " fields=",
+ memCache.fieldHead(), memCache.lastField());
+ exToString(buffer, " constructors=",
+ memCache.consHead(), memCache.lastCons());
+ exToString(buffer, " methods=",
+ memCache.methodHead(), memCache.lastMethod());
+ }
+
+ private void exToString(StringBuffer buffer, String msg,
+ CtMember head, CtMember tail) {
+ buffer.append(msg);
+ while (head != tail) {
+ head = head.next();
+ buffer.append(head);
+ buffer.append(", ");
+ }
+ }
+
+ public AccessorMaker getAccessorMaker() {
+ if (accessors == null)
+ accessors = new AccessorMaker(this);
+
+ return accessors;
+ }
+
+ public ClassFile getClassFile2() {
+ ClassFile cfile = classfile;
+ if (cfile != null)
+ return cfile;
+
+ classPool.compress();
+ if (rawClassfile != null) {
+ try {
+ classfile = new ClassFile(new DataInputStream(
+ new ByteArrayInputStream(rawClassfile)));
+ rawClassfile = null;
+ getCount = GET_THRESHOLD;
+ return classfile;
+ }
+ catch (IOException e) {
+ throw new RuntimeException(e.toString(), e);
+ }
+ }
+
+ InputStream fin = null;
+ try {
+ fin = classPool.openClassfile(getName());
+ if (fin == null)
+ throw new NotFoundException(getName());
+
+ fin = new BufferedInputStream(fin);
+ ClassFile cf = new ClassFile(new DataInputStream(fin));
+ if (!cf.getName().equals(qualifiedName))
+ throw new RuntimeException("cannot find " + qualifiedName + ": "
+ + cf.getName() + " found in "
+ + qualifiedName.replace('.', '/') + ".class");
+
+ classfile = cf;
+ return cf;
+ }
+ catch (NotFoundException e) {
+ throw new RuntimeException(e.toString(), e);
+ }
+ catch (IOException e) {
+ throw new RuntimeException(e.toString(), e);
+ }
+ finally {
+ if (fin != null)
+ try {
+ fin.close();
+ }
+ catch (IOException e) {}
+ }
+ }
+
+ /* Inherited from CtClass. Called by get() in ClassPool.
+ *
+ * @see javassist.CtClass#incGetCounter()
+ * @see #toBytecode(DataOutputStream)
+ */
+ final void incGetCounter() { ++getCount; }
+
+ /**
+ * Invoked from ClassPool#compress().
+ * It releases the class files that have not been recently used
+ * if they are unmodified.
+ */
+ void compress() {
+ if (getCount < GET_THRESHOLD)
+ if (!isModified() && ClassPool.releaseUnmodifiedClassFile)
+ removeClassFile();
+ else if (isFrozen() && !wasPruned)
+ saveClassFile();
+
+ getCount = 0;
+ }
+
+ /**
+ * Converts a ClassFile object into a byte array
+ * for saving memory space.
+ */
+ private synchronized void saveClassFile() {
+ /* getMembers() and releaseClassFile() are also synchronized.
+ */
+ if (classfile == null || hasMemberCache() != null)
+ return;
+
+ ByteArrayOutputStream barray = new ByteArrayOutputStream();
+ DataOutputStream out = new DataOutputStream(barray);
+ try {
+ classfile.write(out);
+ barray.close();
+ rawClassfile = barray.toByteArray();
+ classfile = null;
+ }
+ catch (IOException e) {}
+ }
+
+ private synchronized void removeClassFile() {
+ if (classfile != null && !isModified() && hasMemberCache() == null)
+ classfile = null;
+ }
+
+ public ClassPool getClassPool() { return classPool; }
+
+ void setClassPool(ClassPool cp) { classPool = cp; }
+
+ public URL getURL() throws NotFoundException {
+ URL url = classPool.find(getName());
+ if (url == null)
+ throw new NotFoundException(getName());
+ else
+ return url;
+ }
+
+ public boolean isModified() { return wasChanged; }
+
+ public boolean isFrozen() { return wasFrozen; }
+
+ public void freeze() { wasFrozen = true; }
+
+ void checkModify() throws RuntimeException {
+ if (isFrozen()) {
+ String msg = getName() + " class is frozen";
+ if (wasPruned)
+ msg += " and pruned";
+
+ throw new RuntimeException(msg);
+ }
+
+ wasChanged = true;
+ }
+
+ public void defrost() {
+ checkPruned("defrost");
+ wasFrozen = false;
+ }
+
+ public boolean subtypeOf(CtClass clazz) throws NotFoundException {
+ int i;
+ String cname = clazz.getName();
+ if (this == clazz || getName().equals(cname))
+ return true;
+
+ ClassFile file = getClassFile2();
+ String supername = file.getSuperclass();
+ if (supername != null && supername.equals(cname))
+ return true;
+
+ String[] ifs = file.getInterfaces();
+ int num = ifs.length;
+ for (i = 0; i < num; ++i)
+ if (ifs[i].equals(cname))
+ return true;
+
+ if (supername != null && classPool.get(supername).subtypeOf(clazz))
+ return true;
+
+ for (i = 0; i < num; ++i)
+ if (classPool.get(ifs[i]).subtypeOf(clazz))
+ return true;
+
+ return false;
+ }
+
+ public void setName(String name) throws RuntimeException {
+ String oldname = getName();
+ if (name.equals(oldname))
+ return;
+
+ // check this in advance although classNameChanged() below does.
+ classPool.checkNotFrozen(name);
+ ClassFile cf = getClassFile2();
+ super.setName(name);
+ cf.setName(name);
+ nameReplaced();
+ classPool.classNameChanged(oldname, this);
+ }
+
+ public void replaceClassName(ClassMap classnames)
+ throws RuntimeException
+ {
+ String oldClassName = getName();
+ String newClassName
+ = (String)classnames.get(Descriptor.toJvmName(oldClassName));
+ if (newClassName != null) {
+ newClassName = Descriptor.toJavaName(newClassName);
+ // check this in advance although classNameChanged() below does.
+ classPool.checkNotFrozen(newClassName);
+ }
+
+ super.replaceClassName(classnames);
+ ClassFile cf = getClassFile2();
+ cf.renameClass(classnames);
+ nameReplaced();
+
+ if (newClassName != null) {
+ super.setName(newClassName);
+ classPool.classNameChanged(oldClassName, this);
+ }
+ }
+
+ public void replaceClassName(String oldname, String newname)
+ throws RuntimeException
+ {
+ String thisname = getName();
+ if (thisname.equals(oldname))
+ setName(newname);
+ else {
+ super.replaceClassName(oldname, newname);
+ getClassFile2().renameClass(oldname, newname);
+ nameReplaced();
+ }
+ }
+
+ public boolean isInterface() {
+ return Modifier.isInterface(getModifiers());
+ }
+
+ public boolean isAnnotation() {
+ return Modifier.isAnnotation(getModifiers());
+ }
+
+ public boolean isEnum() {
+ return Modifier.isEnum(getModifiers());
+ }
+
+ public int getModifiers() {
+ ClassFile cf = getClassFile2();
+ int acc = cf.getAccessFlags();
+ acc = AccessFlag.clear(acc, AccessFlag.SUPER);
+ int inner = cf.getInnerAccessFlags();
+ if (inner != -1 && (inner & AccessFlag.STATIC) != 0)
+ acc |= AccessFlag.STATIC;
+
+ return AccessFlag.toModifier(acc);
+ }
+
+ public CtClass[] getNestedClasses() throws NotFoundException {
+ ClassFile cf = getClassFile2();
+ InnerClassesAttribute ica
+ = (InnerClassesAttribute)cf.getAttribute(InnerClassesAttribute.tag);
+ if (ica == null)
+ return new CtClass[0];
+
+ String thisName = cf.getName() + "$";
+ int n = ica.tableLength();
+ ArrayList list = new ArrayList(n);
+ for (int i = 0; i < n; i++) {
+ String name = ica.innerClass(i);
+ if (name != null)
+ if (name.startsWith(thisName)) {
+ // if it is an immediate nested class
+ if (name.lastIndexOf('$') < thisName.length())
+ list.add(classPool.get(name));
+ }
+ }
+
+ return (CtClass[])list.toArray(new CtClass[list.size()]);
+ }
+
+ public void setModifiers(int mod) {
+ ClassFile cf = getClassFile2();
+ if (Modifier.isStatic(mod)) {
+ int flags = cf.getInnerAccessFlags();
+ if (flags != -1 && (flags & AccessFlag.STATIC) != 0)
+ mod = mod & ~Modifier.STATIC;
+ else
+ throw new RuntimeException("cannot change " + getName() + " into a static class");
+ }
+
+ checkModify();
+ cf.setAccessFlags(AccessFlag.of(mod));
+ }
+
+ public boolean hasAnnotation(Class clz) {
+ ClassFile cf = getClassFile2();
+ AnnotationsAttribute ainfo = (AnnotationsAttribute)
+ cf.getAttribute(AnnotationsAttribute.invisibleTag);
+ AnnotationsAttribute ainfo2 = (AnnotationsAttribute)
+ cf.getAttribute(AnnotationsAttribute.visibleTag);
+ return hasAnnotationType(clz, getClassPool(), ainfo, ainfo2);
+ }
+
+ static boolean hasAnnotationType(Class clz, ClassPool cp,
+ AnnotationsAttribute a1, AnnotationsAttribute a2)
+ {
+ Annotation[] anno1, anno2;
+
+ if (a1 == null)
+ anno1 = null;
+ else
+ anno1 = a1.getAnnotations();
+
+ if (a2 == null)
+ anno2 = null;
+ else
+ anno2 = a2.getAnnotations();
+
+ String typeName = clz.getName();
+ if (anno1 != null)
+ for (int i = 0; i < anno1.length; i++)
+ if (anno1[i].getTypeName().equals(typeName))
+ return true;
+
+ if (anno2 != null)
+ for (int i = 0; i < anno2.length; i++)
+ if (anno2[i].getTypeName().equals(typeName))
+ return true;
+
+ return false;
+ }
+
+ public Object getAnnotation(Class clz) throws ClassNotFoundException {
+ ClassFile cf = getClassFile2();
+ AnnotationsAttribute ainfo = (AnnotationsAttribute)
+ cf.getAttribute(AnnotationsAttribute.invisibleTag);
+ AnnotationsAttribute ainfo2 = (AnnotationsAttribute)
+ cf.getAttribute(AnnotationsAttribute.visibleTag);
+ return getAnnotationType(clz, getClassPool(), ainfo, ainfo2);
+ }
+
+ static Object getAnnotationType(Class clz, ClassPool cp,
+ AnnotationsAttribute a1, AnnotationsAttribute a2)
+ throws ClassNotFoundException
+ {
+ Annotation[] anno1, anno2;
+
+ if (a1 == null)
+ anno1 = null;
+ else
+ anno1 = a1.getAnnotations();
+
+ if (a2 == null)
+ anno2 = null;
+ else
+ anno2 = a2.getAnnotations();
+
+ String typeName = clz.getName();
+ if (anno1 != null)
+ for (int i = 0; i < anno1.length; i++)
+ if (anno1[i].getTypeName().equals(typeName))
+ return toAnnoType(anno1[i], cp);
+
+ if (anno2 != null)
+ for (int i = 0; i < anno2.length; i++)
+ if (anno2[i].getTypeName().equals(typeName))
+ return toAnnoType(anno2[i], cp);
+
+ return null;
+ }
+
+ public Object[] getAnnotations() throws ClassNotFoundException {
+ return getAnnotations(false);
+ }
+
+ public Object[] getAvailableAnnotations(){
+ try {
+ return getAnnotations(true);
+ }
+ catch (ClassNotFoundException e) {
+ throw new RuntimeException("Unexpected exception ", e);
+ }
+ }
+
+ private Object[] getAnnotations(boolean ignoreNotFound)
+ throws ClassNotFoundException
+ {
+ ClassFile cf = getClassFile2();
+ AnnotationsAttribute ainfo = (AnnotationsAttribute)
+ cf.getAttribute(AnnotationsAttribute.invisibleTag);
+ AnnotationsAttribute ainfo2 = (AnnotationsAttribute)
+ cf.getAttribute(AnnotationsAttribute.visibleTag);
+ return toAnnotationType(ignoreNotFound, getClassPool(), ainfo, ainfo2);
+ }
+
+ static Object[] toAnnotationType(boolean ignoreNotFound, ClassPool cp,
+ AnnotationsAttribute a1, AnnotationsAttribute a2)
+ throws ClassNotFoundException
+ {
+ Annotation[] anno1, anno2;
+ int size1, size2;
+
+ if (a1 == null) {
+ anno1 = null;
+ size1 = 0;
+ }
+ else {
+ anno1 = a1.getAnnotations();
+ size1 = anno1.length;
+ }
+
+ if (a2 == null) {
+ anno2 = null;
+ size2 = 0;
+ }
+ else {
+ anno2 = a2.getAnnotations();
+ size2 = anno2.length;
+ }
+
+ if (!ignoreNotFound){
+ Object[] result = new Object[size1 + size2];
+ for (int i = 0; i < size1; i++)
+ result[i] = toAnnoType(anno1[i], cp);
+
+ for (int j = 0; j < size2; j++)
+ result[j + size1] = toAnnoType(anno2[j], cp);
+
+ return result;
+ }
+ else{
+ ArrayList annotations = new ArrayList();
+ for (int i = 0 ; i < size1 ; i++){
+ try{
+ annotations.add(toAnnoType(anno1[i], cp));
+ }
+ catch(ClassNotFoundException e){}
+ }
+ for (int j = 0; j < size2; j++) {
+ try{
+ annotations.add(toAnnoType(anno2[j], cp));
+ }
+ catch(ClassNotFoundException e){}
+ }
+
+ return annotations.toArray();
+ }
+ }
+
+ static Object[][] toAnnotationType(boolean ignoreNotFound, ClassPool cp,
+ ParameterAnnotationsAttribute a1,
+ ParameterAnnotationsAttribute a2,
+ MethodInfo minfo)
+ throws ClassNotFoundException
+ {
+ int numParameters = 0;
+ if (a1 != null)
+ numParameters = a1.numParameters();
+ else if (a2 != null)
+ numParameters = a2.numParameters();
+ else
+ numParameters = Descriptor.numOfParameters(minfo.getDescriptor());
+
+ Object[][] result = new Object[numParameters][];
+ for (int i = 0; i < numParameters; i++) {
+ Annotation[] anno1, anno2;
+ int size1, size2;
+
+ if (a1 == null) {
+ anno1 = null;
+ size1 = 0;
+ }
+ else {
+ anno1 = a1.getAnnotations()[i];
+ size1 = anno1.length;
+ }
+
+ if (a2 == null) {
+ anno2 = null;
+ size2 = 0;
+ }
+ else {
+ anno2 = a2.getAnnotations()[i];
+ size2 = anno2.length;
+ }
+
+ if (!ignoreNotFound){
+ result[i] = new Object[size1 + size2];
+ for (int j = 0; j < size1; ++j)
+ result[i][j] = toAnnoType(anno1[j], cp);
+
+ for (int j = 0; j < size2; ++j)
+ result[i][j + size1] = toAnnoType(anno2[j], cp);
+ }
+ else{
+ ArrayList annotations = new ArrayList();
+ for (int j = 0 ; j < size1 ; j++){
+ try{
+ annotations.add(toAnnoType(anno1[j], cp));
+ }
+ catch(ClassNotFoundException e){}
+ }
+ for (int j = 0; j < size2; j++){
+ try{
+ annotations.add(toAnnoType(anno2[j], cp));
+ }
+ catch(ClassNotFoundException e){}
+ }
+
+ result[i] = annotations.toArray();
+ }
+ }
+
+ return result;
+ }
+
+ private static Object toAnnoType(Annotation anno, ClassPool cp)
+ throws ClassNotFoundException
+ {
+ try {
+ ClassLoader cl = cp.getClassLoader();
+ return anno.toAnnotationType(cl, cp);
+ }
+ catch (ClassNotFoundException e) {
+ ClassLoader cl2 = cp.getClass().getClassLoader();
+ return anno.toAnnotationType(cl2, cp);
+ }
+ }
+
+ public boolean subclassOf(CtClass superclass) {
+ if (superclass == null)
+ return false;
+
+ String superName = superclass.getName();
+ CtClass curr = this;
+ try {
+ while (curr != null) {
+ if (curr.getName().equals(superName))
+ return true;
+
+ curr = curr.getSuperclass();
+ }
+ }
+ catch (Exception ignored) {}
+ return false;
+ }
+
+ public CtClass getSuperclass() throws NotFoundException {
+ String supername = getClassFile2().getSuperclass();
+ if (supername == null)
+ return null;
+ else
+ return classPool.get(supername);
+ }
+
+ public void setSuperclass(CtClass clazz) throws CannotCompileException {
+ checkModify();
+ if (isInterface())
+ addInterface(clazz);
+ else
+ getClassFile2().setSuperclass(clazz.getName());
+ }
+
+ public CtClass[] getInterfaces() throws NotFoundException {
+ String[] ifs = getClassFile2().getInterfaces();
+ int num = ifs.length;
+ CtClass[] ifc = new CtClass[num];
+ for (int i = 0; i < num; ++i)
+ ifc[i] = classPool.get(ifs[i]);
+
+ return ifc;
+ }
+
+ public void setInterfaces(CtClass[] list) {
+ checkModify();
+ String[] ifs;
+ if (list == null)
+ ifs = new String[0];
+ else {
+ int num = list.length;
+ ifs = new String[num];
+ for (int i = 0; i < num; ++i)
+ ifs[i] = list[i].getName();
+ }
+
+ getClassFile2().setInterfaces(ifs);
+ }
+
+ public void addInterface(CtClass anInterface) {
+ checkModify();
+ if (anInterface != null)
+ getClassFile2().addInterface(anInterface.getName());
+ }
+
+ public CtClass getDeclaringClass() throws NotFoundException {
+ ClassFile cf = getClassFile2();
+ InnerClassesAttribute ica = (InnerClassesAttribute)cf.getAttribute(
+ InnerClassesAttribute.tag);
+ if (ica == null)
+ return null;
+
+ String name = getName();
+ int n = ica.tableLength();
+ for (int i = 0; i < n; ++i)
+ if (name.equals(ica.innerClass(i))) {
+ String outName = ica.outerClass(i);
+ if (outName != null)
+ return classPool.get(outName);
+ else {
+ // maybe anonymous or local class.
+ EnclosingMethodAttribute ema
+ = (EnclosingMethodAttribute)cf.getAttribute(
+ EnclosingMethodAttribute.tag);
+ if (ema != null)
+ return classPool.get(ema.className());
+ }
+ }
+
+ return null;
+ }
+
+ public CtMethod getEnclosingMethod() throws NotFoundException {
+ ClassFile cf = getClassFile2();
+ EnclosingMethodAttribute ema
+ = (EnclosingMethodAttribute)cf.getAttribute(
+ EnclosingMethodAttribute.tag);
+ if (ema != null) {
+ CtClass enc = classPool.get(ema.className());
+ return enc.getMethod(ema.methodName(), ema.methodDescriptor());
+ }
+
+ return null;
+ }
+
+ public CtClass makeNestedClass(String name, boolean isStatic) {
+ if (!isStatic)
+ throw new RuntimeException(
+ "sorry, only nested static class is supported");
+
+ checkModify();
+ CtClass c = classPool.makeNestedClass(getName() + "$" + name);
+ ClassFile cf = getClassFile2();
+ ClassFile cf2 = c.getClassFile2();
+ InnerClassesAttribute ica = (InnerClassesAttribute)cf.getAttribute(
+ InnerClassesAttribute.tag);
+ if (ica == null) {
+ ica = new InnerClassesAttribute(cf.getConstPool());
+ cf.addAttribute(ica);
+ }
+
+ ica.append(c.getName(), this.getName(), name,
+ (cf2.getAccessFlags() & ~AccessFlag.SUPER) | AccessFlag.STATIC);
+ cf2.addAttribute(ica.copy(cf2.getConstPool(), null));
+ return c;
+ }
+
+ /* flush cached names.
+ */
+ private void nameReplaced() {
+ CtMember.Cache cache = hasMemberCache();
+ if (cache != null) {
+ CtMember mth = cache.methodHead();
+ CtMember tail = cache.lastMethod();
+ while (mth != tail) {
+ mth = mth.next();
+ mth.nameReplaced();
+ }
+ }
+ }
+
+ /**
+ * Returns null if members are not cached.
+ */
+ protected CtMember.Cache hasMemberCache() {
+ if (memberCache != null)
+ return (CtMember.Cache)memberCache.get();
+ else
+ return null;
+ }
+
+ protected synchronized CtMember.Cache getMembers() {
+ CtMember.Cache cache = null;
+ if (memberCache == null
+ || (cache = (CtMember.Cache)memberCache.get()) == null) {
+ cache = new CtMember.Cache(this);
+ makeFieldCache(cache);
+ makeBehaviorCache(cache);
+ memberCache = new WeakReference(cache);
+ }
+
+ return cache;
+ }
+
+ private void makeFieldCache(CtMember.Cache cache) {
+ List list = getClassFile2().getFields();
+ int n = list.size();
+ for (int i = 0; i < n; ++i) {
+ FieldInfo finfo = (FieldInfo)list.get(i);
+ CtField newField = new CtField(finfo, this);
+ cache.addField(newField);
+ }
+ }
+
+ private void makeBehaviorCache(CtMember.Cache cache) {
+ List list = getClassFile2().getMethods();
+ int n = list.size();
+ for (int i = 0; i < n; ++i) {
+ MethodInfo minfo = (MethodInfo)list.get(i);
+ if (minfo.isMethod()) {
+ CtMethod newMethod = new CtMethod(minfo, this);
+ cache.addMethod(newMethod);
+ }
+ else {
+ CtConstructor newCons = new CtConstructor(minfo, this);
+ cache.addConstructor(newCons);
+ }
+ }
+ }
+
+ public CtField[] getFields() {
+ ArrayList alist = new ArrayList();
+ getFields(alist, this);
+ return (CtField[])alist.toArray(new CtField[alist.size()]);
+ }
+
+ private static void getFields(ArrayList alist, CtClass cc) {
+ int i, num;
+ if (cc == null)
+ return;
+
+ try {
+ getFields(alist, cc.getSuperclass());
+ }
+ catch (NotFoundException e) {}
+
+ try {
+ CtClass[] ifs = cc.getInterfaces();
+ num = ifs.length;
+ for (i = 0; i < num; ++i)
+ getFields(alist, ifs[i]);
+ }
+ catch (NotFoundException e) {}
+
+ CtMember.Cache memCache = ((CtClassType)cc).getMembers();
+ CtMember field = memCache.fieldHead();
+ CtMember tail = memCache.lastField();
+ while (field != tail) {
+ field = field.next();
+ if (!Modifier.isPrivate(field.getModifiers()))
+ alist.add(field);
+ }
+ }
+
+ public CtField getField(String name, String desc) throws NotFoundException {
+ CtField f = getField2(name, desc);
+ return checkGetField(f, name, desc);
+ }
+
+ private CtField checkGetField(CtField f, String name, String desc)
+ throws NotFoundException
+ {
+ if (f == null) {
+ String msg = "field: " + name;
+ if (desc != null)
+ msg += " type " + desc;
+
+ throw new NotFoundException(msg + " in " + getName());
+ }
+ else
+ return f;
+ }
+
+ CtField getField2(String name, String desc) {
+ CtField df = getDeclaredField2(name, desc);
+ if (df != null)
+ return df;
+
+ try {
+ CtClass[] ifs = getInterfaces();
+ int num = ifs.length;
+ for (int i = 0; i < num; ++i) {
+ CtField f = ifs[i].getField2(name, desc);
+ if (f != null)
+ return f;
+ }
+
+ CtClass s = getSuperclass();
+ if (s != null)
+ return s.getField2(name, desc);
+ }
+ catch (NotFoundException e) {}
+ return null;
+ }
+
+ public CtField[] getDeclaredFields() {
+ CtMember.Cache memCache = getMembers();
+ CtMember field = memCache.fieldHead();
+ CtMember tail = memCache.lastField();
+ int num = CtMember.Cache.count(field, tail);
+ CtField[] cfs = new CtField[num];
+ int i = 0;
+ while (field != tail) {
+ field = field.next();
+ cfs[i++] = (CtField)field;
+ }
+
+ return cfs;
+ }
+
+ public CtField getDeclaredField(String name) throws NotFoundException {
+ return getDeclaredField(name, null);
+ }
+
+ public CtField getDeclaredField(String name, String desc) throws NotFoundException {
+ CtField f = getDeclaredField2(name, desc);
+ return checkGetField(f, name, desc);
+ }
+
+ private CtField getDeclaredField2(String name, String desc) {
+ CtMember.Cache memCache = getMembers();
+ CtMember field = memCache.fieldHead();
+ CtMember tail = memCache.lastField();
+ while (field != tail) {
+ field = field.next();
+ if (field.getName().equals(name)
+ && (desc == null || desc.equals(field.getSignature())))
+ return (CtField)field;
+ }
+
+ return null;
+ }
+
+ public CtBehavior[] getDeclaredBehaviors() {
+ CtMember.Cache memCache = getMembers();
+ CtMember cons = memCache.consHead();
+ CtMember consTail = memCache.lastCons();
+ int cnum = CtMember.Cache.count(cons, consTail);
+ CtMember mth = memCache.methodHead();
+ CtMember mthTail = memCache.lastMethod();
+ int mnum = CtMember.Cache.count(mth, mthTail);
+
+ CtBehavior[] cb = new CtBehavior[cnum + mnum];
+ int i = 0;
+ while (cons != consTail) {
+ cons = cons.next();
+ cb[i++] = (CtBehavior)cons;
+ }
+
+ while (mth != mthTail) {
+ mth = mth.next();
+ cb[i++] = (CtBehavior)mth;
+ }
+
+ return cb;
+ }
+
+ public CtConstructor[] getConstructors() {
+ CtMember.Cache memCache = getMembers();
+ CtMember cons = memCache.consHead();
+ CtMember consTail = memCache.lastCons();
+
+ int n = 0;
+ CtMember mem = cons;
+ while (mem != consTail) {
+ mem = mem.next();
+ if (isPubCons((CtConstructor)mem))
+ n++;
+ }
+
+ CtConstructor[] result = new CtConstructor[n];
+ int i = 0;
+ mem = cons;
+ while (mem != consTail) {
+ mem = mem.next();
+ CtConstructor cc = (CtConstructor)mem;
+ if (isPubCons(cc))
+ result[i++] = cc;
+ }
+
+ return result;
+ }
+
+ private static boolean isPubCons(CtConstructor cons) {
+ return !Modifier.isPrivate(cons.getModifiers())
+ && cons.isConstructor();
+ }
+
+ public CtConstructor getConstructor(String desc)
+ throws NotFoundException
+ {
+ CtMember.Cache memCache = getMembers();
+ CtMember cons = memCache.consHead();
+ CtMember consTail = memCache.lastCons();
+
+ while (cons != consTail) {
+ cons = cons.next();
+ CtConstructor cc = (CtConstructor)cons;
+ if (cc.getMethodInfo2().getDescriptor().equals(desc)
+ && cc.isConstructor())
+ return cc;
+ }
+
+ return super.getConstructor(desc);
+ }
+
+ public CtConstructor[] getDeclaredConstructors() {
+ CtMember.Cache memCache = getMembers();
+ CtMember cons = memCache.consHead();
+ CtMember consTail = memCache.lastCons();
+
+ int n = 0;
+ CtMember mem = cons;
+ while (mem != consTail) {
+ mem = mem.next();
+ CtConstructor cc = (CtConstructor)mem;
+ if (cc.isConstructor())
+ n++;
+ }
+
+ CtConstructor[] result = new CtConstructor[n];
+ int i = 0;
+ mem = cons;
+ while (mem != consTail) {
+ mem = mem.next();
+ CtConstructor cc = (CtConstructor)mem;
+ if (cc.isConstructor())
+ result[i++] = cc;
+ }
+
+ return result;
+ }
+
+ public CtConstructor getClassInitializer() {
+ CtMember.Cache memCache = getMembers();
+ CtMember cons = memCache.consHead();
+ CtMember consTail = memCache.lastCons();
+
+ while (cons != consTail) {
+ cons = cons.next();
+ CtConstructor cc = (CtConstructor)cons;
+ if (cc.isClassInitializer())
+ return cc;
+ }
+
+ return null;
+ }
+
+ public CtMethod[] getMethods() {
+ HashMap h = new HashMap();
+ getMethods0(h, this);
+ return (CtMethod[])h.values().toArray(new CtMethod[h.size()]);
+ }
+
+ private static void getMethods0(HashMap h, CtClass cc) {
+ try {
+ CtClass[] ifs = cc.getInterfaces();
+ int size = ifs.length;
+ for (int i = 0; i < size; ++i)
+ getMethods0(h, ifs[i]);
+ }
+ catch (NotFoundException e) {}
+
+ try {
+ CtClass s = cc.getSuperclass();
+ if (s != null)
+ getMethods0(h, s);
+ }
+ catch (NotFoundException e) {}
+
+ if (cc instanceof CtClassType) {
+ CtMember.Cache memCache = ((CtClassType)cc).getMembers();
+ CtMember mth = memCache.methodHead();
+ CtMember mthTail = memCache.lastMethod();
+
+ while (mth != mthTail) {
+ mth = mth.next();
+ if (!Modifier.isPrivate(mth.getModifiers()))
+ h.put(((CtMethod)mth).getStringRep(), mth);
+ }
+ }
+ }
+
+ public CtMethod getMethod(String name, String desc)
+ throws NotFoundException
+ {
+ CtMethod m = getMethod0(this, name, desc);
+ if (m != null)
+ return m;
+ else
+ throw new NotFoundException(name + "(..) is not found in "
+ + getName());
+ }
+
+ private static CtMethod getMethod0(CtClass cc,
+ String name, String desc) {
+ if (cc instanceof CtClassType) {
+ CtMember.Cache memCache = ((CtClassType)cc).getMembers();
+ CtMember mth = memCache.methodHead();
+ CtMember mthTail = memCache.lastMethod();
+
+ while (mth != mthTail) {
+ mth = mth.next();
+ if (mth.getName().equals(name)
+ && ((CtMethod)mth).getMethodInfo2().getDescriptor().equals(desc))
+ return (CtMethod)mth;
+ }
+ }
+
+ try {
+ CtClass s = cc.getSuperclass();
+ if (s != null) {
+ CtMethod m = getMethod0(s, name, desc);
+ if (m != null)
+ return m;
+ }
+ }
+ catch (NotFoundException e) {}
+
+ try {
+ CtClass[] ifs = cc.getInterfaces();
+ int size = ifs.length;
+ for (int i = 0; i < size; ++i) {
+ CtMethod m = getMethod0(ifs[i], name, desc);
+ if (m != null)
+ return m;
+ }
+ }
+ catch (NotFoundException e) {}
+ return null;
+ }
+
+ public CtMethod[] getDeclaredMethods() {
+ CtMember.Cache memCache = getMembers();
+ CtMember mth = memCache.methodHead();
+ CtMember mthTail = memCache.lastMethod();
+ int num = CtMember.Cache.count(mth, mthTail);
+ CtMethod[] cms = new CtMethod[num];
+ int i = 0;
+ while (mth != mthTail) {
+ mth = mth.next();
+ cms[i++] = (CtMethod)mth;
+ }
+
+ return cms;
+ }
+
+ public CtMethod getDeclaredMethod(String name) throws NotFoundException {
+ CtMember.Cache memCache = getMembers();
+ CtMember mth = memCache.methodHead();
+ CtMember mthTail = memCache.lastMethod();
+ while (mth != mthTail) {
+ mth = mth.next();
+ if (mth.getName().equals(name))
+ return (CtMethod)mth;
+ }
+
+ throw new NotFoundException(name + "(..) is not found in "
+ + getName());
+ }
+
+ public CtMethod getDeclaredMethod(String name, CtClass[] params)
+ throws NotFoundException
+ {
+ String desc = Descriptor.ofParameters(params);
+ CtMember.Cache memCache = getMembers();
+ CtMember mth = memCache.methodHead();
+ CtMember mthTail = memCache.lastMethod();
+
+ while (mth != mthTail) {
+ mth = mth.next();
+ if (mth.getName().equals(name)
+ && ((CtMethod)mth).getMethodInfo2().getDescriptor().startsWith(desc))
+ return (CtMethod)mth;
+ }
+
+ throw new NotFoundException(name + "(..) is not found in "
+ + getName());
+ }
+
+ public void addField(CtField f, String init)
+ throws CannotCompileException
+ {
+ addField(f, CtField.Initializer.byExpr(init));
+ }
+
+ public void addField(CtField f, CtField.Initializer init)
+ throws CannotCompileException
+ {
+ checkModify();
+ if (f.getDeclaringClass() != this)
+ throw new CannotCompileException("cannot add");
+
+ if (init == null)
+ init = f.getInit();
+
+ if (init != null) {
+ init.check(f.getSignature());
+ int mod = f.getModifiers();
+ if (Modifier.isStatic(mod) && Modifier.isFinal(mod))
+ try {
+ ConstPool cp = getClassFile2().getConstPool();
+ int index = init.getConstantValue(cp, f.getType());
+ if (index != 0) {
+ f.getFieldInfo2().addAttribute(new ConstantAttribute(cp, index));
+ init = null;
+ }
+ }
+ catch (NotFoundException e) {}
+ }
+
+ getMembers().addField(f);
+ getClassFile2().addField(f.getFieldInfo2());
+
+ if (init != null) {
+ FieldInitLink fil = new FieldInitLink(f, init);
+ FieldInitLink link = fieldInitializers;
+ if (link == null)
+ fieldInitializers = fil;
+ else {
+ while (link.next != null)
+ link = link.next;
+
+ link.next = fil;
+ }
+ }
+ }
+
+ public void removeField(CtField f) throws NotFoundException {
+ checkModify();
+ FieldInfo fi = f.getFieldInfo2();
+ ClassFile cf = getClassFile2();
+ if (cf.getFields().remove(fi)) {
+ getMembers().remove(f);
+ gcConstPool = true;
+ }
+ else
+ throw new NotFoundException(f.toString());
+ }
+
+ public CtConstructor makeClassInitializer()
+ throws CannotCompileException
+ {
+ CtConstructor clinit = getClassInitializer();
+ if (clinit != null)
+ return clinit;
+
+ checkModify();
+ ClassFile cf = getClassFile2();
+ Bytecode code = new Bytecode(cf.getConstPool(), 0, 0);
+ modifyClassConstructor(cf, code, 0, 0);
+ return getClassInitializer();
+ }
+
+ public void addConstructor(CtConstructor c)
+ throws CannotCompileException
+ {
+ checkModify();
+ if (c.getDeclaringClass() != this)
+ throw new CannotCompileException("cannot add");
+
+ getMembers().addConstructor(c);
+ getClassFile2().addMethod(c.getMethodInfo2());
+ }
+
+ public void removeConstructor(CtConstructor m) throws NotFoundException {
+ checkModify();
+ MethodInfo mi = m.getMethodInfo2();
+ ClassFile cf = getClassFile2();
+ if (cf.getMethods().remove(mi)) {
+ getMembers().remove(m);
+ gcConstPool = true;
+ }
+ else
+ throw new NotFoundException(m.toString());
+ }
+
+ public void addMethod(CtMethod m) throws CannotCompileException {
+ checkModify();
+ if (m.getDeclaringClass() != this)
+ throw new CannotCompileException("bad declaring class");
+
+ int mod = m.getModifiers();
+ if ((getModifiers() & Modifier.INTERFACE) != 0) {
+ m.setModifiers(mod | Modifier.PUBLIC);
+ if ((mod & Modifier.ABSTRACT) == 0)
+ throw new CannotCompileException(
+ "an interface method must be abstract: " + m.toString());
+ }
+
+ getMembers().addMethod(m);
+ getClassFile2().addMethod(m.getMethodInfo2());
+ if ((mod & Modifier.ABSTRACT) != 0)
+ setModifiers(getModifiers() | Modifier.ABSTRACT);
+ }
+
+ public void removeMethod(CtMethod m) throws NotFoundException {
+ checkModify();
+ MethodInfo mi = m.getMethodInfo2();
+ ClassFile cf = getClassFile2();
+ if (cf.getMethods().remove(mi)) {
+ getMembers().remove(m);
+ gcConstPool = true;
+ }
+ else
+ throw new NotFoundException(m.toString());
+ }
+
+ public byte[] getAttribute(String name) {
+ AttributeInfo ai = getClassFile2().getAttribute(name);
+ if (ai == null)
+ return null;
+ else
+ return ai.get();
+ }
+
+ public void setAttribute(String name, byte[] data) {
+ checkModify();
+ ClassFile cf = getClassFile2();
+ cf.addAttribute(new AttributeInfo(cf.getConstPool(), name, data));
+ }
+
+ public void instrument(CodeConverter converter)
+ throws CannotCompileException
+ {
+ checkModify();
+ ClassFile cf = getClassFile2();
+ ConstPool cp = cf.getConstPool();
+ List list = cf.getMethods();
+ int n = list.size();
+ for (int i = 0; i < n; ++i) {
+ MethodInfo minfo = (MethodInfo)list.get(i);
+ converter.doit(this, minfo, cp);
+ }
+ }
+
+ public void instrument(ExprEditor editor)
+ throws CannotCompileException
+ {
+ checkModify();
+ ClassFile cf = getClassFile2();
+ List list = cf.getMethods();
+ int n = list.size();
+ for (int i = 0; i < n; ++i) {
+ MethodInfo minfo = (MethodInfo)list.get(i);
+ editor.doit(this, minfo);
+ }
+ }
+
+ /**
+ * @see javassist.CtClass#prune()
+ * @see javassist.CtClass#stopPruning(boolean)
+ */
+ public void prune() {
+ if (wasPruned)
+ return;
+
+ wasPruned = wasFrozen = true;
+ getClassFile2().prune();
+ }
+
+ public void rebuildClassFile() { gcConstPool = true; }
+
+ public void toBytecode(DataOutputStream out)
+ throws CannotCompileException, IOException
+ {
+ try {
+ if (isModified()) {
+ checkPruned("toBytecode");
+ ClassFile cf = getClassFile2();
+ if (gcConstPool) {
+ cf.compact();
+ gcConstPool = false;
+ }
+
+ modifyClassConstructor(cf);
+ modifyConstructors(cf);
+ cf.write(out);
+ out.flush();
+ fieldInitializers = null;
+ if (doPruning) {
+ // to save memory
+ cf.prune();
+ wasPruned = true;
+ }
+ }
+ else {
+ classPool.writeClassfile(getName(), out);
+ // to save memory
+ // classfile = null;
+ }
+
+ getCount = 0;
+ wasFrozen = true;
+ }
+ catch (NotFoundException e) {
+ throw new CannotCompileException(e);
+ }
+ catch (IOException e) {
+ throw new CannotCompileException(e);
+ }
+ }
+
+ /* See also checkModified()
+ */
+ private void checkPruned(String method) {
+ if (wasPruned)
+ throw new RuntimeException(method + "(): " + getName()
+ + " was pruned.");
+ }
+
+ public boolean stopPruning(boolean stop) {
+ boolean prev = !doPruning;
+ doPruning = !stop;
+ return prev;
+ }
+
+ private void modifyClassConstructor(ClassFile cf)
+ throws CannotCompileException, NotFoundException
+ {
+ if (fieldInitializers == null)
+ return;
+
+ Bytecode code = new Bytecode(cf.getConstPool(), 0, 0);
+ Javac jv = new Javac(code, this);
+ int stacksize = 0;
+ boolean doInit = false;
+ for (FieldInitLink fi = fieldInitializers; fi != null; fi = fi.next) {
+ CtField f = fi.field;
+ if (Modifier.isStatic(f.getModifiers())) {
+ doInit = true;
+ int s = fi.init.compileIfStatic(f.getType(), f.getName(),
+ code, jv);
+ if (stacksize < s)
+ stacksize = s;
+ }
+ }
+
+ if (doInit) // need an initializer for static fileds.
+ modifyClassConstructor(cf, code, stacksize, 0);
+ }
+
+ private void modifyClassConstructor(ClassFile cf, Bytecode code,
+ int stacksize, int localsize)
+ throws CannotCompileException
+ {
+ MethodInfo m = cf.getStaticInitializer();
+ if (m == null) {
+ code.add(Bytecode.RETURN);
+ code.setMaxStack(stacksize);
+ code.setMaxLocals(localsize);
+ m = new MethodInfo(cf.getConstPool(), "<clinit>", "()V");
+ m.setAccessFlags(AccessFlag.STATIC);
+ m.setCodeAttribute(code.toCodeAttribute());
+ cf.addMethod(m);
+ CtMember.Cache cache = hasMemberCache();
+ if (cache != null)
+ cache.addConstructor(new CtConstructor(m, this));
+ }
+ else {
+ CodeAttribute codeAttr = m.getCodeAttribute();
+ if (codeAttr == null)
+ throw new CannotCompileException("empty <clinit>");
+
+ try {
+ CodeIterator it = codeAttr.iterator();
+ int pos = it.insertEx(code.get());
+ it.insert(code.getExceptionTable(), pos);
+ int maxstack = codeAttr.getMaxStack();
+ if (maxstack < stacksize)
+ codeAttr.setMaxStack(stacksize);
+
+ int maxlocals = codeAttr.getMaxLocals();
+ if (maxlocals < localsize)
+ codeAttr.setMaxLocals(localsize);
+ }
+ catch (BadBytecode e) {
+ throw new CannotCompileException(e);
+ }
+ }
+
+ try {
+ m.rebuildStackMapIf6(classPool, cf);
+ }
+ catch (BadBytecode e) {
+ throw new CannotCompileException(e);
+ }
+ }
+
+ private void modifyConstructors(ClassFile cf)
+ throws CannotCompileException, NotFoundException
+ {
+ if (fieldInitializers == null)
+ return;
+
+ ConstPool cp = cf.getConstPool();
+ List list = cf.getMethods();
+ int n = list.size();
+ for (int i = 0; i < n; ++i) {
+ MethodInfo minfo = (MethodInfo)list.get(i);
+ if (minfo.isConstructor()) {
+ CodeAttribute codeAttr = minfo.getCodeAttribute();
+ if (codeAttr != null)
+ try {
+ Bytecode init = new Bytecode(cp, 0,
+ codeAttr.getMaxLocals());
+ CtClass[] params
+ = Descriptor.getParameterTypes(
+ minfo.getDescriptor(),
+ classPool);
+ int stacksize = makeFieldInitializer(init, params);
+ insertAuxInitializer(codeAttr, init, stacksize);
+ minfo.rebuildStackMapIf6(classPool, cf);
+ }
+ catch (BadBytecode e) {
+ throw new CannotCompileException(e);
+ }
+ }
+ }
+ }
+
+ private static void insertAuxInitializer(CodeAttribute codeAttr,
+ Bytecode initializer,
+ int stacksize)
+ throws BadBytecode
+ {
+ CodeIterator it = codeAttr.iterator();
+ int index = it.skipSuperConstructor();
+ if (index < 0) {
+ index = it.skipThisConstructor();
+ if (index >= 0)
+ return; // this() is called.
+
+ // Neither this() or super() is called.
+ }
+
+ int pos = it.insertEx(initializer.get());
+ it.insert(initializer.getExceptionTable(), pos);
+ int maxstack = codeAttr.getMaxStack();
+ if (maxstack < stacksize)
+ codeAttr.setMaxStack(stacksize);
+ }
+
+ private int makeFieldInitializer(Bytecode code, CtClass[] parameters)
+ throws CannotCompileException, NotFoundException
+ {
+ int stacksize = 0;
+ Javac jv = new Javac(code, this);
+ try {
+ jv.recordParams(parameters, false);
+ }
+ catch (CompileError e) {
+ throw new CannotCompileException(e);
+ }
+
+ for (FieldInitLink fi = fieldInitializers; fi != null; fi = fi.next) {
+ CtField f = fi.field;
+ if (!Modifier.isStatic(f.getModifiers())) {
+ int s = fi.init.compile(f.getType(), f.getName(), code,
+ parameters, jv);
+ if (stacksize < s)
+ stacksize = s;
+ }
+ }
+
+ return stacksize;
+ }
+
+ // Methods used by CtNewWrappedMethod
+
+ Hashtable getHiddenMethods() {
+ if (hiddenMethods == null)
+ hiddenMethods = new Hashtable();
+
+ return hiddenMethods;
+ }
+
+ int getUniqueNumber() { return uniqueNumberSeed++; }
+
+ public String makeUniqueName(String prefix) {
+ HashMap table = new HashMap();
+ makeMemberList(table);
+ Set keys = table.keySet();
+ String[] methods = new String[keys.size()];
+ keys.toArray(methods);
+
+ if (notFindInArray(prefix, methods))
+ return prefix;
+
+ int i = 100;
+ String name;
+ do {
+ if (i > 999)
+ throw new RuntimeException("too many unique name");
+
+ name = prefix + i++;
+ } while (!notFindInArray(name, methods));
+ return name;
+ }
+
+ private static boolean notFindInArray(String prefix, String[] values) {
+ int len = values.length;
+ for (int i = 0; i < len; i++)
+ if (values[i].startsWith(prefix))
+ return false;
+
+ return true;
+ }
+
+ private void makeMemberList(HashMap table) {
+ int mod = getModifiers();
+ if (Modifier.isAbstract(mod) || Modifier.isInterface(mod))
+ try {
+ CtClass[] ifs = getInterfaces();
+ int size = ifs.length;
+ for (int i = 0; i < size; i++) {
+ CtClass ic =ifs[i];
+ if (ic != null && ic instanceof CtClassType)
+ ((CtClassType)ic).makeMemberList(table);
+ }
+ }
+ catch (NotFoundException e) {}
+
+ try {
+ CtClass s = getSuperclass();
+ if (s != null && s instanceof CtClassType)
+ ((CtClassType)s).makeMemberList(table);
+ }
+ catch (NotFoundException e) {}
+
+ List list = getClassFile2().getMethods();
+ int n = list.size();
+ for (int i = 0; i < n; i++) {
+ MethodInfo minfo = (MethodInfo)list.get(i);
+ table.put(minfo.getName(), this);
+ }
+
+ list = getClassFile2().getFields();
+ n = list.size();
+ for (int i = 0; i < n; i++) {
+ FieldInfo finfo = (FieldInfo)list.get(i);
+ table.put(finfo.getName(), this);
+ }
+ }
+}
+
+class FieldInitLink {
+ FieldInitLink next;
+ CtField field;
+ CtField.Initializer init;
+
+ FieldInitLink(CtField f, CtField.Initializer i) {
+ next = null;
+ field = f;
+ init = i;
+ }
+}
diff --git a/src/main/javassist/CtConstructor.java b/src/main/javassist/CtConstructor.java
new file mode 100644
index 0000000..a90b73c
--- /dev/null
+++ b/src/main/javassist/CtConstructor.java
@@ -0,0 +1,402 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist;
+
+import javassist.bytecode.*;
+import javassist.compiler.Javac;
+import javassist.compiler.CompileError;
+
+/**
+ * An instance of CtConstructor represents a constructor.
+ * It may represent a static constructor
+ * (class initializer). To distinguish a constructor and a class
+ * initializer, call <code>isClassInitializer()</code>.
+ *
+ * <p>See the super class <code>CtBehavior</code> as well since
+ * a number of useful methods are in <code>CtBehavior</code>.
+ *
+ * @see CtClass#getDeclaredConstructors()
+ * @see CtClass#getClassInitializer()
+ * @see CtNewConstructor
+ */
+public final class CtConstructor extends CtBehavior {
+ protected CtConstructor(MethodInfo minfo, CtClass declaring) {
+ super(declaring, minfo);
+ }
+
+ /**
+ * Creates a constructor with no constructor body.
+ * The created constructor
+ * must be added to a class with <code>CtClass.addConstructor()</code>.
+ *
+ * <p>The created constructor does not include a constructor body,
+ * which must be specified with <code>setBody()</code>.
+ *
+ * @param declaring the class to which the created method is added.
+ * @param parameters a list of the parameter types
+ *
+ * @see CtClass#addConstructor(CtConstructor)
+ * @see CtConstructor#setBody(String)
+ * @see CtConstructor#setBody(CtConstructor,ClassMap)
+ */
+ public CtConstructor(CtClass[] parameters, CtClass declaring) {
+ this((MethodInfo)null, declaring);
+ ConstPool cp = declaring.getClassFile2().getConstPool();
+ String desc = Descriptor.ofConstructor(parameters);
+ methodInfo = new MethodInfo(cp, "<init>", desc);
+ setModifiers(Modifier.PUBLIC);
+ }
+
+ /**
+ * Creates a copy of a <code>CtConstructor</code> object.
+ * The created constructor must be
+ * added to a class with <code>CtClass.addConstructor()</code>.
+ *
+ * <p>All occurrences of class names in the created constructor
+ * are replaced with names specified by
+ * <code>map</code> if <code>map</code> is not <code>null</code>.
+ *
+ * <p>By default, all the occurrences of the names of the class
+ * declaring <code>src</code> and the superclass are replaced
+ * with the name of the class and the superclass that
+ * the created constructor is added to.
+ * This is done whichever <code>map</code> is null or not.
+ * To prevent this replacement, call <code>ClassMap.fix()</code>
+ * or <code>put()</code> to explicitly specify replacement.
+ *
+ * <p><b>Note:</b> if the <code>.class</code> notation (for example,
+ * <code>String.class</code>) is included in an expression, the
+ * Javac compiler may produce a helper method.
+ * Since this constructor never
+ * copies this helper method, the programmers have the responsiblity of
+ * copying it. Otherwise, use <code>Class.forName()</code> in the
+ * expression.
+ *
+ * @param src the source method.
+ * @param declaring the class to which the created method is added.
+ * @param map the hashtable associating original class names
+ * with substituted names.
+ * It can be <code>null</code>.
+ *
+ * @see CtClass#addConstructor(CtConstructor)
+ * @see ClassMap#fix(String)
+ */
+ public CtConstructor(CtConstructor src, CtClass declaring, ClassMap map)
+ throws CannotCompileException
+ {
+ this((MethodInfo)null, declaring);
+ copy(src, true, map);
+ }
+
+ /**
+ * Returns true if this object represents a constructor.
+ */
+ public boolean isConstructor() {
+ return methodInfo.isConstructor();
+ }
+
+ /**
+ * Returns true if this object represents a static initializer.
+ */
+ public boolean isClassInitializer() {
+ return methodInfo.isStaticInitializer();
+ }
+
+ /**
+ * Returns the constructor name followed by parameter types
+ * such as <code>javassist.CtConstructor(CtClass[],CtClass)</code>.
+ *
+ * @since 3.5
+ */
+ public String getLongName() {
+ return getDeclaringClass().getName()
+ + (isConstructor() ? Descriptor.toString(getSignature())
+ : ("." + MethodInfo.nameClinit + "()"));
+ }
+
+ /**
+ * Obtains the name of this constructor.
+ * It is the same as the simple name of the class declaring this
+ * constructor. If this object represents a class initializer,
+ * then this method returns <code>"<clinit>"</code>.
+ */
+ public String getName() {
+ if (methodInfo.isStaticInitializer())
+ return MethodInfo.nameClinit;
+ else
+ return declaringClass.getSimpleName();
+ }
+
+ /**
+ * Returns true if the constructor (or static initializer)
+ * is the default one. This method returns true if the constructor
+ * takes some arguments but it does not perform anything except
+ * calling <code>super()</code> (the no-argument constructor of
+ * the super class).
+ */
+ public boolean isEmpty() {
+ CodeAttribute ca = getMethodInfo2().getCodeAttribute();
+ if (ca == null)
+ return false; // native or abstract??
+ // they are not allowed, though.
+
+ ConstPool cp = ca.getConstPool();
+ CodeIterator it = ca.iterator();
+ try {
+ int pos, desc;
+ int op0 = it.byteAt(it.next());
+ return op0 == Opcode.RETURN // empty static initializer
+ || (op0 == Opcode.ALOAD_0
+ && it.byteAt(pos = it.next()) == Opcode.INVOKESPECIAL
+ && (desc = cp.isConstructor(getSuperclassName(),
+ it.u16bitAt(pos + 1))) != 0
+ && "()V".equals(cp.getUtf8Info(desc))
+ && it.byteAt(it.next()) == Opcode.RETURN
+ && !it.hasNext());
+ }
+ catch (BadBytecode e) {}
+ return false;
+ }
+
+ private String getSuperclassName() {
+ ClassFile cf = declaringClass.getClassFile2();
+ return cf.getSuperclass();
+ }
+
+ /**
+ * Returns true if this constructor calls a constructor
+ * of the super class. This method returns false if it
+ * calls another constructor of this class by <code>this()</code>.
+ */
+ public boolean callsSuper() throws CannotCompileException {
+ CodeAttribute codeAttr = methodInfo.getCodeAttribute();
+ if (codeAttr != null) {
+ CodeIterator it = codeAttr.iterator();
+ try {
+ int index = it.skipSuperConstructor();
+ return index >= 0;
+ }
+ catch (BadBytecode e) {
+ throw new CannotCompileException(e);
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Sets a constructor body.
+ *
+ * @param src the source code representing the constructor body.
+ * It must be a single statement or block.
+ * If it is <code>null</code>, the substituted
+ * constructor body does nothing except calling
+ * <code>super()</code>.
+ */
+ public void setBody(String src) throws CannotCompileException {
+ if (src == null)
+ if (isClassInitializer())
+ src = ";";
+ else
+ src = "super();";
+
+ super.setBody(src);
+ }
+
+ /**
+ * Copies a constructor body from another constructor.
+ *
+ * <p>All occurrences of the class names in the copied body
+ * are replaced with the names specified by
+ * <code>map</code> if <code>map</code> is not <code>null</code>.
+ *
+ * @param src the method that the body is copied from.
+ * @param map the hashtable associating original class names
+ * with substituted names.
+ * It can be <code>null</code>.
+ */
+ public void setBody(CtConstructor src, ClassMap map)
+ throws CannotCompileException
+ {
+ setBody0(src.declaringClass, src.methodInfo,
+ declaringClass, methodInfo, map);
+ }
+
+ /**
+ * Inserts bytecode just after another constructor in the super class
+ * or this class is called.
+ * It does not work if this object represents a class initializer.
+ *
+ * @param src the source code representing the inserted bytecode.
+ * It must be a single statement or block.
+ */
+ public void insertBeforeBody(String src) throws CannotCompileException {
+ CtClass cc = declaringClass;
+ cc.checkModify();
+ if (isClassInitializer())
+ throw new CannotCompileException("class initializer");
+
+ CodeAttribute ca = methodInfo.getCodeAttribute();
+ CodeIterator iterator = ca.iterator();
+ Bytecode b = new Bytecode(methodInfo.getConstPool(),
+ ca.getMaxStack(), ca.getMaxLocals());
+ b.setStackDepth(ca.getMaxStack());
+ Javac jv = new Javac(b, cc);
+ try {
+ jv.recordParams(getParameterTypes(), false);
+ jv.compileStmnt(src);
+ ca.setMaxStack(b.getMaxStack());
+ ca.setMaxLocals(b.getMaxLocals());
+ iterator.skipConstructor();
+ int pos = iterator.insertEx(b.get());
+ iterator.insert(b.getExceptionTable(), pos);
+ methodInfo.rebuildStackMapIf6(cc.getClassPool(), cc.getClassFile2());
+ }
+ catch (NotFoundException e) {
+ throw new CannotCompileException(e);
+ }
+ catch (CompileError e) {
+ throw new CannotCompileException(e);
+ }
+ catch (BadBytecode e) {
+ throw new CannotCompileException(e);
+ }
+ }
+
+ /* This method is called by addCatch() in CtBehavior.
+ * super() and this() must not be in a try statement.
+ */
+ int getStartPosOfBody(CodeAttribute ca) throws CannotCompileException {
+ CodeIterator ci = ca.iterator();
+ try {
+ ci.skipConstructor();
+ return ci.next();
+ }
+ catch (BadBytecode e) {
+ throw new CannotCompileException(e);
+ }
+ }
+
+ /**
+ * Makes a copy of this constructor and converts it into a method.
+ * The signature of the mehtod is the same as the that of this constructor.
+ * The return type is <code>void</code>. The resulting method must be
+ * appended to the class specified by <code>declaring</code>.
+ * If this constructor is a static initializer, the resulting method takes
+ * no parameter.
+ *
+ * <p>An occurrence of another constructor call <code>this()</code>
+ * or a super constructor call <code>super()</code> is
+ * eliminated from the resulting method.
+ *
+ * <p>The immediate super class of the class declaring this constructor
+ * must be also a super class of the class declaring the resulting method.
+ * If the constructor accesses a field, the class declaring the resulting method
+ * must also declare a field with the same name and type.
+ *
+ * @param name the name of the resulting method.
+ * @param declaring the class declaring the resulting method.
+ */
+ public CtMethod toMethod(String name, CtClass declaring)
+ throws CannotCompileException
+ {
+ return toMethod(name, declaring, null);
+ }
+
+ /**
+ * Makes a copy of this constructor and converts it into a method.
+ * The signature of the method is the same as the that of this constructor.
+ * The return type is <code>void</code>. The resulting method must be
+ * appended to the class specified by <code>declaring</code>.
+ * If this constructor is a static initializer, the resulting method takes
+ * no parameter.
+ *
+ * <p>An occurrence of another constructor call <code>this()</code>
+ * or a super constructor call <code>super()</code> is
+ * eliminated from the resulting method.
+ *
+ * <p>The immediate super class of the class declaring this constructor
+ * must be also a super class of the class declaring the resulting method
+ * (this is obviously true if the second parameter <code>declaring</code> is
+ * the same as the class declaring this constructor).
+ * If the constructor accesses a field, the class declaring the resulting method
+ * must also declare a field with the same name and type.
+ *
+ * @param name the name of the resulting method.
+ * @param declaring the class declaring the resulting method.
+ * It is normally the same as the class declaring this
+ * constructor.
+ * @param map the hash table associating original class names
+ * with substituted names. The original class names will be
+ * replaced while making a copy.
+ * <code>map</code> can be <code>null</code>.
+ */
+ public CtMethod toMethod(String name, CtClass declaring, ClassMap map)
+ throws CannotCompileException
+ {
+ CtMethod method = new CtMethod(null, declaring);
+ method.copy(this, false, map);
+ if (isConstructor()) {
+ MethodInfo minfo = method.getMethodInfo2();
+ CodeAttribute ca = minfo.getCodeAttribute();
+ if (ca != null) {
+ removeConsCall(ca);
+ try {
+ methodInfo.rebuildStackMapIf6(declaring.getClassPool(),
+ declaring.getClassFile2());
+ }
+ catch (BadBytecode e) {
+ throw new CannotCompileException(e);
+ }
+ }
+ }
+
+ method.setName(name);
+ return method;
+ }
+
+ private static void removeConsCall(CodeAttribute ca)
+ throws CannotCompileException
+ {
+ CodeIterator iterator = ca.iterator();
+ try {
+ int pos = iterator.skipConstructor();
+ if (pos >= 0) {
+ int mref = iterator.u16bitAt(pos + 1);
+ String desc = ca.getConstPool().getMethodrefType(mref);
+ int num = Descriptor.numOfParameters(desc) + 1;
+ if (num > 3)
+ pos = iterator.insertGapAt(pos, num - 3, false).position;
+
+ iterator.writeByte(Opcode.POP, pos++); // this
+ iterator.writeByte(Opcode.NOP, pos);
+ iterator.writeByte(Opcode.NOP, pos + 1);
+ Descriptor.Iterator it = new Descriptor.Iterator(desc);
+ while (true) {
+ it.next();
+ if (it.isParameter())
+ iterator.writeByte(it.is2byte() ? Opcode.POP2 : Opcode.POP,
+ pos++);
+ else
+ break;
+ }
+ }
+ }
+ catch (BadBytecode e) {
+ throw new CannotCompileException(e);
+ }
+ }
+}
diff --git a/src/main/javassist/CtField.java b/src/main/javassist/CtField.java
new file mode 100644
index 0000000..c4af7e5
--- /dev/null
+++ b/src/main/javassist/CtField.java
@@ -0,0 +1,1389 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist;
+
+import javassist.bytecode.*;
+import javassist.compiler.Javac;
+import javassist.compiler.SymbolTable;
+import javassist.compiler.CompileError;
+import javassist.compiler.ast.ASTree;
+import javassist.compiler.ast.IntConst;
+import javassist.compiler.ast.DoubleConst;
+import javassist.compiler.ast.StringL;
+
+/**
+ * An instance of CtField represents a field.
+ *
+ * @see CtClass#getDeclaredFields()
+ */
+public class CtField extends CtMember {
+ static final String javaLangString = "java.lang.String";
+
+ protected FieldInfo fieldInfo;
+
+ /**
+ * Creates a <code>CtField</code> object.
+ * The created field must be added to a class
+ * with <code>CtClass.addField()</code>.
+ * An initial value of the field is specified
+ * by a <code>CtField.Initializer</code> object.
+ *
+ * <p>If getter and setter methods are needed,
+ * call <code>CtNewMethod.getter()</code> and
+ * <code>CtNewMethod.setter()</code>.
+ *
+ * @param type field type
+ * @param name field name
+ * @param declaring the class to which the field will be added.
+ *
+ * @see CtClass#addField(CtField)
+ * @see CtNewMethod#getter(String,CtField)
+ * @see CtNewMethod#setter(String,CtField)
+ * @see CtField.Initializer
+ */
+ public CtField(CtClass type, String name, CtClass declaring)
+ throws CannotCompileException
+ {
+ this(Descriptor.of(type), name, declaring);
+ }
+
+ /**
+ * Creates a copy of the given field.
+ * The created field must be added to a class
+ * with <code>CtClass.addField()</code>.
+ * An initial value of the field is specified
+ * by a <code>CtField.Initializer</code> object.
+ *
+ * <p>If getter and setter methods are needed,
+ * call <code>CtNewMethod.getter()</code> and
+ * <code>CtNewMethod.setter()</code>.
+ *
+ * @param src the original field
+ * @param declaring the class to which the field will be added.
+ * @see CtNewMethod#getter(String,CtField)
+ * @see CtNewMethod#setter(String,CtField)
+ * @see CtField.Initializer
+ */
+ public CtField(CtField src, CtClass declaring)
+ throws CannotCompileException
+ {
+ this(src.fieldInfo.getDescriptor(), src.fieldInfo.getName(),
+ declaring);
+ java.util.ListIterator iterator
+ = src.fieldInfo.getAttributes().listIterator();
+ FieldInfo fi = fieldInfo;
+ fi.setAccessFlags(src.fieldInfo.getAccessFlags());
+ ConstPool cp = fi.getConstPool();
+ while (iterator.hasNext()) {
+ AttributeInfo ainfo = (AttributeInfo)iterator.next();
+ fi.addAttribute(ainfo.copy(cp, null));
+ }
+ }
+
+ private CtField(String typeDesc, String name, CtClass clazz)
+ throws CannotCompileException
+ {
+ super(clazz);
+ ClassFile cf = clazz.getClassFile2();
+ if (cf == null)
+ throw new CannotCompileException("bad declaring class: "
+ + clazz.getName());
+
+ fieldInfo = new FieldInfo(cf.getConstPool(), name, typeDesc);
+ }
+
+ CtField(FieldInfo fi, CtClass clazz) {
+ super(clazz);
+ fieldInfo = fi;
+ }
+
+ /**
+ * Returns a String representation of the object.
+ */
+ public String toString() {
+ return getDeclaringClass().getName() + "." + getName()
+ + ":" + fieldInfo.getDescriptor();
+ }
+
+ protected void extendToString(StringBuffer buffer) {
+ buffer.append(' ');
+ buffer.append(getName());
+ buffer.append(' ');
+ buffer.append(fieldInfo.getDescriptor());
+ }
+
+ /* Javac.CtFieldWithInit overrides.
+ */
+ protected ASTree getInitAST() { return null; }
+
+ /* Called by CtClassType.addField().
+ */
+ Initializer getInit() {
+ ASTree tree = getInitAST();
+ if (tree == null)
+ return null;
+ else
+ return Initializer.byExpr(tree);
+ }
+
+ /**
+ * Compiles the given source code and creates a field.
+ * Examples of the source code are:
+ *
+ * <ul><pre>
+ * "public String name;"
+ * "public int k = 3;"</pre></ul>
+ *
+ * <p>Note that the source code ends with <code>';'</code>
+ * (semicolon).
+ *
+ * @param src the source text.
+ * @param declaring the class to which the created field is added.
+ */
+ public static CtField make(String src, CtClass declaring)
+ throws CannotCompileException
+ {
+ Javac compiler = new Javac(declaring);
+ try {
+ CtMember obj = compiler.compile(src);
+ if (obj instanceof CtField)
+ return (CtField)obj; // an instance of Javac.CtFieldWithInit
+ }
+ catch (CompileError e) {
+ throw new CannotCompileException(e);
+ }
+
+ throw new CannotCompileException("not a field");
+ }
+
+ /**
+ * Returns the FieldInfo representing the field in the class file.
+ */
+ public FieldInfo getFieldInfo() {
+ declaringClass.checkModify();
+ return fieldInfo;
+ }
+
+ /**
+ * Returns the FieldInfo representing the field in the class
+ * file (read only).
+ * Normal applications do not need calling this method. Use
+ * <code>getFieldInfo()</code>.
+ *
+ * <p>The <code>FieldInfo</code> object obtained by this method
+ * is read only. Changes to this object might not be reflected
+ * on a class file generated by <code>toBytecode()</code>,
+ * <code>toClass()</code>, etc in <code>CtClass</code>.
+ *
+ * <p>This method is available even if the <code>CtClass</code>
+ * containing this field is frozen. However, if the class is
+ * frozen, the <code>FieldInfo</code> might be also pruned.
+ *
+ * @see #getFieldInfo()
+ * @see CtClass#isFrozen()
+ * @see CtClass#prune()
+ */
+ public FieldInfo getFieldInfo2() { return fieldInfo; }
+
+ /**
+ * Returns the class declaring the field.
+ */
+ public CtClass getDeclaringClass() {
+ // this is redundant but for javadoc.
+ return super.getDeclaringClass();
+ }
+
+ /**
+ * Returns the name of the field.
+ */
+ public String getName() {
+ return fieldInfo.getName();
+ }
+
+ /**
+ * Changes the name of the field.
+ */
+ public void setName(String newName) {
+ declaringClass.checkModify();
+ fieldInfo.setName(newName);
+ }
+
+ /**
+ * Returns the encoded modifiers of the field.
+ *
+ * @see Modifier
+ */
+ public int getModifiers() {
+ return AccessFlag.toModifier(fieldInfo.getAccessFlags());
+ }
+
+ /**
+ * Sets the encoded modifiers of the field.
+ *
+ * @see Modifier
+ */
+ public void setModifiers(int mod) {
+ declaringClass.checkModify();
+ fieldInfo.setAccessFlags(AccessFlag.of(mod));
+ }
+
+ /**
+ * Returns true if the class has the specified annotation class.
+ *
+ * @param clz the annotation class.
+ * @return <code>true</code> if the annotation is found, otherwise <code>false</code>.
+ * @since 3.11
+ */
+ public boolean hasAnnotation(Class clz) {
+ FieldInfo fi = getFieldInfo2();
+ AnnotationsAttribute ainfo = (AnnotationsAttribute)
+ fi.getAttribute(AnnotationsAttribute.invisibleTag);
+ AnnotationsAttribute ainfo2 = (AnnotationsAttribute)
+ fi.getAttribute(AnnotationsAttribute.visibleTag);
+ return CtClassType.hasAnnotationType(clz, getDeclaringClass().getClassPool(),
+ ainfo, ainfo2);
+ }
+
+ /**
+ * Returns the annotation if the class has the specified annotation class.
+ * For example, if an annotation <code>@Author</code> is associated
+ * with this field, an <code>Author</code> object is returned.
+ * The member values can be obtained by calling methods on
+ * the <code>Author</code> object.
+ *
+ * @param clz the annotation class.
+ * @return the annotation if found, otherwise <code>null</code>.
+ * @since 3.11
+ */
+ public Object getAnnotation(Class clz) throws ClassNotFoundException {
+ FieldInfo fi = getFieldInfo2();
+ AnnotationsAttribute ainfo = (AnnotationsAttribute)
+ fi.getAttribute(AnnotationsAttribute.invisibleTag);
+ AnnotationsAttribute ainfo2 = (AnnotationsAttribute)
+ fi.getAttribute(AnnotationsAttribute.visibleTag);
+ return CtClassType.getAnnotationType(clz, getDeclaringClass().getClassPool(),
+ ainfo, ainfo2);
+ }
+
+ /**
+ * Returns the annotations associated with this field.
+ *
+ * @return an array of annotation-type objects.
+ * @see #getAvailableAnnotations()
+ * @since 3.1
+ */
+ public Object[] getAnnotations() throws ClassNotFoundException {
+ return getAnnotations(false);
+ }
+
+ /**
+ * Returns the annotations associated with this field.
+ * If any annotations are not on the classpath, they are not included
+ * in the returned array.
+ *
+ * @return an array of annotation-type objects.
+ * @see #getAnnotations()
+ * @since 3.3
+ */
+ public Object[] getAvailableAnnotations(){
+ try {
+ return getAnnotations(true);
+ }
+ catch (ClassNotFoundException e) {
+ throw new RuntimeException("Unexpected exception", e);
+ }
+ }
+
+ private Object[] getAnnotations(boolean ignoreNotFound) throws ClassNotFoundException {
+ FieldInfo fi = getFieldInfo2();
+ AnnotationsAttribute ainfo = (AnnotationsAttribute)
+ fi.getAttribute(AnnotationsAttribute.invisibleTag);
+ AnnotationsAttribute ainfo2 = (AnnotationsAttribute)
+ fi.getAttribute(AnnotationsAttribute.visibleTag);
+ return CtClassType.toAnnotationType(ignoreNotFound, getDeclaringClass().getClassPool(),
+ ainfo, ainfo2);
+ }
+
+ /**
+ * Returns the character string representing the type of the field.
+ * The field signature is represented by a character string
+ * called a field descriptor, which is defined in the JVM specification.
+ * If two fields have the same type,
+ * <code>getSignature()</code> returns the same string.
+ *
+ * <p>Note that the returned string is not the type signature
+ * contained in the <code>SignatureAttirbute</code>. It is
+ * a descriptor. To obtain a type signature, call the following
+ * methods:
+ *
+ * <ul><pre>getFieldInfo().getAttribute(SignatureAttribute.tag)
+ * </pre></ul>
+ *
+ * @see javassist.bytecode.Descriptor
+ * @see javassist.bytecode.SignatureAttribute
+ */
+ public String getSignature() {
+ return fieldInfo.getDescriptor();
+ }
+
+ /**
+ * Returns the type of the field.
+ */
+ public CtClass getType() throws NotFoundException {
+ return Descriptor.toCtClass(fieldInfo.getDescriptor(),
+ declaringClass.getClassPool());
+ }
+
+ /**
+ * Sets the type of the field.
+ */
+ public void setType(CtClass clazz) {
+ declaringClass.checkModify();
+ fieldInfo.setDescriptor(Descriptor.of(clazz));
+ }
+
+ /**
+ * Returns the value of this field if it is a constant field.
+ * This method works only if the field type is a primitive type
+ * or <code>String</code> type. Otherwise, it returns <code>null</code>.
+ * A constant field is <code>static</code> and <code>final</code>.
+ *
+ * @return a <code>Integer</code>, <code>Long</code>, <code>Float</code>,
+ * <code>Double</code>, <code>Boolean</code>,
+ * or <code>String</code> object
+ * representing the constant value.
+ * <code>null</code> if it is not a constant field
+ * or if the field type is not a primitive type
+ * or <code>String</code>.
+ */
+ public Object getConstantValue() {
+ // When this method is modified,
+ // see also getConstantFieldValue() in TypeChecker.
+
+ int index = fieldInfo.getConstantValue();
+ if (index == 0)
+ return null;
+
+ ConstPool cp = fieldInfo.getConstPool();
+ switch (cp.getTag(index)) {
+ case ConstPool.CONST_Long :
+ return new Long(cp.getLongInfo(index));
+ case ConstPool.CONST_Float :
+ return new Float(cp.getFloatInfo(index));
+ case ConstPool.CONST_Double :
+ return new Double(cp.getDoubleInfo(index));
+ case ConstPool.CONST_Integer :
+ int value = cp.getIntegerInfo(index);
+ // "Z" means boolean type.
+ if ("Z".equals(fieldInfo.getDescriptor()))
+ return new Boolean(value != 0);
+ else
+ return new Integer(value);
+ case ConstPool.CONST_String :
+ return cp.getStringInfo(index);
+ default :
+ throw new RuntimeException("bad tag: " + cp.getTag(index)
+ + " at " + index);
+ }
+ }
+
+ /**
+ * Obtains an attribute with the given name.
+ * If that attribute is not found in the class file, this
+ * method returns null.
+ *
+ * <p>Note that an attribute is a data block specified by
+ * the class file format.
+ * See {@link javassist.bytecode.AttributeInfo}.
+ *
+ * @param name attribute name
+ */
+ public byte[] getAttribute(String name) {
+ AttributeInfo ai = fieldInfo.getAttribute(name);
+ if (ai == null)
+ return null;
+ else
+ return ai.get();
+ }
+
+ /**
+ * Adds an attribute. The attribute is saved in the class file.
+ *
+ * <p>Note that an attribute is a data block specified by
+ * the class file format.
+ * See {@link javassist.bytecode.AttributeInfo}.
+ *
+ * @param name attribute name
+ * @param data attribute value
+ */
+ public void setAttribute(String name, byte[] data) {
+ declaringClass.checkModify();
+ fieldInfo.addAttribute(new AttributeInfo(fieldInfo.getConstPool(),
+ name, data));
+ }
+
+ // inner classes
+
+ /**
+ * Instances of this class specify how to initialize a field.
+ * <code>Initializer</code> is passed to
+ * <code>CtClass.addField()</code> with a <code>CtField</code>.
+ *
+ * <p>This class cannot be instantiated with the <code>new</code> operator.
+ * Factory methods such as <code>byParameter()</code> and
+ * <code>byNew</code>
+ * must be used for the instantiation. They create a new instance with
+ * the given parameters and return it.
+ *
+ * @see CtClass#addField(CtField,CtField.Initializer)
+ */
+ public static abstract class Initializer {
+ /**
+ * Makes an initializer that assigns a constant integer value.
+ * The field must be integer, short, char, or byte type.
+ */
+ public static Initializer constant(int i) {
+ return new IntInitializer(i);
+ }
+
+ /**
+ * Makes an initializer that assigns a constant boolean value.
+ * The field must be boolean type.
+ */
+ public static Initializer constant(boolean b) {
+ return new IntInitializer(b ? 1 : 0);
+ }
+
+ /**
+ * Makes an initializer that assigns a constant long value.
+ * The field must be long type.
+ */
+ public static Initializer constant(long l) {
+ return new LongInitializer(l);
+ }
+
+ /**
+ * Makes an initializer that assigns a constant float value.
+ * The field must be float type.
+ */
+ public static Initializer constant(float l) {
+ return new FloatInitializer(l);
+ }
+
+ /**
+ * Makes an initializer that assigns a constant double value.
+ * The field must be double type.
+ */
+ public static Initializer constant(double d) {
+ return new DoubleInitializer(d);
+ }
+
+ /**
+ * Makes an initializer that assigns a constant string value.
+ * The field must be <code>java.lang.String</code> type.
+ */
+ public static Initializer constant(String s) {
+ return new StringInitializer(s);
+ }
+
+ /**
+ * Makes an initializer using a constructor parameter.
+ *
+ * <p>The initial value is the
+ * N-th parameter given to the constructor of the object including
+ * the field. If the constructor takes less than N parameters,
+ * the field is not initialized.
+ * If the field is static, it is never initialized.
+ *
+ * @param nth the n-th (>= 0) parameter is used as
+ * the initial value.
+ * If nth is 0, then the first parameter is
+ * used.
+ */
+ public static Initializer byParameter(int nth) {
+ ParamInitializer i = new ParamInitializer();
+ i.nthParam = nth;
+ return i;
+ }
+
+ /**
+ * Makes an initializer creating a new object.
+ *
+ * <p>This initializer creates a new object and uses it as the initial
+ * value of the field. The constructor of the created object receives
+ * the parameter:
+ *
+ * <ul><code>Object obj</code> - the object including the field.<br>
+ * </ul>
+ *
+ * <p>If the initialized field is static, then the constructor does
+ * not receive any parameters.
+ *
+ * @param objectType the class instantiated for the initial value.
+ */
+ public static Initializer byNew(CtClass objectType) {
+ NewInitializer i = new NewInitializer();
+ i.objectType = objectType;
+ i.stringParams = null;
+ i.withConstructorParams = false;
+ return i;
+ }
+
+ /**
+ * Makes an initializer creating a new object.
+ *
+ * <p>This initializer creates a new object and uses it as the initial
+ * value of the field. The constructor of the created object receives
+ * the parameters:
+ *
+ * <ul><code>Object obj</code> - the object including the field.<br>
+ * <code>String[] strs</code> - the character strings specified
+ * by <code>stringParams</code><br>
+ * </ul>
+ *
+ * <p>If the initialized field is static, then the constructor
+ * receives only <code>strs</code>.
+ *
+ * @param objectType the class instantiated for the initial value.
+ * @param stringParams the array of strings passed to the
+ * constructor.
+ */
+ public static Initializer byNew(CtClass objectType,
+ String[] stringParams) {
+ NewInitializer i = new NewInitializer();
+ i.objectType = objectType;
+ i.stringParams = stringParams;
+ i.withConstructorParams = false;
+ return i;
+ }
+
+ /**
+ * Makes an initializer creating a new object.
+ *
+ * <p>This initializer creates a new object and uses it as the initial
+ * value of the field. The constructor of the created object receives
+ * the parameters:
+ *
+ * <ul><code>Object obj</code> - the object including the field.<br>
+ * <code>Object[] args</code> - the parameters passed to the
+ * constructor of the object including the
+ * filed.
+ * </ul>
+ *
+ * <p>If the initialized field is static, then the constructor does
+ * not receive any parameters.
+ *
+ * @param objectType the class instantiated for the initial value.
+ *
+ * @see javassist.CtField.Initializer#byNewArray(CtClass,int)
+ * @see javassist.CtField.Initializer#byNewArray(CtClass,int[])
+ */
+ public static Initializer byNewWithParams(CtClass objectType) {
+ NewInitializer i = new NewInitializer();
+ i.objectType = objectType;
+ i.stringParams = null;
+ i.withConstructorParams = true;
+ return i;
+ }
+
+ /**
+ * Makes an initializer creating a new object.
+ *
+ * <p>This initializer creates a new object and uses it as the initial
+ * value of the field. The constructor of the created object receives
+ * the parameters:
+ *
+ * <ul><code>Object obj</code> - the object including the field.<br>
+ * <code>String[] strs</code> - the character strings specified
+ * by <code>stringParams</code><br>
+ * <code>Object[] args</code> - the parameters passed to the
+ * constructor of the object including the
+ * filed.
+ * </ul>
+ *
+ * <p>If the initialized field is static, then the constructor receives
+ * only <code>strs</code>.
+ *
+ * @param objectType the class instantiated for the initial value.
+ * @param stringParams the array of strings passed to the
+ * constructor.
+ */
+ public static Initializer byNewWithParams(CtClass objectType,
+ String[] stringParams) {
+ NewInitializer i = new NewInitializer();
+ i.objectType = objectType;
+ i.stringParams = stringParams;
+ i.withConstructorParams = true;
+ return i;
+ }
+
+ /**
+ * Makes an initializer calling a static method.
+ *
+ * <p>This initializer calls a static method and uses the returned
+ * value as the initial value of the field.
+ * The called method receives the parameters:
+ *
+ * <ul><code>Object obj</code> - the object including the field.<br>
+ * </ul>
+ *
+ * <p>If the initialized field is static, then the method does
+ * not receive any parameters.
+ *
+ * <p>The type of the returned value must be the same as the field
+ * type.
+ *
+ * @param methodClass the class that the static method is
+ * declared in.
+ * @param methodName the name of the satic method.
+ */
+ public static Initializer byCall(CtClass methodClass,
+ String methodName) {
+ MethodInitializer i = new MethodInitializer();
+ i.objectType = methodClass;
+ i.methodName = methodName;
+ i.stringParams = null;
+ i.withConstructorParams = false;
+ return i;
+ }
+
+ /**
+ * Makes an initializer calling a static method.
+ *
+ * <p>This initializer calls a static method and uses the returned
+ * value as the initial value of the field. The called method
+ * receives the parameters:
+ *
+ * <ul><code>Object obj</code> - the object including the field.<br>
+ * <code>String[] strs</code> - the character strings specified
+ * by <code>stringParams</code><br>
+ * </ul>
+ *
+ * <p>If the initialized field is static, then the method
+ * receive only <code>strs</code>.
+ *
+ * <p>The type of the returned value must be the same as the field
+ * type.
+ *
+ * @param methodClass the class that the static method is
+ * declared in.
+ * @param methodName the name of the satic method.
+ * @param stringParams the array of strings passed to the
+ * static method.
+ */
+ public static Initializer byCall(CtClass methodClass,
+ String methodName,
+ String[] stringParams) {
+ MethodInitializer i = new MethodInitializer();
+ i.objectType = methodClass;
+ i.methodName = methodName;
+ i.stringParams = stringParams;
+ i.withConstructorParams = false;
+ return i;
+ }
+
+ /**
+ * Makes an initializer calling a static method.
+ *
+ * <p>This initializer calls a static method and uses the returned
+ * value as the initial value of the field. The called method
+ * receives the parameters:
+ *
+ * <ul><code>Object obj</code> - the object including the field.<br>
+ * <code>Object[] args</code> - the parameters passed to the
+ * constructor of the object including the
+ * filed.
+ * </ul>
+ *
+ * <p>If the initialized field is static, then the method does
+ * not receive any parameters.
+ *
+ * <p>The type of the returned value must be the same as the field
+ * type.
+ *
+ * @param methodClass the class that the static method is
+ * declared in.
+ * @param methodName the name of the satic method.
+ */
+ public static Initializer byCallWithParams(CtClass methodClass,
+ String methodName) {
+ MethodInitializer i = new MethodInitializer();
+ i.objectType = methodClass;
+ i.methodName = methodName;
+ i.stringParams = null;
+ i.withConstructorParams = true;
+ return i;
+ }
+
+ /**
+ * Makes an initializer calling a static method.
+ *
+ * <p>This initializer calls a static method and uses the returned
+ * value as the initial value of the field. The called method
+ * receives the parameters:
+ *
+ * <ul><code>Object obj</code> - the object including the field.<br>
+ * <code>String[] strs</code> - the character strings specified
+ * by <code>stringParams</code><br>
+ * <code>Object[] args</code> - the parameters passed to the
+ * constructor of the object including the
+ * filed.
+ * </ul>
+ *
+ * <p>If the initialized field is static, then the method
+ * receive only <code>strs</code>.
+ *
+ * <p>The type of the returned value must be the same as the field
+ * type.
+ *
+ * @param methodClass the class that the static method is
+ * declared in.
+ * @param methodName the name of the satic method.
+ * @param stringParams the array of strings passed to the
+ * static method.
+ */
+ public static Initializer byCallWithParams(CtClass methodClass,
+ String methodName, String[] stringParams) {
+ MethodInitializer i = new MethodInitializer();
+ i.objectType = methodClass;
+ i.methodName = methodName;
+ i.stringParams = stringParams;
+ i.withConstructorParams = true;
+ return i;
+ }
+
+ /**
+ * Makes an initializer creating a new array.
+ *
+ * @param type the type of the array.
+ * @param size the size of the array.
+ * @throws NotFoundException if the type of the array components
+ * is not found.
+ */
+ public static Initializer byNewArray(CtClass type, int size)
+ throws NotFoundException
+ {
+ return new ArrayInitializer(type.getComponentType(), size);
+ }
+
+ /**
+ * Makes an initializer creating a new multi-dimensional array.
+ *
+ * @param type the type of the array.
+ * @param sizes an <code>int</code> array of the size in every
+ * dimension.
+ * The first element is the size in the first
+ * dimension. The second is in the second, etc.
+ */
+ public static Initializer byNewArray(CtClass type, int[] sizes) {
+ return new MultiArrayInitializer(type, sizes);
+ }
+
+ /**
+ * Makes an initializer.
+ *
+ * @param source initializer expression.
+ */
+ public static Initializer byExpr(String source) {
+ return new CodeInitializer(source);
+ }
+
+ static Initializer byExpr(ASTree source) {
+ return new PtreeInitializer(source);
+ }
+
+ // Check whether this initializer is valid for the field type.
+ // If it is invaild, this method throws an exception.
+ void check(String desc) throws CannotCompileException {}
+
+ // produce codes for initialization
+ abstract int compile(CtClass type, String name, Bytecode code,
+ CtClass[] parameters, Javac drv)
+ throws CannotCompileException;
+
+ // produce codes for initialization
+ abstract int compileIfStatic(CtClass type, String name,
+ Bytecode code, Javac drv) throws CannotCompileException;
+
+ // returns the index of CONSTANT_Integer_info etc
+ // if the value is constant. Otherwise, 0.
+ int getConstantValue(ConstPool cp, CtClass type) { return 0; }
+ }
+
+ static abstract class CodeInitializer0 extends Initializer {
+ abstract void compileExpr(Javac drv) throws CompileError;
+
+ int compile(CtClass type, String name, Bytecode code,
+ CtClass[] parameters, Javac drv)
+ throws CannotCompileException
+ {
+ try {
+ code.addAload(0);
+ compileExpr(drv);
+ code.addPutfield(Bytecode.THIS, name, Descriptor.of(type));
+ return code.getMaxStack();
+ }
+ catch (CompileError e) {
+ throw new CannotCompileException(e);
+ }
+ }
+
+ int compileIfStatic(CtClass type, String name, Bytecode code,
+ Javac drv) throws CannotCompileException
+ {
+ try {
+ compileExpr(drv);
+ code.addPutstatic(Bytecode.THIS, name, Descriptor.of(type));
+ return code.getMaxStack();
+ }
+ catch (CompileError e) {
+ throw new CannotCompileException(e);
+ }
+ }
+
+ int getConstantValue2(ConstPool cp, CtClass type, ASTree tree) {
+ if (type.isPrimitive()) {
+ if (tree instanceof IntConst) {
+ long value = ((IntConst)tree).get();
+ if (type == CtClass.doubleType)
+ return cp.addDoubleInfo((double)value);
+ else if (type == CtClass.floatType)
+ return cp.addFloatInfo((float)value);
+ else if (type == CtClass.longType)
+ return cp.addLongInfo(value);
+ else if (type != CtClass.voidType)
+ return cp.addIntegerInfo((int)value);
+ }
+ else if (tree instanceof DoubleConst) {
+ double value = ((DoubleConst)tree).get();
+ if (type == CtClass.floatType)
+ return cp.addFloatInfo((float)value);
+ else if (type == CtClass.doubleType)
+ return cp.addDoubleInfo(value);
+ }
+ }
+ else if (tree instanceof StringL
+ && type.getName().equals(javaLangString))
+ return cp.addStringInfo(((StringL)tree).get());
+
+ return 0;
+ }
+ }
+
+ static class CodeInitializer extends CodeInitializer0 {
+ private String expression;
+
+ CodeInitializer(String expr) { expression = expr; }
+
+ void compileExpr(Javac drv) throws CompileError {
+ drv.compileExpr(expression);
+ }
+
+ int getConstantValue(ConstPool cp, CtClass type) {
+ try {
+ ASTree t = Javac.parseExpr(expression, new SymbolTable());
+ return getConstantValue2(cp, type, t);
+ }
+ catch (CompileError e) {
+ return 0;
+ }
+ }
+ }
+
+ static class PtreeInitializer extends CodeInitializer0 {
+ private ASTree expression;
+
+ PtreeInitializer(ASTree expr) { expression = expr; }
+
+ void compileExpr(Javac drv) throws CompileError {
+ drv.compileExpr(expression);
+ }
+
+ int getConstantValue(ConstPool cp, CtClass type) {
+ return getConstantValue2(cp, type, expression);
+ }
+ }
+
+ /**
+ * A field initialized with a parameter passed to the constructor
+ * of the class containing that field.
+ */
+ static class ParamInitializer extends Initializer {
+ int nthParam;
+
+ ParamInitializer() {}
+
+ int compile(CtClass type, String name, Bytecode code,
+ CtClass[] parameters, Javac drv)
+ throws CannotCompileException
+ {
+ if (parameters != null && nthParam < parameters.length) {
+ code.addAload(0);
+ int nth = nthParamToLocal(nthParam, parameters, false);
+ int s = code.addLoad(nth, type) + 1;
+ code.addPutfield(Bytecode.THIS, name, Descriptor.of(type));
+ return s; // stack size
+ }
+ else
+ return 0; // do not initialize
+ }
+
+ /**
+ * Computes the index of the local variable that the n-th parameter
+ * is assigned to.
+ *
+ * @param nth n-th parameter
+ * @param params list of parameter types
+ * @param isStatic true if the method is static.
+ */
+ static int nthParamToLocal(int nth, CtClass[] params,
+ boolean isStatic) {
+ CtClass longType = CtClass.longType;
+ CtClass doubleType = CtClass.doubleType;
+ int k;
+ if (isStatic)
+ k = 0;
+ else
+ k = 1; // 0 is THIS.
+
+ for (int i = 0; i < nth; ++i) {
+ CtClass type = params[i];
+ if (type == longType || type == doubleType)
+ k += 2;
+ else
+ ++k;
+ }
+
+ return k;
+ }
+
+ int compileIfStatic(CtClass type, String name, Bytecode code,
+ Javac drv) throws CannotCompileException
+ {
+ return 0;
+ }
+ }
+
+ /**
+ * A field initialized with an object created by the new operator.
+ */
+ static class NewInitializer extends Initializer {
+ CtClass objectType;
+ String[] stringParams;
+ boolean withConstructorParams;
+
+ NewInitializer() {}
+
+ /**
+ * Produces codes in which a new object is created and assigned to
+ * the field as the initial value.
+ */
+ int compile(CtClass type, String name, Bytecode code,
+ CtClass[] parameters, Javac drv)
+ throws CannotCompileException
+ {
+ int stacksize;
+
+ code.addAload(0);
+ code.addNew(objectType);
+ code.add(Bytecode.DUP);
+ code.addAload(0);
+
+ if (stringParams == null)
+ stacksize = 4;
+ else
+ stacksize = compileStringParameter(code) + 4;
+
+ if (withConstructorParams)
+ stacksize += CtNewWrappedMethod.compileParameterList(code,
+ parameters, 1);
+
+ code.addInvokespecial(objectType, "<init>", getDescriptor());
+ code.addPutfield(Bytecode.THIS, name, Descriptor.of(type));
+ return stacksize;
+ }
+
+ private String getDescriptor() {
+ final String desc3
+ = "(Ljava/lang/Object;[Ljava/lang/String;[Ljava/lang/Object;)V";
+
+ if (stringParams == null)
+ if (withConstructorParams)
+ return "(Ljava/lang/Object;[Ljava/lang/Object;)V";
+ else
+ return "(Ljava/lang/Object;)V";
+ else
+ if (withConstructorParams)
+ return desc3;
+ else
+ return "(Ljava/lang/Object;[Ljava/lang/String;)V";
+ }
+
+ /**
+ * Produces codes for a static field.
+ */
+ int compileIfStatic(CtClass type, String name, Bytecode code,
+ Javac drv) throws CannotCompileException
+ {
+ String desc;
+
+ code.addNew(objectType);
+ code.add(Bytecode.DUP);
+
+ int stacksize = 2;
+ if (stringParams == null)
+ desc = "()V";
+ else {
+ desc = "([Ljava/lang/String;)V";
+ stacksize += compileStringParameter(code);
+ }
+
+ code.addInvokespecial(objectType, "<init>", desc);
+ code.addPutstatic(Bytecode.THIS, name, Descriptor.of(type));
+ return stacksize;
+ }
+
+ protected final int compileStringParameter(Bytecode code)
+ throws CannotCompileException
+ {
+ int nparam = stringParams.length;
+ code.addIconst(nparam);
+ code.addAnewarray(javaLangString);
+ for (int j = 0; j < nparam; ++j) {
+ code.add(Bytecode.DUP); // dup
+ code.addIconst(j); // iconst_<j>
+ code.addLdc(stringParams[j]); // ldc ...
+ code.add(Bytecode.AASTORE); // aastore
+ }
+
+ return 4;
+ }
+
+ }
+
+ /**
+ * A field initialized with the result of a static method call.
+ */
+ static class MethodInitializer extends NewInitializer {
+ String methodName;
+ // the method class is specified by objectType.
+
+ MethodInitializer() {}
+
+ /**
+ * Produces codes in which a new object is created and assigned to
+ * the field as the initial value.
+ */
+ int compile(CtClass type, String name, Bytecode code,
+ CtClass[] parameters, Javac drv)
+ throws CannotCompileException
+ {
+ int stacksize;
+
+ code.addAload(0);
+ code.addAload(0);
+
+ if (stringParams == null)
+ stacksize = 2;
+ else
+ stacksize = compileStringParameter(code) + 2;
+
+ if (withConstructorParams)
+ stacksize += CtNewWrappedMethod.compileParameterList(code,
+ parameters, 1);
+
+ String typeDesc = Descriptor.of(type);
+ String mDesc = getDescriptor() + typeDesc;
+ code.addInvokestatic(objectType, methodName, mDesc);
+ code.addPutfield(Bytecode.THIS, name, typeDesc);
+ return stacksize;
+ }
+
+ private String getDescriptor() {
+ final String desc3
+ = "(Ljava/lang/Object;[Ljava/lang/String;[Ljava/lang/Object;)";
+
+ if (stringParams == null)
+ if (withConstructorParams)
+ return "(Ljava/lang/Object;[Ljava/lang/Object;)";
+ else
+ return "(Ljava/lang/Object;)";
+ else
+ if (withConstructorParams)
+ return desc3;
+ else
+ return "(Ljava/lang/Object;[Ljava/lang/String;)";
+ }
+
+ /**
+ * Produces codes for a static field.
+ */
+ int compileIfStatic(CtClass type, String name, Bytecode code,
+ Javac drv) throws CannotCompileException
+ {
+ String desc;
+
+ int stacksize = 1;
+ if (stringParams == null)
+ desc = "()";
+ else {
+ desc = "([Ljava/lang/String;)";
+ stacksize += compileStringParameter(code);
+ }
+
+ String typeDesc = Descriptor.of(type);
+ code.addInvokestatic(objectType, methodName, desc + typeDesc);
+ code.addPutstatic(Bytecode.THIS, name, typeDesc);
+ return stacksize;
+ }
+ }
+
+ static class IntInitializer extends Initializer {
+ int value;
+
+ IntInitializer(int v) { value = v; }
+
+ void check(String desc) throws CannotCompileException {
+ char c = desc.charAt(0);
+ if (c != 'I' && c != 'S' && c != 'B' && c != 'C' && c != 'Z')
+ throw new CannotCompileException("type mismatch");
+ }
+
+ int compile(CtClass type, String name, Bytecode code,
+ CtClass[] parameters, Javac drv)
+ throws CannotCompileException
+ {
+ code.addAload(0);
+ code.addIconst(value);
+ code.addPutfield(Bytecode.THIS, name, Descriptor.of(type));
+ return 2; // stack size
+ }
+
+ int compileIfStatic(CtClass type, String name, Bytecode code,
+ Javac drv) throws CannotCompileException
+ {
+ code.addIconst(value);
+ code.addPutstatic(Bytecode.THIS, name, Descriptor.of(type));
+ return 1; // stack size
+ }
+
+ int getConstantValue(ConstPool cp, CtClass type) {
+ return cp.addIntegerInfo(value);
+ }
+ }
+
+ static class LongInitializer extends Initializer {
+ long value;
+
+ LongInitializer(long v) { value = v; }
+
+ void check(String desc) throws CannotCompileException {
+ if (!desc.equals("J"))
+ throw new CannotCompileException("type mismatch");
+ }
+
+ int compile(CtClass type, String name, Bytecode code,
+ CtClass[] parameters, Javac drv)
+ throws CannotCompileException
+ {
+ code.addAload(0);
+ code.addLdc2w(value);
+ code.addPutfield(Bytecode.THIS, name, Descriptor.of(type));
+ return 3; // stack size
+ }
+
+ int compileIfStatic(CtClass type, String name, Bytecode code,
+ Javac drv) throws CannotCompileException
+ {
+ code.addLdc2w(value);
+ code.addPutstatic(Bytecode.THIS, name, Descriptor.of(type));
+ return 2; // stack size
+ }
+
+ int getConstantValue(ConstPool cp, CtClass type) {
+ if (type == CtClass.longType)
+ return cp.addLongInfo(value);
+ else
+ return 0;
+ }
+ }
+
+ static class FloatInitializer extends Initializer {
+ float value;
+
+ FloatInitializer(float v) { value = v; }
+
+ void check(String desc) throws CannotCompileException {
+ if (!desc.equals("F"))
+ throw new CannotCompileException("type mismatch");
+ }
+
+ int compile(CtClass type, String name, Bytecode code,
+ CtClass[] parameters, Javac drv)
+ throws CannotCompileException
+ {
+ code.addAload(0);
+ code.addFconst(value);
+ code.addPutfield(Bytecode.THIS, name, Descriptor.of(type));
+ return 3; // stack size
+ }
+
+ int compileIfStatic(CtClass type, String name, Bytecode code,
+ Javac drv) throws CannotCompileException
+ {
+ code.addFconst(value);
+ code.addPutstatic(Bytecode.THIS, name, Descriptor.of(type));
+ return 2; // stack size
+ }
+
+ int getConstantValue(ConstPool cp, CtClass type) {
+ if (type == CtClass.floatType)
+ return cp.addFloatInfo(value);
+ else
+ return 0;
+ }
+ }
+
+ static class DoubleInitializer extends Initializer {
+ double value;
+
+ DoubleInitializer(double v) { value = v; }
+
+ void check(String desc) throws CannotCompileException {
+ if (!desc.equals("D"))
+ throw new CannotCompileException("type mismatch");
+ }
+
+ int compile(CtClass type, String name, Bytecode code,
+ CtClass[] parameters, Javac drv)
+ throws CannotCompileException
+ {
+ code.addAload(0);
+ code.addLdc2w(value);
+ code.addPutfield(Bytecode.THIS, name, Descriptor.of(type));
+ return 3; // stack size
+ }
+
+ int compileIfStatic(CtClass type, String name, Bytecode code,
+ Javac drv) throws CannotCompileException
+ {
+ code.addLdc2w(value);
+ code.addPutstatic(Bytecode.THIS, name, Descriptor.of(type));
+ return 2; // stack size
+ }
+
+ int getConstantValue(ConstPool cp, CtClass type) {
+ if (type == CtClass.doubleType)
+ return cp.addDoubleInfo(value);
+ else
+ return 0;
+ }
+ }
+
+ static class StringInitializer extends Initializer {
+ String value;
+
+ StringInitializer(String v) { value = v; }
+
+ int compile(CtClass type, String name, Bytecode code,
+ CtClass[] parameters, Javac drv)
+ throws CannotCompileException
+ {
+ code.addAload(0);
+ code.addLdc(value);
+ code.addPutfield(Bytecode.THIS, name, Descriptor.of(type));
+ return 2; // stack size
+ }
+
+ int compileIfStatic(CtClass type, String name, Bytecode code,
+ Javac drv) throws CannotCompileException
+ {
+ code.addLdc(value);
+ code.addPutstatic(Bytecode.THIS, name, Descriptor.of(type));
+ return 1; // stack size
+ }
+
+ int getConstantValue(ConstPool cp, CtClass type) {
+ if (type.getName().equals(javaLangString))
+ return cp.addStringInfo(value);
+ else
+ return 0;
+ }
+ }
+
+ static class ArrayInitializer extends Initializer {
+ CtClass type;
+ int size;
+
+ ArrayInitializer(CtClass t, int s) { type = t; size = s; }
+
+ private void addNewarray(Bytecode code) {
+ if (type.isPrimitive())
+ code.addNewarray(((CtPrimitiveType)type).getArrayType(),
+ size);
+ else
+ code.addAnewarray(type, size);
+ }
+
+ int compile(CtClass type, String name, Bytecode code,
+ CtClass[] parameters, Javac drv)
+ throws CannotCompileException
+ {
+ code.addAload(0);
+ addNewarray(code);
+ code.addPutfield(Bytecode.THIS, name, Descriptor.of(type));
+ return 2; // stack size
+ }
+
+ int compileIfStatic(CtClass type, String name, Bytecode code,
+ Javac drv) throws CannotCompileException
+ {
+ addNewarray(code);
+ code.addPutstatic(Bytecode.THIS, name, Descriptor.of(type));
+ return 1; // stack size
+ }
+ }
+
+ static class MultiArrayInitializer extends Initializer {
+ CtClass type;
+ int[] dim;
+
+ MultiArrayInitializer(CtClass t, int[] d) { type = t; dim = d; }
+
+ void check(String desc) throws CannotCompileException {
+ if (desc.charAt(0) != '[')
+ throw new CannotCompileException("type mismatch");
+ }
+
+ int compile(CtClass type, String name, Bytecode code,
+ CtClass[] parameters, Javac drv)
+ throws CannotCompileException
+ {
+ code.addAload(0);
+ int s = code.addMultiNewarray(type, dim);
+ code.addPutfield(Bytecode.THIS, name, Descriptor.of(type));
+ return s + 1; // stack size
+ }
+
+ int compileIfStatic(CtClass type, String name, Bytecode code,
+ Javac drv) throws CannotCompileException
+ {
+ int s = code.addMultiNewarray(type, dim);
+ code.addPutstatic(Bytecode.THIS, name, Descriptor.of(type));
+ return s; // stack size
+ }
+ }
+}
diff --git a/src/main/javassist/CtMember.java b/src/main/javassist/CtMember.java
new file mode 100644
index 0000000..0956642
--- /dev/null
+++ b/src/main/javassist/CtMember.java
@@ -0,0 +1,295 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist;
+
+/**
+ * An instance of <code>CtMember</code> represents a field, a constructor,
+ * or a method.
+ */
+public abstract class CtMember {
+ CtMember next; // for internal use
+ protected CtClass declaringClass;
+
+ /* Make a circular link of CtMembers declared in the
+ * same class so that they are garbage-collected together
+ * at the same time.
+ */
+ static class Cache extends CtMember {
+ protected void extendToString(StringBuffer buffer) {}
+ public boolean hasAnnotation(Class clz) { return false; }
+ public Object getAnnotation(Class clz)
+ throws ClassNotFoundException { return null; }
+ public Object[] getAnnotations()
+ throws ClassNotFoundException { return null; }
+ public byte[] getAttribute(String name) { return null; }
+ public Object[] getAvailableAnnotations() { return null; }
+ public int getModifiers() { return 0; }
+ public String getName() { return null; }
+ public String getSignature() { return null; }
+ public void setAttribute(String name, byte[] data) {}
+ public void setModifiers(int mod) {}
+
+ private CtMember methodTail;
+ private CtMember consTail; // constructor tail
+ private CtMember fieldTail;
+
+ Cache(CtClassType decl) {
+ super(decl);
+ methodTail = this;
+ consTail = this;
+ fieldTail = this;
+ fieldTail.next = this;
+ }
+
+ CtMember methodHead() { return this; }
+ CtMember lastMethod() { return methodTail; }
+ CtMember consHead() { return methodTail; } // may include a static initializer
+ CtMember lastCons() { return consTail; }
+ CtMember fieldHead() { return consTail; }
+ CtMember lastField() { return fieldTail; }
+
+ void addMethod(CtMember method) {
+ method.next = methodTail.next;
+ methodTail.next = method;
+ if (methodTail == consTail) {
+ consTail = method;
+ if (methodTail == fieldTail)
+ fieldTail = method;
+ }
+
+ methodTail = method;
+ }
+
+ /* Both constructors and a class initializer.
+ */
+ void addConstructor(CtMember cons) {
+ cons.next = consTail.next;
+ consTail.next = cons;
+ if (consTail == fieldTail)
+ fieldTail = cons;
+
+ consTail = cons;
+ }
+
+ void addField(CtMember field) {
+ field.next = this; // or fieldTail.next
+ fieldTail.next = field;
+ fieldTail = field;
+ }
+
+ static int count(CtMember head, CtMember tail) {
+ int n = 0;
+ while (head != tail) {
+ n++;
+ head = head.next;
+ }
+
+ return n;
+ }
+
+ void remove(CtMember mem) {
+ CtMember m = this;
+ CtMember node;
+ while ((node = m.next) != this) {
+ if (node == mem) {
+ m.next = node.next;
+ if (node == methodTail)
+ methodTail = m;
+
+ if (node == consTail)
+ consTail = m;
+
+ if (node == fieldTail)
+ fieldTail = m;
+
+ break;
+ }
+ else
+ m = m.next;
+ }
+ }
+ }
+
+ protected CtMember(CtClass clazz) {
+ declaringClass = clazz;
+ next = null;
+ }
+
+ final CtMember next() { return next; }
+
+ /**
+ * This method is invoked when setName() or replaceClassName()
+ * in CtClass is called.
+ *
+ * @see CtMethod#nameReplaced()
+ */
+ void nameReplaced() {}
+
+ public String toString() {
+ StringBuffer buffer = new StringBuffer(getClass().getName());
+ buffer.append("@");
+ buffer.append(Integer.toHexString(hashCode()));
+ buffer.append("[");
+ buffer.append(Modifier.toString(getModifiers()));
+ extendToString(buffer);
+ buffer.append("]");
+ return buffer.toString();
+ }
+
+ /**
+ * Invoked by {@link #toString()} to add to the buffer and provide the
+ * complete value. Subclasses should invoke this method, adding a
+ * space before each token. The modifiers for the member are
+ * provided first; subclasses should provide additional data such
+ * as return type, field or method name, etc.
+ */
+ protected abstract void extendToString(StringBuffer buffer);
+
+ /**
+ * Returns the class that declares this member.
+ */
+ public CtClass getDeclaringClass() { return declaringClass; }
+
+ /**
+ * Returns true if this member is accessible from the given class.
+ */
+ public boolean visibleFrom(CtClass clazz) {
+ int mod = getModifiers();
+ if (Modifier.isPublic(mod))
+ return true;
+ else if (Modifier.isPrivate(mod))
+ return clazz == declaringClass;
+ else { // package or protected
+ String declName = declaringClass.getPackageName();
+ String fromName = clazz.getPackageName();
+ boolean visible;
+ if (declName == null)
+ visible = fromName == null;
+ else
+ visible = declName.equals(fromName);
+
+ if (!visible && Modifier.isProtected(mod))
+ return clazz.subclassOf(declaringClass);
+
+ return visible;
+ }
+ }
+
+ /**
+ * Obtains the modifiers of the member.
+ *
+ * @return modifiers encoded with
+ * <code>javassist.Modifier</code>.
+ * @see Modifier
+ */
+ public abstract int getModifiers();
+
+ /**
+ * Sets the encoded modifiers of the member.
+ *
+ * @see Modifier
+ */
+ public abstract void setModifiers(int mod);
+
+ /**
+ * Returns true if the class has the specified annotation class.
+ *
+ * @param clz the annotation class.
+ * @return <code>true</code> if the annotation is found, otherwise <code>false</code>.
+ * @since 3.11
+ */
+ public abstract boolean hasAnnotation(Class clz);
+
+ /**
+ * Returns the annotation if the class has the specified annotation class.
+ * For example, if an annotation <code>@Author</code> is associated
+ * with this member, an <code>Author</code> object is returned.
+ * The member values can be obtained by calling methods on
+ * the <code>Author</code> object.
+ *
+ * @param clz the annotation class.
+ * @return the annotation if found, otherwise <code>null</code>.
+ * @since 3.11
+ */
+ public abstract Object getAnnotation(Class clz) throws ClassNotFoundException;
+
+ /**
+ * Returns the annotations associated with this member.
+ * For example, if an annotation <code>@Author</code> is associated
+ * with this member, the returned array contains an <code>Author</code>
+ * object. The member values can be obtained by calling methods on
+ * the <code>Author</code> object.
+ *
+ * @return an array of annotation-type objects.
+ * @see CtClass#getAnnotations()
+ */
+ public abstract Object[] getAnnotations() throws ClassNotFoundException;
+
+ /**
+ * Returns the annotations associated with this member.
+ * This method is equivalent to <code>getAnnotations()</code>
+ * except that, if any annotations are not on the classpath,
+ * they are not included in the returned array.
+ *
+ * @return an array of annotation-type objects.
+ * @see #getAnnotations()
+ * @see CtClass#getAvailableAnnotations()
+ * @since 3.3
+ */
+ public abstract Object[] getAvailableAnnotations();
+
+ /**
+ * Obtains the name of the member.
+ *
+ * <p>As for constructor names, see <code>getName()</code>
+ * in <code>CtConstructor</code>.
+ *
+ * @see CtConstructor#getName()
+ */
+ public abstract String getName();
+
+ /**
+ * Returns the character string representing the signature of the member.
+ * If two members have the same signature (parameter types etc.),
+ * <code>getSignature()</code> returns the same string.
+ */
+ public abstract String getSignature();
+
+ /**
+ * Obtains a user-defined attribute with the given name.
+ * If that attribute is not found in the class file, this
+ * method returns null.
+ *
+ * <p>Note that an attribute is a data block specified by
+ * the class file format.
+ * See {@link javassist.bytecode.AttributeInfo}.
+ *
+ * @param name attribute name
+ */
+ public abstract byte[] getAttribute(String name);
+
+ /**
+ * Adds a user-defined attribute. The attribute is saved in the class file.
+ *
+ * <p>Note that an attribute is a data block specified by
+ * the class file format.
+ * See {@link javassist.bytecode.AttributeInfo}.
+ *
+ * @param name attribute name
+ * @param data attribute value
+ */
+ public abstract void setAttribute(String name, byte[] data);
+}
diff --git a/src/main/javassist/CtMethod.java b/src/main/javassist/CtMethod.java
new file mode 100644
index 0000000..727ff5b
--- /dev/null
+++ b/src/main/javassist/CtMethod.java
@@ -0,0 +1,435 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist;
+
+import javassist.bytecode.*;
+
+/**
+ * An instance of <code>CtMethod</code> represents a method.
+ *
+ * <p>See the super class <code>CtBehavior</code> since
+ * a number of useful methods are in <code>CtBehavior</code>.
+ * A number of useful factory methods are in <code>CtNewMethod</code>.
+ *
+ * @see CtClass#getDeclaredMethods()
+ * @see CtNewMethod
+ */
+public final class CtMethod extends CtBehavior {
+ protected String cachedStringRep;
+
+ /**
+ * @see #make(MethodInfo minfo, CtClass declaring)
+ */
+ CtMethod(MethodInfo minfo, CtClass declaring) {
+ super(declaring, minfo);
+ cachedStringRep = null;
+ }
+
+ /**
+ * Creates a public abstract method. The created method must be
+ * added to a class with <code>CtClass.addMethod()</code>.
+ *
+ * @param declaring the class to which the created method is added.
+ * @param returnType the type of the returned value
+ * @param mname the method name
+ * @param parameters a list of the parameter types
+ *
+ * @see CtClass#addMethod(CtMethod)
+ */
+ public CtMethod(CtClass returnType, String mname,
+ CtClass[] parameters, CtClass declaring) {
+ this(null, declaring);
+ ConstPool cp = declaring.getClassFile2().getConstPool();
+ String desc = Descriptor.ofMethod(returnType, parameters);
+ methodInfo = new MethodInfo(cp, mname, desc);
+ setModifiers(Modifier.PUBLIC | Modifier.ABSTRACT);
+ }
+
+ /**
+ * Creates a copy of a <code>CtMethod</code> object.
+ * The created method must be
+ * added to a class with <code>CtClass.addMethod()</code>.
+ *
+ * <p>All occurrences of class names in the created method
+ * are replaced with names specified by
+ * <code>map</code> if <code>map</code> is not <code>null</code>.
+ *
+ * <p>For example, suppose that a method <code>at()</code> is as
+ * follows:
+ *
+ * <ul><pre>public X at(int i) {
+ * return (X)super.elementAt(i);
+ * }</pre></ul>
+ *
+ * <p>(<code>X</code> is a class name.) If <code>map</code> substitutes
+ * <code>String</code> for <code>X</code>, then the created method is:
+ *
+ * <ul><pre>public String at(int i) {
+ * return (String)super.elementAt(i);
+ * }</pre></ul>
+ *
+ * <p>By default, all the occurrences of the names of the class
+ * declaring <code>at()</code> and the superclass are replaced
+ * with the name of the class and the superclass that the
+ * created method is added to.
+ * This is done whichever <code>map</code> is null or not.
+ * To prevent this replacement, call <code>ClassMap.fix()</code>
+ * or <code>put()</code> to explicitly specify replacement.
+ *
+ * <p><b>Note:</b> if the <code>.class</code> notation (for example,
+ * <code>String.class</code>) is included in an expression, the
+ * Javac compiler may produce a helper method.
+ * Since this constructor never
+ * copies this helper method, the programmers have the responsiblity of
+ * copying it. Otherwise, use <code>Class.forName()</code> in the
+ * expression.
+ *
+ * @param src the source method.
+ * @param declaring the class to which the created method is added.
+ * @param map the hashtable associating original class names
+ * with substituted names.
+ * It can be <code>null</code>.
+ *
+ * @see CtClass#addMethod(CtMethod)
+ * @see ClassMap#fix(String)
+ */
+ public CtMethod(CtMethod src, CtClass declaring, ClassMap map)
+ throws CannotCompileException
+ {
+ this(null, declaring);
+ copy(src, false, map);
+ }
+
+ /**
+ * Compiles the given source code and creates a method.
+ * This method simply delegates to <code>make()</code> in
+ * <code>CtNewMethod</code>. See it for more details.
+ * <code>CtNewMethod</code> has a number of useful factory methods.
+ *
+ * @param src the source text.
+ * @param declaring the class to which the created method is added.
+ * @see CtNewMethod#make(String, CtClass)
+ */
+ public static CtMethod make(String src, CtClass declaring)
+ throws CannotCompileException
+ {
+ return CtNewMethod.make(src, declaring);
+ }
+
+ /**
+ * Creates a method from a <code>MethodInfo</code> object.
+ *
+ * @param declaring the class declaring the method.
+ * @throws CannotCompileException if the the <code>MethodInfo</code>
+ * object and the declaring class have different
+ * <code>ConstPool</code> objects
+ * @since 3.6
+ */
+ public static CtMethod make(MethodInfo minfo, CtClass declaring)
+ throws CannotCompileException
+ {
+ if (declaring.getClassFile2().getConstPool() != minfo.getConstPool())
+ throw new CannotCompileException("bad declaring class");
+
+ return new CtMethod(minfo, declaring);
+ }
+
+ /**
+ * Returns a hash code value for the method.
+ * If two methods have the same name and signature, then
+ * the hash codes for the two methods are equal.
+ */
+ public int hashCode() {
+ return getStringRep().hashCode();
+ }
+
+ /**
+ * This method is invoked when setName() or replaceClassName()
+ * in CtClass is called.
+ */
+ void nameReplaced() {
+ cachedStringRep = null;
+ }
+
+ /* This method is also called by CtClassType.getMethods0().
+ */
+ final String getStringRep() {
+ if (cachedStringRep == null)
+ cachedStringRep = methodInfo.getName()
+ + Descriptor.getParamDescriptor(methodInfo.getDescriptor());
+
+ return cachedStringRep;
+ }
+
+ /**
+ * Indicates whether <code>obj</code> has the same name and the
+ * same signature as this method.
+ */
+ public boolean equals(Object obj) {
+ return obj != null && obj instanceof CtMethod
+ && ((CtMethod)obj).getStringRep().equals(getStringRep());
+ }
+
+ /**
+ * Returns the method name followed by parameter types
+ * such as <code>javassist.CtMethod.setBody(String)</code>.
+ *
+ * @since 3.5
+ */
+ public String getLongName() {
+ return getDeclaringClass().getName() + "."
+ + getName() + Descriptor.toString(getSignature());
+ }
+
+ /**
+ * Obtains the name of this method.
+ */
+ public String getName() {
+ return methodInfo.getName();
+ }
+
+ /**
+ * Changes the name of this method.
+ */
+ public void setName(String newname) {
+ declaringClass.checkModify();
+ methodInfo.setName(newname);
+ }
+
+ /**
+ * Obtains the type of the returned value.
+ */
+ public CtClass getReturnType() throws NotFoundException {
+ return getReturnType0();
+ }
+
+ /**
+ * Returns true if the method body is empty, that is, <code>{}</code>.
+ * It also returns true if the method is an abstract method.
+ */
+ public boolean isEmpty() {
+ CodeAttribute ca = getMethodInfo2().getCodeAttribute();
+ if (ca == null) // abstract or native
+ return (getModifiers() & Modifier.ABSTRACT) != 0;
+
+ CodeIterator it = ca.iterator();
+ try {
+ return it.hasNext() && it.byteAt(it.next()) == Opcode.RETURN
+ && !it.hasNext();
+ }
+ catch (BadBytecode e) {}
+ return false;
+ }
+
+ /**
+ * Copies a method body from another method.
+ * If this method is abstract, the abstract modifier is removed
+ * after the method body is copied.
+ *
+ * <p>All occurrences of the class names in the copied method body
+ * are replaced with the names specified by
+ * <code>map</code> if <code>map</code> is not <code>null</code>.
+ *
+ * @param src the method that the body is copied from.
+ * @param map the hashtable associating original class names
+ * with substituted names.
+ * It can be <code>null</code>.
+ */
+ public void setBody(CtMethod src, ClassMap map)
+ throws CannotCompileException
+ {
+ setBody0(src.declaringClass, src.methodInfo,
+ declaringClass, methodInfo, map);
+ }
+
+ /**
+ * Replace a method body with a new method body wrapping the
+ * given method.
+ *
+ * @param mbody the wrapped method
+ * @param constParam the constant parameter given to
+ * the wrapped method
+ * (maybe <code>null</code>).
+ *
+ * @see CtNewMethod#wrapped(CtClass,String,CtClass[],CtClass[],CtMethod,CtMethod.ConstParameter,CtClass)
+ */
+ public void setWrappedBody(CtMethod mbody, ConstParameter constParam)
+ throws CannotCompileException
+ {
+ declaringClass.checkModify();
+
+ CtClass clazz = getDeclaringClass();
+ CtClass[] params;
+ CtClass retType;
+ try {
+ params = getParameterTypes();
+ retType = getReturnType();
+ }
+ catch (NotFoundException e) {
+ throw new CannotCompileException(e);
+ }
+
+ Bytecode code = CtNewWrappedMethod.makeBody(clazz,
+ clazz.getClassFile2(),
+ mbody,
+ params, retType,
+ constParam);
+ CodeAttribute cattr = code.toCodeAttribute();
+ methodInfo.setCodeAttribute(cattr);
+ methodInfo.setAccessFlags(methodInfo.getAccessFlags()
+ & ~AccessFlag.ABSTRACT);
+ // rebuilding a stack map table is not needed.
+ }
+
+ // inner classes
+
+ /**
+ * Instances of this class represent a constant parameter.
+ * They are used to specify the parameter given to the methods
+ * created by <code>CtNewMethod.wrapped()</code>.
+ *
+ * @see CtMethod#setWrappedBody(CtMethod,CtMethod.ConstParameter)
+ * @see CtNewMethod#wrapped(CtClass,String,CtClass[],CtClass[],CtMethod,CtMethod.ConstParameter,CtClass)
+ * @see CtNewConstructor#make(CtClass[],CtClass[],int,CtMethod,CtMethod.ConstParameter,CtClass)
+ */
+ public static class ConstParameter {
+ /**
+ * Makes an integer constant.
+ *
+ * @param i the constant value.
+ */
+ public static ConstParameter integer(int i) {
+ return new IntConstParameter(i);
+ }
+
+ /**
+ * Makes a long integer constant.
+ *
+ * @param i the constant value.
+ */
+ public static ConstParameter integer(long i) {
+ return new LongConstParameter(i);
+ }
+
+ /**
+ * Makes an <code>String</code> constant.
+ *
+ * @param s the constant value.
+ */
+ public static ConstParameter string(String s) {
+ return new StringConstParameter(s);
+ }
+
+ ConstParameter() {}
+
+ /**
+ * @return the size of the stack consumption.
+ */
+ int compile(Bytecode code) throws CannotCompileException {
+ return 0;
+ }
+
+ String descriptor() {
+ return defaultDescriptor();
+ }
+
+ /**
+ * @see CtNewWrappedMethod
+ */
+ static String defaultDescriptor() {
+ return "([Ljava/lang/Object;)Ljava/lang/Object;";
+ }
+
+ /**
+ * Returns the descriptor for constructors.
+ *
+ * @see CtNewWrappedConstructor
+ */
+ String constDescriptor() {
+ return defaultConstDescriptor();
+ }
+
+ /**
+ * Returns the default descriptor for constructors.
+ */
+ static String defaultConstDescriptor() {
+ return "([Ljava/lang/Object;)V";
+ }
+ }
+
+ static class IntConstParameter extends ConstParameter {
+ int param;
+
+ IntConstParameter(int i) {
+ param = i;
+ }
+
+ int compile(Bytecode code) throws CannotCompileException {
+ code.addIconst(param);
+ return 1;
+ }
+
+ String descriptor() {
+ return "([Ljava/lang/Object;I)Ljava/lang/Object;";
+ }
+
+ String constDescriptor() {
+ return "([Ljava/lang/Object;I)V";
+ }
+ }
+
+ static class LongConstParameter extends ConstParameter {
+ long param;
+
+ LongConstParameter(long l) {
+ param = l;
+ }
+
+ int compile(Bytecode code) throws CannotCompileException {
+ code.addLconst(param);
+ return 2;
+ }
+
+ String descriptor() {
+ return "([Ljava/lang/Object;J)Ljava/lang/Object;";
+ }
+
+ String constDescriptor() {
+ return "([Ljava/lang/Object;J)V";
+ }
+ }
+
+ static class StringConstParameter extends ConstParameter {
+ String param;
+
+ StringConstParameter(String s) {
+ param = s;
+ }
+
+ int compile(Bytecode code) throws CannotCompileException {
+ code.addLdc(param);
+ return 1;
+ }
+
+ String descriptor() {
+ return "([Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;";
+ }
+
+ String constDescriptor() {
+ return "([Ljava/lang/Object;Ljava/lang/String;)V";
+ }
+ }
+}
diff --git a/src/main/javassist/CtNewClass.java b/src/main/javassist/CtNewClass.java
new file mode 100644
index 0000000..ba48e93
--- /dev/null
+++ b/src/main/javassist/CtNewClass.java
@@ -0,0 +1,125 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import javassist.bytecode.ClassFile;
+
+class CtNewClass extends CtClassType {
+ /* true if the class is an interface.
+ */
+ protected boolean hasConstructor;
+
+ CtNewClass(String name, ClassPool cp,
+ boolean isInterface, CtClass superclass) {
+ super(name, cp);
+ wasChanged = true;
+ String superName;
+ if (isInterface || superclass == null)
+ superName = null;
+ else
+ superName = superclass.getName();
+
+ classfile = new ClassFile(isInterface, name, superName);
+ if (isInterface && superclass != null)
+ classfile.setInterfaces(new String[] { superclass.getName() });
+
+ setModifiers(Modifier.setPublic(getModifiers()));
+ hasConstructor = isInterface;
+ }
+
+ protected void extendToString(StringBuffer buffer) {
+ if (hasConstructor)
+ buffer.append("hasConstructor ");
+
+ super.extendToString(buffer);
+ }
+
+ public void addConstructor(CtConstructor c)
+ throws CannotCompileException
+ {
+ hasConstructor = true;
+ super.addConstructor(c);
+ }
+
+ public void toBytecode(DataOutputStream out)
+ throws CannotCompileException, IOException
+ {
+ if (!hasConstructor)
+ try {
+ inheritAllConstructors();
+ hasConstructor = true;
+ }
+ catch (NotFoundException e) {
+ throw new CannotCompileException(e);
+ }
+
+ super.toBytecode(out);
+ }
+
+ /**
+ * Adds constructors inhrited from the super class.
+ *
+ * <p>After this method is called, the class inherits all the
+ * constructors from the super class. The added constructor
+ * calls the super's constructor with the same signature.
+ */
+ public void inheritAllConstructors()
+ throws CannotCompileException, NotFoundException
+ {
+ CtClass superclazz;
+ CtConstructor[] cs;
+
+ superclazz = getSuperclass();
+ cs = superclazz.getDeclaredConstructors();
+
+ int n = 0;
+ for (int i = 0; i < cs.length; ++i) {
+ CtConstructor c = cs[i];
+ int mod = c.getModifiers();
+ if (isInheritable(mod, superclazz)) {
+ CtConstructor cons
+ = CtNewConstructor.make(c.getParameterTypes(),
+ c.getExceptionTypes(), this);
+ cons.setModifiers(mod & (Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE));
+ addConstructor(cons);
+ ++n;
+ }
+ }
+
+ if (n < 1)
+ throw new CannotCompileException(
+ "no inheritable constructor in " + superclazz.getName());
+
+ }
+
+ private boolean isInheritable(int mod, CtClass superclazz) {
+ if (Modifier.isPrivate(mod))
+ return false;
+
+ if (Modifier.isPackage(mod)) {
+ String pname = getPackageName();
+ String pname2 = superclazz.getPackageName();
+ if (pname == null)
+ return pname2 == null;
+ else
+ return pname.equals(pname2);
+ }
+
+ return true;
+ }
+}
diff --git a/src/main/javassist/CtNewConstructor.java b/src/main/javassist/CtNewConstructor.java
new file mode 100644
index 0000000..f510356
--- /dev/null
+++ b/src/main/javassist/CtNewConstructor.java
@@ -0,0 +1,316 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist;
+
+import javassist.bytecode.*;
+import javassist.compiler.Javac;
+import javassist.compiler.CompileError;
+import javassist.CtMethod.ConstParameter;
+
+/**
+ * A collection of static methods for creating a <code>CtConstructor</code>.
+ * An instance of this class does not make any sense.
+ *
+ * <p>A class initializer (static constructor) cannot be created by the
+ * methods in this class. Call <code>makeClassInitializer()</code> in
+ * <code>CtClass</code> and append code snippet to the body of the class
+ * initializer obtained by <code>makeClassInitializer()</code>.
+ *
+ * @see CtClass#addConstructor(CtConstructor)
+ * @see CtClass#makeClassInitializer()
+ */
+public class CtNewConstructor {
+ /**
+ * Specifies that no parameters are passed to a super-class'
+ * constructor. That is, the default constructor is invoked.
+ */
+ public static final int PASS_NONE = 0; // call super()
+
+ /**
+ * Specifies that parameters are converted into an array of
+ * <code>Object</code> and passed to a super-class'
+ * constructor.
+ */
+ public static final int PASS_ARRAY = 1; // an array of parameters
+
+ /**
+ * Specifies that parameters are passed <i>as is</i>
+ * to a super-class' constructor. The signature of that
+ * constructor must be the same as that of the created constructor.
+ */
+ public static final int PASS_PARAMS = 2;
+
+ /**
+ * Compiles the given source code and creates a constructor.
+ * The source code must include not only the constructor body
+ * but the whole declaration.
+ *
+ * @param src the source text.
+ * @param declaring the class to which the created constructor is added.
+ */
+ public static CtConstructor make(String src, CtClass declaring)
+ throws CannotCompileException
+ {
+ Javac compiler = new Javac(declaring);
+ try {
+ CtMember obj = compiler.compile(src);
+ if (obj instanceof CtConstructor) {
+ // a stack map table has been already created.
+ return (CtConstructor)obj;
+ }
+ }
+ catch (CompileError e) {
+ throw new CannotCompileException(e);
+ }
+
+ throw new CannotCompileException("not a constructor");
+ }
+
+ /**
+ * Creates a public constructor.
+ *
+ * @param parameters a list of the parameter types.
+ * @param exceptions a list of the exception types.
+ * @param body the source text of the constructor body.
+ * It must be a block surrounded by <code>{}</code>.
+ * If it is <code>null</code>, the substituted
+ * constructor body does nothing except calling
+ * <code>super()</code>.
+ * @param declaring the class to which the created method is added.
+ */
+ public static CtConstructor make(CtClass[] parameters,
+ CtClass[] exceptions,
+ String body, CtClass declaring)
+ throws CannotCompileException
+ {
+ try {
+ CtConstructor cc = new CtConstructor(parameters, declaring);
+ cc.setExceptionTypes(exceptions);
+ cc.setBody(body);
+ return cc;
+ }
+ catch (NotFoundException e) {
+ throw new CannotCompileException(e);
+ }
+ }
+
+ /**
+ * Creates a copy of a constructor.
+ * This is a convenience method for calling
+ * {@link CtConstructor#CtConstructor(CtConstructor, CtClass, ClassMap) this constructor}.
+ * See the description of the constructor for particular behavior of the copying.
+ *
+ * @param c the copied constructor.
+ * @param declaring the class to which the created method is added.
+ * @param map the hash table associating original class names
+ * with substituted names.
+ * It can be <code>null</code>.
+ *
+ * @see CtConstructor#CtConstructor(CtConstructor,CtClass,ClassMap)
+ */
+ public static CtConstructor copy(CtConstructor c, CtClass declaring,
+ ClassMap map) throws CannotCompileException {
+ return new CtConstructor(c, declaring, map);
+ }
+
+ /**
+ * Creates a default (public) constructor.
+ *
+ * <p>The created constructor takes no parameter. It calls
+ * <code>super()</code>.
+ */
+ public static CtConstructor defaultConstructor(CtClass declaring)
+ throws CannotCompileException
+ {
+ CtConstructor cons = new CtConstructor((CtClass[])null, declaring);
+
+ ConstPool cp = declaring.getClassFile2().getConstPool();
+ Bytecode code = new Bytecode(cp, 1, 1);
+ code.addAload(0);
+ try {
+ code.addInvokespecial(declaring.getSuperclass(),
+ "<init>", "()V");
+ }
+ catch (NotFoundException e) {
+ throw new CannotCompileException(e);
+ }
+
+ code.add(Bytecode.RETURN);
+
+ // no need to construct a stack map table.
+ cons.getMethodInfo2().setCodeAttribute(code.toCodeAttribute());
+ return cons;
+ }
+
+ /**
+ * Creates a public constructor that only calls a constructor
+ * in the super class. The created constructor receives parameters
+ * specified by <code>parameters</code> but calls the super's
+ * constructor without those parameters (that is, it calls the default
+ * constructor).
+ *
+ * <p>The parameters passed to the created constructor should be
+ * used for field initialization. <code>CtField.Initializer</code>
+ * objects implicitly insert initialization code in constructor
+ * bodies.
+ *
+ * @param parameters parameter types
+ * @param exceptions exception types
+ * @param declaring the class to which the created constructor
+ * is added.
+ * @see CtField.Initializer#byParameter(int)
+ */
+ public static CtConstructor skeleton(CtClass[] parameters,
+ CtClass[] exceptions, CtClass declaring)
+ throws CannotCompileException
+ {
+ return make(parameters, exceptions, PASS_NONE,
+ null, null, declaring);
+ }
+
+ /**
+ * Creates a public constructor that only calls a constructor
+ * in the super class. The created constructor receives parameters
+ * specified by <code>parameters</code> and calls the super's
+ * constructor with those parameters.
+ *
+ * @param parameters parameter types
+ * @param exceptions exception types
+ * @param declaring the class to which the created constructor
+ * is added.
+ */
+ public static CtConstructor make(CtClass[] parameters,
+ CtClass[] exceptions, CtClass declaring)
+ throws CannotCompileException
+ {
+ return make(parameters, exceptions, PASS_PARAMS,
+ null, null, declaring);
+ }
+
+ /**
+ * Creates a public constructor.
+ *
+ * <p>If <code>howto</code> is <code>PASS_PARAMS</code>,
+ * the created constructor calls the super's constructor with the
+ * same signature. The superclass must contain
+ * a constructor taking the same set of parameters as the created one.
+ *
+ * <p>If <code>howto</code> is <code>PASS_NONE</code>,
+ * the created constructor calls the super's default constructor.
+ * The superclass must contain a constructor taking no parameters.
+ *
+ * <p>If <code>howto</code> is <code>PASS_ARRAY</code>,
+ * the created constructor calls the super's constructor
+ * with the given parameters in the form of an array of
+ * <code>Object</code>. The signature of the super's constructor
+ * must be:
+ *
+ * <ul><code>constructor(Object[] params, <type> cvalue)
+ * </code></ul>
+ *
+ * <p>Here, <code>cvalue</code> is the constant value specified
+ * by <code>cparam</code>.
+ *
+ * <p>If <code>cparam</code> is <code>null</code>, the signature
+ * must be:
+ *
+ * <ul><code>constructor(Object[] params)</code></ul>
+ *
+ * <p>If <code>body</code> is not null, a copy of that method is
+ * embedded in the body of the created constructor.
+ * The embedded method is executed after
+ * the super's constructor is called and the values of fields are
+ * initialized. Note that <code>body</code> must not
+ * be a constructor but a method.
+ *
+ * <p>Since the embedded method is wrapped
+ * in parameter-conversion code
+ * as in <code>CtNewMethod.wrapped()</code>,
+ * the constructor parameters are
+ * passed in the form of an array of <code>Object</code>.
+ * The method specified by <code>body</code> must have the
+ * signature shown below:
+ *
+ * <ul><code>Object method(Object[] params, <type> cvalue)
+ * </code></ul>
+ *
+ * <p>If <code>cparam</code> is <code>null</code>, the signature
+ * must be:
+ *
+ * <ul><code>Object method(Object[] params)</code></ul>
+ *
+ * <p>Although the type of the returned value is <code>Object</code>,
+ * the value must be always <code>null</code>.
+ *
+ * <p><i>Example:</i>
+ *
+ * <ul><pre>ClassPool pool = ... ;
+ * CtClass xclass = pool.makeClass("X");
+ * CtMethod method = pool.getMethod("Sample", "m");
+ * xclass.setSuperclass(pool.get("Y"));
+ * CtClass[] argTypes = { CtClass.intType };
+ * ConstParameter cparam = ConstParameter.string("test");
+ * CtConstructor c = CtNewConstructor.make(argTypes, null,
+ * PASS_PARAMS, method, cparam, xclass);
+ * xclass.addConstructor(c);</pre></ul>
+ *
+ * <p>where the class <code>Sample</code> is as follows:
+ *
+ * <ul><pre>public class Sample {
+ * public Object m(Object[] args, String msg) {
+ * System.out.println(msg);
+ * return null;
+ * }
+ * }</pre></ul>
+ *
+ * <p>This program produces the following class:
+ *
+ * <ul><pre>public class X extends Y {
+ * public X(int p0) {
+ * super(p0);
+ * String msg = "test";
+ * Object[] args = new Object[] { p0 };
+ * // begin of copied body
+ * System.out.println(msg);
+ * Object result = null;
+ * // end
+ * }
+ * }</pre></ul>
+ *
+ * @param parameters a list of the parameter types
+ * @param exceptions a list of the exceptions
+ * @param howto how to pass parameters to the super-class'
+ * constructor (<code>PASS_NONE</code>,
+ * <code>PASS_ARRAY</code>,
+ * or <code>PASS_PARAMS</code>)
+ * @param body appended body (may be <code>null</code>).
+ * It must be not a constructor but a method.
+ * @param cparam constant parameter (may be <code>null</code>.)
+ * @param declaring the class to which the created constructor
+ * is added.
+ *
+ * @see CtNewMethod#wrapped(CtClass,String,CtClass[],CtClass[],CtMethod,CtMethod.ConstParameter,CtClass)
+ */
+ public static CtConstructor make(CtClass[] parameters,
+ CtClass[] exceptions, int howto,
+ CtMethod body, ConstParameter cparam,
+ CtClass declaring)
+ throws CannotCompileException
+ {
+ return CtNewWrappedConstructor.wrapped(parameters, exceptions,
+ howto, body, cparam, declaring);
+ }
+}
diff --git a/src/main/javassist/CtNewMethod.java b/src/main/javassist/CtNewMethod.java
new file mode 100644
index 0000000..ec2a5fa
--- /dev/null
+++ b/src/main/javassist/CtNewMethod.java
@@ -0,0 +1,470 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist;
+
+import javassist.bytecode.*;
+import javassist.compiler.Javac;
+import javassist.compiler.CompileError;
+import javassist.CtMethod.ConstParameter;
+
+/**
+ * A collection of static methods for creating a <code>CtMethod</code>.
+ * An instance of this class does not make any sense.
+ *
+ * @see CtClass#addMethod(CtMethod)
+ */
+public class CtNewMethod {
+
+ /**
+ * Compiles the given source code and creates a method.
+ * The source code must include not only the method body
+ * but the whole declaration, for example,
+ *
+ * <ul><pre>"public Object id(Object obj) { return obj; }"</pre></ul>
+ *
+ * @param src the source text.
+ * @param declaring the class to which the created method is added.
+ */
+ public static CtMethod make(String src, CtClass declaring)
+ throws CannotCompileException
+ {
+ return make(src, declaring, null, null);
+ }
+
+ /**
+ * Compiles the given source code and creates a method.
+ * The source code must include not only the method body
+ * but the whole declaration, for example,
+ *
+ * <ul><pre>"public Object id(Object obj) { return obj; }"</pre></ul>
+ *
+ * <p>If the source code includes <code>$proceed()</code>, then
+ * it is compiled into a method call on the specified object.
+ *
+ * @param src the source text.
+ * @param declaring the class to which the created method is added.
+ * @param delegateObj the source text specifying the object
+ * that is called on by <code>$proceed()</code>.
+ * @param delegateMethod the name of the method
+ * that is called by <code>$proceed()</code>.
+ */
+ public static CtMethod make(String src, CtClass declaring,
+ String delegateObj, String delegateMethod)
+ throws CannotCompileException
+ {
+ Javac compiler = new Javac(declaring);
+ try {
+ if (delegateMethod != null)
+ compiler.recordProceed(delegateObj, delegateMethod);
+
+ CtMember obj = compiler.compile(src);
+ if (obj instanceof CtMethod)
+ return (CtMethod)obj;
+ }
+ catch (CompileError e) {
+ throw new CannotCompileException(e);
+ }
+
+ throw new CannotCompileException("not a method");
+ }
+
+ /**
+ * Creates a public (non-static) method. The created method cannot
+ * be changed to a static method later.
+ *
+ * @param returnType the type of the returned value.
+ * @param mname the method name.
+ * @param parameters a list of the parameter types.
+ * @param exceptions a list of the exception types.
+ * @param body the source text of the method body.
+ * It must be a block surrounded by <code>{}</code>.
+ * If it is <code>null</code>, the created method
+ * does nothing except returning zero or null.
+ * @param declaring the class to which the created method is added.
+ * @see #make(int, CtClass, String, CtClass[], CtClass[], String, CtClass)
+ */
+ public static CtMethod make(CtClass returnType,
+ String mname, CtClass[] parameters,
+ CtClass[] exceptions,
+ String body, CtClass declaring)
+ throws CannotCompileException
+ {
+ return make(Modifier.PUBLIC, returnType, mname, parameters, exceptions,
+ body, declaring);
+ }
+
+ /**
+ * Creates a method. <code>modifiers</code> can contain
+ * <code>Modifier.STATIC</code>.
+ *
+ * @param modifiers access modifiers.
+ * @param returnType the type of the returned value.
+ * @param mname the method name.
+ * @param parameters a list of the parameter types.
+ * @param exceptions a list of the exception types.
+ * @param body the source text of the method body.
+ * It must be a block surrounded by <code>{}</code>.
+ * If it is <code>null</code>, the created method
+ * does nothing except returning zero or null.
+ * @param declaring the class to which the created method is added.
+ *
+ * @see Modifier
+ */
+ public static CtMethod make(int modifiers, CtClass returnType,
+ String mname, CtClass[] parameters,
+ CtClass[] exceptions,
+ String body, CtClass declaring)
+ throws CannotCompileException
+ {
+ try {
+ CtMethod cm
+ = new CtMethod(returnType, mname, parameters, declaring);
+ cm.setModifiers(modifiers);
+ cm.setExceptionTypes(exceptions);
+ cm.setBody(body);
+ return cm;
+ }
+ catch (NotFoundException e) {
+ throw new CannotCompileException(e);
+ }
+ }
+
+ /**
+ * Creates a copy of a method. This method is provided for creating
+ * a new method based on an existing method.
+ * This is a convenience method for calling
+ * {@link CtMethod#CtMethod(CtMethod, CtClass, ClassMap) this constructor}.
+ * See the description of the constructor for particular behavior of the copying.
+ *
+ * @param src the source method.
+ * @param declaring the class to which the created method is added.
+ * @param map the hash table associating original class names
+ * with substituted names.
+ * It can be <code>null</code>.
+ *
+ * @see CtMethod#CtMethod(CtMethod,CtClass,ClassMap)
+ */
+ public static CtMethod copy(CtMethod src, CtClass declaring,
+ ClassMap map) throws CannotCompileException {
+ return new CtMethod(src, declaring, map);
+ }
+
+ /**
+ * Creates a copy of a method with a new name.
+ * This method is provided for creating
+ * a new method based on an existing method.
+ * This is a convenience method for calling
+ * {@link CtMethod#CtMethod(CtMethod, CtClass, ClassMap) this constructor}.
+ * See the description of the constructor for particular behavior of the copying.
+ *
+ * @param src the source method.
+ * @param name the name of the created method.
+ * @param declaring the class to which the created method is added.
+ * @param map the hash table associating original class names
+ * with substituted names.
+ * It can be <code>null</code>.
+ *
+ * @see CtMethod#CtMethod(CtMethod,CtClass,ClassMap)
+ */
+ public static CtMethod copy(CtMethod src, String name, CtClass declaring,
+ ClassMap map) throws CannotCompileException {
+ CtMethod cm = new CtMethod(src, declaring, map);
+ cm.setName(name);
+ return cm;
+ }
+
+ /**
+ * Creates a public abstract method.
+ *
+ * @param returnType the type of the returned value
+ * @param mname the method name
+ * @param parameters a list of the parameter types
+ * @param exceptions a list of the exception types
+ * @param declaring the class to which the created method is added.
+ *
+ * @see CtMethod#CtMethod(CtClass,String,CtClass[],CtClass)
+ */
+ public static CtMethod abstractMethod(CtClass returnType,
+ String mname,
+ CtClass[] parameters,
+ CtClass[] exceptions,
+ CtClass declaring)
+ throws NotFoundException
+ {
+ CtMethod cm = new CtMethod(returnType, mname, parameters, declaring);
+ cm.setExceptionTypes(exceptions);
+ return cm;
+ }
+
+ /**
+ * Creates a public getter method. The getter method returns the value
+ * of the specified field in the class to which this method is added.
+ * The created method is initially not static even if the field is
+ * static. Change the modifiers if the method should be static.
+ *
+ * @param methodName the name of the getter
+ * @param field the field accessed.
+ */
+ public static CtMethod getter(String methodName, CtField field)
+ throws CannotCompileException
+ {
+ FieldInfo finfo = field.getFieldInfo2();
+ String fieldType = finfo.getDescriptor();
+ String desc = "()" + fieldType;
+ ConstPool cp = finfo.getConstPool();
+ MethodInfo minfo = new MethodInfo(cp, methodName, desc);
+ minfo.setAccessFlags(AccessFlag.PUBLIC);
+
+ Bytecode code = new Bytecode(cp, 2, 1);
+ try {
+ String fieldName = finfo.getName();
+ if ((finfo.getAccessFlags() & AccessFlag.STATIC) == 0) {
+ code.addAload(0);
+ code.addGetfield(Bytecode.THIS, fieldName, fieldType);
+ }
+ else
+ code.addGetstatic(Bytecode.THIS, fieldName, fieldType);
+
+ code.addReturn(field.getType());
+ }
+ catch (NotFoundException e) {
+ throw new CannotCompileException(e);
+ }
+
+ minfo.setCodeAttribute(code.toCodeAttribute());
+ return new CtMethod(minfo, field.getDeclaringClass());
+ }
+
+ /**
+ * Creates a public setter method. The setter method assigns the
+ * value of the first parameter to the specified field
+ * in the class to which this method is added.
+ * The created method is not static even if the field is
+ * static. You may not change it to be static
+ * by <code>setModifiers()</code> in <code>CtBehavior</code>.
+ *
+ * @param methodName the name of the setter
+ * @param field the field accessed.
+ */
+ public static CtMethod setter(String methodName, CtField field)
+ throws CannotCompileException
+ {
+ FieldInfo finfo = field.getFieldInfo2();
+ String fieldType = finfo.getDescriptor();
+ String desc = "(" + fieldType + ")V";
+ ConstPool cp = finfo.getConstPool();
+ MethodInfo minfo = new MethodInfo(cp, methodName, desc);
+ minfo.setAccessFlags(AccessFlag.PUBLIC);
+
+ Bytecode code = new Bytecode(cp, 3, 3);
+ try {
+ String fieldName = finfo.getName();
+ if ((finfo.getAccessFlags() & AccessFlag.STATIC) == 0) {
+ code.addAload(0);
+ code.addLoad(1, field.getType());
+ code.addPutfield(Bytecode.THIS, fieldName, fieldType);
+ }
+ else {
+ code.addLoad(1, field.getType());
+ code.addPutstatic(Bytecode.THIS, fieldName, fieldType);
+ }
+
+ code.addReturn(null);
+ }
+ catch (NotFoundException e) {
+ throw new CannotCompileException(e);
+ }
+
+ minfo.setCodeAttribute(code.toCodeAttribute());
+ return new CtMethod(minfo, field.getDeclaringClass());
+ }
+
+ /**
+ * Creates a method forwarding to a delegate in
+ * a super class. The created method calls a method specified
+ * by <code>delegate</code> with all the parameters passed to the
+ * created method. If the delegate method returns a value,
+ * the created method returns that value to the caller.
+ * The delegate method must be declared in a super class.
+ *
+ * <p>The following method is an example of the created method.
+ *
+ * <ul><pre>int f(int p, int q) {
+ * return super.f(p, q);
+ * }</pre></ul>
+ *
+ * <p>The name of the created method can be changed by
+ * <code>setName()</code>.
+ *
+ * @param delegate the method that the created method forwards to.
+ * @param declaring the class to which the created method is
+ * added.
+ */
+ public static CtMethod delegator(CtMethod delegate, CtClass declaring)
+ throws CannotCompileException
+ {
+ try {
+ return delegator0(delegate, declaring);
+ }
+ catch (NotFoundException e) {
+ throw new CannotCompileException(e);
+ }
+ }
+
+ private static CtMethod delegator0(CtMethod delegate, CtClass declaring)
+ throws CannotCompileException, NotFoundException
+ {
+ MethodInfo deleInfo = delegate.getMethodInfo2();
+ String methodName = deleInfo.getName();
+ String desc = deleInfo.getDescriptor();
+ ConstPool cp = declaring.getClassFile2().getConstPool();
+ MethodInfo minfo = new MethodInfo(cp, methodName, desc);
+ minfo.setAccessFlags(deleInfo.getAccessFlags());
+
+ ExceptionsAttribute eattr = deleInfo.getExceptionsAttribute();
+ if (eattr != null)
+ minfo.setExceptionsAttribute(
+ (ExceptionsAttribute)eattr.copy(cp, null));
+
+ Bytecode code = new Bytecode(cp, 0, 0);
+ boolean isStatic = Modifier.isStatic(delegate.getModifiers());
+ CtClass deleClass = delegate.getDeclaringClass();
+ CtClass[] params = delegate.getParameterTypes();
+ int s;
+ if (isStatic) {
+ s = code.addLoadParameters(params, 0);
+ code.addInvokestatic(deleClass, methodName, desc);
+ }
+ else {
+ code.addLoad(0, deleClass);
+ s = code.addLoadParameters(params, 1);
+ code.addInvokespecial(deleClass, methodName, desc);
+ }
+
+ code.addReturn(delegate.getReturnType());
+ code.setMaxLocals(++s);
+ code.setMaxStack(s < 2 ? 2 : s); // for a 2-word return value
+ minfo.setCodeAttribute(code.toCodeAttribute());
+ return new CtMethod(minfo, declaring);
+ }
+
+ /**
+ * Creates a wrapped method. The wrapped method receives parameters
+ * in the form of an array of <code>Object</code>.
+ *
+ * <p>The body of the created method is a copy of the body of the method
+ * specified by <code>body</code>. However, it is wrapped in
+ * parameter-conversion code.
+ *
+ * <p>The method specified by <code>body</code> must have this singature:
+ *
+ * <ul><code>Object method(Object[] params, <type> cvalue)
+ * </code></ul>
+ *
+ * <p>The type of the <code>cvalue</code> depends on
+ * <code>constParam</code>.
+ * If <code>constParam</code> is <code>null</code>, the signature
+ * must be:
+ *
+ * <ul><code>Object method(Object[] params)</code></ul>
+ *
+ * <p>The method body copied from <code>body</code> is wrapped in
+ * parameter-conversion code, which converts parameters specified by
+ * <code>parameterTypes</code> into an array of <code>Object</code>.
+ * The returned value is also converted from the <code>Object</code>
+ * type to the type specified by <code>returnType</code>. Thus,
+ * the resulting method body is as follows:
+ *
+ * <ul><pre>Object[] params = new Object[] { p0, p1, ... };
+ * <<i>type</i>> cvalue = <<i>constant-value</i>>;
+ * <i>... copied method body ...</i>
+ * Object result = <<i>returned value</i>>
+ * return (<i><returnType></i>)result;
+ * </pre></ul>
+ *
+ * <p>The variables <code>p0</code>, <code>p2</code>, ... represent
+ * formal parameters of the created method.
+ * The value of <code>cvalue</code> is specified by
+ * <code>constParam</code>.
+ *
+ * <p>If the type of a parameter or a returned value is a primitive
+ * type, then the value is converted into a wrapper object such as
+ * <code>java.lang.Integer</code>. If the type of the returned value
+ * is <code>void</code>, the returned value is discarded.
+ *
+ * <p><i>Example:</i>
+ *
+ * <ul><pre>ClassPool pool = ... ;
+ * CtClass vec = pool.makeClass("intVector");
+ * vec.setSuperclass(pool.get("java.util.Vector"));
+ * CtMethod addMethod = pool.getMethod("Sample", "add0");
+ *
+ * CtClass[] argTypes = { CtClass.intType };
+ * CtMethod m = CtNewMethod.wrapped(CtClass.voidType, "add", argTypes,
+ * null, addMethod, null, vec);
+ * vec.addMethod(m);</pre></ul>
+ *
+ * <p>where the class <code>Sample</code> is as follows:
+ *
+ * <ul><pre>public class Sample extends java.util.Vector {
+ * public Object add0(Object[] args) {
+ * super.addElement(args[0]);
+ * return null;
+ * }
+ * }</pre></ul>
+ *
+ * <p>This program produces a class <code>intVector</code>:
+ *
+ * <ul><pre>public class intVector extends java.util.Vector {
+ * public void add(int p0) {
+ * Object[] args = new Object[] { p0 };
+ * // begin of the copied body
+ * super.addElement(args[0]);
+ * Object result = null;
+ * // end
+ * }
+ * }</pre></ul>
+ *
+ * <p>Note that the type of the parameter to <code>add()</code> depends
+ * only on the value of <code>argTypes</code> passed to
+ * <code>CtNewMethod.wrapped()</code>. Thus, it is easy to
+ * modify this program to produce a
+ * <code>StringVector</code> class, which is a vector containing
+ * only <code>String</code> objects, and other vector classes.
+ *
+ * @param returnType the type of the returned value.
+ * @param mname the method name.
+ * @param parameterTypes a list of the parameter types.
+ * @param exceptionTypes a list of the exception types.
+ * @param body the method body
+ * (must not be a static method).
+ * @param constParam the constant parameter
+ * (maybe <code>null</code>).
+ * @param declaring the class to which the created method is
+ * added.
+ */
+ public static CtMethod wrapped(CtClass returnType,
+ String mname,
+ CtClass[] parameterTypes,
+ CtClass[] exceptionTypes,
+ CtMethod body, ConstParameter constParam,
+ CtClass declaring)
+ throws CannotCompileException
+ {
+ return CtNewWrappedMethod.wrapped(returnType, mname, parameterTypes,
+ exceptionTypes, body, constParam, declaring);
+ }
+}
diff --git a/src/main/javassist/CtNewNestedClass.java b/src/main/javassist/CtNewNestedClass.java
new file mode 100644
index 0000000..3611443
--- /dev/null
+++ b/src/main/javassist/CtNewNestedClass.java
@@ -0,0 +1,66 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist;
+
+import javassist.bytecode.ClassFile;
+import javassist.bytecode.AccessFlag;
+import javassist.bytecode.InnerClassesAttribute;
+
+/**
+ * A newly created public nested class.
+ */
+class CtNewNestedClass extends CtNewClass {
+ CtNewNestedClass(String realName, ClassPool cp, boolean isInterface,
+ CtClass superclass) {
+ super(realName, cp, isInterface, superclass);
+ }
+
+ /**
+ * This method does not change the STATIC bit. The original value is kept.
+ */
+ public void setModifiers(int mod) {
+ mod = mod & ~Modifier.STATIC;
+ super.setModifiers(mod);
+ updateInnerEntry(mod, getName(), this, true);
+ }
+
+ private static void updateInnerEntry(int mod, String name, CtClass clazz, boolean outer) {
+ ClassFile cf = clazz.getClassFile2();
+ InnerClassesAttribute ica = (InnerClassesAttribute)cf.getAttribute(
+ InnerClassesAttribute.tag);
+ if (ica == null)
+ return;
+
+ int n = ica.tableLength();
+ for (int i = 0; i < n; i++)
+ if (name.equals(ica.innerClass(i))) {
+ int acc = ica.accessFlags(i) & AccessFlag.STATIC;
+ ica.setAccessFlags(i, mod | acc);
+ String outName = ica.outerClass(i);
+ if (outName != null && outer)
+ try {
+ CtClass parent = clazz.getClassPool().get(outName);
+ updateInnerEntry(mod, name, parent, false);
+ }
+ catch (NotFoundException e) {
+ throw new RuntimeException("cannot find the declaring class: "
+ + outName);
+ }
+
+ break;
+ }
+ }
+}
diff --git a/src/main/javassist/CtNewWrappedConstructor.java b/src/main/javassist/CtNewWrappedConstructor.java
new file mode 100644
index 0000000..78ab399
--- /dev/null
+++ b/src/main/javassist/CtNewWrappedConstructor.java
@@ -0,0 +1,101 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist;
+
+import javassist.bytecode.*;
+import javassist.CtMethod.ConstParameter;
+
+class CtNewWrappedConstructor extends CtNewWrappedMethod {
+ private static final int PASS_NONE = CtNewConstructor.PASS_NONE;
+ // private static final int PASS_ARRAY = CtNewConstructor.PASS_ARRAY;
+ private static final int PASS_PARAMS = CtNewConstructor.PASS_PARAMS;
+
+ public static CtConstructor wrapped(CtClass[] parameterTypes,
+ CtClass[] exceptionTypes,
+ int howToCallSuper,
+ CtMethod body,
+ ConstParameter constParam,
+ CtClass declaring)
+ throws CannotCompileException
+ {
+ try {
+ CtConstructor cons = new CtConstructor(parameterTypes, declaring);
+ cons.setExceptionTypes(exceptionTypes);
+ Bytecode code = makeBody(declaring, declaring.getClassFile2(),
+ howToCallSuper, body,
+ parameterTypes, constParam);
+ cons.getMethodInfo2().setCodeAttribute(code.toCodeAttribute());
+ return cons;
+ }
+ catch (NotFoundException e) {
+ throw new CannotCompileException(e);
+ }
+ }
+
+ protected static Bytecode makeBody(CtClass declaring, ClassFile classfile,
+ int howToCallSuper,
+ CtMethod wrappedBody,
+ CtClass[] parameters,
+ ConstParameter cparam)
+ throws CannotCompileException
+ {
+ int stacksize, stacksize2;
+
+ int superclazz = classfile.getSuperclassId();
+ Bytecode code = new Bytecode(classfile.getConstPool(), 0, 0);
+ code.setMaxLocals(false, parameters, 0);
+ code.addAload(0);
+ if (howToCallSuper == PASS_NONE) {
+ stacksize = 1;
+ code.addInvokespecial(superclazz, "<init>", "()V");
+ }
+ else if (howToCallSuper == PASS_PARAMS) {
+ stacksize = code.addLoadParameters(parameters, 1) + 1;
+ code.addInvokespecial(superclazz, "<init>",
+ Descriptor.ofConstructor(parameters));
+ }
+ else {
+ stacksize = compileParameterList(code, parameters, 1);
+ String desc;
+ if (cparam == null) {
+ stacksize2 = 2;
+ desc = ConstParameter.defaultConstDescriptor();
+ }
+ else {
+ stacksize2 = cparam.compile(code) + 2;
+ desc = cparam.constDescriptor();
+ }
+
+ if (stacksize < stacksize2)
+ stacksize = stacksize2;
+
+ code.addInvokespecial(superclazz, "<init>", desc);
+ }
+
+ if (wrappedBody == null)
+ code.add(Bytecode.RETURN);
+ else {
+ stacksize2 = makeBody0(declaring, classfile, wrappedBody,
+ false, parameters, CtClass.voidType,
+ cparam, code);
+ if (stacksize < stacksize2)
+ stacksize = stacksize2;
+ }
+
+ code.setMaxStack(stacksize);
+ return code;
+ }
+}
diff --git a/src/main/javassist/CtNewWrappedMethod.java b/src/main/javassist/CtNewWrappedMethod.java
new file mode 100644
index 0000000..94cabd6
--- /dev/null
+++ b/src/main/javassist/CtNewWrappedMethod.java
@@ -0,0 +1,196 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist;
+
+import javassist.bytecode.*;
+import javassist.compiler.JvstCodeGen;
+import java.util.Hashtable;
+import javassist.CtMethod.ConstParameter;
+
+class CtNewWrappedMethod {
+
+ private static final String addedWrappedMethod = "_added_m$";
+
+ public static CtMethod wrapped(CtClass returnType, String mname,
+ CtClass[] parameterTypes,
+ CtClass[] exceptionTypes,
+ CtMethod body, ConstParameter constParam,
+ CtClass declaring)
+ throws CannotCompileException
+ {
+ CtMethod mt = new CtMethod(returnType, mname, parameterTypes,
+ declaring);
+ mt.setModifiers(body.getModifiers());
+ try {
+ mt.setExceptionTypes(exceptionTypes);
+ }
+ catch (NotFoundException e) {
+ throw new CannotCompileException(e);
+ }
+
+ Bytecode code = makeBody(declaring, declaring.getClassFile2(), body,
+ parameterTypes, returnType, constParam);
+ mt.getMethodInfo2().setCodeAttribute(code.toCodeAttribute());
+ return mt;
+ }
+
+ static Bytecode makeBody(CtClass clazz, ClassFile classfile,
+ CtMethod wrappedBody,
+ CtClass[] parameters,
+ CtClass returnType,
+ ConstParameter cparam)
+ throws CannotCompileException
+ {
+ boolean isStatic = Modifier.isStatic(wrappedBody.getModifiers());
+ Bytecode code = new Bytecode(classfile.getConstPool(), 0, 0);
+ int stacksize = makeBody0(clazz, classfile, wrappedBody, isStatic,
+ parameters, returnType, cparam, code);
+ code.setMaxStack(stacksize);
+ code.setMaxLocals(isStatic, parameters, 0);
+ return code;
+ }
+
+ /* The generated method body does not need a stack map table
+ * because it does not contain a branch instruction.
+ */
+ protected static int makeBody0(CtClass clazz, ClassFile classfile,
+ CtMethod wrappedBody,
+ boolean isStatic, CtClass[] parameters,
+ CtClass returnType, ConstParameter cparam,
+ Bytecode code)
+ throws CannotCompileException
+ {
+ if (!(clazz instanceof CtClassType))
+ throw new CannotCompileException("bad declaring class"
+ + clazz.getName());
+
+ if (!isStatic)
+ code.addAload(0);
+
+ int stacksize = compileParameterList(code, parameters,
+ (isStatic ? 0 : 1));
+ int stacksize2;
+ String desc;
+ if (cparam == null) {
+ stacksize2 = 0;
+ desc = ConstParameter.defaultDescriptor();
+ }
+ else {
+ stacksize2 = cparam.compile(code);
+ desc = cparam.descriptor();
+ }
+
+ checkSignature(wrappedBody, desc);
+
+ String bodyname;
+ try {
+ bodyname = addBodyMethod((CtClassType)clazz, classfile,
+ wrappedBody);
+ /* if an exception is thrown below, the method added above
+ * should be removed. (future work :<)
+ */
+ }
+ catch (BadBytecode e) {
+ throw new CannotCompileException(e);
+ }
+
+ if (isStatic)
+ code.addInvokestatic(Bytecode.THIS, bodyname, desc);
+ else
+ code.addInvokespecial(Bytecode.THIS, bodyname, desc);
+
+ compileReturn(code, returnType); // consumes 2 stack entries
+
+ if (stacksize < stacksize2 + 2)
+ stacksize = stacksize2 + 2;
+
+ return stacksize;
+ }
+
+ private static void checkSignature(CtMethod wrappedBody,
+ String descriptor)
+ throws CannotCompileException
+ {
+ if (!descriptor.equals(wrappedBody.getMethodInfo2().getDescriptor()))
+ throw new CannotCompileException(
+ "wrapped method with a bad signature: "
+ + wrappedBody.getDeclaringClass().getName()
+ + '.' + wrappedBody.getName());
+ }
+
+ private static String addBodyMethod(CtClassType clazz,
+ ClassFile classfile,
+ CtMethod src)
+ throws BadBytecode, CannotCompileException
+ {
+ Hashtable bodies = clazz.getHiddenMethods();
+ String bodyname = (String)bodies.get(src);
+ if (bodyname == null) {
+ do {
+ bodyname = addedWrappedMethod + clazz.getUniqueNumber();
+ } while (classfile.getMethod(bodyname) != null);
+ ClassMap map = new ClassMap();
+ map.put(src.getDeclaringClass().getName(), clazz.getName());
+ MethodInfo body = new MethodInfo(classfile.getConstPool(),
+ bodyname, src.getMethodInfo2(),
+ map);
+ int acc = body.getAccessFlags();
+ body.setAccessFlags(AccessFlag.setPrivate(acc));
+ body.addAttribute(new SyntheticAttribute(classfile.getConstPool()));
+ // a stack map is copied. rebuilding it is not needed.
+ classfile.addMethod(body);
+ bodies.put(src, bodyname);
+ CtMember.Cache cache = clazz.hasMemberCache();
+ if (cache != null)
+ cache.addMethod(new CtMethod(body, clazz));
+ }
+
+ return bodyname;
+ }
+
+ /* compileParameterList() returns the stack size used
+ * by the produced code.
+ *
+ * @param regno the index of the local variable in which
+ * the first argument is received.
+ * (0: static method, 1: regular method.)
+ */
+ static int compileParameterList(Bytecode code,
+ CtClass[] params, int regno) {
+ return JvstCodeGen.compileParameterList(code, params, regno);
+ }
+
+ /*
+ * The produced codes cosume 1 or 2 stack entries.
+ */
+ private static void compileReturn(Bytecode code, CtClass type) {
+ if (type.isPrimitive()) {
+ CtPrimitiveType pt = (CtPrimitiveType)type;
+ if (pt != CtClass.voidType) {
+ String wrapper = pt.getWrapperName();
+ code.addCheckcast(wrapper);
+ code.addInvokevirtual(wrapper, pt.getGetMethodName(),
+ pt.getGetMethodDescriptor());
+ }
+
+ code.addOpcode(pt.getReturnOp());
+ }
+ else {
+ code.addCheckcast(type);
+ code.addOpcode(Bytecode.ARETURN);
+ }
+ }
+}
diff --git a/src/main/javassist/CtPrimitiveType.java b/src/main/javassist/CtPrimitiveType.java
new file mode 100644
index 0000000..faf6700
--- /dev/null
+++ b/src/main/javassist/CtPrimitiveType.java
@@ -0,0 +1,111 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist;
+
+/**
+ * An instance of <code>CtPrimitiveType</code> represents a primitive type.
+ * It is obtained from <code>CtClass</code>.
+ */
+public final class CtPrimitiveType extends CtClass {
+ private char descriptor;
+ private String wrapperName;
+ private String getMethodName;
+ private String mDescriptor;
+ private int returnOp;
+ private int arrayType;
+ private int dataSize;
+
+ CtPrimitiveType(String name, char desc, String wrapper,
+ String methodName, String mDesc, int opcode, int atype,
+ int size) {
+ super(name);
+ descriptor = desc;
+ wrapperName = wrapper;
+ getMethodName = methodName;
+ mDescriptor = mDesc;
+ returnOp = opcode;
+ arrayType = atype;
+ dataSize = size;
+ }
+
+ /**
+ * Returns <code>true</code> if this object represents a primitive
+ * Java type: boolean, byte, char, short, int, long, float, double,
+ * or void.
+ */
+ public boolean isPrimitive() { return true; }
+
+ /**
+ * Returns the modifiers for this type.
+ * For decoding, use <code>javassist.Modifier</code>.
+ *
+ * @see Modifier
+ */
+ public int getModifiers() {
+ return Modifier.PUBLIC | Modifier.FINAL;
+ }
+
+ /**
+ * Returns the descriptor representing this type.
+ * For example, if the type is int, then the descriptor is I.
+ */
+ public char getDescriptor() { return descriptor; }
+
+ /**
+ * Returns the name of the wrapper class.
+ * For example, if the type is int, then the wrapper class is
+ * <code>java.lang.Integer</code>.
+ */
+ public String getWrapperName() { return wrapperName; }
+
+ /**
+ * Returns the name of the method for retrieving the value
+ * from the wrapper object.
+ * For example, if the type is int, then the method name is
+ * <code>intValue</code>.
+ */
+ public String getGetMethodName() { return getMethodName; }
+
+ /**
+ * Returns the descriptor of the method for retrieving the value
+ * from the wrapper object.
+ * For example, if the type is int, then the method descriptor is
+ * <code>()I</code>.
+ */
+ public String getGetMethodDescriptor() { return mDescriptor; }
+
+ /**
+ * Returns the opcode for returning a value of the type.
+ * For example, if the type is int, then the returned opcode is
+ * <code>javassit.bytecode.Opcode.IRETURN</code>.
+ */
+ public int getReturnOp() { return returnOp; }
+
+ /**
+ * Returns the array-type code representing the type.
+ * It is used for the newarray instruction.
+ * For example, if the type is int, then this method returns
+ * <code>javassit.bytecode.Opcode.T_INT</code>.
+ */
+ public int getArrayType() { return arrayType; }
+
+ /**
+ * Returns the data size of the primitive type.
+ * If the type is long or double, this method returns 2.
+ * Otherwise, it returns 1.
+ */
+ public int getDataSize() { return dataSize; }
+}
diff --git a/src/main/javassist/Loader.java b/src/main/javassist/Loader.java
new file mode 100644
index 0000000..160ef6e
--- /dev/null
+++ b/src/main/javassist/Loader.java
@@ -0,0 +1,446 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist;
+
+import java.io.*;
+import java.util.Hashtable;
+import java.util.Vector;
+import java.security.ProtectionDomain;
+
+/**
+ * The class loader for Javassist.
+ *
+ * <p>This is a sample class loader using <code>ClassPool</code>.
+ * Unlike a regular class loader, this class loader obtains bytecode
+ * from a <code>ClassPool</code>.
+ *
+ * <p>Note that Javassist can be used without this class loader; programmers
+ * can define their own versions of class loader. They can run
+ * a program even without any user-defined class loader if that program
+ * is statically translated with Javassist.
+ * This class loader is just provided as a utility class.
+ *
+ * <p>Suppose that an instance of <code>MyTranslator</code> implementing
+ * the interface <code>Translator</code> is responsible for modifying
+ * class files.
+ * The startup program of an application using <code>MyTranslator</code>
+ * should be something like this:
+ *
+ * <ul><pre>
+ * import javassist.*;
+ *
+ * public class Main {
+ * public static void main(String[] args) throws Throwable {
+ * MyTranslator myTrans = new MyTranslator();
+ * ClassPool cp = ClassPool.getDefault();
+ * Loader cl = new Loader(cp);
+ * cl.addTranslator(cp, myTrans);
+ * cl.run("MyApp", args);
+ * }
+ * }
+ * </pre></ul>
+ *
+ * <p>Class <code>MyApp</code> is the main program of the application.
+ *
+ * <p>This program should be executed as follows:
+ *
+ * <ul><pre>
+ * % java Main <i>arg1</i> <i>arg2</i>...
+ * </pre></ul>
+ *
+ * <p>It modifies the class <code>MyApp</code> with a <code>MyTranslator</code>
+ * object before the JVM loads it.
+ * Then it calls <code>main()</code> in <code>MyApp</code> with arguments
+ * <i>arg1</i>, <i>arg2</i>, ...
+ *
+ * <p>This program execution is equivalent to:
+ *
+ * <ul><pre>
+ * % java MyApp <i>arg1</i> <i>arg2</i>...
+ * </pre></ul>
+ *
+ * <p>except that classes are translated by <code>MyTranslator</code>
+ * at load time.
+ *
+ * <p>If only a particular class must be modified when it is loaded,
+ * the startup program can be simpler; <code>MyTranslator</code> is
+ * unnecessary. For example, if only a class <code>test.Rectangle</code>
+ * is modified, the <code>main()</code> method above will be the following:
+ *
+ * <ul><pre>
+ * ClassPool cp = ClassPool.getDefault();
+ * Loader cl = new Loader(cp);
+ * CtClass ct = cp.get("test.Rectangle");
+ * ct.setSuperclass(cp.get("test.Point"));
+ * cl.run("MyApp", args);</pre></ul>
+ *
+ * <p>This program changes the super class of the <code>test.Rectangle</code>
+ * class.
+ *
+ * <p><b>Note 1:</b>
+ *
+ * <p>This class loader does not allow the users to intercept the loading
+ * of <code>java.*</code> and <code>javax.*</code> classes (and
+ * <code>sun.*</code>, <code>org.xml.*</code>, ...) unless
+ * <code>Loader.doDelegation</code> is <code>false</code>. This is because
+ * the JVM prohibits a user class loader from loading a system class.
+ * Also see Note 2.
+ * If this behavior is not appropriate, a subclass of <code>Loader</code>
+ * must be defined and <code>loadClassByDelegation()</code> must be overridden.
+ *
+ * <p><b>Note 2:</b>
+ *
+ * <p>If classes are loaded with different class loaders, they belong to
+ * separate name spaces. If class <code>C</code> is loaded by a class
+ * loader <code>CL</code>, all classes that the class <code>C</code>
+ * refers to are also loaded by <code>CL</code>. However, if <code>CL</code>
+ * delegates the loading of the class <code>C</code> to <code>CL'</code>,
+ * then those classes that the class <code>C</code> refers to
+ * are loaded by a parent class loader <code>CL'</code>
+ * instead of <code>CL</code>.
+ *
+ * <p>If an object of class <code>C</code> is assigned
+ * to a variable of class <code>C</code> belonging to a different name
+ * space, then a <code>ClassCastException</code> is thrown.
+ *
+ * <p>Because of the fact above, this loader delegates only the loading of
+ * <code>javassist.Loader</code>
+ * and classes included in package <code>java.*</code> and
+ * <code>javax.*</code> to the parent class
+ * loader. Other classes are directly loaded by this loader.
+ *
+ * <p>For example, suppose that <code>java.lang.String</code> would be loaded
+ * by this loader while <code>java.io.File</code> is loaded by the parent
+ * class loader. If the constructor of <code>java.io.File</code> is called
+ * with an instance of <code>java.lang.String</code>, then it may throw
+ * an exception since it accepts an instance of only the
+ * <code>java.lang.String</code> loaded by the parent class loader.
+ *
+ * @see javassist.ClassPool
+ * @see javassist.Translator
+ */
+public class Loader extends ClassLoader {
+ private Hashtable notDefinedHere; // must be atomic.
+ private Vector notDefinedPackages; // must be atomic.
+ private ClassPool source;
+ private Translator translator;
+ private ProtectionDomain domain;
+
+ /**
+ * Specifies the algorithm of class loading.
+ *
+ * <p>This class loader uses the parent class loader for
+ * <code>java.*</code> and <code>javax.*</code> classes.
+ * If this variable <code>doDelegation</code>
+ * is <code>false</code>, this class loader does not delegate those
+ * classes to the parent class loader.
+ *
+ * <p>The default value is <code>true</code>.
+ */
+ public boolean doDelegation = true;
+
+ /**
+ * Creates a new class loader.
+ */
+ public Loader() {
+ this(null);
+ }
+
+ /**
+ * Creates a new class loader.
+ *
+ * @param cp the source of class files.
+ */
+ public Loader(ClassPool cp) {
+ init(cp);
+ }
+
+ /**
+ * Creates a new class loader
+ * using the specified parent class loader for delegation.
+ *
+ * @param parent the parent class loader.
+ * @param cp the source of class files.
+ */
+ public Loader(ClassLoader parent, ClassPool cp) {
+ super(parent);
+ init(cp);
+ }
+
+ private void init(ClassPool cp) {
+ notDefinedHere = new Hashtable();
+ notDefinedPackages = new Vector();
+ source = cp;
+ translator = null;
+ domain = null;
+ delegateLoadingOf("javassist.Loader");
+ }
+
+ /**
+ * Records a class so that the loading of that class is delegated
+ * to the parent class loader.
+ *
+ * <p>If the given class name ends with <code>.</code> (dot), then
+ * that name is interpreted as a package name. All the classes
+ * in that package and the sub packages are delegated.
+ */
+ public void delegateLoadingOf(String classname) {
+ if (classname.endsWith("."))
+ notDefinedPackages.addElement(classname);
+ else
+ notDefinedHere.put(classname, this);
+ }
+
+ /**
+ * Sets the protection domain for the classes handled by this class
+ * loader. Without registering an appropriate protection domain,
+ * the program loaded by this loader will not work with a security
+ * manager or a signed jar file.
+ */
+ public void setDomain(ProtectionDomain d) {
+ domain = d;
+ }
+
+ /**
+ * Sets the soruce <code>ClassPool</code>.
+ */
+ public void setClassPool(ClassPool cp) {
+ source = cp;
+ }
+
+ /**
+ * Adds a translator, which is called whenever a class is loaded.
+ *
+ * @param cp the <code>ClassPool</code> object for obtaining
+ * a class file.
+ * @param t a translator.
+ * @throws NotFoundException if <code>t.start()</code> throws an exception.
+ * @throws CannotCompileException if <code>t.start()</code> throws an exception.
+ */
+ public void addTranslator(ClassPool cp, Translator t)
+ throws NotFoundException, CannotCompileException {
+ source = cp;
+ translator = t;
+ t.start(cp);
+ }
+
+ /**
+ * Loads a class with an instance of <code>Loader</code>
+ * and calls <code>main()</code> of that class.
+ *
+ * <p>This method calls <code>run()</code>.
+ *
+ * @param args command line parameters.
+ * <ul>
+ * <code>args[0]</code> is the class name to be loaded.
+ * <br><code>args[1..n]</code> are parameters passed
+ * to the target <code>main()</code>.
+ * </ul>
+ *
+ * @see javassist.Loader#run(String[])
+ */
+ public static void main(String[] args) throws Throwable {
+ Loader cl = new Loader();
+ cl.run(args);
+ }
+
+ /**
+ * Loads a class and calls <code>main()</code> in that class.
+ *
+ * @param args command line parameters.
+ * <ul>
+ * <code>args[0]</code> is the class name to be loaded.
+ * <br><code>args[1..n]</code> are parameters passed
+ * to the target <code>main()</code>.
+ * </ul>
+ */
+ public void run(String[] args) throws Throwable {
+ int n = args.length - 1;
+ if (n >= 0) {
+ String[] args2 = new String[n];
+ for (int i = 0; i < n; ++i)
+ args2[i] = args[i + 1];
+
+ run(args[0], args2);
+ }
+ }
+
+ /**
+ * Loads a class and calls <code>main()</code> in that class.
+ *
+ * @param classname the loaded class.
+ * @param args parameters passed to <code>main()</code>.
+ */
+ public void run(String classname, String[] args) throws Throwable {
+ Class c = loadClass(classname);
+ try {
+ c.getDeclaredMethod("main", new Class[] { String[].class }).invoke(
+ null,
+ new Object[] { args });
+ }
+ catch (java.lang.reflect.InvocationTargetException e) {
+ throw e.getTargetException();
+ }
+ }
+
+ /**
+ * Requests the class loader to load a class.
+ */
+ protected Class loadClass(String name, boolean resolve)
+ throws ClassFormatError, ClassNotFoundException {
+ name = name.intern();
+ synchronized (name) {
+ Class c = findLoadedClass(name);
+ if (c == null)
+ c = loadClassByDelegation(name);
+
+ if (c == null)
+ c = findClass(name);
+
+ if (c == null)
+ c = delegateToParent(name);
+
+ if (resolve)
+ resolveClass(c);
+
+ return c;
+ }
+ }
+
+ /**
+ * Finds the specified class using <code>ClassPath</code>.
+ * If the source throws an exception, this returns null.
+ *
+ * <p>This method can be overridden by a subclass of
+ * <code>Loader</code>. Note that the overridden method must not throw
+ * an exception when it just fails to find a class file.
+ *
+ * @return null if the specified class could not be found.
+ * @throws ClassNotFoundException if an exception is thrown while
+ * obtaining a class file.
+ */
+ protected Class findClass(String name) throws ClassNotFoundException {
+ byte[] classfile;
+ try {
+ if (source != null) {
+ if (translator != null)
+ translator.onLoad(source, name);
+
+ try {
+ classfile = source.get(name).toBytecode();
+ }
+ catch (NotFoundException e) {
+ return null;
+ }
+ }
+ else {
+ String jarname = "/" + name.replace('.', '/') + ".class";
+ InputStream in = this.getClass().getResourceAsStream(jarname);
+ if (in == null)
+ return null;
+
+ classfile = ClassPoolTail.readStream(in);
+ }
+ }
+ catch (Exception e) {
+ throw new ClassNotFoundException(
+ "caught an exception while obtaining a class file for "
+ + name, e);
+ }
+
+ int i = name.lastIndexOf('.');
+ if (i != -1) {
+ String pname = name.substring(0, i);
+ if (getPackage(pname) == null)
+ try {
+ definePackage(
+ pname, null, null, null, null, null, null, null);
+ }
+ catch (IllegalArgumentException e) {
+ // ignore. maybe the package object for the same
+ // name has been created just right away.
+ }
+ }
+
+ if (domain == null)
+ return defineClass(name, classfile, 0, classfile.length);
+ else
+ return defineClass(name, classfile, 0, classfile.length, domain);
+ }
+
+ protected Class loadClassByDelegation(String name)
+ throws ClassNotFoundException
+ {
+ /* The swing components must be loaded by a system
+ * class loader.
+ * javax.swing.UIManager loads a (concrete) subclass
+ * of LookAndFeel by a system class loader and cast
+ * an instance of the class to LookAndFeel for
+ * (maybe) a security reason. To avoid failure of
+ * type conversion, LookAndFeel must not be loaded
+ * by this class loader.
+ */
+
+ Class c = null;
+ if (doDelegation)
+ if (name.startsWith("java.")
+ || name.startsWith("javax.")
+ || name.startsWith("sun.")
+ || name.startsWith("com.sun.")
+ || name.startsWith("org.w3c.")
+ || name.startsWith("org.xml.")
+ || notDelegated(name))
+ c = delegateToParent(name);
+
+ return c;
+ }
+
+ private boolean notDelegated(String name) {
+ if (notDefinedHere.get(name) != null)
+ return true;
+
+ int n = notDefinedPackages.size();
+ for (int i = 0; i < n; ++i)
+ if (name.startsWith((String)notDefinedPackages.elementAt(i)))
+ return true;
+
+ return false;
+ }
+
+ protected Class delegateToParent(String classname)
+ throws ClassNotFoundException
+ {
+ ClassLoader cl = getParent();
+ if (cl != null)
+ return cl.loadClass(classname);
+ else
+ return findSystemClass(classname);
+ }
+
+ protected Package getPackage(String name) {
+ return super.getPackage(name);
+ }
+ /*
+ // Package p = super.getPackage(name);
+ Package p = null;
+ if (p == null)
+ return definePackage(name, null, null, null,
+ null, null, null, null);
+ else
+ return p;
+ }
+ */
+}
diff --git a/src/main/javassist/LoaderClassPath.java b/src/main/javassist/LoaderClassPath.java
new file mode 100644
index 0000000..5ecb02e
--- /dev/null
+++ b/src/main/javassist/LoaderClassPath.java
@@ -0,0 +1,95 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist;
+
+import java.io.InputStream;
+import java.net.URL;
+import java.lang.ref.WeakReference;
+
+/**
+ * A class search-path representing a class loader.
+ *
+ * <p>It is used for obtaining a class file from the given
+ * class loader by <code>getResourceAsStream()</code>.
+ * The <code>LoaderClassPath</code> refers to the class loader through
+ * <code>WeakReference</code>. If the class loader is garbage collected,
+ * the other search pathes are examined.
+ *
+ * <p>The given class loader must have both <code>getResourceAsStream()</code>
+ * and <code>getResource()</code>.
+ *
+ * @author <a href="mailto:bill@jboss.org">Bill Burke</a>
+ * @author Shigeru Chiba
+ *
+ * @see ClassPool#insertClassPath(ClassPath)
+ * @see ClassPool#appendClassPath(ClassPath)
+ * @see ClassClassPath
+ */
+public class LoaderClassPath implements ClassPath {
+ private WeakReference clref;
+
+ /**
+ * Creates a search path representing a class loader.
+ */
+ public LoaderClassPath(ClassLoader cl) {
+ clref = new WeakReference(cl);
+ }
+
+ public String toString() {
+ Object cl = null;
+ if (clref != null)
+ cl = clref.get();
+
+ return cl == null ? "<null>" : cl.toString();
+ }
+
+ /**
+ * Obtains a class file from the class loader.
+ * This method calls <code>getResourceAsStream(String)</code>
+ * on the class loader.
+ */
+ public InputStream openClassfile(String classname) {
+ String cname = classname.replace('.', '/') + ".class";
+ ClassLoader cl = (ClassLoader)clref.get();
+ if (cl == null)
+ return null; // not found
+ else
+ return cl.getResourceAsStream(cname);
+ }
+
+ /**
+ * Obtains the URL of the specified class file.
+ * This method calls <code>getResource(String)</code>
+ * on the class loader.
+ *
+ * @return null if the class file could not be found.
+ */
+ public URL find(String classname) {
+ String cname = classname.replace('.', '/') + ".class";
+ ClassLoader cl = (ClassLoader)clref.get();
+ if (cl == null)
+ return null; // not found
+ else
+ return cl.getResource(cname);
+ }
+
+ /**
+ * Closes this class path.
+ */
+ public void close() {
+ clref = null;
+ }
+}
diff --git a/src/main/javassist/Modifier.java b/src/main/javassist/Modifier.java
new file mode 100644
index 0000000..c1b30d6
--- /dev/null
+++ b/src/main/javassist/Modifier.java
@@ -0,0 +1,218 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist;
+
+import javassist.bytecode.AccessFlag;
+
+/**
+ * The Modifier class provides static methods and constants to decode
+ * class and member access modifiers. The constant values are equivalent
+ * to the corresponding values in <code>javassist.bytecode.AccessFlag</code>.
+ *
+ * <p>All the methods/constants in this class are compatible with
+ * ones in <code>java.lang.reflect.Modifier</code>.
+ *
+ * @see CtClass#getModifiers()
+ */
+public class Modifier {
+ public static final int PUBLIC = AccessFlag.PUBLIC;
+ public static final int PRIVATE = AccessFlag.PRIVATE;
+ public static final int PROTECTED = AccessFlag.PROTECTED;
+ public static final int STATIC = AccessFlag.STATIC;
+ public static final int FINAL = AccessFlag.FINAL;
+ public static final int SYNCHRONIZED = AccessFlag.SYNCHRONIZED;
+ public static final int VOLATILE = AccessFlag.VOLATILE;
+ public static final int VARARGS = AccessFlag.VARARGS;
+ public static final int TRANSIENT = AccessFlag.TRANSIENT;
+ public static final int NATIVE = AccessFlag.NATIVE;
+ public static final int INTERFACE = AccessFlag.INTERFACE;
+ public static final int ABSTRACT = AccessFlag.ABSTRACT;
+ public static final int STRICT = AccessFlag.STRICT;
+ public static final int ANNOTATION = AccessFlag.ANNOTATION;
+ public static final int ENUM = AccessFlag.ENUM;
+
+ /**
+ * Returns true if the modifiers include the <tt>public</tt>
+ * modifier.
+ */
+ public static boolean isPublic(int mod) {
+ return (mod & PUBLIC) != 0;
+ }
+
+ /**
+ * Returns true if the modifiers include the <tt>private</tt>
+ * modifier.
+ */
+ public static boolean isPrivate(int mod) {
+ return (mod & PRIVATE) != 0;
+ }
+
+ /**
+ * Returns true if the modifiers include the <tt>protected</tt>
+ * modifier.
+ */
+ public static boolean isProtected(int mod) {
+ return (mod & PROTECTED) != 0;
+ }
+
+ /**
+ * Returns true if the modifiers do not include either
+ * <tt>public</tt>, <tt>protected</tt>, or <tt>private</tt>.
+ */
+ public static boolean isPackage(int mod) {
+ return (mod & (PUBLIC | PRIVATE | PROTECTED)) == 0;
+ }
+
+ /**
+ * Returns true if the modifiers include the <tt>static</tt>
+ * modifier.
+ */
+ public static boolean isStatic(int mod) {
+ return (mod & STATIC) != 0;
+ }
+
+ /**
+ * Returns true if the modifiers include the <tt>final</tt>
+ * modifier.
+ */
+ public static boolean isFinal(int mod) {
+ return (mod & FINAL) != 0;
+ }
+
+ /**
+ * Returns true if the modifiers include the <tt>synchronized</tt>
+ * modifier.
+ */
+ public static boolean isSynchronized(int mod) {
+ return (mod & SYNCHRONIZED) != 0;
+ }
+
+ /**
+ * Returns true if the modifiers include the <tt>volatile</tt>
+ * modifier.
+ */
+ public static boolean isVolatile(int mod) {
+ return (mod & VOLATILE) != 0;
+ }
+
+ /**
+ * Returns true if the modifiers include the <tt>transient</tt>
+ * modifier.
+ */
+ public static boolean isTransient(int mod) {
+ return (mod & TRANSIENT) != 0;
+ }
+
+ /**
+ * Returns true if the modifiers include the <tt>native</tt>
+ * modifier.
+ */
+ public static boolean isNative(int mod) {
+ return (mod & NATIVE) != 0;
+ }
+
+ /**
+ * Returns true if the modifiers include the <tt>interface</tt>
+ * modifier.
+ */
+ public static boolean isInterface(int mod) {
+ return (mod & INTERFACE) != 0;
+ }
+
+ /**
+ * Returns true if the modifiers include the <tt>annotation</tt>
+ * modifier.
+ *
+ * @since 3.2
+ */
+ public static boolean isAnnotation(int mod) {
+ return (mod & ANNOTATION) != 0;
+ }
+
+ /**
+ * Returns true if the modifiers include the <tt>enum</tt>
+ * modifier.
+ *
+ * @since 3.2
+ */
+ public static boolean isEnum(int mod) {
+ return (mod & ENUM) != 0;
+ }
+
+ /**
+ * Returns true if the modifiers include the <tt>abstract</tt>
+ * modifier.
+ */
+ public static boolean isAbstract(int mod) {
+ return (mod & ABSTRACT) != 0;
+ }
+
+ /**
+ * Returns true if the modifiers include the <tt>strictfp</tt>
+ * modifier.
+ */
+ public static boolean isStrict(int mod) {
+ return (mod & STRICT) != 0;
+ }
+
+ /**
+ * Truns the public bit on. The protected and private bits are
+ * cleared.
+ */
+ public static int setPublic(int mod) {
+ return (mod & ~(PRIVATE | PROTECTED)) | PUBLIC;
+ }
+
+ /**
+ * Truns the protected bit on. The protected and public bits are
+ * cleared.
+ */
+ public static int setProtected(int mod) {
+ return (mod & ~(PRIVATE | PUBLIC)) | PROTECTED;
+ }
+
+ /**
+ * Truns the private bit on. The protected and private bits are
+ * cleared.
+ */
+ public static int setPrivate(int mod) {
+ return (mod & ~(PROTECTED | PUBLIC)) | PRIVATE;
+ }
+
+ /**
+ * Clears the public, protected, and private bits.
+ */
+ public static int setPackage(int mod) {
+ return (mod & ~(PROTECTED | PUBLIC | PRIVATE));
+ }
+
+ /**
+ * Clears a specified bit in <code>mod</code>.
+ */
+ public static int clear(int mod, int clearBit) {
+ return mod & ~clearBit;
+ }
+
+ /**
+ * Return a string describing the access modifier flags in
+ * the specified modifier.
+ *
+ * @param mod modifier flags.
+ */
+ public static String toString(int mod) {
+ return java.lang.reflect.Modifier.toString(mod);
+ }
+}
diff --git a/src/main/javassist/NotFoundException.java b/src/main/javassist/NotFoundException.java
new file mode 100644
index 0000000..61de140
--- /dev/null
+++ b/src/main/javassist/NotFoundException.java
@@ -0,0 +1,29 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist;
+
+/**
+ * Signals that something could not be found.
+ */
+public class NotFoundException extends Exception {
+ public NotFoundException(String msg) {
+ super(msg);
+ }
+
+ public NotFoundException(String msg, Exception e) {
+ super(msg + " because of " + e.toString());
+ }
+}
diff --git a/src/main/javassist/SerialVersionUID.java b/src/main/javassist/SerialVersionUID.java
new file mode 100644
index 0000000..5e62310
--- /dev/null
+++ b/src/main/javassist/SerialVersionUID.java
@@ -0,0 +1,210 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist;
+
+import java.io.*;
+import java.lang.reflect.Modifier;
+
+import javassist.bytecode.*;
+import java.util.*;
+import java.security.*;
+
+/**
+ * Utility for calculating serialVersionUIDs for Serializable classes.
+ *
+ * @author Bob Lee (crazybob@crazybob.org)
+ * @author modified by Shigeru Chiba
+ */
+public class SerialVersionUID {
+
+ /**
+ * Adds serialVersionUID if one does not already exist. Call this before
+ * modifying a class to maintain serialization compatability.
+ */
+ public static void setSerialVersionUID(CtClass clazz)
+ throws CannotCompileException, NotFoundException
+ {
+ // check for pre-existing field.
+ try {
+ clazz.getDeclaredField("serialVersionUID");
+ return;
+ }
+ catch (NotFoundException e) {}
+
+ // check if the class is serializable.
+ if (!isSerializable(clazz))
+ return;
+
+ // add field with default value.
+ CtField field = new CtField(CtClass.longType, "serialVersionUID",
+ clazz);
+ field.setModifiers(Modifier.PRIVATE | Modifier.STATIC |
+ Modifier.FINAL);
+ clazz.addField(field, calculateDefault(clazz) + "L");
+ }
+
+ /**
+ * Does the class implement Serializable?
+ */
+ private static boolean isSerializable(CtClass clazz)
+ throws NotFoundException
+ {
+ ClassPool pool = clazz.getClassPool();
+ return clazz.subtypeOf(pool.get("java.io.Serializable"));
+ }
+
+ /**
+ * Calculate default value. See Java Serialization Specification, Stream
+ * Unique Identifiers.
+ */
+ static long calculateDefault(CtClass clazz)
+ throws CannotCompileException
+ {
+ try {
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ DataOutputStream out = new DataOutputStream(bout);
+ ClassFile classFile = clazz.getClassFile();
+
+ // class name.
+ String javaName = javaName(clazz);
+ out.writeUTF(javaName);
+
+ CtMethod[] methods = clazz.getDeclaredMethods();
+
+ // class modifiers.
+ int classMods = clazz.getModifiers();
+ if ((classMods & Modifier.INTERFACE) != 0)
+ if (methods.length > 0)
+ classMods = classMods | Modifier.ABSTRACT;
+ else
+ classMods = classMods & ~Modifier.ABSTRACT;
+
+ out.writeInt(classMods);
+
+ // interfaces.
+ String[] interfaces = classFile.getInterfaces();
+ for (int i = 0; i < interfaces.length; i++)
+ interfaces[i] = javaName(interfaces[i]);
+
+ Arrays.sort(interfaces);
+ for (int i = 0; i < interfaces.length; i++)
+ out.writeUTF(interfaces[i]);
+
+ // fields.
+ CtField[] fields = clazz.getDeclaredFields();
+ Arrays.sort(fields, new Comparator() {
+ public int compare(Object o1, Object o2) {
+ CtField field1 = (CtField)o1;
+ CtField field2 = (CtField)o2;
+ return field1.getName().compareTo(field2.getName());
+ }
+ });
+
+ for (int i = 0; i < fields.length; i++) {
+ CtField field = (CtField) fields[i];
+ int mods = field.getModifiers();
+ if (((mods & Modifier.PRIVATE) == 0) ||
+ ((mods & (Modifier.STATIC | Modifier.TRANSIENT)) == 0)) {
+ out.writeUTF(field.getName());
+ out.writeInt(mods);
+ out.writeUTF(field.getFieldInfo2().getDescriptor());
+ }
+ }
+
+ // static initializer.
+ if (classFile.getStaticInitializer() != null) {
+ out.writeUTF("<clinit>");
+ out.writeInt(Modifier.STATIC);
+ out.writeUTF("()V");
+ }
+
+ // constructors.
+ CtConstructor[] constructors = clazz.getDeclaredConstructors();
+ Arrays.sort(constructors, new Comparator() {
+ public int compare(Object o1, Object o2) {
+ CtConstructor c1 = (CtConstructor)o1;
+ CtConstructor c2 = (CtConstructor)o2;
+ return c1.getMethodInfo2().getDescriptor().compareTo(
+ c2.getMethodInfo2().getDescriptor());
+ }
+ });
+
+ for (int i = 0; i < constructors.length; i++) {
+ CtConstructor constructor = constructors[i];
+ int mods = constructor.getModifiers();
+ if ((mods & Modifier.PRIVATE) == 0) {
+ out.writeUTF("<init>");
+ out.writeInt(mods);
+ out.writeUTF(constructor.getMethodInfo2()
+ .getDescriptor().replace('/', '.'));
+ }
+ }
+
+ // methods.
+ Arrays.sort(methods, new Comparator() {
+ public int compare(Object o1, Object o2) {
+ CtMethod m1 = (CtMethod)o1;
+ CtMethod m2 = (CtMethod)o2;
+ int value = m1.getName().compareTo(m2.getName());
+ if (value == 0)
+ value = m1.getMethodInfo2().getDescriptor()
+ .compareTo(m2.getMethodInfo2().getDescriptor());
+
+ return value;
+ }
+ });
+
+ for (int i = 0; i < methods.length; i++) {
+ CtMethod method = methods[i];
+ int mods = method.getModifiers()
+ & (Modifier.PUBLIC | Modifier.PRIVATE
+ | Modifier.PROTECTED | Modifier.STATIC
+ | Modifier.FINAL | Modifier.SYNCHRONIZED
+ | Modifier.NATIVE | Modifier.ABSTRACT | Modifier.STRICT);
+ if ((mods & Modifier.PRIVATE) == 0) {
+ out.writeUTF(method.getName());
+ out.writeInt(mods);
+ out.writeUTF(method.getMethodInfo2()
+ .getDescriptor().replace('/', '.'));
+ }
+ }
+
+ // calculate hash.
+ out.flush();
+ MessageDigest digest = MessageDigest.getInstance("SHA");
+ byte[] digested = digest.digest(bout.toByteArray());
+ long hash = 0;
+ for (int i = Math.min(digested.length, 8) - 1; i >= 0; i--)
+ hash = (hash << 8) | (digested[i] & 0xFF);
+
+ return hash;
+ }
+ catch (IOException e) {
+ throw new CannotCompileException(e);
+ }
+ catch (NoSuchAlgorithmException e) {
+ throw new CannotCompileException(e);
+ }
+ }
+
+ private static String javaName(CtClass clazz) {
+ return Descriptor.toJavaName(Descriptor.toJvmName(clazz));
+ }
+
+ private static String javaName(String name) {
+ return Descriptor.toJavaName(Descriptor.toJvmName(name));
+ }
+}
diff --git a/src/main/javassist/Translator.java b/src/main/javassist/Translator.java
new file mode 100644
index 0000000..ea44034
--- /dev/null
+++ b/src/main/javassist/Translator.java
@@ -0,0 +1,70 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist;
+
+/**
+ * An observer of <code>Loader</code>.
+ * The users can define a class implementing this
+ * interface and attach an instance of that class to a
+ * <code>Loader</code> object so that it can translate a class file
+ * when the class file is loaded into the JVM.
+ *
+ * @see Loader#addTranslator(ClassPool, Translator)
+ */
+public interface Translator {
+ /**
+ * Is invoked by a <code>Loader</code> for initialization
+ * when the object is attached to the <code>Loader</code> object.
+ * This method can be used for getting (for caching) some
+ * <code>CtClass</code> objects that will be accessed
+ * in <code>onLoad()</code> in <code>Translator</code>.
+ *
+ * @param pool the <code>ClassPool</code> that this translator
+ * should use.
+ * @see Loader
+ * @throws NotFoundException if a <code>CtClass</code> cannot be found.
+ * @throws CannotCompileException if the initialization by this method
+ * fails.
+ */
+ void start(ClassPool pool)
+ throws NotFoundException, CannotCompileException;
+
+ /**
+ * Is invoked by a <code>Loader</code> for notifying that
+ * a class is loaded. The <code>Loader</code> calls
+ *
+ * <ul><pre>
+ * pool.get(classname).toBytecode()</pre></ul>
+ *
+ * to read the class file after <code>onLoad()</code> returns.
+ *
+ * <p><code>classname</code> may be the name of a class
+ * that has not been created yet.
+ * If so, <code>onLoad()</code> must create that class so that
+ * the <code>Loader</code> can read it after <code>onLoad()</code>
+ * returns.
+ *
+ * @param pool the <code>ClassPool</code> that this translator
+ * should use.
+ * @param classname the name of the class being loaded.
+ * @see Loader
+ * @throws NotFoundException if a <code>CtClass</code> cannot be found.
+ * @throws CannotCompileException if the code transformation
+ * by this method fails.
+ */
+ void onLoad(ClassPool pool, String classname)
+ throws NotFoundException, CannotCompileException;
+}
diff --git a/src/main/javassist/URLClassPath.java b/src/main/javassist/URLClassPath.java
new file mode 100644
index 0000000..0cdb820
--- /dev/null
+++ b/src/main/javassist/URLClassPath.java
@@ -0,0 +1,178 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist;
+
+import java.io.*;
+import java.net.*;
+
+/**
+ * A class search-path specified with URL (http).
+ *
+ * @see javassist.ClassPath
+ * @see ClassPool#insertClassPath(ClassPath)
+ * @see ClassPool#appendClassPath(ClassPath)
+ */
+public class URLClassPath implements ClassPath {
+ protected String hostname;
+ protected int port;
+ protected String directory;
+ protected String packageName;
+
+ /**
+ * Creates a search path specified with URL (http).
+ *
+ * <p>This search path is used only if a requested
+ * class name starts with the name specified by <code>packageName</code>.
+ * If <code>packageName</code> is "org.javassist." and a requested class is
+ * "org.javassist.test.Main", then the given URL is used for loading that class.
+ * The <code>URLClassPath</code> obtains a class file from:
+ *
+ * <ul><pre>http://www.javassist.org:80/java/classes/org/javassist/test/Main.class
+ * </pre></ul>
+ *
+ * <p>Here, we assume that <code>host</code> is "www.javassist.org",
+ * <code>port</code> is 80, and <code>directory</code> is "/java/classes/".
+ *
+ * <p>If <code>packageName</code> is <code>null</code>, the URL is used
+ * for loading any class.
+ *
+ * @param host host name
+ * @param port port number
+ * @param directory directory name ending with "/".
+ * It can be "/" (root directory).
+ * It must start with "/".
+ * @param packageName package name. It must end with "." (dot).
+ */
+ public URLClassPath(String host, int port,
+ String directory, String packageName) {
+ hostname = host;
+ this.port = port;
+ this.directory = directory;
+ this.packageName = packageName;
+ }
+
+ public String toString() {
+ return hostname + ":" + port + directory;
+ }
+
+ /**
+ * Opens a class file with http.
+ *
+ * @return null if the class file could not be found.
+ */
+ public InputStream openClassfile(String classname) {
+ try {
+ URLConnection con = openClassfile0(classname);
+ if (con != null)
+ return con.getInputStream();
+ }
+ catch (IOException e) {}
+ return null; // not found
+ }
+
+ private URLConnection openClassfile0(String classname) throws IOException {
+ if (packageName == null || classname.startsWith(packageName)) {
+ String jarname
+ = directory + classname.replace('.', '/') + ".class";
+ return fetchClass0(hostname, port, jarname);
+ }
+ else
+ return null; // not found
+ }
+
+ /**
+ * Returns the URL.
+ *
+ * @return null if the class file could not be obtained.
+ */
+ public URL find(String classname) {
+ try {
+ URLConnection con = openClassfile0(classname);
+ InputStream is = con.getInputStream();
+ if (is != null) {
+ is.close();
+ return con.getURL();
+ }
+ }
+ catch (IOException e) {}
+ return null;
+ }
+
+ /**
+ * Closes this class path.
+ */
+ public void close() {}
+
+ /**
+ * Reads a class file on an http server.
+ *
+ * @param host host name
+ * @param port port number
+ * @param directory directory name ending with "/".
+ * It can be "/" (root directory).
+ * It must start with "/".
+ * @param classname fully-qualified class name
+ */
+ public static byte[] fetchClass(String host, int port,
+ String directory, String classname)
+ throws IOException
+ {
+ byte[] b;
+ URLConnection con = fetchClass0(host, port,
+ directory + classname.replace('.', '/') + ".class");
+ int size = con.getContentLength();
+ InputStream s = con.getInputStream();
+ try {
+ if (size <= 0)
+ b = ClassPoolTail.readStream(s);
+ else {
+ b = new byte[size];
+ int len = 0;
+ do {
+ int n = s.read(b, len, size - len);
+ if (n < 0)
+ throw new IOException("the stream was closed: "
+ + classname);
+
+ len += n;
+ } while (len < size);
+ }
+ }
+ finally {
+ s.close();
+ }
+
+ return b;
+ }
+
+ private static URLConnection fetchClass0(String host, int port,
+ String filename)
+ throws IOException
+ {
+ URL url;
+ try {
+ url = new URL("http", host, port, filename);
+ }
+ catch (MalformedURLException e) {
+ // should never reache here.
+ throw new IOException("invalid URL?");
+ }
+
+ URLConnection con = url.openConnection();
+ con.connect();
+ return con;
+ }
+}
diff --git a/src/main/javassist/bytecode/AccessFlag.java b/src/main/javassist/bytecode/AccessFlag.java
new file mode 100644
index 0000000..6dda112
--- /dev/null
+++ b/src/main/javassist/bytecode/AccessFlag.java
@@ -0,0 +1,132 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode;
+
+/**
+ * A support class providing static methods and constants
+ * for access modifiers such as public, rivate, ...
+ */
+public class AccessFlag {
+ public static final int PUBLIC = 0x0001;
+ public static final int PRIVATE = 0x0002;
+ public static final int PROTECTED = 0x0004;
+ public static final int STATIC = 0x0008;
+ public static final int FINAL = 0x0010;
+ public static final int SYNCHRONIZED = 0x0020;
+ public static final int VOLATILE = 0x0040;
+ public static final int BRIDGE = 0x0040; // for method_info
+ public static final int TRANSIENT = 0x0080;
+ public static final int VARARGS = 0x0080; // for method_info
+ public static final int NATIVE = 0x0100;
+ public static final int INTERFACE = 0x0200;
+ public static final int ABSTRACT = 0x0400;
+ public static final int STRICT = 0x0800;
+ public static final int SYNTHETIC = 0x1000;
+ public static final int ANNOTATION = 0x2000;
+ public static final int ENUM = 0x4000;
+
+ public static final int SUPER = 0x0020;
+
+ // Note: 0x0020 is assigned to both ACC_SUPER and ACC_SYNCHRONIZED
+ // although java.lang.reflect.Modifier does not recognize ACC_SUPER.
+
+ /**
+ * Truns the public bit on. The protected and private bits are
+ * cleared.
+ */
+ public static int setPublic(int accflags) {
+ return (accflags & ~(PRIVATE | PROTECTED)) | PUBLIC;
+ }
+
+ /**
+ * Truns the protected bit on. The protected and public bits are
+ * cleared.
+ */
+ public static int setProtected(int accflags) {
+ return (accflags & ~(PRIVATE | PUBLIC)) | PROTECTED;
+ }
+
+ /**
+ * Truns the private bit on. The protected and private bits are
+ * cleared.
+ */
+ public static int setPrivate(int accflags) {
+ return (accflags & ~(PROTECTED | PUBLIC)) | PRIVATE;
+ }
+
+ /**
+ * Clears the public, protected, and private bits.
+ */
+ public static int setPackage(int accflags) {
+ return (accflags & ~(PROTECTED | PUBLIC | PRIVATE));
+ }
+
+ /**
+ * Returns true if the access flags include the public bit.
+ */
+ public static boolean isPublic(int accflags) {
+ return (accflags & PUBLIC) != 0;
+ }
+
+ /**
+ * Returns true if the access flags include the protected bit.
+ */
+ public static boolean isProtected(int accflags) {
+ return (accflags & PROTECTED) != 0;
+ }
+
+ /**
+ * Returns true if the access flags include the private bit.
+ */
+ public static boolean isPrivate(int accflags) {
+ return (accflags & PRIVATE) != 0;
+ }
+
+ /**
+ * Returns true if the access flags include neither public, protected,
+ * or private.
+ */
+ public static boolean isPackage(int accflags) {
+ return (accflags & (PROTECTED | PUBLIC | PRIVATE)) == 0;
+ }
+
+ /**
+ * Clears a specified bit in <code>accflags</code>.
+ */
+ public static int clear(int accflags, int clearBit) {
+ return accflags & ~clearBit;
+ }
+
+ /**
+ * Converts a javassist.Modifier into
+ * a javassist.bytecode.AccessFlag.
+ *
+ * @param modifier javassist.Modifier
+ */
+ public static int of(int modifier) {
+ return modifier;
+ }
+
+ /**
+ * Converts a javassist.bytecode.AccessFlag
+ * into a javassist.Modifier.
+ *
+ * @param accflags javassist.bytecode.Accessflag
+ */
+ public static int toModifier(int accflags) {
+ return accflags;
+ }
+}
diff --git a/src/main/javassist/bytecode/AnnotationDefaultAttribute.java b/src/main/javassist/bytecode/AnnotationDefaultAttribute.java
new file mode 100644
index 0000000..d591ac8
--- /dev/null
+++ b/src/main/javassist/bytecode/AnnotationDefaultAttribute.java
@@ -0,0 +1,159 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode;
+
+import javassist.CtClass;
+import javassist.bytecode.annotation.AnnotationsWriter;
+import javassist.bytecode.annotation.MemberValue;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * A class representing <code>AnnotationDefault_attribute</code>.
+ *
+ * <p>For example, if you declare the following annotation type:
+ *
+ * <ul><pre>
+ * @interface Author {
+ * String name() default "Shakespeare";
+ * int age() default 99;
+ * }
+ * </pre></ul>
+ *
+ * <p>The defautl values of <code>name</code> and <code>age</code>
+ * are stored as annotation default attributes in <code>Author.class</code>.
+ * The following code snippet obtains the default value of <code>name</code>:
+ *
+ * <ul><pre>
+ * ClassPool pool = ...
+ * CtClass cc = pool.get("Author");
+ * CtMethod cm = cc.getDeclaredMethod("age");
+ * MethodInfo minfo = cm.getMethodInfo();
+ * AnnotationDefaultAttribute ada
+ * = (AnnotationDefaultAttribute)
+ * minfo.getAttribute(AnnotationDefaultAttribute.tag);
+ * MemberValue value = ada.getDefaultValue()); // default value of age
+ * </pre></ul>
+ *
+ * <p>If the following statement is executed after the code above,
+ * the default value of age is set to 80:
+ *
+ * <ul><pre>
+ * ada.setDefaultValue(new IntegerMemberValue(minfo.getConstPool(), 80));
+ * </pre></ul>
+ *
+ * @see AnnotationsAttribute
+ * @see javassist.bytecode.annotation.MemberValue
+ */
+
+public class AnnotationDefaultAttribute extends AttributeInfo {
+ /**
+ * The name of the <code>AnnotationDefault</code> attribute.
+ */
+ public static final String tag = "AnnotationDefault";
+
+ /**
+ * Constructs an <code>AnnotationDefault_attribute</code>.
+ *
+ * @param cp constant pool
+ * @param info the contents of this attribute. It does not
+ * include <code>attribute_name_index</code> or
+ * <code>attribute_length</code>.
+ */
+ public AnnotationDefaultAttribute(ConstPool cp, byte[] info) {
+ super(cp, tag, info);
+ }
+
+ /**
+ * Constructs an empty <code>AnnotationDefault_attribute</code>.
+ * The default value can be set by <code>setDefaultValue()</code>.
+ *
+ * @param cp constant pool
+ * @see #setDefaultValue(javassist.bytecode.annotation.MemberValue)
+ */
+ public AnnotationDefaultAttribute(ConstPool cp) {
+ this(cp, new byte[] { 0, 0 });
+ }
+
+ /**
+ * @param n the attribute name.
+ */
+ AnnotationDefaultAttribute(ConstPool cp, int n, DataInputStream in)
+ throws IOException
+ {
+ super(cp, n, in);
+ }
+
+ /**
+ * Copies this attribute and returns a new copy.
+ */
+ public AttributeInfo copy(ConstPool newCp, Map classnames) {
+ AnnotationsAttribute.Copier copier
+ = new AnnotationsAttribute.Copier(info, constPool, newCp, classnames);
+ try {
+ copier.memberValue(0);
+ return new AnnotationDefaultAttribute(newCp, copier.close());
+ }
+ catch (Exception e) {
+ throw new RuntimeException(e.toString());
+ }
+ }
+
+ /**
+ * Obtains the default value represented by this attribute.
+ */
+ public MemberValue getDefaultValue()
+ {
+ try {
+ return new AnnotationsAttribute.Parser(info, constPool)
+ .parseMemberValue();
+ }
+ catch (Exception e) {
+ throw new RuntimeException(e.toString());
+ }
+ }
+
+ /**
+ * Changes the default value represented by this attribute.
+ *
+ * @param value the new value.
+ * @see javassist.bytecode.annotation.Annotation#createMemberValue(ConstPool, CtClass)
+ */
+ public void setDefaultValue(MemberValue value) {
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ AnnotationsWriter writer = new AnnotationsWriter(output, constPool);
+ try {
+ value.write(writer);
+ writer.close();
+ }
+ catch (IOException e) {
+ throw new RuntimeException(e); // should never reach here.
+ }
+
+ set(output.toByteArray());
+
+ }
+
+ /**
+ * Returns a string representation of this object.
+ */
+ public String toString() {
+ return getDefaultValue().toString();
+ }
+}
diff --git a/src/main/javassist/bytecode/AnnotationsAttribute.java b/src/main/javassist/bytecode/AnnotationsAttribute.java
new file mode 100644
index 0000000..0d2ac09
--- /dev/null
+++ b/src/main/javassist/bytecode/AnnotationsAttribute.java
@@ -0,0 +1,701 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode;
+
+import java.util.Map;
+import java.util.HashMap;
+import java.io.IOException;
+import java.io.DataInputStream;
+import java.io.ByteArrayOutputStream;
+import javassist.bytecode.annotation.*;
+
+/**
+ * A class representing
+ * <code>RuntimeVisibleAnnotations_attribute</code> and
+ * <code>RuntimeInvisibleAnnotations_attribute</code>.
+ *
+ * <p>To obtain an AnnotationAttribute object, invoke
+ * <code>getAttribute(AnnotationsAttribute.visibleTag)</code>
+ * in <code>ClassFile</code>, <code>MethodInfo</code>,
+ * or <code>FieldInfo</code>. The obtained attribute is a
+ * runtime visible annotations attribute.
+ * If the parameter is
+ * <code>AnnotationAttribute.invisibleTag</code>, then the obtained
+ * attribute is a runtime invisible one.
+ *
+ * <p>For example,
+ *
+ * <ul><pre>
+ * import javassist.bytecode.annotation.Annotation;
+ * :
+ * CtMethod m = ... ;
+ * MethodInfo minfo = m.getMethodInfo();
+ * AnnotationsAttribute attr = (AnnotationsAttribute)
+ * minfo.getAttribute(AnnotationsAttribute.invisibleTag);
+ * Annotation an = attr.getAnnotation("Author");
+ * String s = ((StringMemberValue)an.getMemberValue("name")).getValue();
+ * System.out.println("@Author(name=" + s + ")");
+ * </pre></ul>
+ *
+ * <p>This code snippet retrieves an annotation of the type <code>Author</code>
+ * from the <code>MethodInfo</code> object specified by <code>minfo</code>.
+ * Then, it prints the value of <code>name</code> in <code>Author</code>.
+ *
+ * <p>If the annotation type <code>Author</code> is annotated by a meta annotation:
+ *
+ * <ul><pre>
+ * @Retention(RetentionPolicy.RUNTIME)
+ * </pre></ul>
+ *
+ * <p>Then <code>Author</code> is visible at runtime. Therefore, the third
+ * statement of the code snippet above must be changed into:
+ *
+ * <ul><pre>
+ * AnnotationsAttribute attr = (AnnotationsAttribute)
+ * minfo.getAttribute(AnnotationsAttribute.visibleTag);
+ * </pre></ul>
+ *
+ * <p>The attribute tag must be <code>visibleTag</code> instead of
+ * <code>invisibleTag</code>.
+ *
+ * <p>If the member value of an annotation is not specified, the default value
+ * is used as that member value. If so, <code>getMemberValue()</code> in
+ * <code>Annotation</code> returns <code>null</code>
+ * since the default value is not included in the
+ * <code>AnnotationsAttribute</code>. It is included in the
+ * <code>AnnotationDefaultAttribute</code> of the method declared in the
+ * annotation type.
+ *
+ * <p>If you want to record a new AnnotationAttribute object, execute the
+ * following snippet:
+ *
+ * <ul><pre>
+ * ClassFile cf = ... ;
+ * ConstPool cp = cf.getConstPool();
+ * AnnotationsAttribute attr
+ * = new AnnotationsAttribute(cp, AnnotationsAttribute.visibleTag);
+ * Annotation a = new Annotation("Author", cp);
+ * a.addMemberValue("name", new StringMemberValue("Chiba", cp));
+ * attr.setAnnotation(a);
+ * cf.addAttribute(attr);
+ * cf.setVersionToJava5();
+ * </pre></ul>
+ *
+ * <p>The last statement is necessary if the class file was produced by
+ * Javassist or JDK 1.4. Otherwise, it is not necessary.
+ *
+ * @see AnnotationDefaultAttribute
+ * @see javassist.bytecode.annotation.Annotation
+ */
+public class AnnotationsAttribute extends AttributeInfo {
+ /**
+ * The name of the <code>RuntimeVisibleAnnotations</code> attribute.
+ */
+ public static final String visibleTag = "RuntimeVisibleAnnotations";
+
+ /**
+ * The name of the <code>RuntimeInvisibleAnnotations</code> attribute.
+ */
+ public static final String invisibleTag = "RuntimeInvisibleAnnotations";
+
+ /**
+ * Constructs a <code>Runtime(In)VisibleAnnotations_attribute</code>.
+ *
+ * @param cp constant pool
+ * @param attrname attribute name (<code>visibleTag</code> or
+ * <code>invisibleTag</code>).
+ * @param info the contents of this attribute. It does not
+ * include <code>attribute_name_index</code> or
+ * <code>attribute_length</code>.
+ */
+ public AnnotationsAttribute(ConstPool cp, String attrname, byte[] info) {
+ super(cp, attrname, info);
+ }
+
+ /**
+ * Constructs an empty
+ * <code>Runtime(In)VisibleAnnotations_attribute</code>.
+ * A new annotation can be later added to the created attribute
+ * by <code>setAnnotations()</code>.
+ *
+ * @param cp constant pool
+ * @param attrname attribute name (<code>visibleTag</code> or
+ * <code>invisibleTag</code>).
+ * @see #setAnnotations(Annotation[])
+ */
+ public AnnotationsAttribute(ConstPool cp, String attrname) {
+ this(cp, attrname, new byte[] { 0, 0 });
+ }
+
+ /**
+ * @param n the attribute name.
+ */
+ AnnotationsAttribute(ConstPool cp, int n, DataInputStream in)
+ throws IOException
+ {
+ super(cp, n, in);
+ }
+
+ /**
+ * Returns <code>num_annotations</code>.
+ */
+ public int numAnnotations() {
+ return ByteArray.readU16bit(info, 0);
+ }
+
+ /**
+ * Copies this attribute and returns a new copy.
+ */
+ public AttributeInfo copy(ConstPool newCp, Map classnames) {
+ Copier copier = new Copier(info, constPool, newCp, classnames);
+ try {
+ copier.annotationArray();
+ return new AnnotationsAttribute(newCp, getName(), copier.close());
+ }
+ catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Parses the annotations and returns a data structure representing
+ * the annotation with the specified type. See also
+ * <code>getAnnotations()</code> as to the returned data structure.
+ *
+ * @param type the annotation type.
+ * @return null if the specified annotation type is not included.
+ * @see #getAnnotations()
+ */
+ public Annotation getAnnotation(String type) {
+ Annotation[] annotations = getAnnotations();
+ for (int i = 0; i < annotations.length; i++) {
+ if (annotations[i].getTypeName().equals(type))
+ return annotations[i];
+ }
+
+ return null;
+ }
+
+ /**
+ * Adds an annotation. If there is an annotation with the same type,
+ * it is removed before the new annotation is added.
+ *
+ * @param annotation the added annotation.
+ */
+ public void addAnnotation(Annotation annotation) {
+ String type = annotation.getTypeName();
+ Annotation[] annotations = getAnnotations();
+ for (int i = 0; i < annotations.length; i++) {
+ if (annotations[i].getTypeName().equals(type)) {
+ annotations[i] = annotation;
+ setAnnotations(annotations);
+ return;
+ }
+ }
+
+ Annotation[] newlist = new Annotation[annotations.length + 1];
+ System.arraycopy(annotations, 0, newlist, 0, annotations.length);
+ newlist[annotations.length] = annotation;
+ setAnnotations(newlist);
+ }
+
+ /**
+ * Parses the annotations and returns a data structure representing
+ * that parsed annotations. Note that changes of the node values of the
+ * returned tree are not reflected on the annotations represented by
+ * this object unless the tree is copied back to this object by
+ * <code>setAnnotations()</code>.
+ *
+ * @see #setAnnotations(Annotation[])
+ */
+ public Annotation[] getAnnotations() {
+ try {
+ return new Parser(info, constPool).parseAnnotations();
+ }
+ catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Changes the annotations represented by this object according to
+ * the given array of <code>Annotation</code> objects.
+ *
+ * @param annotations the data structure representing the
+ * new annotations.
+ */
+ public void setAnnotations(Annotation[] annotations) {
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ AnnotationsWriter writer = new AnnotationsWriter(output, constPool);
+ try {
+ int n = annotations.length;
+ writer.numAnnotations(n);
+ for (int i = 0; i < n; ++i)
+ annotations[i].write(writer);
+
+ writer.close();
+ }
+ catch (IOException e) {
+ throw new RuntimeException(e); // should never reach here.
+ }
+
+ set(output.toByteArray());
+ }
+
+ /**
+ * Changes the annotations. A call to this method is equivalent to:
+ * <ul><pre>setAnnotations(new Annotation[] { annotation })</pre></ul>
+ *
+ * @param annotation the data structure representing
+ * the new annotation.
+ */
+ public void setAnnotation(Annotation annotation) {
+ setAnnotations(new Annotation[] { annotation });
+ }
+
+ /**
+ * @param oldname a JVM class name.
+ * @param newname a JVM class name.
+ */
+ void renameClass(String oldname, String newname) {
+ HashMap map = new HashMap();
+ map.put(oldname, newname);
+ renameClass(map);
+ }
+
+ void renameClass(Map classnames) {
+ Renamer renamer = new Renamer(info, getConstPool(), classnames);
+ try {
+ renamer.annotationArray();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ void getRefClasses(Map classnames) { renameClass(classnames); }
+
+ /**
+ * Returns a string representation of this object.
+ */
+ public String toString() {
+ Annotation[] a = getAnnotations();
+ StringBuilder sbuf = new StringBuilder();
+ int i = 0;
+ while (i < a.length) {
+ sbuf.append(a[i++].toString());
+ if (i != a.length)
+ sbuf.append(", ");
+ }
+
+ return sbuf.toString();
+ }
+
+ static class Walker {
+ byte[] info;
+
+ Walker(byte[] attrInfo) {
+ info = attrInfo;
+ }
+
+ final void parameters() throws Exception {
+ int numParam = info[0] & 0xff;
+ parameters(numParam, 1);
+ }
+
+ void parameters(int numParam, int pos) throws Exception {
+ for (int i = 0; i < numParam; ++i)
+ pos = annotationArray(pos);
+ }
+
+ final void annotationArray() throws Exception {
+ annotationArray(0);
+ }
+
+ final int annotationArray(int pos) throws Exception {
+ int num = ByteArray.readU16bit(info, pos);
+ return annotationArray(pos + 2, num);
+ }
+
+ int annotationArray(int pos, int num) throws Exception {
+ for (int i = 0; i < num; ++i)
+ pos = annotation(pos);
+
+ return pos;
+ }
+
+ final int annotation(int pos) throws Exception {
+ int type = ByteArray.readU16bit(info, pos);
+ int numPairs = ByteArray.readU16bit(info, pos + 2);
+ return annotation(pos + 4, type, numPairs);
+ }
+
+ int annotation(int pos, int type, int numPairs) throws Exception {
+ for (int j = 0; j < numPairs; ++j)
+ pos = memberValuePair(pos);
+
+ return pos;
+ }
+
+ final int memberValuePair(int pos) throws Exception {
+ int nameIndex = ByteArray.readU16bit(info, pos);
+ return memberValuePair(pos + 2, nameIndex);
+ }
+
+ int memberValuePair(int pos, int nameIndex) throws Exception {
+ return memberValue(pos);
+ }
+
+ final int memberValue(int pos) throws Exception {
+ int tag = info[pos] & 0xff;
+ if (tag == 'e') {
+ int typeNameIndex = ByteArray.readU16bit(info, pos + 1);
+ int constNameIndex = ByteArray.readU16bit(info, pos + 3);
+ enumMemberValue(pos, typeNameIndex, constNameIndex);
+ return pos + 5;
+ }
+ else if (tag == 'c') {
+ int index = ByteArray.readU16bit(info, pos + 1);
+ classMemberValue(pos, index);
+ return pos + 3;
+ }
+ else if (tag == '@')
+ return annotationMemberValue(pos + 1);
+ else if (tag == '[') {
+ int num = ByteArray.readU16bit(info, pos + 1);
+ return arrayMemberValue(pos + 3, num);
+ }
+ else { // primitive types or String.
+ int index = ByteArray.readU16bit(info, pos + 1);
+ constValueMember(tag, index);
+ return pos + 3;
+ }
+ }
+
+ void constValueMember(int tag, int index) throws Exception {}
+
+ void enumMemberValue(int pos, int typeNameIndex, int constNameIndex)
+ throws Exception {
+ }
+
+ void classMemberValue(int pos, int index) throws Exception {}
+
+ int annotationMemberValue(int pos) throws Exception {
+ return annotation(pos);
+ }
+
+ int arrayMemberValue(int pos, int num) throws Exception {
+ for (int i = 0; i < num; ++i) {
+ pos = memberValue(pos);
+ }
+
+ return pos;
+ }
+ }
+
+ static class Renamer extends Walker {
+ ConstPool cpool;
+ Map classnames;
+
+ /**
+ * Constructs a renamer. It renames some class names
+ * into the new names specified by <code>map</code>.
+ *
+ * @param info the annotations attribute.
+ * @param cp the constant pool.
+ * @param map pairs of replaced and substituted class names.
+ * It can be null.
+ */
+ Renamer(byte[] info, ConstPool cp, Map map) {
+ super(info);
+ cpool = cp;
+ classnames = map;
+ }
+
+ int annotation(int pos, int type, int numPairs) throws Exception {
+ renameType(pos - 4, type);
+ return super.annotation(pos, type, numPairs);
+ }
+
+ void enumMemberValue(int pos, int typeNameIndex, int constNameIndex)
+ throws Exception
+ {
+ renameType(pos + 1, typeNameIndex);
+ super.enumMemberValue(pos, typeNameIndex, constNameIndex);
+ }
+
+ void classMemberValue(int pos, int index) throws Exception {
+ renameType(pos + 1, index);
+ super.classMemberValue(pos, index);
+ }
+
+ private void renameType(int pos, int index) {
+ String name = cpool.getUtf8Info(index);
+ String newName = Descriptor.rename(name, classnames);
+ if (!name.equals(newName)) {
+ int index2 = cpool.addUtf8Info(newName);
+ ByteArray.write16bit(index2, info, pos);
+ }
+ }
+ }
+
+ static class Copier extends Walker {
+ ByteArrayOutputStream output;
+ AnnotationsWriter writer;
+ ConstPool srcPool, destPool;
+ Map classnames;
+
+ /**
+ * Constructs a copier. This copier renames some class names
+ * into the new names specified by <code>map</code> when it copies
+ * an annotation attribute.
+ *
+ * @param info the source attribute.
+ * @param src the constant pool of the source class.
+ * @param dest the constant pool of the destination class.
+ * @param map pairs of replaced and substituted class names.
+ * It can be null.
+ */
+ Copier(byte[] info, ConstPool src, ConstPool dest, Map map) {
+ super(info);
+ output = new ByteArrayOutputStream();
+ writer = new AnnotationsWriter(output, dest);
+ srcPool = src;
+ destPool = dest;
+ classnames = map;
+ }
+
+ byte[] close() throws IOException {
+ writer.close();
+ return output.toByteArray();
+ }
+
+ void parameters(int numParam, int pos) throws Exception {
+ writer.numParameters(numParam);
+ super.parameters(numParam, pos);
+ }
+
+ int annotationArray(int pos, int num) throws Exception {
+ writer.numAnnotations(num);
+ return super.annotationArray(pos, num);
+ }
+
+ int annotation(int pos, int type, int numPairs) throws Exception {
+ writer.annotation(copyType(type), numPairs);
+ return super.annotation(pos, type, numPairs);
+ }
+
+ int memberValuePair(int pos, int nameIndex) throws Exception {
+ writer.memberValuePair(copy(nameIndex));
+ return super.memberValuePair(pos, nameIndex);
+ }
+
+ void constValueMember(int tag, int index) throws Exception {
+ writer.constValueIndex(tag, copy(index));
+ super.constValueMember(tag, index);
+ }
+
+ void enumMemberValue(int pos, int typeNameIndex, int constNameIndex)
+ throws Exception
+ {
+ writer.enumConstValue(copyType(typeNameIndex), copy(constNameIndex));
+ super.enumMemberValue(pos, typeNameIndex, constNameIndex);
+ }
+
+ void classMemberValue(int pos, int index) throws Exception {
+ writer.classInfoIndex(copyType(index));
+ super.classMemberValue(pos, index);
+ }
+
+ int annotationMemberValue(int pos) throws Exception {
+ writer.annotationValue();
+ return super.annotationMemberValue(pos);
+ }
+
+ int arrayMemberValue(int pos, int num) throws Exception {
+ writer.arrayValue(num);
+ return super.arrayMemberValue(pos, num);
+ }
+
+ /**
+ * Copies a constant pool entry into the destination constant pool
+ * and returns the index of the copied entry.
+ *
+ * @param srcIndex the index of the copied entry into the source
+ * constant pool.
+ * @return the index of the copied item into the destination
+ * constant pool.
+ */
+ int copy(int srcIndex) {
+ return srcPool.copy(srcIndex, destPool, classnames);
+ }
+
+ /**
+ * Copies a constant pool entry into the destination constant pool
+ * and returns the index of the copied entry. That entry must be
+ * a Utf8Info representing a class name in the L<class name>; form.
+ *
+ * @param srcIndex the index of the copied entry into the source
+ * constant pool.
+ * @return the index of the copied item into the destination
+ * constant pool.
+ */
+ int copyType(int srcIndex) {
+ String name = srcPool.getUtf8Info(srcIndex);
+ String newName = Descriptor.rename(name, classnames);
+ return destPool.addUtf8Info(newName);
+ }
+ }
+
+ static class Parser extends Walker {
+ ConstPool pool;
+ Annotation[][] allParams; // all parameters
+ Annotation[] allAnno; // all annotations
+ Annotation currentAnno; // current annotation
+ MemberValue currentMember; // current member
+
+ /**
+ * Constructs a parser. This parser constructs a parse tree of
+ * the annotations.
+ *
+ * @param info the attribute.
+ * @param src the constant pool.
+ */
+ Parser(byte[] info, ConstPool cp) {
+ super(info);
+ pool = cp;
+ }
+
+ Annotation[][] parseParameters() throws Exception {
+ parameters();
+ return allParams;
+ }
+
+ Annotation[] parseAnnotations() throws Exception {
+ annotationArray();
+ return allAnno;
+ }
+
+ MemberValue parseMemberValue() throws Exception {
+ memberValue(0);
+ return currentMember;
+ }
+
+ void parameters(int numParam, int pos) throws Exception {
+ Annotation[][] params = new Annotation[numParam][];
+ for (int i = 0; i < numParam; ++i) {
+ pos = annotationArray(pos);
+ params[i] = allAnno;
+ }
+
+ allParams = params;
+ }
+
+ int annotationArray(int pos, int num) throws Exception {
+ Annotation[] array = new Annotation[num];
+ for (int i = 0; i < num; ++i) {
+ pos = annotation(pos);
+ array[i] = currentAnno;
+ }
+
+ allAnno = array;
+ return pos;
+ }
+
+ int annotation(int pos, int type, int numPairs) throws Exception {
+ currentAnno = new Annotation(type, pool);
+ return super.annotation(pos, type, numPairs);
+ }
+
+ int memberValuePair(int pos, int nameIndex) throws Exception {
+ pos = super.memberValuePair(pos, nameIndex);
+ currentAnno.addMemberValue(nameIndex, currentMember);
+ return pos;
+ }
+
+ void constValueMember(int tag, int index) throws Exception {
+ MemberValue m;
+ ConstPool cp = pool;
+ switch (tag) {
+ case 'B' :
+ m = new ByteMemberValue(index, cp);
+ break;
+ case 'C' :
+ m = new CharMemberValue(index, cp);
+ break;
+ case 'D' :
+ m = new DoubleMemberValue(index, cp);
+ break;
+ case 'F' :
+ m = new FloatMemberValue(index, cp);
+ break;
+ case 'I' :
+ m = new IntegerMemberValue(index, cp);
+ break;
+ case 'J' :
+ m = new LongMemberValue(index, cp);
+ break;
+ case 'S' :
+ m = new ShortMemberValue(index, cp);
+ break;
+ case 'Z' :
+ m = new BooleanMemberValue(index, cp);
+ break;
+ case 's' :
+ m = new StringMemberValue(index, cp);
+ break;
+ default :
+ throw new RuntimeException("unknown tag:" + tag);
+ }
+
+ currentMember = m;
+ super.constValueMember(tag, index);
+ }
+
+ void enumMemberValue(int pos, int typeNameIndex, int constNameIndex)
+ throws Exception
+ {
+ currentMember = new EnumMemberValue(typeNameIndex,
+ constNameIndex, pool);
+ super.enumMemberValue(pos, typeNameIndex, constNameIndex);
+ }
+
+ void classMemberValue(int pos, int index) throws Exception {
+ currentMember = new ClassMemberValue(index, pool);
+ super.classMemberValue(pos, index);
+ }
+
+ int annotationMemberValue(int pos) throws Exception {
+ Annotation anno = currentAnno;
+ pos = super.annotationMemberValue(pos);
+ currentMember = new AnnotationMemberValue(currentAnno, pool);
+ currentAnno = anno;
+ return pos;
+ }
+
+ int arrayMemberValue(int pos, int num) throws Exception {
+ ArrayMemberValue amv = new ArrayMemberValue(pool);
+ MemberValue[] elements = new MemberValue[num];
+ for (int i = 0; i < num; ++i) {
+ pos = memberValue(pos);
+ elements[i] = currentMember;
+ }
+
+ amv.setValue(elements);
+ currentMember = amv;
+ return pos;
+ }
+ }
+}
diff --git a/src/main/javassist/bytecode/AttributeInfo.java b/src/main/javassist/bytecode/AttributeInfo.java
new file mode 100644
index 0000000..c5da7e1
--- /dev/null
+++ b/src/main/javassist/bytecode/AttributeInfo.java
@@ -0,0 +1,286 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.Map;
+import java.util.ArrayList;
+import java.util.ListIterator;
+import java.util.List;
+import java.util.Iterator;
+
+// Note: if you define a new subclass of AttributeInfo, then
+// update AttributeInfo.read(), .copy(), and (maybe) write().
+
+/**
+ * <code>attribute_info</code> structure.
+ */
+public class AttributeInfo {
+ protected ConstPool constPool;
+ int name;
+ byte[] info;
+
+ protected AttributeInfo(ConstPool cp, int attrname, byte[] attrinfo) {
+ constPool = cp;
+ name = attrname;
+ info = attrinfo;
+ }
+
+ protected AttributeInfo(ConstPool cp, String attrname) {
+ this(cp, attrname, (byte[])null);
+ }
+
+ /**
+ * Constructs an <code>attribute_info</code> structure.
+ *
+ * @param cp constant pool table
+ * @param attrname attribute name
+ * @param attrinfo <code>info</code> field
+ * of <code>attribute_info</code> structure.
+ */
+ public AttributeInfo(ConstPool cp, String attrname, byte[] attrinfo) {
+ this(cp, cp.addUtf8Info(attrname), attrinfo);
+ }
+
+ protected AttributeInfo(ConstPool cp, int n, DataInputStream in)
+ throws IOException
+ {
+ constPool = cp;
+ name = n;
+ int len = in.readInt();
+ info = new byte[len];
+ if (len > 0)
+ in.readFully(info);
+ }
+
+ static AttributeInfo read(ConstPool cp, DataInputStream in)
+ throws IOException
+ {
+ int name = in.readUnsignedShort();
+ String nameStr = cp.getUtf8Info(name);
+ if (nameStr.charAt(0) < 'L') {
+ if (nameStr.equals(AnnotationDefaultAttribute.tag))
+ return new AnnotationDefaultAttribute(cp, name, in);
+ else if (nameStr.equals(CodeAttribute.tag))
+ return new CodeAttribute(cp, name, in);
+ else if (nameStr.equals(ConstantAttribute.tag))
+ return new ConstantAttribute(cp, name, in);
+ else if (nameStr.equals(DeprecatedAttribute.tag))
+ return new DeprecatedAttribute(cp, name, in);
+ else if (nameStr.equals(EnclosingMethodAttribute.tag))
+ return new EnclosingMethodAttribute(cp, name, in);
+ else if (nameStr.equals(ExceptionsAttribute.tag))
+ return new ExceptionsAttribute(cp, name, in);
+ else if (nameStr.equals(InnerClassesAttribute.tag))
+ return new InnerClassesAttribute(cp, name, in);
+ }
+ else {
+ /* Note that the names of Annotations attributes begin with 'R'.
+ */
+ if (nameStr.equals(LineNumberAttribute.tag))
+ return new LineNumberAttribute(cp, name, in);
+ else if (nameStr.equals(LocalVariableAttribute.tag))
+ return new LocalVariableAttribute(cp, name, in);
+ else if (nameStr.equals(LocalVariableTypeAttribute.tag))
+ return new LocalVariableTypeAttribute(cp, name, in);
+ else if (nameStr.equals(AnnotationsAttribute.visibleTag)
+ || nameStr.equals(AnnotationsAttribute.invisibleTag)) {
+ // RuntimeVisibleAnnotations or RuntimeInvisibleAnnotations
+ return new AnnotationsAttribute(cp, name, in);
+ }
+ else if (nameStr.equals(ParameterAnnotationsAttribute.visibleTag)
+ || nameStr.equals(ParameterAnnotationsAttribute.invisibleTag))
+ return new ParameterAnnotationsAttribute(cp, name, in);
+ else if (nameStr.equals(SignatureAttribute.tag))
+ return new SignatureAttribute(cp, name, in);
+ else if (nameStr.equals(SourceFileAttribute.tag))
+ return new SourceFileAttribute(cp, name, in);
+ else if (nameStr.equals(SyntheticAttribute.tag))
+ return new SyntheticAttribute(cp, name, in);
+ else if (nameStr.equals(StackMap.tag))
+ return new StackMap(cp, name, in);
+ else if (nameStr.equals(StackMapTable.tag))
+ return new StackMapTable(cp, name, in);
+ }
+
+ return new AttributeInfo(cp, name, in);
+ }
+
+ /**
+ * Returns an attribute name.
+ */
+ public String getName() {
+ return constPool.getUtf8Info(name);
+ }
+
+ /**
+ * Returns a constant pool table.
+ */
+ public ConstPool getConstPool() { return constPool; }
+
+ /**
+ * Returns the length of this <code>attribute_info</code>
+ * structure.
+ * The returned value is <code>attribute_length + 6</code>.
+ */
+ public int length() {
+ return info.length + 6;
+ }
+
+ /**
+ * Returns the <code>info</code> field
+ * of this <code>attribute_info</code> structure.
+ *
+ * <p>This method is not available if the object is an instance
+ * of <code>CodeAttribute</code>.
+ */
+ public byte[] get() { return info; }
+
+ /**
+ * Sets the <code>info</code> field
+ * of this <code>attribute_info</code> structure.
+ *
+ * <p>This method is not available if the object is an instance
+ * of <code>CodeAttribute</code>.
+ */
+ public void set(byte[] newinfo) { info = newinfo; }
+
+ /**
+ * Makes a copy. Class names are replaced according to the
+ * given <code>Map</code> object.
+ *
+ * @param newCp the constant pool table used by the new copy.
+ * @param classnames pairs of replaced and substituted
+ * class names.
+ */
+ public AttributeInfo copy(ConstPool newCp, Map classnames) {
+ int s = info.length;
+ byte[] srcInfo = info;
+ byte[] newInfo = new byte[s];
+ for (int i = 0; i < s; ++i)
+ newInfo[i] = srcInfo[i];
+
+ return new AttributeInfo(newCp, getName(), newInfo);
+ }
+
+ void write(DataOutputStream out) throws IOException {
+ out.writeShort(name);
+ out.writeInt(info.length);
+ if (info.length > 0)
+ out.write(info);
+ }
+
+ static int getLength(ArrayList list) {
+ int size = 0;
+ int n = list.size();
+ for (int i = 0; i < n; ++i) {
+ AttributeInfo attr = (AttributeInfo)list.get(i);
+ size += attr.length();
+ }
+
+ return size;
+ }
+
+ static AttributeInfo lookup(ArrayList list, String name) {
+ if (list == null)
+ return null;
+
+ ListIterator iterator = list.listIterator();
+ while (iterator.hasNext()) {
+ AttributeInfo ai = (AttributeInfo)iterator.next();
+ if (ai.getName().equals(name))
+ return ai;
+ }
+
+ return null; // no such attribute
+ }
+
+ static synchronized void remove(ArrayList list, String name) {
+ if (list == null)
+ return;
+
+ ListIterator iterator = list.listIterator();
+ while (iterator.hasNext()) {
+ AttributeInfo ai = (AttributeInfo)iterator.next();
+ if (ai.getName().equals(name))
+ iterator.remove();
+ }
+ }
+
+ static void writeAll(ArrayList list, DataOutputStream out)
+ throws IOException
+ {
+ if (list == null)
+ return;
+
+ int n = list.size();
+ for (int i = 0; i < n; ++i) {
+ AttributeInfo attr = (AttributeInfo)list.get(i);
+ attr.write(out);
+ }
+ }
+
+ static ArrayList copyAll(ArrayList list, ConstPool cp) {
+ if (list == null)
+ return null;
+
+ ArrayList newList = new ArrayList();
+ int n = list.size();
+ for (int i = 0; i < n; ++i) {
+ AttributeInfo attr = (AttributeInfo)list.get(i);
+ newList.add(attr.copy(cp, null));
+ }
+
+ return newList;
+ }
+
+ /* The following two methods are used to implement
+ * ClassFile.renameClass().
+ * Only CodeAttribute, LocalVariableAttribute,
+ * AnnotationsAttribute, and SignatureAttribute
+ * override these methods.
+ */
+ void renameClass(String oldname, String newname) {}
+ void renameClass(Map classnames) {}
+
+ static void renameClass(List attributes, String oldname, String newname) {
+ Iterator iterator = attributes.iterator();
+ while (iterator.hasNext()) {
+ AttributeInfo ai = (AttributeInfo)iterator.next();
+ ai.renameClass(oldname, newname);
+ }
+ }
+
+ static void renameClass(List attributes, Map classnames) {
+ Iterator iterator = attributes.iterator();
+ while (iterator.hasNext()) {
+ AttributeInfo ai = (AttributeInfo)iterator.next();
+ ai.renameClass(classnames);
+ }
+ }
+
+ void getRefClasses(Map classnames) {}
+
+ static void getRefClasses(List attributes, Map classnames) {
+ Iterator iterator = attributes.iterator();
+ while (iterator.hasNext()) {
+ AttributeInfo ai = (AttributeInfo)iterator.next();
+ ai.getRefClasses(classnames);
+ }
+ }
+}
diff --git a/src/main/javassist/bytecode/BadBytecode.java b/src/main/javassist/bytecode/BadBytecode.java
new file mode 100644
index 0000000..2f93b52
--- /dev/null
+++ b/src/main/javassist/bytecode/BadBytecode.java
@@ -0,0 +1,33 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode;
+
+/**
+ * Signals that a bad bytecode sequence has been found.
+ */
+public class BadBytecode extends Exception {
+ public BadBytecode(int opcode) {
+ super("bytecode " + opcode);
+ }
+
+ public BadBytecode(String msg) {
+ super(msg);
+ }
+
+ public BadBytecode(String msg, Throwable cause) {
+ super(msg, cause);
+ }
+}
diff --git a/src/main/javassist/bytecode/ByteArray.java b/src/main/javassist/bytecode/ByteArray.java
new file mode 100644
index 0000000..e564822
--- /dev/null
+++ b/src/main/javassist/bytecode/ByteArray.java
@@ -0,0 +1,76 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode;
+
+/**
+ * A collection of static methods for reading and writing a byte array.
+ */
+public class ByteArray {
+ /**
+ * Reads an unsigned 16bit integer at the index.
+ */
+ public static int readU16bit(byte[] code, int index) {
+ return ((code[index] & 0xff) << 8) | (code[index + 1] & 0xff);
+ }
+
+ /**
+ * Reads a signed 16bit integer at the index.
+ */
+ public static int readS16bit(byte[] code, int index) {
+ return (code[index] << 8) | (code[index + 1] & 0xff);
+ }
+
+ /**
+ * Writes a 16bit integer at the index.
+ */
+ public static void write16bit(int value, byte[] code, int index) {
+ code[index] = (byte)(value >>> 8);
+ code[index + 1] = (byte)value;
+ }
+
+ /**
+ * Reads a 32bit integer at the index.
+ */
+ public static int read32bit(byte[] code, int index) {
+ return (code[index] << 24) | ((code[index + 1] & 0xff) << 16)
+ | ((code[index + 2] & 0xff) << 8) | (code[index + 3] & 0xff);
+ }
+
+ /**
+ * Writes a 32bit integer at the index.
+ */
+ public static void write32bit(int value, byte[] code, int index) {
+ code[index] = (byte)(value >>> 24);
+ code[index + 1] = (byte)(value >>> 16);
+ code[index + 2] = (byte)(value >>> 8);
+ code[index + 3] = (byte)value;
+ }
+
+ /**
+ * Copies a 32bit integer.
+ *
+ * @param src the source byte array.
+ * @param isrc the index into the source byte array.
+ * @param dest the destination byte array.
+ * @param idest the index into the destination byte array.
+ */
+ static void copy32bit(byte[] src, int isrc, byte[] dest, int idest) {
+ dest[idest] = src[isrc];
+ dest[idest + 1] = src[isrc + 1];
+ dest[idest + 2] = src[isrc + 2];
+ dest[idest + 3] = src[isrc + 3];
+ }
+}
diff --git a/src/main/javassist/bytecode/ByteStream.java b/src/main/javassist/bytecode/ByteStream.java
new file mode 100644
index 0000000..82b30c9
--- /dev/null
+++ b/src/main/javassist/bytecode/ByteStream.java
@@ -0,0 +1,193 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2010 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode;
+
+import java.io.OutputStream;
+import java.io.IOException;
+
+final class ByteStream extends OutputStream {
+ private byte[] buf;
+ private int count;
+
+ public ByteStream() { this(32); }
+
+ public ByteStream(int size) {
+ buf = new byte[size];
+ count = 0;
+ }
+
+ public int getPos() { return count; }
+ public int size() { return count; }
+
+ public void writeBlank(int len) {
+ enlarge(len);
+ count += len;
+ }
+
+ public void write(byte[] data) {
+ write(data, 0, data.length);
+ }
+
+ public void write(byte[] data, int off, int len) {
+ enlarge(len);
+ System.arraycopy(data, off, buf, count, len);
+ count += len;
+ }
+
+ public void write(int b) {
+ enlarge(1);
+ int oldCount = count;
+ buf[oldCount] = (byte)b;
+ count = oldCount + 1;
+ }
+
+ public void writeShort(int s) {
+ enlarge(2);
+ int oldCount = count;
+ buf[oldCount] = (byte)(s >>> 8);
+ buf[oldCount + 1] = (byte)s;
+ count = oldCount + 2;
+ }
+
+ public void writeInt(int i) {
+ enlarge(4);
+ int oldCount = count;
+ buf[oldCount] = (byte)(i >>> 24);
+ buf[oldCount + 1] = (byte)(i >>> 16);
+ buf[oldCount + 2] = (byte)(i >>> 8);
+ buf[oldCount + 3] = (byte)i;
+ count = oldCount + 4;
+ }
+
+ public void writeLong(long i) {
+ enlarge(8);
+ int oldCount = count;
+ buf[oldCount] = (byte)(i >>> 56);
+ buf[oldCount + 1] = (byte)(i >>> 48);
+ buf[oldCount + 2] = (byte)(i >>> 40);
+ buf[oldCount + 3] = (byte)(i >>> 32);
+ buf[oldCount + 4] = (byte)(i >>> 24);
+ buf[oldCount + 5] = (byte)(i >>> 16);
+ buf[oldCount + 6] = (byte)(i >>> 8);
+ buf[oldCount + 7] = (byte)i;
+ count = oldCount + 8;
+ }
+
+ public void writeFloat(float v) {
+ writeInt(Float.floatToIntBits(v));
+ }
+
+ public void writeDouble(double v) {
+ writeLong(Double.doubleToLongBits(v));
+ }
+
+ public void writeUTF(String s) {
+ int sLen = s.length();
+ int pos = count;
+ enlarge(sLen + 2);
+
+ byte[] buffer = buf;
+ buffer[pos++] = (byte)(sLen >>> 8);
+ buffer[pos++] = (byte)sLen;
+ for (int i = 0; i < sLen; ++i) {
+ char c = s.charAt(i);
+ if (0x01 <= c && c <= 0x7f)
+ buffer[pos++] = (byte)c;
+ else {
+ writeUTF2(s, sLen, i);
+ return;
+ }
+ }
+
+ count = pos;
+ }
+
+ private void writeUTF2(String s, int sLen, int offset) {
+ int size = sLen;
+ for (int i = offset; i < sLen; i++) {
+ int c = s.charAt(i);
+ if (c > 0x7ff)
+ size += 2; // 3 bytes code
+ else if (c == 0 || c > 0x7f)
+ ++size; // 2 bytes code
+ }
+
+ if (size > 65535)
+ throw new RuntimeException(
+ "encoded string too long: " + sLen + size + " bytes");
+
+ enlarge(size + 2);
+ int pos = count;
+ byte[] buffer = buf;
+ buffer[pos] = (byte)(size >>> 8);
+ buffer[pos + 1] = (byte)size;
+ pos += 2 + offset;
+ for (int j = offset; j < sLen; ++j) {
+ int c = s.charAt(j);
+ if (0x01 <= c && c <= 0x7f)
+ buffer[pos++] = (byte) c;
+ else if (c > 0x07ff) {
+ buffer[pos] = (byte)(0xe0 | ((c >> 12) & 0x0f));
+ buffer[pos + 1] = (byte)(0x80 | ((c >> 6) & 0x3f));
+ buffer[pos + 2] = (byte)(0x80 | (c & 0x3f));
+ pos += 3;
+ }
+ else {
+ buffer[pos] = (byte)(0xc0 | ((c >> 6) & 0x1f));
+ buffer[pos + 1] = (byte)(0x80 | (c & 0x3f));
+ pos += 2;
+ }
+ }
+
+ count = pos;
+ }
+
+ public void write(int pos, int value) {
+ buf[pos] = (byte)value;
+ }
+
+ public void writeShort(int pos, int value) {
+ buf[pos] = (byte)(value >>> 8);
+ buf[pos + 1] = (byte)value;
+ }
+
+ public void writeInt(int pos, int value) {
+ buf[pos] = (byte)(value >>> 24);
+ buf[pos + 1] = (byte)(value >>> 16);
+ buf[pos + 2] = (byte)(value >>> 8);
+ buf[pos + 3] = (byte)value;
+ }
+
+ public byte[] toByteArray() {
+ byte[] buf2 = new byte[count];
+ System.arraycopy(buf, 0, buf2, 0, count);
+ return buf2;
+ }
+
+ public void writeTo(OutputStream out) throws IOException {
+ out.write(buf, 0, count);
+ }
+
+ public void enlarge(int delta) {
+ int newCount = count + delta;
+ if (newCount > buf.length) {
+ int newLen = buf.length << 1;
+ byte[] newBuf = new byte[newLen > newCount ? newLen : newCount];
+ System.arraycopy(buf, 0, newBuf, 0, count);
+ buf = newBuf;
+ }
+ }
+}
diff --git a/src/main/javassist/bytecode/Bytecode.java b/src/main/javassist/bytecode/Bytecode.java
new file mode 100644
index 0000000..92fd1f0
--- /dev/null
+++ b/src/main/javassist/bytecode/Bytecode.java
@@ -0,0 +1,1410 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode;
+
+import javassist.CtClass;
+import javassist.CtPrimitiveType;
+
+class ByteVector implements Cloneable {
+ private byte[] buffer;
+ private int size;
+
+ public ByteVector() {
+ buffer = new byte[64];
+ size = 0;
+ }
+
+ public Object clone() throws CloneNotSupportedException {
+ ByteVector bv = (ByteVector)super.clone();
+ bv.buffer = (byte[])buffer.clone();
+ return bv;
+ }
+
+ public final int getSize() { return size; }
+
+ public final byte[] copy() {
+ byte[] b = new byte[size];
+ System.arraycopy(buffer, 0, b, 0, size);
+ return b;
+ }
+
+ public int read(int offset) {
+ if (offset < 0 || size <= offset)
+ throw new ArrayIndexOutOfBoundsException(offset);
+
+ return buffer[offset];
+ }
+
+ public void write(int offset, int value) {
+ if (offset < 0 || size <= offset)
+ throw new ArrayIndexOutOfBoundsException(offset);
+
+ buffer[offset] = (byte)value;
+ }
+
+ public void add(int code) {
+ addGap(1);
+ buffer[size - 1] = (byte)code;
+ }
+
+ public void add(int b1, int b2) {
+ addGap(2);
+ buffer[size - 2] = (byte)b1;
+ buffer[size - 1] = (byte)b2;
+ }
+
+ public void add(int b1, int b2, int b3, int b4) {
+ addGap(4);
+ buffer[size - 4] = (byte)b1;
+ buffer[size - 3] = (byte)b2;
+ buffer[size - 2] = (byte)b3;
+ buffer[size - 1] = (byte)b4;
+ }
+
+ public void addGap(int length) {
+ if (size + length > buffer.length) {
+ int newSize = size << 1;
+ if (newSize < size + length)
+ newSize = size + length;
+
+ byte[] newBuf = new byte[newSize];
+ System.arraycopy(buffer, 0, newBuf, 0, size);
+ buffer = newBuf;
+ }
+
+ size += length;
+ }
+}
+
+/**
+ * A utility class for producing a bytecode sequence.
+ *
+ * <p>A <code>Bytecode</code> object is an unbounded array
+ * containing bytecode. For example,
+ *
+ * <ul><pre>ConstPool cp = ...; // constant pool table
+ * Bytecode b = new Bytecode(cp, 1, 0);
+ * b.addIconst(3);
+ * b.addReturn(CtClass.intType);
+ * CodeAttribute ca = b.toCodeAttribute();</ul></pre>
+ *
+ * <p>This program produces a Code attribute including a bytecode
+ * sequence:
+ *
+ * <ul><pre>iconst_3
+ * ireturn</pre></ul>
+ *
+ * @see ConstPool
+ * @see CodeAttribute
+ */
+public class Bytecode extends ByteVector implements Cloneable, Opcode {
+ /**
+ * Represents the <code>CtClass</code> file using the
+ * constant pool table given to this <code>Bytecode</code> object.
+ */
+ public static final CtClass THIS = ConstPool.THIS;
+
+ ConstPool constPool;
+ int maxStack, maxLocals;
+ ExceptionTable tryblocks;
+ private int stackDepth;
+
+ /**
+ * Constructs a <code>Bytecode</code> object with an empty bytecode
+ * sequence.
+ *
+ * <p>The parameters <code>stacksize</code> and <code>localvars</code>
+ * specify initial values
+ * of <code>max_stack</code> and <code>max_locals</code>.
+ * They can be changed later.
+ *
+ * @param cp constant pool table.
+ * @param stacksize <code>max_stack</code>.
+ * @param localvars <code>max_locals</code>.
+ */
+ public Bytecode(ConstPool cp, int stacksize, int localvars) {
+ constPool = cp;
+ maxStack = stacksize;
+ maxLocals = localvars;
+ tryblocks = new ExceptionTable(cp);
+ stackDepth = 0;
+ }
+
+ /**
+ * Constructs a <code>Bytecode</code> object with an empty bytecode
+ * sequence. The initial values of <code>max_stack</code> and
+ * <code>max_locals</code> are zero.
+ *
+ * @param cp constant pool table.
+ * @see Bytecode#setMaxStack(int)
+ * @see Bytecode#setMaxLocals(int)
+ */
+ public Bytecode(ConstPool cp) {
+ this(cp, 0, 0);
+ }
+
+ /**
+ * Creates and returns a copy of this object.
+ * The constant pool object is shared between this object
+ * and the cloned object.
+ */
+ public Object clone() {
+ try {
+ Bytecode bc = (Bytecode)super.clone();
+ bc.tryblocks = (ExceptionTable)tryblocks.clone();
+ return bc;
+ }
+ catch (CloneNotSupportedException cnse) {
+ throw new RuntimeException(cnse);
+ }
+ }
+
+ /**
+ * Gets a constant pool table.
+ */
+ public ConstPool getConstPool() { return constPool; }
+
+ /**
+ * Returns <code>exception_table</code>.
+ */
+ public ExceptionTable getExceptionTable() { return tryblocks; }
+
+ /**
+ * Converts to a <code>CodeAttribute</code>.
+ */
+ public CodeAttribute toCodeAttribute() {
+ return new CodeAttribute(constPool, maxStack, maxLocals,
+ get(), tryblocks);
+ }
+
+ /**
+ * Returns the length of the bytecode sequence.
+ */
+ public int length() {
+ return getSize();
+ }
+
+ /**
+ * Returns the produced bytecode sequence.
+ */
+ public byte[] get() {
+ return copy();
+ }
+
+ /**
+ * Gets <code>max_stack</code>.
+ */
+ public int getMaxStack() { return maxStack; }
+
+ /**
+ * Sets <code>max_stack</code>.
+ *
+ * <p>This value may be automatically updated when an instruction
+ * is appended. A <code>Bytecode</code> object maintains the current
+ * stack depth whenever an instruction is added
+ * by <code>addOpcode()</code>. For example, if DUP is appended,
+ * the current stack depth is increased by one. If the new stack
+ * depth is more than <code>max_stack</code>, then it is assigned
+ * to <code>max_stack</code>. However, if branch instructions are
+ * appended, the current stack depth may not be correctly maintained.
+ *
+ * @see #addOpcode(int)
+ */
+ public void setMaxStack(int size) {
+ maxStack = size;
+ }
+
+ /**
+ * Gets <code>max_locals</code>.
+ */
+ public int getMaxLocals() { return maxLocals; }
+
+ /**
+ * Sets <code>max_locals</code>.
+ */
+ public void setMaxLocals(int size) {
+ maxLocals = size;
+ }
+
+ /**
+ * Sets <code>max_locals</code>.
+ *
+ * <p>This computes the number of local variables
+ * used to pass method parameters and sets <code>max_locals</code>
+ * to that number plus <code>locals</code>.
+ *
+ * @param isStatic true if <code>params</code> must be
+ * interpreted as parameters to a static method.
+ * @param params parameter types.
+ * @param locals the number of local variables excluding
+ * ones used to pass parameters.
+ */
+ public void setMaxLocals(boolean isStatic, CtClass[] params,
+ int locals) {
+ if (!isStatic)
+ ++locals;
+
+ if (params != null) {
+ CtClass doubleType = CtClass.doubleType;
+ CtClass longType = CtClass.longType;
+ int n = params.length;
+ for (int i = 0; i < n; ++i) {
+ CtClass type = params[i];
+ if (type == doubleType || type == longType)
+ locals += 2;
+ else
+ ++locals;
+ }
+ }
+
+ maxLocals = locals;
+ }
+
+ /**
+ * Increments <code>max_locals</code>.
+ */
+ public void incMaxLocals(int diff) {
+ maxLocals += diff;
+ }
+
+ /**
+ * Adds a new entry of <code>exception_table</code>.
+ */
+ public void addExceptionHandler(int start, int end,
+ int handler, CtClass type) {
+ addExceptionHandler(start, end, handler,
+ constPool.addClassInfo(type));
+ }
+
+ /**
+ * Adds a new entry of <code>exception_table</code>.
+ *
+ * @param type the fully-qualified name of a throwable class.
+ */
+ public void addExceptionHandler(int start, int end,
+ int handler, String type) {
+ addExceptionHandler(start, end, handler,
+ constPool.addClassInfo(type));
+ }
+
+ /**
+ * Adds a new entry of <code>exception_table</code>.
+ */
+ public void addExceptionHandler(int start, int end,
+ int handler, int type) {
+ tryblocks.add(start, end, handler, type);
+ }
+
+ /**
+ * Returns the length of bytecode sequence
+ * that have been added so far.
+ */
+ public int currentPc() {
+ return getSize();
+ }
+
+ /**
+ * Reads a signed 8bit value at the offset from the beginning of the
+ * bytecode sequence.
+ *
+ * @throws ArrayIndexOutOfBoundsException if offset is invalid.
+ */
+ public int read(int offset) {
+ return super.read(offset);
+ }
+
+ /**
+ * Reads a signed 16bit value at the offset from the beginning of the
+ * bytecode sequence.
+ */
+ public int read16bit(int offset) {
+ int v1 = read(offset);
+ int v2 = read(offset + 1);
+ return (v1 << 8) + (v2 & 0xff);
+ }
+
+ /**
+ * Reads a signed 32bit value at the offset from the beginning of the
+ * bytecode sequence.
+ */
+ public int read32bit(int offset) {
+ int v1 = read16bit(offset);
+ int v2 = read16bit(offset + 2);
+ return (v1 << 16) + (v2 & 0xffff);
+ }
+
+ /**
+ * Writes an 8bit value at the offset from the beginning of the
+ * bytecode sequence.
+ *
+ * @throws ArrayIndexOutOfBoundsException if offset is invalid.
+ */
+ public void write(int offset, int value) {
+ super.write(offset, value);
+ }
+
+ /**
+ * Writes an 16bit value at the offset from the beginning of the
+ * bytecode sequence.
+ */
+ public void write16bit(int offset, int value) {
+ write(offset, value >> 8);
+ write(offset + 1, value);
+ }
+
+ /**
+ * Writes an 32bit value at the offset from the beginning of the
+ * bytecode sequence.
+ */
+ public void write32bit(int offset, int value) {
+ write16bit(offset, value >> 16);
+ write16bit(offset + 2, value);
+ }
+
+ /**
+ * Appends an 8bit value to the end of the bytecode sequence.
+ */
+ public void add(int code) {
+ super.add(code);
+ }
+
+ /**
+ * Appends a 32bit value to the end of the bytecode sequence.
+ */
+ public void add32bit(int value) {
+ add(value >> 24, value >> 16, value >> 8, value);
+ }
+
+ /**
+ * Appends the length-byte gap to the end of the bytecode sequence.
+ *
+ * @param length the gap length in byte.
+ */
+ public void addGap(int length) {
+ super.addGap(length);
+ }
+
+ /**
+ * Appends an 8bit opcode to the end of the bytecode sequence.
+ * The current stack depth is updated.
+ * <code>max_stack</code> is updated if the current stack depth
+ * is the deepest so far.
+ *
+ * <p>Note: some instructions such as INVOKEVIRTUAL does not
+ * update the current stack depth since the increment depends
+ * on the method signature.
+ * <code>growStack()</code> must be explicitly called.
+ */
+ public void addOpcode(int code) {
+ add(code);
+ growStack(STACK_GROW[code]);
+ }
+
+ /**
+ * Increases the current stack depth.
+ * It also updates <code>max_stack</code> if the current stack depth
+ * is the deepest so far.
+ *
+ * @param diff the number added to the current stack depth.
+ */
+ public void growStack(int diff) {
+ setStackDepth(stackDepth + diff);
+ }
+
+ /**
+ * Returns the current stack depth.
+ */
+ public int getStackDepth() { return stackDepth; }
+
+ /**
+ * Sets the current stack depth.
+ * It also updates <code>max_stack</code> if the current stack depth
+ * is the deepest so far.
+ *
+ * @param depth new value.
+ */
+ public void setStackDepth(int depth) {
+ stackDepth = depth;
+ if (stackDepth > maxStack)
+ maxStack = stackDepth;
+ }
+
+ /**
+ * Appends a 16bit value to the end of the bytecode sequence.
+ * It never changes the current stack depth.
+ */
+ public void addIndex(int index) {
+ add(index >> 8, index);
+ }
+
+ /**
+ * Appends ALOAD or (WIDE) ALOAD_<n>
+ *
+ * @param n an index into the local variable array.
+ */
+ public void addAload(int n) {
+ if (n < 4)
+ addOpcode(42 + n); // aload_<n>
+ else if (n < 0x100) {
+ addOpcode(ALOAD); // aload
+ add(n);
+ }
+ else {
+ addOpcode(WIDE);
+ addOpcode(ALOAD);
+ addIndex(n);
+ }
+ }
+
+ /**
+ * Appends ASTORE or (WIDE) ASTORE_<n>
+ *
+ * @param n an index into the local variable array.
+ */
+ public void addAstore(int n) {
+ if (n < 4)
+ addOpcode(75 + n); // astore_<n>
+ else if (n < 0x100) {
+ addOpcode(ASTORE); // astore
+ add(n);
+ }
+ else {
+ addOpcode(WIDE);
+ addOpcode(ASTORE);
+ addIndex(n);
+ }
+ }
+
+ /**
+ * Appends ICONST or ICONST_<n>
+ *
+ * @param n the pushed integer constant.
+ */
+ public void addIconst(int n) {
+ if (n < 6 && -2 < n)
+ addOpcode(3 + n); // iconst_<i> -1..5
+ else if (n <= 127 && -128 <= n) {
+ addOpcode(16); // bipush
+ add(n);
+ }
+ else if (n <= 32767 && -32768 <= n) {
+ addOpcode(17); // sipush
+ add(n >> 8);
+ add(n);
+ }
+ else
+ addLdc(constPool.addIntegerInfo(n));
+ }
+
+ /**
+ * Appends an instruction for pushing zero or null on the stack.
+ * If the type is void, this method does not append any instruction.
+ *
+ * @param type the type of the zero value (or null).
+ */
+ public void addConstZero(CtClass type) {
+ if (type.isPrimitive()) {
+ if (type == CtClass.longType)
+ addOpcode(LCONST_0);
+ else if (type == CtClass.floatType)
+ addOpcode(FCONST_0);
+ else if (type == CtClass.doubleType)
+ addOpcode(DCONST_0);
+ else if (type == CtClass.voidType)
+ throw new RuntimeException("void type?");
+ else
+ addOpcode(ICONST_0);
+ }
+ else
+ addOpcode(ACONST_NULL);
+ }
+
+ /**
+ * Appends ILOAD or (WIDE) ILOAD_<n>
+ *
+ * @param n an index into the local variable array.
+ */
+ public void addIload(int n) {
+ if (n < 4)
+ addOpcode(26 + n); // iload_<n>
+ else if (n < 0x100) {
+ addOpcode(ILOAD); // iload
+ add(n);
+ }
+ else {
+ addOpcode(WIDE);
+ addOpcode(ILOAD);
+ addIndex(n);
+ }
+ }
+
+ /**
+ * Appends ISTORE or (WIDE) ISTORE_<n>
+ *
+ * @param n an index into the local variable array.
+ */
+ public void addIstore(int n) {
+ if (n < 4)
+ addOpcode(59 + n); // istore_<n>
+ else if (n < 0x100) {
+ addOpcode(ISTORE); // istore
+ add(n);
+ }
+ else {
+ addOpcode(WIDE);
+ addOpcode(ISTORE);
+ addIndex(n);
+ }
+ }
+
+ /**
+ * Appends LCONST or LCONST_<n>
+ *
+ * @param n the pushed long integer constant.
+ */
+ public void addLconst(long n) {
+ if (n == 0 || n == 1)
+ addOpcode(9 + (int)n); // lconst_<n>
+ else
+ addLdc2w(n);
+ }
+
+ /**
+ * Appends LLOAD or (WIDE) LLOAD_<n>
+ *
+ * @param n an index into the local variable array.
+ */
+ public void addLload(int n) {
+ if (n < 4)
+ addOpcode(30 + n); // lload_<n>
+ else if (n < 0x100) {
+ addOpcode(LLOAD); // lload
+ add(n);
+ }
+ else {
+ addOpcode(WIDE);
+ addOpcode(LLOAD);
+ addIndex(n);
+ }
+ }
+
+ /**
+ * Appends LSTORE or LSTORE_<n>
+ *
+ * @param n an index into the local variable array.
+ */
+ public void addLstore(int n) {
+ if (n < 4)
+ addOpcode(63 + n); // lstore_<n>
+ else if (n < 0x100) {
+ addOpcode(LSTORE); // lstore
+ add(n);
+ }
+ else {
+ addOpcode(WIDE);
+ addOpcode(LSTORE);
+ addIndex(n);
+ }
+ }
+
+ /**
+ * Appends DCONST or DCONST_<n>
+ *
+ * @param d the pushed double constant.
+ */
+ public void addDconst(double d) {
+ if (d == 0.0 || d == 1.0)
+ addOpcode(14 + (int)d); // dconst_<n>
+ else
+ addLdc2w(d);
+ }
+
+ /**
+ * Appends DLOAD or (WIDE) DLOAD_<n>
+ *
+ * @param n an index into the local variable array.
+ */
+ public void addDload(int n) {
+ if (n < 4)
+ addOpcode(38 + n); // dload_<n>
+ else if (n < 0x100) {
+ addOpcode(DLOAD); // dload
+ add(n);
+ }
+ else {
+ addOpcode(WIDE);
+ addOpcode(DLOAD);
+ addIndex(n);
+ }
+ }
+
+ /**
+ * Appends DSTORE or (WIDE) DSTORE_<n>
+ *
+ * @param n an index into the local variable array.
+ */
+ public void addDstore(int n) {
+ if (n < 4)
+ addOpcode(71 + n); // dstore_<n>
+ else if (n < 0x100) {
+ addOpcode(DSTORE); // dstore
+ add(n);
+ }
+ else {
+ addOpcode(WIDE);
+ addOpcode(DSTORE);
+ addIndex(n);
+ }
+ }
+
+ /**
+ * Appends FCONST or FCONST_<n>
+ *
+ * @param f the pushed float constant.
+ */
+ public void addFconst(float f) {
+ if (f == 0.0f || f == 1.0f || f == 2.0f)
+ addOpcode(11 + (int)f); // fconst_<n>
+ else
+ addLdc(constPool.addFloatInfo(f));
+ }
+
+ /**
+ * Appends FLOAD or (WIDE) FLOAD_<n>
+ *
+ * @param n an index into the local variable array.
+ */
+ public void addFload(int n) {
+ if (n < 4)
+ addOpcode(34 + n); // fload_<n>
+ else if (n < 0x100) {
+ addOpcode(FLOAD); // fload
+ add(n);
+ }
+ else {
+ addOpcode(WIDE);
+ addOpcode(FLOAD);
+ addIndex(n);
+ }
+ }
+
+ /**
+ * Appends FSTORE or FSTORE_<n>
+ *
+ * @param n an index into the local variable array.
+ */
+ public void addFstore(int n) {
+ if (n < 4)
+ addOpcode(67 + n); // fstore_<n>
+ else if (n < 0x100) {
+ addOpcode(FSTORE); // fstore
+ add(n);
+ }
+ else {
+ addOpcode(WIDE);
+ addOpcode(FSTORE);
+ addIndex(n);
+ }
+ }
+
+ /**
+ * Appends an instruction for loading a value from the
+ * local variable at the index <code>n</code>.
+ *
+ * @param n the index.
+ * @param type the type of the loaded value.
+ * @return the size of the value (1 or 2 word).
+ */
+ public int addLoad(int n, CtClass type) {
+ if (type.isPrimitive()) {
+ if (type == CtClass.booleanType || type == CtClass.charType
+ || type == CtClass.byteType || type == CtClass.shortType
+ || type == CtClass.intType)
+ addIload(n);
+ else if (type == CtClass.longType) {
+ addLload(n);
+ return 2;
+ }
+ else if(type == CtClass.floatType)
+ addFload(n);
+ else if(type == CtClass.doubleType) {
+ addDload(n);
+ return 2;
+ }
+ else
+ throw new RuntimeException("void type?");
+ }
+ else
+ addAload(n);
+
+ return 1;
+ }
+
+ /**
+ * Appends an instruction for storing a value into the
+ * local variable at the index <code>n</code>.
+ *
+ * @param n the index.
+ * @param type the type of the stored value.
+ * @return 2 if the type is long or double. Otherwise 1.
+ */
+ public int addStore(int n, CtClass type) {
+ if (type.isPrimitive()) {
+ if (type == CtClass.booleanType || type == CtClass.charType
+ || type == CtClass.byteType || type == CtClass.shortType
+ || type == CtClass.intType)
+ addIstore(n);
+ else if (type == CtClass.longType) {
+ addLstore(n);
+ return 2;
+ }
+ else if (type == CtClass.floatType)
+ addFstore(n);
+ else if (type == CtClass.doubleType) {
+ addDstore(n);
+ return 2;
+ }
+ else
+ throw new RuntimeException("void type?");
+ }
+ else
+ addAstore(n);
+
+ return 1;
+ }
+
+ /**
+ * Appends instructions for loading all the parameters onto the
+ * operand stack.
+ *
+ * @param offset the index of the first parameter. It is 0
+ * if the method is static. Otherwise, it is 1.
+ */
+ public int addLoadParameters(CtClass[] params, int offset) {
+ int stacksize = 0;
+ if (params != null) {
+ int n = params.length;
+ for (int i = 0; i < n; ++i)
+ stacksize += addLoad(stacksize + offset, params[i]);
+ }
+
+ return stacksize;
+ }
+
+ /**
+ * Appends CHECKCAST.
+ *
+ * @param c the type.
+ */
+ public void addCheckcast(CtClass c) {
+ addOpcode(CHECKCAST);
+ addIndex(constPool.addClassInfo(c));
+ }
+
+ /**
+ * Appends CHECKCAST.
+ *
+ * @param classname a fully-qualified class name.
+ */
+ public void addCheckcast(String classname) {
+ addOpcode(CHECKCAST);
+ addIndex(constPool.addClassInfo(classname));
+ }
+
+ /**
+ * Appends INSTANCEOF.
+ *
+ * @param classname the class name.
+ */
+ public void addInstanceof(String classname) {
+ addOpcode(INSTANCEOF);
+ addIndex(constPool.addClassInfo(classname));
+ }
+
+ /**
+ * Appends GETFIELD.
+ *
+ * @param c the class.
+ * @param name the field name.
+ * @param type the descriptor of the field type.
+ *
+ * @see Descriptor#of(CtClass)
+ */
+ public void addGetfield(CtClass c, String name, String type) {
+ add(GETFIELD);
+ int ci = constPool.addClassInfo(c);
+ addIndex(constPool.addFieldrefInfo(ci, name, type));
+ growStack(Descriptor.dataSize(type) - 1);
+ }
+
+ /**
+ * Appends GETFIELD.
+ *
+ * @param c the fully-qualified class name.
+ * @param name the field name.
+ * @param type the descriptor of the field type.
+ *
+ * @see Descriptor#of(CtClass)
+ */
+ public void addGetfield(String c, String name, String type) {
+ add(GETFIELD);
+ int ci = constPool.addClassInfo(c);
+ addIndex(constPool.addFieldrefInfo(ci, name, type));
+ growStack(Descriptor.dataSize(type) - 1);
+ }
+
+ /**
+ * Appends GETSTATIC.
+ *
+ * @param c the class
+ * @param name the field name
+ * @param type the descriptor of the field type.
+ *
+ * @see Descriptor#of(CtClass)
+ */
+ public void addGetstatic(CtClass c, String name, String type) {
+ add(GETSTATIC);
+ int ci = constPool.addClassInfo(c);
+ addIndex(constPool.addFieldrefInfo(ci, name, type));
+ growStack(Descriptor.dataSize(type));
+ }
+
+ /**
+ * Appends GETSTATIC.
+ *
+ * @param c the fully-qualified class name
+ * @param name the field name
+ * @param type the descriptor of the field type.
+ *
+ * @see Descriptor#of(CtClass)
+ */
+ public void addGetstatic(String c, String name, String type) {
+ add(GETSTATIC);
+ int ci = constPool.addClassInfo(c);
+ addIndex(constPool.addFieldrefInfo(ci, name, type));
+ growStack(Descriptor.dataSize(type));
+ }
+
+ /**
+ * Appends INVOKESPECIAL.
+ *
+ * @param clazz the target class.
+ * @param name the method name.
+ * @param returnType the return type.
+ * @param paramTypes the parameter types.
+ */
+ public void addInvokespecial(CtClass clazz, String name,
+ CtClass returnType, CtClass[] paramTypes) {
+ String desc = Descriptor.ofMethod(returnType, paramTypes);
+ addInvokespecial(clazz, name, desc);
+ }
+
+ /**
+ * Appends INVOKESPECIAL.
+ *
+ * @param clazz the target class.
+ * @param name the method name
+ * @param desc the descriptor of the method signature.
+ *
+ * @see Descriptor#ofMethod(CtClass,CtClass[])
+ * @see Descriptor#ofConstructor(CtClass[])
+ */
+ public void addInvokespecial(CtClass clazz, String name, String desc) {
+ addInvokespecial(constPool.addClassInfo(clazz), name, desc);
+ }
+
+ /**
+ * Appends INVOKESPECIAL.
+ *
+ * @param clazz the fully-qualified class name.
+ * @param name the method name
+ * @param desc the descriptor of the method signature.
+ *
+ * @see Descriptor#ofMethod(CtClass,CtClass[])
+ * @see Descriptor#ofConstructor(CtClass[])
+ */
+ public void addInvokespecial(String clazz, String name, String desc) {
+ addInvokespecial(constPool.addClassInfo(clazz), name, desc);
+ }
+
+ /**
+ * Appends INVOKESPECIAL.
+ *
+ * @param clazz the index of <code>CONSTANT_Class_info</code>
+ * structure.
+ * @param name the method name
+ * @param desc the descriptor of the method signature.
+ *
+ * @see Descriptor#ofMethod(CtClass,CtClass[])
+ * @see Descriptor#ofConstructor(CtClass[])
+ */
+ public void addInvokespecial(int clazz, String name, String desc) {
+ add(INVOKESPECIAL);
+ addIndex(constPool.addMethodrefInfo(clazz, name, desc));
+ growStack(Descriptor.dataSize(desc) - 1);
+ }
+
+ /**
+ * Appends INVOKESTATIC.
+ *
+ * @param clazz the target class.
+ * @param name the method name
+ * @param returnType the return type.
+ * @param paramTypes the parameter types.
+ */
+ public void addInvokestatic(CtClass clazz, String name,
+ CtClass returnType, CtClass[] paramTypes) {
+ String desc = Descriptor.ofMethod(returnType, paramTypes);
+ addInvokestatic(clazz, name, desc);
+ }
+
+ /**
+ * Appends INVOKESTATIC.
+ *
+ * @param clazz the target class.
+ * @param name the method name
+ * @param desc the descriptor of the method signature.
+ *
+ * @see Descriptor#ofMethod(CtClass,CtClass[])
+ */
+ public void addInvokestatic(CtClass clazz, String name, String desc) {
+ addInvokestatic(constPool.addClassInfo(clazz), name, desc);
+ }
+
+ /**
+ * Appends INVOKESTATIC.
+ *
+ * @param classname the fully-qualified class name.
+ * @param name the method name
+ * @param desc the descriptor of the method signature.
+ *
+ * @see Descriptor#ofMethod(CtClass,CtClass[])
+ */
+ public void addInvokestatic(String classname, String name, String desc) {
+ addInvokestatic(constPool.addClassInfo(classname), name, desc);
+ }
+
+ /**
+ * Appends INVOKESTATIC.
+ *
+ * @param clazz the index of <code>CONSTANT_Class_info</code>
+ * structure.
+ * @param name the method name
+ * @param desc the descriptor of the method signature.
+ *
+ * @see Descriptor#ofMethod(CtClass,CtClass[])
+ */
+ public void addInvokestatic(int clazz, String name, String desc) {
+ add(INVOKESTATIC);
+ addIndex(constPool.addMethodrefInfo(clazz, name, desc));
+ growStack(Descriptor.dataSize(desc));
+ }
+
+ /**
+ * Appends INVOKEVIRTUAL.
+ *
+ * <p>The specified method must not be an inherited method.
+ * It must be directly declared in the class specified
+ * in <code>clazz</code>.
+ *
+ * @param clazz the target class.
+ * @param name the method name
+ * @param returnType the return type.
+ * @param paramTypes the parameter types.
+ */
+ public void addInvokevirtual(CtClass clazz, String name,
+ CtClass returnType, CtClass[] paramTypes) {
+ String desc = Descriptor.ofMethod(returnType, paramTypes);
+ addInvokevirtual(clazz, name, desc);
+ }
+
+ /**
+ * Appends INVOKEVIRTUAL.
+ *
+ * <p>The specified method must not be an inherited method.
+ * It must be directly declared in the class specified
+ * in <code>clazz</code>.
+ *
+ * @param clazz the target class.
+ * @param name the method name
+ * @param desc the descriptor of the method signature.
+ *
+ * @see Descriptor#ofMethod(CtClass,CtClass[])
+ */
+ public void addInvokevirtual(CtClass clazz, String name, String desc) {
+ addInvokevirtual(constPool.addClassInfo(clazz), name, desc);
+ }
+
+ /**
+ * Appends INVOKEVIRTUAL.
+ *
+ * <p>The specified method must not be an inherited method.
+ * It must be directly declared in the class specified
+ * in <code>classname</code>.
+ *
+ * @param classname the fully-qualified class name.
+ * @param name the method name
+ * @param desc the descriptor of the method signature.
+ *
+ * @see Descriptor#ofMethod(CtClass,CtClass[])
+ */
+ public void addInvokevirtual(String classname, String name, String desc) {
+ addInvokevirtual(constPool.addClassInfo(classname), name, desc);
+ }
+
+ /**
+ * Appends INVOKEVIRTUAL.
+ *
+ * <p>The specified method must not be an inherited method.
+ * It must be directly declared in the class specified
+ * by <code>clazz</code>.
+ *
+ * @param clazz the index of <code>CONSTANT_Class_info</code>
+ * structure.
+ * @param name the method name
+ * @param desc the descriptor of the method signature.
+ *
+ * @see Descriptor#ofMethod(CtClass,CtClass[])
+ */
+ public void addInvokevirtual(int clazz, String name, String desc) {
+ add(INVOKEVIRTUAL);
+ addIndex(constPool.addMethodrefInfo(clazz, name, desc));
+ growStack(Descriptor.dataSize(desc) - 1);
+ }
+
+ /**
+ * Appends INVOKEINTERFACE.
+ *
+ * @param clazz the target class.
+ * @param name the method name
+ * @param returnType the return type.
+ * @param paramTypes the parameter types.
+ * @param count the count operand of the instruction.
+ */
+ public void addInvokeinterface(CtClass clazz, String name,
+ CtClass returnType, CtClass[] paramTypes,
+ int count) {
+ String desc = Descriptor.ofMethod(returnType, paramTypes);
+ addInvokeinterface(clazz, name, desc, count);
+ }
+
+ /**
+ * Appends INVOKEINTERFACE.
+ *
+ * @param clazz the target class.
+ * @param name the method name
+ * @param desc the descriptor of the method signature.
+ * @param count the count operand of the instruction.
+ *
+ * @see Descriptor#ofMethod(CtClass,CtClass[])
+ */
+ public void addInvokeinterface(CtClass clazz, String name,
+ String desc, int count) {
+ addInvokeinterface(constPool.addClassInfo(clazz), name, desc,
+ count);
+ }
+
+ /**
+ * Appends INVOKEINTERFACE.
+ *
+ * @param classname the fully-qualified class name.
+ * @param name the method name
+ * @param desc the descriptor of the method signature.
+ * @param count the count operand of the instruction.
+ *
+ * @see Descriptor#ofMethod(CtClass,CtClass[])
+ */
+ public void addInvokeinterface(String classname, String name,
+ String desc, int count) {
+ addInvokeinterface(constPool.addClassInfo(classname), name, desc,
+ count);
+ }
+
+ /**
+ * Appends INVOKEINTERFACE.
+ *
+ * @param clazz the index of <code>CONSTANT_Class_info</code>
+ * structure.
+ * @param name the method name
+ * @param desc the descriptor of the method signature.
+ * @param count the count operand of the instruction.
+ *
+ * @see Descriptor#ofMethod(CtClass,CtClass[])
+ */
+ public void addInvokeinterface(int clazz, String name,
+ String desc, int count) {
+ add(INVOKEINTERFACE);
+ addIndex(constPool.addInterfaceMethodrefInfo(clazz, name, desc));
+ add(count);
+ add(0);
+ growStack(Descriptor.dataSize(desc) - 1);
+ }
+
+ /**
+ * Appends LDC or LDC_W. The pushed item is a <code>String</code>
+ * object.
+ *
+ * @param s the character string pushed by LDC or LDC_W.
+ */
+ public void addLdc(String s) {
+ addLdc(constPool.addStringInfo(s));
+ }
+
+ /**
+ * Appends LDC or LDC_W.
+ *
+ * @param i index into the constant pool.
+ */
+ public void addLdc(int i) {
+ if (i > 0xFF) {
+ addOpcode(LDC_W);
+ addIndex(i);
+ }
+ else {
+ addOpcode(LDC);
+ add(i);
+ }
+ }
+
+ /**
+ * Appends LDC2_W. The pushed item is a long value.
+ */
+ public void addLdc2w(long l) {
+ addOpcode(LDC2_W);
+ addIndex(constPool.addLongInfo(l));
+ }
+
+ /**
+ * Appends LDC2_W. The pushed item is a double value.
+ */
+ public void addLdc2w(double d) {
+ addOpcode(LDC2_W);
+ addIndex(constPool.addDoubleInfo(d));
+ }
+
+ /**
+ * Appends NEW.
+ *
+ * @param clazz the class of the created instance.
+ */
+ public void addNew(CtClass clazz) {
+ addOpcode(NEW);
+ addIndex(constPool.addClassInfo(clazz));
+ }
+
+ /**
+ * Appends NEW.
+ *
+ * @param classname the fully-qualified class name.
+ */
+ public void addNew(String classname) {
+ addOpcode(NEW);
+ addIndex(constPool.addClassInfo(classname));
+ }
+
+ /**
+ * Appends ANEWARRAY.
+ *
+ * @param classname the qualified class name of the element type.
+ */
+ public void addAnewarray(String classname) {
+ addOpcode(ANEWARRAY);
+ addIndex(constPool.addClassInfo(classname));
+ }
+
+ /**
+ * Appends ICONST and ANEWARRAY.
+ *
+ * @param clazz the elememnt type.
+ * @param length the array length.
+ */
+ public void addAnewarray(CtClass clazz, int length) {
+ addIconst(length);
+ addOpcode(ANEWARRAY);
+ addIndex(constPool.addClassInfo(clazz));
+ }
+
+ /**
+ * Appends NEWARRAY for primitive types.
+ *
+ * @param atype <code>T_BOOLEAN</code>, <code>T_CHAR</code>, ...
+ * @see Opcode
+ */
+ public void addNewarray(int atype, int length) {
+ addIconst(length);
+ addOpcode(NEWARRAY);
+ add(atype);
+ }
+
+ /**
+ * Appends MULTINEWARRAY.
+ *
+ * @param clazz the array type.
+ * @param dimensions the sizes of all dimensions.
+ * @return the length of <code>dimensions</code>.
+ */
+ public int addMultiNewarray(CtClass clazz, int[] dimensions) {
+ int len = dimensions.length;
+ for (int i = 0; i < len; ++i)
+ addIconst(dimensions[i]);
+
+ growStack(len);
+ return addMultiNewarray(clazz, len);
+ }
+
+ /**
+ * Appends MULTINEWARRAY. The size of every dimension must have been
+ * already pushed on the stack.
+ *
+ * @param clazz the array type.
+ * @param dim the number of the dimensions.
+ * @return the value of <code>dim</code>.
+ */
+ public int addMultiNewarray(CtClass clazz, int dim) {
+ add(MULTIANEWARRAY);
+ addIndex(constPool.addClassInfo(clazz));
+ add(dim);
+ growStack(1 - dim);
+ return dim;
+ }
+
+ /**
+ * Appends MULTINEWARRAY.
+ *
+ * @param desc the type descriptor of the created array.
+ * @param dim dimensions.
+ * @return the value of <code>dim</code>.
+ */
+ public int addMultiNewarray(String desc, int dim) {
+ add(MULTIANEWARRAY);
+ addIndex(constPool.addClassInfo(desc));
+ add(dim);
+ growStack(1 - dim);
+ return dim;
+ }
+
+ /**
+ * Appends PUTFIELD.
+ *
+ * @param c the target class.
+ * @param name the field name.
+ * @param desc the descriptor of the field type.
+ */
+ public void addPutfield(CtClass c, String name, String desc) {
+ addPutfield0(c, null, name, desc);
+ }
+
+ /**
+ * Appends PUTFIELD.
+ *
+ * @param classname the fully-qualified name of the target class.
+ * @param name the field name.
+ * @param desc the descriptor of the field type.
+ */
+ public void addPutfield(String classname, String name, String desc) {
+ // if classnaem is null, the target class is THIS.
+ addPutfield0(null, classname, name, desc);
+ }
+
+ private void addPutfield0(CtClass target, String classname,
+ String name, String desc) {
+ add(PUTFIELD);
+ // target is null if it represents THIS.
+ int ci = classname == null ? constPool.addClassInfo(target)
+ : constPool.addClassInfo(classname);
+ addIndex(constPool.addFieldrefInfo(ci, name, desc));
+ growStack(-1 - Descriptor.dataSize(desc));
+ }
+
+ /**
+ * Appends PUTSTATIC.
+ *
+ * @param c the target class.
+ * @param name the field name.
+ * @param desc the descriptor of the field type.
+ */
+ public void addPutstatic(CtClass c, String name, String desc) {
+ addPutstatic0(c, null, name, desc);
+ }
+
+ /**
+ * Appends PUTSTATIC.
+ *
+ * @param classname the fully-qualified name of the target class.
+ * @param fieldName the field name.
+ * @param desc the descriptor of the field type.
+ */
+ public void addPutstatic(String classname, String fieldName, String desc) {
+ // if classname is null, the target class is THIS.
+ addPutstatic0(null, classname, fieldName, desc);
+ }
+
+ private void addPutstatic0(CtClass target, String classname,
+ String fieldName, String desc) {
+ add(PUTSTATIC);
+ // target is null if it represents THIS.
+ int ci = classname == null ? constPool.addClassInfo(target)
+ : constPool.addClassInfo(classname);
+ addIndex(constPool.addFieldrefInfo(ci, fieldName, desc));
+ growStack(-Descriptor.dataSize(desc));
+ }
+
+ /**
+ * Appends ARETURN, IRETURN, .., or RETURN.
+ *
+ * @param type the return type.
+ */
+ public void addReturn(CtClass type) {
+ if (type == null)
+ addOpcode(RETURN);
+ else if (type.isPrimitive()) {
+ CtPrimitiveType ptype = (CtPrimitiveType)type;
+ addOpcode(ptype.getReturnOp());
+ }
+ else
+ addOpcode(ARETURN);
+ }
+
+ /**
+ * Appends RET.
+ *
+ * @param var local variable
+ */
+ public void addRet(int var) {
+ if (var < 0x100) {
+ addOpcode(RET);
+ add(var);
+ }
+ else {
+ addOpcode(WIDE);
+ addOpcode(RET);
+ addIndex(var);
+ }
+ }
+
+ /**
+ * Appends instructions for executing
+ * <code>java.lang.System.println(<i>message</i>)</code>.
+ *
+ * @param message printed message.
+ */
+ public void addPrintln(String message) {
+ addGetstatic("java.lang.System", "err", "Ljava/io/PrintStream;");
+ addLdc(message);
+ addInvokevirtual("java.io.PrintStream",
+ "println", "(Ljava/lang/String;)V");
+ }
+}
diff --git a/src/main/javassist/bytecode/ClassFile.java b/src/main/javassist/bytecode/ClassFile.java
new file mode 100644
index 0000000..d07d108
--- /dev/null
+++ b/src/main/javassist/bytecode/ClassFile.java
@@ -0,0 +1,889 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import javassist.CannotCompileException;
+
+/**
+ * <code>ClassFile</code> represents a Java <code>.class</code> file, which
+ * consists of a constant pool, methods, fields, and attributes.
+ *
+ * @see javassist.CtClass#getClassFile()
+ */
+public final class ClassFile {
+ int major, minor; // version number
+ ConstPool constPool;
+ int thisClass;
+ int accessFlags;
+ int superClass;
+ int[] interfaces;
+ ArrayList fields;
+ ArrayList methods;
+ ArrayList attributes;
+ String thisclassname; // not JVM-internal name
+ String[] cachedInterfaces;
+ String cachedSuperclass;
+
+ /**
+ * The major version number of class files
+ * for JDK 1.1.
+ */
+ public static final int JAVA_1 = 45;
+
+ /**
+ * The major version number of class files
+ * for JDK 1.2.
+ */
+ public static final int JAVA_2 = 46;
+
+ /**
+ * The major version number of class files
+ * for JDK 1.3.
+ */
+ public static final int JAVA_3 = 47;
+
+ /**
+ * The major version number of class files
+ * for JDK 1.4.
+ */
+ public static final int JAVA_4 = 48;
+
+ /**
+ * The major version number of class files
+ * for JDK 1.5.
+ */
+ public static final int JAVA_5 = 49;
+
+ /**
+ * The major version number of class files
+ * for JDK 1.6.
+ */
+ public static final int JAVA_6 = 50;
+
+ /**
+ * The major version number of class files
+ * for JDK 1.7.
+ */
+ public static final int JAVA_7 = 51;
+
+ /**
+ * The major version number of class files created
+ * from scratch. The default value is 47 (JDK 1.3)
+ * or 49 (JDK 1.5) if the JVM supports <code>java.lang.StringBuilder</code>.
+ */
+ public static int MAJOR_VERSION = JAVA_3;
+
+ static {
+ try {
+ Class.forName("java.lang.StringBuilder");
+ MAJOR_VERSION = JAVA_5;
+ }
+ catch (Throwable t) {}
+ }
+
+ /**
+ * Constructs a class file from a byte stream.
+ */
+ public ClassFile(DataInputStream in) throws IOException {
+ read(in);
+ }
+
+ /**
+ * Constructs a class file including no members.
+ *
+ * @param isInterface
+ * true if this is an interface. false if this is a class.
+ * @param classname
+ * a fully-qualified class name
+ * @param superclass
+ * a fully-qualified super class name
+ */
+ public ClassFile(boolean isInterface, String classname, String superclass) {
+ major = MAJOR_VERSION;
+ minor = 0; // JDK 1.3 or later
+ constPool = new ConstPool(classname);
+ thisClass = constPool.getThisClassInfo();
+ if (isInterface)
+ accessFlags = AccessFlag.INTERFACE | AccessFlag.ABSTRACT;
+ else
+ accessFlags = AccessFlag.SUPER;
+
+ initSuperclass(superclass);
+ interfaces = null;
+ fields = new ArrayList();
+ methods = new ArrayList();
+ thisclassname = classname;
+
+ attributes = new ArrayList();
+ attributes.add(new SourceFileAttribute(constPool,
+ getSourcefileName(thisclassname)));
+ }
+
+ private void initSuperclass(String superclass) {
+ if (superclass != null) {
+ this.superClass = constPool.addClassInfo(superclass);
+ cachedSuperclass = superclass;
+ }
+ else {
+ this.superClass = constPool.addClassInfo("java.lang.Object");
+ cachedSuperclass = "java.lang.Object";
+ }
+ }
+
+ private static String getSourcefileName(String qname) {
+ int index = qname.lastIndexOf('.');
+ if (index >= 0)
+ qname = qname.substring(index + 1);
+
+ return qname + ".java";
+ }
+
+ /**
+ * Eliminates dead constant pool items. If a method or a field is removed,
+ * the constant pool items used by that method/field become dead items. This
+ * method recreates a constant pool.
+ */
+ public void compact() {
+ ConstPool cp = compact0();
+ ArrayList list = methods;
+ int n = list.size();
+ for (int i = 0; i < n; ++i) {
+ MethodInfo minfo = (MethodInfo)list.get(i);
+ minfo.compact(cp);
+ }
+
+ list = fields;
+ n = list.size();
+ for (int i = 0; i < n; ++i) {
+ FieldInfo finfo = (FieldInfo)list.get(i);
+ finfo.compact(cp);
+ }
+
+ attributes = AttributeInfo.copyAll(attributes, cp);
+ constPool = cp;
+ }
+
+ private ConstPool compact0() {
+ ConstPool cp = new ConstPool(thisclassname);
+ thisClass = cp.getThisClassInfo();
+ String sc = getSuperclass();
+ if (sc != null)
+ superClass = cp.addClassInfo(getSuperclass());
+
+ if (interfaces != null) {
+ int n = interfaces.length;
+ for (int i = 0; i < n; ++i)
+ interfaces[i]
+ = cp.addClassInfo(constPool.getClassInfo(interfaces[i]));
+ }
+
+ return cp;
+ }
+
+ /**
+ * Discards all attributes, associated with both the class file and the
+ * members such as a code attribute and exceptions attribute. The unused
+ * constant pool entries are also discarded (a new packed constant pool is
+ * constructed).
+ */
+ public void prune() {
+ ConstPool cp = compact0();
+ ArrayList newAttributes = new ArrayList();
+ AttributeInfo invisibleAnnotations
+ = getAttribute(AnnotationsAttribute.invisibleTag);
+ if (invisibleAnnotations != null) {
+ invisibleAnnotations = invisibleAnnotations.copy(cp, null);
+ newAttributes.add(invisibleAnnotations);
+ }
+
+ AttributeInfo visibleAnnotations
+ = getAttribute(AnnotationsAttribute.visibleTag);
+ if (visibleAnnotations != null) {
+ visibleAnnotations = visibleAnnotations.copy(cp, null);
+ newAttributes.add(visibleAnnotations);
+ }
+
+ AttributeInfo signature
+ = getAttribute(SignatureAttribute.tag);
+ if (signature != null) {
+ signature = signature.copy(cp, null);
+ newAttributes.add(signature);
+ }
+
+ ArrayList list = methods;
+ int n = list.size();
+ for (int i = 0; i < n; ++i) {
+ MethodInfo minfo = (MethodInfo)list.get(i);
+ minfo.prune(cp);
+ }
+
+ list = fields;
+ n = list.size();
+ for (int i = 0; i < n; ++i) {
+ FieldInfo finfo = (FieldInfo)list.get(i);
+ finfo.prune(cp);
+ }
+
+ attributes = newAttributes;
+ constPool = cp;
+ }
+
+ /**
+ * Returns a constant pool table.
+ */
+ public ConstPool getConstPool() {
+ return constPool;
+ }
+
+ /**
+ * Returns true if this is an interface.
+ */
+ public boolean isInterface() {
+ return (accessFlags & AccessFlag.INTERFACE) != 0;
+ }
+
+ /**
+ * Returns true if this is a final class or interface.
+ */
+ public boolean isFinal() {
+ return (accessFlags & AccessFlag.FINAL) != 0;
+ }
+
+ /**
+ * Returns true if this is an abstract class or an interface.
+ */
+ public boolean isAbstract() {
+ return (accessFlags & AccessFlag.ABSTRACT) != 0;
+ }
+
+ /**
+ * Returns access flags.
+ *
+ * @see javassist.bytecode.AccessFlag
+ */
+ public int getAccessFlags() {
+ return accessFlags;
+ }
+
+ /**
+ * Changes access flags.
+ *
+ * @see javassist.bytecode.AccessFlag
+ */
+ public void setAccessFlags(int acc) {
+ if ((acc & AccessFlag.INTERFACE) == 0)
+ acc |= AccessFlag.SUPER;
+
+ accessFlags = acc;
+ }
+
+ /**
+ * Returns access and property flags of this nested class.
+ * This method returns -1 if the class is not a nested class.
+ *
+ * <p>The returned value is obtained from <code>inner_class_access_flags</code>
+ * of the entry representing this nested class itself
+ * in <code>InnerClasses_attribute</code>>.
+ */
+ public int getInnerAccessFlags() {
+ InnerClassesAttribute ica
+ = (InnerClassesAttribute)getAttribute(InnerClassesAttribute.tag);
+ if (ica == null)
+ return -1;
+
+ String name = getName();
+ int n = ica.tableLength();
+ for (int i = 0; i < n; ++i)
+ if (name.equals(ica.innerClass(i)))
+ return ica.accessFlags(i);
+
+ return -1;
+ }
+
+ /**
+ * Returns the class name.
+ */
+ public String getName() {
+ return thisclassname;
+ }
+
+ /**
+ * Sets the class name. This method substitutes the new name for all
+ * occurrences of the old class name in the class file.
+ */
+ public void setName(String name) {
+ renameClass(thisclassname, name);
+ }
+
+ /**
+ * Returns the super class name.
+ */
+ public String getSuperclass() {
+ if (cachedSuperclass == null)
+ cachedSuperclass = constPool.getClassInfo(superClass);
+
+ return cachedSuperclass;
+ }
+
+ /**
+ * Returns the index of the constant pool entry representing the super
+ * class.
+ */
+ public int getSuperclassId() {
+ return superClass;
+ }
+
+ /**
+ * Sets the super class.
+ *
+ * <p>
+ * The new super class should inherit from the old super class.
+ * This method modifies constructors so that they call constructors declared
+ * in the new super class.
+ */
+ public void setSuperclass(String superclass) throws CannotCompileException {
+ if (superclass == null)
+ superclass = "java.lang.Object";
+
+ try {
+ this.superClass = constPool.addClassInfo(superclass);
+ ArrayList list = methods;
+ int n = list.size();
+ for (int i = 0; i < n; ++i) {
+ MethodInfo minfo = (MethodInfo)list.get(i);
+ minfo.setSuperclass(superclass);
+ }
+ }
+ catch (BadBytecode e) {
+ throw new CannotCompileException(e);
+ }
+ cachedSuperclass = superclass;
+ }
+
+ /**
+ * Replaces all occurrences of a class name in the class file.
+ *
+ * <p>
+ * If class X is substituted for class Y in the class file, X and Y must
+ * have the same signature. If Y provides a method m(), X must provide it
+ * even if X inherits m() from the super class. If this fact is not
+ * guaranteed, the bytecode verifier may cause an error.
+ *
+ * @param oldname
+ * the replaced class name
+ * @param newname
+ * the substituted class name
+ */
+ public final void renameClass(String oldname, String newname) {
+ ArrayList list;
+ int n;
+
+ if (oldname.equals(newname))
+ return;
+
+ if (oldname.equals(thisclassname))
+ thisclassname = newname;
+
+ oldname = Descriptor.toJvmName(oldname);
+ newname = Descriptor.toJvmName(newname);
+ constPool.renameClass(oldname, newname);
+
+ AttributeInfo.renameClass(attributes, oldname, newname);
+ list = methods;
+ n = list.size();
+ for (int i = 0; i < n; ++i) {
+ MethodInfo minfo = (MethodInfo)list.get(i);
+ String desc = minfo.getDescriptor();
+ minfo.setDescriptor(Descriptor.rename(desc, oldname, newname));
+ AttributeInfo.renameClass(minfo.getAttributes(), oldname, newname);
+ }
+
+ list = fields;
+ n = list.size();
+ for (int i = 0; i < n; ++i) {
+ FieldInfo finfo = (FieldInfo)list.get(i);
+ String desc = finfo.getDescriptor();
+ finfo.setDescriptor(Descriptor.rename(desc, oldname, newname));
+ AttributeInfo.renameClass(finfo.getAttributes(), oldname, newname);
+ }
+ }
+
+ /**
+ * Replaces all occurrences of several class names in the class file.
+ *
+ * @param classnames
+ * specifies which class name is replaced with which new name.
+ * Class names must be described with the JVM-internal
+ * representation like <code>java/lang/Object</code>.
+ * @see #renameClass(String,String)
+ */
+ public final void renameClass(Map classnames) {
+ String jvmNewThisName = (String)classnames.get(Descriptor
+ .toJvmName(thisclassname));
+ if (jvmNewThisName != null)
+ thisclassname = Descriptor.toJavaName(jvmNewThisName);
+
+ constPool.renameClass(classnames);
+
+ AttributeInfo.renameClass(attributes, classnames);
+ ArrayList list = methods;
+ int n = list.size();
+ for (int i = 0; i < n; ++i) {
+ MethodInfo minfo = (MethodInfo)list.get(i);
+ String desc = minfo.getDescriptor();
+ minfo.setDescriptor(Descriptor.rename(desc, classnames));
+ AttributeInfo.renameClass(minfo.getAttributes(), classnames);
+ }
+
+ list = fields;
+ n = list.size();
+ for (int i = 0; i < n; ++i) {
+ FieldInfo finfo = (FieldInfo)list.get(i);
+ String desc = finfo.getDescriptor();
+ finfo.setDescriptor(Descriptor.rename(desc, classnames));
+ AttributeInfo.renameClass(finfo.getAttributes(), classnames);
+ }
+ }
+
+ /**
+ * Internal-use only.
+ * <code>CtClass.getRefClasses()</code> calls this method.
+ */
+ public final void getRefClasses(Map classnames) {
+ constPool.renameClass(classnames);
+
+ AttributeInfo.getRefClasses(attributes, classnames);
+ ArrayList list = methods;
+ int n = list.size();
+ for (int i = 0; i < n; ++i) {
+ MethodInfo minfo = (MethodInfo)list.get(i);
+ String desc = minfo.getDescriptor();
+ Descriptor.rename(desc, classnames);
+ AttributeInfo.getRefClasses(minfo.getAttributes(), classnames);
+ }
+
+ list = fields;
+ n = list.size();
+ for (int i = 0; i < n; ++i) {
+ FieldInfo finfo = (FieldInfo)list.get(i);
+ String desc = finfo.getDescriptor();
+ Descriptor.rename(desc, classnames);
+ AttributeInfo.getRefClasses(finfo.getAttributes(), classnames);
+ }
+ }
+
+ /**
+ * Returns the names of the interfaces implemented by the class.
+ * The returned array is read only.
+ */
+ public String[] getInterfaces() {
+ if (cachedInterfaces != null)
+ return cachedInterfaces;
+
+ String[] rtn = null;
+ if (interfaces == null)
+ rtn = new String[0];
+ else {
+ int n = interfaces.length;
+ String[] list = new String[n];
+ for (int i = 0; i < n; ++i)
+ list[i] = constPool.getClassInfo(interfaces[i]);
+
+ rtn = list;
+ }
+
+ cachedInterfaces = rtn;
+ return rtn;
+ }
+
+ /**
+ * Sets the interfaces.
+ *
+ * @param nameList
+ * the names of the interfaces.
+ */
+ public void setInterfaces(String[] nameList) {
+ cachedInterfaces = null;
+ if (nameList != null) {
+ int n = nameList.length;
+ interfaces = new int[n];
+ for (int i = 0; i < n; ++i)
+ interfaces[i] = constPool.addClassInfo(nameList[i]);
+ }
+ }
+
+ /**
+ * Appends an interface to the interfaces implemented by the class.
+ */
+ public void addInterface(String name) {
+ cachedInterfaces = null;
+ int info = constPool.addClassInfo(name);
+ if (interfaces == null) {
+ interfaces = new int[1];
+ interfaces[0] = info;
+ }
+ else {
+ int n = interfaces.length;
+ int[] newarray = new int[n + 1];
+ System.arraycopy(interfaces, 0, newarray, 0, n);
+ newarray[n] = info;
+ interfaces = newarray;
+ }
+ }
+
+ /**
+ * Returns all the fields declared in the class.
+ *
+ * @return a list of <code>FieldInfo</code>.
+ * @see FieldInfo
+ */
+ public List getFields() {
+ return fields;
+ }
+
+ /**
+ * Appends a field to the class.
+ *
+ * @throws DuplicateMemberException when the field is already included.
+ */
+ public void addField(FieldInfo finfo) throws DuplicateMemberException {
+ testExistingField(finfo.getName(), finfo.getDescriptor());
+ fields.add(finfo);
+ }
+
+ /**
+ * Just appends a field to the class.
+ * It does not check field duplication.
+ * Use this method only when minimizing performance overheads
+ * is seriously required.
+ *
+ * @since 3.13
+ */
+ public final void addField2(FieldInfo finfo) {
+ fields.add(finfo);
+ }
+
+ private void testExistingField(String name, String descriptor)
+ throws DuplicateMemberException {
+ ListIterator it = fields.listIterator(0);
+ while (it.hasNext()) {
+ FieldInfo minfo = (FieldInfo)it.next();
+ if (minfo.getName().equals(name))
+ throw new DuplicateMemberException("duplicate field: " + name);
+ }
+ }
+
+ /**
+ * Returns all the methods declared in the class.
+ *
+ * @return a list of <code>MethodInfo</code>.
+ * @see MethodInfo
+ */
+ public List getMethods() {
+ return methods;
+ }
+
+ /**
+ * Returns the method with the specified name. If there are multiple methods
+ * with that name, this method returns one of them.
+ *
+ * @return null if no such method is found.
+ */
+ public MethodInfo getMethod(String name) {
+ ArrayList list = methods;
+ int n = list.size();
+ for (int i = 0; i < n; ++i) {
+ MethodInfo minfo = (MethodInfo)list.get(i);
+ if (minfo.getName().equals(name))
+ return minfo;
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns a static initializer (class initializer), or null if it does not
+ * exist.
+ */
+ public MethodInfo getStaticInitializer() {
+ return getMethod(MethodInfo.nameClinit);
+ }
+
+ /**
+ * Appends a method to the class.
+ * If there is a bridge method with the same name and signature,
+ * then the bridge method is removed before a new method is added.
+ *
+ * @throws DuplicateMemberException when the method is already included.
+ */
+ public void addMethod(MethodInfo minfo) throws DuplicateMemberException {
+ testExistingMethod(minfo);
+ methods.add(minfo);
+ }
+
+ /**
+ * Just appends a method to the class.
+ * It does not check method duplication or remove a bridge method.
+ * Use this method only when minimizing performance overheads
+ * is seriously required.
+ *
+ * @since 3.13
+ */
+ public final void addMethod2(MethodInfo minfo) {
+ methods.add(minfo);
+ }
+
+ private void testExistingMethod(MethodInfo newMinfo)
+ throws DuplicateMemberException
+ {
+ String name = newMinfo.getName();
+ String descriptor = newMinfo.getDescriptor();
+ ListIterator it = methods.listIterator(0);
+ while (it.hasNext())
+ if (isDuplicated(newMinfo, name, descriptor, (MethodInfo)it.next(), it))
+ throw new DuplicateMemberException("duplicate method: " + name
+ + " in " + this.getName());
+ }
+
+ private static boolean isDuplicated(MethodInfo newMethod, String newName,
+ String newDesc, MethodInfo minfo,
+ ListIterator it)
+ {
+ if (!minfo.getName().equals(newName))
+ return false;
+
+ String desc = minfo.getDescriptor();
+ if (!Descriptor.eqParamTypes(desc, newDesc))
+ return false;
+
+ if (desc.equals(newDesc)) {
+ if (notBridgeMethod(minfo))
+ return true;
+ else {
+ it.remove();
+ return false;
+ }
+ }
+ else
+ return notBridgeMethod(minfo) && notBridgeMethod(newMethod);
+ }
+
+ /* For a bridge method, see Sec. 15.12.4.5 of JLS 3rd Ed.
+ */
+ private static boolean notBridgeMethod(MethodInfo minfo) {
+ return (minfo.getAccessFlags() & AccessFlag.BRIDGE) == 0;
+ }
+
+ /**
+ * Returns all the attributes. The returned <code>List</code> object
+ * is shared with this object. If you add a new attribute to the list,
+ * the attribute is also added to the classs file represented by this
+ * object. If you remove an attribute from the list, it is also removed
+ * from the class file.
+ *
+ * @return a list of <code>AttributeInfo</code> objects.
+ * @see AttributeInfo
+ */
+ public List getAttributes() {
+ return attributes;
+ }
+
+ /**
+ * Returns the attribute with the specified name. If there are multiple
+ * attributes with that name, this method returns either of them. It
+ * returns null if the specified attributed is not found.
+ *
+ * @param name attribute name
+ * @see #getAttributes()
+ */
+ public AttributeInfo getAttribute(String name) {
+ ArrayList list = attributes;
+ int n = list.size();
+ for (int i = 0; i < n; ++i) {
+ AttributeInfo ai = (AttributeInfo)list.get(i);
+ if (ai.getName().equals(name))
+ return ai;
+ }
+
+ return null;
+ }
+
+ /**
+ * Appends an attribute. If there is already an attribute with the same
+ * name, the new one substitutes for it.
+ *
+ * @see #getAttributes()
+ */
+ public void addAttribute(AttributeInfo info) {
+ AttributeInfo.remove(attributes, info.getName());
+ attributes.add(info);
+ }
+
+ /**
+ * Returns the source file containing this class.
+ *
+ * @return null if this information is not available.
+ */
+ public String getSourceFile() {
+ SourceFileAttribute sf
+ = (SourceFileAttribute)getAttribute(SourceFileAttribute.tag);
+ if (sf == null)
+ return null;
+ else
+ return sf.getFileName();
+ }
+
+ private void read(DataInputStream in) throws IOException {
+ int i, n;
+ int magic = in.readInt();
+ if (magic != 0xCAFEBABE)
+ throw new IOException("bad magic number: " + Integer.toHexString(magic));
+
+ minor = in.readUnsignedShort();
+ major = in.readUnsignedShort();
+ constPool = new ConstPool(in);
+ accessFlags = in.readUnsignedShort();
+ thisClass = in.readUnsignedShort();
+ constPool.setThisClassInfo(thisClass);
+ superClass = in.readUnsignedShort();
+ n = in.readUnsignedShort();
+ if (n == 0)
+ interfaces = null;
+ else {
+ interfaces = new int[n];
+ for (i = 0; i < n; ++i)
+ interfaces[i] = in.readUnsignedShort();
+ }
+
+ ConstPool cp = constPool;
+ n = in.readUnsignedShort();
+ fields = new ArrayList();
+ for (i = 0; i < n; ++i)
+ addField2(new FieldInfo(cp, in));
+
+ n = in.readUnsignedShort();
+ methods = new ArrayList();
+ for (i = 0; i < n; ++i)
+ addMethod2(new MethodInfo(cp, in));
+
+ attributes = new ArrayList();
+ n = in.readUnsignedShort();
+ for (i = 0; i < n; ++i)
+ addAttribute(AttributeInfo.read(cp, in));
+
+ thisclassname = constPool.getClassInfo(thisClass);
+ }
+
+ /**
+ * Writes a class file represened by this object into an output stream.
+ */
+ public void write(DataOutputStream out) throws IOException {
+ int i, n;
+
+ out.writeInt(0xCAFEBABE); // magic
+ out.writeShort(minor); // minor version
+ out.writeShort(major); // major version
+ constPool.write(out); // constant pool
+ out.writeShort(accessFlags);
+ out.writeShort(thisClass);
+ out.writeShort(superClass);
+
+ if (interfaces == null)
+ n = 0;
+ else
+ n = interfaces.length;
+
+ out.writeShort(n);
+ for (i = 0; i < n; ++i)
+ out.writeShort(interfaces[i]);
+
+ ArrayList list = fields;
+ n = list.size();
+ out.writeShort(n);
+ for (i = 0; i < n; ++i) {
+ FieldInfo finfo = (FieldInfo)list.get(i);
+ finfo.write(out);
+ }
+
+ list = methods;
+ n = list.size();
+ out.writeShort(n);
+ for (i = 0; i < n; ++i) {
+ MethodInfo minfo = (MethodInfo)list.get(i);
+ minfo.write(out);
+ }
+
+ out.writeShort(attributes.size());
+ AttributeInfo.writeAll(attributes, out);
+ }
+
+ /**
+ * Get the Major version.
+ *
+ * @return the major version
+ */
+ public int getMajorVersion() {
+ return major;
+ }
+
+ /**
+ * Set the major version.
+ *
+ * @param major
+ * the major version
+ */
+ public void setMajorVersion(int major) {
+ this.major = major;
+ }
+
+ /**
+ * Get the minor version.
+ *
+ * @return the minor version
+ */
+ public int getMinorVersion() {
+ return minor;
+ }
+
+ /**
+ * Set the minor version.
+ *
+ * @param minor
+ * the minor version
+ */
+ public void setMinorVersion(int minor) {
+ this.minor = minor;
+ }
+
+ /**
+ * Sets the major and minor version to Java 5.
+ *
+ * If the major version is older than 49, Java 5
+ * extensions such as annotations are ignored
+ * by the JVM.
+ */
+ public void setVersionToJava5() {
+ this.major = 49;
+ this.minor = 0;
+ }
+}
diff --git a/src/main/javassist/bytecode/ClassFilePrinter.java b/src/main/javassist/bytecode/ClassFilePrinter.java
new file mode 100644
index 0000000..08078dc
--- /dev/null
+++ b/src/main/javassist/bytecode/ClassFilePrinter.java
@@ -0,0 +1,152 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode;
+
+import java.io.PrintWriter;
+import javassist.Modifier;
+import java.util.List;
+
+/**
+ * A utility class for priting the contents of a class file.
+ * It prints a constant pool table, fields, and methods in a
+ * human readable representation.
+ */
+public class ClassFilePrinter {
+ /**
+ * Prints the contents of a class file to the standard output stream.
+ */
+ public static void print(ClassFile cf) {
+ print(cf, new PrintWriter(System.out, true));
+ }
+
+ /**
+ * Prints the contents of a class file.
+ */
+ public static void print(ClassFile cf, PrintWriter out) {
+ List list;
+ int n;
+
+ /* 0x0020 (SYNCHRONIZED) means ACC_SUPER if the modifiers
+ * are of a class.
+ */
+ int mod
+ = AccessFlag.toModifier(cf.getAccessFlags()
+ & ~AccessFlag.SYNCHRONIZED);
+ out.println("major: " + cf.major + ", minor: " + cf.minor
+ + " modifiers: " + Integer.toHexString(cf.getAccessFlags()));
+ out.println(Modifier.toString(mod) + " class "
+ + cf.getName() + " extends " + cf.getSuperclass());
+
+ String[] infs = cf.getInterfaces();
+ if (infs != null && infs.length > 0) {
+ out.print(" implements ");
+ out.print(infs[0]);
+ for (int i = 1; i < infs.length; ++i)
+ out.print(", " + infs[i]);
+
+ out.println();
+ }
+
+ out.println();
+ list = cf.getFields();
+ n = list.size();
+ for (int i = 0; i < n; ++i) {
+ FieldInfo finfo = (FieldInfo)list.get(i);
+ int acc = finfo.getAccessFlags();
+ out.println(Modifier.toString(AccessFlag.toModifier(acc))
+ + " " + finfo.getName() + "\t"
+ + finfo.getDescriptor());
+ printAttributes(finfo.getAttributes(), out, 'f');
+ }
+
+ out.println();
+ list = cf.getMethods();
+ n = list.size();
+ for (int i = 0; i < n; ++i) {
+ MethodInfo minfo = (MethodInfo)list.get(i);
+ int acc = minfo.getAccessFlags();
+ out.println(Modifier.toString(AccessFlag.toModifier(acc))
+ + " " + minfo.getName() + "\t"
+ + minfo.getDescriptor());
+ printAttributes(minfo.getAttributes(), out, 'm');
+ out.println();
+ }
+
+ out.println();
+ printAttributes(cf.getAttributes(), out, 'c');
+ }
+
+ static void printAttributes(List list, PrintWriter out, char kind) {
+ if (list == null)
+ return;
+
+ int n = list.size();
+ for (int i = 0; i < n; ++i) {
+ AttributeInfo ai = (AttributeInfo)list.get(i);
+ if (ai instanceof CodeAttribute) {
+ CodeAttribute ca = (CodeAttribute)ai;
+ out.println("attribute: " + ai.getName() + ": "
+ + ai.getClass().getName());
+ out.println("max stack " + ca.getMaxStack()
+ + ", max locals " + ca.getMaxLocals()
+ + ", " + ca.getExceptionTable().size()
+ + " catch blocks");
+ out.println("<code attribute begin>");
+ printAttributes(ca.getAttributes(), out, kind);
+ out.println("<code attribute end>");
+ }
+ else if (ai instanceof AnnotationsAttribute) {
+ out.println("annnotation: " + ai.toString());
+ }
+ else if (ai instanceof ParameterAnnotationsAttribute) {
+ out.println("parameter annnotations: " + ai.toString());
+ }
+ else if (ai instanceof StackMapTable) {
+ out.println("<stack map table begin>");
+ StackMapTable.Printer.print((StackMapTable)ai, out);
+ out.println("<stack map table end>");
+ }
+ else if (ai instanceof StackMap) {
+ out.println("<stack map begin>");
+ ((StackMap)ai).print(out);
+ out.println("<stack map end>");
+ }
+ else if (ai instanceof SignatureAttribute) {
+ SignatureAttribute sa = (SignatureAttribute)ai;
+ String sig = sa.getSignature();
+ out.println("signature: " + sig);
+ try {
+ String s;
+ if (kind == 'c')
+ s = SignatureAttribute.toClassSignature(sig).toString();
+ else if (kind == 'm')
+ s = SignatureAttribute.toMethodSignature(sig).toString();
+ else
+ s = SignatureAttribute.toFieldSignature(sig).toString();
+
+ out.println(" " + s);
+ }
+ catch (BadBytecode e) {
+ out.println(" syntax error");
+ }
+ }
+ else
+ out.println("attribute: " + ai.getName()
+ + " (" + ai.get().length + " byte): "
+ + ai.getClass().getName());
+ }
+ }
+}
diff --git a/src/main/javassist/bytecode/ClassFileWriter.java b/src/main/javassist/bytecode/ClassFileWriter.java
new file mode 100644
index 0000000..bb7342a
--- /dev/null
+++ b/src/main/javassist/bytecode/ClassFileWriter.java
@@ -0,0 +1,731 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2010 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode;
+
+import java.io.OutputStream;
+import java.io.DataOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+/**
+ * A quick class-file writer. This is useful when a generated
+ * class file is simple and the code generation should be fast.
+ *
+ * <p>Example:
+ *
+ * <blockquote><pre>
+ * ClassFileWriter cfw = new ClassFileWriter(ClassFile.JAVA_4, 0);
+ * ConstPoolWriter cpw = cfw.getConstPool();
+ *
+ * FieldWriter fw = cfw.getFieldWriter();
+ * fw.add(AccessFlag.PUBLIC, "value", "I", null);
+ * fw.add(AccessFlag.PUBLIC, "value2", "J", null);
+ *
+ * int thisClass = cpw.addClassInfo("sample/Test");
+ * int superClass = cpw.addClassInfo("java/lang/Object");
+ *
+ * MethodWriter mw = cfw.getMethodWriter();
+ *
+ * mw.begin(AccessFlag.PUBLIC, MethodInfo.nameInit, "()V", null, null);
+ * mw.add(Opcode.ALOAD_0);
+ * mw.add(Opcode.INVOKESPECIAL);
+ * int signature = cpw.addNameAndTypeInfo(MethodInfo.nameInit, "()V");
+ * mw.add16(cpw.addMethodrefInfo(superClass, signature));
+ * mw.add(Opcode.RETURN);
+ * mw.codeEnd(1, 1);
+ * mw.end(null, null);
+ *
+ * mw.begin(AccessFlag.PUBLIC, "one", "()I", null, null);
+ * mw.add(Opcode.ICONST_1);
+ * mw.add(Opcode.IRETURN);
+ * mw.codeEnd(1, 1);
+ * mw.end(null, null);
+ *
+ * byte[] classfile = cfw.end(AccessFlag.PUBLIC, thisClass, superClass,
+ * null, null);
+ * </pre></blockquote>
+ *
+ * <p>The code above generates the following class:
+ *
+ * <blockquote><pre>
+ * package sample;
+ * public class Test {
+ * public int value;
+ * public long value2;
+ * public Test() { super(); }
+ * public one() { return 1; }
+ * }
+ * </pre></blockquote>
+ *
+ * @since 3.13
+ */
+public class ClassFileWriter {
+ private ByteStream output;
+ private ConstPoolWriter constPool;
+ private FieldWriter fields;
+ private MethodWriter methods;
+ int thisClass, superClass;
+
+ /**
+ * Constructs a class file writer.
+ *
+ * @param major the major version ({@link ClassFile#JAVA_4}, {@link ClassFile#JAVA_5}, ...).
+ * @param minor the minor version (0 for JDK 1.3 and later).
+ */
+ public ClassFileWriter(int major, int minor) {
+ output = new ByteStream(512);
+ output.writeInt(0xCAFEBABE); // magic
+ output.writeShort(minor);
+ output.writeShort(major);
+ constPool = new ConstPoolWriter(output);
+ fields = new FieldWriter(constPool);
+ methods = new MethodWriter(constPool);
+
+ }
+
+ /**
+ * Returns a constant pool.
+ */
+ public ConstPoolWriter getConstPool() { return constPool; }
+
+ /**
+ * Returns a filed writer.
+ */
+ public FieldWriter getFieldWriter() { return fields; }
+
+ /**
+ * Returns a method writer.
+ */
+ public MethodWriter getMethodWriter() { return methods; }
+
+ /**
+ * Ends writing and returns the contents of the class file.
+ *
+ * @param accessFlags access flags.
+ * @param thisClass this class. an index indicating its <code>CONSTANT_Class_info</code>.
+ * @param superClass super class. an index indicating its <code>CONSTANT_Class_info</code>.
+ * @param interfaces implemented interfaces.
+ * index numbers indicating their <code>ClassInfo</code>.
+ * It may be null.
+ * @param aw attributes of the class file. May be null.
+ *
+ * @see AccessFlag
+ */
+ public byte[] end(int accessFlags, int thisClass, int superClass,
+ int[] interfaces, AttributeWriter aw) {
+ constPool.end();
+ output.writeShort(accessFlags);
+ output.writeShort(thisClass);
+ output.writeShort(superClass);
+ if (interfaces == null)
+ output.writeShort(0);
+ else {
+ int n = interfaces.length;
+ output.writeShort(n);
+ for (int i = 0; i < n; i++)
+ output.writeShort(interfaces[i]);
+ }
+
+ output.enlarge(fields.dataSize() + methods.dataSize() + 6);
+ try {
+ output.writeShort(fields.size());
+ fields.write(output);
+
+ output.writeShort(methods.size());
+ methods.write(output);
+ }
+ catch (IOException e) {}
+
+ writeAttribute(output, aw, 0);
+ return output.toByteArray();
+ }
+
+ /**
+ * Ends writing and writes the contents of the class file into the
+ * given output stream.
+ *
+ * @param accessFlags access flags.
+ * @param thisClass this class. an index indicating its <code>CONSTANT_Class_info</code>.
+ * @param superClass super class. an index indicating its <code>CONSTANT_Class_info</code>.
+ * @param interfaces implemented interfaces.
+ * index numbers indicating their <code>CONSTATNT_Class_info</code>.
+ * It may be null.
+ * @param aw attributes of the class file. May be null.
+ *
+ * @see AccessFlag
+ */
+ public void end(DataOutputStream out,
+ int accessFlags, int thisClass, int superClass,
+ int[] interfaces, AttributeWriter aw)
+ throws IOException
+ {
+ constPool.end();
+ output.writeTo(out);
+ out.writeShort(accessFlags);
+ out.writeShort(thisClass);
+ out.writeShort(superClass);
+ if (interfaces == null)
+ out.writeShort(0);
+ else {
+ int n = interfaces.length;
+ out.writeShort(n);
+ for (int i = 0; i < n; i++)
+ out.writeShort(interfaces[i]);
+ }
+
+ out.writeShort(fields.size());
+ fields.write(out);
+
+ out.writeShort(methods.size());
+ methods.write(out);
+ if (aw == null)
+ out.writeShort(0);
+ else {
+ out.writeShort(aw.size());
+ aw.write(out);
+ }
+ }
+
+ /**
+ * This writes attributes.
+ *
+ * <p>For example, the following object writes a synthetic attribute:
+ *
+ * <pre>
+ * ConstPoolWriter cpw = ...;
+ * final int tag = cpw.addUtf8Info("Synthetic");
+ * AttributeWriter aw = new AttributeWriter() {
+ * public int size() {
+ * return 1;
+ * }
+ * public void write(DataOutputStream out) throws java.io.IOException {
+ * out.writeShort(tag);
+ * out.writeInt(0);
+ * }
+ * };
+ * </pre>
+ */
+ public static interface AttributeWriter {
+ /**
+ * Returns the number of attributes that this writer will
+ * write.
+ */
+ public int size();
+
+ /**
+ * Writes all the contents of the attributes. The binary representation
+ * of the contents is an array of <code>attribute_info</code>.
+ */
+ public void write(DataOutputStream out) throws IOException;
+ }
+
+ static void writeAttribute(ByteStream bs, AttributeWriter aw, int attrCount) {
+ if (aw == null) {
+ bs.writeShort(attrCount);
+ return;
+ }
+
+ bs.writeShort(aw.size() + attrCount);
+ DataOutputStream dos = new DataOutputStream(bs);
+ try {
+ aw.write(dos);
+ dos.flush();
+ }
+ catch (IOException e) {}
+ }
+
+ /**
+ * Field.
+ */
+ public static final class FieldWriter {
+ protected ByteStream output;
+ protected ConstPoolWriter constPool;
+ private int fieldCount;
+
+ FieldWriter(ConstPoolWriter cp) {
+ output = new ByteStream(128);
+ constPool = cp;
+ fieldCount = 0;
+ }
+
+ /**
+ * Adds a new field.
+ *
+ * @param accessFlags access flags.
+ * @param name the field name.
+ * @param descriptor the field type.
+ * @param aw the attributes of the field. may be null.
+ * @see AccessFlag
+ */
+ public void add(int accessFlags, String name, String descriptor, AttributeWriter aw) {
+ int nameIndex = constPool.addUtf8Info(name);
+ int descIndex = constPool.addUtf8Info(descriptor);
+ add(accessFlags, nameIndex, descIndex, aw);
+ }
+
+ /**
+ * Adds a new field.
+ *
+ * @param accessFlags access flags.
+ * @param name the field name. an index indicating its <code>CONSTANT_Utf8_info</code>.
+ * @param descriptor the field type. an index indicating its <code>CONSTANT_Utf8_info</code>.
+ * @param aw the attributes of the field. may be null.
+ * @see AccessFlag
+ */
+ public void add(int accessFlags, int name, int descriptor, AttributeWriter aw) {
+ ++fieldCount;
+ output.writeShort(accessFlags);
+ output.writeShort(name);
+ output.writeShort(descriptor);
+ writeAttribute(output, aw, 0);
+ }
+
+ int size() { return fieldCount; }
+
+ int dataSize() { return output.size(); }
+
+ /**
+ * Writes the added fields.
+ */
+ void write(OutputStream out) throws IOException {
+ output.writeTo(out);
+ }
+ }
+
+ /**
+ * Method.
+ */
+ public static final class MethodWriter {
+ protected ByteStream output;
+ protected ConstPoolWriter constPool;
+ private int methodCount;
+ protected int codeIndex;
+ protected int throwsIndex;
+ protected int stackIndex;
+
+ private int startPos;
+ private boolean isAbstract;
+ private int catchPos;
+ private int catchCount;
+
+ MethodWriter(ConstPoolWriter cp) {
+ output = new ByteStream(256);
+ constPool = cp;
+ methodCount = 0;
+ codeIndex = 0;
+ throwsIndex = 0;
+ stackIndex = 0;
+ }
+
+ /**
+ * Starts Adding a new method.
+ *
+ * @param accessFlags access flags.
+ * @param name the method name.
+ * @param descriptor the method signature.
+ * @param exceptions throws clause. It may be null.
+ * The class names must be the JVM-internal
+ * representations like <code>java/lang/Exception</code>.
+ * @param aw attributes to the <code>Method_info</code>.
+ */
+ public void begin(int accessFlags, String name, String descriptor,
+ String[] exceptions, AttributeWriter aw) {
+ int nameIndex = constPool.addUtf8Info(name);
+ int descIndex = constPool.addUtf8Info(descriptor);
+ int[] intfs;
+ if (exceptions == null)
+ intfs = null;
+ else
+ intfs = constPool.addClassInfo(exceptions);
+
+ begin(accessFlags, nameIndex, descIndex, intfs, aw);
+ }
+
+ /**
+ * Starts adding a new method.
+ *
+ * @param accessFlags access flags.
+ * @param name the method name. an index indicating its <code>CONSTANT_Utf8_info</code>.
+ * @param descriptor the field type. an index indicating its <code>CONSTANT_Utf8_info</code>.
+ * @param exceptions throws clause. indexes indicating <code>CONSTANT_Class_info</code>s.
+ * It may be null.
+ * @param aw attributes to the <code>Method_info</code>.
+ */
+ public void begin(int accessFlags, int name, int descriptor, int[] exceptions, AttributeWriter aw) {
+ ++methodCount;
+ output.writeShort(accessFlags);
+ output.writeShort(name);
+ output.writeShort(descriptor);
+ isAbstract = (accessFlags & AccessFlag.ABSTRACT) != 0;
+
+ int attrCount = isAbstract ? 0 : 1;
+ if (exceptions != null)
+ ++attrCount;
+
+ writeAttribute(output, aw, attrCount);
+
+ if (exceptions != null)
+ writeThrows(exceptions);
+
+ if (!isAbstract) {
+ if (codeIndex == 0)
+ codeIndex = constPool.addUtf8Info(CodeAttribute.tag);
+
+ startPos = output.getPos();
+ output.writeShort(codeIndex);
+ output.writeBlank(12); // attribute_length, maxStack, maxLocals, code_lenth
+ }
+
+ catchPos = -1;
+ catchCount = 0;
+ }
+
+ private void writeThrows(int[] exceptions) {
+ if (throwsIndex == 0)
+ throwsIndex = constPool.addUtf8Info(ExceptionsAttribute.tag);
+
+ output.writeShort(throwsIndex);
+ output.writeInt(exceptions.length * 2 + 2);
+ output.writeShort(exceptions.length);
+ for (int i = 0; i < exceptions.length; i++)
+ output.writeShort(exceptions[i]);
+ }
+
+ /**
+ * Appends an 8bit value of bytecode.
+ *
+ * @see Opcode
+ */
+ public void add(int b) {
+ output.write(b);
+ }
+
+ /**
+ * Appends a 16bit value of bytecode.
+ */
+ public void add16(int b) {
+ output.writeShort(b);
+ }
+
+ /**
+ * Appends a 32bit value of bytecode.
+ */
+ public void add32(int b) {
+ output.writeInt(b);
+ }
+
+ /**
+ * Appends a invokevirtual, inovkespecial, or invokestatic bytecode.
+ *
+ * @see Opcode
+ */
+ public void addInvoke(int opcode, String targetClass, String methodName,
+ String descriptor) {
+ int target = constPool.addClassInfo(targetClass);
+ int nt = constPool.addNameAndTypeInfo(methodName, descriptor);
+ int method = constPool.addMethodrefInfo(target, nt);
+ add(opcode);
+ add16(method);
+ }
+
+ /**
+ * Ends appending bytecode.
+ */
+ public void codeEnd(int maxStack, int maxLocals) {
+ if (!isAbstract) {
+ output.writeShort(startPos + 6, maxStack);
+ output.writeShort(startPos + 8, maxLocals);
+ output.writeInt(startPos + 10, output.getPos() - startPos - 14); // code_length
+ catchPos = output.getPos();
+ catchCount = 0;
+ output.writeShort(0); // number of catch clauses
+ }
+ }
+
+ /**
+ * Appends an <code>exception_table</code> entry to the
+ * <code>Code_attribute</code>. This method is available
+ * only after the <code>codeEnd</code> method is called.
+ *
+ * @param catchType an index indicating a <code>CONSTANT_Class_info</code>.
+ */
+ public void addCatch(int startPc, int endPc, int handlerPc, int catchType) {
+ ++catchCount;
+ output.writeShort(startPc);
+ output.writeShort(endPc);
+ output.writeShort(handlerPc);
+ output.writeShort(catchType);
+ }
+
+ /**
+ * Ends adding a new method. The <code>add</code> method must be
+ * called before the <code>end</code> method is called.
+ *
+ * @param smap a stack map table. may be null.
+ * @param aw attributes to the <code>Code_attribute</code>.
+ * may be null.
+ */
+ public void end(StackMapTable.Writer smap, AttributeWriter aw) {
+ if (isAbstract)
+ return;
+
+ // exception_table_length
+ output.writeShort(catchPos, catchCount);
+
+ int attrCount = smap == null ? 0 : 1;
+ writeAttribute(output, aw, attrCount);
+
+ if (smap != null) {
+ if (stackIndex == 0)
+ stackIndex = constPool.addUtf8Info(StackMapTable.tag);
+
+ output.writeShort(stackIndex);
+ byte[] data = smap.toByteArray();
+ output.writeInt(data.length);
+ output.write(data);
+ }
+
+ // Code attribute_length
+ output.writeInt(startPos + 2, output.getPos() - startPos - 6);
+ }
+
+ int size() { return methodCount; }
+
+ int dataSize() { return output.size(); }
+
+ /**
+ * Writes the added methods.
+ */
+ void write(OutputStream out) throws IOException {
+ output.writeTo(out);
+ }
+ }
+
+ /**
+ * Constant Pool.
+ */
+ public static final class ConstPoolWriter {
+ ByteStream output;
+ protected int startPos;
+ protected int num;
+
+ ConstPoolWriter(ByteStream out) {
+ output = out;
+ startPos = out.getPos();
+ num = 1;
+ output.writeShort(1); // number of entries
+ }
+
+ /**
+ * Makes <code>CONSTANT_Class_info</code> objects for each class name.
+ *
+ * @return an array of indexes indicating <code>CONSTANT_Class_info</code>s.
+ */
+ public int[] addClassInfo(String[] classNames) {
+ int n = classNames.length;
+ int[] result = new int[n];
+ for (int i = 0; i < n; i++)
+ result[i] = addClassInfo(classNames[i]);
+
+ return result;
+ }
+
+ /**
+ * Adds a new <code>CONSTANT_Class_info</code> structure.
+ *
+ * <p>This also adds a <code>CONSTANT_Utf8_info</code> structure
+ * for storing the class name.
+ *
+ * @param jvmname the JVM-internal representation of a class name.
+ * e.g. <code>java/lang/Object</code>.
+ * @return the index of the added entry.
+ */
+ public int addClassInfo(String jvmname) {
+ int utf8 = addUtf8Info(jvmname);
+ output.write(ClassInfo.tag);
+ output.writeShort(utf8);
+ return num++;
+ }
+
+ /**
+ * Adds a new <code>CONSTANT_Class_info</code> structure.
+ *
+ * @param name <code>name_index</code>
+ * @return the index of the added entry.
+ */
+ public int addClassInfo(int name) {
+ output.write(ClassInfo.tag);
+ output.writeShort(name);
+ return num++;
+ }
+
+ /**
+ * Adds a new <code>CONSTANT_NameAndType_info</code> structure.
+ *
+ * @param name <code>name_index</code>
+ * @param type <code>descriptor_index</code>
+ * @return the index of the added entry.
+ */
+ public int addNameAndTypeInfo(String name, String type) {
+ return addNameAndTypeInfo(addUtf8Info(name), addUtf8Info(type));
+ }
+
+ /**
+ * Adds a new <code>CONSTANT_NameAndType_info</code> structure.
+ *
+ * @param name <code>name_index</code>
+ * @param type <code>descriptor_index</code>
+ * @return the index of the added entry.
+ */
+ public int addNameAndTypeInfo(int name, int type) {
+ output.write(NameAndTypeInfo.tag);
+ output.writeShort(name);
+ output.writeShort(type);
+ return num++;
+ }
+
+ /**
+ * Adds a new <code>CONSTANT_Fieldref_info</code> structure.
+ *
+ * @param classInfo <code>class_index</code>
+ * @param nameAndTypeInfo <code>name_and_type_index</code>.
+ * @return the index of the added entry.
+ */
+ public int addFieldrefInfo(int classInfo, int nameAndTypeInfo) {
+ output.write(FieldrefInfo.tag);
+ output.writeShort(classInfo);
+ output.writeShort(nameAndTypeInfo);
+ return num++;
+ }
+
+ /**
+ * Adds a new <code>CONSTANT_Methodref_info</code> structure.
+ *
+ * @param classInfo <code>class_index</code>
+ * @param nameAndTypeInfo <code>name_and_type_index</code>.
+ * @return the index of the added entry.
+ */
+ public int addMethodrefInfo(int classInfo, int nameAndTypeInfo) {
+ output.write(MethodrefInfo.tag);
+ output.writeShort(classInfo);
+ output.writeShort(nameAndTypeInfo);
+ return num++;
+ }
+
+ /**
+ * Adds a new <code>CONSTANT_InterfaceMethodref_info</code>
+ * structure.
+ *
+ * @param classInfo <code>class_index</code>
+ * @param nameAndTypeInfo <code>name_and_type_index</code>.
+ * @return the index of the added entry.
+ */
+ public int addInterfaceMethodrefInfo(int classInfo,
+ int nameAndTypeInfo) {
+ output.write(InterfaceMethodrefInfo.tag);
+ output.writeShort(classInfo);
+ output.writeShort(nameAndTypeInfo);
+ return num++;
+ }
+
+ /**
+ * Adds a new <code>CONSTANT_String_info</code>
+ * structure.
+ *
+ * <p>This also adds a new <code>CONSTANT_Utf8_info</code>
+ * structure.
+ *
+ * @return the index of the added entry.
+ */
+ public int addStringInfo(String str) {
+ int utf8 = addUtf8Info(str);
+ output.write(StringInfo.tag);
+ output.writeShort(utf8);
+ return num++;
+ }
+
+ /**
+ * Adds a new <code>CONSTANT_Integer_info</code>
+ * structure.
+ *
+ * @return the index of the added entry.
+ */
+ public int addIntegerInfo(int i) {
+ output.write(IntegerInfo.tag);
+ output.writeInt(i);
+ return num++;
+ }
+
+ /**
+ * Adds a new <code>CONSTANT_Float_info</code>
+ * structure.
+ *
+ * @return the index of the added entry.
+ */
+ public int addFloatInfo(float f) {
+ output.write(FloatInfo.tag);
+ output.writeFloat(f);
+ return num++;
+ }
+
+ /**
+ * Adds a new <code>CONSTANT_Long_info</code>
+ * structure.
+ *
+ * @return the index of the added entry.
+ */
+ public int addLongInfo(long l) {
+ output.write(LongInfo.tag);
+ output.writeLong(l);
+ int n = num;
+ num += 2;
+ return n;
+ }
+
+ /**
+ * Adds a new <code>CONSTANT_Double_info</code>
+ * structure.
+ *
+ * @return the index of the added entry.
+ */
+ public int addDoubleInfo(double d) {
+ output.write(DoubleInfo.tag);
+ output.writeDouble(d);
+ int n = num;
+ num += 2;
+ return n;
+ }
+
+ /**
+ * Adds a new <code>CONSTANT_Utf8_info</code>
+ * structure.
+ *
+ * @return the index of the added entry.
+ */
+ public int addUtf8Info(String utf8) {
+ output.write(Utf8Info.tag);
+ output.writeUTF(utf8);
+ return num++;
+ }
+
+ /**
+ * Writes the contents of this class pool.
+ */
+ void end() {
+ output.writeShort(startPos, num);
+ }
+ }
+}
diff --git a/src/main/javassist/bytecode/CodeAnalyzer.java b/src/main/javassist/bytecode/CodeAnalyzer.java
new file mode 100644
index 0000000..a078e1f
--- /dev/null
+++ b/src/main/javassist/bytecode/CodeAnalyzer.java
@@ -0,0 +1,262 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode;
+
+/**
+ * Utility for computing <code>max_stack</code>.
+ */
+class CodeAnalyzer implements Opcode {
+ private ConstPool constPool;
+ private CodeAttribute codeAttr;
+
+ public CodeAnalyzer(CodeAttribute ca) {
+ codeAttr = ca;
+ constPool = ca.getConstPool();
+ }
+
+ public int computeMaxStack()
+ throws BadBytecode
+ {
+ /* d = stack[i]
+ * d == 0: not visited
+ * d > 0: the depth is d - 1 after executing the bytecode at i.
+ * d < 0: not visited. the initial depth (before execution) is 1 - d.
+ */
+ CodeIterator ci = codeAttr.iterator();
+ int length = ci.getCodeLength();
+ int[] stack = new int[length];
+ constPool = codeAttr.getConstPool();
+ initStack(stack, codeAttr);
+ boolean repeat;
+ do {
+ repeat = false;
+ for (int i = 0; i < length; ++i)
+ if (stack[i] < 0) {
+ repeat = true;
+ visitBytecode(ci, stack, i);
+ }
+ } while (repeat);
+
+ int maxStack = 1;
+ for (int i = 0; i < length; ++i)
+ if (stack[i] > maxStack)
+ maxStack = stack[i];
+
+ return maxStack - 1; // the base is 1.
+ }
+
+ private void initStack(int[] stack, CodeAttribute ca) {
+ stack[0] = -1;
+ ExceptionTable et = ca.getExceptionTable();
+ if (et != null) {
+ int size = et.size();
+ for (int i = 0; i < size; ++i)
+ stack[et.handlerPc(i)] = -2; // an exception is on stack
+ }
+ }
+
+ private void visitBytecode(CodeIterator ci, int[] stack, int index)
+ throws BadBytecode
+ {
+ int codeLength = stack.length;
+ ci.move(index);
+ int stackDepth = -stack[index];
+ int[] jsrDepth = new int[1];
+ jsrDepth[0] = -1;
+ while (ci.hasNext()) {
+ index = ci.next();
+ stack[index] = stackDepth;
+ int op = ci.byteAt(index);
+ stackDepth = visitInst(op, ci, index, stackDepth);
+ if (stackDepth < 1)
+ throw new BadBytecode("stack underflow at " + index);
+
+ if (processBranch(op, ci, index, codeLength, stack, stackDepth, jsrDepth))
+ break;
+
+ if (isEnd(op)) // return, ireturn, athrow, ...
+ break;
+
+ if (op == JSR || op == JSR_W)
+ --stackDepth;
+ }
+ }
+
+ private boolean processBranch(int opcode, CodeIterator ci, int index,
+ int codeLength, int[] stack, int stackDepth, int[] jsrDepth)
+ throws BadBytecode
+ {
+ if ((IFEQ <= opcode && opcode <= IF_ACMPNE)
+ || opcode == IFNULL || opcode == IFNONNULL) {
+ int target = index + ci.s16bitAt(index + 1);
+ checkTarget(index, target, codeLength, stack, stackDepth);
+ }
+ else {
+ int target, index2;
+ switch (opcode) {
+ case GOTO :
+ target = index + ci.s16bitAt(index + 1);
+ checkTarget(index, target, codeLength, stack, stackDepth);
+ return true;
+ case GOTO_W :
+ target = index + ci.s32bitAt(index + 1);
+ checkTarget(index, target, codeLength, stack, stackDepth);
+ return true;
+ case JSR :
+ case JSR_W :
+ if (opcode == JSR)
+ target = index + ci.s16bitAt(index + 1);
+ else
+ target = index + ci.s32bitAt(index + 1);
+
+ checkTarget(index, target, codeLength, stack, stackDepth);
+ /*
+ * It is unknown which RET comes back to this JSR.
+ * So we assume that if the stack depth at one JSR instruction
+ * is N, then it is also N at other JSRs and N - 1 at all RET
+ * instructions. Note that STACK_GROW[JSR] is 1 since it pushes
+ * a return address on the operand stack.
+ */
+ if (jsrDepth[0] < 0) {
+ jsrDepth[0] = stackDepth;
+ return false;
+ }
+ else if (stackDepth == jsrDepth[0])
+ return false;
+ else
+ throw new BadBytecode(
+ "sorry, cannot compute this data flow due to JSR: "
+ + stackDepth + "," + jsrDepth[0]);
+ case RET :
+ if (jsrDepth[0] < 0) {
+ jsrDepth[0] = stackDepth + 1;
+ return false;
+ }
+ else if (stackDepth + 1 == jsrDepth[0])
+ return true;
+ else
+ throw new BadBytecode(
+ "sorry, cannot compute this data flow due to RET: "
+ + stackDepth + "," + jsrDepth[0]);
+ case LOOKUPSWITCH :
+ case TABLESWITCH :
+ index2 = (index & ~3) + 4;
+ target = index + ci.s32bitAt(index2);
+ checkTarget(index, target, codeLength, stack, stackDepth);
+ if (opcode == LOOKUPSWITCH) {
+ int npairs = ci.s32bitAt(index2 + 4);
+ index2 += 12;
+ for (int i = 0; i < npairs; ++i) {
+ target = index + ci.s32bitAt(index2);
+ checkTarget(index, target, codeLength,
+ stack, stackDepth);
+ index2 += 8;
+ }
+ }
+ else {
+ int low = ci.s32bitAt(index2 + 4);
+ int high = ci.s32bitAt(index2 + 8);
+ int n = high - low + 1;
+ index2 += 12;
+ for (int i = 0; i < n; ++i) {
+ target = index + ci.s32bitAt(index2);
+ checkTarget(index, target, codeLength,
+ stack, stackDepth);
+ index2 += 4;
+ }
+ }
+
+ return true; // always branch.
+ }
+ }
+
+ return false; // may not branch.
+ }
+
+ private void checkTarget(int opIndex, int target, int codeLength,
+ int[] stack, int stackDepth)
+ throws BadBytecode
+ {
+ if (target < 0 || codeLength <= target)
+ throw new BadBytecode("bad branch offset at " + opIndex);
+
+ int d = stack[target];
+ if (d == 0)
+ stack[target] = -stackDepth;
+ else if (d != stackDepth && d != -stackDepth)
+ throw new BadBytecode("verification error (" + stackDepth +
+ "," + d + ") at " + opIndex);
+ }
+
+ private static boolean isEnd(int opcode) {
+ return (IRETURN <= opcode && opcode <= RETURN) || opcode == ATHROW;
+ }
+
+ /**
+ * Visits an instruction.
+ */
+ private int visitInst(int op, CodeIterator ci, int index, int stack)
+ throws BadBytecode
+ {
+ String desc;
+ switch (op) {
+ case GETFIELD :
+ stack += getFieldSize(ci, index) - 1;
+ break;
+ case PUTFIELD :
+ stack -= getFieldSize(ci, index) + 1;
+ break;
+ case GETSTATIC :
+ stack += getFieldSize(ci, index);
+ break;
+ case PUTSTATIC :
+ stack -= getFieldSize(ci, index);
+ break;
+ case INVOKEVIRTUAL :
+ case INVOKESPECIAL :
+ desc = constPool.getMethodrefType(ci.u16bitAt(index + 1));
+ stack += Descriptor.dataSize(desc) - 1;
+ break;
+ case INVOKESTATIC :
+ desc = constPool.getMethodrefType(ci.u16bitAt(index + 1));
+ stack += Descriptor.dataSize(desc);
+ break;
+ case INVOKEINTERFACE :
+ desc = constPool.getInterfaceMethodrefType(
+ ci.u16bitAt(index + 1));
+ stack += Descriptor.dataSize(desc) - 1;
+ break;
+ case ATHROW :
+ stack = 1; // the stack becomes empty (1 means no values).
+ break;
+ case MULTIANEWARRAY :
+ stack += 1 - ci.byteAt(index + 3);
+ break;
+ case WIDE :
+ op = ci.byteAt(index + 1);
+ // don't break here.
+ default :
+ stack += STACK_GROW[op];
+ }
+
+ return stack;
+ }
+
+ private int getFieldSize(CodeIterator ci, int index) {
+ String desc = constPool.getFieldrefType(ci.u16bitAt(index + 1));
+ return Descriptor.dataSize(desc);
+ }
+}
diff --git a/src/main/javassist/bytecode/CodeAttribute.java b/src/main/javassist/bytecode/CodeAttribute.java
new file mode 100644
index 0000000..99dca1d
--- /dev/null
+++ b/src/main/javassist/bytecode/CodeAttribute.java
@@ -0,0 +1,585 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * <code>Code_attribute</code>.
+ *
+ * <p>To browse the <code>code</code> field of
+ * a <code>Code_attribute</code> structure,
+ * use <code>CodeIterator</code>.
+ *
+ * @see CodeIterator
+ */
+public class CodeAttribute extends AttributeInfo implements Opcode {
+ /**
+ * The name of this attribute <code>"Code"</code>.
+ */
+ public static final String tag = "Code";
+
+ // code[] is stored in AttributeInfo.info.
+
+ private int maxStack;
+ private int maxLocals;
+ private ExceptionTable exceptions;
+ private ArrayList attributes;
+
+ /**
+ * Constructs a <code>Code_attribute</code>.
+ *
+ * @param cp constant pool table
+ * @param stack <code>max_stack</code>
+ * @param locals <code>max_locals</code>
+ * @param code <code>code[]</code>
+ * @param etable <code>exception_table[]</code>
+ */
+ public CodeAttribute(ConstPool cp, int stack, int locals, byte[] code,
+ ExceptionTable etable)
+ {
+ super(cp, tag);
+ maxStack = stack;
+ maxLocals = locals;
+ info = code;
+ exceptions = etable;
+ attributes = new ArrayList();
+ }
+
+ /**
+ * Constructs a copy of <code>Code_attribute</code>.
+ * Specified class names are replaced during the copy.
+ *
+ * @param cp constant pool table.
+ * @param src source Code attribute.
+ * @param classnames pairs of replaced and substituted
+ * class names.
+ */
+ private CodeAttribute(ConstPool cp, CodeAttribute src, Map classnames)
+ throws BadBytecode
+ {
+ super(cp, tag);
+
+ maxStack = src.getMaxStack();
+ maxLocals = src.getMaxLocals();
+ exceptions = src.getExceptionTable().copy(cp, classnames);
+ attributes = new ArrayList();
+ List src_attr = src.getAttributes();
+ int num = src_attr.size();
+ for (int i = 0; i < num; ++i) {
+ AttributeInfo ai = (AttributeInfo)src_attr.get(i);
+ attributes.add(ai.copy(cp, classnames));
+ }
+
+ info = src.copyCode(cp, classnames, exceptions, this);
+ }
+
+ CodeAttribute(ConstPool cp, int name_id, DataInputStream in)
+ throws IOException
+ {
+ super(cp, name_id, (byte[])null);
+ int attr_len = in.readInt();
+
+ maxStack = in.readUnsignedShort();
+ maxLocals = in.readUnsignedShort();
+
+ int code_len = in.readInt();
+ info = new byte[code_len];
+ in.readFully(info);
+
+ exceptions = new ExceptionTable(cp, in);
+
+ attributes = new ArrayList();
+ int num = in.readUnsignedShort();
+ for (int i = 0; i < num; ++i)
+ attributes.add(AttributeInfo.read(cp, in));
+ }
+
+ /**
+ * Makes a copy. Class names are replaced according to the
+ * given <code>Map</code> object.
+ *
+ * @param newCp the constant pool table used by the new copy.
+ * @param classnames pairs of replaced and substituted
+ * class names.
+ * @exception RuntimeCopyException if a <code>BadBytecode</code>
+ * exception is thrown, it is
+ * converted into
+ * <code>RuntimeCopyException</code>.
+ *
+ * @return <code>CodeAttribute</code> object.
+ */
+ public AttributeInfo copy(ConstPool newCp, Map classnames)
+ throws RuntimeCopyException
+ {
+ try {
+ return new CodeAttribute(newCp, this, classnames);
+ }
+ catch (BadBytecode e) {
+ throw new RuntimeCopyException("bad bytecode. fatal?");
+ }
+ }
+
+ /**
+ * An exception that may be thrown by <code>copy()</code>
+ * in <code>CodeAttribute</code>.
+ */
+ public static class RuntimeCopyException extends RuntimeException {
+ /**
+ * Constructs an exception.
+ */
+ public RuntimeCopyException(String s) {
+ super(s);
+ }
+ }
+
+ /**
+ * Returns the length of this <code>attribute_info</code>
+ * structure.
+ * The returned value is <code>attribute_length + 6</code>.
+ */
+ public int length() {
+ return 18 + info.length + exceptions.size() * 8
+ + AttributeInfo.getLength(attributes);
+ }
+
+ void write(DataOutputStream out) throws IOException {
+ out.writeShort(name); // attribute_name_index
+ out.writeInt(length() - 6); // attribute_length
+ out.writeShort(maxStack); // max_stack
+ out.writeShort(maxLocals); // max_locals
+ out.writeInt(info.length); // code_length
+ out.write(info); // code
+ exceptions.write(out);
+ out.writeShort(attributes.size()); // attributes_count
+ AttributeInfo.writeAll(attributes, out); // attributes
+ }
+
+ /**
+ * This method is not available.
+ *
+ * @throws java.lang.UnsupportedOperationException always thrown.
+ */
+ public byte[] get() {
+ throw new UnsupportedOperationException("CodeAttribute.get()");
+ }
+
+ /**
+ * This method is not available.
+ *
+ * @throws java.lang.UnsupportedOperationException always thrown.
+ */
+ public void set(byte[] newinfo) {
+ throw new UnsupportedOperationException("CodeAttribute.set()");
+ }
+
+ void renameClass(String oldname, String newname) {
+ AttributeInfo.renameClass(attributes, oldname, newname);
+ }
+
+ void renameClass(Map classnames) {
+ AttributeInfo.renameClass(attributes, classnames);
+ }
+
+ void getRefClasses(Map classnames) {
+ AttributeInfo.getRefClasses(attributes, classnames);
+ }
+
+ /**
+ * Returns the name of the class declaring the method including
+ * this code attribute.
+ */
+ public String getDeclaringClass() {
+ ConstPool cp = getConstPool();
+ return cp.getClassName();
+ }
+
+ /**
+ * Returns <code>max_stack</code>.
+ */
+ public int getMaxStack() {
+ return maxStack;
+ }
+
+ /**
+ * Sets <code>max_stack</code>.
+ */
+ public void setMaxStack(int value) {
+ maxStack = value;
+ }
+
+ /**
+ * Computes the maximum stack size and sets <code>max_stack</code>
+ * to the computed size.
+ *
+ * @throws BadBytecode if this method fails in computing.
+ * @return the newly computed value of <code>max_stack</code>
+ */
+ public int computeMaxStack() throws BadBytecode {
+ maxStack = new CodeAnalyzer(this).computeMaxStack();
+ return maxStack;
+ }
+
+ /**
+ * Returns <code>max_locals</code>.
+ */
+ public int getMaxLocals() {
+ return maxLocals;
+ }
+
+ /**
+ * Sets <code>max_locals</code>.
+ */
+ public void setMaxLocals(int value) {
+ maxLocals = value;
+ }
+
+ /**
+ * Returns <code>code_length</code>.
+ */
+ public int getCodeLength() {
+ return info.length;
+ }
+
+ /**
+ * Returns <code>code[]</code>.
+ */
+ public byte[] getCode() {
+ return info;
+ }
+
+ /**
+ * Sets <code>code[]</code>.
+ */
+ void setCode(byte[] newinfo) { super.set(newinfo); }
+
+ /**
+ * Makes a new iterator for reading this code attribute.
+ */
+ public CodeIterator iterator() {
+ return new CodeIterator(this);
+ }
+
+ /**
+ * Returns <code>exception_table[]</code>.
+ */
+ public ExceptionTable getExceptionTable() { return exceptions; }
+
+ /**
+ * Returns <code>attributes[]</code>.
+ * It returns a list of <code>AttributeInfo</code>.
+ * A new element can be added to the returned list
+ * and an existing element can be removed from the list.
+ *
+ * @see AttributeInfo
+ */
+ public List getAttributes() { return attributes; }
+
+ /**
+ * Returns the attribute with the specified name.
+ * If it is not found, this method returns null.
+ *
+ * @param name attribute name
+ * @return an <code>AttributeInfo</code> object or null.
+ */
+ public AttributeInfo getAttribute(String name) {
+ return AttributeInfo.lookup(attributes, name);
+ }
+
+ /**
+ * Adds a stack map table. If another copy of stack map table
+ * is already contained, the old one is removed.
+ *
+ * @param smt the stack map table added to this code attribute.
+ * If it is null, a new stack map is not added.
+ * Only the old stack map is removed.
+ */
+ public void setAttribute(StackMapTable smt) {
+ AttributeInfo.remove(attributes, StackMapTable.tag);
+ if (smt != null)
+ attributes.add(smt);
+ }
+
+ /**
+ * Adds a stack map table for J2ME (CLDC). If another copy of stack map table
+ * is already contained, the old one is removed.
+ *
+ * @param sm the stack map table added to this code attribute.
+ * If it is null, a new stack map is not added.
+ * Only the old stack map is removed.
+ * @since 3.12
+ */
+ public void setAttribute(StackMap sm) {
+ AttributeInfo.remove(attributes, StackMap.tag);
+ if (sm != null)
+ attributes.add(sm);
+ }
+
+ /**
+ * Copies code.
+ */
+ private byte[] copyCode(ConstPool destCp, Map classnames,
+ ExceptionTable etable, CodeAttribute destCa)
+ throws BadBytecode
+ {
+ int len = getCodeLength();
+ byte[] newCode = new byte[len];
+ destCa.info = newCode;
+ LdcEntry ldc = copyCode(this.info, 0, len, this.getConstPool(),
+ newCode, destCp, classnames);
+ return LdcEntry.doit(newCode, ldc, etable, destCa);
+ }
+
+ private static LdcEntry copyCode(byte[] code, int beginPos, int endPos,
+ ConstPool srcCp, byte[] newcode,
+ ConstPool destCp, Map classnameMap)
+ throws BadBytecode
+ {
+ int i2, index;
+ LdcEntry ldcEntry = null;
+
+ for (int i = beginPos; i < endPos; i = i2) {
+ i2 = CodeIterator.nextOpcode(code, i);
+ byte c = code[i];
+ newcode[i] = c;
+ switch (c & 0xff) {
+ case LDC_W :
+ case LDC2_W :
+ case GETSTATIC :
+ case PUTSTATIC :
+ case GETFIELD :
+ case PUTFIELD :
+ case INVOKEVIRTUAL :
+ case INVOKESPECIAL :
+ case INVOKESTATIC :
+ case NEW :
+ case ANEWARRAY :
+ case CHECKCAST :
+ case INSTANCEOF :
+ copyConstPoolInfo(i + 1, code, srcCp, newcode, destCp,
+ classnameMap);
+ break;
+ case LDC :
+ index = code[i + 1] & 0xff;
+ index = srcCp.copy(index, destCp, classnameMap);
+ if (index < 0x100)
+ newcode[i + 1] = (byte)index;
+ else {
+ newcode[i] = NOP;
+ newcode[i + 1] = NOP;
+ LdcEntry ldc = new LdcEntry();
+ ldc.where = i;
+ ldc.index = index;
+ ldc.next = ldcEntry;
+ ldcEntry = ldc;
+ }
+ break;
+ case INVOKEINTERFACE :
+ copyConstPoolInfo(i + 1, code, srcCp, newcode, destCp,
+ classnameMap);
+ newcode[i + 3] = code[i + 3];
+ newcode[i + 4] = code[i + 4];
+ break;
+ case MULTIANEWARRAY :
+ copyConstPoolInfo(i + 1, code, srcCp, newcode, destCp,
+ classnameMap);
+ newcode[i + 3] = code[i + 3];
+ break;
+ default :
+ while (++i < i2)
+ newcode[i] = code[i];
+
+ break;
+ }
+ }
+
+ return ldcEntry;
+ }
+
+ private static void copyConstPoolInfo(int i, byte[] code, ConstPool srcCp,
+ byte[] newcode, ConstPool destCp,
+ Map classnameMap) {
+ int index = ((code[i] & 0xff) << 8) | (code[i + 1] & 0xff);
+ index = srcCp.copy(index, destCp, classnameMap);
+ newcode[i] = (byte)(index >> 8);
+ newcode[i + 1] = (byte)index;
+ }
+
+ static class LdcEntry {
+ LdcEntry next;
+ int where;
+ int index;
+
+ static byte[] doit(byte[] code, LdcEntry ldc, ExceptionTable etable,
+ CodeAttribute ca)
+ throws BadBytecode
+ {
+ if (ldc != null)
+ code = CodeIterator.changeLdcToLdcW(code, etable, ca, ldc);
+
+ /* The original code was the following:
+
+ while (ldc != null) {
+ int where = ldc.where;
+ code = CodeIterator.insertGapCore0(code, where, 1, false, etable, ca);
+ code[where] = (byte)Opcode.LDC_W;
+ ByteArray.write16bit(ldc.index, code, where + 1);
+ ldc = ldc.next;
+ }
+
+ But this code does not support a large method > 32KB.
+ */
+
+ return code;
+ }
+ }
+
+ /**
+ * Changes the index numbers of the local variables
+ * to append a new parameter.
+ * This method does not update <code>LocalVariableAttribute</code>,
+ * <code>StackMapTable</code>, or <code>StackMap</code>.
+ * These attributes must be explicitly updated.
+ *
+ * @param where the index of the new parameter.
+ * @param size the type size of the new parameter (1 or 2).
+ *
+ * @see LocalVariableAttribute#shiftIndex(int, int)
+ * @see StackMapTable#insertLocal(int, int, int)
+ * @see StackMap#insertLocal(int, int, int)
+ */
+ public void insertLocalVar(int where, int size) throws BadBytecode {
+ CodeIterator ci = iterator();
+ while (ci.hasNext())
+ shiftIndex(ci, where, size);
+
+ setMaxLocals(getMaxLocals() + size);
+ }
+
+ /**
+ * @param lessThan If the index of the local variable is
+ * less than this value, it does not change.
+ * Otherwise, the index is increased.
+ * @param delta the indexes of the local variables are
+ * increased by this value.
+ */
+ private static void shiftIndex(CodeIterator ci, int lessThan, int delta) throws BadBytecode {
+ int index = ci.next();
+ int opcode = ci.byteAt(index);
+ if (opcode < ILOAD)
+ return;
+ else if (opcode < IASTORE) {
+ if (opcode < ILOAD_0) {
+ // iload, lload, fload, dload, aload
+ shiftIndex8(ci, index, opcode, lessThan, delta);
+ }
+ else if (opcode < IALOAD) {
+ // iload_0, ..., aload_3
+ shiftIndex0(ci, index, opcode, lessThan, delta, ILOAD_0, ILOAD);
+ }
+ else if (opcode < ISTORE)
+ return;
+ else if (opcode < ISTORE_0) {
+ // istore, lstore, ...
+ shiftIndex8(ci, index, opcode, lessThan, delta);
+ }
+ else {
+ // istore_0, ..., astore_3
+ shiftIndex0(ci, index, opcode, lessThan, delta, ISTORE_0, ISTORE);
+ }
+ }
+ else if (opcode == IINC) {
+ int var = ci.byteAt(index + 1);
+ if (var < lessThan)
+ return;
+
+ var += delta;
+ if (var < 0x100)
+ ci.writeByte(var, index + 1);
+ else {
+ int plus = (byte)ci.byteAt(index + 2);
+ int pos = ci.insertExGap(3);
+ ci.writeByte(WIDE, pos - 3);
+ ci.writeByte(IINC, pos - 2);
+ ci.write16bit(var, pos - 1);
+ ci.write16bit(plus, pos + 1);
+ }
+ }
+ else if (opcode == RET)
+ shiftIndex8(ci, index, opcode, lessThan, delta);
+ else if (opcode == WIDE) {
+ int var = ci.u16bitAt(index + 2);
+ if (var < lessThan)
+ return;
+
+ var += delta;
+ ci.write16bit(var, index + 2);
+ }
+ }
+
+ private static void shiftIndex8(CodeIterator ci, int index, int opcode,
+ int lessThan, int delta)
+ throws BadBytecode
+ {
+ int var = ci.byteAt(index + 1);
+ if (var < lessThan)
+ return;
+
+ var += delta;
+ if (var < 0x100)
+ ci.writeByte(var, index + 1);
+ else {
+ int pos = ci.insertExGap(2);
+ ci.writeByte(WIDE, pos - 2);
+ ci.writeByte(opcode, pos - 1);
+ ci.write16bit(var, pos);
+ }
+ }
+
+ private static void shiftIndex0(CodeIterator ci, int index, int opcode,
+ int lessThan, int delta,
+ int opcode_i_0, int opcode_i)
+ throws BadBytecode
+ {
+ int var = (opcode - opcode_i_0) % 4;
+ if (var < lessThan)
+ return;
+
+ var += delta;
+ if (var < 4)
+ ci.writeByte(opcode + delta, index);
+ else {
+ opcode = (opcode - opcode_i_0) / 4 + opcode_i;
+ if (var < 0x100) {
+ int pos = ci.insertExGap(1);
+ ci.writeByte(opcode, pos - 1);
+ ci.writeByte(var, pos);
+ }
+ else {
+ int pos = ci.insertExGap(3);
+ ci.writeByte(WIDE, pos - 1);
+ ci.writeByte(opcode, pos);
+ ci.write16bit(var, pos + 1);
+ }
+ }
+ }
+}
diff --git a/src/main/javassist/bytecode/CodeIterator.java b/src/main/javassist/bytecode/CodeIterator.java
new file mode 100644
index 0000000..7781b70
--- /dev/null
+++ b/src/main/javassist/bytecode/CodeIterator.java
@@ -0,0 +1,1571 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode;
+
+import java.util.ArrayList;
+
+/**
+ * An iterator for editing a code attribute.
+ *
+ * <p>If there are multiple <code>CodeIterator</code>s referring to the
+ * same <code>Code_attribute</code>, then inserting a gap by one
+ * <code>CodeIterator</code> will break the other
+ * <code>CodeIterator</code>.
+ *
+ * <p>This iterator does not provide <code>remove()</code>.
+ * If a piece of code in a <code>Code_attribute</code> is unnecessary,
+ * it should be overwritten with <code>NOP</code>.
+ *
+ * @see CodeAttribute#iterator()
+ */
+public class CodeIterator implements Opcode {
+ protected CodeAttribute codeAttr;
+ protected byte[] bytecode;
+ protected int endPos;
+ protected int currentPos;
+ protected int mark;
+
+ protected CodeIterator(CodeAttribute ca) {
+ codeAttr = ca;
+ bytecode = ca.getCode();
+ begin();
+ }
+
+ /**
+ * Moves to the first instruction.
+ */
+ public void begin() {
+ currentPos = mark = 0;
+ endPos = getCodeLength();
+ }
+
+ /**
+ * Moves to the given index.
+ *
+ * <p>The index of the next instruction is set to the given index.
+ * The successive call to <code>next()</code>
+ * returns the index that has been given to <code>move()</code>.
+ *
+ * <p>Note that the index is into the byte array returned by
+ * <code>get().getCode()</code>.
+ *
+ * @see CodeAttribute#getCode()
+ */
+ public void move(int index) {
+ currentPos = index;
+ }
+
+ /**
+ * Sets a mark to the bytecode at the given index.
+ * The mark can be used to track the position of that bytecode
+ * when code blocks are inserted.
+ * If a code block is inclusively inserted at the position of the
+ * bytecode, the mark is set to the inserted code block.
+ *
+ * @see #getMark()
+ * @since 3.11
+ */
+ public void setMark(int index) {
+ mark = index;
+ }
+
+ /**
+ * Gets the index of the position of the mark set by
+ * <code>setMark</code>.
+ *
+ * @return the index of the position.
+ * @see #setMark(int)
+ * @since 3.11
+ */
+ public int getMark() { return mark; }
+
+ /**
+ * Returns a Code attribute read with this iterator.
+ */
+ public CodeAttribute get() {
+ return codeAttr;
+ }
+
+ /**
+ * Returns <code>code_length</code> of <code>Code_attribute</code>.
+ */
+ public int getCodeLength() {
+ return bytecode.length;
+ }
+
+ /**
+ * Returns the unsigned 8bit value at the given index.
+ */
+ public int byteAt(int index) { return bytecode[index] & 0xff; }
+
+ /**
+ * Writes an 8bit value at the given index.
+ */
+ public void writeByte(int value, int index) {
+ bytecode[index] = (byte)value;
+ }
+
+ /**
+ * Returns the unsigned 16bit value at the given index.
+ */
+ public int u16bitAt(int index) {
+ return ByteArray.readU16bit(bytecode, index);
+ }
+
+ /**
+ * Returns the signed 16bit value at the given index.
+ */
+ public int s16bitAt(int index) {
+ return ByteArray.readS16bit(bytecode, index);
+ }
+
+ /**
+ * Writes a 16 bit integer at the index.
+ */
+ public void write16bit(int value, int index) {
+ ByteArray.write16bit(value, bytecode, index);
+ }
+
+ /**
+ * Returns the signed 32bit value at the given index.
+ */
+ public int s32bitAt(int index) {
+ return ByteArray.read32bit(bytecode, index);
+ }
+
+ /**
+ * Writes a 32bit integer at the index.
+ */
+ public void write32bit(int value, int index) {
+ ByteArray.write32bit(value, bytecode, index);
+ }
+
+ /**
+ * Writes a byte array at the index.
+ *
+ * @param code may be a zero-length array.
+ */
+ public void write(byte[] code, int index) {
+ int len = code.length;
+ for (int j = 0; j < len; ++j)
+ bytecode[index++] = code[j];
+ }
+
+ /**
+ * Returns true if there is more instructions.
+ */
+ public boolean hasNext() { return currentPos < endPos; }
+
+ /**
+ * Returns the index of the next instruction
+ * (not the operand following the current opcode).
+ *
+ * <p>Note that the index is into the byte array returned by
+ * <code>get().getCode()</code>.
+ *
+ * @see CodeAttribute#getCode()
+ * @see CodeIterator#byteAt(int)
+ */
+ public int next() throws BadBytecode {
+ int pos = currentPos;
+ currentPos = nextOpcode(bytecode, pos);
+ return pos;
+ }
+
+ /**
+ * Obtains the value that the next call
+ * to <code>next()</code> will return.
+ *
+ * <p>This method is side-effects free.
+ * Successive calls to <code>lookAhead()</code> return the
+ * same value until <code>next()</code> is called.
+ */
+ public int lookAhead() {
+ return currentPos;
+ }
+
+ /**
+ * Moves to the instruction for
+ * either <code>super()</code> or <code>this()</code>.
+ *
+ * <p>This method skips all the instructions for computing arguments
+ * to <code>super()</code> or <code>this()</code>, which should be
+ * placed at the beginning of a constructor body.
+ *
+ * <p>This method returns the index of INVOKESPECIAL instruction
+ * executing <code>super()</code> or <code>this()</code>.
+ * A successive call to <code>next()</code> returns the
+ * index of the next instruction following that INVOKESPECIAL.
+ *
+ * <p>This method works only for a constructor.
+ *
+ * @return the index of the INVOKESPECIAL instruction, or -1
+ * if a constructor invocation is not found.
+ */
+ public int skipConstructor() throws BadBytecode {
+ return skipSuperConstructor0(-1);
+ }
+
+ /**
+ * Moves to the instruction for <code>super()</code>.
+ *
+ * <p>This method skips all the instructions for computing arguments to
+ * <code>super()</code>, which should be
+ * placed at the beginning of a constructor body.
+ *
+ * <p>This method returns the index of INVOKESPECIAL instruction
+ * executing <code>super()</code>.
+ * A successive call to <code>next()</code> returns the
+ * index of the next instruction following that INVOKESPECIAL.
+ *
+ * <p>This method works only for a constructor.
+ *
+ * @return the index of the INVOKESPECIAL instruction, or -1
+ * if a super constructor invocation is not found
+ * but <code>this()</code> is found.
+ */
+ public int skipSuperConstructor() throws BadBytecode {
+ return skipSuperConstructor0(0);
+ }
+
+ /**
+ * Moves to the instruction for <code>this()</code>.
+ *
+ * <p>This method skips all the instructions for computing arguments to
+ * <code>this()</code>, which should be
+ * placed at the beginning of a constructor body.
+ *
+ * <p>This method returns the index of INVOKESPECIAL instruction
+ * executing <code>this()</code>.
+ * A successive call to <code>next()</code> returns the
+ * index of the next instruction following that INVOKESPECIAL.
+ *
+ * <p>This method works only for a constructor.
+ *
+ * @return the index of the INVOKESPECIAL instruction, or -1
+ * if a explicit constructor invocation is not found
+ * but <code>super()</code> is found.
+ */
+ public int skipThisConstructor() throws BadBytecode {
+ return skipSuperConstructor0(1);
+ }
+
+ /* skipSuper 1: this(), 0: super(), -1: both.
+ */
+ private int skipSuperConstructor0(int skipThis) throws BadBytecode {
+ begin();
+ ConstPool cp = codeAttr.getConstPool();
+ String thisClassName = codeAttr.getDeclaringClass();
+ int nested = 0;
+ while (hasNext()) {
+ int index = next();
+ int c = byteAt(index);
+ if (c == NEW)
+ ++nested;
+ else if (c == INVOKESPECIAL) {
+ int mref = ByteArray.readU16bit(bytecode, index + 1);
+ if (cp.getMethodrefName(mref).equals(MethodInfo.nameInit))
+ if (--nested < 0) {
+ if (skipThis < 0)
+ return index;
+
+ String cname = cp.getMethodrefClassName(mref);
+ if (cname.equals(thisClassName) == (skipThis > 0))
+ return index;
+ else
+ break;
+ }
+ }
+ }
+
+ begin();
+ return -1;
+ }
+
+ /**
+ * Inserts the given bytecode sequence
+ * before the next instruction that would be returned by
+ * <code>next()</code> (not before the instruction returned
+ * by the last call to <code>next()</code>).
+ * Branch offsets and the exception table are also updated.
+ *
+ * <p>If the next instruction is at the beginning of a block statement,
+ * then the bytecode is inserted within that block.
+ *
+ * <p>An extra gap may be inserted at the end of the inserted
+ * bytecode sequence for adjusting alignment if the code attribute
+ * includes <code>LOOKUPSWITCH</code> or <code>TABLESWITCH</code>.
+ *
+ * @param code inserted bytecode sequence.
+ * @return the index indicating the first byte of the
+ * inserted byte sequence.
+ */
+ public int insert(byte[] code)
+ throws BadBytecode
+ {
+ return insert0(currentPos, code, false);
+ }
+
+ /**
+ * Inserts the given bytecode sequence
+ * before the instruction at the given index <code>pos</code>.
+ * Branch offsets and the exception table are also updated.
+ *
+ * <p>If the instruction at the given index is at the beginning
+ * of a block statement,
+ * then the bytecode is inserted within that block.
+ *
+ * <p>An extra gap may be inserted at the end of the inserted
+ * bytecode sequence for adjusting alignment if the code attribute
+ * includes <code>LOOKUPSWITCH</code> or <code>TABLESWITCH</code>.
+ *
+ * <p>The index at which the byte sequence is actually inserted
+ * might be different from pos since some other bytes might be
+ * inserted at other positions (e.g. to change <code>GOTO</code>
+ * to <code>GOTO_W</code>).
+ *
+ * @param pos the index at which a byte sequence is inserted.
+ * @param code inserted bytecode sequence.
+ */
+ public void insert(int pos, byte[] code) throws BadBytecode {
+ insert0(pos, code, false);
+ }
+
+ /**
+ * Inserts the given bytecode sequence
+ * before the instruction at the given index <code>pos</code>.
+ * Branch offsets and the exception table are also updated.
+ *
+ * <p>If the instruction at the given index is at the beginning
+ * of a block statement,
+ * then the bytecode is inserted within that block.
+ *
+ * <p>An extra gap may be inserted at the end of the inserted
+ * bytecode sequence for adjusting alignment if the code attribute
+ * includes <code>LOOKUPSWITCH</code> or <code>TABLESWITCH</code>.
+ *
+ * @param pos the index at which a byte sequence is inserted.
+ * @param code inserted bytecode sequence.
+ * @return the index indicating the first byte of the
+ * inserted byte sequence, which might be
+ * different from pos.
+ * @since 3.11
+ */
+ public int insertAt(int pos, byte[] code) throws BadBytecode {
+ return insert0(pos, code, false);
+ }
+
+ /**
+ * Inserts the given bytecode sequence exclusively
+ * before the next instruction that would be returned by
+ * <code>next()</code> (not before the instruction returned
+ * by tha last call to <code>next()</code>).
+ * Branch offsets and the exception table are also updated.
+ *
+ * <p>If the next instruction is at the beginning of a block statement,
+ * then the bytecode is excluded from that block.
+ *
+ * <p>An extra gap may be inserted at the end of the inserted
+ * bytecode sequence for adjusting alignment if the code attribute
+ * includes <code>LOOKUPSWITCH</code> or <code>TABLESWITCH</code>.
+ *
+ * @param code inserted bytecode sequence.
+ * @return the index indicating the first byte of the
+ * inserted byte sequence.
+ */
+ public int insertEx(byte[] code)
+ throws BadBytecode
+ {
+ return insert0(currentPos, code, true);
+ }
+
+ /**
+ * Inserts the given bytecode sequence exclusively
+ * before the instruction at the given index <code>pos</code>.
+ * Branch offsets and the exception table are also updated.
+ *
+ * <p>If the instruction at the given index is at the beginning
+ * of a block statement,
+ * then the bytecode is excluded from that block.
+ *
+ * <p>An extra gap may be inserted at the end of the inserted
+ * bytecode sequence for adjusting alignment if the code attribute
+ * includes <code>LOOKUPSWITCH</code> or <code>TABLESWITCH</code>.
+ *
+ * <p>The index at which the byte sequence is actually inserted
+ * might be different from pos since some other bytes might be
+ * inserted at other positions (e.g. to change <code>GOTO</code>
+ * to <code>GOTO_W</code>).
+ *
+ * @param pos the index at which a byte sequence is inserted.
+ * @param code inserted bytecode sequence.
+ */
+ public void insertEx(int pos, byte[] code) throws BadBytecode {
+ insert0(pos, code, true);
+ }
+
+ /**
+ * Inserts the given bytecode sequence exclusively
+ * before the instruction at the given index <code>pos</code>.
+ * Branch offsets and the exception table are also updated.
+ *
+ * <p>If the instruction at the given index is at the beginning
+ * of a block statement,
+ * then the bytecode is excluded from that block.
+ *
+ * <p>An extra gap may be inserted at the end of the inserted
+ * bytecode sequence for adjusting alignment if the code attribute
+ * includes <code>LOOKUPSWITCH</code> or <code>TABLESWITCH</code>.
+ *
+ * @param pos the index at which a byte sequence is inserted.
+ * @param code inserted bytecode sequence.
+ * @return the index indicating the first byte of the
+ * inserted byte sequence, which might be
+ * different from pos.
+ * @since 3.11
+ */
+ public int insertExAt(int pos, byte[] code) throws BadBytecode {
+ return insert0(pos, code, true);
+ }
+
+ /**
+ * @return the index indicating the first byte of the
+ * inserted byte sequence.
+ */
+ private int insert0(int pos, byte[] code, boolean exclusive)
+ throws BadBytecode
+ {
+ int len = code.length;
+ if (len <= 0)
+ return pos;
+
+ // currentPos will change.
+ pos = insertGapAt(pos, len, exclusive).position;
+
+ int p = pos;
+ for (int j = 0; j < len; ++j)
+ bytecode[p++] = code[j];
+
+ return pos;
+ }
+
+ /**
+ * Inserts a gap
+ * before the next instruction that would be returned by
+ * <code>next()</code> (not before the instruction returned
+ * by the last call to <code>next()</code>).
+ * Branch offsets and the exception table are also updated.
+ * The inserted gap is filled with NOP. The gap length may be
+ * extended to a multiple of 4.
+ *
+ * <p>If the next instruction is at the beginning of a block statement,
+ * then the gap is inserted within that block.
+ *
+ * @param length gap length
+ * @return the index indicating the first byte of the inserted gap.
+ */
+ public int insertGap(int length) throws BadBytecode {
+ return insertGapAt(currentPos, length, false).position;
+ }
+
+ /**
+ * Inserts a gap in front of the instruction at the given
+ * index <code>pos</code>.
+ * Branch offsets and the exception table are also updated.
+ * The inserted gap is filled with NOP. The gap length may be
+ * extended to a multiple of 4.
+ *
+ * <p>If the instruction at the given index is at the beginning
+ * of a block statement,
+ * then the gap is inserted within that block.
+ *
+ * @param pos the index at which a gap is inserted.
+ * @param length gap length.
+ * @return the length of the inserted gap.
+ * It might be bigger than <code>length</code>.
+ */
+ public int insertGap(int pos, int length) throws BadBytecode {
+ return insertGapAt(pos, length, false).length;
+ }
+
+ /**
+ * Inserts an exclusive gap
+ * before the next instruction that would be returned by
+ * <code>next()</code> (not before the instruction returned
+ * by the last call to <code>next()</code>).
+ * Branch offsets and the exception table are also updated.
+ * The inserted gap is filled with NOP. The gap length may be
+ * extended to a multiple of 4.
+ *
+ * <p>If the next instruction is at the beginning of a block statement,
+ * then the gap is excluded from that block.
+ *
+ * @param length gap length
+ * @return the index indicating the first byte of the inserted gap.
+ */
+ public int insertExGap(int length) throws BadBytecode {
+ return insertGapAt(currentPos, length, true).position;
+ }
+
+ /**
+ * Inserts an exclusive gap in front of the instruction at the given
+ * index <code>pos</code>.
+ * Branch offsets and the exception table are also updated.
+ * The inserted gap is filled with NOP. The gap length may be
+ * extended to a multiple of 4.
+ *
+ * <p>If the instruction at the given index is at the beginning
+ * of a block statement,
+ * then the gap is excluded from that block.
+ *
+ * @param pos the index at which a gap is inserted.
+ * @param length gap length.
+ * @return the length of the inserted gap.
+ * It might be bigger than <code>length</code>.
+ */
+ public int insertExGap(int pos, int length) throws BadBytecode {
+ return insertGapAt(pos, length, true).length;
+ }
+
+ /**
+ * An inserted gap.
+ *
+ * @since 3.11
+ */
+ public static class Gap {
+ /**
+ * The position of the gap.
+ */
+ public int position;
+
+ /**
+ * The length of the gap.
+ */
+ public int length;
+ }
+
+ /**
+ * Inserts an inclusive or exclusive gap in front of the instruction
+ * at the given index <code>pos</code>.
+ * Branch offsets and the exception table in the method body
+ * are also updated. The inserted gap is filled with NOP.
+ * The gap length may be extended to a multiple of 4.
+ *
+ * <p>Suppose that the instruction at the given index is at the
+ * beginning of a block statement. If the gap is inclusive,
+ * then it is included within that block. If the gap is exclusive,
+ * then it is excluded from that block.
+ *
+ * <p>The index at which the gap is actually inserted
+ * might be different from pos since some other bytes might be
+ * inserted at other positions (e.g. to change <code>GOTO</code>
+ * to <code>GOTO_W</code>). The index is available from the <code>Gap</code>
+ * object returned by this method.
+ *
+ * <p>Suppose that the gap is inserted at the position of
+ * the next instruction that would be returned by
+ * <code>next()</code> (not the last instruction returned
+ * by the last call to <code>next()</code>). The next
+ * instruction returned by <code>next()</code> after the gap is
+ * inserted is still the same instruction. It is not <code>NOP</code>
+ * at the first byte of the inserted gap.
+ *
+ * @param pos the index at which a gap is inserted.
+ * @param length gap length.
+ * @param exclusive true if exclusive, otherwise false.
+ * @return the position and the length of the inserted gap.
+ * @since 3.11
+ */
+ public Gap insertGapAt(int pos, int length, boolean exclusive)
+ throws BadBytecode
+ {
+ /**
+ * cursorPos indicates the next bytecode whichever exclusive is
+ * true or false.
+ */
+ Gap gap = new Gap();
+ if (length <= 0) {
+ gap.position = pos;
+ gap.length = 0;
+ return gap;
+ }
+
+ byte[] c;
+ int length2;
+ if (bytecode.length + length > Short.MAX_VALUE) {
+ // currentPos might change after calling insertGapCore0w().
+ c = insertGapCore0w(bytecode, pos, length, exclusive,
+ get().getExceptionTable(), codeAttr, gap);
+ pos = gap.position;
+ length2 = length; // == gap.length
+ }
+ else {
+ int cur = currentPos;
+ c = insertGapCore0(bytecode, pos, length, exclusive,
+ get().getExceptionTable(), codeAttr);
+ // insertGapCore0() never changes pos.
+ length2 = c.length - bytecode.length;
+ gap.position = pos;
+ gap.length = length2;
+ if (cur >= pos)
+ currentPos = cur + length2;
+
+ if (mark > pos || (mark == pos && exclusive))
+ mark += length2;
+ }
+
+ codeAttr.setCode(c);
+ bytecode = c;
+ endPos = getCodeLength();
+ updateCursors(pos, length2);
+ return gap;
+ }
+
+ /**
+ * Is called when a gap is inserted. The default implementation is empty.
+ * A subclass can override this method so that cursors will be updated.
+ *
+ * @param pos the position where a gap is inserted.
+ * @param length the length of the gap.
+ */
+ protected void updateCursors(int pos, int length) {
+ // empty
+ }
+
+ /**
+ * Copies and inserts the entries in the given exception table
+ * at the beginning of the exception table in the code attribute
+ * edited by this object.
+ *
+ * @param offset the value added to the code positions included
+ * in the entries.
+ */
+ public void insert(ExceptionTable et, int offset) {
+ codeAttr.getExceptionTable().add(0, et, offset);
+ }
+
+ /**
+ * Appends the given bytecode sequence at the end.
+ *
+ * @param code the bytecode appended.
+ * @return the position of the first byte of the appended bytecode.
+ */
+ public int append(byte[] code) {
+ int size = getCodeLength();
+ int len = code.length;
+ if (len <= 0)
+ return size;
+
+ appendGap(len);
+ byte[] dest = bytecode;
+ for (int i = 0; i < len; ++i)
+ dest[i + size] = code[i];
+
+ return size;
+ }
+
+ /**
+ * Appends a gap at the end of the bytecode sequence.
+ *
+ * @param gapLength gap length
+ */
+ public void appendGap(int gapLength) {
+ byte[] code = bytecode;
+ int codeLength = code.length;
+ byte[] newcode = new byte[codeLength + gapLength];
+
+ int i;
+ for (i = 0; i < codeLength; ++i)
+ newcode[i] = code[i];
+
+ for (i = codeLength; i < codeLength + gapLength; ++i)
+ newcode[i] = NOP;
+
+ codeAttr.setCode(newcode);
+ bytecode = newcode;
+ endPos = getCodeLength();
+ }
+
+ /**
+ * Copies and appends the entries in the given exception table
+ * at the end of the exception table in the code attribute
+ * edited by this object.
+ *
+ * @param offset the value added to the code positions included
+ * in the entries.
+ */
+ public void append(ExceptionTable et, int offset) {
+ ExceptionTable table = codeAttr.getExceptionTable();
+ table.add(table.size(), et, offset);
+ }
+
+ /* opcodeLegth is used for implementing nextOpcode().
+ */
+ private static final int opcodeLength[] = {
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 2, 3,
+ 3, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 0, 0, 1, 1, 1, 1, 1, 1, 3, 3,
+ 3, 3, 3, 3, 3, 5, 0, 3, 2, 3, 1, 1, 3, 3, 1, 1, 0, 4, 3, 3,
+ 5, 5
+ };
+ // 0 .. UNUSED (186), LOOKUPSWITCH, TABLESWITCH, WIDE
+
+ /**
+ * Calculates the index of the next opcode.
+ */
+ static int nextOpcode(byte[] code, int index)
+ throws BadBytecode
+ {
+ int opcode;
+ try {
+ opcode = code[index] & 0xff;
+ }
+ catch (IndexOutOfBoundsException e) {
+ throw new BadBytecode("invalid opcode address");
+ }
+
+ try {
+ int len = opcodeLength[opcode];
+ if (len > 0)
+ return index + len;
+ else if (opcode == WIDE)
+ if (code[index + 1] == (byte)IINC) // WIDE IINC
+ return index + 6;
+ else
+ return index + 4; // WIDE ...
+ else {
+ int index2 = (index & ~3) + 8;
+ if (opcode == LOOKUPSWITCH) {
+ int npairs = ByteArray.read32bit(code, index2);
+ return index2 + npairs * 8 + 4;
+ }
+ else if (opcode == TABLESWITCH) {
+ int low = ByteArray.read32bit(code, index2);
+ int high = ByteArray.read32bit(code, index2 + 4);
+ return index2 + (high - low + 1) * 4 + 8;
+ }
+ // else
+ // throw new BadBytecode(opcode);
+ }
+ }
+ catch (IndexOutOfBoundsException e) {
+ }
+
+ // opcode is UNUSED or an IndexOutOfBoundsException was thrown.
+ throw new BadBytecode(opcode);
+ }
+
+ // methods for implementing insertGap().
+
+ static class AlignmentException extends Exception {}
+
+ /**
+ * insertGapCore0() inserts a gap (some NOPs).
+ * It cannot handle a long code sequence more than 32K. All branch offsets must be
+ * signed 16bits.
+ *
+ * If "where" is the beginning of a block statement and exclusive is false,
+ * then the inserted gap is also included in the block statement.
+ * "where" must indicate the first byte of an opcode.
+ * The inserted gap is filled with NOP. gapLength may be extended to
+ * a multiple of 4.
+ *
+ * This method was also called from CodeAttribute.LdcEntry.doit().
+ *
+ * @param where It must indicate the first byte of an opcode.
+ */
+ static byte[] insertGapCore0(byte[] code, int where, int gapLength,
+ boolean exclusive, ExceptionTable etable, CodeAttribute ca)
+ throws BadBytecode
+ {
+ if (gapLength <= 0)
+ return code;
+
+ try {
+ return insertGapCore1(code, where, gapLength, exclusive, etable, ca);
+ }
+ catch (AlignmentException e) {
+ try {
+ return insertGapCore1(code, where, (gapLength + 3) & ~3,
+ exclusive, etable, ca);
+ }
+ catch (AlignmentException e2) {
+ throw new RuntimeException("fatal error?");
+ }
+ }
+ }
+
+ private static byte[] insertGapCore1(byte[] code, int where, int gapLength,
+ boolean exclusive, ExceptionTable etable,
+ CodeAttribute ca)
+ throws BadBytecode, AlignmentException
+ {
+ int codeLength = code.length;
+ byte[] newcode = new byte[codeLength + gapLength];
+ insertGap2(code, where, gapLength, codeLength, newcode, exclusive);
+ etable.shiftPc(where, gapLength, exclusive);
+ LineNumberAttribute na
+ = (LineNumberAttribute)ca.getAttribute(LineNumberAttribute.tag);
+ if (na != null)
+ na.shiftPc(where, gapLength, exclusive);
+
+ LocalVariableAttribute va = (LocalVariableAttribute)ca.getAttribute(
+ LocalVariableAttribute.tag);
+ if (va != null)
+ va.shiftPc(where, gapLength, exclusive);
+
+ LocalVariableAttribute vta
+ = (LocalVariableAttribute)ca.getAttribute(
+ LocalVariableAttribute.typeTag);
+ if (vta != null)
+ vta.shiftPc(where, gapLength, exclusive);
+
+ StackMapTable smt = (StackMapTable)ca.getAttribute(StackMapTable.tag);
+ if (smt != null)
+ smt.shiftPc(where, gapLength, exclusive);
+
+ StackMap sm = (StackMap)ca.getAttribute(StackMap.tag);
+ if (sm != null)
+ sm.shiftPc(where, gapLength, exclusive);
+
+ return newcode;
+ }
+
+ private static void insertGap2(byte[] code, int where, int gapLength,
+ int endPos, byte[] newcode, boolean exclusive)
+ throws BadBytecode, AlignmentException
+ {
+ int nextPos;
+ int i = 0;
+ int j = 0;
+ for (; i < endPos; i = nextPos) {
+ if (i == where) {
+ int j2 = j + gapLength;
+ while (j < j2)
+ newcode[j++] = NOP;
+ }
+
+ nextPos = nextOpcode(code, i);
+ int inst = code[i] & 0xff;
+ // if<cond>, if_icmp<cond>, if_acmp<cond>, goto, jsr
+ if ((153 <= inst && inst <= 168)
+ || inst == IFNULL || inst == IFNONNULL) {
+ /* 2bytes *signed* offset */
+ int offset = (code[i + 1] << 8) | (code[i + 2] & 0xff);
+ offset = newOffset(i, offset, where, gapLength, exclusive);
+ newcode[j] = code[i];
+ ByteArray.write16bit(offset, newcode, j + 1);
+ j += 3;
+ }
+ else if (inst == GOTO_W || inst == JSR_W) {
+ /* 4bytes offset */
+ int offset = ByteArray.read32bit(code, i + 1);
+ offset = newOffset(i, offset, where, gapLength, exclusive);
+ newcode[j++] = code[i];
+ ByteArray.write32bit(offset, newcode, j);
+ j += 4;
+ }
+ else if (inst == TABLESWITCH) {
+ if (i != j && (gapLength & 3) != 0)
+ throw new AlignmentException();
+
+ int i2 = (i & ~3) + 4; // 0-3 byte padding
+ // IBM JVM 1.4.2 cannot run the following code:
+ // int i0 = i;
+ // while (i0 < i2)
+ // newcode[j++] = code[i0++];
+ // So extracting this code into an external method.
+ // see JIRA JASSIST-74.
+ j = copyGapBytes(newcode, j, code, i, i2);
+
+ int defaultbyte = newOffset(i, ByteArray.read32bit(code, i2),
+ where, gapLength, exclusive);
+ ByteArray.write32bit(defaultbyte, newcode, j);
+ int lowbyte = ByteArray.read32bit(code, i2 + 4);
+ ByteArray.write32bit(lowbyte, newcode, j + 4);
+ int highbyte = ByteArray.read32bit(code, i2 + 8);
+ ByteArray.write32bit(highbyte, newcode, j + 8);
+ j += 12;
+ int i0 = i2 + 12;
+ i2 = i0 + (highbyte - lowbyte + 1) * 4;
+ while (i0 < i2) {
+ int offset = newOffset(i, ByteArray.read32bit(code, i0),
+ where, gapLength, exclusive);
+ ByteArray.write32bit(offset, newcode, j);
+ j += 4;
+ i0 += 4;
+ }
+ }
+ else if (inst == LOOKUPSWITCH) {
+ if (i != j && (gapLength & 3) != 0)
+ throw new AlignmentException();
+
+ int i2 = (i & ~3) + 4; // 0-3 byte padding
+
+ // IBM JVM 1.4.2 cannot run the following code:
+ // int i0 = i;
+ // while (i0 < i2)
+ // newcode[j++] = code[i0++];
+ // So extracting this code into an external method.
+ // see JIRA JASSIST-74.
+ j = copyGapBytes(newcode, j, code, i, i2);
+
+ int defaultbyte = newOffset(i, ByteArray.read32bit(code, i2),
+ where, gapLength, exclusive);
+ ByteArray.write32bit(defaultbyte, newcode, j);
+ int npairs = ByteArray.read32bit(code, i2 + 4);
+ ByteArray.write32bit(npairs, newcode, j + 4);
+ j += 8;
+ int i0 = i2 + 8;
+ i2 = i0 + npairs * 8;
+ while (i0 < i2) {
+ ByteArray.copy32bit(code, i0, newcode, j);
+ int offset = newOffset(i,
+ ByteArray.read32bit(code, i0 + 4),
+ where, gapLength, exclusive);
+ ByteArray.write32bit(offset, newcode, j + 4);
+ j += 8;
+ i0 += 8;
+ }
+ }
+ else
+ while (i < nextPos)
+ newcode[j++] = code[i++];
+ }
+ }
+
+
+ private static int copyGapBytes(byte[] newcode, int j, byte[] code, int i, int iEnd) {
+ switch (iEnd - i) {
+ case 4:
+ newcode[j++] = code[i++];
+ case 3:
+ newcode[j++] = code[i++];
+ case 2:
+ newcode[j++] = code[i++];
+ case 1:
+ newcode[j++] = code[i++];
+ default:
+ }
+
+ return j;
+ }
+
+ private static int newOffset(int i, int offset, int where,
+ int gapLength, boolean exclusive) {
+ int target = i + offset;
+ if (i < where) {
+ if (where < target || (exclusive && where == target))
+ offset += gapLength;
+ }
+ else if (i == where) {
+ // This code is different from the code in Branch#shiftOffset().
+ // see JASSIST-124.
+ if (target < where)
+ offset -= gapLength;
+ }
+ else
+ if (target < where || (!exclusive && where == target))
+ offset -= gapLength;
+
+ return offset;
+ }
+
+ static class Pointers {
+ int cursor;
+ int mark0, mark;
+ ExceptionTable etable;
+ LineNumberAttribute line;
+ LocalVariableAttribute vars, types;
+ StackMapTable stack;
+ StackMap stack2;
+
+ Pointers(int cur, int m, int m0, ExceptionTable et, CodeAttribute ca) {
+ cursor = cur;
+ mark = m;
+ mark0 = m0;
+ etable = et; // non null
+ line = (LineNumberAttribute)ca.getAttribute(LineNumberAttribute.tag);
+ vars = (LocalVariableAttribute)ca.getAttribute(LocalVariableAttribute.tag);
+ types = (LocalVariableAttribute)ca.getAttribute(LocalVariableAttribute.typeTag);
+ stack = (StackMapTable)ca.getAttribute(StackMapTable.tag);
+ stack2 = (StackMap)ca.getAttribute(StackMap.tag);
+ }
+
+ void shiftPc(int where, int gapLength, boolean exclusive) throws BadBytecode {
+ if (where < cursor || (where == cursor && exclusive))
+ cursor += gapLength;
+
+ if (where < mark || (where == mark && exclusive))
+ mark += gapLength;
+
+ if (where < mark0 || (where == mark0 && exclusive))
+ mark0 += gapLength;
+
+ etable.shiftPc(where, gapLength, exclusive);
+ if (line != null)
+ line.shiftPc(where, gapLength, exclusive);
+
+ if (vars != null)
+ vars.shiftPc(where, gapLength, exclusive);
+
+ if (types != null)
+ types.shiftPc(where, gapLength, exclusive);
+
+ if (stack != null)
+ stack.shiftPc(where, gapLength, exclusive);
+
+ if (stack2 != null)
+ stack2.shiftPc(where, gapLength, exclusive);
+ }
+ }
+
+ /*
+ * This method is called from CodeAttribute.LdcEntry.doit().
+ */
+ static byte[] changeLdcToLdcW(byte[] code, ExceptionTable etable,
+ CodeAttribute ca, CodeAttribute.LdcEntry ldcs)
+ throws BadBytecode
+ {
+ ArrayList jumps = makeJumpList(code, code.length);
+ while (ldcs != null) {
+ addLdcW(ldcs, jumps);
+ ldcs = ldcs.next;
+ }
+
+ Pointers pointers = new Pointers(0, 0, 0, etable, ca);
+ byte[] r = insertGap2w(code, 0, 0, false, jumps, pointers);
+ return r;
+ }
+
+ private static void addLdcW(CodeAttribute.LdcEntry ldcs, ArrayList jumps) {
+ int where = ldcs.where;
+ LdcW ldcw = new LdcW(where, ldcs.index);
+ int s = jumps.size();
+ for (int i = 0; i < s; i++)
+ if (where < ((Branch)jumps.get(i)).orgPos) {
+ jumps.add(i, ldcw);
+ return;
+ }
+
+ jumps.add(ldcw);
+ }
+
+ /*
+ * insertGapCore0w() can handle a long code sequence more than 32K.
+ * It guarantees that the length of the inserted gap (NOPs) is equal to
+ * gapLength. No other NOPs except some NOPs following TABLESWITCH or
+ * LOOKUPSWITCH will not be inserted.
+ *
+ * Note: currentPos might be moved.
+ *
+ * @param where It must indicate the first byte of an opcode.
+ * @param newWhere It contains the updated index of the position where a gap
+ * is inserted and the length of the gap.
+ * It must not be null.
+ */
+ private byte[] insertGapCore0w(byte[] code, int where, int gapLength, boolean exclusive,
+ ExceptionTable etable, CodeAttribute ca, Gap newWhere)
+ throws BadBytecode
+ {
+ if (gapLength <= 0)
+ return code;
+
+ ArrayList jumps = makeJumpList(code, code.length);
+ Pointers pointers = new Pointers(currentPos, mark, where, etable, ca);
+ byte[] r = insertGap2w(code, where, gapLength, exclusive, jumps, pointers);
+ currentPos = pointers.cursor;
+ mark = pointers.mark;
+ int where2 = pointers.mark0;
+ if (where2 == currentPos && !exclusive)
+ currentPos += gapLength;
+
+ if (exclusive)
+ where2 -= gapLength;
+
+ newWhere.position = where2;
+ newWhere.length = gapLength;
+ return r;
+ }
+
+ private static byte[] insertGap2w(byte[] code, int where, int gapLength,
+ boolean exclusive, ArrayList jumps, Pointers ptrs)
+ throws BadBytecode
+ {
+ int n = jumps.size();
+ if (gapLength > 0) {
+ ptrs.shiftPc(where, gapLength, exclusive);
+ for (int i = 0; i < n; i++)
+ ((Branch)jumps.get(i)).shift(where, gapLength, exclusive);
+ }
+
+ boolean unstable = true;
+ do {
+ while (unstable) {
+ unstable = false;
+ for (int i = 0; i < n; i++) {
+ Branch b = (Branch)jumps.get(i);
+ if (b.expanded()) {
+ unstable = true;
+ int p = b.pos;
+ int delta = b.deltaSize();
+ ptrs.shiftPc(p, delta, false);
+ for (int j = 0; j < n; j++)
+ ((Branch)jumps.get(j)).shift(p, delta, false);
+ }
+ }
+ }
+
+ for (int i = 0; i < n; i++) {
+ Branch b = (Branch)jumps.get(i);
+ int diff = b.gapChanged();
+ if (diff > 0) {
+ unstable = true;
+ int p = b.pos;
+ ptrs.shiftPc(p, diff, false);
+ for (int j = 0; j < n; j++)
+ ((Branch)jumps.get(j)).shift(p, diff, false);
+ }
+ }
+ } while (unstable);
+
+ return makeExapndedCode(code, jumps, where, gapLength);
+ }
+
+ private static ArrayList makeJumpList(byte[] code, int endPos)
+ throws BadBytecode
+ {
+ ArrayList jumps = new ArrayList();
+ int nextPos;
+ for (int i = 0; i < endPos; i = nextPos) {
+ nextPos = nextOpcode(code, i);
+ int inst = code[i] & 0xff;
+ // if<cond>, if_icmp<cond>, if_acmp<cond>, goto, jsr
+ if ((153 <= inst && inst <= 168)
+ || inst == IFNULL || inst == IFNONNULL) {
+ /* 2bytes *signed* offset */
+ int offset = (code[i + 1] << 8) | (code[i + 2] & 0xff);
+ Branch b;
+ if (inst == GOTO || inst == JSR)
+ b = new Jump16(i, offset);
+ else
+ b = new If16(i, offset);
+
+ jumps.add(b);
+ }
+ else if (inst == GOTO_W || inst == JSR_W) {
+ /* 4bytes offset */
+ int offset = ByteArray.read32bit(code, i + 1);
+ jumps.add(new Jump32(i, offset));
+ }
+ else if (inst == TABLESWITCH) {
+ int i2 = (i & ~3) + 4; // 0-3 byte padding
+ int defaultbyte = ByteArray.read32bit(code, i2);
+ int lowbyte = ByteArray.read32bit(code, i2 + 4);
+ int highbyte = ByteArray.read32bit(code, i2 + 8);
+ int i0 = i2 + 12;
+ int size = highbyte - lowbyte + 1;
+ int[] offsets = new int[size];
+ for (int j = 0; j < size; j++) {
+ offsets[j] = ByteArray.read32bit(code, i0);
+ i0 += 4;
+ }
+
+ jumps.add(new Table(i, defaultbyte, lowbyte, highbyte, offsets));
+ }
+ else if (inst == LOOKUPSWITCH) {
+ int i2 = (i & ~3) + 4; // 0-3 byte padding
+ int defaultbyte = ByteArray.read32bit(code, i2);
+ int npairs = ByteArray.read32bit(code, i2 + 4);
+ int i0 = i2 + 8;
+ int[] matches = new int[npairs];
+ int[] offsets = new int[npairs];
+ for (int j = 0; j < npairs; j++) {
+ matches[j] = ByteArray.read32bit(code, i0);
+ offsets[j] = ByteArray.read32bit(code, i0 + 4);
+ i0 += 8;
+ }
+
+ jumps.add(new Lookup(i, defaultbyte, matches, offsets));
+ }
+ }
+
+ return jumps;
+ }
+
+ private static byte[] makeExapndedCode(byte[] code, ArrayList jumps,
+ int where, int gapLength)
+ throws BadBytecode
+ {
+ int n = jumps.size();
+ int size = code.length + gapLength;
+ for (int i = 0; i < n; i++) {
+ Branch b = (Branch)jumps.get(i);
+ size += b.deltaSize();
+ }
+
+ byte[] newcode = new byte[size];
+ int src = 0, dest = 0, bindex = 0;
+ int len = code.length;
+ Branch b;
+ int bpos;
+ if (0 < n) {
+ b = (Branch)jumps.get(0);
+ bpos = b.orgPos;
+ }
+ else {
+ b = null;
+ bpos = len; // src will be never equal to bpos
+ }
+
+ while (src < len) {
+ if (src == where) {
+ int pos2 = dest + gapLength;
+ while (dest < pos2)
+ newcode[dest++] = NOP;
+ }
+
+ if (src != bpos)
+ newcode[dest++] = code[src++];
+ else {
+ int s = b.write(src, code, dest, newcode);
+ src += s;
+ dest += s + b.deltaSize();
+ if (++bindex < n) {
+ b = (Branch)jumps.get(bindex);
+ bpos = b.orgPos;
+ }
+ else {
+ b = null;
+ bpos = len;
+ }
+ }
+ }
+
+ return newcode;
+ }
+
+ static abstract class Branch {
+ int pos, orgPos;
+ Branch(int p) { pos = orgPos = p; }
+ void shift(int where, int gapLength, boolean exclusive) {
+ if (where < pos || (where == pos && exclusive))
+ pos += gapLength;
+ }
+
+ static int shiftOffset(int i, int offset, int where,
+ int gapLength, boolean exclusive) {
+ int target = i + offset;
+ if (i < where) {
+ if (where < target || (exclusive && where == target))
+ offset += gapLength;
+ }
+ else if (i == where) {
+ // This code is different from the code in CodeIterator#newOffset().
+ // see JASSIST-124.
+ if (target < where && exclusive)
+ offset -= gapLength;
+ else if (where < target && !exclusive)
+ offset += gapLength;
+ }
+ else
+ if (target < where || (!exclusive && where == target))
+ offset -= gapLength;
+
+ return offset;
+ }
+
+ boolean expanded() { return false; }
+ int gapChanged() { return 0; }
+ int deltaSize() { return 0; } // newSize - oldSize
+
+ // This returns the original instruction size.
+ abstract int write(int srcPos, byte[] code, int destPos, byte[] newcode);
+ }
+
+ /* used by changeLdcToLdcW() and CodeAttribute.LdcEntry.
+ */
+ static class LdcW extends Branch {
+ int index;
+ boolean state;
+ LdcW(int p, int i) {
+ super(p);
+ index = i;
+ state = true;
+ }
+
+ boolean expanded() {
+ if (state) {
+ state = false;
+ return true;
+ }
+ else
+ return false;
+ }
+
+ int deltaSize() { return 1; }
+
+ int write(int srcPos, byte[] code, int destPos, byte[] newcode) {
+ newcode[destPos] = LDC_W;
+ ByteArray.write16bit(index, newcode, destPos + 1);
+ return 2;
+ }
+ }
+
+ static abstract class Branch16 extends Branch {
+ int offset;
+ int state;
+ static final int BIT16 = 0;
+ static final int EXPAND = 1;
+ static final int BIT32 = 2;
+
+ Branch16(int p, int off) {
+ super(p);
+ offset = off;
+ state = BIT16;
+ }
+
+ void shift(int where, int gapLength, boolean exclusive) {
+ offset = shiftOffset(pos, offset, where, gapLength, exclusive);
+ super.shift(where, gapLength, exclusive);
+ if (state == BIT16)
+ if (offset < Short.MIN_VALUE || Short.MAX_VALUE < offset)
+ state = EXPAND;
+ }
+
+ boolean expanded() {
+ if (state == EXPAND) {
+ state = BIT32;
+ return true;
+ }
+ else
+ return false;
+ }
+
+ abstract int deltaSize();
+ abstract void write32(int src, byte[] code, int dest, byte[] newcode);
+
+ int write(int src, byte[] code, int dest, byte[] newcode) {
+ if (state == BIT32)
+ write32(src, code, dest, newcode);
+ else {
+ newcode[dest] = code[src];
+ ByteArray.write16bit(offset, newcode, dest + 1);
+ }
+
+ return 3;
+ }
+ }
+
+ // GOTO or JSR
+ static class Jump16 extends Branch16 {
+ Jump16(int p, int off) {
+ super(p, off);
+ }
+
+ int deltaSize() {
+ return state == BIT32 ? 2 : 0;
+ }
+
+ void write32(int src, byte[] code, int dest, byte[] newcode) {
+ newcode[dest] = (byte)(((code[src] & 0xff) == GOTO) ? GOTO_W : JSR_W);
+ ByteArray.write32bit(offset, newcode, dest + 1);
+ }
+ }
+
+ // if<cond>, if_icmp<cond>, or if_acmp<cond>
+ static class If16 extends Branch16 {
+ If16(int p, int off) {
+ super(p, off);
+ }
+
+ int deltaSize() {
+ return state == BIT32 ? 5 : 0;
+ }
+
+ void write32(int src, byte[] code, int dest, byte[] newcode) {
+ newcode[dest] = (byte)opcode(code[src] & 0xff);
+ newcode[dest + 1] = 0;
+ newcode[dest + 2] = 8; // branch_offset = 8
+ newcode[dest + 3] = (byte)GOTO_W;
+ ByteArray.write32bit(offset - 3, newcode, dest + 4);
+ }
+
+ int opcode(int op) {
+ if (op == IFNULL)
+ return IFNONNULL;
+ else if (op == IFNONNULL)
+ return IFNULL;
+ else {
+ if (((op - IFEQ) & 1) == 0)
+ return op + 1;
+ else
+ return op - 1;
+ }
+ }
+ }
+
+ static class Jump32 extends Branch {
+ int offset;
+
+ Jump32(int p, int off) {
+ super(p);
+ offset = off;
+ }
+
+ void shift(int where, int gapLength, boolean exclusive) {
+ offset = shiftOffset(pos, offset, where, gapLength, exclusive);
+ super.shift(where, gapLength, exclusive);
+ }
+
+ int write(int src, byte[] code, int dest, byte[] newcode) {
+ newcode[dest] = code[src];
+ ByteArray.write32bit(offset, newcode, dest + 1);
+ return 5;
+ }
+ }
+
+ static abstract class Switcher extends Branch {
+ int gap, defaultByte;
+ int[] offsets;
+
+ Switcher(int pos, int defaultByte, int[] offsets) {
+ super(pos);
+ this.gap = 3 - (pos & 3);
+ this.defaultByte = defaultByte;
+ this.offsets = offsets;
+ }
+
+ void shift(int where, int gapLength, boolean exclusive) {
+ int p = pos;
+ defaultByte = shiftOffset(p, defaultByte, where, gapLength, exclusive);
+ int num = offsets.length;
+ for (int i = 0; i < num; i++)
+ offsets[i] = shiftOffset(p, offsets[i], where, gapLength, exclusive);
+
+ super.shift(where, gapLength, exclusive);
+ }
+
+ int gapChanged() {
+ int newGap = 3 - (pos & 3);
+ if (newGap > gap) {
+ int diff = newGap - gap;
+ gap = newGap;
+ return diff;
+ }
+
+ return 0;
+ }
+
+ int deltaSize() {
+ return gap - (3 - (orgPos & 3));
+ }
+
+ int write(int src, byte[] code, int dest, byte[] newcode) {
+ int padding = 3 - (pos & 3);
+ int nops = gap - padding;
+ int bytecodeSize = 5 + (3 - (orgPos & 3)) + tableSize();
+ adjustOffsets(bytecodeSize, nops);
+ newcode[dest++] = code[src];
+ while (padding-- > 0)
+ newcode[dest++] = 0;
+
+ ByteArray.write32bit(defaultByte, newcode, dest);
+ int size = write2(dest + 4, newcode);
+ dest += size + 4;
+ while (nops-- > 0)
+ newcode[dest++] = NOP;
+
+ return 5 + (3 - (orgPos & 3)) + size;
+ }
+
+ abstract int write2(int dest, byte[] newcode);
+ abstract int tableSize();
+
+ /* If the new bytecode size is shorter than the original, some NOPs
+ * are appended after this branch instruction (tableswitch or
+ * lookupswitch) to fill the gap.
+ * This method changes a branch offset to point to the first NOP
+ * if the offset originally points to the bytecode next to this
+ * branch instruction. Otherwise, the bytecode would contain
+ * dead code. It complicates the generation of StackMap and
+ * StackMapTable.
+ */
+ void adjustOffsets(int size, int nops) {
+ if (defaultByte == size)
+ defaultByte -= nops;
+
+ for (int i = 0; i < offsets.length; i++)
+ if (offsets[i] == size)
+ offsets[i] -= nops;
+ }
+ }
+
+ static class Table extends Switcher {
+ int low, high;
+
+ Table(int pos, int defaultByte, int low, int high, int[] offsets) {
+ super(pos, defaultByte, offsets);
+ this.low = low;
+ this.high = high;
+ }
+
+ int write2(int dest, byte[] newcode) {
+ ByteArray.write32bit(low, newcode, dest);
+ ByteArray.write32bit(high, newcode, dest + 4);
+ int n = offsets.length;
+ dest += 8;
+ for (int i = 0; i < n; i++) {
+ ByteArray.write32bit(offsets[i], newcode, dest);
+ dest += 4;
+ }
+
+ return 8 + 4 * n;
+ }
+
+ int tableSize() { return 8 + 4 * offsets.length; }
+ }
+
+ static class Lookup extends Switcher {
+ int[] matches;
+
+ Lookup(int pos, int defaultByte, int[] matches, int[] offsets) {
+ super(pos, defaultByte, offsets);
+ this.matches = matches;
+ }
+
+ int write2(int dest, byte[] newcode) {
+ int n = matches.length;
+ ByteArray.write32bit(n, newcode, dest);
+ dest += 4;
+ for (int i = 0; i < n; i++) {
+ ByteArray.write32bit(matches[i], newcode, dest);
+ ByteArray.write32bit(offsets[i], newcode, dest + 4);
+ dest += 8;
+ }
+
+ return 4 + 8 * n;
+ }
+
+ int tableSize() { return 4 + 8 * matches.length; }
+ }
+}
diff --git a/src/main/javassist/bytecode/ConstPool.java b/src/main/javassist/bytecode/ConstPool.java
new file mode 100644
index 0000000..df4e542
--- /dev/null
+++ b/src/main/javassist/bytecode/ConstPool.java
@@ -0,0 +1,1572 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintWriter;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javassist.CtClass;
+
+/**
+ * Constant pool table.
+ */
+public final class ConstPool {
+ LongVector items;
+ int numOfItems;
+ HashMap classes;
+ HashMap strings;
+ ConstInfo[] constInfoCache;
+ int[] constInfoIndexCache;
+ int thisClassInfo;
+
+ private static final int CACHE_SIZE = 32;
+
+ /**
+ * A hash function for CACHE_SIZE
+ */
+ private static int hashFunc(int a, int b) {
+ int h = -2128831035;
+ final int prime = 16777619;
+ h = (h ^ (a & 0xff)) * prime;
+ h = (h ^ (b & 0xff)) * prime;
+
+ // changing the hash key size from 32bit to 5bit
+ h = (h >> 5) ^ (h & 0x1f);
+ return h & 0x1f; // 0..31
+ }
+
+ /**
+ * <code>CONSTANT_Class</code>
+ */
+ public static final int CONST_Class = ClassInfo.tag;
+
+ /**
+ * <code>CONSTANT_Fieldref</code>
+ */
+ public static final int CONST_Fieldref = FieldrefInfo.tag;
+
+ /**
+ * <code>CONSTANT_Methodref</code>
+ */
+ public static final int CONST_Methodref = MethodrefInfo.tag;
+
+ /**
+ * <code>CONSTANT_InterfaceMethodref</code>
+ */
+ public static final int CONST_InterfaceMethodref
+ = InterfaceMethodrefInfo.tag;
+
+ /**
+ * <code>CONSTANT_String</code>
+ */
+ public static final int CONST_String = StringInfo.tag;
+
+ /**
+ * <code>CONSTANT_Integer</code>
+ */
+ public static final int CONST_Integer = IntegerInfo.tag;
+
+ /**
+ * <code>CONSTANT_Float</code>
+ */
+ public static final int CONST_Float = FloatInfo.tag;
+
+ /**
+ * <code>CONSTANT_Long</code>
+ */
+ public static final int CONST_Long = LongInfo.tag;
+
+ /**
+ * <code>CONSTANT_Double</code>
+ */
+ public static final int CONST_Double = DoubleInfo.tag;
+
+ /**
+ * <code>CONSTANT_NameAndType</code>
+ */
+ public static final int CONST_NameAndType = NameAndTypeInfo.tag;
+
+ /**
+ * <code>CONSTANT_Utf8</code>
+ */
+ public static final int CONST_Utf8 = Utf8Info.tag;
+
+ /**
+ * Represents the class using this constant pool table.
+ */
+ public static final CtClass THIS = null;
+
+ /**
+ * Constructs a constant pool table.
+ *
+ * @param thisclass the name of the class using this constant
+ * pool table
+ */
+ public ConstPool(String thisclass) {
+ items = new LongVector();
+ numOfItems = 0;
+ addItem(null); // index 0 is reserved by the JVM.
+ classes = new HashMap();
+ strings = new HashMap();
+ constInfoCache = new ConstInfo[CACHE_SIZE];
+ constInfoIndexCache = new int[CACHE_SIZE];
+ thisClassInfo = addClassInfo(thisclass);
+ }
+
+ /**
+ * Constructs a constant pool table from the given byte stream.
+ *
+ * @param in byte stream.
+ */
+ public ConstPool(DataInputStream in) throws IOException {
+ classes = new HashMap();
+ strings = new HashMap();
+ constInfoCache = new ConstInfo[CACHE_SIZE];
+ constInfoIndexCache = new int[CACHE_SIZE];
+ thisClassInfo = 0;
+ /* read() initializes items and numOfItems, and do addItem(null).
+ */
+ read(in);
+ }
+
+ void prune() {
+ classes = new HashMap();
+ strings = new HashMap();
+ constInfoCache = new ConstInfo[CACHE_SIZE];
+ constInfoIndexCache = new int[CACHE_SIZE];
+ }
+
+ /**
+ * Returns the number of entries in this table.
+ */
+ public int getSize() {
+ return numOfItems;
+ }
+
+ /**
+ * Returns the name of the class using this constant pool table.
+ */
+ public String getClassName() {
+ return getClassInfo(thisClassInfo);
+ }
+
+ /**
+ * Returns the index of <code>CONSTANT_Class_info</code> structure
+ * specifying the class using this constant pool table.
+ */
+ public int getThisClassInfo() {
+ return thisClassInfo;
+ }
+
+ void setThisClassInfo(int i) {
+ thisClassInfo = i;
+ }
+
+ ConstInfo getItem(int n) {
+ return items.elementAt(n);
+ }
+
+ /**
+ * Returns the <code>tag</code> field of the constant pool table
+ * entry at the given index.
+ */
+ public int getTag(int index) {
+ return getItem(index).getTag();
+ }
+
+ /**
+ * Reads <code>CONSTANT_Class_info</code> structure
+ * at the given index.
+ *
+ * @return a fully-qualified class or interface name specified
+ * by <code>name_index</code>. If the type is an array
+ * type, this method returns an encoded name like
+ * <code>[java.lang.Object;</code> (note that the separators
+ * are not slashes but dots).
+ * @see javassist.ClassPool#getCtClass(String)
+ */
+ public String getClassInfo(int index) {
+ ClassInfo c = (ClassInfo)getItem(index);
+ if (c == null)
+ return null;
+ else
+ return Descriptor.toJavaName(getUtf8Info(c.name));
+ }
+
+ /**
+ * Reads the <code>name_index</code> field of the
+ * <code>CONSTANT_NameAndType_info</code> structure
+ * at the given index.
+ */
+ public int getNameAndTypeName(int index) {
+ NameAndTypeInfo ntinfo = (NameAndTypeInfo)getItem(index);
+ return ntinfo.memberName;
+ }
+
+ /**
+ * Reads the <code>descriptor_index</code> field of the
+ * <code>CONSTANT_NameAndType_info</code> structure
+ * at the given index.
+ */
+ public int getNameAndTypeDescriptor(int index) {
+ NameAndTypeInfo ntinfo = (NameAndTypeInfo)getItem(index);
+ return ntinfo.typeDescriptor;
+ }
+
+ /**
+ * Reads the <code>class_index</code> field of the
+ * <code>CONSTANT_Fieldref_info</code>,
+ * <code>CONSTANT_Methodref_info</code>,
+ * or <code>CONSTANT_Interfaceref_info</code>,
+ * structure at the given index.
+ *
+ * @since 3.6
+ */
+ public int getMemberClass(int index) {
+ MemberrefInfo minfo = (MemberrefInfo)getItem(index);
+ return minfo.classIndex;
+ }
+
+ /**
+ * Reads the <code>name_and_type_index</code> field of the
+ * <code>CONSTANT_Fieldref_info</code>,
+ * <code>CONSTANT_Methodref_info</code>,
+ * or <code>CONSTANT_Interfaceref_info</code>,
+ * structure at the given index.
+ *
+ * @since 3.6
+ */
+ public int getMemberNameAndType(int index) {
+ MemberrefInfo minfo = (MemberrefInfo)getItem(index);
+ return minfo.nameAndTypeIndex;
+ }
+
+ /**
+ * Reads the <code>class_index</code> field of the
+ * <code>CONSTANT_Fieldref_info</code> structure
+ * at the given index.
+ */
+ public int getFieldrefClass(int index) {
+ FieldrefInfo finfo = (FieldrefInfo)getItem(index);
+ return finfo.classIndex;
+ }
+
+ /**
+ * Reads the <code>class_index</code> field of the
+ * <code>CONSTANT_Fieldref_info</code> structure
+ * at the given index.
+ *
+ * @return the name of the class at that <code>class_index</code>.
+ */
+ public String getFieldrefClassName(int index) {
+ FieldrefInfo f = (FieldrefInfo)getItem(index);
+ if (f == null)
+ return null;
+ else
+ return getClassInfo(f.classIndex);
+ }
+
+ /**
+ * Reads the <code>name_and_type_index</code> field of the
+ * <code>CONSTANT_Fieldref_info</code> structure
+ * at the given index.
+ */
+ public int getFieldrefNameAndType(int index) {
+ FieldrefInfo finfo = (FieldrefInfo)getItem(index);
+ return finfo.nameAndTypeIndex;
+ }
+
+ /**
+ * Reads the <code>name_index</code> field of the
+ * <code>CONSTANT_NameAndType_info</code> structure
+ * indirectly specified by the given index.
+ *
+ * @param index an index to a <code>CONSTANT_Fieldref_info</code>.
+ * @return the name of the field.
+ */
+ public String getFieldrefName(int index) {
+ FieldrefInfo f = (FieldrefInfo)getItem(index);
+ if (f == null)
+ return null;
+ else {
+ NameAndTypeInfo n = (NameAndTypeInfo)getItem(f.nameAndTypeIndex);
+ if(n == null)
+ return null;
+ else
+ return getUtf8Info(n.memberName);
+ }
+ }
+
+ /**
+ * Reads the <code>descriptor_index</code> field of the
+ * <code>CONSTANT_NameAndType_info</code> structure
+ * indirectly specified by the given index.
+ *
+ * @param index an index to a <code>CONSTANT_Fieldref_info</code>.
+ * @return the type descriptor of the field.
+ */
+ public String getFieldrefType(int index) {
+ FieldrefInfo f = (FieldrefInfo)getItem(index);
+ if (f == null)
+ return null;
+ else {
+ NameAndTypeInfo n = (NameAndTypeInfo)getItem(f.nameAndTypeIndex);
+ if(n == null)
+ return null;
+ else
+ return getUtf8Info(n.typeDescriptor);
+ }
+ }
+
+ /**
+ * Reads the <code>class_index</code> field of the
+ * <code>CONSTANT_Methodref_info</code> structure
+ * at the given index.
+ */
+ public int getMethodrefClass(int index) {
+ MethodrefInfo minfo = (MethodrefInfo)getItem(index);
+ return minfo.classIndex;
+ }
+
+ /**
+ * Reads the <code>class_index</code> field of the
+ * <code>CONSTANT_Methodref_info</code> structure
+ * at the given index.
+ *
+ * @return the name of the class at that <code>class_index</code>.
+ */
+ public String getMethodrefClassName(int index) {
+ MethodrefInfo minfo = (MethodrefInfo)getItem(index);
+ if (minfo == null)
+ return null;
+ else
+ return getClassInfo(minfo.classIndex);
+ }
+
+ /**
+ * Reads the <code>name_and_type_index</code> field of the
+ * <code>CONSTANT_Methodref_info</code> structure
+ * at the given index.
+ */
+ public int getMethodrefNameAndType(int index) {
+ MethodrefInfo minfo = (MethodrefInfo)getItem(index);
+ return minfo.nameAndTypeIndex;
+ }
+
+ /**
+ * Reads the <code>name_index</code> field of the
+ * <code>CONSTANT_NameAndType_info</code> structure
+ * indirectly specified by the given index.
+ *
+ * @param index an index to a <code>CONSTANT_Methodref_info</code>.
+ * @return the name of the method.
+ */
+ public String getMethodrefName(int index) {
+ MethodrefInfo minfo = (MethodrefInfo)getItem(index);
+ if (minfo == null)
+ return null;
+ else {
+ NameAndTypeInfo n
+ = (NameAndTypeInfo)getItem(minfo.nameAndTypeIndex);
+ if(n == null)
+ return null;
+ else
+ return getUtf8Info(n.memberName);
+ }
+ }
+
+ /**
+ * Reads the <code>descriptor_index</code> field of the
+ * <code>CONSTANT_NameAndType_info</code> structure
+ * indirectly specified by the given index.
+ *
+ * @param index an index to a <code>CONSTANT_Methodref_info</code>.
+ * @return the descriptor of the method.
+ */
+ public String getMethodrefType(int index) {
+ MethodrefInfo minfo = (MethodrefInfo)getItem(index);
+ if (minfo == null)
+ return null;
+ else {
+ NameAndTypeInfo n
+ = (NameAndTypeInfo)getItem(minfo.nameAndTypeIndex);
+ if(n == null)
+ return null;
+ else
+ return getUtf8Info(n.typeDescriptor);
+ }
+ }
+
+ /**
+ * Reads the <code>class_index</code> field of the
+ * <code>CONSTANT_InterfaceMethodref_info</code> structure
+ * at the given index.
+ */
+ public int getInterfaceMethodrefClass(int index) {
+ InterfaceMethodrefInfo minfo
+ = (InterfaceMethodrefInfo)getItem(index);
+ return minfo.classIndex;
+ }
+
+ /**
+ * Reads the <code>class_index</code> field of the
+ * <code>CONSTANT_InterfaceMethodref_info</code> structure
+ * at the given index.
+ *
+ * @return the name of the class at that <code>class_index</code>.
+ */
+ public String getInterfaceMethodrefClassName(int index) {
+ InterfaceMethodrefInfo minfo
+ = (InterfaceMethodrefInfo)getItem(index);
+ return getClassInfo(minfo.classIndex);
+ }
+
+ /**
+ * Reads the <code>name_and_type_index</code> field of the
+ * <code>CONSTANT_InterfaceMethodref_info</code> structure
+ * at the given index.
+ */
+ public int getInterfaceMethodrefNameAndType(int index) {
+ InterfaceMethodrefInfo minfo
+ = (InterfaceMethodrefInfo)getItem(index);
+ return minfo.nameAndTypeIndex;
+ }
+
+ /**
+ * Reads the <code>name_index</code> field of the
+ * <code>CONSTANT_NameAndType_info</code> structure
+ * indirectly specified by the given index.
+ *
+ * @param index an index to
+ * a <code>CONSTANT_InterfaceMethodref_info</code>.
+ * @return the name of the method.
+ */
+ public String getInterfaceMethodrefName(int index) {
+ InterfaceMethodrefInfo minfo
+ = (InterfaceMethodrefInfo)getItem(index);
+ if (minfo == null)
+ return null;
+ else {
+ NameAndTypeInfo n
+ = (NameAndTypeInfo)getItem(minfo.nameAndTypeIndex);
+ if(n == null)
+ return null;
+ else
+ return getUtf8Info(n.memberName);
+ }
+ }
+
+ /**
+ * Reads the <code>descriptor_index</code> field of the
+ * <code>CONSTANT_NameAndType_info</code> structure
+ * indirectly specified by the given index.
+ *
+ * @param index an index to
+ * a <code>CONSTANT_InterfaceMethodref_info</code>.
+ * @return the descriptor of the method.
+ */
+ public String getInterfaceMethodrefType(int index) {
+ InterfaceMethodrefInfo minfo
+ = (InterfaceMethodrefInfo)getItem(index);
+ if (minfo == null)
+ return null;
+ else {
+ NameAndTypeInfo n
+ = (NameAndTypeInfo)getItem(minfo.nameAndTypeIndex);
+ if(n == null)
+ return null;
+ else
+ return getUtf8Info(n.typeDescriptor);
+ }
+ }
+ /**
+ * Reads <code>CONSTANT_Integer_info</code>, <code>_Float_info</code>,
+ * <code>_Long_info</code>, <code>_Double_info</code>, or
+ * <code>_String_info</code> structure.
+ * These are used with the LDC instruction.
+ *
+ * @return a <code>String</code> value or a wrapped primitive-type
+ * value.
+ */
+ public Object getLdcValue(int index) {
+ ConstInfo constInfo = this.getItem(index);
+ Object value = null;
+ if (constInfo instanceof StringInfo)
+ value = this.getStringInfo(index);
+ else if (constInfo instanceof FloatInfo)
+ value = new Float(getFloatInfo(index));
+ else if (constInfo instanceof IntegerInfo)
+ value = new Integer(getIntegerInfo(index));
+ else if (constInfo instanceof LongInfo)
+ value = new Long(getLongInfo(index));
+ else if (constInfo instanceof DoubleInfo)
+ value = new Double(getDoubleInfo(index));
+ else
+ value = null;
+
+ return value;
+ }
+
+ /**
+ * Reads <code>CONSTANT_Integer_info</code> structure
+ * at the given index.
+ *
+ * @return the value specified by this entry.
+ */
+ public int getIntegerInfo(int index) {
+ IntegerInfo i = (IntegerInfo)getItem(index);
+ return i.value;
+ }
+
+ /**
+ * Reads <code>CONSTANT_Float_info</code> structure
+ * at the given index.
+ *
+ * @return the value specified by this entry.
+ */
+ public float getFloatInfo(int index) {
+ FloatInfo i = (FloatInfo)getItem(index);
+ return i.value;
+ }
+
+ /**
+ * Reads <code>CONSTANT_Long_info</code> structure
+ * at the given index.
+ *
+ * @return the value specified by this entry.
+ */
+ public long getLongInfo(int index) {
+ LongInfo i = (LongInfo)getItem(index);
+ return i.value;
+ }
+
+ /**
+ * Reads <code>CONSTANT_Double_info</code> structure
+ * at the given index.
+ *
+ * @return the value specified by this entry.
+ */
+ public double getDoubleInfo(int index) {
+ DoubleInfo i = (DoubleInfo)getItem(index);
+ return i.value;
+ }
+
+ /**
+ * Reads <code>CONSTANT_String_info</code> structure
+ * at the given index.
+ *
+ * @return the string specified by <code>string_index</code>.
+ */
+ public String getStringInfo(int index) {
+ StringInfo si = (StringInfo)getItem(index);
+ return getUtf8Info(si.string);
+ }
+
+ /**
+ * Reads <code>CONSTANT_utf8_info</code> structure
+ * at the given index.
+ *
+ * @return the string specified by this entry.
+ */
+ public String getUtf8Info(int index) {
+ Utf8Info utf = (Utf8Info)getItem(index);
+ return utf.string;
+ }
+
+ /**
+ * Determines whether <code>CONSTANT_Methodref_info</code>
+ * structure at the given index represents the constructor
+ * of the given class.
+ *
+ * @return the <code>descriptor_index</code> specifying
+ * the type descriptor of the that constructor.
+ * If it is not that constructor,
+ * <code>isConstructor()</code> returns 0.
+ */
+ public int isConstructor(String classname, int index) {
+ return isMember(classname, MethodInfo.nameInit, index);
+ }
+
+ /**
+ * Determines whether <code>CONSTANT_Methodref_info</code>,
+ * <code>CONSTANT_Fieldref_info</code>, or
+ * <code>CONSTANT_InterfaceMethodref_info</code> structure
+ * at the given index represents the member with the specified
+ * name and declaring class.
+ *
+ * @param classname the class declaring the member
+ * @param membername the member name
+ * @param index the index into the constant pool table
+ *
+ * @return the <code>descriptor_index</code> specifying
+ * the type descriptor of that member.
+ * If it is not that member,
+ * <code>isMember()</code> returns 0.
+ */
+ public int isMember(String classname, String membername, int index) {
+ MemberrefInfo minfo = (MemberrefInfo)getItem(index);
+ if (getClassInfo(minfo.classIndex).equals(classname)) {
+ NameAndTypeInfo ntinfo
+ = (NameAndTypeInfo)getItem(minfo.nameAndTypeIndex);
+ if (getUtf8Info(ntinfo.memberName).equals(membername))
+ return ntinfo.typeDescriptor;
+ }
+
+ return 0; // false
+ }
+
+ /**
+ * Determines whether <code>CONSTANT_Methodref_info</code>,
+ * <code>CONSTANT_Fieldref_info</code>, or
+ * <code>CONSTANT_InterfaceMethodref_info</code> structure
+ * at the given index has the name and the descriptor
+ * given as the arguments.
+ *
+ * @param membername the member name
+ * @param desc the descriptor of the member.
+ * @param index the index into the constant pool table
+ *
+ * @return the name of the target class specified by
+ * the <code>..._info</code> structure
+ * at <code>index</code>.
+ * Otherwise, null if that structure does not
+ * match the given member name and descriptor.
+ */
+ public String eqMember(String membername, String desc, int index) {
+ MemberrefInfo minfo = (MemberrefInfo)getItem(index);
+ NameAndTypeInfo ntinfo
+ = (NameAndTypeInfo)getItem(minfo.nameAndTypeIndex);
+ if (getUtf8Info(ntinfo.memberName).equals(membername)
+ && getUtf8Info(ntinfo.typeDescriptor).equals(desc))
+ return getClassInfo(minfo.classIndex);
+ else
+ return null; // false
+ }
+
+ private int addItem(ConstInfo info) {
+ items.addElement(info);
+ return numOfItems++;
+ }
+
+ /**
+ * Copies the n-th item in this ConstPool object into the destination
+ * ConstPool object.
+ * The class names that the item refers to are renamed according
+ * to the given map.
+ *
+ * @param n the <i>n</i>-th item
+ * @param dest destination constant pool table
+ * @param classnames the map or null.
+ * @return the index of the copied item into the destination ClassPool.
+ */
+ public int copy(int n, ConstPool dest, Map classnames) {
+ if (n == 0)
+ return 0;
+
+ ConstInfo info = getItem(n);
+ return info.copy(this, dest, classnames);
+ }
+
+ int addConstInfoPadding() {
+ return addItem(new ConstInfoPadding());
+ }
+
+ /**
+ * Adds a new <code>CONSTANT_Class_info</code> structure.
+ *
+ * <p>This also adds a <code>CONSTANT_Utf8_info</code> structure
+ * for storing the class name.
+ *
+ * @return the index of the added entry.
+ */
+ public int addClassInfo(CtClass c) {
+ if (c == THIS)
+ return thisClassInfo;
+ else if (!c.isArray())
+ return addClassInfo(c.getName());
+ else {
+ // an array type is recorded in the hashtable with
+ // the key "[L<classname>;" instead of "<classname>".
+ //
+ // note: toJvmName(toJvmName(c)) is equal to toJvmName(c).
+
+ return addClassInfo(Descriptor.toJvmName(c));
+ }
+ }
+
+ /**
+ * Adds a new <code>CONSTANT_Class_info</code> structure.
+ *
+ * <p>This also adds a <code>CONSTANT_Utf8_info</code> structure
+ * for storing the class name.
+ *
+ * @param qname a fully-qualified class name
+ * (or the JVM-internal representation of that name).
+ * @return the index of the added entry.
+ */
+ public int addClassInfo(String qname) {
+ ClassInfo info = (ClassInfo)classes.get(qname);
+ if (info != null)
+ return info.index;
+ else {
+ int utf8 = addUtf8Info(Descriptor.toJvmName(qname));
+ info = new ClassInfo(utf8, numOfItems);
+ classes.put(qname, info);
+ return addItem(info);
+ }
+ }
+
+ /**
+ * Adds a new <code>CONSTANT_NameAndType_info</code> structure.
+ *
+ * <p>This also adds <code>CONSTANT_Utf8_info</code> structures.
+ *
+ * @param name <code>name_index</code>
+ * @param type <code>descriptor_index</code>
+ * @return the index of the added entry.
+ */
+ public int addNameAndTypeInfo(String name, String type) {
+ return addNameAndTypeInfo(addUtf8Info(name), addUtf8Info(type));
+ }
+
+ /**
+ * Adds a new <code>CONSTANT_NameAndType_info</code> structure.
+ *
+ * @param name <code>name_index</code>
+ * @param type <code>descriptor_index</code>
+ * @return the index of the added entry.
+ */
+ public int addNameAndTypeInfo(int name, int type) {
+ int h = hashFunc(name, type);
+ ConstInfo ci = constInfoCache[h];
+ if (ci != null && ci instanceof NameAndTypeInfo && ci.hashCheck(name, type))
+ return constInfoIndexCache[h];
+ else {
+ NameAndTypeInfo item = new NameAndTypeInfo(name, type);
+ constInfoCache[h] = item;
+ int i = addItem(item);
+ constInfoIndexCache[h] = i;
+ return i;
+ }
+ }
+
+ /**
+ * Adds a new <code>CONSTANT_Fieldref_info</code> structure.
+ *
+ * <p>This also adds a new <code>CONSTANT_NameAndType_info</code>
+ * structure.
+ *
+ * @param classInfo <code>class_index</code>
+ * @param name <code>name_index</code>
+ * of <code>CONSTANT_NameAndType_info</code>.
+ * @param type <code>descriptor_index</code>
+ * of <code>CONSTANT_NameAndType_info</code>.
+ * @return the index of the added entry.
+ */
+ public int addFieldrefInfo(int classInfo, String name, String type) {
+ int nt = addNameAndTypeInfo(name, type);
+ return addFieldrefInfo(classInfo, nt);
+ }
+
+ /**
+ * Adds a new <code>CONSTANT_Fieldref_info</code> structure.
+ *
+ * @param classInfo <code>class_index</code>
+ * @param nameAndTypeInfo <code>name_and_type_index</code>.
+ * @return the index of the added entry.
+ */
+ public int addFieldrefInfo(int classInfo, int nameAndTypeInfo) {
+ int h = hashFunc(classInfo, nameAndTypeInfo);
+ ConstInfo ci = constInfoCache[h];
+ if (ci != null && ci instanceof FieldrefInfo && ci.hashCheck(classInfo, nameAndTypeInfo))
+ return constInfoIndexCache[h];
+ else {
+ FieldrefInfo item = new FieldrefInfo(classInfo, nameAndTypeInfo);
+ constInfoCache[h] = item;
+ int i = addItem(item);
+ constInfoIndexCache[h] = i;
+ return i;
+ }
+ }
+
+ /**
+ * Adds a new <code>CONSTANT_Methodref_info</code> structure.
+ *
+ * <p>This also adds a new <code>CONSTANT_NameAndType_info</code>
+ * structure.
+ *
+ * @param classInfo <code>class_index</code>
+ * @param name <code>name_index</code>
+ * of <code>CONSTANT_NameAndType_info</code>.
+ * @param type <code>descriptor_index</code>
+ * of <code>CONSTANT_NameAndType_info</code>.
+ * @return the index of the added entry.
+ */
+ public int addMethodrefInfo(int classInfo, String name, String type) {
+ int nt = addNameAndTypeInfo(name, type);
+ return addMethodrefInfo(classInfo, nt);
+ }
+
+ /**
+ * Adds a new <code>CONSTANT_Methodref_info</code> structure.
+ *
+ * @param classInfo <code>class_index</code>
+ * @param nameAndTypeInfo <code>name_and_type_index</code>.
+ * @return the index of the added entry.
+ */
+ public int addMethodrefInfo(int classInfo, int nameAndTypeInfo) {
+ int h = hashFunc(classInfo, nameAndTypeInfo);
+ ConstInfo ci = constInfoCache[h];
+ if (ci != null && ci instanceof MethodrefInfo && ci.hashCheck(classInfo, nameAndTypeInfo))
+ return constInfoIndexCache[h];
+ else {
+ MethodrefInfo item = new MethodrefInfo(classInfo, nameAndTypeInfo);
+ constInfoCache[h] = item;
+ int i = addItem(item);
+ constInfoIndexCache[h] = i;
+ return i;
+ }
+ }
+
+ /**
+ * Adds a new <code>CONSTANT_InterfaceMethodref_info</code>
+ * structure.
+ *
+ * <p>This also adds a new <code>CONSTANT_NameAndType_info</code>
+ * structure.
+ *
+ * @param classInfo <code>class_index</code>
+ * @param name <code>name_index</code>
+ * of <code>CONSTANT_NameAndType_info</code>.
+ * @param type <code>descriptor_index</code>
+ * of <code>CONSTANT_NameAndType_info</code>.
+ * @return the index of the added entry.
+ */
+ public int addInterfaceMethodrefInfo(int classInfo, String name,
+ String type) {
+ int nt = addNameAndTypeInfo(name, type);
+ return addInterfaceMethodrefInfo(classInfo, nt);
+ }
+
+ /**
+ * Adds a new <code>CONSTANT_InterfaceMethodref_info</code>
+ * structure.
+ *
+ * @param classInfo <code>class_index</code>
+ * @param nameAndTypeInfo <code>name_and_type_index</code>.
+ * @return the index of the added entry.
+ */
+ public int addInterfaceMethodrefInfo(int classInfo,
+ int nameAndTypeInfo) {
+ int h = hashFunc(classInfo, nameAndTypeInfo);
+ ConstInfo ci = constInfoCache[h];
+ if (ci != null && ci instanceof InterfaceMethodrefInfo && ci.hashCheck(classInfo, nameAndTypeInfo))
+ return constInfoIndexCache[h];
+ else {
+ InterfaceMethodrefInfo item =new InterfaceMethodrefInfo(classInfo, nameAndTypeInfo);
+ constInfoCache[h] = item;
+ int i = addItem(item);
+ constInfoIndexCache[h] = i;
+ return i;
+ }
+ }
+
+ /**
+ * Adds a new <code>CONSTANT_String_info</code>
+ * structure.
+ *
+ * <p>This also adds a new <code>CONSTANT_Utf8_info</code>
+ * structure.
+ *
+ * @return the index of the added entry.
+ */
+ public int addStringInfo(String str) {
+ return addItem(new StringInfo(addUtf8Info(str)));
+ }
+
+ /**
+ * Adds a new <code>CONSTANT_Integer_info</code>
+ * structure.
+ *
+ * @return the index of the added entry.
+ */
+ public int addIntegerInfo(int i) {
+ return addItem(new IntegerInfo(i));
+ }
+
+ /**
+ * Adds a new <code>CONSTANT_Float_info</code>
+ * structure.
+ *
+ * @return the index of the added entry.
+ */
+ public int addFloatInfo(float f) {
+ return addItem(new FloatInfo(f));
+ }
+
+ /**
+ * Adds a new <code>CONSTANT_Long_info</code>
+ * structure.
+ *
+ * @return the index of the added entry.
+ */
+ public int addLongInfo(long l) {
+ int i = addItem(new LongInfo(l));
+ addItem(new ConstInfoPadding());
+ return i;
+ }
+
+ /**
+ * Adds a new <code>CONSTANT_Double_info</code>
+ * structure.
+ *
+ * @return the index of the added entry.
+ */
+ public int addDoubleInfo(double d) {
+ int i = addItem(new DoubleInfo(d));
+ addItem(new ConstInfoPadding());
+ return i;
+ }
+
+ /**
+ * Adds a new <code>CONSTANT_Utf8_info</code>
+ * structure.
+ *
+ * <p>If the given utf8 string has been already recorded in the
+ * table, then this method does not add a new entry to avoid adding
+ * a duplicated entry.
+ * Instead, it returns the index of the entry already recorded.
+ *
+ * @return the index of the added entry.
+ */
+ public int addUtf8Info(String utf8) {
+ Utf8Info info = (Utf8Info)strings.get(utf8);
+ if (info != null)
+ return info.index;
+ else {
+ info = new Utf8Info(utf8, numOfItems);
+ strings.put(utf8, info);
+ return addItem(info);
+ }
+ }
+
+ /**
+ * Get all the class names.
+ *
+ * @return a set of class names
+ */
+ public Set getClassNames()
+ {
+ HashSet result = new HashSet();
+ LongVector v = items;
+ int size = numOfItems;
+ for (int i = 1; i < size; ++i) {
+ String className = v.elementAt(i).getClassName(this);
+ if (className != null)
+ result.add(className);
+ }
+ return result;
+ }
+
+ /**
+ * Replaces all occurrences of a class name.
+ *
+ * @param oldName the replaced name (JVM-internal representation).
+ * @param newName the substituted name (JVM-internal representation).
+ */
+ public void renameClass(String oldName, String newName) {
+ LongVector v = items;
+ int size = numOfItems;
+ classes = new HashMap(classes.size() * 2);
+ for (int i = 1; i < size; ++i) {
+ ConstInfo ci = v.elementAt(i);
+ ci.renameClass(this, oldName, newName);
+ ci.makeHashtable(this);
+ }
+ }
+
+ /**
+ * Replaces all occurrences of class names.
+ *
+ * @param classnames specifies pairs of replaced and substituted
+ * name.
+ */
+ public void renameClass(Map classnames) {
+ LongVector v = items;
+ int size = numOfItems;
+ classes = new HashMap(classes.size() * 2);
+ for (int i = 1; i < size; ++i) {
+ ConstInfo ci = v.elementAt(i);
+ ci.renameClass(this, classnames);
+ ci.makeHashtable(this);
+ }
+ }
+
+ private void read(DataInputStream in) throws IOException {
+ int n = in.readUnsignedShort();
+
+ items = new LongVector(n);
+ numOfItems = 0;
+ addItem(null); // index 0 is reserved by the JVM.
+
+ while (--n > 0) { // index 0 is reserved by JVM
+ int tag = readOne(in);
+ if ((tag == LongInfo.tag) || (tag == DoubleInfo.tag)) {
+ addItem(new ConstInfoPadding());
+ --n;
+ }
+ }
+
+ int i = 1;
+ while (true) {
+ ConstInfo info = items.elementAt(i++);
+ if (info == null)
+ break;
+ else
+ info.makeHashtable(this);
+ }
+ }
+
+ private int readOne(DataInputStream in) throws IOException {
+ ConstInfo info;
+ int tag = in.readUnsignedByte();
+ switch (tag) {
+ case Utf8Info.tag : // 1
+ info = new Utf8Info(in, numOfItems);
+ strings.put(((Utf8Info)info).string, info);
+ break;
+ case IntegerInfo.tag : // 3
+ info = new IntegerInfo(in);
+ break;
+ case FloatInfo.tag : // 4
+ info = new FloatInfo(in);
+ break;
+ case LongInfo.tag : // 5
+ info = new LongInfo(in);
+ break;
+ case DoubleInfo.tag : // 6
+ info = new DoubleInfo(in);
+ break;
+ case ClassInfo.tag : // 7
+ info = new ClassInfo(in, numOfItems);
+ // classes.put(<classname>, info);
+ break;
+ case StringInfo.tag : // 8
+ info = new StringInfo(in);
+ break;
+ case FieldrefInfo.tag : // 9
+ info = new FieldrefInfo(in);
+ break;
+ case MethodrefInfo.tag : // 10
+ info = new MethodrefInfo(in);
+ break;
+ case InterfaceMethodrefInfo.tag : // 11
+ info = new InterfaceMethodrefInfo(in);
+ break;
+ case NameAndTypeInfo.tag : // 12
+ info = new NameAndTypeInfo(in);
+ break;
+ default :
+ throw new IOException("invalid constant type: " + tag);
+ }
+
+ addItem(info);
+ return tag;
+ }
+
+ /**
+ * Writes the contents of the constant pool table.
+ */
+ public void write(DataOutputStream out) throws IOException {
+ out.writeShort(numOfItems);
+ LongVector v = items;
+ int size = numOfItems;
+ for (int i = 1; i < size; ++i)
+ v.elementAt(i).write(out);
+ }
+
+ /**
+ * Prints the contents of the constant pool table.
+ */
+ public void print() {
+ print(new PrintWriter(System.out, true));
+ }
+
+ /**
+ * Prints the contents of the constant pool table.
+ */
+ public void print(PrintWriter out) {
+ int size = numOfItems;
+ for (int i = 1; i < size; ++i) {
+ out.print(i);
+ out.print(" ");
+ items.elementAt(i).print(out);
+ }
+ }
+}
+
+abstract class ConstInfo {
+ public abstract int getTag();
+
+ public String getClassName(ConstPool cp) { return null; }
+ public void renameClass(ConstPool cp, String oldName, String newName) {}
+ public void renameClass(ConstPool cp, Map classnames) {}
+ public abstract int copy(ConstPool src, ConstPool dest, Map classnames);
+ // ** classnames is a mapping between JVM names.
+
+ public abstract void write(DataOutputStream out) throws IOException;
+ public abstract void print(PrintWriter out);
+
+ void makeHashtable(ConstPool cp) {} // called after read() finishes in ConstPool.
+
+ boolean hashCheck(int a, int b) { return false; }
+
+ public String toString() {
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ PrintWriter out = new PrintWriter(bout);
+ print(out);
+ return bout.toString();
+ }
+}
+
+/* padding following DoubleInfo or LongInfo.
+ */
+class ConstInfoPadding extends ConstInfo {
+ public int getTag() { return 0; }
+
+ public int copy(ConstPool src, ConstPool dest, Map map) {
+ return dest.addConstInfoPadding();
+ }
+
+ public void write(DataOutputStream out) throws IOException {}
+
+ public void print(PrintWriter out) {
+ out.println("padding");
+ }
+}
+
+class ClassInfo extends ConstInfo {
+ static final int tag = 7;
+ int name;
+ int index;
+
+ public ClassInfo(int className, int i) {
+ name = className;
+ index = i;
+ }
+
+ public ClassInfo(DataInputStream in, int i) throws IOException {
+ name = in.readUnsignedShort();
+ index = i;
+ }
+
+ public int getTag() { return tag; }
+
+ public String getClassName(ConstPool cp) {
+ return cp.getUtf8Info(name);
+ };
+
+ public void renameClass(ConstPool cp, String oldName, String newName) {
+ String nameStr = cp.getUtf8Info(name);
+ if (nameStr.equals(oldName))
+ name = cp.addUtf8Info(newName);
+ else if (nameStr.charAt(0) == '[') {
+ String nameStr2 = Descriptor.rename(nameStr, oldName, newName);
+ if (nameStr != nameStr2)
+ name = cp.addUtf8Info(nameStr2);
+ }
+ }
+
+ public void renameClass(ConstPool cp, Map map) {
+ String oldName = cp.getUtf8Info(name);
+ if (oldName.charAt(0) == '[') {
+ String newName = Descriptor.rename(oldName, map);
+ if (oldName != newName)
+ name = cp.addUtf8Info(newName);
+ }
+ else {
+ String newName = (String)map.get(oldName);
+ if (newName != null && !newName.equals(oldName))
+ name = cp.addUtf8Info(newName);
+ }
+ }
+
+ public int copy(ConstPool src, ConstPool dest, Map map) {
+ String classname = src.getUtf8Info(name);
+ if (map != null) {
+ String newname = (String)map.get(classname);
+ if (newname != null)
+ classname = newname;
+ }
+
+ return dest.addClassInfo(classname);
+ }
+
+ public void write(DataOutputStream out) throws IOException {
+ out.writeByte(tag);
+ out.writeShort(name);
+ }
+
+ public void print(PrintWriter out) {
+ out.print("Class #");
+ out.println(name);
+ }
+
+ void makeHashtable(ConstPool cp) {
+ String name = Descriptor.toJavaName(getClassName(cp));
+ cp.classes.put(name, this);
+ }
+}
+
+class NameAndTypeInfo extends ConstInfo {
+ static final int tag = 12;
+ int memberName;
+ int typeDescriptor;
+
+ public NameAndTypeInfo(int name, int type) {
+ memberName = name;
+ typeDescriptor = type;
+ }
+
+ public NameAndTypeInfo(DataInputStream in) throws IOException {
+ memberName = in.readUnsignedShort();
+ typeDescriptor = in.readUnsignedShort();
+ }
+
+ boolean hashCheck(int a, int b) { return a == memberName && b == typeDescriptor; }
+
+ public int getTag() { return tag; }
+
+ public void renameClass(ConstPool cp, String oldName, String newName) {
+ String type = cp.getUtf8Info(typeDescriptor);
+ String type2 = Descriptor.rename(type, oldName, newName);
+ if (type != type2)
+ typeDescriptor = cp.addUtf8Info(type2);
+ }
+
+ public void renameClass(ConstPool cp, Map map) {
+ String type = cp.getUtf8Info(typeDescriptor);
+ String type2 = Descriptor.rename(type, map);
+ if (type != type2)
+ typeDescriptor = cp.addUtf8Info(type2);
+ }
+
+ public int copy(ConstPool src, ConstPool dest, Map map) {
+ String mname = src.getUtf8Info(memberName);
+ String tdesc = src.getUtf8Info(typeDescriptor);
+ tdesc = Descriptor.rename(tdesc, map);
+ return dest.addNameAndTypeInfo(dest.addUtf8Info(mname),
+ dest.addUtf8Info(tdesc));
+ }
+
+ public void write(DataOutputStream out) throws IOException {
+ out.writeByte(tag);
+ out.writeShort(memberName);
+ out.writeShort(typeDescriptor);
+ }
+
+ public void print(PrintWriter out) {
+ out.print("NameAndType #");
+ out.print(memberName);
+ out.print(", type #");
+ out.println(typeDescriptor);
+ }
+}
+
+abstract class MemberrefInfo extends ConstInfo {
+ int classIndex;
+ int nameAndTypeIndex;
+
+ public MemberrefInfo(int cindex, int ntindex) {
+ classIndex = cindex;
+ nameAndTypeIndex = ntindex;
+ }
+
+ public MemberrefInfo(DataInputStream in) throws IOException {
+ classIndex = in.readUnsignedShort();
+ nameAndTypeIndex = in.readUnsignedShort();
+ }
+
+ public int copy(ConstPool src, ConstPool dest, Map map) {
+ int classIndex2 = src.getItem(classIndex).copy(src, dest, map);
+ int ntIndex2 = src.getItem(nameAndTypeIndex).copy(src, dest, map);
+ return copy2(dest, classIndex2, ntIndex2);
+ }
+
+ boolean hashCheck(int a, int b) { return a == classIndex && b == nameAndTypeIndex; }
+
+ abstract protected int copy2(ConstPool dest, int cindex, int ntindex);
+
+ public void write(DataOutputStream out) throws IOException {
+ out.writeByte(getTag());
+ out.writeShort(classIndex);
+ out.writeShort(nameAndTypeIndex);
+ }
+
+ public void print(PrintWriter out) {
+ out.print(getTagName() + " #");
+ out.print(classIndex);
+ out.print(", name&type #");
+ out.println(nameAndTypeIndex);
+ }
+
+ public abstract String getTagName();
+}
+
+class FieldrefInfo extends MemberrefInfo {
+ static final int tag = 9;
+
+ public FieldrefInfo(int cindex, int ntindex) {
+ super(cindex, ntindex);
+ }
+
+ public FieldrefInfo(DataInputStream in) throws IOException {
+ super(in);
+ }
+
+ public int getTag() { return tag; }
+
+ public String getTagName() { return "Field"; }
+
+ protected int copy2(ConstPool dest, int cindex, int ntindex) {
+ return dest.addFieldrefInfo(cindex, ntindex);
+ }
+}
+
+class MethodrefInfo extends MemberrefInfo {
+ static final int tag = 10;
+
+ public MethodrefInfo(int cindex, int ntindex) {
+ super(cindex, ntindex);
+ }
+
+ public MethodrefInfo(DataInputStream in) throws IOException {
+ super(in);
+ }
+
+ public int getTag() { return tag; }
+
+ public String getTagName() { return "Method"; }
+
+ protected int copy2(ConstPool dest, int cindex, int ntindex) {
+ return dest.addMethodrefInfo(cindex, ntindex);
+ }
+}
+
+class InterfaceMethodrefInfo extends MemberrefInfo {
+ static final int tag = 11;
+
+ public InterfaceMethodrefInfo(int cindex, int ntindex) {
+ super(cindex, ntindex);
+ }
+
+ public InterfaceMethodrefInfo(DataInputStream in) throws IOException {
+ super(in);
+ }
+
+ public int getTag() { return tag; }
+
+ public String getTagName() { return "Interface"; }
+
+ protected int copy2(ConstPool dest, int cindex, int ntindex) {
+ return dest.addInterfaceMethodrefInfo(cindex, ntindex);
+ }
+}
+
+class StringInfo extends ConstInfo {
+ static final int tag = 8;
+ int string;
+
+ public StringInfo(int str) {
+ string = str;
+ }
+
+ public StringInfo(DataInputStream in) throws IOException {
+ string = in.readUnsignedShort();
+ }
+
+ public int getTag() { return tag; }
+
+ public int copy(ConstPool src, ConstPool dest, Map map) {
+ return dest.addStringInfo(src.getUtf8Info(string));
+ }
+
+ public void write(DataOutputStream out) throws IOException {
+ out.writeByte(tag);
+ out.writeShort(string);
+ }
+
+ public void print(PrintWriter out) {
+ out.print("String #");
+ out.println(string);
+ }
+}
+
+class IntegerInfo extends ConstInfo {
+ static final int tag = 3;
+ int value;
+
+ public IntegerInfo(int i) {
+ value = i;
+ }
+
+ public IntegerInfo(DataInputStream in) throws IOException {
+ value = in.readInt();
+ }
+
+ public int getTag() { return tag; }
+
+ public int copy(ConstPool src, ConstPool dest, Map map) {
+ return dest.addIntegerInfo(value);
+ }
+
+ public void write(DataOutputStream out) throws IOException {
+ out.writeByte(tag);
+ out.writeInt(value);
+ }
+
+ public void print(PrintWriter out) {
+ out.print("Integer ");
+ out.println(value);
+ }
+}
+
+class FloatInfo extends ConstInfo {
+ static final int tag = 4;
+ float value;
+
+ public FloatInfo(float f) {
+ value = f;
+ }
+
+ public FloatInfo(DataInputStream in) throws IOException {
+ value = in.readFloat();
+ }
+
+ public int getTag() { return tag; }
+
+ public int copy(ConstPool src, ConstPool dest, Map map) {
+ return dest.addFloatInfo(value);
+ }
+
+ public void write(DataOutputStream out) throws IOException {
+ out.writeByte(tag);
+ out.writeFloat(value);
+ }
+
+ public void print(PrintWriter out) {
+ out.print("Float ");
+ out.println(value);
+ }
+}
+
+class LongInfo extends ConstInfo {
+ static final int tag = 5;
+ long value;
+
+ public LongInfo(long l) {
+ value = l;
+ }
+
+ public LongInfo(DataInputStream in) throws IOException {
+ value = in.readLong();
+ }
+
+ public int getTag() { return tag; }
+
+ public int copy(ConstPool src, ConstPool dest, Map map) {
+ return dest.addLongInfo(value);
+ }
+
+ public void write(DataOutputStream out) throws IOException {
+ out.writeByte(tag);
+ out.writeLong(value);
+ }
+
+ public void print(PrintWriter out) {
+ out.print("Long ");
+ out.println(value);
+ }
+}
+
+class DoubleInfo extends ConstInfo {
+ static final int tag = 6;
+ double value;
+
+ public DoubleInfo(double d) {
+ value = d;
+ }
+
+ public DoubleInfo(DataInputStream in) throws IOException {
+ value = in.readDouble();
+ }
+
+ public int getTag() { return tag; }
+
+ public int copy(ConstPool src, ConstPool dest, Map map) {
+ return dest.addDoubleInfo(value);
+ }
+
+ public void write(DataOutputStream out) throws IOException {
+ out.writeByte(tag);
+ out.writeDouble(value);
+ }
+
+ public void print(PrintWriter out) {
+ out.print("Double ");
+ out.println(value);
+ }
+}
+
+class Utf8Info extends ConstInfo {
+ static final int tag = 1;
+ String string;
+ int index;
+
+ public Utf8Info(String utf8, int i) {
+ string = utf8;
+ index = i;
+ }
+
+ public Utf8Info(DataInputStream in, int i) throws IOException {
+ string = in.readUTF();
+ index = i;
+ }
+
+ public int getTag() { return tag; }
+
+ public int copy(ConstPool src, ConstPool dest, Map map) {
+ return dest.addUtf8Info(string);
+ }
+
+ public void write(DataOutputStream out) throws IOException {
+ out.writeByte(tag);
+ out.writeUTF(string);
+ }
+
+ public void print(PrintWriter out) {
+ out.print("UTF8 \"");
+ out.print(string);
+ out.println("\"");
+ }
+}
diff --git a/src/main/javassist/bytecode/ConstantAttribute.java b/src/main/javassist/bytecode/ConstantAttribute.java
new file mode 100644
index 0000000..47f993b
--- /dev/null
+++ b/src/main/javassist/bytecode/ConstantAttribute.java
@@ -0,0 +1,72 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode;
+
+import java.io.DataInputStream;
+import java.util.Map;
+import java.io.IOException;
+
+/**
+ * <code>ConstantValue_attribute</code>.
+ */
+public class ConstantAttribute extends AttributeInfo {
+ /**
+ * The name of this attribute <code>"ConstantValue"</code>.
+ */
+ public static final String tag = "ConstantValue";
+
+ ConstantAttribute(ConstPool cp, int n, DataInputStream in)
+ throws IOException
+ {
+ super(cp, n, in);
+ }
+
+ /**
+ * Constructs a ConstantValue attribute.
+ *
+ * @param cp a constant pool table.
+ * @param index <code>constantvalue_index</code>
+ * of <code>ConstantValue_attribute</code>.
+ */
+ public ConstantAttribute(ConstPool cp, int index) {
+ super(cp, tag);
+ byte[] bvalue = new byte[2];
+ bvalue[0] = (byte)(index >>> 8);
+ bvalue[1] = (byte)index;
+ set(bvalue);
+ }
+
+ /**
+ * Returns <code>constantvalue_index</code>.
+ */
+ public int getConstantValue() {
+ return ByteArray.readU16bit(get(), 0);
+ }
+
+ /**
+ * Makes a copy. Class names are replaced according to the
+ * given <code>Map</code> object.
+ *
+ * @param newCp the constant pool table used by the new copy.
+ * @param classnames pairs of replaced and substituted
+ * class names.
+ */
+ public AttributeInfo copy(ConstPool newCp, Map classnames) {
+ int index = getConstPool().copy(getConstantValue(), newCp,
+ classnames);
+ return new ConstantAttribute(newCp, index);
+ }
+}
diff --git a/src/main/javassist/bytecode/DeprecatedAttribute.java b/src/main/javassist/bytecode/DeprecatedAttribute.java
new file mode 100644
index 0000000..41099ce
--- /dev/null
+++ b/src/main/javassist/bytecode/DeprecatedAttribute.java
@@ -0,0 +1,55 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * <code>Deprecated_attribute</code>.
+ */
+public class DeprecatedAttribute extends AttributeInfo {
+ /**
+ * The name of this attribute <code>"Deprecated"</code>.
+ */
+ public static final String tag = "Deprecated";
+
+ DeprecatedAttribute(ConstPool cp, int n, DataInputStream in)
+ throws IOException
+ {
+ super(cp, n, in);
+ }
+
+ /**
+ * Constructs a Deprecated attribute.
+ *
+ * @param cp a constant pool table.
+ */
+ public DeprecatedAttribute(ConstPool cp) {
+ super(cp, tag, new byte[0]);
+ }
+
+ /**
+ * Makes a copy.
+ *
+ * @param newCp the constant pool table used by the new copy.
+ * @param classnames should be null.
+ */
+ public AttributeInfo copy(ConstPool newCp, Map classnames) {
+ return new DeprecatedAttribute(newCp);
+ }
+}
diff --git a/src/main/javassist/bytecode/Descriptor.java b/src/main/javassist/bytecode/Descriptor.java
new file mode 100644
index 0000000..5662222
--- /dev/null
+++ b/src/main/javassist/bytecode/Descriptor.java
@@ -0,0 +1,871 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode;
+
+import javassist.ClassPool;
+import javassist.CtClass;
+import javassist.CtPrimitiveType;
+import javassist.NotFoundException;
+import java.util.Map;
+
+/**
+ * A support class for dealing with descriptors.
+ *
+ * <p>See chapter 4.3 in "The Java Virtual Machine Specification (2nd ed.)"
+ */
+public class Descriptor {
+ /**
+ * Converts a class name into the internal representation used in
+ * the JVM.
+ *
+ * <p>Note that <code>toJvmName(toJvmName(s))</code> is equivalent
+ * to <code>toJvmName(s)</code>.
+ */
+ public static String toJvmName(String classname) {
+ return classname.replace('.', '/');
+ }
+
+ /**
+ * Converts a class name from the internal representation used in
+ * the JVM to the normal one used in Java.
+ * This method does not deal with an array type name such as
+ * "[Ljava/lang/Object;" and "[I;". For such names, use
+ * <code>toClassName()</code>.
+ *
+ * @see #toClassName(String)
+ */
+ public static String toJavaName(String classname) {
+ return classname.replace('/', '.');
+ }
+
+ /**
+ * Returns the internal representation of the class name in the
+ * JVM.
+ */
+ public static String toJvmName(CtClass clazz) {
+ if (clazz.isArray())
+ return of(clazz);
+ else
+ return toJvmName(clazz.getName());
+ }
+
+ /**
+ * Converts to a Java class name from a descriptor.
+ *
+ * @param descriptor type descriptor.
+ */
+ public static String toClassName(String descriptor) {
+ int arrayDim = 0;
+ int i = 0;
+ char c = descriptor.charAt(0);
+ while (c == '[') {
+ ++arrayDim;
+ c = descriptor.charAt(++i);
+ }
+
+ String name;
+ if (c == 'L') {
+ int i2 = descriptor.indexOf(';', i++);
+ name = descriptor.substring(i, i2).replace('/', '.');
+ i = i2;
+ }
+ else if (c == 'V')
+ name = "void";
+ else if (c == 'I')
+ name = "int";
+ else if (c == 'B')
+ name = "byte";
+ else if (c == 'J')
+ name = "long";
+ else if (c == 'D')
+ name = "double";
+ else if (c == 'F')
+ name = "float";
+ else if (c == 'C')
+ name = "char";
+ else if (c == 'S')
+ name = "short";
+ else if (c == 'Z')
+ name = "boolean";
+ else
+ throw new RuntimeException("bad descriptor: " + descriptor);
+
+ if (i + 1 != descriptor.length())
+ throw new RuntimeException("multiple descriptors?: " + descriptor);
+
+ if (arrayDim == 0)
+ return name;
+ else {
+ StringBuffer sbuf = new StringBuffer(name);
+ do {
+ sbuf.append("[]");
+ } while (--arrayDim > 0);
+
+ return sbuf.toString();
+ }
+ }
+
+ /**
+ * Converts to a descriptor from a Java class name
+ */
+ public static String of(String classname) {
+ if (classname.equals("void"))
+ return "V";
+ else if (classname.equals("int"))
+ return "I";
+ else if (classname.equals("byte"))
+ return "B";
+ else if (classname.equals("long"))
+ return "J";
+ else if (classname.equals("double"))
+ return "D";
+ else if (classname.equals("float"))
+ return "F";
+ else if (classname.equals("char"))
+ return "C";
+ else if (classname.equals("short"))
+ return "S";
+ else if (classname.equals("boolean"))
+ return "Z";
+ else
+ return "L" + toJvmName(classname) + ";";
+ }
+
+ /**
+ * Substitutes a class name
+ * in the given descriptor string.
+ *
+ * @param desc descriptor string
+ * @param oldname replaced JVM class name
+ * @param newname substituted JVM class name
+ *
+ * @see Descriptor#toJvmName(String)
+ */
+ public static String rename(String desc, String oldname, String newname) {
+ if (desc.indexOf(oldname) < 0)
+ return desc;
+
+ StringBuffer newdesc = new StringBuffer();
+ int head = 0;
+ int i = 0;
+ for (;;) {
+ int j = desc.indexOf('L', i);
+ if (j < 0)
+ break;
+ else if (desc.startsWith(oldname, j + 1)
+ && desc.charAt(j + oldname.length() + 1) == ';') {
+ newdesc.append(desc.substring(head, j));
+ newdesc.append('L');
+ newdesc.append(newname);
+ newdesc.append(';');
+ head = i = j + oldname.length() + 2;
+ }
+ else {
+ i = desc.indexOf(';', j) + 1;
+ if (i < 1)
+ break; // ';' was not found.
+ }
+ }
+
+ if (head == 0)
+ return desc;
+ else {
+ int len = desc.length();
+ if (head < len)
+ newdesc.append(desc.substring(head, len));
+
+ return newdesc.toString();
+ }
+ }
+
+ /**
+ * Substitutes class names in the given descriptor string
+ * according to the given <code>map</code>.
+ *
+ * @param map a map between replaced and substituted
+ * JVM class names.
+ * @see Descriptor#toJvmName(String)
+ */
+ public static String rename(String desc, Map map) {
+ if (map == null)
+ return desc;
+
+ StringBuffer newdesc = new StringBuffer();
+ int head = 0;
+ int i = 0;
+ for (;;) {
+ int j = desc.indexOf('L', i);
+ if (j < 0)
+ break;
+
+ int k = desc.indexOf(';', j);
+ if (k < 0)
+ break;
+
+ i = k + 1;
+ String name = desc.substring(j + 1, k);
+ String name2 = (String)map.get(name);
+ if (name2 != null) {
+ newdesc.append(desc.substring(head, j));
+ newdesc.append('L');
+ newdesc.append(name2);
+ newdesc.append(';');
+ head = i;
+ }
+ }
+
+ if (head == 0)
+ return desc;
+ else {
+ int len = desc.length();
+ if (head < len)
+ newdesc.append(desc.substring(head, len));
+
+ return newdesc.toString();
+ }
+ }
+
+ /**
+ * Returns the descriptor representing the given type.
+ */
+ public static String of(CtClass type) {
+ StringBuffer sbuf = new StringBuffer();
+ toDescriptor(sbuf, type);
+ return sbuf.toString();
+ }
+
+ private static void toDescriptor(StringBuffer desc, CtClass type) {
+ if (type.isArray()) {
+ desc.append('[');
+ try {
+ toDescriptor(desc, type.getComponentType());
+ }
+ catch (NotFoundException e) {
+ desc.append('L');
+ String name = type.getName();
+ desc.append(toJvmName(name.substring(0, name.length() - 2)));
+ desc.append(';');
+ }
+ }
+ else if (type.isPrimitive()) {
+ CtPrimitiveType pt = (CtPrimitiveType)type;
+ desc.append(pt.getDescriptor());
+ }
+ else { // class type
+ desc.append('L');
+ desc.append(type.getName().replace('.', '/'));
+ desc.append(';');
+ }
+ }
+
+ /**
+ * Returns the descriptor representing a constructor receiving
+ * the given parameter types.
+ *
+ * @param paramTypes parameter types
+ */
+ public static String ofConstructor(CtClass[] paramTypes) {
+ return ofMethod(CtClass.voidType, paramTypes);
+ }
+
+ /**
+ * Returns the descriptor representing a method that receives
+ * the given parameter types and returns the given type.
+ *
+ * @param returnType return type
+ * @param paramTypes parameter types
+ */
+ public static String ofMethod(CtClass returnType, CtClass[] paramTypes) {
+ StringBuffer desc = new StringBuffer();
+ desc.append('(');
+ if (paramTypes != null) {
+ int n = paramTypes.length;
+ for (int i = 0; i < n; ++i)
+ toDescriptor(desc, paramTypes[i]);
+ }
+
+ desc.append(')');
+ if (returnType != null)
+ toDescriptor(desc, returnType);
+
+ return desc.toString();
+ }
+
+ /**
+ * Returns the descriptor representing a list of parameter types.
+ * For example, if the given parameter types are two <code>int</code>,
+ * then this method returns <code>"(II)"</code>.
+ *
+ * @param paramTypes parameter types
+ */
+ public static String ofParameters(CtClass[] paramTypes) {
+ return ofMethod(null, paramTypes);
+ }
+
+ /**
+ * Appends a parameter type to the parameter list represented
+ * by the given descriptor.
+ *
+ * <p><code>classname</code> must not be an array type.
+ *
+ * @param classname parameter type (not primitive type)
+ * @param desc descriptor
+ */
+ public static String appendParameter(String classname, String desc) {
+ int i = desc.indexOf(')');
+ if (i < 0)
+ return desc;
+ else {
+ StringBuffer newdesc = new StringBuffer();
+ newdesc.append(desc.substring(0, i));
+ newdesc.append('L');
+ newdesc.append(classname.replace('.', '/'));
+ newdesc.append(';');
+ newdesc.append(desc.substring(i));
+ return newdesc.toString();
+ }
+ }
+
+ /**
+ * Inserts a parameter type at the beginning of the parameter
+ * list represented
+ * by the given descriptor.
+ *
+ * <p><code>classname</code> must not be an array type.
+ *
+ * @param classname parameter type (not primitive type)
+ * @param desc descriptor
+ */
+ public static String insertParameter(String classname, String desc) {
+ if (desc.charAt(0) != '(')
+ return desc;
+ else
+ return "(L" + classname.replace('.', '/') + ';'
+ + desc.substring(1);
+ }
+
+ /**
+ * Appends a parameter type to the parameter list represented
+ * by the given descriptor. The appended parameter becomes
+ * the last parameter.
+ *
+ * @param type the type of the appended parameter.
+ * @param descriptor the original descriptor.
+ */
+ public static String appendParameter(CtClass type, String descriptor) {
+ int i = descriptor.indexOf(')');
+ if (i < 0)
+ return descriptor;
+ else {
+ StringBuffer newdesc = new StringBuffer();
+ newdesc.append(descriptor.substring(0, i));
+ toDescriptor(newdesc, type);
+ newdesc.append(descriptor.substring(i));
+ return newdesc.toString();
+ }
+ }
+
+ /**
+ * Inserts a parameter type at the beginning of the parameter
+ * list represented
+ * by the given descriptor.
+ *
+ * @param type the type of the inserted parameter.
+ * @param descriptor the descriptor of the method.
+ */
+ public static String insertParameter(CtClass type,
+ String descriptor) {
+ if (descriptor.charAt(0) != '(')
+ return descriptor;
+ else
+ return "(" + of(type) + descriptor.substring(1);
+ }
+
+ /**
+ * Changes the return type included in the given descriptor.
+ *
+ * <p><code>classname</code> must not be an array type.
+ *
+ * @param classname return type
+ * @param desc descriptor
+ */
+ public static String changeReturnType(String classname, String desc) {
+ int i = desc.indexOf(')');
+ if (i < 0)
+ return desc;
+ else {
+ StringBuffer newdesc = new StringBuffer();
+ newdesc.append(desc.substring(0, i + 1));
+ newdesc.append('L');
+ newdesc.append(classname.replace('.', '/'));
+ newdesc.append(';');
+ return newdesc.toString();
+ }
+ }
+
+ /**
+ * Returns the <code>CtClass</code> objects representing the parameter
+ * types specified by the given descriptor.
+ *
+ * @param desc descriptor
+ * @param cp the class pool used for obtaining
+ * a <code>CtClass</code> object.
+ */
+ public static CtClass[] getParameterTypes(String desc, ClassPool cp)
+ throws NotFoundException
+ {
+ if (desc.charAt(0) != '(')
+ return null;
+ else {
+ int num = numOfParameters(desc);
+ CtClass[] args = new CtClass[num];
+ int n = 0;
+ int i = 1;
+ do {
+ i = toCtClass(cp, desc, i, args, n++);
+ } while (i > 0);
+ return args;
+ }
+ }
+
+ /**
+ * Returns true if the list of the parameter types of desc1 is equal to
+ * that of desc2.
+ * For example, "(II)V" and "(II)I" are equal.
+ */
+ public static boolean eqParamTypes(String desc1, String desc2) {
+ if (desc1.charAt(0) != '(')
+ return false;
+
+ for (int i = 0; true; ++i) {
+ char c = desc1.charAt(i);
+ if (c != desc2.charAt(i))
+ return false;
+
+ if (c == ')')
+ return true;
+ }
+ }
+
+ /**
+ * Returns the signature of the given descriptor. The signature does
+ * not include the return type. For example, the signature of "(I)V"
+ * is "(I)".
+ */
+ public static String getParamDescriptor(String decl) {
+ return decl.substring(0, decl.indexOf(')') + 1);
+ }
+
+ /**
+ * Returns the <code>CtClass</code> object representing the return
+ * type specified by the given descriptor.
+ *
+ * @param desc descriptor
+ * @param cp the class pool used for obtaining
+ * a <code>CtClass</code> object.
+ */
+ public static CtClass getReturnType(String desc, ClassPool cp)
+ throws NotFoundException
+ {
+ int i = desc.indexOf(')');
+ if (i < 0)
+ return null;
+ else {
+ CtClass[] type = new CtClass[1];
+ toCtClass(cp, desc, i + 1, type, 0);
+ return type[0];
+ }
+ }
+
+ /**
+ * Returns the number of the prameters included in the given
+ * descriptor.
+ *
+ * @param desc descriptor
+ */
+ public static int numOfParameters(String desc) {
+ int n = 0;
+ int i = 1;
+ for (;;) {
+ char c = desc.charAt(i);
+ if (c == ')')
+ break;
+
+ while (c == '[')
+ c = desc.charAt(++i);
+
+ if (c == 'L') {
+ i = desc.indexOf(';', i) + 1;
+ if (i <= 0)
+ throw new IndexOutOfBoundsException("bad descriptor");
+ }
+ else
+ ++i;
+
+ ++n;
+ }
+
+ return n;
+ }
+
+ /**
+ * Returns a <code>CtClass</code> object representing the type
+ * specified by the given descriptor.
+ *
+ * <p>This method works even if the package-class separator is
+ * not <code>/</code> but <code>.</code> (period). For example,
+ * it accepts <code>Ljava.lang.Object;</code>
+ * as well as <code>Ljava/lang/Object;</code>.
+ *
+ * @param desc descriptor.
+ * @param cp the class pool used for obtaining
+ * a <code>CtClass</code> object.
+ */
+ public static CtClass toCtClass(String desc, ClassPool cp)
+ throws NotFoundException
+ {
+ CtClass[] clazz = new CtClass[1];
+ int res = toCtClass(cp, desc, 0, clazz, 0);
+ if (res >= 0)
+ return clazz[0];
+ else {
+ // maybe, you forgot to surround the class name with
+ // L and ;. It violates the protocol, but I'm tolerant...
+ return cp.get(desc.replace('/', '.'));
+ }
+ }
+
+ private static int toCtClass(ClassPool cp, String desc, int i,
+ CtClass[] args, int n)
+ throws NotFoundException
+ {
+ int i2;
+ String name;
+
+ int arrayDim = 0;
+ char c = desc.charAt(i);
+ while (c == '[') {
+ ++arrayDim;
+ c = desc.charAt(++i);
+ }
+
+ if (c == 'L') {
+ i2 = desc.indexOf(';', ++i);
+ name = desc.substring(i, i2++).replace('/', '.');
+ }
+ else {
+ CtClass type = toPrimitiveClass(c);
+ if (type == null)
+ return -1; // error
+
+ i2 = i + 1;
+ if (arrayDim == 0) {
+ args[n] = type;
+ return i2; // neither an array type or a class type
+ }
+ else
+ name = type.getName();
+ }
+
+ if (arrayDim > 0) {
+ StringBuffer sbuf = new StringBuffer(name);
+ while (arrayDim-- > 0)
+ sbuf.append("[]");
+
+ name = sbuf.toString();
+ }
+
+ args[n] = cp.get(name);
+ return i2;
+ }
+
+ static CtClass toPrimitiveClass(char c) {
+ CtClass type = null;
+ switch (c) {
+ case 'Z' :
+ type = CtClass.booleanType;
+ break;
+ case 'C' :
+ type = CtClass.charType;
+ break;
+ case 'B' :
+ type = CtClass.byteType;
+ break;
+ case 'S' :
+ type = CtClass.shortType;
+ break;
+ case 'I' :
+ type = CtClass.intType;
+ break;
+ case 'J' :
+ type = CtClass.longType;
+ break;
+ case 'F' :
+ type = CtClass.floatType;
+ break;
+ case 'D' :
+ type = CtClass.doubleType;
+ break;
+ case 'V' :
+ type = CtClass.voidType;
+ break;
+ }
+
+ return type;
+ }
+
+ /**
+ * Computes the dimension of the array represented by the given
+ * descriptor. For example, if the descriptor is <code>"[[I"</code>,
+ * then this method returns 2.
+ *
+ * @param desc the descriptor.
+ * @return 0 if the descriptor does not represent an array type.
+ */
+ public static int arrayDimension(String desc) {
+ int dim = 0;
+ while (desc.charAt(dim) == '[')
+ ++dim;
+
+ return dim;
+ }
+
+ /**
+ * Returns the descriptor of the type of the array component.
+ * For example, if the given descriptor is
+ * <code>"[[Ljava/lang/String;"</code> and the given dimension is 2,
+ * then this method returns <code>"Ljava/lang/String;"</code>.
+ *
+ * @param desc the descriptor.
+ * @param dim the array dimension.
+ */
+ public static String toArrayComponent(String desc, int dim) {
+ return desc.substring(dim);
+ }
+
+ /**
+ * Computes the data size specified by the given descriptor.
+ * For example, if the descriptor is "D", this method returns 2.
+ *
+ * <p>If the descriptor represents a method type, this method returns
+ * (the size of the returned value) - (the sum of the data sizes
+ * of all the parameters). For example, if the descriptor is
+ * <code>"(I)D"</code>, then this method returns 1 (= 2 - 1).
+ *
+ * @param desc descriptor
+ */
+ public static int dataSize(String desc) {
+ return dataSize(desc, true);
+ }
+
+ /**
+ * Computes the data size of parameters.
+ * If one of the parameters is double type, the size of that parameter
+ * is 2 words. For example, if the given descriptor is
+ * <code>"(IJ)D"</code>, then this method returns 3. The size of the
+ * return type is not computed.
+ *
+ * @param desc a method descriptor.
+ */
+ public static int paramSize(String desc) {
+ return -dataSize(desc, false);
+ }
+
+ private static int dataSize(String desc, boolean withRet) {
+ int n = 0;
+ char c = desc.charAt(0);
+ if (c == '(') {
+ int i = 1;
+ for (;;) {
+ c = desc.charAt(i);
+ if (c == ')') {
+ c = desc.charAt(i + 1);
+ break;
+ }
+
+ boolean array = false;
+ while (c == '[') {
+ array = true;
+ c = desc.charAt(++i);
+ }
+
+ if (c == 'L') {
+ i = desc.indexOf(';', i) + 1;
+ if (i <= 0)
+ throw new IndexOutOfBoundsException("bad descriptor");
+ }
+ else
+ ++i;
+
+ if (!array && (c == 'J' || c == 'D'))
+ n -= 2;
+ else
+ --n;
+ }
+ }
+
+ if (withRet)
+ if (c == 'J' || c == 'D')
+ n += 2;
+ else if (c != 'V')
+ ++n;
+
+ return n;
+ }
+
+ /**
+ * Returns a human-readable representation of the
+ * given descriptor. For example, <code>Ljava/lang/Object;</code>
+ * is converted into <code>java.lang.Object</code>.
+ * <code>(I[I)V</code> is converted into <code>(int, int[])</code>
+ * (the return type is ignored).
+ */
+ public static String toString(String desc) {
+ return PrettyPrinter.toString(desc);
+ }
+
+ static class PrettyPrinter {
+ static String toString(String desc) {
+ StringBuffer sbuf = new StringBuffer();
+ if (desc.charAt(0) == '(') {
+ int pos = 1;
+ sbuf.append('(');
+ while (desc.charAt(pos) != ')') {
+ if (pos > 1)
+ sbuf.append(',');
+
+ pos = readType(sbuf, pos, desc);
+ }
+
+ sbuf.append(')');
+ }
+ else
+ readType(sbuf, 0, desc);
+
+ return sbuf.toString();
+ }
+
+ static int readType(StringBuffer sbuf, int pos, String desc) {
+ char c = desc.charAt(pos);
+ int arrayDim = 0;
+ while (c == '[') {
+ arrayDim++;
+ c = desc.charAt(++pos);
+ }
+
+ if (c == 'L')
+ while (true) {
+ c = desc.charAt(++pos);
+ if (c == ';')
+ break;
+
+ if (c == '/')
+ c = '.';
+
+ sbuf.append(c);
+ }
+ else {
+ CtClass t = toPrimitiveClass(c);
+ sbuf.append(t.getName());
+ }
+
+ while (arrayDim-- > 0)
+ sbuf.append("[]");
+
+ return pos + 1;
+ }
+ }
+
+ /**
+ * An Iterator over a descriptor.
+ */
+ public static class Iterator {
+ private String desc;
+ private int index, curPos;
+ private boolean param;
+
+ /**
+ * Constructs an iterator.
+ *
+ * @param s descriptor.
+ */
+ public Iterator(String s) {
+ desc = s;
+ index = curPos = 0;
+ param = false;
+ }
+
+ /**
+ * Returns true if the iteration has more elements.
+ */
+ public boolean hasNext() {
+ return index < desc.length();
+ }
+
+ /**
+ * Returns true if the current element is a parameter type.
+ */
+ public boolean isParameter() { return param; }
+
+ /**
+ * Returns the first character of the current element.
+ */
+ public char currentChar() { return desc.charAt(curPos); }
+
+ /**
+ * Returns true if the current element is double or long type.
+ */
+ public boolean is2byte() {
+ char c = currentChar();
+ return c == 'D' || c == 'J';
+ }
+
+ /**
+ * Returns the position of the next type character.
+ * That type character becomes a new current element.
+ */
+ public int next() {
+ int nextPos = index;
+ char c = desc.charAt(nextPos);
+ if (c == '(') {
+ ++index;
+ c = desc.charAt(++nextPos);
+ param = true;
+ }
+
+ if (c == ')') {
+ ++index;
+ c = desc.charAt(++nextPos);
+ param = false;
+ }
+
+ while (c == '[')
+ c = desc.charAt(++nextPos);
+
+ if (c == 'L') {
+ nextPos = desc.indexOf(';', nextPos) + 1;
+ if (nextPos <= 0)
+ throw new IndexOutOfBoundsException("bad descriptor");
+ }
+ else
+ ++nextPos;
+
+ curPos = index;
+ index = nextPos;
+ return curPos;
+ }
+ }
+}
diff --git a/src/main/javassist/bytecode/DuplicateMemberException.java b/src/main/javassist/bytecode/DuplicateMemberException.java
new file mode 100644
index 0000000..7c1e0e6
--- /dev/null
+++ b/src/main/javassist/bytecode/DuplicateMemberException.java
@@ -0,0 +1,30 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode;
+
+import javassist.CannotCompileException;
+
+/**
+ * An exception thrown when adding a duplicate member is requested.
+ *
+ * @see ClassFile#addMethod(MethodInfo)
+ * @see ClassFile#addField(FieldInfo)
+ */
+public class DuplicateMemberException extends CannotCompileException {
+ public DuplicateMemberException(String msg) {
+ super(msg);
+ }
+}
diff --git a/src/main/javassist/bytecode/EnclosingMethodAttribute.java b/src/main/javassist/bytecode/EnclosingMethodAttribute.java
new file mode 100644
index 0000000..c924f50
--- /dev/null
+++ b/src/main/javassist/bytecode/EnclosingMethodAttribute.java
@@ -0,0 +1,133 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * <code>EnclosingMethod_attribute</code>.
+ */
+public class EnclosingMethodAttribute extends AttributeInfo {
+ /**
+ * The name of this attribute <code>"EnclosingMethod"</code>.
+ */
+ public static final String tag = "EnclosingMethod";
+
+ EnclosingMethodAttribute(ConstPool cp, int n, DataInputStream in)
+ throws IOException
+ {
+ super(cp, n, in);
+ }
+
+ /**
+ * Constructs an EnclosingMethod attribute.
+ *
+ * @param cp a constant pool table.
+ * @param className the name of the innermost enclosing class.
+ * @param methodName the name of the enclosing method.
+ * @param methodDesc the descriptor of the enclosing method.
+ */
+ public EnclosingMethodAttribute(ConstPool cp, String className,
+ String methodName, String methodDesc) {
+ super(cp, tag);
+ int ci = cp.addClassInfo(className);
+ int ni = cp.addNameAndTypeInfo(methodName, methodDesc);
+ byte[] bvalue = new byte[4];
+ bvalue[0] = (byte)(ci >>> 8);
+ bvalue[1] = (byte)ci;
+ bvalue[2] = (byte)(ni >>> 8);
+ bvalue[3] = (byte)ni;
+ set(bvalue);
+ }
+
+ /**
+ * Constructs an EnclosingMethod attribute.
+ * The value of <code>method_index</code> is set to 0.
+ *
+ * @param cp a constant pool table.
+ * @param className the name of the innermost enclosing class.
+ */
+ public EnclosingMethodAttribute(ConstPool cp, String className) {
+ super(cp, tag);
+ int ci = cp.addClassInfo(className);
+ int ni = 0;
+ byte[] bvalue = new byte[4];
+ bvalue[0] = (byte)(ci >>> 8);
+ bvalue[1] = (byte)ci;
+ bvalue[2] = (byte)(ni >>> 8);
+ bvalue[3] = (byte)ni;
+ set(bvalue);
+ }
+
+ /**
+ * Returns the value of <code>class_index</code>.
+ */
+ public int classIndex() {
+ return ByteArray.readU16bit(get(), 0);
+ }
+
+ /**
+ * Returns the value of <code>method_index</code>.
+ */
+ public int methodIndex() {
+ return ByteArray.readU16bit(get(), 2);
+ }
+
+ /**
+ * Returns the name of the class specified by <code>class_index</code>.
+ */
+ public String className() {
+ return getConstPool().getClassInfo(classIndex());
+ }
+
+ /**
+ * Returns the method name specified by <code>method_index</code>.
+ */
+ public String methodName() {
+ ConstPool cp = getConstPool();
+ int mi = methodIndex();
+ int ni = cp.getNameAndTypeName(mi);
+ return cp.getUtf8Info(ni);
+ }
+
+ /**
+ * Returns the method descriptor specified by <code>method_index</code>.
+ */
+ public String methodDescriptor() {
+ ConstPool cp = getConstPool();
+ int mi = methodIndex();
+ int ti = cp.getNameAndTypeDescriptor(mi);
+ return cp.getUtf8Info(ti);
+ }
+
+ /**
+ * Makes a copy. Class names are replaced according to the
+ * given <code>Map</code> object.
+ *
+ * @param newCp the constant pool table used by the new copy.
+ * @param classnames pairs of replaced and substituted
+ * class names.
+ */
+ public AttributeInfo copy(ConstPool newCp, Map classnames) {
+ if (methodIndex() == 0)
+ return new EnclosingMethodAttribute(newCp, className());
+ else
+ return new EnclosingMethodAttribute(newCp, className(),
+ methodName(), methodDescriptor());
+ }
+}
diff --git a/src/main/javassist/bytecode/ExceptionTable.java b/src/main/javassist/bytecode/ExceptionTable.java
new file mode 100644
index 0000000..1c74e6e
--- /dev/null
+++ b/src/main/javassist/bytecode/ExceptionTable.java
@@ -0,0 +1,280 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Map;
+
+class ExceptionTableEntry {
+ int startPc;
+ int endPc;
+ int handlerPc;
+ int catchType;
+
+ ExceptionTableEntry(int start, int end, int handle, int type) {
+ startPc = start;
+ endPc = end;
+ handlerPc = handle;
+ catchType = type;
+ }
+}
+
+/**
+ * <code>exception_table[]</code> of <code>Code_attribute</code>.
+ */
+public class ExceptionTable implements Cloneable {
+ private ConstPool constPool;
+ private ArrayList entries;
+
+ /**
+ * Constructs an <code>exception_table[]</code>.
+ *
+ * @param cp constant pool table.
+ */
+ public ExceptionTable(ConstPool cp) {
+ constPool = cp;
+ entries = new ArrayList();
+ }
+
+ ExceptionTable(ConstPool cp, DataInputStream in) throws IOException {
+ constPool = cp;
+ int length = in.readUnsignedShort();
+ ArrayList list = new ArrayList(length);
+ for (int i = 0; i < length; ++i) {
+ int start = in.readUnsignedShort();
+ int end = in.readUnsignedShort();
+ int handle = in.readUnsignedShort();
+ int type = in.readUnsignedShort();
+ list.add(new ExceptionTableEntry(start, end, handle, type));
+ }
+
+ entries = list;
+ }
+
+ /**
+ * Creates and returns a copy of this object.
+ * The constant pool object is shared between this object
+ * and the cloned object.
+ */
+ public Object clone() throws CloneNotSupportedException {
+ ExceptionTable r = (ExceptionTable)super.clone();
+ r.entries = new ArrayList(entries);
+ return r;
+ }
+
+ /**
+ * Returns <code>exception_table_length</code>, which is the number
+ * of entries in the <code>exception_table[]</code>.
+ */
+ public int size() {
+ return entries.size();
+ }
+
+ /**
+ * Returns <code>startPc</code> of the <i>n</i>-th entry.
+ *
+ * @param nth the <i>n</i>-th (>= 0).
+ */
+ public int startPc(int nth) {
+ ExceptionTableEntry e = (ExceptionTableEntry)entries.get(nth);
+ return e.startPc;
+ }
+
+ /**
+ * Sets <code>startPc</code> of the <i>n</i>-th entry.
+ *
+ * @param nth the <i>n</i>-th (>= 0).
+ * @param value new value.
+ */
+ public void setStartPc(int nth, int value) {
+ ExceptionTableEntry e = (ExceptionTableEntry)entries.get(nth);
+ e.startPc = value;
+ }
+
+ /**
+ * Returns <code>endPc</code> of the <i>n</i>-th entry.
+ *
+ * @param nth the <i>n</i>-th (>= 0).
+ */
+ public int endPc(int nth) {
+ ExceptionTableEntry e = (ExceptionTableEntry)entries.get(nth);
+ return e.endPc;
+ }
+
+ /**
+ * Sets <code>endPc</code> of the <i>n</i>-th entry.
+ *
+ * @param nth the <i>n</i>-th (>= 0).
+ * @param value new value.
+ */
+ public void setEndPc(int nth, int value) {
+ ExceptionTableEntry e = (ExceptionTableEntry)entries.get(nth);
+ e.endPc = value;
+ }
+
+ /**
+ * Returns <code>handlerPc</code> of the <i>n</i>-th entry.
+ *
+ * @param nth the <i>n</i>-th (>= 0).
+ */
+ public int handlerPc(int nth) {
+ ExceptionTableEntry e = (ExceptionTableEntry)entries.get(nth);
+ return e.handlerPc;
+ }
+
+ /**
+ * Sets <code>handlerPc</code> of the <i>n</i>-th entry.
+ *
+ * @param nth the <i>n</i>-th (>= 0).
+ * @param value new value.
+ */
+ public void setHandlerPc(int nth, int value) {
+ ExceptionTableEntry e = (ExceptionTableEntry)entries.get(nth);
+ e.handlerPc = value;
+ }
+
+ /**
+ * Returns <code>catchType</code> of the <i>n</i>-th entry.
+ *
+ * @param nth the <i>n</i>-th (>= 0).
+ * @return an index into the <code>constant_pool</code> table,
+ * or zero if this exception handler is for all exceptions.
+ */
+ public int catchType(int nth) {
+ ExceptionTableEntry e = (ExceptionTableEntry)entries.get(nth);
+ return e.catchType;
+ }
+
+ /**
+ * Sets <code>catchType</code> of the <i>n</i>-th entry.
+ *
+ * @param nth the <i>n</i>-th (>= 0).
+ * @param value new value.
+ */
+ public void setCatchType(int nth, int value) {
+ ExceptionTableEntry e = (ExceptionTableEntry)entries.get(nth);
+ e.catchType = value;
+ }
+
+ /**
+ * Copies the given exception table at the specified position
+ * in the table.
+ *
+ * @param index index (>= 0) at which the entry is to be inserted.
+ * @param offset the offset added to the code position.
+ */
+ public void add(int index, ExceptionTable table, int offset) {
+ int len = table.size();
+ while (--len >= 0) {
+ ExceptionTableEntry e
+ = (ExceptionTableEntry)table.entries.get(len);
+ add(index, e.startPc + offset, e.endPc + offset,
+ e.handlerPc + offset, e.catchType);
+ }
+ }
+
+ /**
+ * Adds a new entry at the specified position in the table.
+ *
+ * @param index index (>= 0) at which the entry is to be inserted.
+ * @param start <code>startPc</code>
+ * @param end <code>endPc</code>
+ * @param handler <code>handlerPc</code>
+ * @param type <code>catchType</code>
+ */
+ public void add(int index, int start, int end, int handler, int type) {
+ if (start < end)
+ entries.add(index,
+ new ExceptionTableEntry(start, end, handler, type));
+ }
+
+ /**
+ * Appends a new entry at the end of the table.
+ *
+ * @param start <code>startPc</code>
+ * @param end <code>endPc</code>
+ * @param handler <code>handlerPc</code>
+ * @param type <code>catchType</code>
+ */
+ public void add(int start, int end, int handler, int type) {
+ if (start < end)
+ entries.add(new ExceptionTableEntry(start, end, handler, type));
+ }
+
+ /**
+ * Removes the entry at the specified position in the table.
+ *
+ * @param index the index of the removed entry.
+ */
+ public void remove(int index) {
+ entries.remove(index);
+ }
+
+ /**
+ * Makes a copy of this <code>exception_table[]</code>.
+ * Class names are replaced according to the
+ * given <code>Map</code> object.
+ *
+ * @param newCp the constant pool table used by the new copy.
+ * @param classnames pairs of replaced and substituted
+ * class names.
+ */
+ public ExceptionTable copy(ConstPool newCp, Map classnames) {
+ ExceptionTable et = new ExceptionTable(newCp);
+ ConstPool srcCp = constPool;
+ int len = size();
+ for (int i = 0; i < len; ++i) {
+ ExceptionTableEntry e = (ExceptionTableEntry)entries.get(i);
+ int type = srcCp.copy(e.catchType, newCp, classnames);
+ et.add(e.startPc, e.endPc, e.handlerPc, type);
+ }
+
+ return et;
+ }
+
+ void shiftPc(int where, int gapLength, boolean exclusive) {
+ int len = size();
+ for (int i = 0; i < len; ++i) {
+ ExceptionTableEntry e = (ExceptionTableEntry)entries.get(i);
+ e.startPc = shiftPc(e.startPc, where, gapLength, exclusive);
+ e.endPc = shiftPc(e.endPc, where, gapLength, exclusive);
+ e.handlerPc = shiftPc(e.handlerPc, where, gapLength, exclusive);
+ }
+ }
+
+ private static int shiftPc(int pc, int where, int gapLength,
+ boolean exclusive) {
+ if (pc > where || (exclusive && pc == where))
+ pc += gapLength;
+
+ return pc;
+ }
+
+ void write(DataOutputStream out) throws IOException {
+ int len = size();
+ out.writeShort(len); // exception_table_length
+ for (int i = 0; i < len; ++i) {
+ ExceptionTableEntry e = (ExceptionTableEntry)entries.get(i);
+ out.writeShort(e.startPc);
+ out.writeShort(e.endPc);
+ out.writeShort(e.handlerPc);
+ out.writeShort(e.catchType);
+ }
+ }
+}
diff --git a/src/main/javassist/bytecode/ExceptionsAttribute.java b/src/main/javassist/bytecode/ExceptionsAttribute.java
new file mode 100644
index 0000000..2fe34dd
--- /dev/null
+++ b/src/main/javassist/bytecode/ExceptionsAttribute.java
@@ -0,0 +1,173 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * <code>Exceptions_attribute</code>.
+ */
+public class ExceptionsAttribute extends AttributeInfo {
+ /**
+ * The name of this attribute <code>"Exceptions"</code>.
+ */
+ public static final String tag = "Exceptions";
+
+ ExceptionsAttribute(ConstPool cp, int n, DataInputStream in)
+ throws IOException
+ {
+ super(cp, n, in);
+ }
+
+ /**
+ * Constructs a copy of an exceptions attribute.
+ *
+ * @param cp constant pool table.
+ * @param src source attribute.
+ */
+ private ExceptionsAttribute(ConstPool cp, ExceptionsAttribute src,
+ Map classnames) {
+ super(cp, tag);
+ copyFrom(src, classnames);
+ }
+
+ /**
+ * Constructs a new exceptions attribute.
+ *
+ * @param cp constant pool table.
+ */
+ public ExceptionsAttribute(ConstPool cp) {
+ super(cp, tag);
+ byte[] data = new byte[2];
+ data[0] = data[1] = 0; // empty
+ this.info = data;
+ }
+
+ /**
+ * Makes a copy. Class names are replaced according to the
+ * given <code>Map</code> object.
+ *
+ * @param newCp the constant pool table used by the new copy.
+ * @param classnames pairs of replaced and substituted
+ * class names. It can be <code>null</code>.
+ */
+ public AttributeInfo copy(ConstPool newCp, Map classnames) {
+ return new ExceptionsAttribute(newCp, this, classnames);
+ }
+
+ /**
+ * Copies the contents from a source attribute.
+ * Specified class names are replaced during the copy.
+ *
+ * @param srcAttr source Exceptions attribute
+ * @param classnames pairs of replaced and substituted
+ * class names.
+ */
+ private void copyFrom(ExceptionsAttribute srcAttr, Map classnames) {
+ ConstPool srcCp = srcAttr.constPool;
+ ConstPool destCp = this.constPool;
+ byte[] src = srcAttr.info;
+ int num = src.length;
+ byte[] dest = new byte[num];
+ dest[0] = src[0];
+ dest[1] = src[1]; // the number of elements.
+ for (int i = 2; i < num; i += 2) {
+ int index = ByteArray.readU16bit(src, i);
+ ByteArray.write16bit(srcCp.copy(index, destCp, classnames),
+ dest, i);
+ }
+
+ this.info = dest;
+ }
+
+ /**
+ * Returns <code>exception_index_table[]</code>.
+ */
+ public int[] getExceptionIndexes() {
+ byte[] blist = info;
+ int n = blist.length;
+ if (n <= 2)
+ return null;
+
+ int[] elist = new int[n / 2 - 1];
+ int k = 0;
+ for (int j = 2; j < n; j += 2)
+ elist[k++] = ((blist[j] & 0xff) << 8) | (blist[j + 1] & 0xff);
+
+ return elist;
+ }
+
+ /**
+ * Returns the names of exceptions that the method may throw.
+ */
+ public String[] getExceptions() {
+ byte[] blist = info;
+ int n = blist.length;
+ if (n <= 2)
+ return null;
+
+ String[] elist = new String[n / 2 - 1];
+ int k = 0;
+ for (int j = 2; j < n; j += 2) {
+ int index = ((blist[j] & 0xff) << 8) | (blist[j + 1] & 0xff);
+ elist[k++] = constPool.getClassInfo(index);
+ }
+
+ return elist;
+ }
+
+ /**
+ * Sets <code>exception_index_table[]</code>.
+ */
+ public void setExceptionIndexes(int[] elist) {
+ int n = elist.length;
+ byte[] blist = new byte[n * 2 + 2];
+ ByteArray.write16bit(n, blist, 0);
+ for (int i = 0; i < n; ++i)
+ ByteArray.write16bit(elist[i], blist, i * 2 + 2);
+
+ info = blist;
+ }
+
+ /**
+ * Sets the names of exceptions that the method may throw.
+ */
+ public void setExceptions(String[] elist) {
+ int n = elist.length;
+ byte[] blist = new byte[n * 2 + 2];
+ ByteArray.write16bit(n, blist, 0);
+ for (int i = 0; i < n; ++i)
+ ByteArray.write16bit(constPool.addClassInfo(elist[i]),
+ blist, i * 2 + 2);
+
+ info = blist;
+ }
+
+ /**
+ * Returns <code>number_of_exceptions</code>.
+ */
+ public int tableLength() { return info.length / 2 - 1; }
+
+ /**
+ * Returns the value of <code>exception_index_table[nth]</code>.
+ */
+ public int getException(int nth) {
+ int index = nth * 2 + 2; // nth >= 0
+ return ((info[index] & 0xff) << 8) | (info[index + 1] & 0xff);
+ }
+}
diff --git a/src/main/javassist/bytecode/FieldInfo.java b/src/main/javassist/bytecode/FieldInfo.java
new file mode 100644
index 0000000..f474373
--- /dev/null
+++ b/src/main/javassist/bytecode/FieldInfo.java
@@ -0,0 +1,266 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * <code>field_info</code> structure.
+ *
+ * @see javassist.CtField#getFieldInfo()
+ */
+public final class FieldInfo {
+ ConstPool constPool;
+ int accessFlags;
+ int name;
+ String cachedName;
+ String cachedType;
+ int descriptor;
+ ArrayList attribute; // may be null.
+
+ private FieldInfo(ConstPool cp) {
+ constPool = cp;
+ accessFlags = 0;
+ attribute = null;
+ }
+
+ /**
+ * Constructs a <code>field_info</code> structure.
+ *
+ * @param cp a constant pool table
+ * @param fieldName field name
+ * @param desc field descriptor
+ *
+ * @see Descriptor
+ */
+ public FieldInfo(ConstPool cp, String fieldName, String desc) {
+ this(cp);
+ name = cp.addUtf8Info(fieldName);
+ cachedName = fieldName;
+ descriptor = cp.addUtf8Info(desc);
+ }
+
+ FieldInfo(ConstPool cp, DataInputStream in) throws IOException {
+ this(cp);
+ read(in);
+ }
+
+ /**
+ * Returns a string representation of the object.
+ */
+ public String toString() {
+ return getName() + " " + getDescriptor();
+ }
+
+ /**
+ * Copies all constant pool items to a given new constant pool
+ * and replaces the original items with the new ones.
+ * This is used for garbage collecting the items of removed fields
+ * and methods.
+ *
+ * @param cp the destination
+ */
+ void compact(ConstPool cp) {
+ name = cp.addUtf8Info(getName());
+ descriptor = cp.addUtf8Info(getDescriptor());
+ attribute = AttributeInfo.copyAll(attribute, cp);
+ constPool = cp;
+ }
+
+ void prune(ConstPool cp) {
+ ArrayList newAttributes = new ArrayList();
+ AttributeInfo invisibleAnnotations
+ = getAttribute(AnnotationsAttribute.invisibleTag);
+ if (invisibleAnnotations != null) {
+ invisibleAnnotations = invisibleAnnotations.copy(cp, null);
+ newAttributes.add(invisibleAnnotations);
+ }
+
+ AttributeInfo visibleAnnotations
+ = getAttribute(AnnotationsAttribute.visibleTag);
+ if (visibleAnnotations != null) {
+ visibleAnnotations = visibleAnnotations.copy(cp, null);
+ newAttributes.add(visibleAnnotations);
+ }
+
+ AttributeInfo signature
+ = getAttribute(SignatureAttribute.tag);
+ if (signature != null) {
+ signature = signature.copy(cp, null);
+ newAttributes.add(signature);
+ }
+
+ int index = getConstantValue();
+ if (index != 0) {
+ index = constPool.copy(index, cp, null);
+ newAttributes.add(new ConstantAttribute(cp, index));
+ }
+
+ attribute = newAttributes;
+ name = cp.addUtf8Info(getName());
+ descriptor = cp.addUtf8Info(getDescriptor());
+ constPool = cp;
+ }
+
+ /**
+ * Returns the constant pool table used
+ * by this <code>field_info</code>.
+ */
+ public ConstPool getConstPool() {
+ return constPool;
+ }
+
+ /**
+ * Returns the field name.
+ */
+ public String getName() {
+ if (cachedName == null)
+ cachedName = constPool.getUtf8Info(name);
+
+ return cachedName;
+ }
+
+ /**
+ * Sets the field name.
+ */
+ public void setName(String newName) {
+ name = constPool.addUtf8Info(newName);
+ cachedName = newName;
+ }
+
+ /**
+ * Returns the access flags.
+ *
+ * @see AccessFlag
+ */
+ public int getAccessFlags() {
+ return accessFlags;
+ }
+
+ /**
+ * Sets the access flags.
+ *
+ * @see AccessFlag
+ */
+ public void setAccessFlags(int acc) {
+ accessFlags = acc;
+ }
+
+ /**
+ * Returns the field descriptor.
+ *
+ * @see Descriptor
+ */
+ public String getDescriptor() {
+ return constPool.getUtf8Info(descriptor);
+ }
+
+ /**
+ * Sets the field descriptor.
+ *
+ * @see Descriptor
+ */
+ public void setDescriptor(String desc) {
+ if (!desc.equals(getDescriptor()))
+ descriptor = constPool.addUtf8Info(desc);
+ }
+
+ /**
+ * Finds a ConstantValue attribute and returns the index into
+ * the <code>constant_pool</code> table.
+ *
+ * @return 0 if a ConstantValue attribute is not found.
+ */
+ public int getConstantValue() {
+ if ((accessFlags & AccessFlag.STATIC) == 0)
+ return 0;
+
+ ConstantAttribute attr
+ = (ConstantAttribute)getAttribute(ConstantAttribute.tag);
+ if (attr == null)
+ return 0;
+ else
+ return attr.getConstantValue();
+ }
+
+ /**
+ * Returns all the attributes. The returned <code>List</code> object
+ * is shared with this object. If you add a new attribute to the list,
+ * the attribute is also added to the field represented by this
+ * object. If you remove an attribute from the list, it is also removed
+ * from the field.
+ *
+ * @return a list of <code>AttributeInfo</code> objects.
+ * @see AttributeInfo
+ */
+ public List getAttributes() {
+ if (attribute == null)
+ attribute = new ArrayList();
+
+ return attribute;
+ }
+
+ /**
+ * Returns the attribute with the specified name.
+ * It returns null if the specified attribute is not found.
+ *
+ * @param name attribute name
+ * @see #getAttributes()
+ */
+ public AttributeInfo getAttribute(String name) {
+ return AttributeInfo.lookup(attribute, name);
+ }
+
+ /**
+ * Appends an attribute. If there is already an attribute with
+ * the same name, the new one substitutes for it.
+ *
+ * @see #getAttributes()
+ */
+ public void addAttribute(AttributeInfo info) {
+ if (attribute == null)
+ attribute = new ArrayList();
+
+ AttributeInfo.remove(attribute, info.getName());
+ attribute.add(info);
+ }
+
+ private void read(DataInputStream in) throws IOException {
+ accessFlags = in.readUnsignedShort();
+ name = in.readUnsignedShort();
+ descriptor = in.readUnsignedShort();
+ int n = in.readUnsignedShort();
+ attribute = new ArrayList();
+ for (int i = 0; i < n; ++i)
+ attribute.add(AttributeInfo.read(constPool, in));
+ }
+
+ void write(DataOutputStream out) throws IOException {
+ out.writeShort(accessFlags);
+ out.writeShort(name);
+ out.writeShort(descriptor);
+ if (attribute == null)
+ out.writeShort(0);
+ else {
+ out.writeShort(attribute.size());
+ AttributeInfo.writeAll(attribute, out);
+ }
+ }
+}
diff --git a/src/main/javassist/bytecode/InnerClassesAttribute.java b/src/main/javassist/bytecode/InnerClassesAttribute.java
new file mode 100644
index 0000000..df5645a
--- /dev/null
+++ b/src/main/javassist/bytecode/InnerClassesAttribute.java
@@ -0,0 +1,241 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode;
+
+import java.io.DataInputStream;
+import java.util.Map;
+import java.io.IOException;
+
+/**
+ * <code>InnerClasses_attribute</code>.
+ */
+public class InnerClassesAttribute extends AttributeInfo {
+ /**
+ * The name of this attribute <code>"InnerClasses"</code>.
+ */
+ public static final String tag = "InnerClasses";
+
+ InnerClassesAttribute(ConstPool cp, int n, DataInputStream in)
+ throws IOException
+ {
+ super(cp, n, in);
+ }
+
+ private InnerClassesAttribute(ConstPool cp, byte[] info) {
+ super(cp, tag, info);
+ }
+
+ /**
+ * Constructs an empty InnerClasses attribute.
+ *
+ * @see #append(String, String, String, int)
+ */
+ public InnerClassesAttribute(ConstPool cp) {
+ super(cp, tag, new byte[2]);
+ ByteArray.write16bit(0, get(), 0);
+ }
+
+ /**
+ * Returns <code>number_of_classes</code>.
+ */
+ public int tableLength() { return ByteArray.readU16bit(get(), 0); }
+
+ /**
+ * Returns <code>classes[nth].inner_class_info_index</code>.
+ */
+ public int innerClassIndex(int nth) {
+ return ByteArray.readU16bit(get(), nth * 8 + 2);
+ }
+
+ /**
+ * Returns the class name indicated
+ * by <code>classes[nth].inner_class_info_index</code>.
+ *
+ * @return null or the class name.
+ */
+ public String innerClass(int nth) {
+ int i = innerClassIndex(nth);
+ if (i == 0)
+ return null;
+ else
+ return constPool.getClassInfo(i);
+ }
+
+ /**
+ * Sets <code>classes[nth].inner_class_info_index</code> to
+ * the given index.
+ */
+ public void setInnerClassIndex(int nth, int index) {
+ ByteArray.write16bit(index, get(), nth * 8 + 2);
+ }
+
+ /**
+ * Returns <code>classes[nth].outer_class_info_index</code>.
+ */
+ public int outerClassIndex(int nth) {
+ return ByteArray.readU16bit(get(), nth * 8 + 4);
+ }
+
+ /**
+ * Returns the class name indicated
+ * by <code>classes[nth].outer_class_info_index</code>.
+ *
+ * @return null or the class name.
+ */
+ public String outerClass(int nth) {
+ int i = outerClassIndex(nth);
+ if (i == 0)
+ return null;
+ else
+ return constPool.getClassInfo(i);
+ }
+
+ /**
+ * Sets <code>classes[nth].outer_class_info_index</code> to
+ * the given index.
+ */
+ public void setOuterClassIndex(int nth, int index) {
+ ByteArray.write16bit(index, get(), nth * 8 + 4);
+ }
+
+ /**
+ * Returns <code>classes[nth].inner_name_index</code>.
+ */
+ public int innerNameIndex(int nth) {
+ return ByteArray.readU16bit(get(), nth * 8 + 6);
+ }
+
+ /**
+ * Returns the simple class name indicated
+ * by <code>classes[nth].inner_name_index</code>.
+ *
+ * @return null or the class name.
+ */
+ public String innerName(int nth) {
+ int i = innerNameIndex(nth);
+ if (i == 0)
+ return null;
+ else
+ return constPool.getUtf8Info(i);
+ }
+
+ /**
+ * Sets <code>classes[nth].inner_name_index</code> to
+ * the given index.
+ */
+ public void setInnerNameIndex(int nth, int index) {
+ ByteArray.write16bit(index, get(), nth * 8 + 6);
+ }
+
+ /**
+ * Returns <code>classes[nth].inner_class_access_flags</code>.
+ */
+ public int accessFlags(int nth) {
+ return ByteArray.readU16bit(get(), nth * 8 + 8);
+ }
+
+ /**
+ * Sets <code>classes[nth].inner_class_access_flags</code> to
+ * the given index.
+ */
+ public void setAccessFlags(int nth, int flags) {
+ ByteArray.write16bit(flags, get(), nth * 8 + 8);
+ }
+
+ /**
+ * Appends a new entry.
+ *
+ * @param inner <code>inner_class_info_index</code>
+ * @param outer <code>outer_class_info_index</code>
+ * @param name <code>inner_name_index</code>
+ * @param flags <code>inner_class_access_flags</code>
+ */
+ public void append(String inner, String outer, String name, int flags) {
+ int i = constPool.addClassInfo(inner);
+ int o = constPool.addClassInfo(outer);
+ int n = constPool.addUtf8Info(name);
+ append(i, o, n, flags);
+ }
+
+ /**
+ * Appends a new entry.
+ *
+ * @param inner <code>inner_class_info_index</code>
+ * @param outer <code>outer_class_info_index</code>
+ * @param name <code>inner_name_index</code>
+ * @param flags <code>inner_class_access_flags</code>
+ */
+ public void append(int inner, int outer, int name, int flags) {
+ byte[] data = get();
+ int len = data.length;
+ byte[] newData = new byte[len + 8];
+ for (int i = 2; i < len; ++i)
+ newData[i] = data[i];
+
+ int n = ByteArray.readU16bit(data, 0);
+ ByteArray.write16bit(n + 1, newData, 0);
+
+ ByteArray.write16bit(inner, newData, len);
+ ByteArray.write16bit(outer, newData, len + 2);
+ ByteArray.write16bit(name, newData, len + 4);
+ ByteArray.write16bit(flags, newData, len + 6);
+
+ set(newData);
+ }
+
+ /**
+ * Makes a copy. Class names are replaced according to the
+ * given <code>Map</code> object.
+ *
+ * @param newCp the constant pool table used by the new copy.
+ * @param classnames pairs of replaced and substituted
+ * class names.
+ */
+ public AttributeInfo copy(ConstPool newCp, Map classnames) {
+ byte[] src = get();
+ byte[] dest = new byte[src.length];
+ ConstPool cp = getConstPool();
+ InnerClassesAttribute attr = new InnerClassesAttribute(newCp, dest);
+ int n = ByteArray.readU16bit(src, 0);
+ ByteArray.write16bit(n, dest, 0);
+ int j = 2;
+ for (int i = 0; i < n; ++i) {
+ int innerClass = ByteArray.readU16bit(src, j);
+ int outerClass = ByteArray.readU16bit(src, j + 2);
+ int innerName = ByteArray.readU16bit(src, j + 4);
+ int innerAccess = ByteArray.readU16bit(src, j + 6);
+
+ if (innerClass != 0)
+ innerClass = cp.copy(innerClass, newCp, classnames);
+
+ ByteArray.write16bit(innerClass, dest, j);
+
+ if (outerClass != 0)
+ outerClass = cp.copy(outerClass, newCp, classnames);
+
+ ByteArray.write16bit(outerClass, dest, j + 2);
+
+ if (innerName != 0)
+ innerName = cp.copy(innerName, newCp, classnames);
+
+ ByteArray.write16bit(innerName, dest, j + 4);
+ ByteArray.write16bit(innerAccess, dest, j + 6);
+ j += 8;
+ }
+
+ return attr;
+ }
+}
diff --git a/src/main/javassist/bytecode/InstructionPrinter.java b/src/main/javassist/bytecode/InstructionPrinter.java
new file mode 100644
index 0000000..f0a20e1
--- /dev/null
+++ b/src/main/javassist/bytecode/InstructionPrinter.java
@@ -0,0 +1,281 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+package javassist.bytecode;
+
+import java.io.PrintStream;
+
+import javassist.CtMethod;
+
+/**
+ * Simple utility class for printing the instructions of a method.
+ *
+ * @author Jason T. Greene
+ */
+public class InstructionPrinter implements Opcode {
+
+ private final static String opcodes[] = Mnemonic.OPCODE;
+ private final PrintStream stream;
+
+ public InstructionPrinter(PrintStream stream) {
+ this.stream = stream;
+ }
+
+ public static void print(CtMethod method, PrintStream stream) {
+ (new InstructionPrinter(stream)).print(method);
+ }
+
+ public void print(CtMethod method) {
+ MethodInfo info = method.getMethodInfo2();
+ ConstPool pool = info.getConstPool();
+ CodeAttribute code = info.getCodeAttribute();
+ if (code == null)
+ return;
+
+ CodeIterator iterator = code.iterator();
+ while (iterator.hasNext()) {
+ int pos;
+ try {
+ pos = iterator.next();
+ } catch (BadBytecode e) {
+ throw new RuntimeException(e);
+ }
+
+ stream.println(pos + ": " + instructionString(iterator, pos, pool));
+ }
+ }
+
+ public static String instructionString(CodeIterator iter, int pos, ConstPool pool) {
+ int opcode = iter.byteAt(pos);
+
+ if (opcode > opcodes.length || opcode < 0)
+ throw new IllegalArgumentException("Invalid opcode, opcode: " + opcode + " pos: "+ pos);
+
+ String opstring = opcodes[opcode];
+ switch (opcode) {
+ case BIPUSH:
+ return opstring + " " + iter.byteAt(pos + 1);
+ case SIPUSH:
+ return opstring + " " + iter.s16bitAt(pos + 1);
+ case LDC:
+ return opstring + " " + ldc(pool, iter.byteAt(pos + 1));
+ case LDC_W :
+ case LDC2_W :
+ return opstring + " " + ldc(pool, iter.u16bitAt(pos + 1));
+ case ILOAD:
+ case LLOAD:
+ case FLOAD:
+ case DLOAD:
+ case ALOAD:
+ case ISTORE:
+ case LSTORE:
+ case FSTORE:
+ case DSTORE:
+ case ASTORE:
+ return opstring + " " + iter.byteAt(pos + 1);
+ case IFEQ:
+ case IFGE:
+ case IFGT:
+ case IFLE:
+ case IFLT:
+ case IFNE:
+ case IFNONNULL:
+ case IFNULL:
+ case IF_ACMPEQ:
+ case IF_ACMPNE:
+ case IF_ICMPEQ:
+ case IF_ICMPGE:
+ case IF_ICMPGT:
+ case IF_ICMPLE:
+ case IF_ICMPLT:
+ case IF_ICMPNE:
+ return opstring + " " + (iter.s16bitAt(pos + 1) + pos);
+ case IINC:
+ return opstring + " " + iter.byteAt(pos + 1);
+ case GOTO:
+ case JSR:
+ return opstring + " " + (iter.s16bitAt(pos + 1) + pos);
+ case RET:
+ return opstring + " " + iter.byteAt(pos + 1);
+ case TABLESWITCH:
+ return tableSwitch(iter, pos);
+ case LOOKUPSWITCH:
+ return lookupSwitch(iter, pos);
+ case GETSTATIC:
+ case PUTSTATIC:
+ case GETFIELD:
+ case PUTFIELD:
+ return opstring + " " + fieldInfo(pool, iter.u16bitAt(pos + 1));
+ case INVOKEVIRTUAL:
+ case INVOKESPECIAL:
+ case INVOKESTATIC:
+ return opstring + " " + methodInfo(pool, iter.u16bitAt(pos + 1));
+ case INVOKEINTERFACE:
+ return opstring + " " + interfaceMethodInfo(pool, iter.u16bitAt(pos + 1));
+ case 186:
+ throw new RuntimeException("Bad opcode 186");
+ case NEW:
+ return opstring + " " + classInfo(pool, iter.u16bitAt(pos + 1));
+ case NEWARRAY:
+ return opstring + " " + arrayInfo(iter.byteAt(pos + 1));
+ case ANEWARRAY:
+ case CHECKCAST:
+ return opstring + " " + classInfo(pool, iter.u16bitAt(pos + 1));
+ case WIDE:
+ return wide(iter, pos);
+ case MULTIANEWARRAY:
+ return opstring + " " + classInfo(pool, iter.u16bitAt(pos + 1));
+ case GOTO_W:
+ case JSR_W:
+ return opstring + " " + (iter.s32bitAt(pos + 1)+ pos);
+ default:
+ return opstring;
+ }
+ }
+
+
+ private static String wide(CodeIterator iter, int pos) {
+ int opcode = iter.byteAt(pos + 1);
+ int index = iter.u16bitAt(pos + 2);
+ switch (opcode) {
+ case ILOAD:
+ case LLOAD:
+ case FLOAD:
+ case DLOAD:
+ case ALOAD:
+ case ISTORE:
+ case LSTORE:
+ case FSTORE:
+ case DSTORE:
+ case ASTORE:
+ case IINC:
+ case RET:
+ return opcodes[opcode] + " " + index;
+ default:
+ throw new RuntimeException("Invalid WIDE operand");
+ }
+ }
+
+
+ private static String arrayInfo(int type) {
+ switch (type) {
+ case T_BOOLEAN:
+ return "boolean";
+ case T_CHAR:
+ return "char";
+ case T_BYTE:
+ return "byte";
+ case T_SHORT:
+ return "short";
+ case T_INT:
+ return "int";
+ case T_LONG:
+ return "long";
+ case T_FLOAT:
+ return "float";
+ case T_DOUBLE:
+ return "double";
+ default:
+ throw new RuntimeException("Invalid array type");
+ }
+ }
+
+
+ private static String classInfo(ConstPool pool, int index) {
+ return "#" + index + " = Class " + pool.getClassInfo(index);
+ }
+
+
+ private static String interfaceMethodInfo(ConstPool pool, int index) {
+ return "#" + index + " = Method "
+ + pool.getInterfaceMethodrefClassName(index) + "."
+ + pool.getInterfaceMethodrefName(index) + "("
+ + pool.getInterfaceMethodrefType(index) + ")";
+ }
+
+ private static String methodInfo(ConstPool pool, int index) {
+ return "#" + index + " = Method "
+ + pool.getMethodrefClassName(index) + "."
+ + pool.getMethodrefName(index) + "("
+ + pool.getMethodrefType(index) + ")";
+ }
+
+
+ private static String fieldInfo(ConstPool pool, int index) {
+ return "#" + index + " = Field "
+ + pool.getFieldrefClassName(index) + "."
+ + pool.getFieldrefName(index) + "("
+ + pool.getFieldrefType(index) + ")";
+ }
+
+
+ private static String lookupSwitch(CodeIterator iter, int pos) {
+ StringBuffer buffer = new StringBuffer("lookupswitch {\n");
+ int index = (pos & ~3) + 4;
+ // default
+ buffer.append("\t\tdefault: ").append(pos + iter.s32bitAt(index)).append("\n");
+ int npairs = iter.s32bitAt(index += 4);
+ int end = npairs * 8 + (index += 4);
+
+ for (; index < end; index += 8) {
+ int match = iter.s32bitAt(index);
+ int target = iter.s32bitAt(index + 4) + pos;
+ buffer.append("\t\t").append(match).append(": ").append(target).append("\n");
+ }
+
+ buffer.setCharAt(buffer.length() - 1, '}');
+ return buffer.toString();
+ }
+
+
+ private static String tableSwitch(CodeIterator iter, int pos) {
+ StringBuffer buffer = new StringBuffer("tableswitch {\n");
+ int index = (pos & ~3) + 4;
+ // default
+ buffer.append("\t\tdefault: ").append(pos + iter.s32bitAt(index)).append("\n");
+ int low = iter.s32bitAt(index += 4);
+ int high = iter.s32bitAt(index += 4);
+ int end = (high - low + 1) * 4 + (index += 4);
+
+ // Offset table
+ for (int key = low; index < end; index += 4, key++) {
+ int target = iter.s32bitAt(index) + pos;
+ buffer.append("\t\t").append(key).append(": ").append(target).append("\n");
+ }
+
+ buffer.setCharAt(buffer.length() - 1, '}');
+ return buffer.toString();
+ }
+
+
+ private static String ldc(ConstPool pool, int index) {
+ int tag = pool.getTag(index);
+ switch (tag) {
+ case ConstPool.CONST_String:
+ return "#" + index + " = \"" + pool.getStringInfo(index) + "\"";
+ case ConstPool.CONST_Integer:
+ return "#" + index + " = int " + pool.getIntegerInfo(index);
+ case ConstPool.CONST_Float:
+ return "#" + index + " = float " + pool.getFloatInfo(index);
+ case ConstPool.CONST_Long:
+ return "#" + index + " = long " + pool.getLongInfo(index);
+ case ConstPool.CONST_Double:
+ return "#" + index + " = int " + pool.getDoubleInfo(index);
+ case ConstPool.CONST_Class:
+ return classInfo(pool, index);
+ default:
+ throw new RuntimeException("bad LDC: " + tag);
+ }
+ }
+}
diff --git a/src/main/javassist/bytecode/LineNumberAttribute.java b/src/main/javassist/bytecode/LineNumberAttribute.java
new file mode 100644
index 0000000..f384d2f
--- /dev/null
+++ b/src/main/javassist/bytecode/LineNumberAttribute.java
@@ -0,0 +1,181 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * <code>LineNumberTable_attribute</code>.
+ */
+public class LineNumberAttribute extends AttributeInfo {
+ /**
+ * The name of this attribute <code>"LineNumberTable"</code>.
+ */
+ public static final String tag = "LineNumberTable";
+
+ LineNumberAttribute(ConstPool cp, int n, DataInputStream in)
+ throws IOException
+ {
+ super(cp, n, in);
+ }
+
+ private LineNumberAttribute(ConstPool cp, byte[] i) {
+ super(cp, tag, i);
+ }
+
+ /**
+ * Returns <code>line_number_table_length</code>.
+ * This represents the number of entries in the table.
+ */
+ public int tableLength() {
+ return ByteArray.readU16bit(info, 0);
+ }
+
+ /**
+ * Returns <code>line_number_table[i].start_pc</code>.
+ * This represents the index into the code array at which the code
+ * for a new line in the original source file begins.
+ *
+ * @param i the i-th entry.
+ */
+ public int startPc(int i) {
+ return ByteArray.readU16bit(info, i * 4 + 2);
+ }
+
+ /**
+ * Returns <code>line_number_table[i].line_number</code>.
+ * This represents the corresponding line number in the original
+ * source file.
+ *
+ * @param i the i-th entry.
+ */
+ public int lineNumber(int i) {
+ return ByteArray.readU16bit(info, i * 4 + 4);
+ }
+
+ /**
+ * Returns the line number corresponding to the specified bytecode.
+ *
+ * @param pc the index into the code array.
+ */
+ public int toLineNumber(int pc) {
+ int n = tableLength();
+ int i = 0;
+ for (; i < n; ++i)
+ if (pc < startPc(i))
+ if (i == 0)
+ return lineNumber(0);
+ else
+ break;
+
+ return lineNumber(i - 1);
+ }
+
+ /**
+ * Returns the index into the code array at which the code for
+ * the specified line begins.
+ *
+ * @param line the line number.
+ * @return -1 if the specified line is not found.
+ */
+ public int toStartPc(int line) {
+ int n = tableLength();
+ for (int i = 0; i < n; ++i)
+ if (line == lineNumber(i))
+ return startPc(i);
+
+ return -1;
+ }
+
+ /**
+ * Used as a return type of <code>toNearPc()</code>.
+ */
+ static public class Pc {
+ /**
+ * The index into the code array.
+ */
+ public int index;
+ /**
+ * The line number.
+ */
+ public int line;
+ }
+
+ /**
+ * Returns the index into the code array at which the code for
+ * the specified line (or the nearest line after the specified one)
+ * begins.
+ *
+ * @param line the line number.
+ * @return a pair of the index and the line number of the
+ * bytecode at that index.
+ */
+ public Pc toNearPc(int line) {
+ int n = tableLength();
+ int nearPc = 0;
+ int distance = 0;
+ if (n > 0) {
+ distance = lineNumber(0) - line;
+ nearPc = startPc(0);
+ }
+
+ for (int i = 1; i < n; ++i) {
+ int d = lineNumber(i) - line;
+ if ((d < 0 && d > distance)
+ || (d >= 0 && (d < distance || distance < 0))) {
+ distance = d;
+ nearPc = startPc(i);
+ }
+ }
+
+ Pc res = new Pc();
+ res.index = nearPc;
+ res.line = line + distance;
+ return res;
+ }
+
+ /**
+ * Makes a copy.
+ *
+ * @param newCp the constant pool table used by the new copy.
+ * @param classnames should be null.
+ */
+ public AttributeInfo copy(ConstPool newCp, Map classnames) {
+ byte[] src = info;
+ int num = src.length;
+ byte[] dest = new byte[num];
+ for (int i = 0; i < num; ++i)
+ dest[i] = src[i];
+
+ LineNumberAttribute attr = new LineNumberAttribute(newCp, dest);
+ return attr;
+ }
+
+ /**
+ * Adjusts start_pc if bytecode is inserted in a method body.
+ */
+ void shiftPc(int where, int gapLength, boolean exclusive) {
+ int n = tableLength();
+ for (int i = 0; i < n; ++i) {
+ int pos = i * 4 + 2;
+ int pc = ByteArray.readU16bit(info, pos);
+ if (pc > where || (exclusive && pc == where))
+ ByteArray.write16bit(pc + gapLength, info, pos);
+ }
+ }
+}
diff --git a/src/main/javassist/bytecode/LocalVariableAttribute.java b/src/main/javassist/bytecode/LocalVariableAttribute.java
new file mode 100644
index 0000000..3d44a29
--- /dev/null
+++ b/src/main/javassist/bytecode/LocalVariableAttribute.java
@@ -0,0 +1,333 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * <code>LocalVariableTable_attribute</code>.
+ */
+public class LocalVariableAttribute extends AttributeInfo {
+ /**
+ * The name of this attribute <code>"LocalVariableTable"</code>.
+ */
+ public static final String tag = "LocalVariableTable";
+
+ /**
+ * The name of the attribute <code>"LocalVariableTypeTable"</code>.
+ */
+ public static final String typeTag = "LocalVariableTypeTable";
+
+ /**
+ * Constructs an empty LocalVariableTable.
+ */
+ public LocalVariableAttribute(ConstPool cp) {
+ super(cp, tag, new byte[2]);
+ ByteArray.write16bit(0, info, 0);
+ }
+
+ /**
+ * Constructs an empty LocalVariableTable.
+ *
+ * @param name the attribute name.
+ * <code>LocalVariableAttribute.tag</code> or
+ * <code>LocalVariableAttribute.typeTag</code>.
+ * @see #tag
+ * @see #typeTag
+ * @since 3.1
+ * @deprecated
+ */
+ public LocalVariableAttribute(ConstPool cp, String name) {
+ super(cp, name, new byte[2]);
+ ByteArray.write16bit(0, info, 0);
+ }
+
+ LocalVariableAttribute(ConstPool cp, int n, DataInputStream in)
+ throws IOException
+ {
+ super(cp, n, in);
+ }
+
+ LocalVariableAttribute(ConstPool cp, String name, byte[] i) {
+ super(cp, name, i);
+ }
+
+ /**
+ * Appends a new entry to <code>local_variable_table</code>.
+ *
+ * @param startPc <code>start_pc</code>
+ * @param length <code>length</code>
+ * @param nameIndex <code>name_index</code>
+ * @param descriptorIndex <code>descriptor_index</code>
+ * @param index <code>index</code>
+ */
+ public void addEntry(int startPc, int length, int nameIndex,
+ int descriptorIndex, int index) {
+ int size = info.length;
+ byte[] newInfo = new byte[size + 10];
+ ByteArray.write16bit(tableLength() + 1, newInfo, 0);
+ for (int i = 2; i < size; ++i)
+ newInfo[i] = info[i];
+
+ ByteArray.write16bit(startPc, newInfo, size);
+ ByteArray.write16bit(length, newInfo, size + 2);
+ ByteArray.write16bit(nameIndex, newInfo, size + 4);
+ ByteArray.write16bit(descriptorIndex, newInfo, size + 6);
+ ByteArray.write16bit(index, newInfo, size + 8);
+ info = newInfo;
+ }
+
+ void renameClass(String oldname, String newname) {
+ ConstPool cp = getConstPool();
+ int n = tableLength();
+ for (int i = 0; i < n; ++i) {
+ int pos = i * 10 + 2;
+ int index = ByteArray.readU16bit(info, pos + 6);
+ if (index != 0) {
+ String desc = cp.getUtf8Info(index);
+ desc = renameEntry(desc, oldname, newname);
+ ByteArray.write16bit(cp.addUtf8Info(desc), info, pos + 6);
+ }
+ }
+ }
+
+ String renameEntry(String desc, String oldname, String newname) {
+ return Descriptor.rename(desc, oldname, newname);
+ }
+
+ void renameClass(Map classnames) {
+ ConstPool cp = getConstPool();
+ int n = tableLength();
+ for (int i = 0; i < n; ++i) {
+ int pos = i * 10 + 2;
+ int index = ByteArray.readU16bit(info, pos + 6);
+ if (index != 0) {
+ String desc = cp.getUtf8Info(index);
+ desc = renameEntry(desc, classnames);
+ ByteArray.write16bit(cp.addUtf8Info(desc), info, pos + 6);
+ }
+ }
+ }
+
+ String renameEntry(String desc, Map classnames) {
+ return Descriptor.rename(desc, classnames);
+ }
+
+ /**
+ * For each <code>local_variable_table[i].index</code>,
+ * this method increases <code>index</code> by <code>delta</code>.
+ *
+ * @param lessThan the index does not change if it
+ * is less than this value.
+ */
+ public void shiftIndex(int lessThan, int delta) {
+ int size = info.length;
+ for (int i = 2; i < size; i += 10){
+ int org = ByteArray.readU16bit(info, i + 8);
+ if (org >= lessThan)
+ ByteArray.write16bit(org + delta, info, i + 8);
+ }
+ }
+
+ /**
+ * Returns <code>local_variable_table_length</code>.
+ * This represents the number of entries in the table.
+ */
+ public int tableLength() {
+ return ByteArray.readU16bit(info, 0);
+ }
+
+ /**
+ * Returns <code>local_variable_table[i].start_pc</code>.
+ * This represents the index into the code array from which the local
+ * variable is effective.
+ *
+ * @param i the i-th entry.
+ */
+ public int startPc(int i) {
+ return ByteArray.readU16bit(info, i * 10 + 2);
+ }
+
+ /**
+ * Returns <code>local_variable_table[i].length</code>.
+ * This represents the length of the code region in which the local
+ * variable is effective.
+ *
+ * @param i the i-th entry.
+ */
+ public int codeLength(int i) {
+ return ByteArray.readU16bit(info, i * 10 + 4);
+ }
+
+ /**
+ * Adjusts start_pc and length if bytecode is inserted in a method body.
+ */
+ void shiftPc(int where, int gapLength, boolean exclusive) {
+ int n = tableLength();
+ for (int i = 0; i < n; ++i) {
+ int pos = i * 10 + 2;
+ int pc = ByteArray.readU16bit(info, pos);
+ int len = ByteArray.readU16bit(info, pos + 2);
+
+ /* if pc == 0, then the local variable is a method parameter.
+ */
+ if (pc > where || (exclusive && pc == where && pc != 0))
+ ByteArray.write16bit(pc + gapLength, info, pos);
+ else if (pc + len > where || (exclusive && pc + len == where))
+ ByteArray.write16bit(len + gapLength, info, pos + 2);
+ }
+ }
+
+ /**
+ * Returns the value of <code>local_variable_table[i].name_index</code>.
+ * This represents the name of the local variable.
+ *
+ * @param i the i-th entry.
+ */
+ public int nameIndex(int i) {
+ return ByteArray.readU16bit(info, i * 10 + 6);
+ }
+
+ /**
+ * Returns the name of the local variable
+ * specified by <code>local_variable_table[i].name_index</code>.
+ *
+ * @param i the i-th entry.
+ */
+ public String variableName(int i) {
+ return getConstPool().getUtf8Info(nameIndex(i));
+ }
+
+ /**
+ * Returns the value of
+ * <code>local_variable_table[i].descriptor_index</code>.
+ * This represents the type descriptor of the local variable.
+ * <p>
+ * If this attribute represents a LocalVariableTypeTable attribute,
+ * this method returns the value of
+ * <code>local_variable_type_table[i].signature_index</code>.
+ * It represents the type of the local variable.
+ *
+ * @param i the i-th entry.
+ */
+ public int descriptorIndex(int i) {
+ return ByteArray.readU16bit(info, i * 10 + 8);
+ }
+
+ /**
+ * This method is equivalent to <code>descriptorIndex()</code>.
+ * If this attribute represents a LocalVariableTypeTable attribute,
+ * this method should be used instead of <code>descriptorIndex()</code>
+ * since the method name is more appropriate.
+ *
+ * @param i the i-th entry.
+ * @see #descriptorIndex(int)
+ * @see SignatureAttribute#toFieldSignature(String)
+ */
+ public int signatureIndex(int i) {
+ return descriptorIndex(i);
+ }
+
+ /**
+ * Returns the type descriptor of the local variable
+ * specified by <code>local_variable_table[i].descriptor_index</code>.
+ * <p>
+ * If this attribute represents a LocalVariableTypeTable attribute,
+ * this method returns the type signature of the local variable
+ * specified by <code>local_variable_type_table[i].signature_index</code>.
+ *
+ * @param i the i-th entry.
+ */
+ public String descriptor(int i) {
+ return getConstPool().getUtf8Info(descriptorIndex(i));
+ }
+
+ /**
+ * This method is equivalent to <code>descriptor()</code>.
+ * If this attribute represents a LocalVariableTypeTable attribute,
+ * this method should be used instead of <code>descriptor()</code>
+ * since the method name is more appropriate.
+ *
+ * <p>To parse the string, call <code>toFieldSignature(String)</code>
+ * in <code>SignatureAttribute</code>.
+ *
+ * @param i the i-th entry.
+ * @see #descriptor(int)
+ * @see SignatureAttribute#toFieldSignature(String)
+ */
+ public String signature(int i) {
+ return descriptor(i);
+ }
+
+ /**
+ * Returns <code>local_variable_table[i].index</code>.
+ * This represents the index of the local variable.
+ *
+ * @param i the i-th entry.
+ */
+ public int index(int i) {
+ return ByteArray.readU16bit(info, i * 10 + 10);
+ }
+
+ /**
+ * Makes a copy.
+ *
+ * @param newCp the constant pool table used by the new copy.
+ * @param classnames should be null.
+ */
+ public AttributeInfo copy(ConstPool newCp, Map classnames) {
+ byte[] src = get();
+ byte[] dest = new byte[src.length];
+ ConstPool cp = getConstPool();
+ LocalVariableAttribute attr = makeThisAttr(newCp, dest);
+ int n = ByteArray.readU16bit(src, 0);
+ ByteArray.write16bit(n, dest, 0);
+ int j = 2;
+ for (int i = 0; i < n; ++i) {
+ int start = ByteArray.readU16bit(src, j);
+ int len = ByteArray.readU16bit(src, j + 2);
+ int name = ByteArray.readU16bit(src, j + 4);
+ int type = ByteArray.readU16bit(src, j + 6);
+ int index = ByteArray.readU16bit(src, j + 8);
+
+ ByteArray.write16bit(start, dest, j);
+ ByteArray.write16bit(len, dest, j + 2);
+ if (name != 0)
+ name = cp.copy(name, newCp, null);
+
+ ByteArray.write16bit(name, dest, j + 4);
+
+ if (type != 0) {
+ String sig = cp.getUtf8Info(type);
+ sig = Descriptor.rename(sig, classnames);
+ type = newCp.addUtf8Info(sig);
+ }
+
+ ByteArray.write16bit(type, dest, j + 6);
+ ByteArray.write16bit(index, dest, j + 8);
+ j += 10;
+ }
+
+ return attr;
+ }
+
+ // LocalVariableTypeAttribute overrides this method.
+ LocalVariableAttribute makeThisAttr(ConstPool cp, byte[] dest) {
+ return new LocalVariableAttribute(cp, tag, dest);
+ }
+}
diff --git a/src/main/javassist/bytecode/LocalVariableTypeAttribute.java b/src/main/javassist/bytecode/LocalVariableTypeAttribute.java
new file mode 100644
index 0000000..d7ac098
--- /dev/null
+++ b/src/main/javassist/bytecode/LocalVariableTypeAttribute.java
@@ -0,0 +1,62 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * <code>LocalVariableTypeTable_attribute</code>.
+ *
+ * @since 3.11
+ */
+public class LocalVariableTypeAttribute extends LocalVariableAttribute {
+ /**
+ * The name of the attribute <code>"LocalVariableTypeTable"</code>.
+ */
+ public static final String tag = LocalVariableAttribute.typeTag;
+
+ /**
+ * Constructs an empty LocalVariableTypeTable.
+ */
+ public LocalVariableTypeAttribute(ConstPool cp) {
+ super(cp, tag, new byte[2]);
+ ByteArray.write16bit(0, info, 0);
+ }
+
+ LocalVariableTypeAttribute(ConstPool cp, int n, DataInputStream in)
+ throws IOException
+ {
+ super(cp, n, in);
+ }
+
+ private LocalVariableTypeAttribute(ConstPool cp, byte[] dest) {
+ super(cp, tag, dest);
+ }
+
+ String renameEntry(String desc, String oldname, String newname) {
+ return SignatureAttribute.renameClass(desc, oldname, newname);
+ }
+
+ String renameEntry(String desc, Map classnames) {
+ return SignatureAttribute.renameClass(desc, classnames);
+ }
+
+ LocalVariableAttribute makeThisAttr(ConstPool cp, byte[] dest) {
+ return new LocalVariableTypeAttribute(cp, dest);
+ }
+}
diff --git a/src/main/javassist/bytecode/LongVector.java b/src/main/javassist/bytecode/LongVector.java
new file mode 100644
index 0000000..1f76b4a
--- /dev/null
+++ b/src/main/javassist/bytecode/LongVector.java
@@ -0,0 +1,63 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode;
+
+final class LongVector {
+ static final int ASIZE = 128;
+ static final int ABITS = 7; // ASIZE = 2^ABITS
+ static final int VSIZE = 8;
+ private ConstInfo[][] objects;
+ private int elements;
+
+ public LongVector() {
+ objects = new ConstInfo[VSIZE][];
+ elements = 0;
+ }
+
+ public LongVector(int initialSize) {
+ int vsize = ((initialSize >> ABITS) & ~(VSIZE - 1)) + VSIZE;
+ objects = new ConstInfo[vsize][];
+ elements = 0;
+ }
+
+ public int size() { return elements; }
+
+ public int capacity() { return objects.length * ASIZE; }
+
+ public ConstInfo elementAt(int i) {
+ if (i < 0 || elements <= i)
+ return null;
+
+ return objects[i >> ABITS][i & (ASIZE - 1)];
+ }
+
+ public void addElement(ConstInfo value) {
+ int nth = elements >> ABITS;
+ int offset = elements & (ASIZE - 1);
+ int len = objects.length;
+ if (nth >= len) {
+ ConstInfo[][] newObj = new ConstInfo[len + VSIZE][];
+ System.arraycopy(objects, 0, newObj, 0, len);
+ objects = newObj;
+ }
+
+ if (objects[nth] == null)
+ objects[nth] = new ConstInfo[ASIZE];
+
+ objects[nth][offset] = value;
+ elements++;
+ }
+}
diff --git a/src/main/javassist/bytecode/MethodInfo.java b/src/main/javassist/bytecode/MethodInfo.java
new file mode 100644
index 0000000..aae98ea
--- /dev/null
+++ b/src/main/javassist/bytecode/MethodInfo.java
@@ -0,0 +1,542 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import javassist.ClassPool;
+import javassist.bytecode.stackmap.MapMaker;
+
+/**
+ * <code>method_info</code> structure.
+ *
+ * @see javassist.CtMethod#getMethodInfo()
+ * @see javassist.CtConstructor#getMethodInfo()
+ */
+public class MethodInfo {
+ ConstPool constPool;
+ int accessFlags;
+ int name;
+ String cachedName;
+ int descriptor;
+ ArrayList attribute; // may be null
+
+ /**
+ * If this value is true, Javassist maintains a <code>StackMap</code> attribute
+ * generated by the <code>preverify</code> tool of J2ME (CLDC). The initial
+ * value of this field is <code>false</code>.
+ */
+ public static boolean doPreverify = false;
+
+ /**
+ * The name of constructors: <code><init></code>.
+ */
+ public static final String nameInit = "<init>";
+
+ /**
+ * The name of class initializer (static initializer):
+ * <code><clinit></code>.
+ */
+ public static final String nameClinit = "<clinit>";
+
+ private MethodInfo(ConstPool cp) {
+ constPool = cp;
+ attribute = null;
+ }
+
+ /**
+ * Constructs a <code>method_info</code> structure. The initial value of
+ * <code>access_flags</code> is zero.
+ *
+ * @param cp
+ * a constant pool table
+ * @param methodname
+ * method name
+ * @param desc
+ * method descriptor
+ * @see Descriptor
+ */
+ public MethodInfo(ConstPool cp, String methodname, String desc) {
+ this(cp);
+ accessFlags = 0;
+ name = cp.addUtf8Info(methodname);
+ cachedName = methodname;
+ descriptor = constPool.addUtf8Info(desc);
+ }
+
+ MethodInfo(ConstPool cp, DataInputStream in) throws IOException {
+ this(cp);
+ read(in);
+ }
+
+ /**
+ * Constructs a copy of <code>method_info</code> structure. Class names
+ * appearing in the source <code>method_info</code> are renamed according
+ * to <code>classnameMap</code>.
+ *
+ * <p>
+ * Note: only <code>Code</code> and <code>Exceptions</code> attributes
+ * are copied from the source. The other attributes are ignored.
+ *
+ * @param cp
+ * a constant pool table
+ * @param methodname
+ * a method name
+ * @param src
+ * a source <code>method_info</code>
+ * @param classnameMap
+ * specifies pairs of replaced and substituted name.
+ * @see Descriptor
+ */
+ public MethodInfo(ConstPool cp, String methodname, MethodInfo src,
+ Map classnameMap) throws BadBytecode {
+ this(cp);
+ read(src, methodname, classnameMap);
+ }
+
+ /**
+ * Returns a string representation of the object.
+ */
+ public String toString() {
+ return getName() + " " + getDescriptor();
+ }
+
+ /**
+ * Copies all constant pool items to a given new constant pool
+ * and replaces the original items with the new ones.
+ * This is used for garbage collecting the items of removed fields
+ * and methods.
+ *
+ * @param cp the destination
+ */
+ void compact(ConstPool cp) {
+ name = cp.addUtf8Info(getName());
+ descriptor = cp.addUtf8Info(getDescriptor());
+ attribute = AttributeInfo.copyAll(attribute, cp);
+ constPool = cp;
+ }
+
+ void prune(ConstPool cp) {
+ ArrayList newAttributes = new ArrayList();
+
+ AttributeInfo invisibleAnnotations
+ = getAttribute(AnnotationsAttribute.invisibleTag);
+ if (invisibleAnnotations != null) {
+ invisibleAnnotations = invisibleAnnotations.copy(cp, null);
+ newAttributes.add(invisibleAnnotations);
+ }
+
+ AttributeInfo visibleAnnotations
+ = getAttribute(AnnotationsAttribute.visibleTag);
+ if (visibleAnnotations != null) {
+ visibleAnnotations = visibleAnnotations.copy(cp, null);
+ newAttributes.add(visibleAnnotations);
+ }
+
+ AttributeInfo parameterInvisibleAnnotations
+ = getAttribute(ParameterAnnotationsAttribute.invisibleTag);
+ if (parameterInvisibleAnnotations != null) {
+ parameterInvisibleAnnotations = parameterInvisibleAnnotations.copy(cp, null);
+ newAttributes.add(parameterInvisibleAnnotations);
+ }
+
+ AttributeInfo parameterVisibleAnnotations
+ = getAttribute(ParameterAnnotationsAttribute.visibleTag);
+ if (parameterVisibleAnnotations != null) {
+ parameterVisibleAnnotations = parameterVisibleAnnotations.copy(cp, null);
+ newAttributes.add(parameterVisibleAnnotations);
+ }
+
+ AnnotationDefaultAttribute defaultAttribute
+ = (AnnotationDefaultAttribute) getAttribute(AnnotationDefaultAttribute.tag);
+ if (defaultAttribute != null)
+ newAttributes.add(defaultAttribute);
+
+ ExceptionsAttribute ea = getExceptionsAttribute();
+ if (ea != null)
+ newAttributes.add(ea);
+
+ AttributeInfo signature
+ = getAttribute(SignatureAttribute.tag);
+ if (signature != null) {
+ signature = signature.copy(cp, null);
+ newAttributes.add(signature);
+ }
+
+ attribute = newAttributes;
+ name = cp.addUtf8Info(getName());
+ descriptor = cp.addUtf8Info(getDescriptor());
+ constPool = cp;
+ }
+
+ /**
+ * Returns a method name.
+ */
+ public String getName() {
+ if (cachedName == null)
+ cachedName = constPool.getUtf8Info(name);
+
+ return cachedName;
+ }
+
+ /**
+ * Sets a method name.
+ */
+ public void setName(String newName) {
+ name = constPool.addUtf8Info(newName);
+ cachedName = newName;
+ }
+
+ /**
+ * Returns true if this is not a constructor or a class initializer (static
+ * initializer).
+ */
+ public boolean isMethod() {
+ String n = getName();
+ return !n.equals(nameInit) && !n.equals(nameClinit);
+ }
+
+ /**
+ * Returns a constant pool table used by this method.
+ */
+ public ConstPool getConstPool() {
+ return constPool;
+ }
+
+ /**
+ * Returns true if this is a constructor.
+ */
+ public boolean isConstructor() {
+ return getName().equals(nameInit);
+ }
+
+ /**
+ * Returns true if this is a class initializer (static initializer).
+ */
+ public boolean isStaticInitializer() {
+ return getName().equals(nameClinit);
+ }
+
+ /**
+ * Returns access flags.
+ *
+ * @see AccessFlag
+ */
+ public int getAccessFlags() {
+ return accessFlags;
+ }
+
+ /**
+ * Sets access flags.
+ *
+ * @see AccessFlag
+ */
+ public void setAccessFlags(int acc) {
+ accessFlags = acc;
+ }
+
+ /**
+ * Returns a method descriptor.
+ *
+ * @see Descriptor
+ */
+ public String getDescriptor() {
+ return constPool.getUtf8Info(descriptor);
+ }
+
+ /**
+ * Sets a method descriptor.
+ *
+ * @see Descriptor
+ */
+ public void setDescriptor(String desc) {
+ if (!desc.equals(getDescriptor()))
+ descriptor = constPool.addUtf8Info(desc);
+ }
+
+ /**
+ * Returns all the attributes. The returned <code>List</code> object
+ * is shared with this object. If you add a new attribute to the list,
+ * the attribute is also added to the method represented by this
+ * object. If you remove an attribute from the list, it is also removed
+ * from the method.
+ *
+ * @return a list of <code>AttributeInfo</code> objects.
+ * @see AttributeInfo
+ */
+ public List getAttributes() {
+ if (attribute == null)
+ attribute = new ArrayList();
+
+ return attribute;
+ }
+
+ /**
+ * Returns the attribute with the specified name. If it is not found, this
+ * method returns null.
+ *
+ * @param name attribute name
+ * @return an <code>AttributeInfo</code> object or null.
+ * @see #getAttributes()
+ */
+ public AttributeInfo getAttribute(String name) {
+ return AttributeInfo.lookup(attribute, name);
+ }
+
+ /**
+ * Appends an attribute. If there is already an attribute with the same
+ * name, the new one substitutes for it.
+ *
+ * @see #getAttributes()
+ */
+ public void addAttribute(AttributeInfo info) {
+ if (attribute == null)
+ attribute = new ArrayList();
+
+ AttributeInfo.remove(attribute, info.getName());
+ attribute.add(info);
+ }
+
+ /**
+ * Returns an Exceptions attribute.
+ *
+ * @return an Exceptions attribute or null if it is not specified.
+ */
+ public ExceptionsAttribute getExceptionsAttribute() {
+ AttributeInfo info = AttributeInfo.lookup(attribute,
+ ExceptionsAttribute.tag);
+ return (ExceptionsAttribute)info;
+ }
+
+ /**
+ * Returns a Code attribute.
+ *
+ * @return a Code attribute or null if it is not specified.
+ */
+ public CodeAttribute getCodeAttribute() {
+ AttributeInfo info = AttributeInfo.lookup(attribute, CodeAttribute.tag);
+ return (CodeAttribute)info;
+ }
+
+ /**
+ * Removes an Exception attribute.
+ */
+ public void removeExceptionsAttribute() {
+ AttributeInfo.remove(attribute, ExceptionsAttribute.tag);
+ }
+
+ /**
+ * Adds an Exception attribute.
+ *
+ * <p>
+ * The added attribute must share the same constant pool table as this
+ * <code>method_info</code> structure.
+ */
+ public void setExceptionsAttribute(ExceptionsAttribute cattr) {
+ removeExceptionsAttribute();
+ if (attribute == null)
+ attribute = new ArrayList();
+
+ attribute.add(cattr);
+ }
+
+ /**
+ * Removes a Code attribute.
+ */
+ public void removeCodeAttribute() {
+ AttributeInfo.remove(attribute, CodeAttribute.tag);
+ }
+
+ /**
+ * Adds a Code attribute.
+ *
+ * <p>
+ * The added attribute must share the same constant pool table as this
+ * <code>method_info</code> structure.
+ */
+ public void setCodeAttribute(CodeAttribute cattr) {
+ removeCodeAttribute();
+ if (attribute == null)
+ attribute = new ArrayList();
+
+ attribute.add(cattr);
+ }
+
+ /**
+ * Rebuilds a stack map table if the class file is for Java 6
+ * or later. Java 5 or older Java VMs do not recognize a stack
+ * map table. If <code>doPreverify</code> is true, this method
+ * also rebuilds a stack map for J2ME (CLDC).
+ *
+ * @param pool used for making type hierarchy.
+ * @param cf rebuild if this class file is for Java 6 or later.
+ * @see #rebuildStackMap(ClassPool)
+ * @see #rebuildStackMapForME(ClassPool)
+ * @since 3.6
+ */
+ public void rebuildStackMapIf6(ClassPool pool, ClassFile cf)
+ throws BadBytecode
+ {
+ if (cf.getMajorVersion() >= ClassFile.JAVA_6)
+ rebuildStackMap(pool);
+
+ if (doPreverify)
+ rebuildStackMapForME(pool);
+ }
+
+ /**
+ * Rebuilds a stack map table. If no stack map table is included,
+ * a new one is created. If this <code>MethodInfo</code> does not
+ * include a code attribute, nothing happens.
+ *
+ * @param pool used for making type hierarchy.
+ * @see StackMapTable
+ * @since 3.6
+ */
+ public void rebuildStackMap(ClassPool pool) throws BadBytecode {
+ CodeAttribute ca = getCodeAttribute();
+ if (ca != null) {
+ StackMapTable smt = MapMaker.make(pool, this);
+ ca.setAttribute(smt);
+ }
+ }
+
+ /**
+ * Rebuilds a stack map table for J2ME (CLDC). If no stack map table is included,
+ * a new one is created. If this <code>MethodInfo</code> does not
+ * include a code attribute, nothing happens.
+ *
+ * @param pool used for making type hierarchy.
+ * @see StackMapTable
+ * @since 3.12
+ */
+ public void rebuildStackMapForME(ClassPool pool) throws BadBytecode {
+ CodeAttribute ca = getCodeAttribute();
+ if (ca != null) {
+ StackMap sm = MapMaker.make2(pool, this);
+ ca.setAttribute(sm);
+ }
+ }
+
+ /**
+ * Returns the line number of the source line corresponding to the specified
+ * bytecode contained in this method.
+ *
+ * @param pos
+ * the position of the bytecode (>= 0). an index into the code
+ * array.
+ * @return -1 if this information is not available.
+ */
+ public int getLineNumber(int pos) {
+ CodeAttribute ca = getCodeAttribute();
+ if (ca == null)
+ return -1;
+
+ LineNumberAttribute ainfo = (LineNumberAttribute)ca
+ .getAttribute(LineNumberAttribute.tag);
+ if (ainfo == null)
+ return -1;
+
+ return ainfo.toLineNumber(pos);
+ }
+
+ /**
+ * Changes a super constructor called by this constructor.
+ *
+ * <p>
+ * This method modifies a call to <code>super()</code>, which should be
+ * at the head of a constructor body, so that a constructor in a different
+ * super class is called. This method does not change actual parameters.
+ * Hence the new super class must have a constructor with the same signature
+ * as the original one.
+ *
+ * <p>
+ * This method should be called when the super class of the class declaring
+ * this method is changed.
+ *
+ * <p>
+ * This method does not perform anything unless this <code>MethodInfo</code>
+ * represents a constructor.
+ *
+ * @param superclass
+ * the new super class
+ */
+ public void setSuperclass(String superclass) throws BadBytecode {
+ if (!isConstructor())
+ return;
+
+ CodeAttribute ca = getCodeAttribute();
+ byte[] code = ca.getCode();
+ CodeIterator iterator = ca.iterator();
+ int pos = iterator.skipSuperConstructor();
+ if (pos >= 0) { // not this()
+ ConstPool cp = constPool;
+ int mref = ByteArray.readU16bit(code, pos + 1);
+ int nt = cp.getMethodrefNameAndType(mref);
+ int sc = cp.addClassInfo(superclass);
+ int mref2 = cp.addMethodrefInfo(sc, nt);
+ ByteArray.write16bit(mref2, code, pos + 1);
+ }
+ }
+
+ private void read(MethodInfo src, String methodname, Map classnames)
+ throws BadBytecode {
+ ConstPool destCp = constPool;
+ accessFlags = src.accessFlags;
+ name = destCp.addUtf8Info(methodname);
+ cachedName = methodname;
+ ConstPool srcCp = src.constPool;
+ String desc = srcCp.getUtf8Info(src.descriptor);
+ String desc2 = Descriptor.rename(desc, classnames);
+ descriptor = destCp.addUtf8Info(desc2);
+
+ attribute = new ArrayList();
+ ExceptionsAttribute eattr = src.getExceptionsAttribute();
+ if (eattr != null)
+ attribute.add(eattr.copy(destCp, classnames));
+
+ CodeAttribute cattr = src.getCodeAttribute();
+ if (cattr != null)
+ attribute.add(cattr.copy(destCp, classnames));
+ }
+
+ private void read(DataInputStream in) throws IOException {
+ accessFlags = in.readUnsignedShort();
+ name = in.readUnsignedShort();
+ descriptor = in.readUnsignedShort();
+ int n = in.readUnsignedShort();
+ attribute = new ArrayList();
+ for (int i = 0; i < n; ++i)
+ attribute.add(AttributeInfo.read(constPool, in));
+ }
+
+ void write(DataOutputStream out) throws IOException {
+ out.writeShort(accessFlags);
+ out.writeShort(name);
+ out.writeShort(descriptor);
+
+ if (attribute == null)
+ out.writeShort(0);
+ else {
+ out.writeShort(attribute.size());
+ AttributeInfo.writeAll(attribute, out);
+ }
+ }
+}
diff --git a/src/main/javassist/bytecode/Mnemonic.java b/src/main/javassist/bytecode/Mnemonic.java
new file mode 100644
index 0000000..2eb596a
--- /dev/null
+++ b/src/main/javassist/bytecode/Mnemonic.java
@@ -0,0 +1,241 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode;
+
+/**
+ * JVM Instruction Names.
+ *
+ * <p>This interface has been separated from javassist.bytecode.Opcode
+ * because typical bytecode translators do not use mnemonics. If this
+ * interface were merged with Opcode, extra memory would be unnecessary
+ * consumed.
+ *
+ * @see Opcode
+ */
+public interface Mnemonic {
+
+ /**
+ * The instruction names (mnemonics) sorted by the opcode.
+ * The length of this array is 202 (jsr_w=201).
+ *
+ * <p>The value at index 186 is null since no instruction is
+ * assigned to 186.
+ */
+ String[] OPCODE = {
+ "nop", /* 0*/
+ "aconst_null", /* 1*/
+ "iconst_m1", /* 2*/
+ "iconst_0", /* 3*/
+ "iconst_1", /* 4*/
+ "iconst_2", /* 5*/
+ "iconst_3", /* 6*/
+ "iconst_4", /* 7*/
+ "iconst_5", /* 8*/
+ "lconst_0", /* 9*/
+ "lconst_1", /* 10*/
+ "fconst_0", /* 11*/
+ "fconst_1", /* 12*/
+ "fconst_2", /* 13*/
+ "dconst_0", /* 14*/
+ "dconst_1", /* 15*/
+ "bipush", /* 16*/
+ "sipush", /* 17*/
+ "ldc", /* 18*/
+ "ldc_w", /* 19*/
+ "ldc2_w", /* 20*/
+ "iload", /* 21*/
+ "lload", /* 22*/
+ "fload", /* 23*/
+ "dload", /* 24*/
+ "aload", /* 25*/
+ "iload_0", /* 26*/
+ "iload_1", /* 27*/
+ "iload_2", /* 28*/
+ "iload_3", /* 29*/
+ "lload_0", /* 30*/
+ "lload_1", /* 31*/
+ "lload_2", /* 32*/
+ "lload_3", /* 33*/
+ "fload_0", /* 34*/
+ "fload_1", /* 35*/
+ "fload_2", /* 36*/
+ "fload_3", /* 37*/
+ "dload_0", /* 38*/
+ "dload_1", /* 39*/
+ "dload_2", /* 40*/
+ "dload_3", /* 41*/
+ "aload_0", /* 42*/
+ "aload_1", /* 43*/
+ "aload_2", /* 44*/
+ "aload_3", /* 45*/
+ "iaload", /* 46*/
+ "laload", /* 47*/
+ "faload", /* 48*/
+ "daload", /* 49*/
+ "aaload", /* 50*/
+ "baload", /* 51*/
+ "caload", /* 52*/
+ "saload", /* 53*/
+ "istore", /* 54*/
+ "lstore", /* 55*/
+ "fstore", /* 56*/
+ "dstore", /* 57*/
+ "astore", /* 58*/
+ "istore_0", /* 59*/
+ "istore_1", /* 60*/
+ "istore_2", /* 61*/
+ "istore_3", /* 62*/
+ "lstore_0", /* 63*/
+ "lstore_1", /* 64*/
+ "lstore_2", /* 65*/
+ "lstore_3", /* 66*/
+ "fstore_0", /* 67*/
+ "fstore_1", /* 68*/
+ "fstore_2", /* 69*/
+ "fstore_3", /* 70*/
+ "dstore_0", /* 71*/
+ "dstore_1", /* 72*/
+ "dstore_2", /* 73*/
+ "dstore_3", /* 74*/
+ "astore_0", /* 75*/
+ "astore_1", /* 76*/
+ "astore_2", /* 77*/
+ "astore_3", /* 78*/
+ "iastore", /* 79*/
+ "lastore", /* 80*/
+ "fastore", /* 81*/
+ "dastore", /* 82*/
+ "aastore", /* 83*/
+ "bastore", /* 84*/
+ "castore", /* 85*/
+ "sastore", /* 86*/
+ "pop", /* 87*/
+ "pop2", /* 88*/
+ "dup", /* 89*/
+ "dup_x1", /* 90*/
+ "dup_x2", /* 91*/
+ "dup2", /* 92*/
+ "dup2_x1", /* 93*/
+ "dup2_x2", /* 94*/
+ "swap", /* 95*/
+ "iadd", /* 96*/
+ "ladd", /* 97*/
+ "fadd", /* 98*/
+ "dadd", /* 99*/
+ "isub", /* 100*/
+ "lsub", /* 101*/
+ "fsub", /* 102*/
+ "dsub", /* 103*/
+ "imul", /* 104*/
+ "lmul", /* 105*/
+ "fmul", /* 106*/
+ "dmul", /* 107*/
+ "idiv", /* 108*/
+ "ldiv", /* 109*/
+ "fdiv", /* 110*/
+ "ddiv", /* 111*/
+ "irem", /* 112*/
+ "lrem", /* 113*/
+ "frem", /* 114*/
+ "drem", /* 115*/
+ "ineg", /* 116*/
+ "lneg", /* 117*/
+ "fneg", /* 118*/
+ "dneg", /* 119*/
+ "ishl", /* 120*/
+ "lshl", /* 121*/
+ "ishr", /* 122*/
+ "lshr", /* 123*/
+ "iushr", /* 124*/
+ "lushr", /* 125*/
+ "iand", /* 126*/
+ "land", /* 127*/
+ "ior", /* 128*/
+ "lor", /* 129*/
+ "ixor", /* 130*/
+ "lxor", /* 131*/
+ "iinc", /* 132*/
+ "i2l", /* 133*/
+ "i2f", /* 134*/
+ "i2d", /* 135*/
+ "l2i", /* 136*/
+ "l2f", /* 137*/
+ "l2d", /* 138*/
+ "f2i", /* 139*/
+ "f2l", /* 140*/
+ "f2d", /* 141*/
+ "d2i", /* 142*/
+ "d2l", /* 143*/
+ "d2f", /* 144*/
+ "i2b", /* 145*/
+ "i2c", /* 146*/
+ "i2s", /* 147*/
+ "lcmp", /* 148*/
+ "fcmpl", /* 149*/
+ "fcmpg", /* 150*/
+ "dcmpl", /* 151*/
+ "dcmpg", /* 152*/
+ "ifeq", /* 153*/
+ "ifne", /* 154*/
+ "iflt", /* 155*/
+ "ifge", /* 156*/
+ "ifgt", /* 157*/
+ "ifle", /* 158*/
+ "if_icmpeq", /* 159*/
+ "if_icmpne", /* 160*/
+ "if_icmplt", /* 161*/
+ "if_icmpge", /* 162*/
+ "if_icmpgt", /* 163*/
+ "if_icmple", /* 164*/
+ "if_acmpeq", /* 165*/
+ "if_acmpne", /* 166*/
+ "goto", /* 167*/
+ "jsr", /* 168*/
+ "ret", /* 169*/
+ "tableswitch", /* 170*/
+ "lookupswitch", /* 171*/
+ "ireturn", /* 172*/
+ "lreturn", /* 173*/
+ "freturn", /* 174*/
+ "dreturn", /* 175*/
+ "areturn", /* 176*/
+ "return", /* 177*/
+ "getstatic", /* 178*/
+ "putstatic", /* 179*/
+ "getfield", /* 180*/
+ "putfield", /* 181*/
+ "invokevirtual", /* 182*/
+ "invokespecial", /* 183*/
+ "invokestatic", /* 184*/
+ "invokeinterface", /* 185*/
+ null,
+ "new", /* 187*/
+ "newarray", /* 188*/
+ "anewarray", /* 189*/
+ "arraylength", /* 190*/
+ "athrow", /* 191*/
+ "checkcast", /* 192*/
+ "instanceof", /* 193*/
+ "monitorenter", /* 194*/
+ "monitorexit", /* 195*/
+ "wide", /* 196*/
+ "multianewarray", /* 197*/
+ "ifnull", /* 198*/
+ "ifnonnull", /* 199*/
+ "goto_w", /* 200*/
+ "jsr_w" /* 201*/
+ };
+}
diff --git a/src/main/javassist/bytecode/Opcode.java b/src/main/javassist/bytecode/Opcode.java
new file mode 100644
index 0000000..db34b11
--- /dev/null
+++ b/src/main/javassist/bytecode/Opcode.java
@@ -0,0 +1,447 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode;
+
+/**
+ * JVM Instruction Set.
+ *
+ * <p>This interface defines opcodes and
+ * array types for the NEWARRAY instruction.
+ *
+ * @see Mnemonic
+ */
+public interface Opcode {
+ /* Opcodes */
+
+ int AALOAD = 50;
+ int AASTORE = 83;
+ int ACONST_NULL = 1;
+ int ALOAD = 25;
+ int ALOAD_0 = 42;
+ int ALOAD_1 = 43;
+ int ALOAD_2 = 44;
+ int ALOAD_3 = 45;
+ int ANEWARRAY = 189;
+ int ARETURN = 176;
+ int ARRAYLENGTH = 190;
+ int ASTORE = 58;
+ int ASTORE_0 = 75;
+ int ASTORE_1 = 76;
+ int ASTORE_2 = 77;
+ int ASTORE_3 = 78;
+ int ATHROW = 191;
+ int BALOAD = 51;
+ int BASTORE = 84;
+ int BIPUSH = 16;
+ int CALOAD = 52;
+ int CASTORE = 85;
+ int CHECKCAST = 192;
+ int D2F = 144;
+ int D2I = 142;
+ int D2L = 143;
+ int DADD = 99;
+ int DALOAD = 49;
+ int DASTORE = 82;
+ int DCMPG = 152;
+ int DCMPL = 151;
+ int DCONST_0 = 14;
+ int DCONST_1 = 15;
+ int DDIV = 111;
+ int DLOAD = 24;
+ int DLOAD_0 = 38;
+ int DLOAD_1 = 39;
+ int DLOAD_2 = 40;
+ int DLOAD_3 = 41;
+ int DMUL = 107;
+ int DNEG = 119;
+ int DREM = 115;
+ int DRETURN = 175;
+ int DSTORE = 57;
+ int DSTORE_0 = 71;
+ int DSTORE_1 = 72;
+ int DSTORE_2 = 73;
+ int DSTORE_3 = 74;
+ int DSUB = 103;
+ int DUP = 89;
+ int DUP2 = 92;
+ int DUP2_X1 = 93;
+ int DUP2_X2 = 94;
+ int DUP_X1 = 90;
+ int DUP_X2 = 91;
+ int F2D = 141;
+ int F2I = 139;
+ int F2L = 140;
+ int FADD = 98;
+ int FALOAD = 48;
+ int FASTORE = 81;
+ int FCMPG = 150;
+ int FCMPL = 149;
+ int FCONST_0 = 11;
+ int FCONST_1 = 12;
+ int FCONST_2 = 13;
+ int FDIV = 110;
+ int FLOAD = 23;
+ int FLOAD_0 = 34;
+ int FLOAD_1 = 35;
+ int FLOAD_2 = 36;
+ int FLOAD_3 = 37;
+ int FMUL = 106;
+ int FNEG = 118;
+ int FREM = 114;
+ int FRETURN = 174;
+ int FSTORE = 56;
+ int FSTORE_0 = 67;
+ int FSTORE_1 = 68;
+ int FSTORE_2 = 69;
+ int FSTORE_3 = 70;
+ int FSUB = 102;
+ int GETFIELD = 180;
+ int GETSTATIC = 178;
+ int GOTO = 167;
+ int GOTO_W = 200;
+ int I2B = 145;
+ int I2C = 146;
+ int I2D = 135;
+ int I2F = 134;
+ int I2L = 133;
+ int I2S = 147;
+ int IADD = 96;
+ int IALOAD = 46;
+ int IAND = 126;
+ int IASTORE = 79;
+ int ICONST_0 = 3;
+ int ICONST_1 = 4;
+ int ICONST_2 = 5;
+ int ICONST_3 = 6;
+ int ICONST_4 = 7;
+ int ICONST_5 = 8;
+ int ICONST_M1 = 2;
+ int IDIV = 108;
+ int IFEQ = 153;
+ int IFGE = 156;
+ int IFGT = 157;
+ int IFLE = 158;
+ int IFLT = 155;
+ int IFNE = 154;
+ int IFNONNULL = 199;
+ int IFNULL = 198;
+ int IF_ACMPEQ = 165;
+ int IF_ACMPNE = 166;
+ int IF_ICMPEQ = 159;
+ int IF_ICMPGE = 162;
+ int IF_ICMPGT = 163;
+ int IF_ICMPLE = 164;
+ int IF_ICMPLT = 161;
+ int IF_ICMPNE = 160;
+ int IINC = 132;
+ int ILOAD = 21;
+ int ILOAD_0 = 26;
+ int ILOAD_1 = 27;
+ int ILOAD_2 = 28;
+ int ILOAD_3 = 29;
+ int IMUL = 104;
+ int INEG = 116;
+ int INSTANCEOF = 193;
+ int INVOKEINTERFACE = 185;
+ int INVOKESPECIAL = 183;
+ int INVOKESTATIC = 184;
+ int INVOKEVIRTUAL = 182;
+ int IOR = 128;
+ int IREM = 112;
+ int IRETURN = 172;
+ int ISHL = 120;
+ int ISHR = 122;
+ int ISTORE = 54;
+ int ISTORE_0 = 59;
+ int ISTORE_1 = 60;
+ int ISTORE_2 = 61;
+ int ISTORE_3 = 62;
+ int ISUB = 100;
+ int IUSHR = 124;
+ int IXOR = 130;
+ int JSR = 168;
+ int JSR_W = 201;
+ int L2D = 138;
+ int L2F = 137;
+ int L2I = 136;
+ int LADD = 97;
+ int LALOAD = 47;
+ int LAND = 127;
+ int LASTORE = 80;
+ int LCMP = 148;
+ int LCONST_0 = 9;
+ int LCONST_1 = 10;
+ int LDC = 18;
+ int LDC2_W = 20;
+ int LDC_W = 19;
+ int LDIV = 109;
+ int LLOAD = 22;
+ int LLOAD_0 = 30;
+ int LLOAD_1 = 31;
+ int LLOAD_2 = 32;
+ int LLOAD_3 = 33;
+ int LMUL = 105;
+ int LNEG = 117;
+ int LOOKUPSWITCH = 171;
+ int LOR = 129;
+ int LREM = 113;
+ int LRETURN = 173;
+ int LSHL = 121;
+ int LSHR = 123;
+ int LSTORE = 55;
+ int LSTORE_0 = 63;
+ int LSTORE_1 = 64;
+ int LSTORE_2 = 65;
+ int LSTORE_3 = 66;
+ int LSUB = 101;
+ int LUSHR = 125;
+ int LXOR = 131;
+ int MONITORENTER = 194;
+ int MONITOREXIT = 195;
+ int MULTIANEWARRAY = 197;
+ int NEW = 187;
+ int NEWARRAY = 188;
+ int NOP = 0;
+ int POP = 87;
+ int POP2 = 88;
+ int PUTFIELD = 181;
+ int PUTSTATIC = 179;
+ int RET = 169;
+ int RETURN = 177;
+ int SALOAD = 53;
+ int SASTORE = 86;
+ int SIPUSH = 17;
+ int SWAP = 95;
+ int TABLESWITCH = 170;
+ int WIDE = 196;
+
+ /* array-type code for the newarray instruction */
+
+ int T_BOOLEAN = 4;
+ int T_CHAR = 5;
+ int T_FLOAT = 6;
+ int T_DOUBLE = 7;
+ int T_BYTE = 8;
+ int T_SHORT = 9;
+ int T_INT = 10;
+ int T_LONG = 11;
+
+ /* how many values are pushed on the operand stack. */
+ int[] STACK_GROW = {
+ 0, // nop, 0
+ 1, // aconst_null, 1
+ 1, // iconst_m1, 2
+ 1, // iconst_0, 3
+ 1, // iconst_1, 4
+ 1, // iconst_2, 5
+ 1, // iconst_3, 6
+ 1, // iconst_4, 7
+ 1, // iconst_5, 8
+ 2, // lconst_0, 9
+ 2, // lconst_1, 10
+ 1, // fconst_0, 11
+ 1, // fconst_1, 12
+ 1, // fconst_2, 13
+ 2, // dconst_0, 14
+ 2, // dconst_1, 15
+ 1, // bipush, 16
+ 1, // sipush, 17
+ 1, // ldc, 18
+ 1, // ldc_w, 19
+ 2, // ldc2_w, 20
+ 1, // iload, 21
+ 2, // lload, 22
+ 1, // fload, 23
+ 2, // dload, 24
+ 1, // aload, 25
+ 1, // iload_0, 26
+ 1, // iload_1, 27
+ 1, // iload_2, 28
+ 1, // iload_3, 29
+ 2, // lload_0, 30
+ 2, // lload_1, 31
+ 2, // lload_2, 32
+ 2, // lload_3, 33
+ 1, // fload_0, 34
+ 1, // fload_1, 35
+ 1, // fload_2, 36
+ 1, // fload_3, 37
+ 2, // dload_0, 38
+ 2, // dload_1, 39
+ 2, // dload_2, 40
+ 2, // dload_3, 41
+ 1, // aload_0, 42
+ 1, // aload_1, 43
+ 1, // aload_2, 44
+ 1, // aload_3, 45
+ -1, // iaload, 46
+ 0, // laload, 47
+ -1, // faload, 48
+ 0, // daload, 49
+ -1, // aaload, 50
+ -1, // baload, 51
+ -1, // caload, 52
+ -1, // saload, 53
+ -1, // istore, 54
+ -2, // lstore, 55
+ -1, // fstore, 56
+ -2, // dstore, 57
+ -1, // astore, 58
+ -1, // istore_0, 59
+ -1, // istore_1, 60
+ -1, // istore_2, 61
+ -1, // istore_3, 62
+ -2, // lstore_0, 63
+ -2, // lstore_1, 64
+ -2, // lstore_2, 65
+ -2, // lstore_3, 66
+ -1, // fstore_0, 67
+ -1, // fstore_1, 68
+ -1, // fstore_2, 69
+ -1, // fstore_3, 70
+ -2, // dstore_0, 71
+ -2, // dstore_1, 72
+ -2, // dstore_2, 73
+ -2, // dstore_3, 74
+ -1, // astore_0, 75
+ -1, // astore_1, 76
+ -1, // astore_2, 77
+ -1, // astore_3, 78
+ -3, // iastore, 79
+ -4, // lastore, 80
+ -3, // fastore, 81
+ -4, // dastore, 82
+ -3, // aastore, 83
+ -3, // bastore, 84
+ -3, // castore, 85
+ -3, // sastore, 86
+ -1, // pop, 87
+ -2, // pop2, 88
+ 1, // dup, 89
+ 1, // dup_x1, 90
+ 1, // dup_x2, 91
+ 2, // dup2, 92
+ 2, // dup2_x1, 93
+ 2, // dup2_x2, 94
+ 0, // swap, 95
+ -1, // iadd, 96
+ -2, // ladd, 97
+ -1, // fadd, 98
+ -2, // dadd, 99
+ -1, // isub, 100
+ -2, // lsub, 101
+ -1, // fsub, 102
+ -2, // dsub, 103
+ -1, // imul, 104
+ -2, // lmul, 105
+ -1, // fmul, 106
+ -2, // dmul, 107
+ -1, // idiv, 108
+ -2, // ldiv, 109
+ -1, // fdiv, 110
+ -2, // ddiv, 111
+ -1, // irem, 112
+ -2, // lrem, 113
+ -1, // frem, 114
+ -2, // drem, 115
+ 0, // ineg, 116
+ 0, // lneg, 117
+ 0, // fneg, 118
+ 0, // dneg, 119
+ -1, // ishl, 120
+ -1, // lshl, 121
+ -1, // ishr, 122
+ -1, // lshr, 123
+ -1, // iushr, 124
+ -1, // lushr, 125
+ -1, // iand, 126
+ -2, // land, 127
+ -1, // ior, 128
+ -2, // lor, 129
+ -1, // ixor, 130
+ -2, // lxor, 131
+ 0, // iinc, 132
+ 1, // i2l, 133
+ 0, // i2f, 134
+ 1, // i2d, 135
+ -1, // l2i, 136
+ -1, // l2f, 137
+ 0, // l2d, 138
+ 0, // f2i, 139
+ 1, // f2l, 140
+ 1, // f2d, 141
+ -1, // d2i, 142
+ 0, // d2l, 143
+ -1, // d2f, 144
+ 0, // i2b, 145
+ 0, // i2c, 146
+ 0, // i2s, 147
+ -3, // lcmp, 148
+ -1, // fcmpl, 149
+ -1, // fcmpg, 150
+ -3, // dcmpl, 151
+ -3, // dcmpg, 152
+ -1, // ifeq, 153
+ -1, // ifne, 154
+ -1, // iflt, 155
+ -1, // ifge, 156
+ -1, // ifgt, 157
+ -1, // ifle, 158
+ -2, // if_icmpeq, 159
+ -2, // if_icmpne, 160
+ -2, // if_icmplt, 161
+ -2, // if_icmpge, 162
+ -2, // if_icmpgt, 163
+ -2, // if_icmple, 164
+ -2, // if_acmpeq, 165
+ -2, // if_acmpne, 166
+ 0, // goto, 167
+ 1, // jsr, 168
+ 0, // ret, 169
+ -1, // tableswitch, 170
+ -1, // lookupswitch, 171
+ -1, // ireturn, 172
+ -2, // lreturn, 173
+ -1, // freturn, 174
+ -2, // dreturn, 175
+ -1, // areturn, 176
+ 0, // return, 177
+ 0, // getstatic, 178 depends on the type
+ 0, // putstatic, 179 depends on the type
+ 0, // getfield, 180 depends on the type
+ 0, // putfield, 181 depends on the type
+ 0, // invokevirtual, 182 depends on the type
+ 0, // invokespecial, 183 depends on the type
+ 0, // invokestatic, 184 depends on the type
+ 0, // invokeinterface, 185 depends on the type
+ 0, // undefined, 186
+ 1, // new, 187
+ 0, // newarray, 188
+ 0, // anewarray, 189
+ 0, // arraylength, 190
+ -1, // athrow, 191 stack is cleared
+ 0, // checkcast, 192
+ 0, // instanceof, 193
+ -1, // monitorenter, 194
+ -1, // monitorexit, 195
+ 0, // wide, 196 depends on the following opcode
+ 0, // multianewarray, 197 depends on the dimensions
+ -1, // ifnull, 198
+ -1, // ifnonnull, 199
+ 0, // goto_w, 200
+ 1 // jsr_w, 201
+ };
+}
diff --git a/src/main/javassist/bytecode/ParameterAnnotationsAttribute.java b/src/main/javassist/bytecode/ParameterAnnotationsAttribute.java
new file mode 100644
index 0000000..246afc1
--- /dev/null
+++ b/src/main/javassist/bytecode/ParameterAnnotationsAttribute.java
@@ -0,0 +1,214 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.io.IOException;
+import java.io.DataInputStream;
+import java.io.ByteArrayOutputStream;
+
+import javassist.bytecode.AnnotationsAttribute.Copier;
+import javassist.bytecode.AnnotationsAttribute.Parser;
+import javassist.bytecode.AnnotationsAttribute.Renamer;
+import javassist.bytecode.annotation.*;
+
+/**
+ * A class representing <code>RuntimeVisibleAnnotations_attribute</code> and
+ * <code>RuntimeInvisibleAnnotations_attribute</code>.
+ *
+ * <p>To obtain an ParameterAnnotationAttribute object, invoke
+ * <code>getAttribute(ParameterAnnotationsAttribute.invisibleTag)</code>
+ * in <code>MethodInfo</code>.
+ * The obtained attribute is a
+ * runtime invisible annotations attribute.
+ * If the parameter is
+ * <code>ParameterAnnotationAttribute.visibleTag</code>, then the obtained
+ * attribute is a runtime visible one.
+ */
+public class ParameterAnnotationsAttribute extends AttributeInfo {
+ /**
+ * The name of the <code>RuntimeVisibleParameterAnnotations</code>
+ * attribute.
+ */
+ public static final String visibleTag
+ = "RuntimeVisibleParameterAnnotations";
+
+ /**
+ * The name of the <code>RuntimeInvisibleParameterAnnotations</code>
+ * attribute.
+ */
+ public static final String invisibleTag
+ = "RuntimeInvisibleParameterAnnotations";
+ /**
+ * Constructs
+ * a <code>Runtime(In)VisibleParameterAnnotations_attribute</code>.
+ *
+ * @param cp constant pool
+ * @param attrname attribute name (<code>visibleTag</code> or
+ * <code>invisibleTag</code>).
+ * @param info the contents of this attribute. It does not
+ * include <code>attribute_name_index</code> or
+ * <code>attribute_length</code>.
+ */
+ public ParameterAnnotationsAttribute(ConstPool cp, String attrname,
+ byte[] info) {
+ super(cp, attrname, info);
+ }
+
+ /**
+ * Constructs an empty
+ * <code>Runtime(In)VisibleParameterAnnotations_attribute</code>.
+ * A new annotation can be later added to the created attribute
+ * by <code>setAnnotations()</code>.
+ *
+ * @param cp constant pool
+ * @param attrname attribute name (<code>visibleTag</code> or
+ * <code>invisibleTag</code>).
+ * @see #setAnnotations(Annotation[][])
+ */
+ public ParameterAnnotationsAttribute(ConstPool cp, String attrname) {
+ this(cp, attrname, new byte[] { 0 });
+ }
+
+ /**
+ * @param n the attribute name.
+ */
+ ParameterAnnotationsAttribute(ConstPool cp, int n, DataInputStream in)
+ throws IOException
+ {
+ super(cp, n, in);
+ }
+
+ /**
+ * Returns <code>num_parameters</code>.
+ */
+ public int numParameters() {
+ return info[0] & 0xff;
+ }
+
+ /**
+ * Copies this attribute and returns a new copy.
+ */
+ public AttributeInfo copy(ConstPool newCp, Map classnames) {
+ Copier copier = new Copier(info, constPool, newCp, classnames);
+ try {
+ copier.parameters();
+ return new ParameterAnnotationsAttribute(newCp, getName(),
+ copier.close());
+ }
+ catch (Exception e) {
+ throw new RuntimeException(e.toString());
+ }
+ }
+
+ /**
+ * Parses the annotations and returns a data structure representing
+ * that parsed annotations. Note that changes of the node values of the
+ * returned tree are not reflected on the annotations represented by
+ * this object unless the tree is copied back to this object by
+ * <code>setAnnotations()</code>.
+ *
+ * @return Each element of the returned array represents an array of
+ * annotations that are associated with each method parameter.
+ *
+ * @see #setAnnotations(Annotation[][])
+ */
+ public Annotation[][] getAnnotations() {
+ try {
+ return new Parser(info, constPool).parseParameters();
+ }
+ catch (Exception e) {
+ throw new RuntimeException(e.toString());
+ }
+ }
+
+ /**
+ * Changes the annotations represented by this object according to
+ * the given array of <code>Annotation</code> objects.
+ *
+ * @param params the data structure representing the
+ * new annotations. Every element of this array
+ * is an array of <code>Annotation</code> and
+ * it represens annotations of each method parameter.
+ */
+ public void setAnnotations(Annotation[][] params) {
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ AnnotationsWriter writer = new AnnotationsWriter(output, constPool);
+ try {
+ int n = params.length;
+ writer.numParameters(n);
+ for (int i = 0; i < n; ++i) {
+ Annotation[] anno = params[i];
+ writer.numAnnotations(anno.length);
+ for (int j = 0; j < anno.length; ++j)
+ anno[j].write(writer);
+ }
+
+ writer.close();
+ }
+ catch (IOException e) {
+ throw new RuntimeException(e); // should never reach here.
+ }
+
+ set(output.toByteArray());
+ }
+
+ /**
+ * @param oldname a JVM class name.
+ * @param newname a JVM class name.
+ */
+ void renameClass(String oldname, String newname) {
+ HashMap map = new HashMap();
+ map.put(oldname, newname);
+ renameClass(map);
+ }
+
+ void renameClass(Map classnames) {
+ Renamer renamer = new Renamer(info, getConstPool(), classnames);
+ try {
+ renamer.parameters();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ void getRefClasses(Map classnames) { renameClass(classnames); }
+
+ /**
+ * Returns a string representation of this object.
+ */
+ public String toString() {
+ Annotation[][] aa = getAnnotations();
+ StringBuilder sbuf = new StringBuilder();
+ int k = 0;
+ while (k < aa.length) {
+ Annotation[] a = aa[k++];
+ int i = 0;
+ while (i < a.length) {
+ sbuf.append(a[i++].toString());
+ if (i != a.length)
+ sbuf.append(" ");
+ }
+
+ if (k != aa.length)
+ sbuf.append(", ");
+ }
+
+ return sbuf.toString();
+
+ }
+}
diff --git a/src/main/javassist/bytecode/SignatureAttribute.java b/src/main/javassist/bytecode/SignatureAttribute.java
new file mode 100644
index 0000000..958e93f
--- /dev/null
+++ b/src/main/javassist/bytecode/SignatureAttribute.java
@@ -0,0 +1,828 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.util.Map;
+import java.util.ArrayList;
+import javassist.CtClass;
+
+/**
+ * <code>Signature_attribute</code>.
+ */
+public class SignatureAttribute extends AttributeInfo {
+ /**
+ * The name of this attribute <code>"Signature"</code>.
+ */
+ public static final String tag = "Signature";
+
+ SignatureAttribute(ConstPool cp, int n, DataInputStream in)
+ throws IOException
+ {
+ super(cp, n, in);
+ }
+
+ /**
+ * Constructs a Signature attribute.
+ *
+ * @param cp a constant pool table.
+ * @param signature the signature represented by this attribute.
+ */
+ public SignatureAttribute(ConstPool cp, String signature) {
+ super(cp, tag);
+ int index = cp.addUtf8Info(signature);
+ byte[] bvalue = new byte[2];
+ bvalue[0] = (byte)(index >>> 8);
+ bvalue[1] = (byte)index;
+ set(bvalue);
+ }
+
+ /**
+ * Returns the signature indicated by <code>signature_index</code>.
+ *
+ * @see #toClassSignature(String)
+ * @see #toMethodSignature(String)
+ */
+ public String getSignature() {
+ return getConstPool().getUtf8Info(ByteArray.readU16bit(get(), 0));
+ }
+
+ /**
+ * Sets <code>signature_index</code> to the index of the given signature,
+ * which is added to a constant pool.
+ *
+ * @param sig new signature.
+ * @since 3.11
+ */
+ public void setSignature(String sig) {
+ int index = getConstPool().addUtf8Info(sig);
+ ByteArray.write16bit(index, info, 0);
+ }
+
+ /**
+ * Makes a copy. Class names are replaced according to the
+ * given <code>Map</code> object.
+ *
+ * @param newCp the constant pool table used by the new copy.
+ * @param classnames pairs of replaced and substituted
+ * class names.
+ */
+ public AttributeInfo copy(ConstPool newCp, Map classnames) {
+ return new SignatureAttribute(newCp, getSignature());
+ }
+
+ void renameClass(String oldname, String newname) {
+ String sig = renameClass(getSignature(), oldname, newname);
+ setSignature(sig);
+ }
+
+ void renameClass(Map classnames) {
+ String sig = renameClass(getSignature(), classnames);
+ setSignature(sig);
+ }
+
+ static String renameClass(String desc, String oldname, String newname) {
+ Map map = new java.util.HashMap();
+ map.put(oldname, newname);
+ return renameClass(desc, map);
+ }
+
+ static String renameClass(String desc, Map map) {
+ if (map == null)
+ return desc;
+
+ StringBuilder newdesc = new StringBuilder();
+ int head = 0;
+ int i = 0;
+ for (;;) {
+ int j = desc.indexOf('L', i);
+ if (j < 0)
+ break;
+
+ StringBuilder nameBuf = new StringBuilder();
+ int k = j;
+ char c;
+ try {
+ while ((c = desc.charAt(++k)) != ';') {
+ nameBuf.append(c);
+ if (c == '<') {
+ while ((c = desc.charAt(++k)) != '>')
+ nameBuf.append(c);
+
+ nameBuf.append(c);
+ }
+ }
+ }
+ catch (IndexOutOfBoundsException e) { break; }
+ i = k + 1;
+ String name = nameBuf.toString();
+ String name2 = (String)map.get(name);
+ if (name2 != null) {
+ newdesc.append(desc.substring(head, j));
+ newdesc.append('L');
+ newdesc.append(name2);
+ newdesc.append(c);
+ head = i;
+ }
+ }
+
+ if (head == 0)
+ return desc;
+ else {
+ int len = desc.length();
+ if (head < len)
+ newdesc.append(desc.substring(head, len));
+
+ return newdesc.toString();
+ }
+ }
+
+ private static boolean isNamePart(int c) {
+ return c != ';' && c != '<';
+ }
+
+ static private class Cursor {
+ int position = 0;
+
+ int indexOf(String s, int ch) throws BadBytecode {
+ int i = s.indexOf(ch, position);
+ if (i < 0)
+ throw error(s);
+ else {
+ position = i + 1;
+ return i;
+ }
+ }
+ }
+
+ /**
+ * Class signature.
+ */
+ public static class ClassSignature {
+ TypeParameter[] params;
+ ClassType superClass;
+ ClassType[] interfaces;
+ ClassSignature(TypeParameter[] p, ClassType s, ClassType[] i) {
+ params = p;
+ superClass = s;
+ interfaces = i;
+ }
+
+ /**
+ * Returns the type parameters.
+ *
+ * @return a zero-length array if the type parameters are not specified.
+ */
+ public TypeParameter[] getParameters() {
+ return params;
+ }
+
+ /**
+ * Returns the super class.
+ */
+ public ClassType getSuperClass() { return superClass; }
+
+ /**
+ * Returns the super interfaces.
+ *
+ * @return a zero-length array if the super interfaces are not specified.
+ */
+ public ClassType[] getInterfaces() { return interfaces; }
+
+ /**
+ * Returns the string representation.
+ */
+ public String toString() {
+ StringBuffer sbuf = new StringBuffer();
+
+ TypeParameter.toString(sbuf, params);
+ sbuf.append(" extends ").append(superClass);
+ if (interfaces.length > 0) {
+ sbuf.append(" implements ");
+ Type.toString(sbuf, interfaces);
+ }
+
+ return sbuf.toString();
+ }
+ }
+
+ /**
+ * Method type signature.
+ */
+ public static class MethodSignature {
+ TypeParameter[] typeParams;
+ Type[] params;
+ Type retType;
+ ObjectType[] exceptions;
+
+ MethodSignature(TypeParameter[] tp, Type[] p, Type ret, ObjectType[] ex) {
+ typeParams = tp;
+ params = p;
+ retType = ret;
+ exceptions = ex;
+ }
+
+ /**
+ * Returns the formal type parameters.
+ *
+ * @return a zero-length array if the type parameters are not specified.
+ */
+ public TypeParameter[] getTypeParameters() { return typeParams; }
+
+ /**
+ * Returns the types of the formal parameters.
+ *
+ * @return a zero-length array if no formal parameter is taken.
+ */
+ public Type[] getParameterTypes() { return params; }
+
+ /**
+ * Returns the type of the returned value.
+ */
+ public Type getReturnType() { return retType; }
+
+ /**
+ * Returns the types of the exceptions that may be thrown.
+ *
+ * @return a zero-length array if exceptions are never thrown or
+ * the exception types are not parameterized types or type variables.
+ */
+ public ObjectType[] getExceptionTypes() { return exceptions; }
+
+ /**
+ * Returns the string representation.
+ */
+ public String toString() {
+ StringBuffer sbuf = new StringBuffer();
+
+ TypeParameter.toString(sbuf, typeParams);
+ sbuf.append(" (");
+ Type.toString(sbuf, params);
+ sbuf.append(") ");
+ sbuf.append(retType);
+ if (exceptions.length > 0) {
+ sbuf.append(" throws ");
+ Type.toString(sbuf, exceptions);
+ }
+
+ return sbuf.toString();
+ }
+ }
+
+ /**
+ * Formal type parameters.
+ */
+ public static class TypeParameter {
+ String name;
+ ObjectType superClass;
+ ObjectType[] superInterfaces;
+
+ TypeParameter(String sig, int nb, int ne, ObjectType sc, ObjectType[] si) {
+ name = sig.substring(nb, ne);
+ superClass = sc;
+ superInterfaces = si;
+ }
+
+ /**
+ * Returns the name of the type parameter.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns the class bound of this parameter.
+ *
+ * @return null if the class bound is not specified.
+ */
+ public ObjectType getClassBound() { return superClass; }
+
+ /**
+ * Returns the interface bound of this parameter.
+ *
+ * @return a zero-length array if the interface bound is not specified.
+ */
+ public ObjectType[] getInterfaceBound() { return superInterfaces; }
+
+ /**
+ * Returns the string representation.
+ */
+ public String toString() {
+ StringBuffer sbuf = new StringBuffer(getName());
+ if (superClass != null)
+ sbuf.append(" extends ").append(superClass.toString());
+
+ int len = superInterfaces.length;
+ if (len > 0) {
+ for (int i = 0; i < len; i++) {
+ if (i > 0 || superClass != null)
+ sbuf.append(" & ");
+ else
+ sbuf.append(" extends ");
+
+ sbuf.append(superInterfaces[i].toString());
+ }
+ }
+
+ return sbuf.toString();
+ }
+
+ static void toString(StringBuffer sbuf, TypeParameter[] tp) {
+ sbuf.append('<');
+ for (int i = 0; i < tp.length; i++) {
+ if (i > 0)
+ sbuf.append(", ");
+
+ sbuf.append(tp[i]);
+ }
+
+ sbuf.append('>');
+ }
+ }
+
+ /**
+ * Type argument.
+ */
+ public static class TypeArgument {
+ ObjectType arg;
+ char wildcard;
+
+ TypeArgument(ObjectType a, char w) {
+ arg = a;
+ wildcard = w;
+ }
+
+ /**
+ * Returns the kind of this type argument.
+ *
+ * @return <code>' '</code> (not-wildcard), <code>'*'</code> (wildcard), <code>'+'</code> (wildcard with
+ * upper bound), or <code>'-'</code> (wildcard with lower bound).
+ */
+ public char getKind() { return wildcard; }
+
+ /**
+ * Returns true if this type argument is a wildcard type
+ * such as <code>?</code>, <code>? extends String</code>, or <code>? super Integer</code>.
+ */
+ public boolean isWildcard() { return wildcard != ' '; }
+
+ /**
+ * Returns the type represented by this argument
+ * if the argument is not a wildcard type. Otherwise, this method
+ * returns the upper bound (if the kind is '+'),
+ * the lower bound (if the kind is '-'), or null (if the upper or lower
+ * bound is not specified).
+ */
+ public ObjectType getType() { return arg; }
+
+ /**
+ * Returns the string representation.
+ */
+ public String toString() {
+ if (wildcard == '*')
+ return "?";
+
+ String type = arg.toString();
+ if (wildcard == ' ')
+ return type;
+ else if (wildcard == '+')
+ return "? extends " + type;
+ else
+ return "? super " + type;
+ }
+ }
+
+ /**
+ * Primitive types and object types.
+ */
+ public static abstract class Type {
+ static void toString(StringBuffer sbuf, Type[] ts) {
+ for (int i = 0; i < ts.length; i++) {
+ if (i > 0)
+ sbuf.append(", ");
+
+ sbuf.append(ts[i]);
+ }
+ }
+ }
+
+ /**
+ * Primitive types.
+ */
+ public static class BaseType extends Type {
+ char descriptor;
+ BaseType(char c) { descriptor = c; }
+
+ /**
+ * Returns the descriptor representing this primitive type.
+ *
+ * @see javassist.bytecode.Descriptor
+ */
+ public char getDescriptor() { return descriptor; }
+
+ /**
+ * Returns the <code>CtClass</code> representing this
+ * primitive type.
+ */
+ public CtClass getCtlass() {
+ return Descriptor.toPrimitiveClass(descriptor);
+ }
+
+ /**
+ * Returns the string representation.
+ */
+ public String toString() {
+ return Descriptor.toClassName(Character.toString(descriptor));
+ }
+ }
+
+ /**
+ * Class types, array types, and type variables.
+ */
+ public static abstract class ObjectType extends Type {}
+
+ /**
+ * Class types.
+ */
+ public static class ClassType extends ObjectType {
+ String name;
+ TypeArgument[] arguments;
+
+ static ClassType make(String s, int b, int e,
+ TypeArgument[] targs, ClassType parent) {
+ if (parent == null)
+ return new ClassType(s, b, e, targs);
+ else
+ return new NestedClassType(s, b, e, targs, parent);
+ }
+
+ ClassType(String signature, int begin, int end, TypeArgument[] targs) {
+ name = signature.substring(begin, end).replace('/', '.');
+ arguments = targs;
+ }
+
+ /**
+ * Returns the class name.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns the type arguments.
+ *
+ * @return null if no type arguments are given to this class.
+ */
+ public TypeArgument[] getTypeArguments() { return arguments; }
+
+ /**
+ * If this class is a member of another class, returns the
+ * class in which this class is declared.
+ *
+ * @return null if this class is not a member of another class.
+ */
+ public ClassType getDeclaringClass() { return null; }
+
+ /**
+ * Returns the string representation.
+ */
+ public String toString() {
+ StringBuffer sbuf = new StringBuffer();
+ ClassType parent = getDeclaringClass();
+ if (parent != null)
+ sbuf.append(parent.toString()).append('.');
+
+ sbuf.append(name);
+ if (arguments != null) {
+ sbuf.append('<');
+ int n = arguments.length;
+ for (int i = 0; i < n; i++) {
+ if (i > 0)
+ sbuf.append(", ");
+
+ sbuf.append(arguments[i].toString());
+ }
+
+ sbuf.append('>');
+ }
+
+ return sbuf.toString();
+ }
+ }
+
+ /**
+ * Nested class types.
+ */
+ public static class NestedClassType extends ClassType {
+ ClassType parent;
+ NestedClassType(String s, int b, int e,
+ TypeArgument[] targs, ClassType p) {
+ super(s, b, e, targs);
+ parent = p;
+ }
+
+ /**
+ * Returns the class that declares this nested class.
+ * This nested class is a member of that declaring class.
+ */
+ public ClassType getDeclaringClass() { return parent; }
+ }
+
+ /**
+ * Array types.
+ */
+ public static class ArrayType extends ObjectType {
+ int dim;
+ Type componentType;
+
+ public ArrayType(int d, Type comp) {
+ dim = d;
+ componentType = comp;
+ }
+
+ /**
+ * Returns the dimension of the array.
+ */
+ public int getDimension() { return dim; }
+
+ /**
+ * Returns the component type.
+ */
+ public Type getComponentType() {
+ return componentType;
+ }
+
+ /**
+ * Returns the string representation.
+ */
+ public String toString() {
+ StringBuffer sbuf = new StringBuffer(componentType.toString());
+ for (int i = 0; i < dim; i++)
+ sbuf.append("[]");
+
+ return sbuf.toString();
+ }
+ }
+
+ /**
+ * Type variables.
+ */
+ public static class TypeVariable extends ObjectType {
+ String name;
+
+ TypeVariable(String sig, int begin, int end) {
+ name = sig.substring(begin, end);
+ }
+
+ /**
+ * Returns the variable name.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns the string representation.
+ */
+ public String toString() {
+ return name;
+ }
+ }
+
+ /**
+ * Parses the given signature string as a class signature.
+ *
+ * @param sig the signature.
+ * @throws BadBytecode thrown when a syntactical error is found.
+ * @since 3.5
+ */
+ public static ClassSignature toClassSignature(String sig) throws BadBytecode {
+ try {
+ return parseSig(sig);
+ }
+ catch (IndexOutOfBoundsException e) {
+ throw error(sig);
+ }
+ }
+
+ /**
+ * Parses the given signature string as a method type signature.
+ *
+ * @param sig the signature.
+ * @throws BadBytecode thrown when a syntactical error is found.
+ * @since 3.5
+ */
+ public static MethodSignature toMethodSignature(String sig) throws BadBytecode {
+ try {
+ return parseMethodSig(sig);
+ }
+ catch (IndexOutOfBoundsException e) {
+ throw error(sig);
+ }
+ }
+
+ /**
+ * Parses the given signature string as a field type signature.
+ *
+ * @param sig the signature string.
+ * @return the field type signature.
+ * @throws BadBytecode thrown when a syntactical error is found.
+ * @since 3.5
+ */
+ public static ObjectType toFieldSignature(String sig) throws BadBytecode {
+ try {
+ return parseObjectType(sig, new Cursor(), false);
+ }
+ catch (IndexOutOfBoundsException e) {
+ throw error(sig);
+ }
+ }
+
+ private static ClassSignature parseSig(String sig)
+ throws BadBytecode, IndexOutOfBoundsException
+ {
+ Cursor cur = new Cursor();
+ TypeParameter[] tp = parseTypeParams(sig, cur);
+ ClassType superClass = parseClassType(sig, cur);
+ int sigLen = sig.length();
+ ArrayList ifArray = new ArrayList();
+ while (cur.position < sigLen && sig.charAt(cur.position) == 'L')
+ ifArray.add(parseClassType(sig, cur));
+
+ ClassType[] ifs
+ = (ClassType[])ifArray.toArray(new ClassType[ifArray.size()]);
+ return new ClassSignature(tp, superClass, ifs);
+ }
+
+ private static MethodSignature parseMethodSig(String sig)
+ throws BadBytecode
+ {
+ Cursor cur = new Cursor();
+ TypeParameter[] tp = parseTypeParams(sig, cur);
+ if (sig.charAt(cur.position++) != '(')
+ throw error(sig);
+
+ ArrayList params = new ArrayList();
+ while (sig.charAt(cur.position) != ')') {
+ Type t = parseType(sig, cur);
+ params.add(t);
+ }
+
+ cur.position++;
+ Type ret = parseType(sig, cur);
+ int sigLen = sig.length();
+ ArrayList exceptions = new ArrayList();
+ while (cur.position < sigLen && sig.charAt(cur.position) == '^') {
+ cur.position++;
+ ObjectType t = parseObjectType(sig, cur, false);
+ if (t instanceof ArrayType)
+ throw error(sig);
+
+ exceptions.add(t);
+ }
+
+ Type[] p = (Type[])params.toArray(new Type[params.size()]);
+ ObjectType[] ex = (ObjectType[])exceptions.toArray(new ObjectType[exceptions.size()]);
+ return new MethodSignature(tp, p, ret, ex);
+ }
+
+ private static TypeParameter[] parseTypeParams(String sig, Cursor cur)
+ throws BadBytecode
+ {
+ ArrayList typeParam = new ArrayList();
+ if (sig.charAt(cur.position) == '<') {
+ cur.position++;
+ while (sig.charAt(cur.position) != '>') {
+ int nameBegin = cur.position;
+ int nameEnd = cur.indexOf(sig, ':');
+ ObjectType classBound = parseObjectType(sig, cur, true);
+ ArrayList ifBound = new ArrayList();
+ while (sig.charAt(cur.position) == ':') {
+ cur.position++;
+ ObjectType t = parseObjectType(sig, cur, false);
+ ifBound.add(t);
+ }
+
+ TypeParameter p = new TypeParameter(sig, nameBegin, nameEnd,
+ classBound, (ObjectType[])ifBound.toArray(new ObjectType[ifBound.size()]));
+ typeParam.add(p);
+ }
+
+ cur.position++;
+ }
+
+ return (TypeParameter[])typeParam.toArray(new TypeParameter[typeParam.size()]);
+ }
+
+ private static ObjectType parseObjectType(String sig, Cursor c, boolean dontThrow)
+ throws BadBytecode
+ {
+ int i;
+ int begin = c.position;
+ switch (sig.charAt(begin)) {
+ case 'L' :
+ return parseClassType2(sig, c, null);
+ case 'T' :
+ i = c.indexOf(sig, ';');
+ return new TypeVariable(sig, begin + 1, i);
+ case '[' :
+ return parseArray(sig, c);
+ default :
+ if (dontThrow)
+ return null;
+ else
+ throw error(sig);
+ }
+ }
+
+ private static ClassType parseClassType(String sig, Cursor c)
+ throws BadBytecode
+ {
+ if (sig.charAt(c.position) == 'L')
+ return parseClassType2(sig, c, null);
+ else
+ throw error(sig);
+ }
+
+ private static ClassType parseClassType2(String sig, Cursor c, ClassType parent)
+ throws BadBytecode
+ {
+ int start = ++c.position;
+ char t;
+ do {
+ t = sig.charAt(c.position++);
+ } while (t != '$' && t != '<' && t != ';');
+ int end = c.position - 1;
+ TypeArgument[] targs;
+ if (t == '<') {
+ targs = parseTypeArgs(sig, c);
+ t = sig.charAt(c.position++);
+ }
+ else
+ targs = null;
+
+ ClassType thisClass = ClassType.make(sig, start, end, targs, parent);
+ if (t == '$') {
+ c.position--;
+ return parseClassType2(sig, c, thisClass);
+ }
+ else
+ return thisClass;
+ }
+
+ private static TypeArgument[] parseTypeArgs(String sig, Cursor c) throws BadBytecode {
+ ArrayList args = new ArrayList();
+ char t;
+ while ((t = sig.charAt(c.position++)) != '>') {
+ TypeArgument ta;
+ if (t == '*' )
+ ta = new TypeArgument(null, '*');
+ else {
+ if (t != '+' && t != '-') {
+ t = ' ';
+ c.position--;
+ }
+
+ ta = new TypeArgument(parseObjectType(sig, c, false), t);
+ }
+
+ args.add(ta);
+ }
+
+ return (TypeArgument[])args.toArray(new TypeArgument[args.size()]);
+ }
+
+ private static ObjectType parseArray(String sig, Cursor c) throws BadBytecode {
+ int dim = 1;
+ while (sig.charAt(++c.position) == '[')
+ dim++;
+
+ return new ArrayType(dim, parseType(sig, c));
+ }
+
+ private static Type parseType(String sig, Cursor c) throws BadBytecode {
+ Type t = parseObjectType(sig, c, true);
+ if (t == null)
+ t = new BaseType(sig.charAt(c.position++));
+
+ return t;
+ }
+
+ private static BadBytecode error(String sig) {
+ return new BadBytecode("bad signature: " + sig);
+ }
+}
diff --git a/src/main/javassist/bytecode/SourceFileAttribute.java b/src/main/javassist/bytecode/SourceFileAttribute.java
new file mode 100644
index 0000000..c104eb7
--- /dev/null
+++ b/src/main/javassist/bytecode/SourceFileAttribute.java
@@ -0,0 +1,70 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * <code>SourceFile_attribute</code>.
+ */
+public class SourceFileAttribute extends AttributeInfo {
+ /**
+ * The name of this attribute <code>"SourceFile"</code>.
+ */
+ public static final String tag = "SourceFile";
+
+ SourceFileAttribute(ConstPool cp, int n, DataInputStream in)
+ throws IOException
+ {
+ super(cp, n, in);
+ }
+
+ /**
+ * Constructs a SourceFile attribute.
+ *
+ * @param cp a constant pool table.
+ * @param filename the name of the source file.
+ */
+ public SourceFileAttribute(ConstPool cp, String filename) {
+ super(cp, tag);
+ int index = cp.addUtf8Info(filename);
+ byte[] bvalue = new byte[2];
+ bvalue[0] = (byte)(index >>> 8);
+ bvalue[1] = (byte)index;
+ set(bvalue);
+ }
+
+ /**
+ * Returns the file name indicated by <code>sourcefile_index</code>.
+ */
+ public String getFileName() {
+ return getConstPool().getUtf8Info(ByteArray.readU16bit(get(), 0));
+ }
+
+ /**
+ * Makes a copy. Class names are replaced according to the
+ * given <code>Map</code> object.
+ *
+ * @param newCp the constant pool table used by the new copy.
+ * @param classnames pairs of replaced and substituted
+ * class names.
+ */
+ public AttributeInfo copy(ConstPool newCp, Map classnames) {
+ return new SourceFileAttribute(newCp, getFileName());
+ }
+}
diff --git a/src/main/javassist/bytecode/StackMap.java b/src/main/javassist/bytecode/StackMap.java
new file mode 100644
index 0000000..ac0582e
--- /dev/null
+++ b/src/main/javassist/bytecode/StackMap.java
@@ -0,0 +1,544 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.util.Map;
+
+import javassist.CannotCompileException;
+import javassist.bytecode.StackMapTable.InsertLocal;
+import javassist.bytecode.StackMapTable.NewRemover;
+import javassist.bytecode.StackMapTable.Shifter;
+
+/**
+ * Another <code>stack_map</code> attribute defined in CLDC 1.1 for J2ME.
+ *
+ * <p>This is an entry in the attributes table of a Code attribute.
+ * It was introduced by J2ME CLDC 1.1 (JSR 139) for pre-verification.
+ *
+ * <p>According to the CLDC specification, the sizes of some fields are not 16bit
+ * but 32bit if the code size is more than 64K or the number of the local variables
+ * is more than 64K. However, for the J2ME CLDC technology, they are always 16bit.
+ * The implementation of the StackMap class assumes they are 16bit.
+ *
+ * @see MethodInfo#doPreverify
+ * @see StackMapTable
+ * @since 3.12
+ */
+public class StackMap extends AttributeInfo {
+ /**
+ * The name of this attribute <code>"StackMap"</code>.
+ */
+ public static final String tag = "StackMap";
+
+
+ /**
+ * Constructs a <code>stack_map</code> attribute.
+ */
+ StackMap(ConstPool cp, byte[] newInfo) {
+ super(cp, tag, newInfo);
+ }
+
+ StackMap(ConstPool cp, int name_id, DataInputStream in)
+ throws IOException
+ {
+ super(cp, name_id, in);
+ }
+
+ /**
+ * Returns <code>number_of_entries</code>.
+ */
+ public int numOfEntries() {
+ return ByteArray.readU16bit(info, 0);
+ }
+
+ /**
+ * <code>Top_variable_info.tag</code>.
+ */
+ public static final int TOP = 0;
+
+ /**
+ * <code>Integer_variable_info.tag</code>.
+ */
+ public static final int INTEGER = 1;
+
+ /**
+ * <code>Float_variable_info.tag</code>.
+ */
+ public static final int FLOAT = 2;
+
+ /**
+ * <code>Double_variable_info.tag</code>.
+ */
+ public static final int DOUBLE = 3;
+
+ /**
+ * <code>Long_variable_info.tag</code>.
+ */
+ public static final int LONG = 4;
+
+ /**
+ * <code>Null_variable_info.tag</code>.
+ */
+ public static final int NULL = 5;
+
+ /**
+ * <code>UninitializedThis_variable_info.tag</code>.
+ */
+ public static final int THIS = 6;
+
+ /**
+ * <code>Object_variable_info.tag</code>.
+ */
+ public static final int OBJECT = 7;
+
+ /**
+ * <code>Uninitialized_variable_info.tag</code>.
+ */
+ public static final int UNINIT = 8;
+
+ /**
+ * Makes a copy.
+ */
+ public AttributeInfo copy(ConstPool newCp, Map classnames) {
+ Copier copier = new Copier(this, newCp, classnames);
+ copier.visit();
+ return copier.getStackMap();
+ }
+
+ /**
+ * A code walker for a StackMap attribute.
+ */
+ public static class Walker {
+ byte[] info;
+
+ /**
+ * Constructs a walker.
+ */
+ public Walker(StackMap sm) {
+ info = sm.get();
+ }
+
+ /**
+ * Visits each entry of the stack map frames.
+ */
+ public void visit() {
+ int num = ByteArray.readU16bit(info, 0);
+ int pos = 2;
+ for (int i = 0; i < num; i++) {
+ int offset = ByteArray.readU16bit(info, pos);
+ int numLoc = ByteArray.readU16bit(info, pos + 2);
+ pos = locals(pos + 4, offset, numLoc);
+ int numStack = ByteArray.readU16bit(info, pos);
+ pos = stack(pos + 2, offset, numStack);
+ }
+ }
+
+ /**
+ * Invoked when <code>locals</code> of <code>stack_map_frame</code>
+ * is visited.
+ */
+ public int locals(int pos, int offset, int num) {
+ return typeInfoArray(pos, offset, num, true);
+ }
+
+ /**
+ * Invoked when <code>stack</code> of <code>stack_map_frame</code>
+ * is visited.
+ */
+ public int stack(int pos, int offset, int num) {
+ return typeInfoArray(pos, offset, num, false);
+ }
+
+ /**
+ * Invoked when an array of <code>verification_type_info</code> is
+ * visited.
+ *
+ * @param num the number of elements.
+ * @param isLocals true if this array is for <code>locals</code>.
+ * false if it is for <code>stack</code>.
+ */
+ public int typeInfoArray(int pos, int offset, int num, boolean isLocals) {
+ for (int k = 0; k < num; k++)
+ pos = typeInfoArray2(k, pos);
+
+ return pos;
+ }
+
+ int typeInfoArray2(int k, int pos) {
+ byte tag = info[pos];
+ if (tag == OBJECT) {
+ int clazz = ByteArray.readU16bit(info, pos + 1);
+ objectVariable(pos, clazz);
+ pos += 3;
+ }
+ else if (tag == UNINIT) {
+ int offsetOfNew = ByteArray.readU16bit(info, pos + 1);
+ uninitialized(pos, offsetOfNew);
+ pos += 3;
+ }
+ else {
+ typeInfo(pos, tag);
+ pos++;
+ }
+
+ return pos;
+ }
+
+ /**
+ * Invoked when an element of <code>verification_type_info</code>
+ * (except <code>Object_variable_info</code> and
+ * <code>Uninitialized_variable_info</code>) is visited.
+ */
+ public void typeInfo(int pos, byte tag) {}
+
+ /**
+ * Invoked when an element of type <code>Object_variable_info</code>
+ * is visited.
+ */
+ public void objectVariable(int pos, int clazz) {}
+
+ /**
+ * Invoked when an element of type <code>Uninitialized_variable_info</code>
+ * is visited.
+ */
+ public void uninitialized(int pos, int offset) {}
+ }
+
+ static class Copier extends Walker {
+ byte[] dest;
+ ConstPool srcCp, destCp;
+ Map classnames;
+
+ Copier(StackMap map, ConstPool newCp, Map classnames) {
+ super(map);
+ srcCp = map.getConstPool();
+ dest = new byte[info.length];
+ destCp = newCp;
+ this.classnames = classnames;
+ }
+
+ public void visit() {
+ int num = ByteArray.readU16bit(info, 0);
+ ByteArray.write16bit(num, dest, 0);
+ super.visit();
+ }
+
+ public int locals(int pos, int offset, int num) {
+ ByteArray.write16bit(offset, dest, pos - 4);
+ return super.locals(pos, offset, num);
+ }
+
+ public int typeInfoArray(int pos, int offset, int num, boolean isLocals) {
+ ByteArray.write16bit(num, dest, pos - 2);
+ return super.typeInfoArray(pos, offset, num, isLocals);
+ }
+
+ public void typeInfo(int pos, byte tag) {
+ dest[pos] = tag;
+ }
+
+ public void objectVariable(int pos, int clazz) {
+ dest[pos] = OBJECT;
+ int newClazz = srcCp.copy(clazz, destCp, classnames);
+ ByteArray.write16bit(newClazz, dest, pos + 1);
+ }
+
+ public void uninitialized(int pos, int offset) {
+ dest[pos] = UNINIT;
+ ByteArray.write16bit(offset, dest, pos + 1);
+ }
+
+ public StackMap getStackMap() {
+ return new StackMap(destCp, dest);
+ }
+ }
+
+ /**
+ * Updates this stack map table when a new local variable is inserted
+ * for a new parameter.
+ *
+ * @param index the index of the added local variable.
+ * @param tag the type tag of that local variable.
+ * It is available by <code>StackMapTable.typeTagOf(char)</code>.
+ * @param classInfo the index of the <code>CONSTANT_Class_info</code> structure
+ * in a constant pool table. This should be zero unless the tag
+ * is <code>ITEM_Object</code>.
+ *
+ * @see javassist.CtBehavior#addParameter(javassist.CtClass)
+ * @see StackMapTable#typeTagOf(char)
+ * @see ConstPool
+ */
+ public void insertLocal(int index, int tag, int classInfo)
+ throws BadBytecode
+ {
+ byte[] data = new InsertLocal(this, index, tag, classInfo).doit();
+ this.set(data);
+ }
+
+ static class SimpleCopy extends Walker {
+ Writer writer;
+
+ SimpleCopy(StackMap map) {
+ super(map);
+ writer = new Writer();
+ }
+
+ byte[] doit() {
+ visit();
+ return writer.toByteArray();
+ }
+
+ public void visit() {
+ int num = ByteArray.readU16bit(info, 0);
+ writer.write16bit(num);
+ super.visit();
+ }
+
+ public int locals(int pos, int offset, int num) {
+ writer.write16bit(offset);
+ return super.locals(pos, offset, num);
+ }
+
+ public int typeInfoArray(int pos, int offset, int num, boolean isLocals) {
+ writer.write16bit(num);
+ return super.typeInfoArray(pos, offset, num, isLocals);
+ }
+
+ public void typeInfo(int pos, byte tag) {
+ writer.writeVerifyTypeInfo(tag, 0);
+ }
+
+ public void objectVariable(int pos, int clazz) {
+ writer.writeVerifyTypeInfo(OBJECT, clazz);
+ }
+
+ public void uninitialized(int pos, int offset) {
+ writer.writeVerifyTypeInfo(UNINIT, offset);
+ }
+ }
+
+ static class InsertLocal extends SimpleCopy {
+ private int varIndex;
+ private int varTag, varData;
+
+ InsertLocal(StackMap map, int varIndex, int varTag, int varData) {
+ super(map);
+ this.varIndex = varIndex;
+ this.varTag = varTag;
+ this.varData = varData;
+ }
+
+ public int typeInfoArray(int pos, int offset, int num, boolean isLocals) {
+ if (!isLocals || num < varIndex)
+ return super.typeInfoArray(pos, offset, num, isLocals);
+
+ writer.write16bit(num + 1);
+ for (int k = 0; k < num; k++) {
+ if (k == varIndex)
+ writeVarTypeInfo();
+
+ pos = typeInfoArray2(k, pos);
+ }
+
+ if (num == varIndex)
+ writeVarTypeInfo();
+
+ return pos;
+ }
+
+ private void writeVarTypeInfo() {
+ if (varTag == OBJECT)
+ writer.writeVerifyTypeInfo(OBJECT, varData);
+ else if (varTag == UNINIT)
+ writer.writeVerifyTypeInfo(UNINIT, varData);
+ else
+ writer.writeVerifyTypeInfo(varTag, 0);
+ }
+ }
+
+ void shiftPc(int where, int gapSize, boolean exclusive)
+ throws BadBytecode
+ {
+ new Shifter(this, where, gapSize, exclusive).visit();
+ }
+
+ static class Shifter extends Walker {
+ private int where, gap;
+ private boolean exclusive;
+
+ public Shifter(StackMap smt, int where, int gap, boolean exclusive) {
+ super(smt);
+ this.where = where;
+ this.gap = gap;
+ this.exclusive = exclusive;
+ }
+
+ public int locals(int pos, int offset, int num) {
+ if (exclusive ? where <= offset : where < offset)
+ ByteArray.write16bit(offset + gap, info, pos - 4);
+
+ return super.locals(pos, offset, num);
+ }
+ }
+
+ /**
+ * Undocumented method. Do not use; internal-use only.
+ *
+ * <p>This method is for javassist.convert.TransformNew.
+ * It is called to update the stack map when
+ * the NEW opcode (and the following DUP) is removed.
+ *
+ * @param where the position of the removed NEW opcode.
+ */
+ public void removeNew(int where) throws CannotCompileException {
+ byte[] data = new NewRemover(this, where).doit();
+ this.set(data);
+ }
+
+ static class NewRemover extends SimpleCopy {
+ int posOfNew;
+
+ NewRemover(StackMap map, int where) {
+ super(map);
+ posOfNew = where;
+ }
+
+ public int stack(int pos, int offset, int num) {
+ return stackTypeInfoArray(pos, offset, num);
+ }
+
+ private int stackTypeInfoArray(int pos, int offset, int num) {
+ int p = pos;
+ int count = 0;
+ for (int k = 0; k < num; k++) {
+ byte tag = info[p];
+ if (tag == OBJECT)
+ p += 3;
+ else if (tag == UNINIT) {
+ int offsetOfNew = ByteArray.readU16bit(info, p + 1);
+ if (offsetOfNew == posOfNew)
+ count++;
+
+ p += 3;
+ }
+ else
+ p++;
+ }
+
+ writer.write16bit(num - count);
+ for (int k = 0; k < num; k++) {
+ byte tag = info[pos];
+ if (tag == OBJECT) {
+ int clazz = ByteArray.readU16bit(info, pos + 1);
+ objectVariable(pos, clazz);
+ pos += 3;
+ }
+ else if (tag == UNINIT) {
+ int offsetOfNew = ByteArray.readU16bit(info, pos + 1);
+ if (offsetOfNew != posOfNew)
+ uninitialized(pos, offsetOfNew);
+
+ pos += 3;
+ }
+ else {
+ typeInfo(pos, tag);
+ pos++;
+ }
+ }
+
+ return pos;
+ }
+ }
+
+ /**
+ * Prints this stack map.
+ */
+ public void print(java.io.PrintWriter out) {
+ new Printer(this, out).print();
+ }
+
+ static class Printer extends Walker {
+ private java.io.PrintWriter writer;
+
+ public Printer(StackMap map, java.io.PrintWriter out) {
+ super(map);
+ writer = out;
+ }
+
+ public void print() {
+ int num = ByteArray.readU16bit(info, 0);
+ writer.println(num + " entries");
+ visit();
+ }
+
+ public int locals(int pos, int offset, int num) {
+ writer.println(" * offset " + offset);
+ return super.locals(pos, offset, num);
+ }
+ }
+
+ /**
+ * Internal use only.
+ */
+ public static class Writer {
+ // see javassist.bytecode.stackmap.MapMaker
+
+ private ByteArrayOutputStream output;
+
+ /**
+ * Constructs a writer.
+ */
+ public Writer() {
+ output = new ByteArrayOutputStream();
+ }
+
+ /**
+ * Converts the written data into a byte array.
+ */
+ public byte[] toByteArray() {
+ return output.toByteArray();
+ }
+
+ /**
+ * Converts to a <code>StackMap</code> attribute.
+ */
+ public StackMap toStackMap(ConstPool cp) {
+ return new StackMap(cp, output.toByteArray());
+ }
+
+ /**
+ * Writes a <code>union verification_type_info</code> value.
+ *
+ * @param data <code>cpool_index</code> or <code>offset</code>.
+ */
+ public void writeVerifyTypeInfo(int tag, int data) {
+ output.write(tag);
+ if (tag == StackMap.OBJECT || tag == StackMap.UNINIT)
+ write16bit(data);
+ }
+
+ /**
+ * Writes a 16bit value.
+ */
+ public void write16bit(int value) {
+ output.write((value >>> 8) & 0xff);
+ output.write(value & 0xff);
+ }
+ }
+}
diff --git a/src/main/javassist/bytecode/StackMapTable.java b/src/main/javassist/bytecode/StackMapTable.java
new file mode 100644
index 0000000..f3cea6f
--- /dev/null
+++ b/src/main/javassist/bytecode/StackMapTable.java
@@ -0,0 +1,949 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintWriter;
+import java.io.IOException;
+import java.util.Map;
+import javassist.CannotCompileException;
+
+/**
+ * <code>stack_map</code> attribute.
+ *
+ * <p>This is an entry in the attributes table of a Code attribute.
+ * It was introduced by J2SE 6 for the process of verification by
+ * typechecking.
+ *
+ * @see StackMap
+ * @since 3.4
+ */
+public class StackMapTable extends AttributeInfo {
+ /**
+ * The name of this attribute <code>"StackMapTable"</code>.
+ */
+ public static final String tag = "StackMapTable";
+
+ /**
+ * Constructs a <code>stack_map</code> attribute.
+ */
+ StackMapTable(ConstPool cp, byte[] newInfo) {
+ super(cp, tag, newInfo);
+ }
+
+ StackMapTable(ConstPool cp, int name_id, DataInputStream in)
+ throws IOException
+ {
+ super(cp, name_id, in);
+ }
+
+ /**
+ * Makes a copy.
+ *
+ * @exception RuntimeCopyException if a <code>BadBytecode</code>
+ * exception is thrown while copying,
+ * it is converted into
+ * <code>RuntimeCopyException</code>.
+ *
+ */
+ public AttributeInfo copy(ConstPool newCp, Map classnames)
+ throws RuntimeCopyException
+ {
+ try {
+ return new StackMapTable(newCp,
+ new Copier(this.constPool, info, newCp).doit());
+ }
+ catch (BadBytecode e) {
+ throw new RuntimeCopyException("bad bytecode. fatal?");
+ }
+ }
+
+ /**
+ * An exception that may be thrown by <code>copy()</code>
+ * in <code>StackMapTable</code>.
+ */
+ public static class RuntimeCopyException extends RuntimeException {
+ /**
+ * Constructs an exception.
+ */
+ public RuntimeCopyException(String s) {
+ super(s);
+ }
+ }
+
+ void write(DataOutputStream out) throws IOException {
+ super.write(out);
+ }
+
+ /**
+ * <code>Top_variable_info.tag</code>.
+ */
+ public static final int TOP = 0;
+
+ /**
+ * <code>Integer_variable_info.tag</code>.
+ */
+ public static final int INTEGER = 1;
+
+ /**
+ * <code>Float_variable_info.tag</code>.
+ */
+ public static final int FLOAT = 2;
+
+ /**
+ * <code>Double_variable_info.tag</code>.
+ */
+ public static final int DOUBLE = 3;
+
+ /**
+ * <code>Long_variable_info.tag</code>.
+ */
+ public static final int LONG = 4;
+
+ /**
+ * <code>Null_variable_info.tag</code>.
+ */
+ public static final int NULL = 5;
+
+ /**
+ * <code>UninitializedThis_variable_info.tag</code>.
+ */
+ public static final int THIS = 6;
+
+ /**
+ * <code>Object_variable_info.tag</code>.
+ */
+ public static final int OBJECT = 7;
+
+ /**
+ * <code>Uninitialized_variable_info.tag</code>.
+ */
+ public static final int UNINIT = 8;
+
+ /**
+ * A code walker for a StackMapTable attribute.
+ */
+ public static class Walker {
+ byte[] info;
+ int numOfEntries;
+
+ /**
+ * Constructs a walker.
+ *
+ * @param smt the StackMapTable that this walker
+ * walks around.
+ */
+ public Walker(StackMapTable smt) {
+ this(smt.get());
+ }
+
+ /**
+ * Constructs a walker.
+ *
+ * @param data the <code>info</code> field of the
+ * <code>attribute_info</code> structure.
+ * It can be obtained by <code>get()</code>
+ * in the <code>AttributeInfo</code> class.
+ */
+ public Walker(byte[] data) {
+ info = data;
+ numOfEntries = ByteArray.readU16bit(data, 0);
+ }
+
+ /**
+ * Returns the number of the entries.
+ */
+ public final int size() { return numOfEntries; }
+
+ /**
+ * Visits each entry of the stack map frames.
+ */
+ public void parse() throws BadBytecode {
+ int n = numOfEntries;
+ int pos = 2;
+ for (int i = 0; i < n; i++)
+ pos = stackMapFrames(pos, i);
+ }
+
+ /**
+ * Invoked when the next entry of the stack map frames is visited.
+ *
+ * @param pos the position of the frame in the <code>info</code>
+ * field of <code>attribute_info</code> structure.
+ * @param nth the frame is the N-th
+ * (0, 1st, 2nd, 3rd, 4th, ...) entry.
+ * @return the position of the next frame.
+ */
+ int stackMapFrames(int pos, int nth) throws BadBytecode {
+ int type = info[pos] & 0xff;
+ if (type < 64) {
+ sameFrame(pos, type);
+ pos++;
+ }
+ else if (type < 128)
+ pos = sameLocals(pos, type);
+ else if (type < 247)
+ throw new BadBytecode("bad frame_type in StackMapTable");
+ else if (type == 247) // SAME_LOCALS_1_STACK_ITEM_EXTENDED
+ pos = sameLocals(pos, type);
+ else if (type < 251) {
+ int offset = ByteArray.readU16bit(info, pos + 1);
+ chopFrame(pos, offset, 251 - type);
+ pos += 3;
+ }
+ else if (type == 251) { // SAME_FRAME_EXTENDED
+ int offset = ByteArray.readU16bit(info, pos + 1);
+ sameFrame(pos, offset);
+ pos += 3;
+ }
+ else if (type < 255)
+ pos = appendFrame(pos, type);
+ else // FULL_FRAME
+ pos = fullFrame(pos);
+
+ return pos;
+ }
+
+ /**
+ * Invoked if the visited frame is a <code>same_frame</code> or
+ * a <code>same_frame_extended</code>.
+ *
+ * @param pos the position of this frame in the <code>info</code>
+ * field of <code>attribute_info</code> structure.
+ * @param offsetDelta
+ */
+ public void sameFrame(int pos, int offsetDelta) throws BadBytecode {}
+
+ private int sameLocals(int pos, int type) throws BadBytecode {
+ int top = pos;
+ int offset;
+ if (type < 128)
+ offset = type - 64;
+ else { // type == 247
+ offset = ByteArray.readU16bit(info, pos + 1);
+ pos += 2;
+ }
+
+ int tag = info[pos + 1] & 0xff;
+ int data = 0;
+ if (tag == OBJECT || tag == UNINIT) {
+ data = ByteArray.readU16bit(info, pos + 2);
+ pos += 2;
+ }
+
+ sameLocals(top, offset, tag, data);
+ return pos + 2;
+ }
+
+ /**
+ * Invoked if the visited frame is a <code>same_locals_1_stack_item_frame</code>
+ * or a <code>same_locals_1_stack_item_frame_extended</code>.
+ *
+ * @param pos the position.
+ * @param offsetDelta
+ * @param stackTag <code>stack[0].tag</code>.
+ * @param stackData <code>stack[0].cpool_index</code>
+ * if the tag is <code>OBJECT</code>,
+ * or <code>stack[0].offset</code>
+ * if the tag is <code>UNINIT</code>.
+ */
+ public void sameLocals(int pos, int offsetDelta, int stackTag, int stackData)
+ throws BadBytecode {}
+
+ /**
+ * Invoked if the visited frame is a <code>chop_frame</code>.
+ *
+ * @param pos the position.
+ * @param offsetDelta
+ * @param k the <cod>k</code> last locals are absent.
+ */
+ public void chopFrame(int pos, int offsetDelta, int k) throws BadBytecode {}
+
+ private int appendFrame(int pos, int type) throws BadBytecode {
+ int k = type - 251;
+ int offset = ByteArray.readU16bit(info, pos + 1);
+ int[] tags = new int[k];
+ int[] data = new int[k];
+ int p = pos + 3;
+ for (int i = 0; i < k; i++) {
+ int tag = info[p] & 0xff;
+ tags[i] = tag;
+ if (tag == OBJECT || tag == UNINIT) {
+ data[i] = ByteArray.readU16bit(info, p + 1);
+ p += 3;
+ }
+ else {
+ data[i] = 0;
+ p++;
+ }
+ }
+
+ appendFrame(pos, offset, tags, data);
+ return p;
+ }
+
+ /**
+ * Invoked if the visited frame is a <code>append_frame</code>.
+ *
+ * @param pos the position.
+ * @param offsetDelta
+ * @param tags <code>locals[i].tag</code>.
+ * @param data <code>locals[i].cpool_index</code>
+ * or <cod>locals[i].offset</code>.
+ */
+ public void appendFrame(int pos, int offsetDelta, int[] tags, int[] data)
+ throws BadBytecode {}
+
+ private int fullFrame(int pos) throws BadBytecode {
+ int offset = ByteArray.readU16bit(info, pos + 1);
+ int numOfLocals = ByteArray.readU16bit(info, pos + 3);
+ int[] localsTags = new int[numOfLocals];
+ int[] localsData = new int[numOfLocals];
+ int p = verifyTypeInfo(pos + 5, numOfLocals, localsTags, localsData);
+ int numOfItems = ByteArray.readU16bit(info, p);
+ int[] itemsTags = new int[numOfItems];
+ int[] itemsData = new int[numOfItems];
+ p = verifyTypeInfo(p + 2, numOfItems, itemsTags, itemsData);
+ fullFrame(pos, offset, localsTags, localsData, itemsTags, itemsData);
+ return p;
+ }
+
+ /**
+ * Invoked if the visited frame is <code>full_frame</code>.
+ *
+ * @param pos the position.
+ * @param offsetDelta
+ * @param localTags <code>locals[i].tag</code>
+ * @param localData <code>locals[i].cpool_index</code>
+ * or <code>locals[i].offset</code>
+ * @param stackTags <code>stack[i].tag</code>
+ * @param stackData <code>stack[i].cpool_index</code>
+ * or <code>stack[i].offset</code>
+ */
+ public void fullFrame(int pos, int offsetDelta, int[] localTags, int[] localData,
+ int[] stackTags, int[] stackData)
+ throws BadBytecode {}
+
+ private int verifyTypeInfo(int pos, int n, int[] tags, int[] data) {
+ for (int i = 0; i < n; i++) {
+ int tag = info[pos++] & 0xff;
+ tags[i] = tag;
+ if (tag == OBJECT || tag == UNINIT) {
+ data[i] = ByteArray.readU16bit(info, pos);
+ pos += 2;
+ }
+ }
+
+ return pos;
+ }
+ }
+
+ static class SimpleCopy extends Walker {
+ private Writer writer;
+
+ public SimpleCopy(byte[] data) {
+ super(data);
+ writer = new Writer(data.length);
+ }
+
+ public byte[] doit() throws BadBytecode {
+ parse();
+ return writer.toByteArray();
+ }
+
+ public void sameFrame(int pos, int offsetDelta) {
+ writer.sameFrame(offsetDelta);
+ }
+
+ public void sameLocals(int pos, int offsetDelta, int stackTag, int stackData) {
+ writer.sameLocals(offsetDelta, stackTag, copyData(stackTag, stackData));
+ }
+
+ public void chopFrame(int pos, int offsetDelta, int k) {
+ writer.chopFrame(offsetDelta, k);
+ }
+
+ public void appendFrame(int pos, int offsetDelta, int[] tags, int[] data) {
+ writer.appendFrame(offsetDelta, tags, copyData(tags, data));
+ }
+
+ public void fullFrame(int pos, int offsetDelta, int[] localTags, int[] localData,
+ int[] stackTags, int[] stackData) {
+ writer.fullFrame(offsetDelta, localTags, copyData(localTags, localData),
+ stackTags, copyData(stackTags, stackData));
+ }
+
+ protected int copyData(int tag, int data) {
+ return data;
+ }
+
+ protected int[] copyData(int[] tags, int[] data) {
+ return data;
+ }
+ }
+
+ static class Copier extends SimpleCopy {
+ private ConstPool srcPool, destPool;
+
+ public Copier(ConstPool src, byte[] data, ConstPool dest) {
+ super(data);
+ srcPool = src;
+ destPool = dest;
+ }
+
+ protected int copyData(int tag, int data) {
+ if (tag == OBJECT)
+ return srcPool.copy(data, destPool, null);
+ else
+ return data;
+ }
+
+ protected int[] copyData(int[] tags, int[] data) {
+ int[] newData = new int[data.length];
+ for (int i = 0; i < data.length; i++)
+ if (tags[i] == OBJECT)
+ newData[i] = srcPool.copy(data[i], destPool, null);
+ else
+ newData[i] = data[i];
+
+ return newData;
+ }
+ }
+
+ /**
+ * Updates this stack map table when a new local variable is inserted
+ * for a new parameter.
+ *
+ * @param index the index of the added local variable.
+ * @param tag the type tag of that local variable.
+ * @param classInfo the index of the <code>CONSTANT_Class_info</code> structure
+ * in a constant pool table. This should be zero unless the tag
+ * is <code>ITEM_Object</code>.
+ *
+ * @see javassist.CtBehavior#addParameter(javassist.CtClass)
+ * @see #typeTagOf(char)
+ * @see ConstPool
+ */
+ public void insertLocal(int index, int tag, int classInfo)
+ throws BadBytecode
+ {
+ byte[] data = new InsertLocal(this.get(), index, tag, classInfo).doit();
+ this.set(data);
+ }
+
+ /**
+ * Returns the tag of the type specified by the
+ * descriptor. This method returns <code>INTEGER</code>
+ * unless the descriptor is either D (double), F (float),
+ * J (long), L (class type), or [ (array).
+ *
+ * @param descriptor the type descriptor.
+ * @see Descriptor
+ */
+ public static int typeTagOf(char descriptor) {
+ switch (descriptor) {
+ case 'D' :
+ return DOUBLE;
+ case 'F' :
+ return FLOAT;
+ case 'J' :
+ return LONG;
+ case 'L' :
+ case '[' :
+ return OBJECT;
+ // case 'V' :
+ default :
+ return INTEGER;
+ }
+ }
+
+ /* This implementation assumes that a local variable initially
+ * holding a parameter value is never changed to be a different
+ * type.
+ *
+ */
+ static class InsertLocal extends SimpleCopy {
+ private int varIndex;
+ private int varTag, varData;
+
+ public InsertLocal(byte[] data, int varIndex, int varTag, int varData) {
+ super(data);
+ this.varIndex = varIndex;
+ this.varTag = varTag;
+ this.varData = varData;
+ }
+
+ public void fullFrame(int pos, int offsetDelta, int[] localTags, int[] localData,
+ int[] stackTags, int[] stackData) {
+ int len = localTags.length;
+ if (len < varIndex) {
+ super.fullFrame(pos, offsetDelta, localTags, localData, stackTags, stackData);
+ return;
+ }
+
+ int typeSize = (varTag == LONG || varTag == DOUBLE) ? 2 : 1;
+ int[] localTags2 = new int[len + typeSize];
+ int[] localData2 = new int[len + typeSize];
+ int index = varIndex;
+ int j = 0;
+ for (int i = 0; i < len; i++) {
+ if (j == index)
+ j += typeSize;
+
+ localTags2[j] = localTags[i];
+ localData2[j++] = localData[i];
+ }
+
+ localTags2[index] = varTag;
+ localData2[index] = varData;
+ if (typeSize > 1) {
+ localTags2[index + 1] = TOP;
+ localData2[index + 1] = 0;
+ }
+
+ super.fullFrame(pos, offsetDelta, localTags2, localData2, stackTags, stackData);
+ }
+ }
+
+ /**
+ * A writer of stack map tables.
+ */
+ public static class Writer {
+ ByteArrayOutputStream output;
+ int numOfEntries;
+
+ /**
+ * Constructs a writer.
+ * @param size the initial buffer size.
+ */
+ public Writer(int size) {
+ output = new ByteArrayOutputStream(size);
+ numOfEntries = 0;
+ output.write(0); // u2 number_of_entries
+ output.write(0);
+ }
+
+ /**
+ * Returns the stack map table written out.
+ */
+ public byte[] toByteArray() {
+ byte[] b = output.toByteArray();
+ ByteArray.write16bit(numOfEntries, b, 0);
+ return b;
+ }
+
+ /**
+ * Constructs and a return a stack map table containing
+ * the written stack map entries.
+ *
+ * @param cp the constant pool used to write
+ * the stack map entries.
+ */
+ public StackMapTable toStackMapTable(ConstPool cp) {
+ return new StackMapTable(cp, toByteArray());
+ }
+
+ /**
+ * Writes a <code>same_frame</code> or a <code>same_frame_extended</code>.
+ */
+ public void sameFrame(int offsetDelta) {
+ numOfEntries++;
+ if (offsetDelta < 64)
+ output.write(offsetDelta);
+ else {
+ output.write(251); // SAME_FRAME_EXTENDED
+ write16(offsetDelta);
+ }
+ }
+
+ /**
+ * Writes a <code>same_locals_1_stack_item</code>
+ * or a <code>same_locals_1_stack_item_extended</code>.
+ *
+ * @param tag <code>stack[0].tag</code>.
+ * @param data <code>stack[0].cpool_index</code>
+ * if the tag is <code>OBJECT</code>,
+ * or <cod>stack[0].offset</code>
+ * if the tag is <code>UNINIT</code>.
+ * Otherwise, this parameter is not used.
+ */
+ public void sameLocals(int offsetDelta, int tag, int data) {
+ numOfEntries++;
+ if (offsetDelta < 64)
+ output.write(offsetDelta + 64);
+ else {
+ output.write(247); // SAME_LOCALS_1_STACK_ITEM_EXTENDED
+ write16(offsetDelta);
+ }
+
+ writeTypeInfo(tag, data);
+ }
+
+ /**
+ * Writes a <code>chop_frame</code>.
+ *
+ * @param k the number of absent locals. 1, 2, or 3.
+ */
+ public void chopFrame(int offsetDelta, int k) {
+ numOfEntries++;
+ output.write(251 - k);
+ write16(offsetDelta);
+ }
+
+ /**
+ * Writes a <code>append_frame</code>. The number of the appended
+ * locals is specified by the length of <code>tags</code>.
+ *
+ * @param tags <code>locals[].tag</code>.
+ * The length of this array must be
+ * either 1, 2, or 3.
+ * @param data <code>locals[].cpool_index</code>
+ * if the tag is <code>OBJECT</code>,
+ * or <cod>locals[].offset</code>
+ * if the tag is <code>UNINIT</code>.
+ * Otherwise, this parameter is not used.
+ */
+ public void appendFrame(int offsetDelta, int[] tags, int[] data) {
+ numOfEntries++;
+ int k = tags.length; // k is 1, 2, or 3
+ output.write(k + 251);
+ write16(offsetDelta);
+ for (int i = 0; i < k; i++)
+ writeTypeInfo(tags[i], data[i]);
+ }
+
+ /**
+ * Writes a <code>full_frame</code>.
+ * <code>number_of_locals</code> and <code>number_of_stack_items</code>
+ * are specified by the the length of <code>localTags</code> and
+ * <code>stackTags</code>.
+ *
+ * @param localTags <code>locals[].tag</code>.
+ * @param localData <code>locals[].cpool_index</code>
+ * if the tag is <code>OBJECT</code>,
+ * or <cod>locals[].offset</code>
+ * if the tag is <code>UNINIT</code>.
+ * Otherwise, this parameter is not used.
+ * @param stackTags <code>stack[].tag</code>.
+ * @param stackData <code>stack[].cpool_index</code>
+ * if the tag is <code>OBJECT</code>,
+ * or <cod>stack[].offset</code>
+ * if the tag is <code>UNINIT</code>.
+ * Otherwise, this parameter is not used.
+ */
+ public void fullFrame(int offsetDelta, int[] localTags, int[] localData,
+ int[] stackTags, int[] stackData) {
+ numOfEntries++;
+ output.write(255); // FULL_FRAME
+ write16(offsetDelta);
+ int n = localTags.length;
+ write16(n);
+ for (int i = 0; i < n; i++)
+ writeTypeInfo(localTags[i], localData[i]);
+
+ n = stackTags.length;
+ write16(n);
+ for (int i = 0; i < n; i++)
+ writeTypeInfo(stackTags[i], stackData[i]);
+ }
+
+ private void writeTypeInfo(int tag, int data) {
+ output.write(tag);
+ if (tag == OBJECT || tag == UNINIT)
+ write16(data);
+ }
+
+ private void write16(int value) {
+ output.write((value >>> 8) & 0xff);
+ output.write(value & 0xff);
+ }
+ }
+
+ /**
+ * Prints the stack table map.
+ */
+ public void println(PrintWriter w) {
+ Printer.print(this, w);
+ }
+
+ /**
+ * Prints the stack table map.
+ *
+ * @param ps a print stream such as <code>System.out</code>.
+ */
+ public void println(java.io.PrintStream ps) {
+ Printer.print(this, new java.io.PrintWriter(ps, true));
+ }
+
+ static class Printer extends Walker {
+ private PrintWriter writer;
+ private int offset;
+
+ /**
+ * Prints the stack table map.
+ */
+ public static void print(StackMapTable smt, PrintWriter writer) {
+ try {
+ new Printer(smt.get(), writer).parse();
+ }
+ catch (BadBytecode e) {
+ writer.println(e.getMessage());
+ }
+ }
+
+ Printer(byte[] data, PrintWriter pw) {
+ super(data);
+ writer = pw;
+ offset = -1;
+ }
+
+ public void sameFrame(int pos, int offsetDelta) {
+ offset += offsetDelta + 1;
+ writer.println(offset + " same frame: " + offsetDelta);
+ }
+
+ public void sameLocals(int pos, int offsetDelta, int stackTag, int stackData) {
+ offset += offsetDelta + 1;
+ writer.println(offset + " same locals: " + offsetDelta);
+ printTypeInfo(stackTag, stackData);
+ }
+
+ public void chopFrame(int pos, int offsetDelta, int k) {
+ offset += offsetDelta + 1;
+ writer.println(offset + " chop frame: " + offsetDelta + ", " + k + " last locals");
+ }
+
+ public void appendFrame(int pos, int offsetDelta, int[] tags, int[] data) {
+ offset += offsetDelta + 1;
+ writer.println(offset + " append frame: " + offsetDelta);
+ for (int i = 0; i < tags.length; i++)
+ printTypeInfo(tags[i], data[i]);
+ }
+
+ public void fullFrame(int pos, int offsetDelta, int[] localTags, int[] localData,
+ int[] stackTags, int[] stackData) {
+ offset += offsetDelta + 1;
+ writer.println(offset + " full frame: " + offsetDelta);
+ writer.println("[locals]");
+ for (int i = 0; i < localTags.length; i++)
+ printTypeInfo(localTags[i], localData[i]);
+
+ writer.println("[stack]");
+ for (int i = 0; i < stackTags.length; i++)
+ printTypeInfo(stackTags[i], stackData[i]);
+ }
+
+ private void printTypeInfo(int tag, int data) {
+ String msg = null;
+ switch (tag) {
+ case TOP :
+ msg = "top";
+ break;
+ case INTEGER :
+ msg = "integer";
+ break;
+ case FLOAT :
+ msg = "float";
+ break;
+ case DOUBLE :
+ msg = "double";
+ break;
+ case LONG :
+ msg = "long";
+ break;
+ case NULL :
+ msg = "null";
+ break;
+ case THIS :
+ msg = "this";
+ break;
+ case OBJECT :
+ msg = "object (cpool_index " + data + ")";
+ break;
+ case UNINIT :
+ msg = "uninitialized (offset " + data + ")";
+ break;
+ }
+
+ writer.print(" ");
+ writer.println(msg);
+ }
+ }
+
+ void shiftPc(int where, int gapSize, boolean exclusive)
+ throws BadBytecode
+ {
+ new Shifter(this, where, gapSize, exclusive).doit();
+ }
+
+ static class Shifter extends Walker {
+ private StackMapTable stackMap;
+ private int where, gap;
+ private int position;
+ private byte[] updatedInfo;
+ private boolean exclusive;
+
+ public Shifter(StackMapTable smt, int where, int gap, boolean exclusive) {
+ super(smt);
+ stackMap = smt;
+ this.where = where;
+ this.gap = gap;
+ this.position = 0;
+ this.updatedInfo = null;
+ this.exclusive = exclusive;
+ }
+
+ public void doit() throws BadBytecode {
+ parse();
+ if (updatedInfo != null)
+ stackMap.set(updatedInfo);
+ }
+
+ public void sameFrame(int pos, int offsetDelta) {
+ update(pos, offsetDelta, 0, 251);
+ }
+
+ public void sameLocals(int pos, int offsetDelta, int stackTag, int stackData) {
+ update(pos, offsetDelta, 64, 247);
+ }
+
+ private void update(int pos, int offsetDelta, int base, int entry) {
+ int oldPos = position;
+ position = oldPos + offsetDelta + (oldPos == 0 ? 0 : 1);
+ boolean match;
+ if (exclusive)
+ match = oldPos < where && where <= position;
+ else
+ match = oldPos <= where && where < position;
+
+ if (match) {
+ int newDelta = offsetDelta + gap;
+ position += gap;
+ if (newDelta < 64)
+ info[pos] = (byte)(newDelta + base);
+ else if (offsetDelta < 64) {
+ byte[] newinfo = insertGap(info, pos, 2);
+ newinfo[pos] = (byte)entry;
+ ByteArray.write16bit(newDelta, newinfo, pos + 1);
+ updatedInfo = newinfo;
+ }
+ else
+ ByteArray.write16bit(newDelta, info, pos + 1);
+ }
+ }
+
+ private static byte[] insertGap(byte[] info, int where, int gap) {
+ int len = info.length;
+ byte[] newinfo = new byte[len + gap];
+ for (int i = 0; i < len; i++)
+ newinfo[i + (i < where ? 0 : gap)] = info[i];
+
+ return newinfo;
+ }
+
+ public void chopFrame(int pos, int offsetDelta, int k) {
+ update(pos, offsetDelta);
+ }
+
+ public void appendFrame(int pos, int offsetDelta, int[] tags, int[] data) {
+ update(pos, offsetDelta);
+ }
+
+ public void fullFrame(int pos, int offsetDelta, int[] localTags, int[] localData,
+ int[] stackTags, int[] stackData) {
+ update(pos, offsetDelta);
+ }
+
+ private void update(int pos, int offsetDelta) {
+ int oldPos = position;
+ position = oldPos + offsetDelta + (oldPos == 0 ? 0 : 1);
+ boolean match;
+ if (exclusive)
+ match = oldPos < where && where <= position;
+ else
+ match = oldPos <= where && where < position;
+
+ if (match) {
+ int newDelta = offsetDelta + gap;
+ ByteArray.write16bit(newDelta, info, pos + 1);
+ position += gap;
+ }
+ }
+ }
+
+ /**
+ * Undocumented method. Do not use; internal-use only.
+ *
+ * <p>This method is for javassist.convert.TransformNew.
+ * It is called to update the stack map table when
+ * the NEW opcode (and the following DUP) is removed.
+ *
+ * @param where the position of the removed NEW opcode.
+ */
+ public void removeNew(int where) throws CannotCompileException {
+ try {
+ byte[] data = new NewRemover(this.get(), where).doit();
+ this.set(data);
+ }
+ catch (BadBytecode e) {
+ throw new CannotCompileException("bad stack map table", e);
+ }
+ }
+
+ static class NewRemover extends SimpleCopy {
+ int posOfNew;
+
+ public NewRemover(byte[] data, int pos) {
+ super(data);
+ posOfNew = pos;
+ }
+
+ public void sameLocals(int pos, int offsetDelta, int stackTag, int stackData) {
+ if (stackTag == UNINIT && stackData == posOfNew)
+ super.sameFrame(pos, offsetDelta);
+ else
+ super.sameLocals(pos, offsetDelta, stackTag, stackData);
+ }
+
+ public void fullFrame(int pos, int offsetDelta, int[] localTags, int[] localData,
+ int[] stackTags, int[] stackData) {
+ int n = stackTags.length - 1;
+ for (int i = 0; i < n; i++)
+ if (stackTags[i] == UNINIT && stackData[i] == posOfNew
+ && stackTags[i + 1] == UNINIT && stackData[i + 1] == posOfNew) {
+ n++;
+ int[] stackTags2 = new int[n - 2];
+ int[] stackData2 = new int[n - 2];
+ int k = 0;
+ for (int j = 0; j < n; j++)
+ if (j == i)
+ j++;
+ else {
+ stackTags2[k] = stackTags[j];
+ stackData2[k++] = stackData[j];
+ }
+
+ stackTags = stackTags2;
+ stackData = stackData2;
+ break;
+ }
+
+ super.fullFrame(pos, offsetDelta, localTags, localData, stackTags, stackData);
+ }
+ }
+}
diff --git a/src/main/javassist/bytecode/SyntheticAttribute.java b/src/main/javassist/bytecode/SyntheticAttribute.java
new file mode 100644
index 0000000..8cc697d
--- /dev/null
+++ b/src/main/javassist/bytecode/SyntheticAttribute.java
@@ -0,0 +1,55 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * <code>Synthetic_attribute</code>.
+ */
+public class SyntheticAttribute extends AttributeInfo {
+ /**
+ * The name of this attribute <code>"Synthetic"</code>.
+ */
+ public static final String tag = "Synthetic";
+
+ SyntheticAttribute(ConstPool cp, int n, DataInputStream in)
+ throws IOException
+ {
+ super(cp, n, in);
+ }
+
+ /**
+ * Constructs a Synthetic attribute.
+ *
+ * @param cp a constant pool table.
+ */
+ public SyntheticAttribute(ConstPool cp) {
+ super(cp, tag, new byte[0]);
+ }
+
+ /**
+ * Makes a copy.
+ *
+ * @param newCp the constant pool table used by the new copy.
+ * @param classnames should be null.
+ */
+ public AttributeInfo copy(ConstPool newCp, Map classnames) {
+ return new SyntheticAttribute(newCp);
+ }
+}
diff --git a/src/main/javassist/bytecode/analysis/Analyzer.java b/src/main/javassist/bytecode/analysis/Analyzer.java
new file mode 100644
index 0000000..ea56599
--- /dev/null
+++ b/src/main/javassist/bytecode/analysis/Analyzer.java
@@ -0,0 +1,422 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+package javassist.bytecode.analysis;
+
+import java.util.Iterator;
+
+import javassist.ClassPool;
+import javassist.CtClass;
+import javassist.CtMethod;
+import javassist.NotFoundException;
+import javassist.bytecode.AccessFlag;
+import javassist.bytecode.BadBytecode;
+import javassist.bytecode.CodeAttribute;
+import javassist.bytecode.CodeIterator;
+import javassist.bytecode.ConstPool;
+import javassist.bytecode.Descriptor;
+import javassist.bytecode.ExceptionTable;
+import javassist.bytecode.MethodInfo;
+import javassist.bytecode.Opcode;
+
+/**
+ * A data-flow analyzer that determines the type state of the stack and local
+ * variable table at every reachable instruction in a method. During analysis,
+ * bytecode verification is performed in a similar manner to that described
+ * in the JVM specification.
+ *
+ * <p>Example:</p>
+ *
+ * <pre>
+ * // Method to analyze
+ * public Object doSomething(int x) {
+ * Number n;
+ * if (x < 5) {
+ * n = new Double(0);
+ * } else {
+ * n = new Long(0);
+ * }
+ *
+ * return n;
+ * }
+ *
+ * // Which compiles to:
+ * // 0: iload_1
+ * // 1: iconst_5
+ * // 2: if_icmpge 17
+ * // 5: new #18; //class java/lang/Double
+ * // 8: dup
+ * // 9: dconst_0
+ * // 10: invokespecial #44; //Method java/lang/Double."<init>":(D)V
+ * // 13: astore_2
+ * // 14: goto 26
+ * // 17: new #16; //class java/lang/Long
+ * // 20: dup
+ * // 21: lconst_1
+ * // 22: invokespecial #47; //Method java/lang/Long."<init>":(J)V
+ * // 25: astore_2
+ * // 26: aload_2
+ * // 27: areturn
+ *
+ * public void analyzeIt(CtClass clazz, MethodInfo method) {
+ * Analyzer analyzer = new Analyzer();
+ * Frame[] frames = analyzer.analyze(clazz, method);
+ * frames[0].getLocal(0).getCtClass(); // returns clazz;
+ * frames[0].getLocal(1).getCtClass(); // returns java.lang.String
+ * frames[1].peek(); // returns Type.INTEGER
+ * frames[27].peek().getCtClass(); // returns java.lang.Number
+ * }
+ * </pre>
+ *
+ * @see FramePrinter
+ * @author Jason T. Greene
+ */
+public class Analyzer implements Opcode {
+ private final SubroutineScanner scanner = new SubroutineScanner();
+ private CtClass clazz;
+ private ExceptionInfo[] exceptions;
+ private Frame[] frames;
+ private Subroutine[] subroutines;
+
+ private static class ExceptionInfo {
+ private int end;
+ private int handler;
+ private int start;
+ private Type type;
+
+ private ExceptionInfo(int start, int end, int handler, Type type) {
+ this.start = start;
+ this.end = end;
+ this.handler = handler;
+ this.type = type;
+ }
+ }
+
+ /**
+ * Performs data-flow analysis on a method and returns an array, indexed by
+ * instruction position, containing the starting frame state of all reachable
+ * instructions. Non-reachable code, and illegal code offsets are represented
+ * as a null in the frame state array. This can be used to detect dead code.
+ *
+ * If the method does not contain code (it is either native or abstract), null
+ * is returned.
+ *
+ * @param clazz the declaring class of the method
+ * @param method the method to analyze
+ * @return an array, indexed by instruction position, of the starting frame state,
+ * or null if this method doesn't have code
+ * @throws BadBytecode if the bytecode does not comply with the JVM specification
+ */
+ public Frame[] analyze(CtClass clazz, MethodInfo method) throws BadBytecode {
+ this.clazz = clazz;
+ CodeAttribute codeAttribute = method.getCodeAttribute();
+ // Native or Abstract
+ if (codeAttribute == null)
+ return null;
+
+ int maxLocals = codeAttribute.getMaxLocals();
+ int maxStack = codeAttribute.getMaxStack();
+ int codeLength = codeAttribute.getCodeLength();
+
+ CodeIterator iter = codeAttribute.iterator();
+ IntQueue queue = new IntQueue();
+
+ exceptions = buildExceptionInfo(method);
+ subroutines = scanner.scan(method);
+
+ Executor executor = new Executor(clazz.getClassPool(), method.getConstPool());
+ frames = new Frame[codeLength];
+ frames[iter.lookAhead()] = firstFrame(method, maxLocals, maxStack);
+ queue.add(iter.next());
+ while (!queue.isEmpty()) {
+ analyzeNextEntry(method, iter, queue, executor);
+ }
+
+ return frames;
+ }
+
+ /**
+ * Performs data-flow analysis on a method and returns an array, indexed by
+ * instruction position, containing the starting frame state of all reachable
+ * instructions. Non-reachable code, and illegal code offsets are represented
+ * as a null in the frame state array. This can be used to detect dead code.
+ *
+ * If the method does not contain code (it is either native or abstract), null
+ * is returned.
+ *
+ * @param method the method to analyze
+ * @return an array, indexed by instruction position, of the starting frame state,
+ * or null if this method doesn't have code
+ * @throws BadBytecode if the bytecode does not comply with the JVM specification
+ */
+ public Frame[] analyze(CtMethod method) throws BadBytecode {
+ return analyze(method.getDeclaringClass(), method.getMethodInfo2());
+ }
+
+ private void analyzeNextEntry(MethodInfo method, CodeIterator iter,
+ IntQueue queue, Executor executor) throws BadBytecode {
+ int pos = queue.take();
+ iter.move(pos);
+ iter.next();
+
+ Frame frame = frames[pos].copy();
+ Subroutine subroutine = subroutines[pos];
+
+ try {
+ executor.execute(method, pos, iter, frame, subroutine);
+ } catch (RuntimeException e) {
+ throw new BadBytecode(e.getMessage() + "[pos = " + pos + "]", e);
+ }
+
+ int opcode = iter.byteAt(pos);
+
+ if (opcode == TABLESWITCH) {
+ mergeTableSwitch(queue, pos, iter, frame);
+ } else if (opcode == LOOKUPSWITCH) {
+ mergeLookupSwitch(queue, pos, iter, frame);
+ } else if (opcode == RET) {
+ mergeRet(queue, iter, pos, frame, subroutine);
+ } else if (Util.isJumpInstruction(opcode)) {
+ int target = Util.getJumpTarget(pos, iter);
+
+ if (Util.isJsr(opcode)) {
+ // Merge the state before the jsr into the next instruction
+ mergeJsr(queue, frames[pos], subroutines[target], pos, lookAhead(iter, pos));
+ } else if (! Util.isGoto(opcode)) {
+ merge(queue, frame, lookAhead(iter, pos));
+ }
+
+ merge(queue, frame, target);
+ } else if (opcode != ATHROW && ! Util.isReturn(opcode)) {
+ // Can advance to next instruction
+ merge(queue, frame, lookAhead(iter, pos));
+ }
+
+ // Merge all exceptions that are reachable from this instruction.
+ // The redundancy is intentional, since the state must be based
+ // on the current instruction frame.
+ mergeExceptionHandlers(queue, method, pos, frame);
+ }
+
+ private ExceptionInfo[] buildExceptionInfo(MethodInfo method) {
+ ConstPool constPool = method.getConstPool();
+ ClassPool classes = clazz.getClassPool();
+
+ ExceptionTable table = method.getCodeAttribute().getExceptionTable();
+ ExceptionInfo[] exceptions = new ExceptionInfo[table.size()];
+ for (int i = 0; i < table.size(); i++) {
+ int index = table.catchType(i);
+ Type type;
+ try {
+ type = index == 0 ? Type.THROWABLE : Type.get(classes.get(constPool.getClassInfo(index)));
+ } catch (NotFoundException e) {
+ throw new IllegalStateException(e.getMessage());
+ }
+
+ exceptions[i] = new ExceptionInfo(table.startPc(i), table.endPc(i), table.handlerPc(i), type);
+ }
+
+ return exceptions;
+ }
+
+ private Frame firstFrame(MethodInfo method, int maxLocals, int maxStack) {
+ int pos = 0;
+
+ Frame first = new Frame(maxLocals, maxStack);
+ if ((method.getAccessFlags() & AccessFlag.STATIC) == 0) {
+ first.setLocal(pos++, Type.get(clazz));
+ }
+
+ CtClass[] parameters;
+ try {
+ parameters = Descriptor.getParameterTypes(method.getDescriptor(), clazz.getClassPool());
+ } catch (NotFoundException e) {
+ throw new RuntimeException(e);
+ }
+
+ for (int i = 0; i < parameters.length; i++) {
+ Type type = zeroExtend(Type.get(parameters[i]));
+ first.setLocal(pos++, type);
+ if (type.getSize() == 2)
+ first.setLocal(pos++, Type.TOP);
+ }
+
+ return first;
+ }
+
+ private int getNext(CodeIterator iter, int of, int restore) throws BadBytecode {
+ iter.move(of);
+ iter.next();
+ int next = iter.lookAhead();
+ iter.move(restore);
+ iter.next();
+
+ return next;
+ }
+
+ private int lookAhead(CodeIterator iter, int pos) throws BadBytecode {
+ if (! iter.hasNext())
+ throw new BadBytecode("Execution falls off end! [pos = " + pos + "]");
+
+ return iter.lookAhead();
+ }
+
+
+ private void merge(IntQueue queue, Frame frame, int target) {
+ Frame old = frames[target];
+ boolean changed;
+
+ if (old == null) {
+ frames[target] = frame.copy();
+ changed = true;
+ } else {
+ changed = old.merge(frame);
+ }
+
+ if (changed) {
+ queue.add(target);
+ }
+ }
+
+ private void mergeExceptionHandlers(IntQueue queue, MethodInfo method, int pos, Frame frame) {
+ for (int i = 0; i < exceptions.length; i++) {
+ ExceptionInfo exception = exceptions[i];
+
+ // Start is inclusive, while end is exclusive!
+ if (pos >= exception.start && pos < exception.end) {
+ Frame newFrame = frame.copy();
+ newFrame.clearStack();
+ newFrame.push(exception.type);
+ merge(queue, newFrame, exception.handler);
+ }
+ }
+ }
+
+ private void mergeJsr(IntQueue queue, Frame frame, Subroutine sub, int pos, int next) throws BadBytecode {
+ if (sub == null)
+ throw new BadBytecode("No subroutine at jsr target! [pos = " + pos + "]");
+
+ Frame old = frames[next];
+ boolean changed = false;
+
+ if (old == null) {
+ old = frames[next] = frame.copy();
+ changed = true;
+ } else {
+ for (int i = 0; i < frame.localsLength(); i++) {
+ // Skip everything accessed by a subroutine, mergeRet must handle this
+ if (!sub.isAccessed(i)) {
+ Type oldType = old.getLocal(i);
+ Type newType = frame.getLocal(i);
+ if (oldType == null) {
+ old.setLocal(i, newType);
+ changed = true;
+ continue;
+ }
+
+ newType = oldType.merge(newType);
+ // Always set the type, in case a multi-type switched to a standard type.
+ old.setLocal(i, newType);
+ if (!newType.equals(oldType) || newType.popChanged())
+ changed = true;
+ }
+ }
+ }
+
+ if (! old.isJsrMerged()) {
+ old.setJsrMerged(true);
+ changed = true;
+ }
+
+ if (changed && old.isRetMerged())
+ queue.add(next);
+
+ }
+
+ private void mergeLookupSwitch(IntQueue queue, int pos, CodeIterator iter, Frame frame) throws BadBytecode {
+ int index = (pos & ~3) + 4;
+ // default
+ merge(queue, frame, pos + iter.s32bitAt(index));
+ int npairs = iter.s32bitAt(index += 4);
+ int end = npairs * 8 + (index += 4);
+
+ // skip "match"
+ for (index += 4; index < end; index += 8) {
+ int target = iter.s32bitAt(index) + pos;
+ merge(queue, frame, target);
+ }
+ }
+
+ private void mergeRet(IntQueue queue, CodeIterator iter, int pos, Frame frame, Subroutine subroutine) throws BadBytecode {
+ if (subroutine == null)
+ throw new BadBytecode("Ret on no subroutine! [pos = " + pos + "]");
+
+ Iterator callerIter = subroutine.callers().iterator();
+ while (callerIter.hasNext()) {
+ int caller = ((Integer) callerIter.next()).intValue();
+ int returnLoc = getNext(iter, caller, pos);
+ boolean changed = false;
+
+ Frame old = frames[returnLoc];
+ if (old == null) {
+ old = frames[returnLoc] = frame.copyStack();
+ changed = true;
+ } else {
+ changed = old.mergeStack(frame);
+ }
+
+ for (Iterator i = subroutine.accessed().iterator(); i.hasNext(); ) {
+ int index = ((Integer)i.next()).intValue();
+ Type oldType = old.getLocal(index);
+ Type newType = frame.getLocal(index);
+ if (oldType != newType) {
+ old.setLocal(index, newType);
+ changed = true;
+ }
+ }
+
+ if (! old.isRetMerged()) {
+ old.setRetMerged(true);
+ changed = true;
+ }
+
+ if (changed && old.isJsrMerged())
+ queue.add(returnLoc);
+ }
+ }
+
+
+ private void mergeTableSwitch(IntQueue queue, int pos, CodeIterator iter, Frame frame) throws BadBytecode {
+ // Skip 4 byte alignment padding
+ int index = (pos & ~3) + 4;
+ // default
+ merge(queue, frame, pos + iter.s32bitAt(index));
+ int low = iter.s32bitAt(index += 4);
+ int high = iter.s32bitAt(index += 4);
+ int end = (high - low + 1) * 4 + (index += 4);
+
+ // Offset table
+ for (; index < end; index += 4) {
+ int target = iter.s32bitAt(index) + pos;
+ merge(queue, frame, target);
+ }
+ }
+
+ private Type zeroExtend(Type type) {
+ if (type == Type.SHORT || type == Type.BYTE || type == Type.CHAR || type == Type.BOOLEAN)
+ return Type.INTEGER;
+
+ return type;
+ }
+}
diff --git a/src/main/javassist/bytecode/analysis/Executor.java b/src/main/javassist/bytecode/analysis/Executor.java
new file mode 100644
index 0000000..562cc5d
--- /dev/null
+++ b/src/main/javassist/bytecode/analysis/Executor.java
@@ -0,0 +1,1031 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+package javassist.bytecode.analysis;
+
+import javassist.ClassPool;
+import javassist.CtClass;
+import javassist.NotFoundException;
+import javassist.bytecode.BadBytecode;
+import javassist.bytecode.CodeIterator;
+import javassist.bytecode.ConstPool;
+import javassist.bytecode.Descriptor;
+import javassist.bytecode.MethodInfo;
+import javassist.bytecode.Opcode;
+
+/**
+ * Executor is responsible for modeling the effects of a JVM instruction on a frame.
+ *
+ * @author Jason T. Greene
+ */
+public class Executor implements Opcode {
+ private final ConstPool constPool;
+ private final ClassPool classPool;
+ private final Type STRING_TYPE;
+ private final Type CLASS_TYPE;
+ private final Type THROWABLE_TYPE;
+ private int lastPos;
+
+ public Executor(ClassPool classPool, ConstPool constPool) {
+ this.constPool = constPool;
+ this.classPool = classPool;
+
+ try {
+ STRING_TYPE = getType("java.lang.String");
+ CLASS_TYPE = getType("java.lang.Class");
+ THROWABLE_TYPE = getType("java.lang.Throwable");
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+
+ /**
+ * Execute the instruction, modeling the effects on the specified frame and subroutine.
+ * If a subroutine is passed, the access flags will be modified if this instruction accesses
+ * the local variable table.
+ *
+ * @param method the method containing the instruction
+ * @param pos the position of the instruction in the method
+ * @param iter the code iterator used to find the instruction
+ * @param frame the frame to modify to represent the result of the instruction
+ * @param subroutine the optional subroutine this instruction belongs to.
+ * @throws BadBytecode if the bytecode violates the jvm spec
+ */
+ public void execute(MethodInfo method, int pos, CodeIterator iter, Frame frame, Subroutine subroutine) throws BadBytecode {
+ this.lastPos = pos;
+ int opcode = iter.byteAt(pos);
+
+
+ // Declared opcode in order
+ switch (opcode) {
+ case NOP:
+ break;
+ case ACONST_NULL:
+ frame.push(Type.UNINIT);
+ break;
+ case ICONST_M1:
+ case ICONST_0:
+ case ICONST_1:
+ case ICONST_2:
+ case ICONST_3:
+ case ICONST_4:
+ case ICONST_5:
+ frame.push(Type.INTEGER);
+ break;
+ case LCONST_0:
+ case LCONST_1:
+ frame.push(Type.LONG);
+ frame.push(Type.TOP);
+ break;
+ case FCONST_0:
+ case FCONST_1:
+ case FCONST_2:
+ frame.push(Type.FLOAT);
+ break;
+ case DCONST_0:
+ case DCONST_1:
+ frame.push(Type.DOUBLE);
+ frame.push(Type.TOP);
+ break;
+ case BIPUSH:
+ case SIPUSH:
+ frame.push(Type.INTEGER);
+ break;
+ case LDC:
+ evalLDC(iter.byteAt(pos + 1), frame);
+ break;
+ case LDC_W :
+ case LDC2_W :
+ evalLDC(iter.u16bitAt(pos + 1), frame);
+ break;
+ case ILOAD:
+ evalLoad(Type.INTEGER, iter.byteAt(pos + 1), frame, subroutine);
+ break;
+ case LLOAD:
+ evalLoad(Type.LONG, iter.byteAt(pos + 1), frame, subroutine);
+ break;
+ case FLOAD:
+ evalLoad(Type.FLOAT, iter.byteAt(pos + 1), frame, subroutine);
+ break;
+ case DLOAD:
+ evalLoad(Type.DOUBLE, iter.byteAt(pos + 1), frame, subroutine);
+ break;
+ case ALOAD:
+ evalLoad(Type.OBJECT, iter.byteAt(pos + 1), frame, subroutine);
+ break;
+ case ILOAD_0:
+ case ILOAD_1:
+ case ILOAD_2:
+ case ILOAD_3:
+ evalLoad(Type.INTEGER, opcode - ILOAD_0, frame, subroutine);
+ break;
+ case LLOAD_0:
+ case LLOAD_1:
+ case LLOAD_2:
+ case LLOAD_3:
+ evalLoad(Type.LONG, opcode - LLOAD_0, frame, subroutine);
+ break;
+ case FLOAD_0:
+ case FLOAD_1:
+ case FLOAD_2:
+ case FLOAD_3:
+ evalLoad(Type.FLOAT, opcode - FLOAD_0, frame, subroutine);
+ break;
+ case DLOAD_0:
+ case DLOAD_1:
+ case DLOAD_2:
+ case DLOAD_3:
+ evalLoad(Type.DOUBLE, opcode - DLOAD_0, frame, subroutine);
+ break;
+ case ALOAD_0:
+ case ALOAD_1:
+ case ALOAD_2:
+ case ALOAD_3:
+ evalLoad(Type.OBJECT, opcode - ALOAD_0, frame, subroutine);
+ break;
+ case IALOAD:
+ evalArrayLoad(Type.INTEGER, frame);
+ break;
+ case LALOAD:
+ evalArrayLoad(Type.LONG, frame);
+ break;
+ case FALOAD:
+ evalArrayLoad(Type.FLOAT, frame);
+ break;
+ case DALOAD:
+ evalArrayLoad(Type.DOUBLE, frame);
+ break;
+ case AALOAD:
+ evalArrayLoad(Type.OBJECT, frame);
+ break;
+ case BALOAD:
+ case CALOAD:
+ case SALOAD:
+ evalArrayLoad(Type.INTEGER, frame);
+ break;
+ case ISTORE:
+ evalStore(Type.INTEGER, iter.byteAt(pos + 1), frame, subroutine);
+ break;
+ case LSTORE:
+ evalStore(Type.LONG, iter.byteAt(pos + 1), frame, subroutine);
+ break;
+ case FSTORE:
+ evalStore(Type.FLOAT, iter.byteAt(pos + 1), frame, subroutine);
+ break;
+ case DSTORE:
+ evalStore(Type.DOUBLE, iter.byteAt(pos + 1), frame, subroutine);
+ break;
+ case ASTORE:
+ evalStore(Type.OBJECT, iter.byteAt(pos + 1), frame, subroutine);
+ break;
+ case ISTORE_0:
+ case ISTORE_1:
+ case ISTORE_2:
+ case ISTORE_3:
+ evalStore(Type.INTEGER, opcode - ISTORE_0, frame, subroutine);
+ break;
+ case LSTORE_0:
+ case LSTORE_1:
+ case LSTORE_2:
+ case LSTORE_3:
+ evalStore(Type.LONG, opcode - LSTORE_0, frame, subroutine);
+ break;
+ case FSTORE_0:
+ case FSTORE_1:
+ case FSTORE_2:
+ case FSTORE_3:
+ evalStore(Type.FLOAT, opcode - FSTORE_0, frame, subroutine);
+ break;
+ case DSTORE_0:
+ case DSTORE_1:
+ case DSTORE_2:
+ case DSTORE_3:
+ evalStore(Type.DOUBLE, opcode - DSTORE_0, frame, subroutine);
+ break;
+ case ASTORE_0:
+ case ASTORE_1:
+ case ASTORE_2:
+ case ASTORE_3:
+ evalStore(Type.OBJECT, opcode - ASTORE_0, frame, subroutine);
+ break;
+ case IASTORE:
+ evalArrayStore(Type.INTEGER, frame);
+ break;
+ case LASTORE:
+ evalArrayStore(Type.LONG, frame);
+ break;
+ case FASTORE:
+ evalArrayStore(Type.FLOAT, frame);
+ break;
+ case DASTORE:
+ evalArrayStore(Type.DOUBLE, frame);
+ break;
+ case AASTORE:
+ evalArrayStore(Type.OBJECT, frame);
+ break;
+ case BASTORE:
+ case CASTORE:
+ case SASTORE:
+ evalArrayStore(Type.INTEGER, frame);
+ break;
+ case POP:
+ if (frame.pop() == Type.TOP)
+ throw new BadBytecode("POP can not be used with a category 2 value, pos = " + pos);
+ break;
+ case POP2:
+ frame.pop();
+ frame.pop();
+ break;
+ case DUP: {
+ Type type = frame.peek();
+ if (type == Type.TOP)
+ throw new BadBytecode("DUP can not be used with a category 2 value, pos = " + pos);
+
+ frame.push(frame.peek());
+ break;
+ }
+ case DUP_X1:
+ case DUP_X2: {
+ Type type = frame.peek();
+ if (type == Type.TOP)
+ throw new BadBytecode("DUP can not be used with a category 2 value, pos = " + pos);
+ int end = frame.getTopIndex();
+ int insert = end - (opcode - DUP_X1) - 1;
+ frame.push(type);
+
+ while (end > insert) {
+ frame.setStack(end, frame.getStack(end - 1));
+ end--;
+ }
+ frame.setStack(insert, type);
+ break;
+ }
+ case DUP2:
+ frame.push(frame.getStack(frame.getTopIndex() - 1));
+ frame.push(frame.getStack(frame.getTopIndex() - 1));
+ break;
+ case DUP2_X1:
+ case DUP2_X2: {
+ int end = frame.getTopIndex();
+ int insert = end - (opcode - DUP2_X1) - 1;
+ Type type1 = frame.getStack(frame.getTopIndex() - 1);
+ Type type2 = frame.peek();
+ frame.push(type1);
+ frame.push(type2);
+ while (end > insert) {
+ frame.setStack(end, frame.getStack(end - 2));
+ end--;
+ }
+ frame.setStack(insert, type2);
+ frame.setStack(insert - 1, type1);
+ break;
+ }
+ case SWAP: {
+ Type type1 = frame.pop();
+ Type type2 = frame.pop();
+ if (type1.getSize() == 2 || type2.getSize() == 2)
+ throw new BadBytecode("Swap can not be used with category 2 values, pos = " + pos);
+ frame.push(type1);
+ frame.push(type2);
+ break;
+ }
+
+ // Math
+ case IADD:
+ evalBinaryMath(Type.INTEGER, frame);
+ break;
+ case LADD:
+ evalBinaryMath(Type.LONG, frame);
+ break;
+ case FADD:
+ evalBinaryMath(Type.FLOAT, frame);
+ break;
+ case DADD:
+ evalBinaryMath(Type.DOUBLE, frame);
+ break;
+ case ISUB:
+ evalBinaryMath(Type.INTEGER, frame);
+ break;
+ case LSUB:
+ evalBinaryMath(Type.LONG, frame);
+ break;
+ case FSUB:
+ evalBinaryMath(Type.FLOAT, frame);
+ break;
+ case DSUB:
+ evalBinaryMath(Type.DOUBLE, frame);
+ break;
+ case IMUL:
+ evalBinaryMath(Type.INTEGER, frame);
+ break;
+ case LMUL:
+ evalBinaryMath(Type.LONG, frame);
+ break;
+ case FMUL:
+ evalBinaryMath(Type.FLOAT, frame);
+ break;
+ case DMUL:
+ evalBinaryMath(Type.DOUBLE, frame);
+ break;
+ case IDIV:
+ evalBinaryMath(Type.INTEGER, frame);
+ break;
+ case LDIV:
+ evalBinaryMath(Type.LONG, frame);
+ break;
+ case FDIV:
+ evalBinaryMath(Type.FLOAT, frame);
+ break;
+ case DDIV:
+ evalBinaryMath(Type.DOUBLE, frame);
+ break;
+ case IREM:
+ evalBinaryMath(Type.INTEGER, frame);
+ break;
+ case LREM:
+ evalBinaryMath(Type.LONG, frame);
+ break;
+ case FREM:
+ evalBinaryMath(Type.FLOAT, frame);
+ break;
+ case DREM:
+ evalBinaryMath(Type.DOUBLE, frame);
+ break;
+
+ // Unary
+ case INEG:
+ verifyAssignable(Type.INTEGER, simplePeek(frame));
+ break;
+ case LNEG:
+ verifyAssignable(Type.LONG, simplePeek(frame));
+ break;
+ case FNEG:
+ verifyAssignable(Type.FLOAT, simplePeek(frame));
+ break;
+ case DNEG:
+ verifyAssignable(Type.DOUBLE, simplePeek(frame));
+ break;
+
+ // Shifts
+ case ISHL:
+ evalShift(Type.INTEGER, frame);
+ break;
+ case LSHL:
+ evalShift(Type.LONG, frame);
+ break;
+ case ISHR:
+ evalShift(Type.INTEGER, frame);
+ break;
+ case LSHR:
+ evalShift(Type.LONG, frame);
+ break;
+ case IUSHR:
+ evalShift(Type.INTEGER,frame);
+ break;
+ case LUSHR:
+ evalShift(Type.LONG, frame);
+ break;
+
+ // Bitwise Math
+ case IAND:
+ evalBinaryMath(Type.INTEGER, frame);
+ break;
+ case LAND:
+ evalBinaryMath(Type.LONG, frame);
+ break;
+ case IOR:
+ evalBinaryMath(Type.INTEGER, frame);
+ break;
+ case LOR:
+ evalBinaryMath(Type.LONG, frame);
+ break;
+ case IXOR:
+ evalBinaryMath(Type.INTEGER, frame);
+ break;
+ case LXOR:
+ evalBinaryMath(Type.LONG, frame);
+ break;
+
+ case IINC: {
+ int index = iter.byteAt(pos + 1);
+ verifyAssignable(Type.INTEGER, frame.getLocal(index));
+ access(index, Type.INTEGER, subroutine);
+ break;
+ }
+
+ // Conversion
+ case I2L:
+ verifyAssignable(Type.INTEGER, simplePop(frame));
+ simplePush(Type.LONG, frame);
+ break;
+ case I2F:
+ verifyAssignable(Type.INTEGER, simplePop(frame));
+ simplePush(Type.FLOAT, frame);
+ break;
+ case I2D:
+ verifyAssignable(Type.INTEGER, simplePop(frame));
+ simplePush(Type.DOUBLE, frame);
+ break;
+ case L2I:
+ verifyAssignable(Type.LONG, simplePop(frame));
+ simplePush(Type.INTEGER, frame);
+ break;
+ case L2F:
+ verifyAssignable(Type.LONG, simplePop(frame));
+ simplePush(Type.FLOAT, frame);
+ break;
+ case L2D:
+ verifyAssignable(Type.LONG, simplePop(frame));
+ simplePush(Type.DOUBLE, frame);
+ break;
+ case F2I:
+ verifyAssignable(Type.FLOAT, simplePop(frame));
+ simplePush(Type.INTEGER, frame);
+ break;
+ case F2L:
+ verifyAssignable(Type.FLOAT, simplePop(frame));
+ simplePush(Type.LONG, frame);
+ break;
+ case F2D:
+ verifyAssignable(Type.FLOAT, simplePop(frame));
+ simplePush(Type.DOUBLE, frame);
+ break;
+ case D2I:
+ verifyAssignable(Type.DOUBLE, simplePop(frame));
+ simplePush(Type.INTEGER, frame);
+ break;
+ case D2L:
+ verifyAssignable(Type.DOUBLE, simplePop(frame));
+ simplePush(Type.LONG, frame);
+ break;
+ case D2F:
+ verifyAssignable(Type.DOUBLE, simplePop(frame));
+ simplePush(Type.FLOAT, frame);
+ break;
+ case I2B:
+ case I2C:
+ case I2S:
+ verifyAssignable(Type.INTEGER, frame.peek());
+ break;
+ case LCMP:
+ verifyAssignable(Type.LONG, simplePop(frame));
+ verifyAssignable(Type.LONG, simplePop(frame));
+ frame.push(Type.INTEGER);
+ break;
+ case FCMPL:
+ case FCMPG:
+ verifyAssignable(Type.FLOAT, simplePop(frame));
+ verifyAssignable(Type.FLOAT, simplePop(frame));
+ frame.push(Type.INTEGER);
+ break;
+ case DCMPL:
+ case DCMPG:
+ verifyAssignable(Type.DOUBLE, simplePop(frame));
+ verifyAssignable(Type.DOUBLE, simplePop(frame));
+ frame.push(Type.INTEGER);
+ break;
+
+ // Control flow
+ case IFEQ:
+ case IFNE:
+ case IFLT:
+ case IFGE:
+ case IFGT:
+ case IFLE:
+ verifyAssignable(Type.INTEGER, simplePop(frame));
+ break;
+ case IF_ICMPEQ:
+ case IF_ICMPNE:
+ case IF_ICMPLT:
+ case IF_ICMPGE:
+ case IF_ICMPGT:
+ case IF_ICMPLE:
+ verifyAssignable(Type.INTEGER, simplePop(frame));
+ verifyAssignable(Type.INTEGER, simplePop(frame));
+ break;
+ case IF_ACMPEQ:
+ case IF_ACMPNE:
+ verifyAssignable(Type.OBJECT, simplePop(frame));
+ verifyAssignable(Type.OBJECT, simplePop(frame));
+ break;
+ case GOTO:
+ break;
+ case JSR:
+ frame.push(Type.RETURN_ADDRESS);
+ break;
+ case RET:
+ verifyAssignable(Type.RETURN_ADDRESS, frame.getLocal(iter.byteAt(pos + 1)));
+ break;
+ case TABLESWITCH:
+ case LOOKUPSWITCH:
+ case IRETURN:
+ verifyAssignable(Type.INTEGER, simplePop(frame));
+ break;
+ case LRETURN:
+ verifyAssignable(Type.LONG, simplePop(frame));
+ break;
+ case FRETURN:
+ verifyAssignable(Type.FLOAT, simplePop(frame));
+ break;
+ case DRETURN:
+ verifyAssignable(Type.DOUBLE, simplePop(frame));
+ break;
+ case ARETURN:
+ try {
+ CtClass returnType = Descriptor.getReturnType(method.getDescriptor(), classPool);
+ verifyAssignable(Type.get(returnType), simplePop(frame));
+ } catch (NotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ break;
+ case RETURN:
+ break;
+ case GETSTATIC:
+ evalGetField(opcode, iter.u16bitAt(pos + 1), frame);
+ break;
+ case PUTSTATIC:
+ evalPutField(opcode, iter.u16bitAt(pos + 1), frame);
+ break;
+ case GETFIELD:
+ evalGetField(opcode, iter.u16bitAt(pos + 1), frame);
+ break;
+ case PUTFIELD:
+ evalPutField(opcode, iter.u16bitAt(pos + 1), frame);
+ break;
+ case INVOKEVIRTUAL:
+ case INVOKESPECIAL:
+ case INVOKESTATIC:
+ evalInvokeMethod(opcode, iter.u16bitAt(pos + 1), frame);
+ break;
+ case INVOKEINTERFACE:
+ evalInvokeIntfMethod(opcode, iter.u16bitAt(pos + 1), frame);
+ break;
+ case 186:
+ throw new RuntimeException("Bad opcode 186");
+ case NEW:
+ frame.push(resolveClassInfo(constPool.getClassInfo(iter.u16bitAt(pos + 1))));
+ break;
+ case NEWARRAY:
+ evalNewArray(pos, iter, frame);
+ break;
+ case ANEWARRAY:
+ evalNewObjectArray(pos, iter, frame);
+ break;
+ case ARRAYLENGTH: {
+ Type array = simplePop(frame);
+ if (! array.isArray() && array != Type.UNINIT)
+ throw new BadBytecode("Array length passed a non-array [pos = " + pos + "]: " + array);
+ frame.push(Type.INTEGER);
+ break;
+ }
+ case ATHROW:
+ verifyAssignable(THROWABLE_TYPE, simplePop(frame));
+ break;
+ case CHECKCAST:
+ verifyAssignable(Type.OBJECT, simplePop(frame));
+ frame.push(typeFromDesc(constPool.getClassInfo(iter.u16bitAt(pos + 1))));
+ break;
+ case INSTANCEOF:
+ verifyAssignable(Type.OBJECT, simplePop(frame));
+ frame.push(Type.INTEGER);
+ break;
+ case MONITORENTER:
+ case MONITOREXIT:
+ verifyAssignable(Type.OBJECT, simplePop(frame));
+ break;
+ case WIDE:
+ evalWide(pos, iter, frame, subroutine);
+ break;
+ case MULTIANEWARRAY:
+ evalNewObjectArray(pos, iter, frame);
+ break;
+ case IFNULL:
+ case IFNONNULL:
+ verifyAssignable(Type.OBJECT, simplePop(frame));
+ break;
+ case GOTO_W:
+ break;
+ case JSR_W:
+ frame.push(Type.RETURN_ADDRESS);
+ break;
+ }
+ }
+
+ private Type zeroExtend(Type type) {
+ if (type == Type.SHORT || type == Type.BYTE || type == Type.CHAR || type == Type.BOOLEAN)
+ return Type.INTEGER;
+
+ return type;
+ }
+
+ private void evalArrayLoad(Type expectedComponent, Frame frame) throws BadBytecode {
+ Type index = frame.pop();
+ Type array = frame.pop();
+
+ // Special case, an array defined by aconst_null
+ // TODO - we might need to be more inteligent about this
+ if (array == Type.UNINIT) {
+ verifyAssignable(Type.INTEGER, index);
+ if (expectedComponent == Type.OBJECT) {
+ simplePush(Type.UNINIT, frame);
+ } else {
+ simplePush(expectedComponent, frame);
+ }
+ return;
+ }
+
+ Type component = array.getComponent();
+
+ if (component == null)
+ throw new BadBytecode("Not an array! [pos = " + lastPos + "]: " + component);
+
+ component = zeroExtend(component);
+
+ verifyAssignable(expectedComponent, component);
+ verifyAssignable(Type.INTEGER, index);
+ simplePush(component, frame);
+ }
+
+ private void evalArrayStore(Type expectedComponent, Frame frame) throws BadBytecode {
+ Type value = simplePop(frame);
+ Type index = frame.pop();
+ Type array = frame.pop();
+
+ if (array == Type.UNINIT) {
+ verifyAssignable(Type.INTEGER, index);
+ return;
+ }
+
+ Type component = array.getComponent();
+
+ if (component == null)
+ throw new BadBytecode("Not an array! [pos = " + lastPos + "]: " + component);
+
+ component = zeroExtend(component);
+
+ verifyAssignable(expectedComponent, component);
+ verifyAssignable(Type.INTEGER, index);
+
+ // This intentionally only checks for Object on aastore
+ // downconverting of an array (no casts)
+ // e.g. Object[] blah = new String[];
+ // blah[2] = (Object) "test";
+ // blah[3] = new Integer(); // compiler doesnt catch it (has legal bytecode),
+ // // but will throw arraystoreexception
+ if (expectedComponent == Type.OBJECT) {
+ verifyAssignable(expectedComponent, value);
+ } else {
+ verifyAssignable(component, value);
+ }
+ }
+
+ private void evalBinaryMath(Type expected, Frame frame) throws BadBytecode {
+ Type value2 = simplePop(frame);
+ Type value1 = simplePop(frame);
+
+ verifyAssignable(expected, value2);
+ verifyAssignable(expected, value1);
+ simplePush(value1, frame);
+ }
+
+ private void evalGetField(int opcode, int index, Frame frame) throws BadBytecode {
+ String desc = constPool.getFieldrefType(index);
+ Type type = zeroExtend(typeFromDesc(desc));
+
+ if (opcode == GETFIELD) {
+ Type objectType = resolveClassInfo(constPool.getFieldrefClassName(index));
+ verifyAssignable(objectType, simplePop(frame));
+ }
+
+ simplePush(type, frame);
+ }
+
+ private void evalInvokeIntfMethod(int opcode, int index, Frame frame) throws BadBytecode {
+ String desc = constPool.getInterfaceMethodrefType(index);
+ Type[] types = paramTypesFromDesc(desc);
+ int i = types.length;
+
+ while (i > 0)
+ verifyAssignable(zeroExtend(types[--i]), simplePop(frame));
+
+ String classInfo = constPool.getInterfaceMethodrefClassName(index);
+ Type objectType = resolveClassInfo(classInfo);
+ verifyAssignable(objectType, simplePop(frame));
+
+ Type returnType = returnTypeFromDesc(desc);
+ if (returnType != Type.VOID)
+ simplePush(zeroExtend(returnType), frame);
+ }
+
+ private void evalInvokeMethod(int opcode, int index, Frame frame) throws BadBytecode {
+ String desc = constPool.getMethodrefType(index);
+ Type[] types = paramTypesFromDesc(desc);
+ int i = types.length;
+
+ while (i > 0)
+ verifyAssignable(zeroExtend(types[--i]), simplePop(frame));
+
+ if (opcode != INVOKESTATIC) {
+ Type objectType = resolveClassInfo(constPool.getMethodrefClassName(index));
+ verifyAssignable(objectType, simplePop(frame));
+ }
+
+ Type returnType = returnTypeFromDesc(desc);
+ if (returnType != Type.VOID)
+ simplePush(zeroExtend(returnType), frame);
+ }
+
+
+ private void evalLDC(int index, Frame frame) throws BadBytecode {
+ int tag = constPool.getTag(index);
+ Type type;
+ switch (tag) {
+ case ConstPool.CONST_String:
+ type = STRING_TYPE;
+ break;
+ case ConstPool.CONST_Integer:
+ type = Type.INTEGER;
+ break;
+ case ConstPool.CONST_Float:
+ type = Type.FLOAT;
+ break;
+ case ConstPool.CONST_Long:
+ type = Type.LONG;
+ break;
+ case ConstPool.CONST_Double:
+ type = Type.DOUBLE;
+ break;
+ case ConstPool.CONST_Class:
+ type = CLASS_TYPE;
+ break;
+ default:
+ throw new BadBytecode("bad LDC [pos = " + lastPos + "]: " + tag);
+ }
+
+ simplePush(type, frame);
+ }
+
+ private void evalLoad(Type expected, int index, Frame frame, Subroutine subroutine) throws BadBytecode {
+ Type type = frame.getLocal(index);
+
+ verifyAssignable(expected, type);
+
+ simplePush(type, frame);
+ access(index, type, subroutine);
+ }
+
+ private void evalNewArray(int pos, CodeIterator iter, Frame frame) throws BadBytecode {
+ verifyAssignable(Type.INTEGER, simplePop(frame));
+ Type type = null;
+ int typeInfo = iter.byteAt(pos + 1);
+ switch (typeInfo) {
+ case T_BOOLEAN:
+ type = getType("boolean[]");
+ break;
+ case T_CHAR:
+ type = getType("char[]");
+ break;
+ case T_BYTE:
+ type = getType("byte[]");
+ break;
+ case T_SHORT:
+ type = getType("short[]");
+ break;
+ case T_INT:
+ type = getType("int[]");
+ break;
+ case T_LONG:
+ type = getType("long[]");
+ break;
+ case T_FLOAT:
+ type = getType("float[]");
+ break;
+ case T_DOUBLE:
+ type = getType("double[]");
+ break;
+ default:
+ throw new BadBytecode("Invalid array type [pos = " + pos + "]: " + typeInfo);
+
+ }
+
+ frame.push(type);
+ }
+
+ private void evalNewObjectArray(int pos, CodeIterator iter, Frame frame) throws BadBytecode {
+ // Convert to x[] format
+ Type type = resolveClassInfo(constPool.getClassInfo(iter.u16bitAt(pos + 1)));
+ String name = type.getCtClass().getName();
+ int opcode = iter.byteAt(pos);
+ int dimensions;
+
+ if (opcode == MULTIANEWARRAY) {
+ dimensions = iter.byteAt(pos + 3);
+ } else {
+ name = name + "[]";
+ dimensions = 1;
+ }
+
+ while (dimensions-- > 0) {
+ verifyAssignable(Type.INTEGER, simplePop(frame));
+ }
+
+ simplePush(getType(name), frame);
+ }
+
+ private void evalPutField(int opcode, int index, Frame frame) throws BadBytecode {
+ String desc = constPool.getFieldrefType(index);
+ Type type = zeroExtend(typeFromDesc(desc));
+
+ verifyAssignable(type, simplePop(frame));
+
+ if (opcode == PUTFIELD) {
+ Type objectType = resolveClassInfo(constPool.getFieldrefClassName(index));
+ verifyAssignable(objectType, simplePop(frame));
+ }
+ }
+
+ private void evalShift(Type expected, Frame frame) throws BadBytecode {
+ Type value2 = simplePop(frame);
+ Type value1 = simplePop(frame);
+
+ verifyAssignable(Type.INTEGER, value2);
+ verifyAssignable(expected, value1);
+ simplePush(value1, frame);
+ }
+
+ private void evalStore(Type expected, int index, Frame frame, Subroutine subroutine) throws BadBytecode {
+ Type type = simplePop(frame);
+
+ // RETURN_ADDRESS is allowed by ASTORE
+ if (! (expected == Type.OBJECT && type == Type.RETURN_ADDRESS))
+ verifyAssignable(expected, type);
+ simpleSetLocal(index, type, frame);
+ access(index, type, subroutine);
+ }
+
+ private void evalWide(int pos, CodeIterator iter, Frame frame, Subroutine subroutine) throws BadBytecode {
+ int opcode = iter.byteAt(pos + 1);
+ int index = iter.u16bitAt(pos + 2);
+ switch (opcode) {
+ case ILOAD:
+ evalLoad(Type.INTEGER, index, frame, subroutine);
+ break;
+ case LLOAD:
+ evalLoad(Type.LONG, index, frame, subroutine);
+ break;
+ case FLOAD:
+ evalLoad(Type.FLOAT, index, frame, subroutine);
+ break;
+ case DLOAD:
+ evalLoad(Type.DOUBLE, index, frame, subroutine);
+ break;
+ case ALOAD:
+ evalLoad(Type.OBJECT, index, frame, subroutine);
+ break;
+ case ISTORE:
+ evalStore(Type.INTEGER, index, frame, subroutine);
+ break;
+ case LSTORE:
+ evalStore(Type.LONG, index, frame, subroutine);
+ break;
+ case FSTORE:
+ evalStore(Type.FLOAT, index, frame, subroutine);
+ break;
+ case DSTORE:
+ evalStore(Type.DOUBLE, index, frame, subroutine);
+ break;
+ case ASTORE:
+ evalStore(Type.OBJECT, index, frame, subroutine);
+ break;
+ case IINC:
+ verifyAssignable(Type.INTEGER, frame.getLocal(index));
+ break;
+ case RET:
+ verifyAssignable(Type.RETURN_ADDRESS, frame.getLocal(index));
+ break;
+ default:
+ throw new BadBytecode("Invalid WIDE operand [pos = " + pos + "]: " + opcode);
+ }
+
+ }
+
+ private Type getType(String name) throws BadBytecode {
+ try {
+ return Type.get(classPool.get(name));
+ } catch (NotFoundException e) {
+ throw new BadBytecode("Could not find class [pos = " + lastPos + "]: " + name);
+ }
+ }
+
+ private Type[] paramTypesFromDesc(String desc) throws BadBytecode {
+ CtClass classes[] = null;
+ try {
+ classes = Descriptor.getParameterTypes(desc, classPool);
+ } catch (NotFoundException e) {
+ throw new BadBytecode("Could not find class in descriptor [pos = " + lastPos + "]: " + e.getMessage());
+ }
+
+ if (classes == null)
+ throw new BadBytecode("Could not obtain parameters for descriptor [pos = " + lastPos + "]: " + desc);
+
+ Type[] types = new Type[classes.length];
+ for (int i = 0; i < types.length; i++)
+ types[i] = Type.get(classes[i]);
+
+ return types;
+ }
+
+ private Type returnTypeFromDesc(String desc) throws BadBytecode {
+ CtClass clazz = null;
+ try {
+ clazz = Descriptor.getReturnType(desc, classPool);
+ } catch (NotFoundException e) {
+ throw new BadBytecode("Could not find class in descriptor [pos = " + lastPos + "]: " + e.getMessage());
+ }
+
+ if (clazz == null)
+ throw new BadBytecode("Could not obtain return type for descriptor [pos = " + lastPos + "]: " + desc);
+
+ return Type.get(clazz);
+ }
+
+ private Type simplePeek(Frame frame) {
+ Type type = frame.peek();
+ return (type == Type.TOP) ? frame.getStack(frame.getTopIndex() - 1) : type;
+ }
+
+ private Type simplePop(Frame frame) {
+ Type type = frame.pop();
+ return (type == Type.TOP) ? frame.pop() : type;
+ }
+
+ private void simplePush(Type type, Frame frame) {
+ frame.push(type);
+ if (type.getSize() == 2)
+ frame.push(Type.TOP);
+ }
+
+ private void access(int index, Type type, Subroutine subroutine) {
+ if (subroutine == null)
+ return;
+ subroutine.access(index);
+ if (type.getSize() == 2)
+ subroutine.access(index + 1);
+ }
+
+ private void simpleSetLocal(int index, Type type, Frame frame) {
+ frame.setLocal(index, type);
+ if (type.getSize() == 2)
+ frame.setLocal(index + 1, Type.TOP);
+ }
+
+ private Type resolveClassInfo(String info) throws BadBytecode {
+ CtClass clazz = null;
+ try {
+ if (info.charAt(0) == '[') {
+ clazz = Descriptor.toCtClass(info, classPool);
+ } else {
+ clazz = classPool.get(info);
+ }
+
+ } catch (NotFoundException e) {
+ throw new BadBytecode("Could not find class in descriptor [pos = " + lastPos + "]: " + e.getMessage());
+ }
+
+ if (clazz == null)
+ throw new BadBytecode("Could not obtain type for descriptor [pos = " + lastPos + "]: " + info);
+
+ return Type.get(clazz);
+ }
+
+ private Type typeFromDesc(String desc) throws BadBytecode {
+ CtClass clazz = null;
+ try {
+ clazz = Descriptor.toCtClass(desc, classPool);
+ } catch (NotFoundException e) {
+ throw new BadBytecode("Could not find class in descriptor [pos = " + lastPos + "]: " + e.getMessage());
+ }
+
+ if (clazz == null)
+ throw new BadBytecode("Could not obtain type for descriptor [pos = " + lastPos + "]: " + desc);
+
+ return Type.get(clazz);
+ }
+
+ private void verifyAssignable(Type expected, Type type) throws BadBytecode {
+ if (! expected.isAssignableFrom(type))
+ throw new BadBytecode("Expected type: " + expected + " Got: " + type + " [pos = " + lastPos + "]");
+ }
+}
diff --git a/src/main/javassist/bytecode/analysis/Frame.java b/src/main/javassist/bytecode/analysis/Frame.java
new file mode 100644
index 0000000..cf646f4
--- /dev/null
+++ b/src/main/javassist/bytecode/analysis/Frame.java
@@ -0,0 +1,288 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+package javassist.bytecode.analysis;
+
+
+/**
+ * Represents the stack frame and local variable table at a particular point in time.
+ *
+ * @author Jason T. Greene
+ */
+public class Frame {
+ private Type[] locals;
+ private Type[] stack;
+ private int top;
+ private boolean jsrMerged;
+ private boolean retMerged;
+
+ /**
+ * Create a new frame with the specified local variable table size, and max stack size
+ *
+ * @param locals the number of local variable table entries
+ * @param stack the maximum stack size
+ */
+ public Frame(int locals, int stack) {
+ this.locals = new Type[locals];
+ this.stack = new Type[stack];
+ }
+
+ /**
+ * Returns the local varaible table entry at index.
+ *
+ * @param index the position in the table
+ * @return the type if one exists, or null if the position is empty
+ */
+ public Type getLocal(int index) {
+ return locals[index];
+ }
+
+ /**
+ * Sets the local variable table entry at index to a type.
+ *
+ * @param index the position in the table
+ * @param type the type to set at the position
+ */
+ public void setLocal(int index, Type type) {
+ locals[index] = type;
+ }
+
+
+ /**
+ * Returns the type on the stack at the specified index.
+ *
+ * @param index the position on the stack
+ * @return the type of the stack position
+ */
+ public Type getStack(int index) {
+ return stack[index];
+ }
+
+ /**
+ * Sets the type of the stack position
+ *
+ * @param index the position on the stack
+ * @param type the type to set
+ */
+ public void setStack(int index, Type type) {
+ stack[index] = type;
+ }
+
+ /**
+ * Empties the stack
+ */
+ public void clearStack() {
+ top = 0;
+ }
+
+ /**
+ * Gets the index of the type sitting at the top of the stack.
+ * This is not to be confused with a length operation which
+ * would return the number of elements, not the position of
+ * the last element.
+ *
+ * @return the position of the element at the top of the stack
+ */
+ public int getTopIndex() {
+ return top - 1;
+ }
+
+ /**
+ * Returns the number of local variable table entries, specified
+ * at construction.
+ *
+ * @return the number of local variable table entries
+ */
+ public int localsLength() {
+ return locals.length;
+ }
+
+ /**
+ * Gets the top of the stack without altering it
+ *
+ * @return the top of the stack
+ */
+ public Type peek() {
+ if (top < 1)
+ throw new IndexOutOfBoundsException("Stack is empty");
+
+ return stack[top - 1];
+ }
+
+ /**
+ * Alters the stack to contain one less element and return it.
+ *
+ * @return the element popped from the stack
+ */
+ public Type pop() {
+ if (top < 1)
+ throw new IndexOutOfBoundsException("Stack is empty");
+ return stack[--top];
+ }
+
+ /**
+ * Alters the stack by placing the passed type on the top
+ *
+ * @param type the type to add to the top
+ */
+ public void push(Type type) {
+ stack[top++] = type;
+ }
+
+
+ /**
+ * Makes a shallow copy of this frame, i.e. the type instances will
+ * remain the same.
+ *
+ * @return the shallow copy
+ */
+ public Frame copy() {
+ Frame frame = new Frame(locals.length, stack.length);
+ System.arraycopy(locals, 0, frame.locals, 0, locals.length);
+ System.arraycopy(stack, 0, frame.stack, 0, stack.length);
+ frame.top = top;
+ return frame;
+ }
+
+ /**
+ * Makes a shallow copy of the stack portion of this frame. The local
+ * variable table size will be copied, but its contents will be empty.
+ *
+ * @return the shallow copy of the stack
+ */
+ public Frame copyStack() {
+ Frame frame = new Frame(locals.length, stack.length);
+ System.arraycopy(stack, 0, frame.stack, 0, stack.length);
+ frame.top = top;
+ return frame;
+ }
+
+ /**
+ * Merges all types on the stack of this frame instance with that of the specified frame.
+ * The local variable table is left untouched.
+ *
+ * @param frame the frame to merge the stack from
+ * @return true if any changes where made
+ */
+ public boolean mergeStack(Frame frame) {
+ boolean changed = false;
+ if (top != frame.top)
+ throw new RuntimeException("Operand stacks could not be merged, they are different sizes!");
+
+ for (int i = 0; i < top; i++) {
+ if (stack[i] != null) {
+ Type prev = stack[i];
+ Type merged = prev.merge(frame.stack[i]);
+ if (merged == Type.BOGUS)
+ throw new RuntimeException("Operand stacks could not be merged due to differing primitive types: pos = " + i);
+
+ stack[i] = merged;
+ // always replace the instance in case a multi-interface type changes to a normal Type
+ if ((! merged.equals(prev)) || merged.popChanged()) {
+ changed = true;
+ }
+ }
+ }
+
+ return changed;
+ }
+
+ /**
+ * Merges all types on the stack and local variable table of this frame with that of the specified
+ * type.
+ *
+ * @param frame the frame to merge with
+ * @return true if any changes to this frame where made by this merge
+ */
+ public boolean merge(Frame frame) {
+ boolean changed = false;
+
+ // Local variable table
+ for (int i = 0; i < locals.length; i++) {
+ if (locals[i] != null) {
+ Type prev = locals[i];
+ Type merged = prev.merge(frame.locals[i]);
+ // always replace the instance in case a multi-interface type changes to a normal Type
+ locals[i] = merged;
+ if (! merged.equals(prev) || merged.popChanged()) {
+ changed = true;
+ }
+ } else if (frame.locals[i] != null) {
+ locals[i] = frame.locals[i];
+ changed = true;
+ }
+ }
+
+ changed |= mergeStack(frame);
+ return changed;
+ }
+
+ public String toString() {
+ StringBuffer buffer = new StringBuffer();
+
+ buffer.append("locals = [");
+ for (int i = 0; i < locals.length; i++) {
+ buffer.append(locals[i] == null ? "empty" : locals[i].toString());
+ if (i < locals.length - 1)
+ buffer.append(", ");
+ }
+ buffer.append("] stack = [");
+ for (int i = 0; i < top; i++) {
+ buffer.append(stack[i]);
+ if (i < top - 1)
+ buffer.append(", ");
+ }
+ buffer.append("]");
+
+ return buffer.toString();
+ }
+
+ /**
+ * Whether or not state from the source JSR instruction has been merged
+ *
+ * @return true if JSR state has been merged
+ */
+ boolean isJsrMerged() {
+ return jsrMerged;
+ }
+
+ /**
+ * Sets whether of not the state from the source JSR instruction has been merged
+ *
+ * @param jsrMerged true if merged, otherwise false
+ */
+ void setJsrMerged(boolean jsrMerged) {
+ this.jsrMerged = jsrMerged;
+ }
+
+ /**
+ * Whether or not state from the RET instruction, of the subroutine that was jumped
+ * to has been merged.
+ *
+ * @return true if RET state has been merged
+ */
+ boolean isRetMerged() {
+ return retMerged;
+ }
+
+ /**
+ * Sets whether or not state from the RET instruction, of the subroutine that was jumped
+ * to has been merged.
+ *
+ * @param retMerged true if RET state has been merged
+ */
+ void setRetMerged(boolean retMerged) {
+ this.retMerged = retMerged;
+ }
+}
diff --git a/src/main/javassist/bytecode/analysis/FramePrinter.java b/src/main/javassist/bytecode/analysis/FramePrinter.java
new file mode 100644
index 0000000..fc99cd3
--- /dev/null
+++ b/src/main/javassist/bytecode/analysis/FramePrinter.java
@@ -0,0 +1,147 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+package javassist.bytecode.analysis;
+
+import java.io.PrintStream;
+
+import javassist.CtClass;
+import javassist.CtMethod;
+import javassist.Modifier;
+import javassist.NotFoundException;
+import javassist.bytecode.BadBytecode;
+import javassist.bytecode.CodeAttribute;
+import javassist.bytecode.CodeIterator;
+import javassist.bytecode.ConstPool;
+import javassist.bytecode.Descriptor;
+import javassist.bytecode.InstructionPrinter;
+import javassist.bytecode.MethodInfo;
+
+/**
+ * A utility class for printing a merged view of the frame state and the
+ * instructions of a method.
+ *
+ * @author Jason T. Greene
+ */
+public final class FramePrinter {
+ private final PrintStream stream;
+
+ /**
+ * Constructs a bytecode printer.
+ */
+ public FramePrinter(PrintStream stream) {
+ this.stream = stream;
+ }
+
+ /**
+ * Prints all the methods declared in the given class.
+ */
+ public static void print(CtClass clazz, PrintStream stream) {
+ (new FramePrinter(stream)).print(clazz);
+ }
+
+ /**
+ * Prints all the methods declared in the given class.
+ */
+ public void print(CtClass clazz) {
+ CtMethod[] methods = clazz.getDeclaredMethods();
+ for (int i = 0; i < methods.length; i++) {
+ print(methods[i]);
+ }
+ }
+
+ private String getMethodString(CtMethod method) {
+ try {
+ return Modifier.toString(method.getModifiers()) + " "
+ + method.getReturnType().getName() + " " + method.getName()
+ + Descriptor.toString(method.getSignature()) + ";";
+ } catch (NotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Prints the instructions and the frame states of the given method.
+ */
+ public void print(CtMethod method) {
+ stream.println("\n" + getMethodString(method));
+ MethodInfo info = method.getMethodInfo2();
+ ConstPool pool = info.getConstPool();
+ CodeAttribute code = info.getCodeAttribute();
+ if (code == null)
+ return;
+
+ Frame[] frames;
+ try {
+ frames = (new Analyzer()).analyze(method.getDeclaringClass(), info);
+ } catch (BadBytecode e) {
+ throw new RuntimeException(e);
+ }
+
+ int spacing = String.valueOf(code.getCodeLength()).length();
+
+ CodeIterator iterator = code.iterator();
+ while (iterator.hasNext()) {
+ int pos;
+ try {
+ pos = iterator.next();
+ } catch (BadBytecode e) {
+ throw new RuntimeException(e);
+ }
+
+ stream.println(pos + ": " + InstructionPrinter.instructionString(iterator, pos, pool));
+
+ addSpacing(spacing + 3);
+ Frame frame = frames[pos];
+ if (frame == null) {
+ stream.println("--DEAD CODE--");
+ continue;
+ }
+ printStack(frame);
+
+ addSpacing(spacing + 3);
+ printLocals(frame);
+ }
+
+ }
+
+ private void printStack(Frame frame) {
+ stream.print("stack [");
+ int top = frame.getTopIndex();
+ for (int i = 0; i <= top; i++) {
+ if (i > 0)
+ stream.print(", ");
+ Type type = frame.getStack(i);
+ stream.print(type);
+ }
+ stream.println("]");
+ }
+
+ private void printLocals(Frame frame) {
+ stream.print("locals [");
+ int length = frame.localsLength();
+ for (int i = 0; i < length; i++) {
+ if (i > 0)
+ stream.print(", ");
+ Type type = frame.getLocal(i);
+ stream.print(type == null ? "empty" : type.toString());
+ }
+ stream.println("]");
+ }
+
+ private void addSpacing(int count) {
+ while (count-- > 0)
+ stream.print(' ');
+ }
+}
diff --git a/src/main/javassist/bytecode/analysis/IntQueue.java b/src/main/javassist/bytecode/analysis/IntQueue.java
new file mode 100644
index 0000000..d50cddc
--- /dev/null
+++ b/src/main/javassist/bytecode/analysis/IntQueue.java
@@ -0,0 +1,56 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+package javassist.bytecode.analysis;
+
+import java.util.NoSuchElementException;
+
+class IntQueue {
+ private static class Entry {
+ private IntQueue.Entry next;
+ private int value;
+ private Entry(int value) {
+ this.value = value;
+ }
+ }
+ private IntQueue.Entry head;
+
+ private IntQueue.Entry tail;
+
+ void add(int value) {
+ IntQueue.Entry entry = new Entry(value);
+ if (tail != null)
+ tail.next = entry;
+ tail = entry;
+
+ if (head == null)
+ head = entry;
+ }
+
+ boolean isEmpty() {
+ return head == null;
+ }
+
+ int take() {
+ if (head == null)
+ throw new NoSuchElementException();
+
+ int value = head.value;
+ head = head.next;
+ if (head == null)
+ tail = null;
+
+ return value;
+ }
+}
\ No newline at end of file
diff --git a/src/main/javassist/bytecode/analysis/MultiArrayType.java b/src/main/javassist/bytecode/analysis/MultiArrayType.java
new file mode 100644
index 0000000..750116c
--- /dev/null
+++ b/src/main/javassist/bytecode/analysis/MultiArrayType.java
@@ -0,0 +1,129 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+package javassist.bytecode.analysis;
+
+import javassist.ClassPool;
+import javassist.CtClass;
+import javassist.NotFoundException;
+
+/**
+ * Represents an array of {@link MultiType} instances.
+ *
+ * @author Jason T. Greene
+ */
+public class MultiArrayType extends Type {
+ private MultiType component;
+ private int dims;
+
+ public MultiArrayType(MultiType component, int dims) {
+ super(null);
+ this.component = component;
+ this.dims = dims;
+ }
+
+ public CtClass getCtClass() {
+ CtClass clazz = component.getCtClass();
+ if (clazz == null)
+ return null;
+
+ ClassPool pool = clazz.getClassPool();
+ if (pool == null)
+ pool = ClassPool.getDefault();
+
+ String name = arrayName(clazz.getName(), dims);
+
+ try {
+ return pool.get(name);
+ } catch (NotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ boolean popChanged() {
+ return component.popChanged();
+ }
+
+ public int getDimensions() {
+ return dims;
+ }
+
+ public Type getComponent() {
+ return dims == 1 ? (Type)component : new MultiArrayType(component, dims - 1);
+ }
+
+ public int getSize() {
+ return 1;
+ }
+
+ public boolean isArray() {
+ return true;
+ }
+
+ public boolean isAssignableFrom(Type type) {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ public boolean isReference() {
+ return true;
+ }
+
+ public boolean isAssignableTo(Type type) {
+ if (eq(type.getCtClass(), Type.OBJECT.getCtClass()))
+ return true;
+
+ if (eq(type.getCtClass(), Type.CLONEABLE.getCtClass()))
+ return true;
+
+ if (eq(type.getCtClass(), Type.SERIALIZABLE.getCtClass()))
+ return true;
+
+ if (! type.isArray())
+ return false;
+
+ Type typeRoot = getRootComponent(type);
+ int typeDims = type.getDimensions();
+
+ if (typeDims > dims)
+ return false;
+
+ if (typeDims < dims) {
+ if (eq(typeRoot.getCtClass(), Type.OBJECT.getCtClass()))
+ return true;
+
+ if (eq(typeRoot.getCtClass(), Type.CLONEABLE.getCtClass()))
+ return true;
+
+ if (eq(typeRoot.getCtClass(), Type.SERIALIZABLE.getCtClass()))
+ return true;
+
+ return false;
+ }
+
+ return component.isAssignableTo(typeRoot);
+ }
+
+ public boolean equals(Object o) {
+ if (! (o instanceof MultiArrayType))
+ return false;
+ MultiArrayType multi = (MultiArrayType)o;
+
+ return component.equals(multi.component) && dims == multi.dims;
+ }
+
+ public String toString() {
+ // follows the same detailed formating scheme as component
+ return arrayName(component.toString(), dims);
+ }
+}
diff --git a/src/main/javassist/bytecode/analysis/MultiType.java b/src/main/javassist/bytecode/analysis/MultiType.java
new file mode 100644
index 0000000..3fb1488
--- /dev/null
+++ b/src/main/javassist/bytecode/analysis/MultiType.java
@@ -0,0 +1,313 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+package javassist.bytecode.analysis;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import javassist.CtClass;
+
+/**
+ * MultiType represents an unresolved type. Whenever two <literal>Type</literal>
+ * instances are merged, if they share more than one super type (either an
+ * interface or a superclass), then a <literal>MultiType</literal> is used to
+ * represent the possible super types. The goal of a <literal>MultiType</literal>
+ * is to reduce the set of possible types down to a single resolved type. This
+ * is done by eliminating non-assignable types from the typeset when the
+ * <literal>MultiType</literal> is passed as an argument to
+ * {@link Type#isAssignableFrom(Type)}, as well as removing non-intersecting
+ * types during a merge.
+ *
+ * Note: Currently the <litera>MultiType</literal> instance is reused as much
+ * as possible so that updates are visible from all frames. In addition, all
+ * <literal>MultiType</literal> merge paths are also updated. This is somewhat
+ * hackish, but it appears to handle most scenarios.
+ *
+ * @author Jason T. Greene
+ */
+
+/* TODO - A better, but more involved, approach would be to track the instruction
+ * offset that resulted in the creation of this type, and
+ * whenever the typeset changes, to force a merge on that position. This
+ * would require creating a new MultiType instance every time the typeset
+ * changes, and somehow communicating assignment changes to the Analyzer
+ */
+public class MultiType extends Type {
+ private Map interfaces;
+ private Type resolved;
+ private Type potentialClass;
+ private MultiType mergeSource;
+ private boolean changed = false;
+
+ public MultiType(Map interfaces) {
+ this(interfaces, null);
+ }
+
+ public MultiType(Map interfaces, Type potentialClass) {
+ super(null);
+ this.interfaces = interfaces;
+ this.potentialClass = potentialClass;
+ }
+
+ /**
+ * Gets the class that corresponds with this type. If this information
+ * is not yet known, java.lang.Object will be returned.
+ */
+ public CtClass getCtClass() {
+ if (resolved != null)
+ return resolved.getCtClass();
+
+ return Type.OBJECT.getCtClass();
+ }
+
+ /**
+ * Always returns null since this type is never used for an array.
+ */
+ public Type getComponent() {
+ return null;
+ }
+
+ /**
+ * Always returns 1, since this type is a reference.
+ */
+ public int getSize() {
+ return 1;
+ }
+
+ /**
+ * Always reutnrs false since this type is never used for an array
+ */
+ public boolean isArray() {
+ return false;
+ }
+
+ /**
+ * Returns true if the internal state has changed.
+ */
+ boolean popChanged() {
+ boolean changed = this.changed;
+ this.changed = false;
+ return changed;
+ }
+
+ public boolean isAssignableFrom(Type type) {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ public boolean isAssignableTo(Type type) {
+ if (resolved != null)
+ return type.isAssignableFrom(resolved);
+
+ if (Type.OBJECT.equals(type))
+ return true;
+
+ if (potentialClass != null && !type.isAssignableFrom(potentialClass))
+ potentialClass = null;
+
+ Map map = mergeMultiAndSingle(this, type);
+
+ if (map.size() == 1 && potentialClass == null) {
+ // Update previous merge paths to the same resolved type
+ resolved = Type.get((CtClass)map.values().iterator().next());
+ propogateResolved();
+
+ return true;
+ }
+
+ // Keep all previous merge paths up to date
+ if (map.size() >= 1) {
+ interfaces = map;
+ propogateState();
+
+ return true;
+ }
+
+ if (potentialClass != null) {
+ resolved = potentialClass;
+ propogateResolved();
+
+ return true;
+ }
+
+ return false;
+ }
+
+ private void propogateState() {
+ MultiType source = mergeSource;
+ while (source != null) {
+ source.interfaces = interfaces;
+ source.potentialClass = potentialClass;
+ source = source.mergeSource;
+ }
+ }
+
+ private void propogateResolved() {
+ MultiType source = mergeSource;
+ while (source != null) {
+ source.resolved = resolved;
+ source = source.mergeSource;
+ }
+ }
+
+ /**
+ * Always returns true, since this type is always a reference.
+ *
+ * @return true
+ */
+ public boolean isReference() {
+ return true;
+ }
+
+ private Map getAllMultiInterfaces(MultiType type) {
+ Map map = new HashMap();
+
+ Iterator iter = type.interfaces.values().iterator();
+ while (iter.hasNext()) {
+ CtClass intf = (CtClass)iter.next();
+ map.put(intf.getName(), intf);
+ getAllInterfaces(intf, map);
+ }
+
+ return map;
+ }
+
+
+ private Map mergeMultiInterfaces(MultiType type1, MultiType type2) {
+ Map map1 = getAllMultiInterfaces(type1);
+ Map map2 = getAllMultiInterfaces(type2);
+
+ return findCommonInterfaces(map1, map2);
+ }
+
+ private Map mergeMultiAndSingle(MultiType multi, Type single) {
+ Map map1 = getAllMultiInterfaces(multi);
+ Map map2 = getAllInterfaces(single.getCtClass(), null);
+
+ return findCommonInterfaces(map1, map2);
+ }
+
+ private boolean inMergeSource(MultiType source) {
+ while (source != null) {
+ if (source == this)
+ return true;
+
+ source = source.mergeSource;
+ }
+
+ return false;
+ }
+
+ public Type merge(Type type) {
+ if (this == type)
+ return this;
+
+ if (type == UNINIT)
+ return this;
+
+ if (type == BOGUS)
+ return BOGUS;
+
+ if (type == null)
+ return this;
+
+ if (resolved != null)
+ return resolved.merge(type);
+
+ if (potentialClass != null) {
+ Type mergePotential = potentialClass.merge(type);
+ if (! mergePotential.equals(potentialClass) || mergePotential.popChanged()) {
+ potentialClass = Type.OBJECT.equals(mergePotential) ? null : mergePotential;
+ changed = true;
+ }
+ }
+
+ Map merged;
+
+ if (type instanceof MultiType) {
+ MultiType multi = (MultiType)type;
+
+ if (multi.resolved != null) {
+ merged = mergeMultiAndSingle(this, multi.resolved);
+ } else {
+ merged = mergeMultiInterfaces(multi, this);
+ if (! inMergeSource(multi))
+ mergeSource = multi;
+ }
+ } else {
+ merged = mergeMultiAndSingle(this, type);
+ }
+
+ // Keep all previous merge paths up to date
+ if (merged.size() > 1 || (merged.size() == 1 && potentialClass != null)) {
+ // Check for changes
+ if (merged.size() != interfaces.size()) {
+ changed = true;
+ } else if (changed == false){
+ Iterator iter = merged.keySet().iterator();
+ while (iter.hasNext())
+ if (! interfaces.containsKey(iter.next()))
+ changed = true;
+ }
+
+ interfaces = merged;
+ propogateState();
+
+ return this;
+ }
+
+ if (merged.size() == 1) {
+ resolved = Type.get((CtClass) merged.values().iterator().next());
+ } else if (potentialClass != null){
+ resolved = potentialClass;
+ } else {
+ resolved = OBJECT;
+ }
+
+ propogateResolved();
+
+ return resolved;
+ }
+
+ public boolean equals(Object o) {
+ if (! (o instanceof MultiType))
+ return false;
+
+ MultiType multi = (MultiType) o;
+ if (resolved != null)
+ return resolved.equals(multi.resolved);
+ else if (multi.resolved != null)
+ return false;
+
+ return interfaces.keySet().equals(multi.interfaces.keySet());
+ }
+
+ public String toString() {
+ if (resolved != null)
+ return resolved.toString();
+
+ StringBuffer buffer = new StringBuffer("{");
+ Iterator iter = interfaces.keySet().iterator();
+ while (iter.hasNext()) {
+ buffer.append(iter.next());
+ buffer.append(", ");
+ }
+ buffer.setLength(buffer.length() - 2);
+ if (potentialClass != null)
+ buffer.append(", *").append(potentialClass.toString());
+ buffer.append("}");
+ return buffer.toString();
+ }
+}
diff --git a/src/main/javassist/bytecode/analysis/Subroutine.java b/src/main/javassist/bytecode/analysis/Subroutine.java
new file mode 100644
index 0000000..1347813
--- /dev/null
+++ b/src/main/javassist/bytecode/analysis/Subroutine.java
@@ -0,0 +1,66 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+package javassist.bytecode.analysis;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Represents a nested method subroutine (marked by JSR and RET).
+ *
+ * @author Jason T. Greene
+ */
+public class Subroutine {
+ //private Set callers = new HashSet();
+ private List callers = new ArrayList();
+ private Set access = new HashSet();
+ private int start;
+
+ public Subroutine(int start, int caller) {
+ this.start = start;
+ callers.add(new Integer(caller));
+ }
+
+ public void addCaller(int caller) {
+ callers.add(new Integer(caller));
+ }
+
+ public int start() {
+ return start;
+ }
+
+ public void access(int index) {
+ access.add(new Integer(index));
+ }
+
+ public boolean isAccessed(int index) {
+ return access.contains(new Integer(index));
+ }
+
+ public Collection accessed() {
+ return access;
+ }
+
+ public Collection callers() {
+ return callers;
+ }
+
+ public String toString() {
+ return "start = " + start + " callers = " + callers.toString();
+ }
+}
\ No newline at end of file
diff --git a/src/main/javassist/bytecode/analysis/SubroutineScanner.java b/src/main/javassist/bytecode/analysis/SubroutineScanner.java
new file mode 100644
index 0000000..f42202e
--- /dev/null
+++ b/src/main/javassist/bytecode/analysis/SubroutineScanner.java
@@ -0,0 +1,156 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+package javassist.bytecode.analysis;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javassist.bytecode.BadBytecode;
+import javassist.bytecode.CodeAttribute;
+import javassist.bytecode.CodeIterator;
+import javassist.bytecode.ExceptionTable;
+import javassist.bytecode.MethodInfo;
+import javassist.bytecode.Opcode;
+
+/**
+ * Discovers the subroutines in a method, and tracks all callers.
+ *
+ * @author Jason T. Greene
+ */
+public class SubroutineScanner implements Opcode {
+
+ private Subroutine[] subroutines;
+ Map subTable = new HashMap();
+ Set done = new HashSet();
+
+
+ public Subroutine[] scan(MethodInfo method) throws BadBytecode {
+ CodeAttribute code = method.getCodeAttribute();
+ CodeIterator iter = code.iterator();
+
+ subroutines = new Subroutine[code.getCodeLength()];
+ subTable.clear();
+ done.clear();
+
+ scan(0, iter, null);
+
+ ExceptionTable exceptions = code.getExceptionTable();
+ for (int i = 0; i < exceptions.size(); i++) {
+ int handler = exceptions.handlerPc(i);
+ // If an exception is thrown in subroutine, the handler
+ // is part of the same subroutine.
+ scan(handler, iter, subroutines[exceptions.startPc(i)]);
+ }
+
+ return subroutines;
+ }
+
+ private void scan(int pos, CodeIterator iter, Subroutine sub) throws BadBytecode {
+ // Skip already processed blocks
+ if (done.contains(new Integer(pos)))
+ return;
+
+ done.add(new Integer(pos));
+
+ int old = iter.lookAhead();
+ iter.move(pos);
+
+ boolean next;
+ do {
+ pos = iter.next();
+ next = scanOp(pos, iter, sub) && iter.hasNext();
+ } while (next);
+
+ iter.move(old);
+ }
+
+ private boolean scanOp(int pos, CodeIterator iter, Subroutine sub) throws BadBytecode {
+ subroutines[pos] = sub;
+
+ int opcode = iter.byteAt(pos);
+
+ if (opcode == TABLESWITCH) {
+ scanTableSwitch(pos, iter, sub);
+
+ return false;
+ }
+
+ if (opcode == LOOKUPSWITCH) {
+ scanLookupSwitch(pos, iter, sub);
+
+ return false;
+ }
+
+ // All forms of return and throw end current code flow
+ if (Util.isReturn(opcode) || opcode == RET || opcode == ATHROW)
+ return false;
+
+ if (Util.isJumpInstruction(opcode)) {
+ int target = Util.getJumpTarget(pos, iter);
+ if (opcode == JSR || opcode == JSR_W) {
+ Subroutine s = (Subroutine) subTable.get(new Integer(target));
+ if (s == null) {
+ s = new Subroutine(target, pos);
+ subTable.put(new Integer(target), s);
+ scan(target, iter, s);
+ } else {
+ s.addCaller(pos);
+ }
+ } else {
+ scan(target, iter, sub);
+
+ // GOTO ends current code flow
+ if (Util.isGoto(opcode))
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private void scanLookupSwitch(int pos, CodeIterator iter, Subroutine sub) throws BadBytecode {
+ int index = (pos & ~3) + 4;
+ // default
+ scan(pos + iter.s32bitAt(index), iter, sub);
+ int npairs = iter.s32bitAt(index += 4);
+ int end = npairs * 8 + (index += 4);
+
+ // skip "match"
+ for (index += 4; index < end; index += 8) {
+ int target = iter.s32bitAt(index) + pos;
+ scan(target, iter, sub);
+ }
+ }
+
+ private void scanTableSwitch(int pos, CodeIterator iter, Subroutine sub) throws BadBytecode {
+ // Skip 4 byte alignment padding
+ int index = (pos & ~3) + 4;
+ // default
+ scan(pos + iter.s32bitAt(index), iter, sub);
+ int low = iter.s32bitAt(index += 4);
+ int high = iter.s32bitAt(index += 4);
+ int end = (high - low + 1) * 4 + (index += 4);
+
+ // Offset table
+ for (; index < end; index += 4) {
+ int target = iter.s32bitAt(index) + pos;
+ scan(target, iter, sub);
+ }
+ }
+
+
+}
diff --git a/src/main/javassist/bytecode/analysis/Type.java b/src/main/javassist/bytecode/analysis/Type.java
new file mode 100644
index 0000000..234f050
--- /dev/null
+++ b/src/main/javassist/bytecode/analysis/Type.java
@@ -0,0 +1,592 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+package javassist.bytecode.analysis;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import javassist.ClassPool;
+import javassist.CtClass;
+import javassist.NotFoundException;
+
+/**
+ * Represents a JVM type in data-flow analysis. This abstraction is necessary since
+ * a JVM type not only includes all normal Java types, but also a few special types
+ * that are used by the JVM internally. See the static field types on this class for
+ * more info on these special types.
+ *
+ * All primitive and special types reuse the same instance, so identity comparison can
+ * be used when examining them. Normal java types must use {@link #equals(Object)} to
+ * compare type instances.
+ *
+ * In most cases, applications which consume this API, only need to call {@link #getCtClass()}
+ * to obtain the needed type information.
+ *
+ * @author Jason T. Greene
+ */
+public class Type {
+ private final CtClass clazz;
+ private final boolean special;
+
+ private static final Map prims = new IdentityHashMap();
+ /** Represents the double primitive type */
+ public static final Type DOUBLE = new Type(CtClass.doubleType);
+ /** Represents the boolean primitive type */
+ public static final Type BOOLEAN = new Type(CtClass.booleanType);
+ /** Represents the long primitive type */
+ public static final Type LONG = new Type(CtClass.longType);
+ /** Represents the char primitive type */
+ public static final Type CHAR = new Type(CtClass.charType);
+ /** Represents the byte primitive type */
+ public static final Type BYTE = new Type(CtClass.byteType);
+ /** Represents the short primitive type */
+ public static final Type SHORT = new Type(CtClass.shortType);
+ /** Represents the integer primitive type */
+ public static final Type INTEGER = new Type(CtClass.intType);
+ /** Represents the float primitive type */
+ public static final Type FLOAT = new Type(CtClass.floatType);
+ /** Represents the void primitive type */
+ public static final Type VOID = new Type(CtClass.voidType);
+
+ /**
+ * Represents an unknown, or null type. This occurs when aconst_null is used.
+ * It is important not to treat this type as java.lang.Object, since a null can
+ * be assigned to any reference type. The analyzer will replace these with
+ * an actual known type if it can be determined by a merged path with known type
+ * information. If this type is encountered on a frame then it is guaranteed to
+ * be null, and the type information is simply not available. Any attempts to
+ * infer the type, without further information from the compiler would be a guess.
+ */
+ public static final Type UNINIT = new Type(null);
+
+ /**
+ * Represents an internal JVM return address, which is used by the RET
+ * instruction to return to a JSR that invoked the subroutine.
+ */
+ public static final Type RETURN_ADDRESS = new Type(null, true);
+
+ /** A placeholder used by the analyzer for the second word position of a double-word type */
+ public static final Type TOP = new Type(null, true);
+
+ /**
+ * Represents a non-accessible value. Code cannot access the value this type
+ * represents. It occurs when bytecode reuses a local variable table
+ * position with non-mergable types. An example would be compiled code which
+ * uses the same position for a primitive type in one branch, and a reference type
+ * in another branch.
+ */
+ public static final Type BOGUS = new Type(null, true);
+
+ /** Represents the java.lang.Object reference type */
+ public static final Type OBJECT = lookupType("java.lang.Object");
+ /** Represents the java.io.Serializable reference type */
+ public static final Type SERIALIZABLE = lookupType("java.io.Serializable");
+ /** Represents the java.lang.Coneable reference type */
+ public static final Type CLONEABLE = lookupType("java.lang.Cloneable");
+ /** Represents the java.lang.Throwable reference type */
+ public static final Type THROWABLE = lookupType("java.lang.Throwable");
+
+ static {
+ prims.put(CtClass.doubleType, DOUBLE);
+ prims.put(CtClass.longType, LONG);
+ prims.put(CtClass.charType, CHAR);
+ prims.put(CtClass.shortType, SHORT);
+ prims.put(CtClass.intType, INTEGER);
+ prims.put(CtClass.floatType, FLOAT);
+ prims.put(CtClass.byteType, BYTE);
+ prims.put(CtClass.booleanType, BOOLEAN);
+ prims.put(CtClass.voidType, VOID);
+
+ }
+
+ /**
+ * Obtain the Type for a given class. If the class is a primitive,
+ * the the unique type instance for the primitive will be returned.
+ * Otherwise a new Type instance representing the class is returned.
+ *
+ * @param clazz The java class
+ * @return a type instance for this class
+ */
+ public static Type get(CtClass clazz) {
+ Type type = (Type)prims.get(clazz);
+ return type != null ? type : new Type(clazz);
+ }
+
+ private static Type lookupType(String name) {
+ try {
+ return new Type(ClassPool.getDefault().get(name));
+ } catch (NotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ Type(CtClass clazz) {
+ this(clazz, false);
+ }
+
+ private Type(CtClass clazz, boolean special) {
+ this.clazz = clazz;
+ this.special = special;
+ }
+
+ // Used to indicate a merge internally triggered a change
+ boolean popChanged() {
+ return false;
+ }
+
+ /**
+ * Gets the word size of this type. Double-word types, such as long and double
+ * will occupy two positions on the local variable table or stack.
+ *
+ * @return the number of words needed to hold this type
+ */
+ public int getSize() {
+ return clazz == CtClass.doubleType || clazz == CtClass.longType || this == TOP ? 2 : 1;
+ }
+
+ /**
+ * Returns the class this type represents. If the type is special, null will be returned.
+ *
+ * @return the class for this type, or null if special
+ */
+ public CtClass getCtClass() {
+ return clazz;
+ }
+
+ /**
+ * Returns whether or not this type is a normal java reference, i.e. it is or extends java.lang.Object.
+ *
+ * @return true if a java reference, false if a primitive or special
+ */
+ public boolean isReference() {
+ return !special && (clazz == null || !clazz.isPrimitive());
+ }
+
+ /**
+ * Returns whether or not the type is special. A special type is one that is either used
+ * for internal tracking, or is only used internally by the JVM.
+ *
+ * @return true if special, false if not
+ */
+ public boolean isSpecial() {
+ return special;
+ }
+
+ /**
+ * Returns whether or not this type is an array.
+ *
+ * @return true if an array, false if not
+ */
+ public boolean isArray() {
+ return clazz != null && clazz.isArray();
+ }
+
+ /**
+ * Returns the number of dimensions of this array. If the type is not an
+ * array zero is returned.
+ *
+ * @return zero if not an array, otherwise the number of array dimensions.
+ */
+ public int getDimensions() {
+ if (!isArray()) return 0;
+
+ String name = clazz.getName();
+ int pos = name.length() - 1;
+ int count = 0;
+ while (name.charAt(pos) == ']' ) {
+ pos -= 2;
+ count++;
+ }
+
+ return count;
+ }
+
+ /**
+ * Returns the array component if this type is an array. If the type
+ * is not an array null is returned.
+ *
+ * @return the array component if an array, otherwise null
+ */
+ public Type getComponent() {
+ if (this.clazz == null || !this.clazz.isArray())
+ return null;
+
+ CtClass component;
+ try {
+ component = this.clazz.getComponentType();
+ } catch (NotFoundException e) {
+ throw new RuntimeException(e);
+ }
+
+ Type type = (Type)prims.get(component);
+ return (type != null) ? type : new Type(component);
+ }
+
+ /**
+ * Determines whether this type is assignable, to the passed type.
+ * A type is assignable to another if it is either the same type, or
+ * a sub-type.
+ *
+ * @param type the type to test assignability to
+ * @return true if this is assignable to type, otherwise false
+ */
+ public boolean isAssignableFrom(Type type) {
+ if (this == type)
+ return true;
+
+ if ((type == UNINIT && isReference()) || this == UNINIT && type.isReference())
+ return true;
+
+ if (type instanceof MultiType)
+ return ((MultiType)type).isAssignableTo(this);
+
+ if (type instanceof MultiArrayType)
+ return ((MultiArrayType)type).isAssignableTo(this);
+
+
+ // Primitives and Special types must be identical
+ if (clazz == null || clazz.isPrimitive())
+ return false;
+
+ try {
+ return type.clazz.subtypeOf(clazz);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Finds the common base type, or interface which both this and the specified
+ * type can be assigned. If there is more than one possible answer, then a {@link MultiType},
+ * or a {@link MultiArrayType} is returned. Multi-types have special rules,
+ * and successive merges and assignment tests on them will alter their internal state,
+ * as well as other multi-types they have been merged with. This method is used by
+ * the data-flow analyzer to merge the type state from multiple branches.
+ *
+ * @param type the type to merge with
+ * @return the merged type
+ */
+ public Type merge(Type type) {
+ if (type == this)
+ return this;
+ if (type == null)
+ return this;
+ if (type == Type.UNINIT)
+ return this;
+ if (this == Type.UNINIT)
+ return type;
+
+ // Unequal primitives and special types can not be merged
+ if (! type.isReference() || ! this.isReference())
+ return BOGUS;
+
+ // Centralize merging of multi-interface types
+ if (type instanceof MultiType)
+ return type.merge(this);
+
+ if (type.isArray() && this.isArray())
+ return mergeArray(type);
+
+ try {
+ return mergeClasses(type);
+ } catch (NotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ Type getRootComponent(Type type) {
+ while (type.isArray())
+ type = type.getComponent();
+
+ return type;
+ }
+
+ private Type createArray(Type rootComponent, int dims) {
+ if (rootComponent instanceof MultiType)
+ return new MultiArrayType((MultiType) rootComponent, dims);
+
+ String name = arrayName(rootComponent.clazz.getName(), dims);
+
+ Type type;
+ try {
+ type = Type.get(getClassPool(rootComponent).get(name));
+ } catch (NotFoundException e) {
+ throw new RuntimeException(e);
+ }
+
+ return type;
+ }
+
+ String arrayName(String component, int dims) {
+ // Using char[] since we have no StringBuilder in JDK4, and StringBuffer is slow.
+ // Although, this is more efficient even if we did have one.
+ int i = component.length();
+ int size = i + dims * 2;
+ char[] string = new char[size];
+ component.getChars(0, i, string, 0);
+ while (i < size) {
+ string[i++] = '[';
+ string[i++] = ']';
+ }
+ component = new String(string);
+ return component;
+ }
+
+ private ClassPool getClassPool(Type rootComponent) {
+ ClassPool pool = rootComponent.clazz.getClassPool();
+ return pool != null ? pool : ClassPool.getDefault();
+ }
+
+ private Type mergeArray(Type type) {
+ Type typeRoot = getRootComponent(type);
+ Type thisRoot = getRootComponent(this);
+ int typeDims = type.getDimensions();
+ int thisDims = this.getDimensions();
+
+ // Array commponents can be merged when the dimensions are equal
+ if (typeDims == thisDims) {
+ Type mergedComponent = thisRoot.merge(typeRoot);
+
+ // If the components can not be merged (a primitive component mixed with a different type)
+ // then Object is the common type.
+ if (mergedComponent == Type.BOGUS)
+ return Type.OBJECT;
+
+ return createArray(mergedComponent, thisDims);
+ }
+
+ Type targetRoot;
+ int targetDims;
+
+ if (typeDims < thisDims) {
+ targetRoot = typeRoot;
+ targetDims = typeDims;
+ } else {
+ targetRoot = thisRoot;
+ targetDims = thisDims;
+ }
+
+ // Special case, arrays are cloneable and serializable, so prefer them when dimensions differ
+ if (eq(CLONEABLE.clazz, targetRoot.clazz) || eq(SERIALIZABLE.clazz, targetRoot.clazz))
+ return createArray(targetRoot, targetDims);
+
+ return createArray(OBJECT, targetDims);
+ }
+
+ private static CtClass findCommonSuperClass(CtClass one, CtClass two) throws NotFoundException {
+ CtClass deep = one;
+ CtClass shallow = two;
+ CtClass backupShallow = shallow;
+ CtClass backupDeep = deep;
+
+ // Phase 1 - Find the deepest hierarchy, set deep and shallow correctly
+ for (;;) {
+ // In case we get lucky, and find a match early
+ if (eq(deep, shallow) && deep.getSuperclass() != null)
+ return deep;
+
+ CtClass deepSuper = deep.getSuperclass();
+ CtClass shallowSuper = shallow.getSuperclass();
+
+ if (shallowSuper == null) {
+ // right, now reset shallow
+ shallow = backupShallow;
+ break;
+ }
+
+ if (deepSuper == null) {
+ // wrong, swap them, since deep is now useless, its our tmp before we swap it
+ deep = backupDeep;
+ backupDeep = backupShallow;
+ backupShallow = deep;
+
+ deep = shallow;
+ shallow = backupShallow;
+ break;
+ }
+
+ deep = deepSuper;
+ shallow = shallowSuper;
+ }
+
+ // Phase 2 - Move deepBackup up by (deep end - deep)
+ for (;;) {
+ deep = deep.getSuperclass();
+ if (deep == null)
+ break;
+
+ backupDeep = backupDeep.getSuperclass();
+ }
+
+ deep = backupDeep;
+
+ // Phase 3 - The hierarchy positions are now aligned
+ // The common super class is easy to find now
+ while (!eq(deep, shallow)) {
+ deep = deep.getSuperclass();
+ shallow = shallow.getSuperclass();
+ }
+
+ return deep;
+ }
+
+ private Type mergeClasses(Type type) throws NotFoundException {
+ CtClass superClass = findCommonSuperClass(this.clazz, type.clazz);
+
+ // If its Object, then try and find a common interface(s)
+ if (superClass.getSuperclass() == null) {
+ Map interfaces = findCommonInterfaces(type);
+ if (interfaces.size() == 1)
+ return new Type((CtClass) interfaces.values().iterator().next());
+ if (interfaces.size() > 1)
+ return new MultiType(interfaces);
+
+ // Only Object is in common
+ return new Type(superClass);
+ }
+
+ // Check for a common interface that is not on the found supertype
+ Map commonDeclared = findExclusiveDeclaredInterfaces(type, superClass);
+ if (commonDeclared.size() > 0) {
+ return new MultiType(commonDeclared, new Type(superClass));
+ }
+
+ return new Type(superClass);
+ }
+
+ private Map findCommonInterfaces(Type type) {
+ Map typeMap = getAllInterfaces(type.clazz, null);
+ Map thisMap = getAllInterfaces(this.clazz, null);
+
+ return findCommonInterfaces(typeMap, thisMap);
+ }
+
+ private Map findExclusiveDeclaredInterfaces(Type type, CtClass exclude) {
+ Map typeMap = getDeclaredInterfaces(type.clazz, null);
+ Map thisMap = getDeclaredInterfaces(this.clazz, null);
+ Map excludeMap = getAllInterfaces(exclude, null);
+
+ Iterator i = excludeMap.keySet().iterator();
+ while (i.hasNext()) {
+ Object intf = i.next();
+ typeMap.remove(intf);
+ thisMap.remove(intf);
+ }
+
+ return findCommonInterfaces(typeMap, thisMap);
+ }
+
+
+ Map findCommonInterfaces(Map typeMap, Map alterMap) {
+ Iterator i = alterMap.keySet().iterator();
+ while (i.hasNext()) {
+ if (! typeMap.containsKey(i.next()))
+ i.remove();
+ }
+
+ // Reduce to subinterfaces
+ // This does not need to be recursive since we make a copy,
+ // and that copy contains all super types for the whole hierarchy
+ i = new ArrayList(alterMap.values()).iterator();
+ while (i.hasNext()) {
+ CtClass intf = (CtClass) i.next();
+ CtClass[] interfaces;
+ try {
+ interfaces = intf.getInterfaces();
+ } catch (NotFoundException e) {
+ throw new RuntimeException(e);
+ }
+
+ for (int c = 0; c < interfaces.length; c++)
+ alterMap.remove(interfaces[c].getName());
+ }
+
+ return alterMap;
+ }
+
+ Map getAllInterfaces(CtClass clazz, Map map) {
+ if (map == null)
+ map = new HashMap();
+
+ if (clazz.isInterface())
+ map.put(clazz.getName(), clazz);
+ do {
+ try {
+ CtClass[] interfaces = clazz.getInterfaces();
+ for (int i = 0; i < interfaces.length; i++) {
+ CtClass intf = interfaces[i];
+ map.put(intf.getName(), intf);
+ getAllInterfaces(intf, map);
+ }
+
+ clazz = clazz.getSuperclass();
+ } catch (NotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ } while (clazz != null);
+
+ return map;
+ }
+
+ Map getDeclaredInterfaces(CtClass clazz, Map map) {
+ if (map == null)
+ map = new HashMap();
+
+ if (clazz.isInterface())
+ map.put(clazz.getName(), clazz);
+
+ CtClass[] interfaces;
+ try {
+ interfaces = clazz.getInterfaces();
+ } catch (NotFoundException e) {
+ throw new RuntimeException(e);
+ }
+
+ for (int i = 0; i < interfaces.length; i++) {
+ CtClass intf = interfaces[i];
+ map.put(intf.getName(), intf);
+ getDeclaredInterfaces(intf, map);
+ }
+
+ return map;
+ }
+
+ public boolean equals(Object o) {
+ if (! (o instanceof Type))
+ return false;
+
+ return o.getClass() == getClass() && eq(clazz, ((Type)o).clazz);
+ }
+
+ static boolean eq(CtClass one, CtClass two) {
+ return one == two || (one != null && two != null && one.getName().equals(two.getName()));
+ }
+
+ public String toString() {
+ if (this == BOGUS)
+ return "BOGUS";
+ if (this == UNINIT)
+ return "UNINIT";
+ if (this == RETURN_ADDRESS)
+ return "RETURN ADDRESS";
+ if (this == TOP)
+ return "TOP";
+
+ return clazz == null ? "null" : clazz.getName();
+ }
+}
diff --git a/src/main/javassist/bytecode/analysis/Util.java b/src/main/javassist/bytecode/analysis/Util.java
new file mode 100644
index 0000000..a8cdfcc
--- /dev/null
+++ b/src/main/javassist/bytecode/analysis/Util.java
@@ -0,0 +1,47 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+package javassist.bytecode.analysis;
+
+import javassist.bytecode.CodeIterator;
+import javassist.bytecode.Opcode;
+
+/**
+ * A set of common utility methods.
+ *
+ * @author Jason T. Greene
+ */
+public class Util implements Opcode {
+ public static int getJumpTarget(int pos, CodeIterator iter) {
+ int opcode = iter.byteAt(pos);
+ pos += (opcode == JSR_W || opcode == GOTO_W) ? iter.s32bitAt(pos + 1) : iter.s16bitAt(pos + 1);
+ return pos;
+ }
+
+ public static boolean isJumpInstruction(int opcode) {
+ return (opcode >= IFEQ && opcode <= JSR) || opcode == IFNULL || opcode == IFNONNULL || opcode == JSR_W || opcode == GOTO_W;
+ }
+
+ public static boolean isGoto(int opcode) {
+ return opcode == GOTO || opcode == GOTO_W;
+ }
+
+ public static boolean isJsr(int opcode) {
+ return opcode == JSR || opcode == JSR_W;
+ }
+
+ public static boolean isReturn(int opcode) {
+ return (opcode >= IRETURN && opcode <= RETURN);
+ }
+}
diff --git a/src/main/javassist/bytecode/analysis/package.html b/src/main/javassist/bytecode/analysis/package.html
new file mode 100644
index 0000000..b141670
--- /dev/null
+++ b/src/main/javassist/bytecode/analysis/package.html
@@ -0,0 +1,19 @@
+<html>
+<body>
+Bytecode Analysis API.
+
+<p>This package provides an API for performing data-flow analysis on a method's bytecode.
+This allows the user to determine the type state of the stack and local variable table
+at the start of every instruction. In addition this API can be used to validate
+bytecode, find dead bytecode, and identify unnecessary checkcasts.
+
+<p>The users of this package must know the specifications of
+class file and Java bytecode. For more details, read this book:
+
+<ul>Tim Lindholm and Frank Yellin,
+"The Java Virtual Machine Specification 2nd Ed.",
+Addison-Wesley, 1999.
+</ul>
+
+</body>
+</html>
diff --git a/src/main/javassist/bytecode/annotation/Annotation.java b/src/main/javassist/bytecode/annotation/Annotation.java
new file mode 100644
index 0000000..b48cc8e
--- /dev/null
+++ b/src/main/javassist/bytecode/annotation/Annotation.java
@@ -0,0 +1,347 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 2004 Bill Burke. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode.annotation;
+
+import javassist.bytecode.ConstPool;
+import javassist.bytecode.Descriptor;
+import javassist.ClassPool;
+import javassist.CtClass;
+import javassist.CtMethod;
+import javassist.NotFoundException;
+
+import java.io.IOException;
+import java.util.LinkedHashMap;
+import java.util.Set;
+import java.util.Iterator;
+
+/**
+ * The <code>annotation</code> structure.
+ *
+ * <p>An instance of this class is returned by
+ * <code>getAnnotations()</code> in <code>AnnotationsAttribute</code>
+ * or in <code>ParameterAnnotationsAttribute</code>.
+ *
+ * @see javassist.bytecode.AnnotationsAttribute#getAnnotations()
+ * @see javassist.bytecode.ParameterAnnotationsAttribute#getAnnotations()
+ * @see MemberValue
+ * @see MemberValueVisitor
+ * @see AnnotationsWriter
+ *
+ * @author <a href="mailto:bill@jboss.org">Bill Burke</a>
+ * @author Shigeru Chiba
+ * @author <a href="mailto:adrian@jboss.org">Adrian Brock</a>
+ */
+public class Annotation {
+ static class Pair {
+ int name;
+ MemberValue value;
+ }
+
+ ConstPool pool;
+ int typeIndex;
+ LinkedHashMap members; // this sould be LinkedHashMap
+ // but it is not supported by JDK 1.3.
+
+ /**
+ * Constructs an annotation including no members. A member can be
+ * later added to the created annotation by <code>addMemberValue()</code>.
+ *
+ * @param type the index into the constant pool table.
+ * the entry at that index must be the
+ * <code>CONSTANT_Utf8_Info</code> structure
+ * repreenting the name of the annotation interface type.
+ * @param cp the constant pool table.
+ *
+ * @see #addMemberValue(String, MemberValue)
+ */
+ public Annotation(int type, ConstPool cp) {
+ pool = cp;
+ typeIndex = type;
+ members = null;
+ }
+
+ /**
+ * Constructs an annotation including no members. A member can be
+ * later added to the created annotation by <code>addMemberValue()</code>.
+ *
+ * @param typeName the name of the annotation interface type.
+ * @param cp the constant pool table.
+ *
+ * @see #addMemberValue(String, MemberValue)
+ */
+ public Annotation(String typeName, ConstPool cp) {
+ this(cp.addUtf8Info(Descriptor.of(typeName)), cp);
+ }
+
+ /**
+ * Constructs an annotation that can be accessed through the interface
+ * represented by <code>clazz</code>. The values of the members are
+ * not specified.
+ *
+ * @param cp the constant pool table.
+ * @param clazz the interface.
+ * @throws NotFoundException when the clazz is not found
+ */
+ public Annotation(ConstPool cp, CtClass clazz)
+ throws NotFoundException
+ {
+ // todo Enums are not supported right now.
+ this(cp.addUtf8Info(Descriptor.of(clazz.getName())), cp);
+
+ if (!clazz.isInterface())
+ throw new RuntimeException(
+ "Only interfaces are allowed for Annotation creation.");
+
+ CtMethod methods[] = clazz.getDeclaredMethods();
+ if (methods.length > 0) {
+ members = new LinkedHashMap();
+ }
+
+ for (int i = 0; i < methods.length; i++) {
+ CtClass returnType = methods[i].getReturnType();
+ addMemberValue(methods[i].getName(),
+ createMemberValue(cp, returnType));
+
+ }
+ }
+
+ /**
+ * Makes an instance of <code>MemberValue</code>.
+ *
+ * @param cp the constant pool table.
+ * @param type the type of the member.
+ * @return the member value
+ * @throws NotFoundException when the type is not found
+ */
+ public static MemberValue createMemberValue(ConstPool cp, CtClass type)
+ throws NotFoundException
+ {
+ if (type == CtClass.booleanType)
+ return new BooleanMemberValue(cp);
+ else if (type == CtClass.byteType)
+ return new ByteMemberValue(cp);
+ else if (type == CtClass.charType)
+ return new CharMemberValue(cp);
+ else if (type == CtClass.shortType)
+ return new ShortMemberValue(cp);
+ else if (type == CtClass.intType)
+ return new IntegerMemberValue(cp);
+ else if (type == CtClass.longType)
+ return new LongMemberValue(cp);
+ else if (type == CtClass.floatType)
+ return new FloatMemberValue(cp);
+ else if (type == CtClass.doubleType)
+ return new DoubleMemberValue(cp);
+ else if (type.getName().equals("java.lang.Class"))
+ return new ClassMemberValue(cp);
+ else if (type.getName().equals("java.lang.String"))
+ return new StringMemberValue(cp);
+ else if (type.isArray()) {
+ CtClass arrayType = type.getComponentType();
+ MemberValue member = createMemberValue(cp, arrayType);
+ return new ArrayMemberValue(member, cp);
+ }
+ else if (type.isInterface()) {
+ Annotation info = new Annotation(cp, type);
+ return new AnnotationMemberValue(info, cp);
+ }
+ else {
+ // treat as enum. I know this is not typed,
+ // but JBoss has an Annotation Compiler for JDK 1.4
+ // and I want it to work with that. - Bill Burke
+ EnumMemberValue emv = new EnumMemberValue(cp);
+ emv.setType(type.getName());
+ return emv;
+ }
+ }
+
+ /**
+ * Adds a new member.
+ *
+ * @param nameIndex the index into the constant pool table.
+ * The entry at that index must be
+ * a <code>CONSTANT_Utf8_info</code> structure.
+ * structure representing the member name.
+ * @param value the member value.
+ */
+ public void addMemberValue(int nameIndex, MemberValue value) {
+ Pair p = new Pair();
+ p.name = nameIndex;
+ p.value = value;
+ addMemberValue(p);
+ }
+
+ /**
+ * Adds a new member.
+ *
+ * @param name the member name.
+ * @param value the member value.
+ */
+ public void addMemberValue(String name, MemberValue value) {
+ Pair p = new Pair();
+ p.name = pool.addUtf8Info(name);
+ p.value = value;
+ if (members == null)
+ members = new LinkedHashMap();
+
+ members.put(name, p);
+ }
+
+ private void addMemberValue(Pair pair) {
+ String name = pool.getUtf8Info(pair.name);
+ if (members == null)
+ members = new LinkedHashMap();
+
+ members.put(name, pair);
+ }
+
+ /**
+ * Returns a string representation of the annotation.
+ */
+ public String toString() {
+ StringBuffer buf = new StringBuffer("@");
+ buf.append(getTypeName());
+ if (members != null) {
+ buf.append("(");
+ Iterator mit = members.keySet().iterator();
+ while (mit.hasNext()) {
+ String name = (String)mit.next();
+ buf.append(name).append("=").append(getMemberValue(name));
+ if (mit.hasNext())
+ buf.append(", ");
+ }
+ buf.append(")");
+ }
+
+ return buf.toString();
+ }
+
+ /**
+ * Obtains the name of the annotation type.
+ *
+ * @return the type name
+ */
+ public String getTypeName() {
+ return Descriptor.toClassName(pool.getUtf8Info(typeIndex));
+ }
+
+ /**
+ * Obtains all the member names.
+ *
+ * @return null if no members are defined.
+ */
+ public Set getMemberNames() {
+ if (members == null)
+ return null;
+ else
+ return members.keySet();
+ }
+
+ /**
+ * Obtains the member value with the given name.
+ *
+ * <p>If this annotation does not have a value for the
+ * specified member,
+ * this method returns null. It does not return a
+ * <code>MemberValue</code> with the default value.
+ * The default value can be obtained from the annotation type.
+ *
+ * @param name the member name
+ * @return null if the member cannot be found or if the value is
+ * the default value.
+ *
+ * @see javassist.bytecode.AnnotationDefaultAttribute
+ */
+ public MemberValue getMemberValue(String name) {
+ if (members == null)
+ return null;
+ else {
+ Pair p = (Pair)members.get(name);
+ if (p == null)
+ return null;
+ else
+ return p.value;
+ }
+ }
+
+ /**
+ * Constructs an annotation-type object representing this annotation.
+ * For example, if this annotation represents <code>@Author</code>,
+ * this method returns an <code>Author</code> object.
+ *
+ * @param cl class loader for loading an annotation type.
+ * @param cp class pool for obtaining class files.
+ * @return the annotation
+ * @throws ClassNotFoundException if the class cannot found.
+ * @throws NoSuchClassError if the class linkage fails.
+ */
+ public Object toAnnotationType(ClassLoader cl, ClassPool cp)
+ throws ClassNotFoundException, NoSuchClassError
+ {
+ return AnnotationImpl.make(cl,
+ MemberValue.loadClass(cl, getTypeName()),
+ cp, this);
+ }
+
+ /**
+ * Writes this annotation.
+ *
+ * @param writer the output.
+ * @throws IOException for an error during the write
+ */
+ public void write(AnnotationsWriter writer) throws IOException {
+ String typeName = pool.getUtf8Info(typeIndex);
+ if (members == null) {
+ writer.annotation(typeName, 0);
+ return;
+ }
+
+ writer.annotation(typeName, members.size());
+ Iterator it = members.values().iterator();
+ while (it.hasNext()) {
+ Pair pair = (Pair)it.next();
+ writer.memberValuePair(pair.name);
+ pair.value.write(writer);
+ }
+ }
+
+ /**
+ * Returns true if the given object represents the same annotation
+ * as this object. The equality test checks the member values.
+ */
+ public boolean equals(Object obj) {
+ if (obj == this)
+ return true;
+ if (obj == null || obj instanceof Annotation == false)
+ return false;
+
+ Annotation other = (Annotation) obj;
+
+ if (getTypeName().equals(other.getTypeName()) == false)
+ return false;
+
+ LinkedHashMap otherMembers = other.members;
+ if (members == otherMembers)
+ return true;
+ else if (members == null)
+ return otherMembers == null;
+ else
+ if (otherMembers == null)
+ return false;
+ else
+ return members.equals(otherMembers);
+ }
+}
diff --git a/src/main/javassist/bytecode/annotation/AnnotationImpl.java b/src/main/javassist/bytecode/annotation/AnnotationImpl.java
new file mode 100644
index 0000000..dfd23bb
--- /dev/null
+++ b/src/main/javassist/bytecode/annotation/AnnotationImpl.java
@@ -0,0 +1,304 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode.annotation;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+import javassist.ClassPool;
+import javassist.CtClass;
+import javassist.NotFoundException;
+import javassist.bytecode.AnnotationDefaultAttribute;
+import javassist.bytecode.ClassFile;
+import javassist.bytecode.MethodInfo;
+
+/**
+ * Internal-use only. This is a helper class internally used for implementing
+ * <code>toAnnotationType()</code> in <code>Annotation</code>.
+ *
+ * @author Shigeru Chiba
+ * @author <a href="mailto:bill@jboss.org">Bill Burke</a>
+ * @author <a href="mailto:adrian@jboss.org">Adrian Brock</a>
+ */
+public class AnnotationImpl implements InvocationHandler {
+ private static final String JDK_ANNOTATION_CLASS_NAME = "java.lang.annotation.Annotation";
+ private static Method JDK_ANNOTATION_TYPE_METHOD = null;
+
+ private Annotation annotation;
+ private ClassPool pool;
+ private ClassLoader classLoader;
+ private transient Class annotationType;
+ private transient int cachedHashCode = Integer.MIN_VALUE;
+
+ static {
+ // Try to resolve the JDK annotation type method
+ try {
+ Class clazz = Class.forName(JDK_ANNOTATION_CLASS_NAME);
+ JDK_ANNOTATION_TYPE_METHOD = clazz.getMethod("annotationType", (Class[])null);
+ }
+ catch (Exception ignored) {
+ // Probably not JDK5+
+ }
+ }
+
+ /**
+ * Constructs an annotation object.
+ *
+ * @param cl class loader for obtaining annotation types.
+ * @param clazz the annotation type.
+ * @param cp class pool for containing an annotation
+ * type (or null).
+ * @param anon the annotation.
+ * @return the annotation
+ */
+ public static Object make(ClassLoader cl, Class clazz, ClassPool cp,
+ Annotation anon) {
+ AnnotationImpl handler = new AnnotationImpl(anon, cp, cl);
+ return Proxy.newProxyInstance(cl, new Class[] { clazz }, handler);
+ }
+
+ private AnnotationImpl(Annotation a, ClassPool cp, ClassLoader loader) {
+ annotation = a;
+ pool = cp;
+ classLoader = loader;
+ }
+
+ /**
+ * Obtains the name of the annotation type.
+ *
+ * @return the type name
+ */
+ public String getTypeName() {
+ return annotation.getTypeName();
+ }
+
+ /**
+ * Get the annotation type
+ *
+ * @return the annotation class
+ * @throws NoClassDefFoundError when the class could not loaded
+ */
+ private Class getAnnotationType() {
+ if (annotationType == null) {
+ String typeName = annotation.getTypeName();
+ try {
+ annotationType = classLoader.loadClass(typeName);
+ }
+ catch (ClassNotFoundException e) {
+ NoClassDefFoundError error = new NoClassDefFoundError("Error loading annotation class: " + typeName);
+ error.setStackTrace(e.getStackTrace());
+ throw error;
+ }
+ }
+ return annotationType;
+ }
+
+ /**
+ * Obtains the internal data structure representing the annotation.
+ *
+ * @return the annotation
+ */
+ public Annotation getAnnotation() {
+ return annotation;
+ }
+
+ /**
+ * Executes a method invocation on a proxy instance.
+ * The implementations of <code>toString()</code>, <code>equals()</code>,
+ * and <code>hashCode()</code> are directly supplied by the
+ * <code>AnnotationImpl</code>. The <code>annotationType()</code> method
+ * is also available on the proxy instance.
+ */
+ public Object invoke(Object proxy, Method method, Object[] args)
+ throws Throwable
+ {
+ String name = method.getName();
+ if (Object.class == method.getDeclaringClass()) {
+ if ("equals".equals(name)) {
+ Object obj = args[0];
+ return new Boolean(checkEquals(obj));
+ }
+ else if ("toString".equals(name))
+ return annotation.toString();
+ else if ("hashCode".equals(name))
+ return new Integer(hashCode());
+ }
+ else if ("annotationType".equals(name)
+ && method.getParameterTypes().length == 0)
+ return getAnnotationType();
+
+ MemberValue mv = annotation.getMemberValue(name);
+ if (mv == null)
+ return getDefault(name, method);
+ else
+ return mv.getValue(classLoader, pool, method);
+ }
+
+ private Object getDefault(String name, Method method)
+ throws ClassNotFoundException, RuntimeException
+ {
+ String classname = annotation.getTypeName();
+ if (pool != null) {
+ try {
+ CtClass cc = pool.get(classname);
+ ClassFile cf = cc.getClassFile2();
+ MethodInfo minfo = cf.getMethod(name);
+ if (minfo != null) {
+ AnnotationDefaultAttribute ainfo
+ = (AnnotationDefaultAttribute)
+ minfo.getAttribute(AnnotationDefaultAttribute.tag);
+ if (ainfo != null) {
+ MemberValue mv = ainfo.getDefaultValue();
+ return mv.getValue(classLoader, pool, method);
+ }
+ }
+ }
+ catch (NotFoundException e) {
+ throw new RuntimeException("cannot find a class file: "
+ + classname);
+ }
+ }
+
+ throw new RuntimeException("no default value: " + classname + "."
+ + name + "()");
+ }
+
+ /**
+ * Returns a hash code value for this object.
+ */
+ public int hashCode() {
+ if (cachedHashCode == Integer.MIN_VALUE) {
+ int hashCode = 0;
+
+ // Load the annotation class
+ getAnnotationType();
+
+ Method[] methods = annotationType.getDeclaredMethods();
+ for (int i = 0; i < methods.length; ++ i) {
+ String name = methods[i].getName();
+ int valueHashCode = 0;
+
+ // Get the value
+ MemberValue mv = annotation.getMemberValue(name);
+ Object value = null;
+ try {
+ if (mv != null)
+ value = mv.getValue(classLoader, pool, methods[i]);
+ if (value == null)
+ value = getDefault(name, methods[i]);
+ }
+ catch (RuntimeException e) {
+ throw e;
+ }
+ catch (Exception e) {
+ throw new RuntimeException("Error retrieving value " + name + " for annotation " + annotation.getTypeName(), e);
+ }
+
+ // Calculate the hash code
+ if (value != null) {
+ if (value.getClass().isArray())
+ valueHashCode = arrayHashCode(value);
+ else
+ valueHashCode = value.hashCode();
+ }
+ hashCode += 127 * name.hashCode() ^ valueHashCode;
+ }
+
+ cachedHashCode = hashCode;
+ }
+ return cachedHashCode;
+ }
+
+ /**
+ * Check that another annotation equals ourselves.
+ *
+ * @param obj the other annotation
+ * @return the true when equals false otherwise
+ * @throws Exception for any problem
+ */
+ private boolean checkEquals(Object obj) throws Exception {
+ if (obj == null)
+ return false;
+
+ // Optimization when the other is one of ourselves
+ if (obj instanceof Proxy) {
+ InvocationHandler ih = Proxy.getInvocationHandler(obj);
+ if (ih instanceof AnnotationImpl) {
+ AnnotationImpl other = (AnnotationImpl) ih;
+ return annotation.equals(other.annotation);
+ }
+ }
+
+ Class otherAnnotationType = (Class) JDK_ANNOTATION_TYPE_METHOD.invoke(obj, (Object[])null);
+ if (getAnnotationType().equals(otherAnnotationType) == false)
+ return false;
+
+ Method[] methods = annotationType.getDeclaredMethods();
+ for (int i = 0; i < methods.length; ++ i) {
+ String name = methods[i].getName();
+
+ // Get the value
+ MemberValue mv = annotation.getMemberValue(name);
+ Object value = null;
+ Object otherValue = null;
+ try {
+ if (mv != null)
+ value = mv.getValue(classLoader, pool, methods[i]);
+ if (value == null)
+ value = getDefault(name, methods[i]);
+ otherValue = methods[i].invoke(obj, (Object[])null);
+ }
+ catch (RuntimeException e) {
+ throw e;
+ }
+ catch (Exception e) {
+ throw new RuntimeException("Error retrieving value " + name + " for annotation " + annotation.getTypeName(), e);
+ }
+
+ if (value == null && otherValue != null)
+ return false;
+ if (value != null && value.equals(otherValue) == false)
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Calculates the hashCode of an array using the same
+ * algorithm as java.util.Arrays.hashCode()
+ *
+ * @param object the object
+ * @return the hashCode
+ */
+ private static int arrayHashCode(Object object)
+ {
+ if (object == null)
+ return 0;
+
+ int result = 1;
+
+ Object[] array = (Object[]) object;
+ for (int i = 0; i < array.length; ++i) {
+ int elementHashCode = 0;
+ if (array[i] != null)
+ elementHashCode = array[i].hashCode();
+ result = 31 * result + elementHashCode;
+ }
+ return result;
+ }
+}
diff --git a/src/main/javassist/bytecode/annotation/AnnotationMemberValue.java b/src/main/javassist/bytecode/annotation/AnnotationMemberValue.java
new file mode 100644
index 0000000..368c82a
--- /dev/null
+++ b/src/main/javassist/bytecode/annotation/AnnotationMemberValue.java
@@ -0,0 +1,95 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 2004 Bill Burke. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+package javassist.bytecode.annotation;
+
+import javassist.ClassPool;
+import javassist.bytecode.ConstPool;
+import java.io.IOException;
+import java.lang.reflect.Method;
+
+/**
+ * Nested annotation.
+ *
+ * @author <a href="mailto:bill@jboss.org">Bill Burke</a>
+ * @author Shigeru Chiba
+ */
+public class AnnotationMemberValue extends MemberValue {
+ Annotation value;
+
+ /**
+ * Constructs an annotation member. The initial value is not specified.
+ */
+ public AnnotationMemberValue(ConstPool cp) {
+ this(null, cp);
+ }
+
+ /**
+ * Constructs an annotation member. The initial value is specified by
+ * the first parameter.
+ */
+ public AnnotationMemberValue(Annotation a, ConstPool cp) {
+ super('@', cp);
+ value = a;
+ }
+
+ Object getValue(ClassLoader cl, ClassPool cp, Method m)
+ throws ClassNotFoundException
+ {
+ return AnnotationImpl.make(cl, getType(cl), cp, value);
+ }
+
+ Class getType(ClassLoader cl) throws ClassNotFoundException {
+ if (value == null)
+ throw new ClassNotFoundException("no type specified");
+ else
+ return loadClass(cl, value.getTypeName());
+ }
+
+ /**
+ * Obtains the value.
+ */
+ public Annotation getValue() {
+ return value;
+ }
+
+ /**
+ * Sets the value of this member.
+ */
+ public void setValue(Annotation newValue) {
+ value = newValue;
+ }
+
+ /**
+ * Obtains the string representation of this object.
+ */
+ public String toString() {
+ return value.toString();
+ }
+
+ /**
+ * Writes the value.
+ */
+ public void write(AnnotationsWriter writer) throws IOException {
+ writer.annotationValue();
+ value.write(writer);
+ }
+
+ /**
+ * Accepts a visitor.
+ */
+ public void accept(MemberValueVisitor visitor) {
+ visitor.visitAnnotationMemberValue(this);
+ }
+}
diff --git a/src/main/javassist/bytecode/annotation/AnnotationsWriter.java b/src/main/javassist/bytecode/annotation/AnnotationsWriter.java
new file mode 100644
index 0000000..f435d8f
--- /dev/null
+++ b/src/main/javassist/bytecode/annotation/AnnotationsWriter.java
@@ -0,0 +1,353 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode.annotation;
+
+import java.io.*;
+
+import javassist.bytecode.ByteArray;
+import javassist.bytecode.ConstPool;
+
+/**
+ * A convenience class for constructing a
+ * <code>..Annotations_attribute</code>.
+ * See the source code of the <code>AnnotationsAttribute.Copier</code> class.
+ *
+ * <p>The following code snippet is an example of use of this class:
+ *
+ * <ul><pre>
+ * ConstPool pool = ...;
+ * output = new ByteArrayOutputStream();
+ * writer = new AnnotationsWriter(output, pool);
+ *
+ * writer.numAnnotations(1);
+ * writer.annotation("Author", 2);
+ * writer.memberValuePair("name");
+ * writer.constValueIndex("chiba");
+ * writer.memberValuePair("address");
+ * writer.constValueIndex("tokyo");
+ *
+ * writer.close();
+ * byte[] attribute_info = output.toByteArray();
+ * AnnotationsAttribute anno
+ * = new AnnotationsAttribute(pool, AnnotationsAttribute.visibleTag,
+ * attribute_info);
+ * </pre></ul>
+ *
+ * <p>The code snippet above generates the annotation attribute
+ * corresponding to this annotation:
+ *
+ * <ul><pre>
+ * @Author(name = "chiba", address = "tokyo")
+ * </pre></ul>
+ *
+ * @see javassist.bytecode.AnnotationsAttribute
+ * @see javassist.bytecode.ParameterAnnotationsAttribute
+ */
+public class AnnotationsWriter {
+ private OutputStream output;
+ private ConstPool pool;
+
+ /**
+ * Constructs with the given output stream.
+ *
+ * @param os the output stream.
+ * @param cp the constant pool.
+ */
+ public AnnotationsWriter(OutputStream os, ConstPool cp) {
+ output = os;
+ pool = cp;
+ }
+
+ /**
+ * Obtains the constant pool given to the constructor.
+ */
+ public ConstPool getConstPool() {
+ return pool;
+ }
+
+ /**
+ * Closes the output stream.
+ *
+ */
+ public void close() throws IOException {
+ output.close();
+ }
+
+ /**
+ * Writes <code>num_parameters</code> in
+ * <code>Runtime(In)VisibleParameterAnnotations_attribute</code>.
+ * This method must be followed by <code>num</code> calls to
+ * <code>numAnnotations()</code>.
+ */
+ public void numParameters(int num) throws IOException {
+ output.write(num);
+ }
+
+ /**
+ * Writes <code>num_annotations</code> in
+ * <code>Runtime(In)VisibleAnnotations_attribute</code>.
+ * This method must be followed by <code>num</code> calls to
+ * <code>annotation()</code>.
+ */
+ public void numAnnotations(int num) throws IOException {
+ write16bit(num);
+ }
+
+ /**
+ * Writes <code>annotation</code>.
+ * This method must be followed by <code>numMemberValuePairs</code>
+ * calls to <code>memberValuePair()</code>.
+ *
+ * @param type the annotation interface name.
+ * @param numMemberValuePairs <code>num_member_value_pairs</code>
+ * in <code>annotation</code>.
+ */
+ public void annotation(String type, int numMemberValuePairs)
+ throws IOException
+ {
+ annotation(pool.addUtf8Info(type), numMemberValuePairs);
+ }
+
+ /**
+ * Writes <code>annotation</code>.
+ * This method must be followed by <code>numMemberValuePairs</code>
+ * calls to <code>memberValuePair()</code>.
+ *
+ * @param typeIndex <code>type_index</code> in <code>annotation</code>.
+ * @param numMemberValuePairs <code>num_member_value_pairs</code>
+ * in <code>annotation</code>.
+ */
+ public void annotation(int typeIndex, int numMemberValuePairs)
+ throws IOException
+ {
+ write16bit(typeIndex);
+ write16bit(numMemberValuePairs);
+ }
+
+ /**
+ * Writes an element of a <code>member_value_pairs</code> array
+ * in <code>annotation</code>.
+ * This method must be followed by a
+ * call to <code>constValueIndex()</code>, <code>enumConstValue()</code>,
+ * etc.
+ *
+ * @param memberName the name of the annotation type member.
+ */
+ public void memberValuePair(String memberName) throws IOException {
+ memberValuePair(pool.addUtf8Info(memberName));
+ }
+
+ /**
+ * Writes an element of a <code>member_value_pairs</code> array
+ * in <code>annotation</code>.
+ * This method must be followed by a
+ * call to <code>constValueIndex()</code>, <code>enumConstValue()</code>,
+ * etc.
+ *
+ * @param memberNameIndex <code>member_name_index</code>
+ * in <code>member_value_pairs</code> array.
+ */
+ public void memberValuePair(int memberNameIndex) throws IOException {
+ write16bit(memberNameIndex);
+ }
+
+ /**
+ * Writes <code>tag</code> and <code>const_value_index</code>
+ * in <code>member_value</code>.
+ *
+ * @param value the constant value.
+ */
+ public void constValueIndex(boolean value) throws IOException {
+ constValueIndex('Z', pool.addIntegerInfo(value ? 1 : 0));
+ }
+
+ /**
+ * Writes <code>tag</code> and <code>const_value_index</code>
+ * in <code>member_value</code>.
+ *
+ * @param value the constant value.
+ */
+ public void constValueIndex(byte value) throws IOException {
+ constValueIndex('B', pool.addIntegerInfo(value));
+ }
+
+ /**
+ * Writes <code>tag</code> and <code>const_value_index</code>
+ * in <code>member_value</code>.
+ *
+ * @param value the constant value.
+ */
+ public void constValueIndex(char value) throws IOException {
+ constValueIndex('C', pool.addIntegerInfo(value));
+ }
+
+ /**
+ * Writes <code>tag</code> and <code>const_value_index</code>
+ * in <code>member_value</code>.
+ *
+ * @param value the constant value.
+ */
+ public void constValueIndex(short value) throws IOException {
+ constValueIndex('S', pool.addIntegerInfo(value));
+ }
+
+ /**
+ * Writes <code>tag</code> and <code>const_value_index</code>
+ * in <code>member_value</code>.
+ *
+ * @param value the constant value.
+ */
+ public void constValueIndex(int value) throws IOException {
+ constValueIndex('I', pool.addIntegerInfo(value));
+ }
+
+ /**
+ * Writes <code>tag</code> and <code>const_value_index</code>
+ * in <code>member_value</code>.
+ *
+ * @param value the constant value.
+ */
+ public void constValueIndex(long value) throws IOException {
+ constValueIndex('J', pool.addLongInfo(value));
+ }
+
+ /**
+ * Writes <code>tag</code> and <code>const_value_index</code>
+ * in <code>member_value</code>.
+ *
+ * @param value the constant value.
+ */
+ public void constValueIndex(float value) throws IOException {
+ constValueIndex('F', pool.addFloatInfo(value));
+ }
+
+ /**
+ * Writes <code>tag</code> and <code>const_value_index</code>
+ * in <code>member_value</code>.
+ *
+ * @param value the constant value.
+ */
+ public void constValueIndex(double value) throws IOException {
+ constValueIndex('D', pool.addDoubleInfo(value));
+ }
+
+ /**
+ * Writes <code>tag</code> and <code>const_value_index</code>
+ * in <code>member_value</code>.
+ *
+ * @param value the constant value.
+ */
+ public void constValueIndex(String value) throws IOException {
+ constValueIndex('s', pool.addUtf8Info(value));
+ }
+
+ /**
+ * Writes <code>tag</code> and <code>const_value_index</code>
+ * in <code>member_value</code>.
+ *
+ * @param tag <code>tag</code> in <code>member_value</code>.
+ * @param index <code>const_value_index</code>
+ * in <code>member_value</code>.
+ */
+ public void constValueIndex(int tag, int index)
+ throws IOException
+ {
+ output.write(tag);
+ write16bit(index);
+ }
+
+ /**
+ * Writes <code>tag</code> and <code>enum_const_value</code>
+ * in <code>member_value</code>.
+ *
+ * @param typeName the type name of the enum constant.
+ * @param constName the simple name of the enum constant.
+ */
+ public void enumConstValue(String typeName, String constName)
+ throws IOException
+ {
+ enumConstValue(pool.addUtf8Info(typeName),
+ pool.addUtf8Info(constName));
+ }
+
+ /**
+ * Writes <code>tag</code> and <code>enum_const_value</code>
+ * in <code>member_value</code>.
+ *
+ * @param typeNameIndex <code>type_name_index</code>
+ * in <code>member_value</code>.
+ * @param constNameIndex <code>const_name_index</code>
+ * in <code>member_value</code>.
+ */
+ public void enumConstValue(int typeNameIndex, int constNameIndex)
+ throws IOException
+ {
+ output.write('e');
+ write16bit(typeNameIndex);
+ write16bit(constNameIndex);
+ }
+
+ /**
+ * Writes <code>tag</code> and <code>class_info_index</code>
+ * in <code>member_value</code>.
+ *
+ * @param name the class name.
+ */
+ public void classInfoIndex(String name) throws IOException {
+ classInfoIndex(pool.addUtf8Info(name));
+ }
+
+ /**
+ * Writes <code>tag</code> and <code>class_info_index</code>
+ * in <code>member_value</code>.
+ *
+ * @param index <code>class_info_index</code>
+ */
+ public void classInfoIndex(int index) throws IOException {
+ output.write('c');
+ write16bit(index);
+ }
+
+ /**
+ * Writes <code>tag</code> and <code>annotation_value</code>
+ * in <code>member_value</code>.
+ * This method must be followed by a call to <code>annotation()</code>.
+ */
+ public void annotationValue() throws IOException {
+ output.write('@');
+ }
+
+ /**
+ * Writes <code>tag</code> and <code>array_value</code>
+ * in <code>member_value</code>.
+ * This method must be followed by <code>numValues</code> calls
+ * to <code>constValueIndex()</code>, <code>enumConstValue()</code>,
+ * etc.
+ *
+ * @param numValues <code>num_values</code>
+ * in <code>array_value</code>.
+ */
+ public void arrayValue(int numValues) throws IOException {
+ output.write('[');
+ write16bit(numValues);
+ }
+
+ private void write16bit(int value) throws IOException {
+ byte[] buf = new byte[2];
+ ByteArray.write16bit(value, buf, 0);
+ output.write(buf);
+ }
+}
diff --git a/src/main/javassist/bytecode/annotation/ArrayMemberValue.java b/src/main/javassist/bytecode/annotation/ArrayMemberValue.java
new file mode 100644
index 0000000..75ffe13
--- /dev/null
+++ b/src/main/javassist/bytecode/annotation/ArrayMemberValue.java
@@ -0,0 +1,144 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 2004 Bill Burke. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+package javassist.bytecode.annotation;
+
+import javassist.ClassPool;
+import javassist.bytecode.ConstPool;
+import java.io.IOException;
+import java.lang.reflect.Array;
+import java.lang.reflect.Method;
+
+/**
+ * Array member.
+ *
+ * @author <a href="mailto:bill@jboss.org">Bill Burke</a>
+ * @author Shigeru Chiba
+ */
+public class ArrayMemberValue extends MemberValue {
+ MemberValue type;
+ MemberValue[] values;
+
+ /**
+ * Constructs an array. The initial value or type are not specified.
+ */
+ public ArrayMemberValue(ConstPool cp) {
+ super('[', cp);
+ type = null;
+ values = null;
+ }
+
+ /**
+ * Constructs an array. The initial value is not specified.
+ *
+ * @param t the type of the array elements.
+ */
+ public ArrayMemberValue(MemberValue t, ConstPool cp) {
+ super('[', cp);
+ type = t;
+ values = null;
+ }
+
+ Object getValue(ClassLoader cl, ClassPool cp, Method method)
+ throws ClassNotFoundException
+ {
+ if (values == null)
+ throw new ClassNotFoundException(
+ "no array elements found: " + method.getName());
+
+ int size = values.length;
+ Class clazz;
+ if (type == null) {
+ clazz = method.getReturnType().getComponentType();
+ if (clazz == null || size > 0)
+ throw new ClassNotFoundException("broken array type: "
+ + method.getName());
+ }
+ else
+ clazz = type.getType(cl);
+
+ Object a = Array.newInstance(clazz, size);
+ for (int i = 0; i < size; i++)
+ Array.set(a, i, values[i].getValue(cl, cp, method));
+
+ return a;
+ }
+
+ Class getType(ClassLoader cl) throws ClassNotFoundException {
+ if (type == null)
+ throw new ClassNotFoundException("no array type specified");
+
+ Object a = Array.newInstance(type.getType(cl), 0);
+ return a.getClass();
+ }
+
+ /**
+ * Obtains the type of the elements.
+ *
+ * @return null if the type is not specified.
+ */
+ public MemberValue getType() {
+ return type;
+ }
+
+ /**
+ * Obtains the elements of the array.
+ */
+ public MemberValue[] getValue() {
+ return values;
+ }
+
+ /**
+ * Sets the elements of the array.
+ */
+ public void setValue(MemberValue[] elements) {
+ values = elements;
+ if (elements != null && elements.length > 0)
+ type = elements[0];
+ }
+
+ /**
+ * Obtains the string representation of this object.
+ */
+ public String toString() {
+ StringBuffer buf = new StringBuffer("{");
+ if (values != null) {
+ for (int i = 0; i < values.length; i++) {
+ buf.append(values[i].toString());
+ if (i + 1 < values.length)
+ buf.append(", ");
+ }
+ }
+
+ buf.append("}");
+ return buf.toString();
+ }
+
+ /**
+ * Writes the value.
+ */
+ public void write(AnnotationsWriter writer) throws IOException {
+ int num = values.length;
+ writer.arrayValue(num);
+ for (int i = 0; i < num; ++i)
+ values[i].write(writer);
+ }
+
+ /**
+ * Accepts a visitor.
+ */
+ public void accept(MemberValueVisitor visitor) {
+ visitor.visitArrayMemberValue(this);
+ }
+}
diff --git a/src/main/javassist/bytecode/annotation/BooleanMemberValue.java b/src/main/javassist/bytecode/annotation/BooleanMemberValue.java
new file mode 100644
index 0000000..29432b1
--- /dev/null
+++ b/src/main/javassist/bytecode/annotation/BooleanMemberValue.java
@@ -0,0 +1,102 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 2004 Bill Burke. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+package javassist.bytecode.annotation;
+
+import javassist.ClassPool;
+import javassist.bytecode.ConstPool;
+import java.io.IOException;
+import java.lang.reflect.Method;
+
+/**
+ * Boolean constant value.
+ *
+ * @author <a href="mailto:bill@jboss.org">Bill Burke</a>
+ * @author Shigeru Chiba
+ */
+public class BooleanMemberValue extends MemberValue {
+ int valueIndex;
+
+ /**
+ * Constructs a boolean constant value. The initial value is specified
+ * by the constant pool entry at the given index.
+ *
+ * @param index the index of a CONSTANT_Integer_info structure.
+ */
+ public BooleanMemberValue(int index, ConstPool cp) {
+ super('Z', cp);
+ this.valueIndex = index;
+ }
+
+ /**
+ * Constructs a boolean constant value.
+ *
+ * @param b the initial value.
+ */
+ public BooleanMemberValue(boolean b, ConstPool cp) {
+ super('Z', cp);
+ setValue(b);
+ }
+
+ /**
+ * Constructs a boolean constant value. The initial value is false.
+ */
+ public BooleanMemberValue(ConstPool cp) {
+ super('Z', cp);
+ setValue(false);
+ }
+
+ Object getValue(ClassLoader cl, ClassPool cp, Method m) {
+ return new Boolean(getValue());
+ }
+
+ Class getType(ClassLoader cl) {
+ return boolean.class;
+ }
+
+ /**
+ * Obtains the value of the member.
+ */
+ public boolean getValue() {
+ return cp.getIntegerInfo(valueIndex) != 0;
+ }
+
+ /**
+ * Sets the value of the member.
+ */
+ public void setValue(boolean newValue) {
+ valueIndex = cp.addIntegerInfo(newValue ? 1 : 0);
+ }
+
+ /**
+ * Obtains the string representation of this object.
+ */
+ public String toString() {
+ return getValue() ? "true" : "false";
+ }
+
+ /**
+ * Writes the value.
+ */
+ public void write(AnnotationsWriter writer) throws IOException {
+ writer.constValueIndex(getValue());
+ }
+
+ /**
+ * Accepts a visitor.
+ */
+ public void accept(MemberValueVisitor visitor) {
+ visitor.visitBooleanMemberValue(this);
+ }
+}
diff --git a/src/main/javassist/bytecode/annotation/ByteMemberValue.java b/src/main/javassist/bytecode/annotation/ByteMemberValue.java
new file mode 100644
index 0000000..90763b0
--- /dev/null
+++ b/src/main/javassist/bytecode/annotation/ByteMemberValue.java
@@ -0,0 +1,102 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 2004 Bill Burke. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+package javassist.bytecode.annotation;
+
+import javassist.ClassPool;
+import javassist.bytecode.ConstPool;
+import java.io.IOException;
+import java.lang.reflect.Method;
+
+/**
+ * Byte constant value.
+ *
+ * @author <a href="mailto:bill@jboss.org">Bill Burke</a>
+ * @author Shigeru Chiba
+ */
+public class ByteMemberValue extends MemberValue {
+ int valueIndex;
+
+ /**
+ * Constructs a byte constant value. The initial value is specified
+ * by the constant pool entry at the given index.
+ *
+ * @param index the index of a CONSTANT_Integer_info structure.
+ */
+ public ByteMemberValue(int index, ConstPool cp) {
+ super('B', cp);
+ this.valueIndex = index;
+ }
+
+ /**
+ * Constructs a byte constant value.
+ *
+ * @param b the initial value.
+ */
+ public ByteMemberValue(byte b, ConstPool cp) {
+ super('B', cp);
+ setValue(b);
+ }
+
+ /**
+ * Constructs a byte constant value. The initial value is 0.
+ */
+ public ByteMemberValue(ConstPool cp) {
+ super('B', cp);
+ setValue((byte)0);
+ }
+
+ Object getValue(ClassLoader cl, ClassPool cp, Method m) {
+ return new Byte(getValue());
+ }
+
+ Class getType(ClassLoader cl) {
+ return byte.class;
+ }
+
+ /**
+ * Obtains the value of the member.
+ */
+ public byte getValue() {
+ return (byte)cp.getIntegerInfo(valueIndex);
+ }
+
+ /**
+ * Sets the value of the member.
+ */
+ public void setValue(byte newValue) {
+ valueIndex = cp.addIntegerInfo(newValue);
+ }
+
+ /**
+ * Obtains the string representation of this object.
+ */
+ public String toString() {
+ return Byte.toString(getValue());
+ }
+
+ /**
+ * Writes the value.
+ */
+ public void write(AnnotationsWriter writer) throws IOException {
+ writer.constValueIndex(getValue());
+ }
+
+ /**
+ * Accepts a visitor.
+ */
+ public void accept(MemberValueVisitor visitor) {
+ visitor.visitByteMemberValue(this);
+ }
+}
diff --git a/src/main/javassist/bytecode/annotation/CharMemberValue.java b/src/main/javassist/bytecode/annotation/CharMemberValue.java
new file mode 100644
index 0000000..f6691df
--- /dev/null
+++ b/src/main/javassist/bytecode/annotation/CharMemberValue.java
@@ -0,0 +1,103 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 2004 Bill Burke. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode.annotation;
+
+import javassist.ClassPool;
+import javassist.bytecode.ConstPool;
+import java.io.IOException;
+import java.lang.reflect.Method;
+
+/**
+ * Char constant value.
+ *
+ * @author <a href="mailto:bill@jboss.org">Bill Burke</a>
+ * @author Shigeru Chiba
+ */
+public class CharMemberValue extends MemberValue {
+ int valueIndex;
+
+ /**
+ * Constructs a char constant value. The initial value is specified
+ * by the constant pool entry at the given index.
+ *
+ * @param index the index of a CONSTANT_Integer_info structure.
+ */
+ public CharMemberValue(int index, ConstPool cp) {
+ super('C', cp);
+ this.valueIndex = index;
+ }
+
+ /**
+ * Constructs a char constant value.
+ *
+ * @param c the initial value.
+ */
+ public CharMemberValue(char c, ConstPool cp) {
+ super('C', cp);
+ setValue(c);
+ }
+
+ /**
+ * Constructs a char constant value. The initial value is '\0'.
+ */
+ public CharMemberValue(ConstPool cp) {
+ super('C', cp);
+ setValue('\0');
+ }
+
+ Object getValue(ClassLoader cl, ClassPool cp, Method m) {
+ return new Character(getValue());
+ }
+
+ Class getType(ClassLoader cl) {
+ return char.class;
+ }
+
+ /**
+ * Obtains the value of the member.
+ */
+ public char getValue() {
+ return (char)cp.getIntegerInfo(valueIndex);
+ }
+
+ /**
+ * Sets the value of the member.
+ */
+ public void setValue(char newValue) {
+ valueIndex = cp.addIntegerInfo(newValue);
+ }
+
+ /**
+ * Obtains the string representation of this object.
+ */
+ public String toString() {
+ return Character.toString(getValue());
+ }
+
+ /**
+ * Writes the value.
+ */
+ public void write(AnnotationsWriter writer) throws IOException {
+ writer.constValueIndex(getValue());
+ }
+
+ /**
+ * Accepts a visitor.
+ */
+ public void accept(MemberValueVisitor visitor) {
+ visitor.visitCharMemberValue(this);
+ }
+}
diff --git a/src/main/javassist/bytecode/annotation/ClassMemberValue.java b/src/main/javassist/bytecode/annotation/ClassMemberValue.java
new file mode 100644
index 0000000..c29dbb2
--- /dev/null
+++ b/src/main/javassist/bytecode/annotation/ClassMemberValue.java
@@ -0,0 +1,132 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 2004 Bill Burke. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode.annotation;
+
+import javassist.ClassPool;
+import javassist.bytecode.ConstPool;
+import javassist.bytecode.Descriptor;
+import java.io.IOException;
+import java.lang.reflect.Method;
+
+/**
+ * Class value.
+ *
+ * @author <a href="mailto:bill@jboss.org">Bill Burke</a>
+ * @author Shigeru Chiba
+ */
+public class ClassMemberValue extends MemberValue {
+ int valueIndex;
+
+ /**
+ * Constructs a class value. The initial value is specified
+ * by the constant pool entry at the given index.
+ *
+ * @param index the index of a CONSTANT_Utf8_info structure.
+ */
+ public ClassMemberValue(int index, ConstPool cp) {
+ super('c', cp);
+ this.valueIndex = index;
+ }
+
+ /**
+ * Constructs a class value.
+ *
+ * @param className the initial value.
+ */
+ public ClassMemberValue(String className, ConstPool cp) {
+ super('c', cp);
+ setValue(className);
+ }
+
+ /**
+ * Constructs a class value.
+ * The initial value is java.lang.Class.
+ */
+ public ClassMemberValue(ConstPool cp) {
+ super('c', cp);
+ setValue("java.lang.Class");
+ }
+
+ Object getValue(ClassLoader cl, ClassPool cp, Method m)
+ throws ClassNotFoundException {
+ final String classname = getValue();
+ if (classname.equals("void"))
+ return void.class;
+ else if (classname.equals("int"))
+ return int.class;
+ else if (classname.equals("byte"))
+ return byte.class;
+ else if (classname.equals("long"))
+ return long.class;
+ else if (classname.equals("double"))
+ return double.class;
+ else if (classname.equals("float"))
+ return float.class;
+ else if (classname.equals("char"))
+ return char.class;
+ else if (classname.equals("short"))
+ return short.class;
+ else if (classname.equals("boolean"))
+ return boolean.class;
+ else
+ return loadClass(cl, classname);
+ }
+
+ Class getType(ClassLoader cl) throws ClassNotFoundException {
+ return loadClass(cl, "java.lang.Class");
+ }
+
+ /**
+ * Obtains the value of the member.
+ *
+ * @return fully-qualified class name.
+ */
+ public String getValue() {
+ String v = cp.getUtf8Info(valueIndex);
+ return Descriptor.toClassName(v);
+ }
+
+ /**
+ * Sets the value of the member.
+ *
+ * @param newClassName fully-qualified class name.
+ */
+ public void setValue(String newClassName) {
+ String setTo = Descriptor.of(newClassName);
+ valueIndex = cp.addUtf8Info(setTo);
+ }
+
+ /**
+ * Obtains the string representation of this object.
+ */
+ public String toString() {
+ return "<" + getValue() + " class>";
+ }
+
+ /**
+ * Writes the value.
+ */
+ public void write(AnnotationsWriter writer) throws IOException {
+ writer.classInfoIndex(cp.getUtf8Info(valueIndex));
+ }
+
+ /**
+ * Accepts a visitor.
+ */
+ public void accept(MemberValueVisitor visitor) {
+ visitor.visitClassMemberValue(this);
+ }
+}
diff --git a/src/main/javassist/bytecode/annotation/DoubleMemberValue.java b/src/main/javassist/bytecode/annotation/DoubleMemberValue.java
new file mode 100644
index 0000000..f2825ad
--- /dev/null
+++ b/src/main/javassist/bytecode/annotation/DoubleMemberValue.java
@@ -0,0 +1,104 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 2004 Bill Burke. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode.annotation;
+
+import javassist.ClassPool;
+import javassist.bytecode.ConstPool;
+import java.io.IOException;
+import java.lang.reflect.Method;
+
+/**
+ * Double floating-point number constant value.
+ *
+ * @author <a href="mailto:bill@jboss.org">Bill Burke</a>
+ * @author Shigeru Chiba
+ * @version $Revision: 1.7 $
+ */
+public class DoubleMemberValue extends MemberValue {
+ int valueIndex;
+
+ /**
+ * Constructs a double constant value. The initial value is specified
+ * by the constant pool entry at the given index.
+ *
+ * @param index the index of a CONSTANT_Double_info structure.
+ */
+ public DoubleMemberValue(int index, ConstPool cp) {
+ super('D', cp);
+ this.valueIndex = index;
+ }
+
+ /**
+ * Constructs a double constant value.
+ *
+ * @param d the initial value.
+ */
+ public DoubleMemberValue(double d, ConstPool cp) {
+ super('D', cp);
+ setValue(d);
+ }
+
+ /**
+ * Constructs a double constant value. The initial value is 0.0.
+ */
+ public DoubleMemberValue(ConstPool cp) {
+ super('D', cp);
+ setValue(0.0);
+ }
+
+ Object getValue(ClassLoader cl, ClassPool cp, Method m) {
+ return new Double(getValue());
+ }
+
+ Class getType(ClassLoader cl) {
+ return double.class;
+ }
+
+ /**
+ * Obtains the value of the member.
+ */
+ public double getValue() {
+ return cp.getDoubleInfo(valueIndex);
+ }
+
+ /**
+ * Sets the value of the member.
+ */
+ public void setValue(double newValue) {
+ valueIndex = cp.addDoubleInfo(newValue);
+ }
+
+ /**
+ * Obtains the string representation of this object.
+ */
+ public String toString() {
+ return Double.toString(getValue());
+ }
+
+ /**
+ * Writes the value.
+ */
+ public void write(AnnotationsWriter writer) throws IOException {
+ writer.constValueIndex(getValue());
+ }
+
+ /**
+ * Accepts a visitor.
+ */
+ public void accept(MemberValueVisitor visitor) {
+ visitor.visitDoubleMemberValue(this);
+ }
+}
diff --git a/src/main/javassist/bytecode/annotation/EnumMemberValue.java b/src/main/javassist/bytecode/annotation/EnumMemberValue.java
new file mode 100644
index 0000000..effec09
--- /dev/null
+++ b/src/main/javassist/bytecode/annotation/EnumMemberValue.java
@@ -0,0 +1,125 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 2004 Bill Burke. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode.annotation;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+
+import javassist.ClassPool;
+import javassist.bytecode.ConstPool;
+import javassist.bytecode.Descriptor;
+
+/**
+ * Enum constant value.
+ *
+ * @author <a href="mailto:bill@jboss.org">Bill Burke</a>
+ * @author Shigeru Chiba
+ */
+public class EnumMemberValue extends MemberValue {
+ int typeIndex, valueIndex;
+
+ /**
+ * Constructs an enum constant value. The initial value is specified
+ * by the constant pool entries at the given indexes.
+ *
+ * @param type the index of a CONSTANT_Utf8_info structure
+ * representing the enum type.
+ * @param value the index of a CONSTANT_Utf8_info structure.
+ * representing the enum value.
+ */
+ public EnumMemberValue(int type, int value, ConstPool cp) {
+ super('e', cp);
+ this.typeIndex = type;
+ this.valueIndex = value;
+ }
+
+ /**
+ * Constructs an enum constant value.
+ * The initial value is not specified.
+ */
+ public EnumMemberValue(ConstPool cp) {
+ super('e', cp);
+ typeIndex = valueIndex = 0;
+ }
+
+ Object getValue(ClassLoader cl, ClassPool cp, Method m)
+ throws ClassNotFoundException
+ {
+ try {
+ return getType(cl).getField(getValue()).get(null);
+ }
+ catch (NoSuchFieldException e) {
+ throw new ClassNotFoundException(getType() + "." + getValue());
+ }
+ catch (IllegalAccessException e) {
+ throw new ClassNotFoundException(getType() + "." + getValue());
+ }
+ }
+
+ Class getType(ClassLoader cl) throws ClassNotFoundException {
+ return loadClass(cl, getType());
+ }
+
+ /**
+ * Obtains the enum type name.
+ *
+ * @return a fully-qualified type name.
+ */
+ public String getType() {
+ return Descriptor.toClassName(cp.getUtf8Info(typeIndex));
+ }
+
+ /**
+ * Changes the enum type name.
+ *
+ * @param typename a fully-qualified type name.
+ */
+ public void setType(String typename) {
+ typeIndex = cp.addUtf8Info(Descriptor.of(typename));
+ }
+
+ /**
+ * Obtains the name of the enum constant value.
+ */
+ public String getValue() {
+ return cp.getUtf8Info(valueIndex);
+ }
+
+ /**
+ * Changes the name of the enum constant value.
+ */
+ public void setValue(String name) {
+ valueIndex = cp.addUtf8Info(name);
+ }
+
+ public String toString() {
+ return getType() + "." + getValue();
+ }
+
+ /**
+ * Writes the value.
+ */
+ public void write(AnnotationsWriter writer) throws IOException {
+ writer.enumConstValue(cp.getUtf8Info(typeIndex), getValue());
+ }
+
+ /**
+ * Accepts a visitor.
+ */
+ public void accept(MemberValueVisitor visitor) {
+ visitor.visitEnumMemberValue(this);
+ }
+}
diff --git a/src/main/javassist/bytecode/annotation/FloatMemberValue.java b/src/main/javassist/bytecode/annotation/FloatMemberValue.java
new file mode 100644
index 0000000..7188b5e
--- /dev/null
+++ b/src/main/javassist/bytecode/annotation/FloatMemberValue.java
@@ -0,0 +1,104 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 2004 Bill Burke. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode.annotation;
+
+import javassist.ClassPool;
+import javassist.bytecode.ConstPool;
+import java.io.IOException;
+import java.lang.reflect.Method;
+
+/**
+ * Floating-point number constant value.
+ *
+ * @author <a href="mailto:bill@jboss.org">Bill Burke</a>
+ * @author Shigeru Chiba
+ * @version $Revision: 1.7 $
+ */
+public class FloatMemberValue extends MemberValue {
+ int valueIndex;
+
+ /**
+ * Constructs a float constant value. The initial value is specified
+ * by the constant pool entry at the given index.
+ *
+ * @param index the index of a CONSTANT_Float_info structure.
+ */
+ public FloatMemberValue(int index, ConstPool cp) {
+ super('F', cp);
+ this.valueIndex = index;
+ }
+
+ /**
+ * Constructs a float constant value.
+ *
+ * @param f the initial value.
+ */
+ public FloatMemberValue(float f, ConstPool cp) {
+ super('F', cp);
+ setValue(f);
+ }
+
+ /**
+ * Constructs a float constant value. The initial value is 0.0.
+ */
+ public FloatMemberValue(ConstPool cp) {
+ super('F', cp);
+ setValue(0.0F);
+ }
+
+ Object getValue(ClassLoader cl, ClassPool cp, Method m) {
+ return new Float(getValue());
+ }
+
+ Class getType(ClassLoader cl) {
+ return float.class;
+ }
+
+ /**
+ * Obtains the value of the member.
+ */
+ public float getValue() {
+ return cp.getFloatInfo(valueIndex);
+ }
+
+ /**
+ * Sets the value of the member.
+ */
+ public void setValue(float newValue) {
+ valueIndex = cp.addFloatInfo(newValue);
+ }
+
+ /**
+ * Obtains the string representation of this object.
+ */
+ public String toString() {
+ return Float.toString(getValue());
+ }
+
+ /**
+ * Writes the value.
+ */
+ public void write(AnnotationsWriter writer) throws IOException {
+ writer.constValueIndex(getValue());
+ }
+
+ /**
+ * Accepts a visitor.
+ */
+ public void accept(MemberValueVisitor visitor) {
+ visitor.visitFloatMemberValue(this);
+ }
+}
diff --git a/src/main/javassist/bytecode/annotation/IntegerMemberValue.java b/src/main/javassist/bytecode/annotation/IntegerMemberValue.java
new file mode 100644
index 0000000..2f2a907
--- /dev/null
+++ b/src/main/javassist/bytecode/annotation/IntegerMemberValue.java
@@ -0,0 +1,109 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 2004 Bill Burke. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode.annotation;
+
+import javassist.ClassPool;
+import javassist.bytecode.ConstPool;
+import java.io.IOException;
+import java.lang.reflect.Method;
+
+/**
+ * Integer constant value.
+ *
+ * @author <a href="mailto:bill@jboss.org">Bill Burke</a>
+ * @author Shigeru Chiba
+ */
+public class IntegerMemberValue extends MemberValue {
+ int valueIndex;
+
+ /**
+ * Constructs an int constant value. The initial value is specified
+ * by the constant pool entry at the given index.
+ *
+ * @param index the index of a CONSTANT_Integer_info structure.
+ */
+ public IntegerMemberValue(int index, ConstPool cp) {
+ super('I', cp);
+ this.valueIndex = index;
+ }
+
+ /**
+ * Constructs an int constant value.
+ * Note that this constructor receives <b>the initial value
+ * as the second parameter</b>
+ * unlike the corresponding constructors in the sibling classes.
+ * This is for making a difference from the constructor that receives
+ * an index into the constant pool table as the first parameter.
+ * Note that the index is also int type.
+ *
+ * @param value the initial value.
+ */
+ public IntegerMemberValue(ConstPool cp, int value) {
+ super('I', cp);
+ setValue(value);
+ }
+
+ /**
+ * Constructs an int constant value. The initial value is 0.
+ */
+ public IntegerMemberValue(ConstPool cp) {
+ super('I', cp);
+ setValue(0);
+ }
+
+ Object getValue(ClassLoader cl, ClassPool cp, Method m) {
+ return new Integer(getValue());
+ }
+
+ Class getType(ClassLoader cl) {
+ return int.class;
+ }
+
+ /**
+ * Obtains the value of the member.
+ */
+ public int getValue() {
+ return cp.getIntegerInfo(valueIndex);
+ }
+
+ /**
+ * Sets the value of the member.
+ */
+ public void setValue(int newValue) {
+ valueIndex = cp.addIntegerInfo(newValue);
+ }
+
+ /**
+ * Obtains the string representation of this object.
+ */
+ public String toString() {
+ return Integer.toString(getValue());
+ }
+
+ /**
+ * Writes the value.
+ */
+ public void write(AnnotationsWriter writer) throws IOException {
+ writer.constValueIndex(getValue());
+ }
+
+ /**
+ * Accepts a visitor.
+ */
+ public void accept(MemberValueVisitor visitor) {
+ visitor.visitIntegerMemberValue(this);
+ }
+}
diff --git a/src/main/javassist/bytecode/annotation/LongMemberValue.java b/src/main/javassist/bytecode/annotation/LongMemberValue.java
new file mode 100644
index 0000000..2afd4a0
--- /dev/null
+++ b/src/main/javassist/bytecode/annotation/LongMemberValue.java
@@ -0,0 +1,103 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 2004 Bill Burke. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode.annotation;
+
+import javassist.ClassPool;
+import javassist.bytecode.ConstPool;
+import java.io.IOException;
+import java.lang.reflect.Method;
+
+/**
+ * Long integer constant value.
+ *
+ * @author <a href="mailto:bill@jboss.org">Bill Burke</a>
+ * @author Shigeru Chiba
+ */
+public class LongMemberValue extends MemberValue {
+ int valueIndex;
+
+ /**
+ * Constructs a long constant value. The initial value is specified
+ * by the constant pool entry at the given index.
+ *
+ * @param index the index of a CONSTANT_Long_info structure.
+ */
+ public LongMemberValue(int index, ConstPool cp) {
+ super('J', cp);
+ this.valueIndex = index;
+ }
+
+ /**
+ * Constructs a long constant value.
+ *
+ * @param j the initial value.
+ */
+ public LongMemberValue(long j, ConstPool cp) {
+ super('J', cp);
+ setValue(j);
+ }
+
+ /**
+ * Constructs a long constant value. The initial value is 0.
+ */
+ public LongMemberValue(ConstPool cp) {
+ super('J', cp);
+ setValue(0L);
+ }
+
+ Object getValue(ClassLoader cl, ClassPool cp, Method m) {
+ return new Long(getValue());
+ }
+
+ Class getType(ClassLoader cl) {
+ return long.class;
+ }
+
+ /**
+ * Obtains the value of the member.
+ */
+ public long getValue() {
+ return cp.getLongInfo(valueIndex);
+ }
+
+ /**
+ * Sets the value of the member.
+ */
+ public void setValue(long newValue) {
+ valueIndex = cp.addLongInfo(newValue);
+ }
+
+ /**
+ * Obtains the string representation of this object.
+ */
+ public String toString() {
+ return Long.toString(getValue());
+ }
+
+ /**
+ * Writes the value.
+ */
+ public void write(AnnotationsWriter writer) throws IOException {
+ writer.constValueIndex(getValue());
+ }
+
+ /**
+ * Accepts a visitor.
+ */
+ public void accept(MemberValueVisitor visitor) {
+ visitor.visitLongMemberValue(this);
+ }
+}
diff --git a/src/main/javassist/bytecode/annotation/MemberValue.java b/src/main/javassist/bytecode/annotation/MemberValue.java
new file mode 100644
index 0000000..18796ee
--- /dev/null
+++ b/src/main/javassist/bytecode/annotation/MemberValue.java
@@ -0,0 +1,88 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 2004 Bill Burke. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode.annotation;
+
+import javassist.ClassPool;
+import javassist.bytecode.ConstPool;
+import javassist.bytecode.Descriptor;
+
+import java.io.IOException;
+import java.lang.reflect.Array;
+import java.lang.reflect.Method;
+
+/**
+ * The value of a member declared in an annotation.
+ *
+ * @see Annotation#getMemberValue(String)
+ * @author <a href="mailto:bill@jboss.org">Bill Burke</a>
+ * @author Shigeru Chiba
+ */
+public abstract class MemberValue {
+ ConstPool cp;
+ char tag;
+
+ MemberValue(char tag, ConstPool cp) {
+ this.cp = cp;
+ this.tag = tag;
+ }
+
+ /**
+ * Returns the value. If the value type is a primitive type, the
+ * returned value is boxed.
+ */
+ abstract Object getValue(ClassLoader cl, ClassPool cp, Method m)
+ throws ClassNotFoundException;
+
+ abstract Class getType(ClassLoader cl) throws ClassNotFoundException;
+
+ static Class loadClass(ClassLoader cl, String classname)
+ throws ClassNotFoundException, NoSuchClassError
+ {
+ try {
+ return Class.forName(convertFromArray(classname), true, cl);
+ }
+ catch (LinkageError e) {
+ throw new NoSuchClassError(classname, e);
+ }
+ }
+
+ private static String convertFromArray(String classname)
+ {
+ int index = classname.indexOf("[]");
+ if (index != -1) {
+ String rawType = classname.substring(0, index);
+ StringBuffer sb = new StringBuffer(Descriptor.of(rawType));
+ while (index != -1) {
+ sb.insert(0, "[");
+ index = classname.indexOf("[]", index + 1);
+ }
+ return sb.toString().replace('/', '.');
+ }
+ return classname;
+ }
+
+ /**
+ * Accepts a visitor.
+ */
+ public abstract void accept(MemberValueVisitor visitor);
+
+ /**
+ * Writes the value.
+ */
+ public abstract void write(AnnotationsWriter w) throws IOException;
+}
+
+
diff --git a/src/main/javassist/bytecode/annotation/MemberValueVisitor.java b/src/main/javassist/bytecode/annotation/MemberValueVisitor.java
new file mode 100644
index 0000000..6944bd0
--- /dev/null
+++ b/src/main/javassist/bytecode/annotation/MemberValueVisitor.java
@@ -0,0 +1,38 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 2004 Bill Burke. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode.annotation;
+
+/**
+ * Visitor for traversing member values included in an annotation.
+ *
+ * @see MemberValue#accept(MemberValueVisitor)
+ * @author <a href="mailto:bill@jboss.org">Bill Burke</a>
+ */
+public interface MemberValueVisitor {
+ public void visitAnnotationMemberValue(AnnotationMemberValue node);
+ public void visitArrayMemberValue(ArrayMemberValue node);
+ public void visitBooleanMemberValue(BooleanMemberValue node);
+ public void visitByteMemberValue(ByteMemberValue node);
+ public void visitCharMemberValue(CharMemberValue node);
+ public void visitDoubleMemberValue(DoubleMemberValue node);
+ public void visitEnumMemberValue(EnumMemberValue node);
+ public void visitFloatMemberValue(FloatMemberValue node);
+ public void visitIntegerMemberValue(IntegerMemberValue node);
+ public void visitLongMemberValue(LongMemberValue node);
+ public void visitShortMemberValue(ShortMemberValue node);
+ public void visitStringMemberValue(StringMemberValue node);
+ public void visitClassMemberValue(ClassMemberValue node);
+}
diff --git a/src/main/javassist/bytecode/annotation/NoSuchClassError.java b/src/main/javassist/bytecode/annotation/NoSuchClassError.java
new file mode 100644
index 0000000..c6d1a12
--- /dev/null
+++ b/src/main/javassist/bytecode/annotation/NoSuchClassError.java
@@ -0,0 +1,39 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2009 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode.annotation;
+
+/**
+ * Thrown if the linkage fails.
+ * It keeps the name of the class that caused this error.
+ */
+public class NoSuchClassError extends Error {
+ private String className;
+
+ /**
+ * Constructs an exception.
+ */
+ public NoSuchClassError(String className, Error cause) {
+ super(cause.toString(), cause);
+ this.className = className;
+ }
+
+ /**
+ * Returns the name of the class not found.
+ */
+ public String getClassName() {
+ return className;
+ }
+}
diff --git a/src/main/javassist/bytecode/annotation/ShortMemberValue.java b/src/main/javassist/bytecode/annotation/ShortMemberValue.java
new file mode 100644
index 0000000..3ccf380
--- /dev/null
+++ b/src/main/javassist/bytecode/annotation/ShortMemberValue.java
@@ -0,0 +1,103 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 2004 Bill Burke. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode.annotation;
+
+import javassist.ClassPool;
+import javassist.bytecode.ConstPool;
+import java.io.IOException;
+import java.lang.reflect.Method;
+
+/**
+ * Short integer constant value.
+ *
+ * @author <a href="mailto:bill@jboss.org">Bill Burke</a>
+ * @author Shigeru Chiba
+ */
+public class ShortMemberValue extends MemberValue {
+ int valueIndex;
+
+ /**
+ * Constructs a short constant value. The initial value is specified
+ * by the constant pool entry at the given index.
+ *
+ * @param index the index of a CONSTANT_Integer_info structure.
+ */
+ public ShortMemberValue(int index, ConstPool cp) {
+ super('S', cp);
+ this.valueIndex = index;
+ }
+
+ /**
+ * Constructs a short constant value.
+ *
+ * @param s the initial value.
+ */
+ public ShortMemberValue(short s, ConstPool cp) {
+ super('S', cp);
+ setValue(s);
+ }
+
+ /**
+ * Constructs a short constant value. The initial value is 0.
+ */
+ public ShortMemberValue(ConstPool cp) {
+ super('S', cp);
+ setValue((short)0);
+ }
+
+ Object getValue(ClassLoader cl, ClassPool cp, Method m) {
+ return new Short(getValue());
+ }
+
+ Class getType(ClassLoader cl) {
+ return short.class;
+ }
+
+ /**
+ * Obtains the value of the member.
+ */
+ public short getValue() {
+ return (short)cp.getIntegerInfo(valueIndex);
+ }
+
+ /**
+ * Sets the value of the member.
+ */
+ public void setValue(short newValue) {
+ valueIndex = cp.addIntegerInfo(newValue);
+ }
+
+ /**
+ * Obtains the string representation of this object.
+ */
+ public String toString() {
+ return Short.toString(getValue());
+ }
+
+ /**
+ * Writes the value.
+ */
+ public void write(AnnotationsWriter writer) throws IOException {
+ writer.constValueIndex(getValue());
+ }
+
+ /**
+ * Accepts a visitor.
+ */
+ public void accept(MemberValueVisitor visitor) {
+ visitor.visitShortMemberValue(this);
+ }
+}
diff --git a/src/main/javassist/bytecode/annotation/StringMemberValue.java b/src/main/javassist/bytecode/annotation/StringMemberValue.java
new file mode 100644
index 0000000..970fb8f
--- /dev/null
+++ b/src/main/javassist/bytecode/annotation/StringMemberValue.java
@@ -0,0 +1,103 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 2004 Bill Burke. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode.annotation;
+
+import javassist.ClassPool;
+import javassist.bytecode.ConstPool;
+import java.io.IOException;
+import java.lang.reflect.Method;
+
+/**
+ * String constant value.
+ *
+ * @author <a href="mailto:bill@jboss.org">Bill Burke</a>
+ * @author Shigeru Chiba
+ */
+public class StringMemberValue extends MemberValue {
+ int valueIndex;
+
+ /**
+ * Constructs a string constant value. The initial value is specified
+ * by the constant pool entry at the given index.
+ *
+ * @param index the index of a CONSTANT_Utf8_info structure.
+ */
+ public StringMemberValue(int index, ConstPool cp) {
+ super('s', cp);
+ this.valueIndex = index;
+ }
+
+ /**
+ * Constructs a string constant value.
+ *
+ * @param str the initial value.
+ */
+ public StringMemberValue(String str, ConstPool cp) {
+ super('s', cp);
+ setValue(str);
+ }
+
+ /**
+ * Constructs a string constant value. The initial value is "".
+ */
+ public StringMemberValue(ConstPool cp) {
+ super('s', cp);
+ setValue("");
+ }
+
+ Object getValue(ClassLoader cl, ClassPool cp, Method m) {
+ return getValue();
+ }
+
+ Class getType(ClassLoader cl) {
+ return String.class;
+ }
+
+ /**
+ * Obtains the value of the member.
+ */
+ public String getValue() {
+ return cp.getUtf8Info(valueIndex);
+ }
+
+ /**
+ * Sets the value of the member.
+ */
+ public void setValue(String newValue) {
+ valueIndex = cp.addUtf8Info(newValue);
+ }
+
+ /**
+ * Obtains the string representation of this object.
+ */
+ public String toString() {
+ return "\"" + getValue() + "\"";
+ }
+
+ /**
+ * Writes the value.
+ */
+ public void write(AnnotationsWriter writer) throws IOException {
+ writer.constValueIndex(getValue());
+ }
+
+ /**
+ * Accepts a visitor.
+ */
+ public void accept(MemberValueVisitor visitor) {
+ visitor.visitStringMemberValue(this);
+ }
+}
diff --git a/src/main/javassist/bytecode/annotation/package.html b/src/main/javassist/bytecode/annotation/package.html
new file mode 100644
index 0000000..d0656db
--- /dev/null
+++ b/src/main/javassist/bytecode/annotation/package.html
@@ -0,0 +1,8 @@
+<html>
+<body>
+Bytecode-level Annotations API.
+
+<p>This package provides low-level API for editing annotations attributes.
+
+</body>
+</html>
diff --git a/src/main/javassist/bytecode/package.html b/src/main/javassist/bytecode/package.html
new file mode 100644
index 0000000..9da3888
--- /dev/null
+++ b/src/main/javassist/bytecode/package.html
@@ -0,0 +1,18 @@
+<html>
+<body>
+Bytecode-level API.
+
+<p>This package provides low-level API for editing a raw class file.
+It allows the users to read and modify a constant pool entry, a single
+bytecode instruction, and so on.
+
+<p>The users of this package must know the specifications of
+class file and Java bytecode. For more details, read this book:
+
+<ul>Tim Lindholm and Frank Yellin,
+"The Java Virtual Machine Specification 2nd Ed.",
+Addison-Wesley, 1999.
+</ul>
+
+</body>
+</html>
diff --git a/src/main/javassist/bytecode/stackmap/BasicBlock.java b/src/main/javassist/bytecode/stackmap/BasicBlock.java
new file mode 100644
index 0000000..e5b64d3
--- /dev/null
+++ b/src/main/javassist/bytecode/stackmap/BasicBlock.java
@@ -0,0 +1,398 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode.stackmap;
+
+import javassist.bytecode.*;
+import java.util.HashMap;
+import java.util.ArrayList;
+
+/**
+ * A basic block is a sequence of bytecode that does not contain jump/branch
+ * instructions except at the last bytecode.
+ * Since Java6 or later does not allow JSR, this class deals with JSR as a
+ * non-branch instruction.
+ */
+public class BasicBlock {
+ public int position, length;
+ public int incoming; // the number of incoming branches.
+ public BasicBlock[] exit; // null if the block is a leaf.
+ public boolean stop; // true if the block ends with an unconditional jump.
+ public Catch toCatch;
+
+ protected BasicBlock(int pos) {
+ position = pos;
+ length = 0;
+ incoming = 0;
+ }
+
+ public static BasicBlock find(BasicBlock[] blocks, int pos)
+ throws BadBytecode
+ {
+ for (int i = 0; i < blocks.length; i++) {
+ int iPos = blocks[i].position;
+ if (iPos <= pos && pos < iPos + blocks[i].length)
+ return blocks[i];
+ }
+
+ throw new BadBytecode("no basic block at " + pos);
+ }
+
+ public static class Catch {
+ Catch next;
+ BasicBlock body;
+ int typeIndex;
+ Catch(BasicBlock b, int i, Catch c) {
+ body = b;
+ typeIndex = i;
+ next = c;
+ }
+ }
+
+ public String toString() {
+ StringBuffer sbuf = new StringBuffer();
+ String cname = this.getClass().getName();
+ int i = cname.lastIndexOf('.');
+ sbuf.append(i < 0 ? cname : cname.substring(i + 1));
+ sbuf.append("[");
+ toString2(sbuf);
+ sbuf.append("]");
+ return sbuf.toString();
+ }
+
+ protected void toString2(StringBuffer sbuf) {
+ sbuf.append("pos=").append(position).append(", len=")
+ .append(length).append(", in=").append(incoming)
+ .append(", exit{");
+ if (exit != null) {
+ for (int i = 0; i < exit.length; i++)
+ sbuf.append(exit[i].position).append(", ");
+ }
+
+ sbuf.append("}, {");
+ Catch th = toCatch;
+ while (th != null) {
+ sbuf.append("(").append(th.body.position).append(", ")
+ .append(th.typeIndex).append("), ");
+ th = th.next;
+ }
+
+ sbuf.append("}");
+ }
+
+ static class Mark implements Comparable {
+ int position;
+ BasicBlock block;
+ BasicBlock[] jump;
+ boolean alwaysJmp; // true if a unconditional branch.
+ int size; // 0 unless the mark indicates RETURN etc.
+ Catch catcher;
+
+ Mark(int p) {
+ position = p;
+ block = null;
+ jump = null;
+ alwaysJmp = false;
+ size = 0;
+ catcher = null;
+ }
+
+ public int compareTo(Object obj) {
+ if (obj instanceof Mark) {
+ int pos = ((Mark)obj).position;
+ return position - pos;
+ }
+
+ return -1;
+ }
+
+ void setJump(BasicBlock[] bb, int s, boolean always) {
+ jump = bb;
+ size = s;
+ alwaysJmp = always;
+ }
+ }
+
+ public static class Maker {
+ /* Override these two methods if a subclass of BasicBlock must be
+ * instantiated.
+ */
+ protected BasicBlock makeBlock(int pos) {
+ return new BasicBlock(pos);
+ }
+
+ protected BasicBlock[] makeArray(int size) {
+ return new BasicBlock[size];
+ }
+
+ private BasicBlock[] makeArray(BasicBlock b) {
+ BasicBlock[] array = makeArray(1);
+ array[0] = b;
+ return array;
+ }
+
+ private BasicBlock[] makeArray(BasicBlock b1, BasicBlock b2) {
+ BasicBlock[] array = makeArray(2);
+ array[0] = b1;
+ array[1] = b2;
+ return array;
+ }
+
+ public BasicBlock[] make(MethodInfo minfo) throws BadBytecode {
+ CodeAttribute ca = minfo.getCodeAttribute();
+ if (ca == null)
+ return null;
+
+ CodeIterator ci = ca.iterator();
+ return make(ci, 0, ci.getCodeLength(), ca.getExceptionTable());
+ }
+
+ public BasicBlock[] make(CodeIterator ci, int begin, int end,
+ ExceptionTable et)
+ throws BadBytecode
+ {
+ HashMap marks = makeMarks(ci, begin, end, et);
+ BasicBlock[] bb = makeBlocks(marks);
+ addCatchers(bb, et);
+ return bb;
+ }
+
+ /* Branch target
+ */
+ private Mark makeMark(HashMap table, int pos) {
+ return makeMark0(table, pos, true, true);
+ }
+
+ /* Branch instruction.
+ * size > 0
+ */
+ private Mark makeMark(HashMap table, int pos, BasicBlock[] jump,
+ int size, boolean always) {
+ Mark m = makeMark0(table, pos, false, false);
+ m.setJump(jump, size, always);
+ return m;
+ }
+
+ private Mark makeMark0(HashMap table, int pos,
+ boolean isBlockBegin, boolean isTarget) {
+ Integer p = new Integer(pos);
+ Mark m = (Mark)table.get(p);
+ if (m == null) {
+ m = new Mark(pos);
+ table.put(p, m);
+ }
+
+ if (isBlockBegin) {
+ if (m.block == null)
+ m.block = makeBlock(pos);
+
+ if (isTarget)
+ m.block.incoming++;
+ }
+
+ return m;
+ }
+
+ private HashMap makeMarks(CodeIterator ci, int begin, int end,
+ ExceptionTable et)
+ throws BadBytecode
+ {
+ ci.begin();
+ ci.move(begin);
+ HashMap marks = new HashMap();
+ while (ci.hasNext()) {
+ int index = ci.next();
+ if (index >= end)
+ break;
+
+ int op = ci.byteAt(index);
+ if ((Opcode.IFEQ <= op && op <= Opcode.IF_ACMPNE)
+ || op == Opcode.IFNULL || op == Opcode.IFNONNULL) {
+ Mark to = makeMark(marks, index + ci.s16bitAt(index + 1));
+ Mark next = makeMark(marks, index + 3);
+ makeMark(marks, index, makeArray(to.block, next.block), 3, false);
+ }
+ else if (Opcode.GOTO <= op && op <= Opcode.LOOKUPSWITCH)
+ switch (op) {
+ case Opcode.GOTO :
+ makeGoto(marks, index, index + ci.s16bitAt(index + 1), 3);
+ break;
+ case Opcode.JSR :
+ makeJsr(marks, index, index + ci.s16bitAt(index + 1), 3);
+ break;
+ case Opcode.RET :
+ makeMark(marks, index, null, 2, true);
+ break;
+ case Opcode.TABLESWITCH : {
+ int pos = (index & ~3) + 4;
+ int low = ci.s32bitAt(pos + 4);
+ int high = ci.s32bitAt(pos + 8);
+ int ncases = high - low + 1;
+ BasicBlock[] to = makeArray(ncases + 1);
+ to[0] = makeMark(marks, index + ci.s32bitAt(pos)).block; // default branch target
+ int p = pos + 12;
+ int n = p + ncases * 4;
+ int k = 1;
+ while (p < n) {
+ to[k++] = makeMark(marks, index + ci.s32bitAt(p)).block;
+ p += 4;
+ }
+ makeMark(marks, index, to, n - index, true);
+ break; }
+ case Opcode.LOOKUPSWITCH : {
+ int pos = (index & ~3) + 4;
+ int ncases = ci.s32bitAt(pos + 4);
+ BasicBlock[] to = makeArray(ncases + 1);
+ to[0] = makeMark(marks, index + ci.s32bitAt(pos)).block; // default branch target
+ int p = pos + 8 + 4;
+ int n = p + ncases * 8 - 4;
+ int k = 1;
+ while (p < n) {
+ to[k++] = makeMark(marks, index + ci.s32bitAt(p)).block;
+ p += 8;
+ }
+ makeMark(marks, index, to, n - index, true);
+ break; }
+ }
+ else if ((Opcode.IRETURN <= op && op <= Opcode.RETURN) || op == Opcode.ATHROW)
+ makeMark(marks, index, null, 1, true);
+ else if (op == Opcode.GOTO_W)
+ makeGoto(marks, index, index + ci.s32bitAt(index + 1), 5);
+ else if (op == Opcode.JSR_W)
+ makeJsr(marks, index, index + ci.s32bitAt(index + 1), 5);
+ else if (op == Opcode.WIDE && ci.byteAt(index + 1) == Opcode.RET)
+ makeMark(marks, index, null, 1, true);
+ }
+
+ if (et != null) {
+ int i = et.size();
+ while (--i >= 0) {
+ makeMark0(marks, et.startPc(i), true, false);
+ makeMark(marks, et.handlerPc(i));
+ }
+ }
+
+ return marks;
+ }
+
+ private void makeGoto(HashMap marks, int pos, int target, int size) {
+ Mark to = makeMark(marks, target);
+ BasicBlock[] jumps = makeArray(to.block);
+ makeMark(marks, pos, jumps, size, true);
+ }
+
+ /**
+ * We ignore JSR since Java 6 or later does not allow it.
+ */
+ protected void makeJsr(HashMap marks, int pos, int target, int size) {
+ /*
+ Mark to = makeMark(marks, target);
+ Mark next = makeMark(marks, pos + size);
+ BasicBlock[] jumps = makeArray(to.block, next.block);
+ makeMark(marks, pos, jumps, size, false);
+ */
+ }
+
+ private BasicBlock[] makeBlocks(HashMap markTable) {
+ Mark[] marks = (Mark[])markTable.values()
+ .toArray(new Mark[markTable.size()]);
+ java.util.Arrays.sort(marks);
+ ArrayList blocks = new ArrayList();
+ int i = 0;
+ BasicBlock prev;
+ if (marks.length > 0 && marks[0].position == 0 && marks[0].block != null)
+ prev = getBBlock(marks[i++]);
+ else
+ prev = makeBlock(0);
+
+ blocks.add(prev);
+ while (i < marks.length) {
+ Mark m = marks[i++];
+ BasicBlock bb = getBBlock(m);
+ if (bb == null) {
+ // the mark indicates a branch instruction
+ if (prev.length > 0) {
+ // the previous mark already has exits.
+ prev = makeBlock(prev.position + prev.length);
+ blocks.add(prev);
+ }
+
+ prev.length = m.position + m.size - prev.position;
+ prev.exit = m.jump;
+ prev.stop = m.alwaysJmp;
+ }
+ else {
+ // the mark indicates a branch target
+ if (prev.length == 0) {
+ prev.length = m.position - prev.position;
+ bb.incoming++;
+ prev.exit = makeArray(bb);
+ }
+ else {
+ // the previous mark already has exits.
+ int prevPos = prev.position;
+ if (prevPos + prev.length < m.position) {
+ prev = makeBlock(prevPos + prev.length);
+ prev.length = m.position - prevPos;
+ // the incoming flow from dead code is not counted
+ // bb.incoming++;
+ prev.exit = makeArray(bb);
+ }
+ }
+
+ blocks.add(bb);
+ prev = bb;
+ }
+ }
+
+ return (BasicBlock[])blocks.toArray(makeArray(blocks.size()));
+ }
+
+ private static BasicBlock getBBlock(Mark m) {
+ BasicBlock b = m.block;
+ if (b != null && m.size > 0) {
+ b.exit = m.jump;
+ b.length = m.size;
+ b.stop = m.alwaysJmp;
+ }
+
+ return b;
+ }
+
+ private void addCatchers(BasicBlock[] blocks, ExceptionTable et)
+ throws BadBytecode
+ {
+ if (et == null)
+ return;
+
+ int i = et.size();
+ while (--i >= 0) {
+ BasicBlock handler = find(blocks, et.handlerPc(i));
+ int start = et.startPc(i);
+ int end = et.endPc(i);
+ int type = et.catchType(i);
+ handler.incoming--;
+ for (int k = 0; k < blocks.length; k++) {
+ BasicBlock bb = blocks[k];
+ int iPos = bb.position;
+ if (start <= iPos && iPos < end) {
+ bb.toCatch = new Catch(handler, type, bb.toCatch);
+ handler.incoming++;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/javassist/bytecode/stackmap/Liveness.java b/src/main/javassist/bytecode/stackmap/Liveness.java
new file mode 100644
index 0000000..4acd65e
--- /dev/null
+++ b/src/main/javassist/bytecode/stackmap/Liveness.java
@@ -0,0 +1,365 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+ package javassist.bytecode.stackmap;
+
+import javassist.bytecode.*;
+
+public class Liveness {
+ protected static final byte UNKNOWN = 0;
+ protected static final byte READ = 1;
+ protected static final byte UPDATED = 2;
+ protected byte[] localsUsage;
+
+ /**
+ * If true, all the arguments become alive within the whole method body.
+ *
+ * To correctly compute a stack map table, all the arguments must
+ * be alive (localsUsage[?] must be READ) at least in the first block.
+ */
+ public static boolean useArgs = true;
+
+ public void compute(CodeIterator ci, TypedBlock[] blocks, int maxLocals,
+ TypeData[] args)
+ throws BadBytecode
+ {
+ computeUsage(ci, blocks, maxLocals);
+ if (useArgs)
+ useAllArgs(blocks, args);
+
+ computeLiveness1(blocks[0]);
+ while (hasChanged(blocks))
+ computeLiveness2(blocks[0]);
+ }
+
+ private void useAllArgs(TypedBlock[] blocks, TypeData[] args) {
+ for (int k = 0; k < blocks.length; k++) {
+ byte[] usage = blocks[k].localsUsage;
+ for (int i = 0; i < args.length; i++)
+ if (args[i] != TypeTag.TOP)
+ usage[i] = READ;
+ }
+ }
+
+ static final int NOT_YET = 0;
+ static final int CHANGED_LAST = 1;
+ static final int DONE = 2;
+ static final int CHANGED_NOW = 3;
+
+ private void computeLiveness1(TypedBlock tb) {
+ if (tb.updating) {
+ // a loop was detected.
+ computeLiveness1u(tb);
+ return;
+ }
+
+ if (tb.inputs != null)
+ return;
+
+ tb.updating = true;
+ byte[] usage = tb.localsUsage;
+ int n = usage.length;
+ boolean[] in = new boolean[n];
+ for (int i = 0; i < n; i++)
+ in[i] = usage[i] == READ;
+
+ BasicBlock.Catch handlers = tb.toCatch;
+ while (handlers != null) {
+ TypedBlock h = (TypedBlock)handlers.body;
+ computeLiveness1(h);
+ for (int k = 0; k < n; k++)
+ if (h.inputs[k])
+ in[k] = true;
+
+ handlers = handlers.next;
+ }
+
+ if (tb.exit != null) {
+ for (int i = 0; i < tb.exit.length; i++) {
+ TypedBlock e = (TypedBlock)tb.exit[i];
+ computeLiveness1(e);
+ for (int k = 0; k < n; k++)
+ if (!in[k])
+ in[k] = usage[k] == UNKNOWN && e.inputs[k];
+ }
+ }
+
+ tb.updating = false;
+ if (tb.inputs == null) {
+ tb.inputs = in;
+ tb.status = DONE;
+ }
+ else {
+ for (int i = 0; i < n; i++)
+ if (in[i] && !tb.inputs[i]) {
+ tb.inputs[i] = true;
+ tb.status = CHANGED_NOW;
+ }
+ }
+ }
+
+ private void computeLiveness1u(TypedBlock tb) {
+ if (tb.inputs == null) {
+ byte[] usage = tb.localsUsage;
+ int n = usage.length;
+ boolean[] in = new boolean[n];
+ for (int i = 0; i < n; i++)
+ in[i] = usage[i] == READ;
+
+ tb.inputs = in;
+ tb.status = DONE;
+ }
+ }
+
+ private void computeLiveness2(TypedBlock tb) {
+ if (tb.updating || tb.status >= DONE)
+ return;
+
+ tb.updating = true;
+ if (tb.exit == null)
+ tb.status = DONE;
+ else {
+ boolean changed = false;
+ for (int i = 0; i < tb.exit.length; i++) {
+ TypedBlock e = (TypedBlock)tb.exit[i];
+ computeLiveness2(e);
+ if (e.status != DONE)
+ changed = true;
+ }
+
+ if (changed) {
+ changed = false;
+ byte[] usage = tb.localsUsage;
+ int n = usage.length;
+ for (int i = 0; i < tb.exit.length; i++) {
+ TypedBlock e = (TypedBlock)tb.exit[i];
+ if (e.status != DONE)
+ for (int k = 0; k < n; k++)
+ if (!tb.inputs[k]) {
+ if (usage[k] == UNKNOWN && e.inputs[k]) {
+ tb.inputs[k] = true;
+ changed = true;
+ }
+ }
+ }
+
+ tb.status = changed ? CHANGED_NOW : DONE;
+ }
+ else
+ tb.status = DONE;
+ }
+
+ if (computeLiveness2except(tb))
+ tb.status = CHANGED_NOW;
+
+ tb.updating = false;
+ }
+
+ private boolean computeLiveness2except(TypedBlock tb) {
+ BasicBlock.Catch handlers = tb.toCatch;
+ boolean changed = false;
+ while (handlers != null) {
+ TypedBlock h = (TypedBlock)handlers.body;
+ computeLiveness2(h);
+ if (h.status != DONE) {
+ boolean[] in = tb.inputs;
+ int n = in.length;
+ for (int k = 0; k < n; k++)
+ if (!in[k] && h.inputs[k]) {
+ in[k] = true;
+ changed = true;
+ }
+ }
+
+ handlers = handlers.next;
+ }
+
+ return changed;
+ }
+
+ private boolean hasChanged(TypedBlock[] blocks) {
+ int n = blocks.length;
+ boolean changed = false;
+ for (int i = 0; i < n; i++) {
+ TypedBlock tb = blocks[i];
+ if (tb.status == CHANGED_NOW) {
+ tb.status = CHANGED_LAST;
+ changed = true;
+ }
+ else
+ tb.status = NOT_YET;
+ }
+
+ return changed;
+ }
+
+ private void computeUsage(CodeIterator ci, TypedBlock[] blocks, int maxLocals)
+ throws BadBytecode
+ {
+ int n = blocks.length;
+ for (int i = 0; i < n; i++) {
+ TypedBlock tb = blocks[i];
+ localsUsage = tb.localsUsage = new byte[maxLocals];
+ int pos = tb.position;
+ analyze(ci, pos, pos + tb.length);
+ localsUsage = null;
+ }
+ }
+
+ protected final void readLocal(int reg) {
+ if (localsUsage[reg] == UNKNOWN)
+ localsUsage[reg] = READ;
+ }
+
+ protected final void writeLocal(int reg) {
+ if (localsUsage[reg] == UNKNOWN)
+ localsUsage[reg] = UPDATED;
+ }
+
+ protected void analyze(CodeIterator ci, int begin, int end)
+ throws BadBytecode
+ {
+ ci.begin();
+ ci.move(begin);
+ while (ci.hasNext()) {
+ int index = ci.next();
+ if (index >= end)
+ break;
+
+ int op = ci.byteAt(index);
+ if (op < 96)
+ if (op < 54)
+ doOpcode0_53(ci, index, op);
+ else
+ doOpcode54_95(ci, index, op);
+ else
+ if (op == Opcode.IINC) {
+ // this does not call writeLocal().
+ readLocal(ci.byteAt(index + 1));
+ }
+ else if (op == Opcode.WIDE)
+ doWIDE(ci, index);
+ }
+ }
+
+ private void doOpcode0_53(CodeIterator ci, int pos, int op) {
+ switch (op) {
+ case Opcode.ILOAD :
+ case Opcode.LLOAD :
+ case Opcode.FLOAD :
+ case Opcode.DLOAD :
+ case Opcode.ALOAD :
+ readLocal(ci.byteAt(pos + 1));
+ break;
+ case Opcode.ILOAD_0 :
+ case Opcode.ILOAD_1 :
+ case Opcode.ILOAD_2 :
+ case Opcode.ILOAD_3 :
+ readLocal(op - Opcode.ILOAD_0);
+ break;
+ case Opcode.LLOAD_0 :
+ case Opcode.LLOAD_1 :
+ case Opcode.LLOAD_2 :
+ case Opcode.LLOAD_3 :
+ readLocal(op - Opcode.LLOAD_0);
+ break;
+ case Opcode.FLOAD_0 :
+ case Opcode.FLOAD_1 :
+ case Opcode.FLOAD_2 :
+ case Opcode.FLOAD_3 :
+ readLocal(op - Opcode.FLOAD_0);
+ break;
+ case Opcode.DLOAD_0 :
+ case Opcode.DLOAD_1 :
+ case Opcode.DLOAD_2 :
+ case Opcode.DLOAD_3 :
+ readLocal(op - Opcode.DLOAD_0);
+ break;
+ case Opcode.ALOAD_0 :
+ case Opcode.ALOAD_1 :
+ case Opcode.ALOAD_2 :
+ case Opcode.ALOAD_3 :
+ readLocal(op - Opcode.ALOAD_0);
+ break;
+ }
+ }
+
+ private void doOpcode54_95(CodeIterator ci, int pos, int op) {
+ switch (op) {
+ case Opcode.ISTORE :
+ case Opcode.LSTORE :
+ case Opcode.FSTORE :
+ case Opcode.DSTORE :
+ case Opcode.ASTORE :
+ writeLocal(ci.byteAt(pos + 1));
+ break;
+ case Opcode.ISTORE_0 :
+ case Opcode.ISTORE_1 :
+ case Opcode.ISTORE_2 :
+ case Opcode.ISTORE_3 :
+ writeLocal(op - Opcode.ISTORE_0);
+ break;
+ case Opcode.LSTORE_0 :
+ case Opcode.LSTORE_1 :
+ case Opcode.LSTORE_2 :
+ case Opcode.LSTORE_3 :
+ writeLocal(op - Opcode.LSTORE_0);
+ break;
+ case Opcode.FSTORE_0 :
+ case Opcode.FSTORE_1 :
+ case Opcode.FSTORE_2 :
+ case Opcode.FSTORE_3 :
+ writeLocal(op - Opcode.FSTORE_0);
+ break;
+ case Opcode.DSTORE_0 :
+ case Opcode.DSTORE_1 :
+ case Opcode.DSTORE_2 :
+ case Opcode.DSTORE_3 :
+ writeLocal(op - Opcode.DSTORE_0);
+ break;
+ case Opcode.ASTORE_0 :
+ case Opcode.ASTORE_1 :
+ case Opcode.ASTORE_2 :
+ case Opcode.ASTORE_3 :
+ writeLocal(op - Opcode.ASTORE_0);
+ break;
+ }
+ }
+
+ private void doWIDE(CodeIterator ci, int pos) throws BadBytecode {
+ int op = ci.byteAt(pos + 1);
+ int var = ci.u16bitAt(pos + 2);
+ switch (op) {
+ case Opcode.ILOAD :
+ case Opcode.LLOAD :
+ case Opcode.FLOAD :
+ case Opcode.DLOAD :
+ case Opcode.ALOAD :
+ readLocal(var);
+ break;
+ case Opcode.ISTORE :
+ case Opcode.LSTORE :
+ case Opcode.FSTORE :
+ case Opcode.DSTORE :
+ case Opcode.ASTORE :
+ writeLocal(var);
+ break;
+ case Opcode.IINC :
+ readLocal(var);
+ // this does not call writeLocal().
+ break;
+ }
+ }
+}
diff --git a/src/main/javassist/bytecode/stackmap/MapMaker.java b/src/main/javassist/bytecode/stackmap/MapMaker.java
new file mode 100644
index 0000000..92cd37c
--- /dev/null
+++ b/src/main/javassist/bytecode/stackmap/MapMaker.java
@@ -0,0 +1,526 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode.stackmap;
+
+import javassist.ClassPool;
+import javassist.bytecode.*;
+
+/**
+ * Stack map maker.
+ */
+public class MapMaker extends Tracer {
+ /*
+ public static void main(String[] args) throws Exception {
+ boolean useMain2 = args[0].equals("0");
+ if (useMain2 && args.length > 1) {
+ main2(args);
+ return;
+ }
+
+ for (int i = 0; i < args.length; i++)
+ main1(args[i]);
+ }
+
+ public static void main1(String className) throws Exception {
+ ClassPool cp = ClassPool.getDefault();
+ //javassist.CtClass cc = cp.get(className);
+ javassist.CtClass cc = cp.makeClass(new java.io.FileInputStream(className));
+ System.out.println(className);
+ ClassFile cf = cc.getClassFile();
+ java.util.List minfos = cf.getMethods();
+ for (int i = 0; i < minfos.size(); i++) {
+ MethodInfo minfo = (MethodInfo)minfos.get(i);
+ CodeAttribute ca = minfo.getCodeAttribute();
+ if (ca != null)
+ ca.setAttribute(make(cp, minfo));
+ }
+
+ cc.writeFile("tmp");
+ }
+
+ public static void main2(String[] args) throws Exception {
+ ClassPool cp = ClassPool.getDefault();
+ //javassist.CtClass cc = cp.get(args[1]);
+ javassist.CtClass cc = cp.makeClass(new java.io.FileInputStream(args[1]));
+ MethodInfo minfo;
+ if (args[2].equals("_init_"))
+ minfo = cc.getDeclaredConstructors()[0].getMethodInfo();
+ // minfo = cc.getClassInitializer().getMethodInfo();
+ else
+ minfo = cc.getDeclaredMethod(args[2]).getMethodInfo();
+
+ CodeAttribute ca = minfo.getCodeAttribute();
+ if (ca == null) {
+ System.out.println("abstarct method");
+ return;
+ }
+
+ TypedBlock[] blocks = TypedBlock.makeBlocks(minfo, ca, false);
+ MapMaker mm = new MapMaker(cp, minfo, ca);
+ mm.make(blocks, ca.getCode());
+ for (int i = 0; i < blocks.length; i++)
+ System.out.println(blocks[i]);
+ }
+ */
+
+ /**
+ * Computes the stack map table of the given method and returns it.
+ * It returns null if the given method does not have to have a
+ * stack map table.
+ */
+ public static StackMapTable make(ClassPool classes, MethodInfo minfo)
+ throws BadBytecode
+ {
+ CodeAttribute ca = minfo.getCodeAttribute();
+ if (ca == null)
+ return null;
+
+ TypedBlock[] blocks = TypedBlock.makeBlocks(minfo, ca, true);
+ if (blocks == null)
+ return null;
+
+ MapMaker mm = new MapMaker(classes, minfo, ca);
+ mm.make(blocks, ca.getCode());
+ return mm.toStackMap(blocks);
+ }
+
+ /**
+ * Computes the stack map table for J2ME.
+ * It returns null if the given method does not have to have a
+ * stack map table.
+ */
+ public static StackMap make2(ClassPool classes, MethodInfo minfo)
+ throws BadBytecode
+ {
+ CodeAttribute ca = minfo.getCodeAttribute();
+ if (ca == null)
+ return null;
+
+ TypedBlock[] blocks = TypedBlock.makeBlocks(minfo, ca, true);
+ if (blocks == null)
+ return null;
+
+ MapMaker mm = new MapMaker(classes, minfo, ca);
+ mm.make(blocks, ca.getCode());
+ return mm.toStackMap2(minfo.getConstPool(), blocks);
+ }
+
+ public MapMaker(ClassPool classes, MethodInfo minfo, CodeAttribute ca) {
+ super(classes, minfo.getConstPool(),
+ ca.getMaxStack(), ca.getMaxLocals(),
+ TypedBlock.getRetType(minfo.getDescriptor()));
+ }
+
+ protected MapMaker(MapMaker old, boolean copyStack) {
+ super(old, copyStack);
+ }
+
+ /**
+ * Runs an analyzer (Phase 1 and 2).
+ */
+ void make(TypedBlock[] blocks, byte[] code)
+ throws BadBytecode
+ {
+ TypedBlock first = blocks[0];
+ fixParamTypes(first);
+ TypeData[] srcTypes = first.localsTypes;
+ copyFrom(srcTypes.length, srcTypes, this.localsTypes);
+ make(code, first);
+
+ int n = blocks.length;
+ for (int i = 0; i < n; i++)
+ evalExpected(blocks[i]);
+ }
+
+ /*
+ * If a parameter type is String but it is used only as Object
+ * within the method body, this MapMaker class will report its type
+ * is Object. To avoid this, fixParamTypes calls TypeData.setType()
+ * on each parameter type.
+ */
+ private void fixParamTypes(TypedBlock first) throws BadBytecode {
+ TypeData[] types = first.localsTypes;
+ int n = types.length;
+ for (int i = 0; i < n; i++) {
+ TypeData t = types[i];
+ if (t instanceof TypeData.ClassName) {
+ /* Skip the following statement if t.isNullType() is true
+ * although a parameter type is never null type.
+ */
+ TypeData.setType(t, t.getName(), classPool);
+ }
+ }
+ }
+
+ // Phase 1
+
+ private void make(byte[] code, TypedBlock tb)
+ throws BadBytecode
+ {
+ BasicBlock.Catch handlers = tb.toCatch;
+ while (handlers != null) {
+ traceException(code, handlers);
+ handlers = handlers.next;
+ }
+
+ int pos = tb.position;
+ int end = pos + tb.length;
+ while (pos < end)
+ pos += doOpcode(pos, code);
+
+ if (tb.exit != null) {
+ for (int i = 0; i < tb.exit.length; i++) {
+ TypedBlock e = (TypedBlock)tb.exit[i];
+ if (e.alreadySet())
+ mergeMap(e, true);
+ else {
+ recordStackMap(e);
+ MapMaker maker = new MapMaker(this, true);
+ maker.make(code, e);
+ }
+ }
+ }
+ }
+
+ private void traceException(byte[] code, TypedBlock.Catch handler)
+ throws BadBytecode
+ {
+ TypedBlock tb = (TypedBlock)handler.body;
+ if (tb.alreadySet())
+ mergeMap(tb, false);
+ else {
+ recordStackMap(tb, handler.typeIndex);
+ MapMaker maker = new MapMaker(this, false);
+
+ /* the following code is equivalent to maker.copyFrom(this)
+ * except stackTypes are not copied.
+ */
+ maker.stackTypes[0] = tb.stackTypes[0].getSelf();
+ maker.stackTop = 1;
+ maker.make(code, tb);
+ }
+ }
+
+ private void mergeMap(TypedBlock dest, boolean mergeStack) {
+ boolean[] inputs = dest.inputs;
+ int n = inputs.length;
+ for (int i = 0; i < n; i++)
+ if (inputs[i])
+ merge(localsTypes[i], dest.localsTypes[i]);
+
+ if (mergeStack) {
+ n = stackTop;
+ for (int i = 0; i < n; i++)
+ merge(stackTypes[i], dest.stackTypes[i]);
+ }
+ }
+
+ private void merge(TypeData td, TypeData target) {
+ boolean tdIsObj = false;
+ boolean targetIsObj = false;
+ // td or target is null if it is TOP.
+ if (td != TOP && td.isObjectType())
+ tdIsObj = true;
+
+ if (target != TOP && target.isObjectType())
+ targetIsObj = true;
+
+ if (tdIsObj && targetIsObj)
+ target.merge(td);
+ }
+
+ private void recordStackMap(TypedBlock target)
+ throws BadBytecode
+ {
+ TypeData[] tStackTypes = new TypeData[stackTypes.length];
+ int st = stackTop;
+ copyFrom(st, stackTypes, tStackTypes);
+ recordStackMap0(target, st, tStackTypes);
+ }
+
+ private void recordStackMap(TypedBlock target, int exceptionType)
+ throws BadBytecode
+ {
+ String type;
+ if (exceptionType == 0)
+ type = "java.lang.Throwable";
+ else
+ type = cpool.getClassInfo(exceptionType);
+
+ TypeData[] tStackTypes = new TypeData[stackTypes.length];
+ tStackTypes[0] = new TypeData.ClassName(type);
+
+ recordStackMap0(target, 1, tStackTypes);
+ }
+
+ private void recordStackMap0(TypedBlock target, int st, TypeData[] tStackTypes)
+ throws BadBytecode
+ {
+ int n = localsTypes.length;
+ TypeData[] tLocalsTypes = new TypeData[n];
+ int k = copyFrom(n, localsTypes, tLocalsTypes);
+
+ boolean[] inputs = target.inputs;
+ for (int i = 0; i < n; i++)
+ if (!inputs[i])
+ tLocalsTypes[i] = TOP;
+
+ target.setStackMap(st, tStackTypes, k, tLocalsTypes);
+ }
+
+ // Phase 2
+
+ void evalExpected(TypedBlock target) throws BadBytecode {
+ ClassPool cp = classPool;
+ evalExpected(cp, target.stackTop, target.stackTypes);
+ TypeData[] types = target.localsTypes;
+ if (types != null) // unless this block is dead code
+ evalExpected(cp, types.length, types);
+ }
+
+ private static void evalExpected(ClassPool cp, int n, TypeData[] types)
+ throws BadBytecode
+ {
+ for (int i = 0; i < n; i++) {
+ TypeData td = types[i];
+ if (td != null)
+ td.evalExpectedType(cp);
+ }
+ }
+
+ // Phase 3
+
+ public StackMapTable toStackMap(TypedBlock[] blocks) {
+ StackMapTable.Writer writer = new StackMapTable.Writer(32);
+ int n = blocks.length;
+ TypedBlock prev = blocks[0];
+ int offsetDelta = prev.length;
+ if (prev.incoming > 0) { // the first instruction is a branch target.
+ writer.sameFrame(0);
+ offsetDelta--;
+ }
+
+ for (int i = 1; i < n; i++) {
+ TypedBlock bb = blocks[i];
+ if (isTarget(bb, blocks[i - 1])) {
+ bb.resetNumLocals();
+ int diffL = stackMapDiff(prev.numLocals, prev.localsTypes,
+ bb.numLocals, bb.localsTypes);
+ toStackMapBody(writer, bb, diffL, offsetDelta, prev);
+ offsetDelta = bb.length - 1;
+ prev = bb;
+ }
+ else
+ offsetDelta += bb.length;
+ }
+
+ return writer.toStackMapTable(cpool);
+ }
+
+ /**
+ * Returns true if cur is a branch target.
+ */
+ private boolean isTarget(TypedBlock cur, TypedBlock prev) {
+ int in = cur.incoming;
+ if (in > 1)
+ return true;
+ else if (in < 1)
+ return false;
+
+ return prev.stop;
+ }
+
+ private void toStackMapBody(StackMapTable.Writer writer, TypedBlock bb,
+ int diffL, int offsetDelta, TypedBlock prev) {
+ // if diffL is -100, two TypeData arrays do not share
+ // any elements.
+
+ int stackTop = bb.stackTop;
+ if (stackTop == 0) {
+ if (diffL == 0) {
+ writer.sameFrame(offsetDelta);
+ return;
+ }
+ else if (0 > diffL && diffL >= -3) {
+ writer.chopFrame(offsetDelta, -diffL);
+ return;
+ }
+ else if (0 < diffL && diffL <= 3) {
+ int[] data = new int[diffL];
+ int[] tags = fillStackMap(bb.numLocals - prev.numLocals,
+ prev.numLocals, data,
+ bb.localsTypes);
+ writer.appendFrame(offsetDelta, tags, data);
+ return;
+ }
+ }
+ else if (stackTop == 1 && diffL == 0) {
+ TypeData td = bb.stackTypes[0];
+ if (td == TOP)
+ writer.sameLocals(offsetDelta, StackMapTable.TOP, 0);
+ else
+ writer.sameLocals(offsetDelta, td.getTypeTag(),
+ td.getTypeData(cpool));
+ return;
+ }
+ else if (stackTop == 2 && diffL == 0) {
+ TypeData td = bb.stackTypes[0];
+ if (td != TOP && td.is2WordType()) {
+ // bb.stackTypes[1] must be TOP.
+ writer.sameLocals(offsetDelta, td.getTypeTag(),
+ td.getTypeData(cpool));
+ return;
+ }
+ }
+
+ int[] sdata = new int[stackTop];
+ int[] stags = fillStackMap(stackTop, 0, sdata, bb.stackTypes);
+ int[] ldata = new int[bb.numLocals];
+ int[] ltags = fillStackMap(bb.numLocals, 0, ldata, bb.localsTypes);
+ writer.fullFrame(offsetDelta, ltags, ldata, stags, sdata);
+ }
+
+ private int[] fillStackMap(int num, int offset, int[] data, TypeData[] types) {
+ int realNum = diffSize(types, offset, offset + num);
+ ConstPool cp = cpool;
+ int[] tags = new int[realNum];
+ int j = 0;
+ for (int i = 0; i < num; i++) {
+ TypeData td = types[offset + i];
+ if (td == TOP) {
+ tags[j] = StackMapTable.TOP;
+ data[j] = 0;
+ }
+ else {
+ tags[j] = td.getTypeTag();
+ data[j] = td.getTypeData(cp);
+ if (td.is2WordType())
+ i++;
+ }
+
+ j++;
+ }
+
+ return tags;
+ }
+
+ private static int stackMapDiff(int oldTdLen, TypeData[] oldTd,
+ int newTdLen, TypeData[] newTd)
+ {
+ int diff = newTdLen - oldTdLen;
+ int len;
+ if (diff > 0)
+ len = oldTdLen;
+ else
+ len = newTdLen;
+
+ if (stackMapEq(oldTd, newTd, len))
+ if (diff > 0)
+ return diffSize(newTd, len, newTdLen);
+ else
+ return -diffSize(oldTd, len, oldTdLen);
+ else
+ return -100;
+ }
+
+ private static boolean stackMapEq(TypeData[] oldTd, TypeData[] newTd, int len) {
+ for (int i = 0; i < len; i++) {
+ TypeData td = oldTd[i];
+ if (td == TOP) { // the next element to LONG/DOUBLE is TOP.
+ if (newTd[i] != TOP)
+ return false;
+ }
+ else
+ if (!oldTd[i].equals(newTd[i]))
+ return false;
+ }
+
+ return true;
+ }
+
+ private static int diffSize(TypeData[] types, int offset, int len) {
+ int num = 0;
+ while (offset < len) {
+ TypeData td = types[offset++];
+ num++;
+ if (td != TOP && td.is2WordType())
+ offset++;
+ }
+
+ return num;
+ }
+
+ // Phase 3 for J2ME
+
+ public StackMap toStackMap2(ConstPool cp, TypedBlock[] blocks) {
+ StackMap.Writer writer = new StackMap.Writer();
+ int n = blocks.length; // should be > 0
+ boolean[] effective = new boolean[n];
+ TypedBlock prev = blocks[0];
+
+ // Is the first instruction a branch target?
+ effective[0] = prev.incoming > 0;
+
+ int num = effective[0] ? 1 : 0;
+ for (int i = 1; i < n; i++) {
+ TypedBlock bb = blocks[i];
+ if (effective[i] = isTarget(bb, blocks[i - 1])) {
+ bb.resetNumLocals();
+ prev = bb;
+ num++;
+ }
+ }
+
+ if (num == 0)
+ return null;
+
+ writer.write16bit(num);
+ for (int i = 0; i < n; i++)
+ if (effective[i])
+ writeStackFrame(writer, cp, blocks[i].position, blocks[i]);
+
+ return writer.toStackMap(cp);
+ }
+
+ private void writeStackFrame(StackMap.Writer writer, ConstPool cp, int offset, TypedBlock tb) {
+ writer.write16bit(offset);
+ writeVerifyTypeInfo(writer, cp, tb.localsTypes, tb.numLocals);
+ writeVerifyTypeInfo(writer, cp, tb.stackTypes, tb.stackTop);
+ }
+
+ private void writeVerifyTypeInfo(StackMap.Writer writer, ConstPool cp, TypeData[] types, int num) {
+ int numDWord = 0;
+ for (int i = 0; i < num; i++) {
+ TypeData td = types[i];
+ if (td != null && td.is2WordType()) {
+ numDWord++;
+ i++;
+ }
+ }
+
+ writer.write16bit(num - numDWord);
+ for (int i = 0; i < num; i++) {
+ TypeData td = types[i];
+ if (td == TOP)
+ writer.writeVerifyTypeInfo(StackMap.TOP, 0);
+ else {
+ writer.writeVerifyTypeInfo(td.getTypeTag(), td.getTypeData(cp));
+ if (td.is2WordType())
+ i++;
+ }
+ }
+ }
+}
diff --git a/src/main/javassist/bytecode/stackmap/Tracer.java b/src/main/javassist/bytecode/stackmap/Tracer.java
new file mode 100644
index 0000000..89e788d
--- /dev/null
+++ b/src/main/javassist/bytecode/stackmap/Tracer.java
@@ -0,0 +1,920 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode.stackmap;
+
+import javassist.bytecode.ByteArray;
+import javassist.bytecode.Opcode;
+import javassist.bytecode.ConstPool;
+import javassist.bytecode.Descriptor;
+import javassist.bytecode.BadBytecode;
+import javassist.ClassPool;
+
+/*
+ * A class for performing abstract interpretation.
+ * See also MapMaker class.
+ */
+
+public abstract class Tracer implements TypeTag {
+ protected ClassPool classPool;
+ protected ConstPool cpool;
+ protected String returnType;
+
+ protected int stackTop;
+ protected TypeData[] stackTypes;
+ protected TypeData[] localsTypes;
+
+ public Tracer(ClassPool classes, ConstPool cp, int maxStack, int maxLocals,
+ String retType) {
+ classPool = classes;
+ cpool = cp;
+ returnType = retType;
+ stackTop = 0;
+ stackTypes = new TypeData[maxStack];
+ localsTypes = new TypeData[maxLocals];
+ }
+
+ public Tracer(Tracer t, boolean copyStack) {
+ classPool = t.classPool;
+ cpool = t.cpool;
+ returnType = t.returnType;
+
+ stackTop = t.stackTop;
+ int size = t.stackTypes.length;
+ stackTypes = new TypeData[size];
+ if (copyStack)
+ copyFrom(t.stackTop, t.stackTypes, stackTypes);
+
+ int size2 = t.localsTypes.length;
+ localsTypes = new TypeData[size2];
+ copyFrom(size2, t.localsTypes, localsTypes);
+ }
+
+ protected static int copyFrom(int n, TypeData[] srcTypes, TypeData[] destTypes) {
+ int k = -1;
+ for (int i = 0; i < n; i++) {
+ TypeData t = srcTypes[i];
+ destTypes[i] = t == TOP ? TOP : t.getSelf();
+ if (t != TOP)
+ if (t.is2WordType())
+ k = i + 1;
+ else
+ k = i;
+ }
+
+ return k + 1;
+ }
+
+ /**
+ * Does abstract interpretation on the given bytecode instruction.
+ * It records whether or not a local variable (i.e. register) is accessed.
+ * If the instruction requires that a local variable or
+ * a stack element has a more specific type, this method updates the
+ * type of it.
+ *
+ * @param pos the position of the instruction.
+ * @return the size of the instruction at POS.
+ */
+ protected int doOpcode(int pos, byte[] code) throws BadBytecode {
+ try {
+ int op = code[pos] & 0xff;
+ if (op < 96)
+ if (op < 54)
+ return doOpcode0_53(pos, code, op);
+ else
+ return doOpcode54_95(pos, code, op);
+ else
+ if (op < 148)
+ return doOpcode96_147(pos, code, op);
+ else
+ return doOpcode148_201(pos, code, op);
+ }
+ catch (ArrayIndexOutOfBoundsException e) {
+ throw new BadBytecode("inconsistent stack height " + e.getMessage());
+ }
+ }
+
+ protected void visitBranch(int pos, byte[] code, int offset) throws BadBytecode {}
+ protected void visitGoto(int pos, byte[] code, int offset) throws BadBytecode {}
+ protected void visitReturn(int pos, byte[] code) throws BadBytecode {}
+ protected void visitThrow(int pos, byte[] code) throws BadBytecode {}
+
+ /**
+ * @param pos the position of TABLESWITCH
+ * @param code bytecode
+ * @param n the number of case labels
+ * @param offsetPos the position of the branch-target table.
+ * @param defaultOffset the offset to the default branch target.
+ */
+ protected void visitTableSwitch(int pos, byte[] code, int n,
+ int offsetPos, int defaultOffset) throws BadBytecode {}
+
+ /**
+ * @param pos the position of LOOKUPSWITCH
+ * @param code bytecode
+ * @param n the number of case labels
+ * @param offsetPos the position of the table of pairs of a value and a branch target.
+ * @param defaultOffset the offset to the default branch target.
+ */
+ protected void visitLookupSwitch(int pos, byte[] code, int n,
+ int pairsPos, int defaultOffset) throws BadBytecode {}
+
+ /**
+ * Invoked when the visited instruction is jsr.
+ * Java6 or later does not allow using RET.
+ */
+ protected void visitJSR(int pos, byte[] code) throws BadBytecode {
+ /* Since JSR pushes a return address onto the operand stack,
+ * the stack map at the entry point of a subroutine is
+ * stackTypes resulting after executing the following code:
+ *
+ * stackTypes[stackTop++] = TOP;
+ */
+ }
+
+ /**
+ * Invoked when the visited instruction is ret or wide ret.
+ * Java6 or later does not allow using RET.
+ */
+ protected void visitRET(int pos, byte[] code) throws BadBytecode {}
+
+ private int doOpcode0_53(int pos, byte[] code, int op) throws BadBytecode {
+ int reg;
+ TypeData[] stackTypes = this.stackTypes;
+ switch (op) {
+ case Opcode.NOP :
+ break;
+ case Opcode.ACONST_NULL :
+ stackTypes[stackTop++] = new TypeData.NullType();
+ break;
+ case Opcode.ICONST_M1 :
+ case Opcode.ICONST_0 :
+ case Opcode.ICONST_1 :
+ case Opcode.ICONST_2 :
+ case Opcode.ICONST_3 :
+ case Opcode.ICONST_4 :
+ case Opcode.ICONST_5 :
+ stackTypes[stackTop++] = INTEGER;
+ break;
+ case Opcode.LCONST_0 :
+ case Opcode.LCONST_1 :
+ stackTypes[stackTop++] = LONG;
+ stackTypes[stackTop++] = TOP;
+ break;
+ case Opcode.FCONST_0 :
+ case Opcode.FCONST_1 :
+ case Opcode.FCONST_2 :
+ stackTypes[stackTop++] = FLOAT;
+ break;
+ case Opcode.DCONST_0 :
+ case Opcode.DCONST_1 :
+ stackTypes[stackTop++] = DOUBLE;
+ stackTypes[stackTop++] = TOP;
+ break;
+ case Opcode.BIPUSH :
+ case Opcode.SIPUSH :
+ stackTypes[stackTop++] = INTEGER;
+ return op == Opcode.SIPUSH ? 3 : 2;
+ case Opcode.LDC :
+ doLDC(code[pos + 1] & 0xff);
+ return 2;
+ case Opcode.LDC_W :
+ case Opcode.LDC2_W :
+ doLDC(ByteArray.readU16bit(code, pos + 1));
+ return 3;
+ case Opcode.ILOAD :
+ return doXLOAD(INTEGER, code, pos);
+ case Opcode.LLOAD :
+ return doXLOAD(LONG, code, pos);
+ case Opcode.FLOAD :
+ return doXLOAD(FLOAT, code, pos);
+ case Opcode.DLOAD :
+ return doXLOAD(DOUBLE, code, pos);
+ case Opcode.ALOAD :
+ return doALOAD(code[pos + 1] & 0xff);
+ case Opcode.ILOAD_0 :
+ case Opcode.ILOAD_1 :
+ case Opcode.ILOAD_2 :
+ case Opcode.ILOAD_3 :
+ stackTypes[stackTop++] = INTEGER;
+ break;
+ case Opcode.LLOAD_0 :
+ case Opcode.LLOAD_1 :
+ case Opcode.LLOAD_2 :
+ case Opcode.LLOAD_3 :
+ stackTypes[stackTop++] = LONG;
+ stackTypes[stackTop++] = TOP;
+ break;
+ case Opcode.FLOAD_0 :
+ case Opcode.FLOAD_1 :
+ case Opcode.FLOAD_2 :
+ case Opcode.FLOAD_3 :
+ stackTypes[stackTop++] = FLOAT;
+ break;
+ case Opcode.DLOAD_0 :
+ case Opcode.DLOAD_1 :
+ case Opcode.DLOAD_2 :
+ case Opcode.DLOAD_3 :
+ stackTypes[stackTop++] = DOUBLE;
+ stackTypes[stackTop++] = TOP;
+ break;
+ case Opcode.ALOAD_0 :
+ case Opcode.ALOAD_1 :
+ case Opcode.ALOAD_2 :
+ case Opcode.ALOAD_3 :
+ reg = op - Opcode.ALOAD_0;
+ stackTypes[stackTop++] = localsTypes[reg];
+ break;
+ case Opcode.IALOAD :
+ stackTypes[--stackTop - 1] = INTEGER;
+ break;
+ case Opcode.LALOAD :
+ stackTypes[stackTop - 2] = LONG;
+ stackTypes[stackTop - 1] = TOP;
+ break;
+ case Opcode.FALOAD :
+ stackTypes[--stackTop - 1] = FLOAT;
+ break;
+ case Opcode.DALOAD :
+ stackTypes[stackTop - 2] = DOUBLE;
+ stackTypes[stackTop - 1] = TOP;
+ break;
+ case Opcode.AALOAD : {
+ int s = --stackTop - 1;
+ TypeData data = stackTypes[s];
+ if (data == null || !data.isObjectType())
+ throw new BadBytecode("bad AALOAD");
+ else
+ stackTypes[s] = new TypeData.ArrayElement(data);
+
+ break; }
+ case Opcode.BALOAD :
+ case Opcode.CALOAD :
+ case Opcode.SALOAD :
+ stackTypes[--stackTop - 1] = INTEGER;
+ break;
+ default :
+ throw new RuntimeException("fatal");
+ }
+
+ return 1;
+ }
+
+ private void doLDC(int index) {
+ TypeData[] stackTypes = this.stackTypes;
+ int tag = cpool.getTag(index);
+ if (tag == ConstPool.CONST_String)
+ stackTypes[stackTop++] = new TypeData.ClassName("java.lang.String");
+ else if (tag == ConstPool.CONST_Integer)
+ stackTypes[stackTop++] = INTEGER;
+ else if (tag == ConstPool.CONST_Float)
+ stackTypes[stackTop++] = FLOAT;
+ else if (tag == ConstPool.CONST_Long) {
+ stackTypes[stackTop++] = LONG;
+ stackTypes[stackTop++] = TOP;
+ }
+ else if (tag == ConstPool.CONST_Double) {
+ stackTypes[stackTop++] = DOUBLE;
+ stackTypes[stackTop++] = TOP;
+ }
+ else if (tag == ConstPool.CONST_Class)
+ stackTypes[stackTop++] = new TypeData.ClassName("java.lang.Class");
+ else
+ throw new RuntimeException("bad LDC: " + tag);
+ }
+
+ private int doXLOAD(TypeData type, byte[] code, int pos) {
+ int localVar = code[pos + 1] & 0xff;
+ return doXLOAD(localVar, type);
+ }
+
+ private int doXLOAD(int localVar, TypeData type) {
+ stackTypes[stackTop++] = type;
+ if (type.is2WordType())
+ stackTypes[stackTop++] = TOP;
+
+ return 2;
+ }
+
+ private int doALOAD(int localVar) { // int localVar, TypeData type) {
+ stackTypes[stackTop++] = localsTypes[localVar];
+ return 2;
+ }
+
+ private int doOpcode54_95(int pos, byte[] code, int op) throws BadBytecode {
+ TypeData[] localsTypes = this.localsTypes;
+ TypeData[] stackTypes = this.stackTypes;
+ switch (op) {
+ case Opcode.ISTORE :
+ return doXSTORE(pos, code, INTEGER);
+ case Opcode.LSTORE :
+ return doXSTORE(pos, code, LONG);
+ case Opcode.FSTORE :
+ return doXSTORE(pos, code, FLOAT);
+ case Opcode.DSTORE :
+ return doXSTORE(pos, code, DOUBLE);
+ case Opcode.ASTORE :
+ return doASTORE(code[pos + 1] & 0xff);
+ case Opcode.ISTORE_0 :
+ case Opcode.ISTORE_1 :
+ case Opcode.ISTORE_2 :
+ case Opcode.ISTORE_3 :
+ { int var = op - Opcode.ISTORE_0;
+ localsTypes[var] = INTEGER;
+ stackTop--; }
+ break;
+ case Opcode.LSTORE_0 :
+ case Opcode.LSTORE_1 :
+ case Opcode.LSTORE_2 :
+ case Opcode.LSTORE_3 :
+ { int var = op - Opcode.LSTORE_0;
+ localsTypes[var] = LONG;
+ localsTypes[var + 1] = TOP;
+ stackTop -= 2; }
+ break;
+ case Opcode.FSTORE_0 :
+ case Opcode.FSTORE_1 :
+ case Opcode.FSTORE_2 :
+ case Opcode.FSTORE_3 :
+ { int var = op - Opcode.FSTORE_0;
+ localsTypes[var] = FLOAT;
+ stackTop--; }
+ break;
+ case Opcode.DSTORE_0 :
+ case Opcode.DSTORE_1 :
+ case Opcode.DSTORE_2 :
+ case Opcode.DSTORE_3 :
+ { int var = op - Opcode.DSTORE_0;
+ localsTypes[var] = DOUBLE;
+ localsTypes[var + 1] = TOP;
+ stackTop -= 2; }
+ break;
+ case Opcode.ASTORE_0 :
+ case Opcode.ASTORE_1 :
+ case Opcode.ASTORE_2 :
+ case Opcode.ASTORE_3 :
+ { int var = op - Opcode.ASTORE_0;
+ doASTORE(var);
+ break; }
+ case Opcode.IASTORE :
+ case Opcode.LASTORE :
+ case Opcode.FASTORE :
+ case Opcode.DASTORE :
+ stackTop -= (op == Opcode.LASTORE || op == Opcode.DASTORE) ? 4 : 3;
+ break;
+ case Opcode.AASTORE :
+ TypeData.setType(stackTypes[stackTop - 1],
+ TypeData.ArrayElement.getElementType(stackTypes[stackTop - 3].getName()),
+ classPool);
+ stackTop -= 3;
+ break;
+ case Opcode.BASTORE :
+ case Opcode.CASTORE :
+ case Opcode.SASTORE :
+ stackTop -= 3;
+ break;
+ case Opcode.POP :
+ stackTop--;
+ break;
+ case Opcode.POP2 :
+ stackTop -= 2;
+ break;
+ case Opcode.DUP : {
+ int sp = stackTop;
+ stackTypes[sp] = stackTypes[sp - 1];
+ stackTop = sp + 1;
+ break; }
+ case Opcode.DUP_X1 :
+ case Opcode.DUP_X2 : {
+ int len = op - Opcode.DUP_X1 + 2;
+ doDUP_XX(1, len);
+ int sp = stackTop;
+ stackTypes[sp - len] = stackTypes[sp];
+ stackTop = sp + 1;
+ break; }
+ case Opcode.DUP2 :
+ doDUP_XX(2, 2);
+ stackTop += 2;
+ break;
+ case Opcode.DUP2_X1 :
+ case Opcode.DUP2_X2 : {
+ int len = op - Opcode.DUP2_X1 + 3;
+ doDUP_XX(2, len);
+ int sp = stackTop;
+ stackTypes[sp - len] = stackTypes[sp];
+ stackTypes[sp - len + 1] = stackTypes[sp + 1];
+ stackTop = sp + 2;
+ break; }
+ case Opcode.SWAP : {
+ int sp = stackTop - 1;
+ TypeData t = stackTypes[sp];
+ stackTypes[sp] = stackTypes[sp - 1];
+ stackTypes[sp - 1] = t;
+ break; }
+ default :
+ throw new RuntimeException("fatal");
+ }
+
+ return 1;
+ }
+
+ private int doXSTORE(int pos, byte[] code, TypeData type) {
+ int index = code[pos + 1] & 0xff;
+ return doXSTORE(index, type);
+ }
+
+ private int doXSTORE(int index, TypeData type) {
+ stackTop--;
+ localsTypes[index] = type;
+ if (type.is2WordType()) {
+ stackTop--;
+ localsTypes[index + 1] = TOP;
+ }
+
+ return 2;
+ }
+
+ private int doASTORE(int index) {
+ stackTop--;
+ // implicit upcast might be done.
+ localsTypes[index] = stackTypes[stackTop].copy();
+ return 2;
+ }
+
+ private void doDUP_XX(int delta, int len) {
+ TypeData types[] = stackTypes;
+ int sp = stackTop - 1;
+ int end = sp - len;
+ while (sp > end) {
+ types[sp + delta] = types[sp];
+ sp--;
+ }
+ }
+
+ private int doOpcode96_147(int pos, byte[] code, int op) {
+ if (op <= Opcode.LXOR) { // IADD...LXOR
+ stackTop += Opcode.STACK_GROW[op];
+ return 1;
+ }
+
+ switch (op) {
+ case Opcode.IINC :
+ // this does not call writeLocal().
+ return 3;
+ case Opcode.I2L :
+ stackTypes[stackTop] = LONG;
+ stackTypes[stackTop - 1] = TOP;
+ stackTop++;
+ break;
+ case Opcode.I2F :
+ stackTypes[stackTop - 1] = FLOAT;
+ break;
+ case Opcode.I2D :
+ stackTypes[stackTop] = DOUBLE;
+ stackTypes[stackTop - 1] = TOP;
+ stackTop++;
+ break;
+ case Opcode.L2I :
+ stackTypes[--stackTop - 1] = INTEGER;
+ break;
+ case Opcode.L2F :
+ stackTypes[--stackTop - 1] = FLOAT;
+ break;
+ case Opcode.L2D :
+ stackTypes[stackTop - 1] = DOUBLE;
+ break;
+ case Opcode.F2I :
+ stackTypes[stackTop - 1] = INTEGER;
+ break;
+ case Opcode.F2L :
+ stackTypes[stackTop - 1] = TOP;
+ stackTypes[stackTop++] = LONG;
+ break;
+ case Opcode.F2D :
+ stackTypes[stackTop - 1] = TOP;
+ stackTypes[stackTop++] = DOUBLE;
+ break;
+ case Opcode.D2I :
+ stackTypes[--stackTop - 1] = INTEGER;
+ break;
+ case Opcode.D2L :
+ stackTypes[stackTop - 1] = LONG;
+ break;
+ case Opcode.D2F :
+ stackTypes[--stackTop - 1] = FLOAT;
+ break;
+ case Opcode.I2B :
+ case Opcode.I2C :
+ case Opcode.I2S :
+ break;
+ default :
+ throw new RuntimeException("fatal");
+ }
+
+ return 1;
+ }
+
+ private int doOpcode148_201(int pos, byte[] code, int op) throws BadBytecode {
+ switch (op) {
+ case Opcode.LCMP :
+ stackTypes[stackTop - 4] = INTEGER;
+ stackTop -= 3;
+ break;
+ case Opcode.FCMPL :
+ case Opcode.FCMPG :
+ stackTypes[--stackTop - 1] = INTEGER;
+ break;
+ case Opcode.DCMPL :
+ case Opcode.DCMPG :
+ stackTypes[stackTop - 4] = INTEGER;
+ stackTop -= 3;
+ break;
+ case Opcode.IFEQ :
+ case Opcode.IFNE :
+ case Opcode.IFLT :
+ case Opcode.IFGE :
+ case Opcode.IFGT :
+ case Opcode.IFLE :
+ stackTop--; // branch
+ visitBranch(pos, code, ByteArray.readS16bit(code, pos + 1));
+ return 3;
+ case Opcode.IF_ICMPEQ :
+ case Opcode.IF_ICMPNE :
+ case Opcode.IF_ICMPLT :
+ case Opcode.IF_ICMPGE :
+ case Opcode.IF_ICMPGT :
+ case Opcode.IF_ICMPLE :
+ case Opcode.IF_ACMPEQ :
+ case Opcode.IF_ACMPNE :
+ stackTop -= 2; // branch
+ visitBranch(pos, code, ByteArray.readS16bit(code, pos + 1));
+ return 3;
+ case Opcode.GOTO :
+ visitGoto(pos, code, ByteArray.readS16bit(code, pos + 1));
+ return 3; // branch
+ case Opcode.JSR :
+ visitJSR(pos, code);
+ return 3; // branch
+ case Opcode.RET :
+ visitRET(pos, code);
+ return 2;
+ case Opcode.TABLESWITCH : {
+ stackTop--; // branch
+ int pos2 = (pos & ~3) + 8;
+ int low = ByteArray.read32bit(code, pos2);
+ int high = ByteArray.read32bit(code, pos2 + 4);
+ int n = high - low + 1;
+ visitTableSwitch(pos, code, n, pos2 + 8, ByteArray.read32bit(code, pos2 - 4));
+ return n * 4 + 16 - (pos & 3); }
+ case Opcode.LOOKUPSWITCH : {
+ stackTop--; // branch
+ int pos2 = (pos & ~3) + 8;
+ int n = ByteArray.read32bit(code, pos2);
+ visitLookupSwitch(pos, code, n, pos2 + 4, ByteArray.read32bit(code, pos2 - 4));
+ return n * 8 + 12 - (pos & 3); }
+ case Opcode.IRETURN :
+ stackTop--;
+ visitReturn(pos, code);
+ break;
+ case Opcode.LRETURN :
+ stackTop -= 2;
+ visitReturn(pos, code);
+ break;
+ case Opcode.FRETURN :
+ stackTop--;
+ visitReturn(pos, code);
+ break;
+ case Opcode.DRETURN :
+ stackTop -= 2;
+ visitReturn(pos, code);
+ break;
+ case Opcode.ARETURN :
+ TypeData.setType(stackTypes[--stackTop], returnType, classPool);
+ visitReturn(pos, code);
+ break;
+ case Opcode.RETURN :
+ visitReturn(pos, code);
+ break;
+ case Opcode.GETSTATIC :
+ return doGetField(pos, code, false);
+ case Opcode.PUTSTATIC :
+ return doPutField(pos, code, false);
+ case Opcode.GETFIELD :
+ return doGetField(pos, code, true);
+ case Opcode.PUTFIELD :
+ return doPutField(pos, code, true);
+ case Opcode.INVOKEVIRTUAL :
+ case Opcode.INVOKESPECIAL :
+ return doInvokeMethod(pos, code, true);
+ case Opcode.INVOKESTATIC :
+ return doInvokeMethod(pos, code, false);
+ case Opcode.INVOKEINTERFACE :
+ return doInvokeIntfMethod(pos, code);
+ case 186 :
+ throw new RuntimeException("bad opcode 186");
+ case Opcode.NEW : {
+ int i = ByteArray.readU16bit(code, pos + 1);
+ stackTypes[stackTop++]
+ = new TypeData.UninitData(pos, cpool.getClassInfo(i));
+ return 3; }
+ case Opcode.NEWARRAY :
+ return doNEWARRAY(pos, code);
+ case Opcode.ANEWARRAY : {
+ int i = ByteArray.readU16bit(code, pos + 1);
+ String type = cpool.getClassInfo(i).replace('.', '/');
+ if (type.charAt(0) == '[')
+ type = "[" + type;
+ else
+ type = "[L" + type + ";";
+
+ stackTypes[stackTop - 1]
+ = new TypeData.ClassName(type);
+ return 3; }
+ case Opcode.ARRAYLENGTH :
+ TypeData.setType(stackTypes[stackTop - 1], "[Ljava.lang.Object;", classPool);
+ stackTypes[stackTop - 1] = INTEGER;
+ break;
+ case Opcode.ATHROW :
+ TypeData.setType(stackTypes[--stackTop], "java.lang.Throwable", classPool);
+ visitThrow(pos, code);
+ break;
+ case Opcode.CHECKCAST : {
+ // TypeData.setType(stackTypes[stackTop - 1], "java.lang.Object", classPool);
+ int i = ByteArray.readU16bit(code, pos + 1);
+ stackTypes[stackTop - 1] = new TypeData.ClassName(cpool.getClassInfo(i));
+ return 3; }
+ case Opcode.INSTANCEOF :
+ // TypeData.setType(stackTypes[stackTop - 1], "java.lang.Object", classPool);
+ stackTypes[stackTop - 1] = INTEGER;
+ return 3;
+ case Opcode.MONITORENTER :
+ case Opcode.MONITOREXIT :
+ stackTop--;
+ // TypeData.setType(stackTypes[stackTop], "java.lang.Object", classPool);
+ break;
+ case Opcode.WIDE :
+ return doWIDE(pos, code);
+ case Opcode.MULTIANEWARRAY :
+ return doMultiANewArray(pos, code);
+ case Opcode.IFNULL :
+ case Opcode.IFNONNULL :
+ stackTop--; // branch
+ visitBranch(pos, code, ByteArray.readS16bit(code, pos + 1));
+ return 3;
+ case Opcode.GOTO_W :
+ visitGoto(pos, code, ByteArray.read32bit(code, pos + 1));
+ return 5; // branch
+ case Opcode.JSR_W :
+ visitJSR(pos, code);
+ return 5;
+ }
+ return 1;
+ }
+
+ private int doWIDE(int pos, byte[] code) throws BadBytecode {
+ int op = code[pos + 1] & 0xff;
+ switch (op) {
+ case Opcode.ILOAD :
+ doWIDE_XLOAD(pos, code, INTEGER);
+ break;
+ case Opcode.LLOAD :
+ doWIDE_XLOAD(pos, code, LONG);
+ break;
+ case Opcode.FLOAD :
+ doWIDE_XLOAD(pos, code, FLOAT);
+ break;
+ case Opcode.DLOAD :
+ doWIDE_XLOAD(pos, code, DOUBLE);
+ break;
+ case Opcode.ALOAD : {
+ int index = ByteArray.readU16bit(code, pos + 2);
+ doALOAD(index);
+ break; }
+ case Opcode.ISTORE :
+ doWIDE_STORE(pos, code, INTEGER);
+ break;
+ case Opcode.LSTORE :
+ doWIDE_STORE(pos, code, LONG);
+ break;
+ case Opcode.FSTORE :
+ doWIDE_STORE(pos, code, FLOAT);
+ break;
+ case Opcode.DSTORE :
+ doWIDE_STORE(pos, code, DOUBLE);
+ break;
+ case Opcode.ASTORE : {
+ int index = ByteArray.readU16bit(code, pos + 2);
+ doASTORE(index);
+ break; }
+ case Opcode.IINC :
+ // this does not call writeLocal().
+ return 6;
+ case Opcode.RET :
+ visitRET(pos, code);
+ break;
+ default :
+ throw new RuntimeException("bad WIDE instruction: " + op);
+ }
+
+ return 4;
+ }
+
+ private void doWIDE_XLOAD(int pos, byte[] code, TypeData type) {
+ int index = ByteArray.readU16bit(code, pos + 2);
+ doXLOAD(index, type);
+ }
+
+ private void doWIDE_STORE(int pos, byte[] code, TypeData type) {
+ int index = ByteArray.readU16bit(code, pos + 2);
+ doXSTORE(index, type);
+ }
+
+ private int doPutField(int pos, byte[] code, boolean notStatic) throws BadBytecode {
+ int index = ByteArray.readU16bit(code, pos + 1);
+ String desc = cpool.getFieldrefType(index);
+ stackTop -= Descriptor.dataSize(desc);
+ char c = desc.charAt(0);
+ if (c == 'L')
+ TypeData.setType(stackTypes[stackTop], getFieldClassName(desc, 0), classPool);
+ else if (c == '[')
+ TypeData.setType(stackTypes[stackTop], desc, classPool);
+
+ setFieldTarget(notStatic, index);
+ return 3;
+ }
+
+ private int doGetField(int pos, byte[] code, boolean notStatic) throws BadBytecode {
+ int index = ByteArray.readU16bit(code, pos + 1);
+ setFieldTarget(notStatic, index);
+ String desc = cpool.getFieldrefType(index);
+ pushMemberType(desc);
+ return 3;
+ }
+
+ private void setFieldTarget(boolean notStatic, int index) throws BadBytecode {
+ if (notStatic) {
+ String className = cpool.getFieldrefClassName(index);
+ TypeData.setType(stackTypes[--stackTop], className, classPool);
+ }
+ }
+
+ private int doNEWARRAY(int pos, byte[] code) {
+ int s = stackTop - 1;
+ String type;
+ switch (code[pos + 1] & 0xff) {
+ case Opcode.T_BOOLEAN :
+ type = "[Z";
+ break;
+ case Opcode.T_CHAR :
+ type = "[C";
+ break;
+ case Opcode.T_FLOAT :
+ type = "[F";
+ break;
+ case Opcode.T_DOUBLE :
+ type = "[D";
+ break;
+ case Opcode.T_BYTE :
+ type = "[B";
+ break;
+ case Opcode.T_SHORT :
+ type = "[S";
+ break;
+ case Opcode.T_INT :
+ type = "[I";
+ break;
+ case Opcode.T_LONG :
+ type = "[J";
+ break;
+ default :
+ throw new RuntimeException("bad newarray");
+ }
+
+ stackTypes[s] = new TypeData.ClassName(type);
+ return 2;
+ }
+
+ private int doMultiANewArray(int pos, byte[] code) {
+ int i = ByteArray.readU16bit(code, pos + 1);
+ int dim = code[pos + 3] & 0xff;
+ stackTop -= dim - 1;
+
+ String type = cpool.getClassInfo(i).replace('.', '/');
+ stackTypes[stackTop - 1] = new TypeData.ClassName(type);
+ return 4;
+ }
+
+ private int doInvokeMethod(int pos, byte[] code, boolean notStatic) throws BadBytecode {
+ int i = ByteArray.readU16bit(code, pos + 1);
+ String desc = cpool.getMethodrefType(i);
+ checkParamTypes(desc, 1);
+ if (notStatic) {
+ String className = cpool.getMethodrefClassName(i);
+ TypeData.setType(stackTypes[--stackTop], className, classPool);
+ }
+
+ pushMemberType(desc);
+ return 3;
+ }
+
+ private int doInvokeIntfMethod(int pos, byte[] code) throws BadBytecode {
+ int i = ByteArray.readU16bit(code, pos + 1);
+ String desc = cpool.getInterfaceMethodrefType(i);
+ checkParamTypes(desc, 1);
+ String className = cpool.getInterfaceMethodrefClassName(i);
+ TypeData.setType(stackTypes[--stackTop], className, classPool);
+ pushMemberType(desc);
+ return 5;
+ }
+
+ private void pushMemberType(String descriptor) {
+ int top = 0;
+ if (descriptor.charAt(0) == '(') {
+ top = descriptor.indexOf(')') + 1;
+ if (top < 1)
+ throw new IndexOutOfBoundsException("bad descriptor: "
+ + descriptor);
+ }
+
+ TypeData[] types = stackTypes;
+ int index = stackTop;
+ switch (descriptor.charAt(top)) {
+ case '[' :
+ types[index] = new TypeData.ClassName(descriptor.substring(top));
+ break;
+ case 'L' :
+ types[index] = new TypeData.ClassName(getFieldClassName(descriptor, top));
+ break;
+ case 'J' :
+ types[index] = LONG;
+ types[index + 1] = TOP;
+ stackTop += 2;
+ return;
+ case 'F' :
+ types[index] = FLOAT;
+ break;
+ case 'D' :
+ types[index] = DOUBLE;
+ types[index + 1] = TOP;
+ stackTop += 2;
+ return;
+ case 'V' :
+ return;
+ default : // C, B, S, I, Z
+ types[index] = INTEGER;
+ break;
+ }
+
+ stackTop++;
+ }
+
+ private static String getFieldClassName(String desc, int index) {
+ return desc.substring(index + 1, desc.length() - 1).replace('/', '.');
+ }
+
+ private void checkParamTypes(String desc, int i) throws BadBytecode {
+ char c = desc.charAt(i);
+ if (c == ')')
+ return;
+
+ int k = i;
+ boolean array = false;
+ while (c == '[') {
+ array = true;
+ c = desc.charAt(++k);
+ }
+
+ if (c == 'L') {
+ k = desc.indexOf(';', k) + 1;
+ if (k <= 0)
+ throw new IndexOutOfBoundsException("bad descriptor");
+ }
+ else
+ k++;
+
+ checkParamTypes(desc, k);
+ if (!array && (c == 'J' || c == 'D'))
+ stackTop -= 2;
+ else
+ stackTop--;
+
+ if (array)
+ TypeData.setType(stackTypes[stackTop],
+ desc.substring(i, k), classPool);
+ else if (c == 'L')
+ TypeData.setType(stackTypes[stackTop],
+ desc.substring(i + 1, k - 1).replace('/', '.'), classPool);
+ }
+}
diff --git a/src/main/javassist/bytecode/stackmap/TypeData.java b/src/main/javassist/bytecode/stackmap/TypeData.java
new file mode 100644
index 0000000..f6c6c4e
--- /dev/null
+++ b/src/main/javassist/bytecode/stackmap/TypeData.java
@@ -0,0 +1,509 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode.stackmap;
+
+import javassist.ClassPool;
+import javassist.CtClass;
+import javassist.NotFoundException;
+import javassist.bytecode.ConstPool;
+import javassist.bytecode.StackMapTable;
+import javassist.bytecode.BadBytecode;
+import java.util.ArrayList;
+
+public abstract class TypeData {
+ /* Memo:
+ * array type is a subtype of Cloneable and Serializable
+ */
+
+ protected TypeData() {}
+
+ public abstract void merge(TypeData neighbor);
+
+ /**
+ * Sets the type name of this object type. If the given type name is
+ * a subclass of the current type name, then the given name becomes
+ * the name of this object type.
+ *
+ * @param className dot-separated name unless the type is an array type.
+ */
+ static void setType(TypeData td, String className, ClassPool cp) throws BadBytecode {
+ if (td == TypeTag.TOP)
+ throw new BadBytecode("unset variable");
+ else
+ td.setType(className, cp);
+ }
+
+ public abstract boolean equals(Object obj);
+
+ public abstract int getTypeTag();
+ public abstract int getTypeData(ConstPool cp);
+
+ /*
+ * See UninitData.getSelf().
+ */
+ public TypeData getSelf() { return this; }
+
+ /* An operand value is copied when it is stored in a
+ * local variable.
+ */
+ public abstract TypeData copy();
+
+ public abstract boolean isObjectType();
+ public boolean is2WordType() { return false; }
+ public boolean isNullType() { return false; }
+
+ public abstract String getName() throws BadBytecode;
+ protected abstract void setType(String s, ClassPool cp) throws BadBytecode;
+ public abstract void evalExpectedType(ClassPool cp) throws BadBytecode;
+ public abstract String getExpected() throws BadBytecode;
+
+ /**
+ * Primitive types.
+ */
+ protected static class BasicType extends TypeData {
+ private String name;
+ private int typeTag;
+
+ public BasicType(String type, int tag) {
+ name = type;
+ typeTag = tag;
+ }
+
+ public void merge(TypeData neighbor) {}
+
+ public boolean equals(Object obj) {
+ return this == obj;
+ }
+
+ public int getTypeTag() { return typeTag; }
+ public int getTypeData(ConstPool cp) { return 0; }
+
+ public boolean isObjectType() { return false; }
+
+ public boolean is2WordType() {
+ return typeTag == StackMapTable.LONG
+ || typeTag == StackMapTable.DOUBLE;
+ }
+
+ public TypeData copy() {
+ return this;
+ }
+
+ public void evalExpectedType(ClassPool cp) throws BadBytecode {}
+
+ public String getExpected() throws BadBytecode {
+ return name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ protected void setType(String s, ClassPool cp) throws BadBytecode {
+ throw new BadBytecode("conflict: " + name + " and " + s);
+ }
+
+ public String toString() { return name; }
+ }
+
+ protected static abstract class TypeName extends TypeData {
+ protected ArrayList equivalences;
+
+ protected String expectedName;
+ private CtClass cache;
+ private boolean evalDone;
+
+ protected TypeName() {
+ equivalences = new ArrayList();
+ equivalences.add(this);
+ expectedName = null;
+ cache = null;
+ evalDone = false;
+ }
+
+ public void merge(TypeData neighbor) {
+ if (this == neighbor)
+ return;
+
+ if (!(neighbor instanceof TypeName))
+ return; // neighbor might be UninitData
+
+ TypeName neighbor2 = (TypeName)neighbor;
+ ArrayList list = equivalences;
+ ArrayList list2 = neighbor2.equivalences;
+ if (list == list2)
+ return;
+
+ int n = list2.size();
+ for (int i = 0; i < n; i++) {
+ TypeName tn = (TypeName)list2.get(i);
+ add(list, tn);
+ tn.equivalences = list;
+ }
+ }
+
+ private static void add(ArrayList list, TypeData td) {
+ int n = list.size();
+ for (int i = 0; i < n; i++)
+ if (list.get(i) == td)
+ return;
+
+ list.add(td);
+ }
+
+ /* NullType overrides this method.
+ */
+ public int getTypeTag() { return StackMapTable.OBJECT; }
+
+ public int getTypeData(ConstPool cp) {
+ String type;
+ try {
+ type = getExpected();
+ } catch (BadBytecode e) {
+ throw new RuntimeException("fatal error: ", e);
+ }
+
+ return getTypeData2(cp, type);
+ }
+
+ /* NullType overrides this method.
+ */
+ protected int getTypeData2(ConstPool cp, String type) {
+ return cp.addClassInfo(type);
+ }
+
+ public boolean equals(Object obj) {
+ if (obj instanceof TypeName) {
+ try {
+ TypeName tn = (TypeName)obj;
+ return getExpected().equals(tn.getExpected());
+ }
+ catch (BadBytecode e) {}
+ }
+
+ return false;
+ }
+
+ public boolean isObjectType() { return true; }
+
+ protected void setType(String typeName, ClassPool cp) throws BadBytecode {
+ if (update(cp, expectedName, typeName))
+ expectedName = typeName;
+ }
+
+ public void evalExpectedType(ClassPool cp) throws BadBytecode {
+ if (this.evalDone)
+ return;
+
+ ArrayList equiv = this.equivalences;
+ int n = equiv.size();
+ String name = evalExpectedType2(equiv, n);
+ if (name == null) {
+ name = this.expectedName;
+ for (int i = 0; i < n; i++) {
+ TypeData td = (TypeData)equiv.get(i);
+ if (td instanceof TypeName) {
+ TypeName tn = (TypeName)td;
+ if (update(cp, name, tn.expectedName))
+ name = tn.expectedName;
+ }
+ }
+ }
+
+ for (int i = 0; i < n; i++) {
+ TypeData td = (TypeData)equiv.get(i);
+ if (td instanceof TypeName) {
+ TypeName tn = (TypeName)td;
+ tn.expectedName = name;
+ tn.cache = null;
+ tn.evalDone = true;
+ }
+ }
+ }
+
+ private String evalExpectedType2(ArrayList equiv, int n) throws BadBytecode {
+ String origName = null;
+ for (int i = 0; i < n; i++) {
+ TypeData td = (TypeData)equiv.get(i);
+ if (!td.isNullType())
+ if (origName == null)
+ origName = td.getName();
+ else if (!origName.equals(td.getName()))
+ return null;
+ }
+
+ return origName;
+ }
+
+ protected boolean isTypeName() { return true; }
+
+ private boolean update(ClassPool cp, String oldName, String typeName) throws BadBytecode {
+ if (typeName == null)
+ return false;
+ else if (oldName == null)
+ return true;
+ else if (oldName.equals(typeName))
+ return false;
+ else if (typeName.charAt(0) == '['
+ && oldName.equals("[Ljava.lang.Object;")) {
+ /* this rule is not correct but Tracer class sets the type
+ of the operand of arraylength to java.lang.Object[].
+ Thus, int[] etc. must be a subtype of java.lang.Object[].
+ */
+ return true;
+ }
+
+ try {
+ if (cache == null)
+ cache = cp.get(oldName);
+
+ CtClass cache2 = cp.get(typeName);
+ if (cache2.subtypeOf(cache)) {
+ cache = cache2;
+ return true;
+ }
+ else
+ return false;
+ }
+ catch (NotFoundException e) {
+ throw new BadBytecode("cannot find " + e.getMessage());
+ }
+ }
+
+ /* See also NullType.getExpected().
+ */
+ public String getExpected() throws BadBytecode {
+ ArrayList equiv = equivalences;
+ if (equiv.size() == 1)
+ return getName();
+ else {
+ String en = expectedName;
+ if (en == null)
+ return "java.lang.Object";
+ else
+ return en;
+ }
+ }
+
+ public String toString() {
+ try {
+ String en = expectedName;
+ if (en != null)
+ return en;
+
+ String name = getName();
+ if (equivalences.size() == 1)
+ return name;
+ else
+ return name + "?";
+ }
+ catch (BadBytecode e) {
+ return "<" + e.getMessage() + ">";
+ }
+ }
+ }
+
+ /**
+ * Type data for OBJECT.
+ */
+ public static class ClassName extends TypeName {
+ private String name; // dot separated. null if this object is a copy of another.
+
+ public ClassName(String n) {
+ name = n;
+ }
+
+ public TypeData copy() {
+ return new ClassName(name);
+ }
+
+ public String getName() { // never returns null.
+ return name;
+ }
+ }
+
+ /**
+ * Type data for NULL or OBJECT.
+ * The types represented by the instances of this class are
+ * initially NULL but will be OBJECT.
+ */
+ public static class NullType extends ClassName {
+ public NullType() {
+ super("null"); // type name
+ }
+
+ public TypeData copy() {
+ return new NullType();
+ }
+
+ public boolean isNullType() { return true; }
+
+ public int getTypeTag() {
+ try {
+ if ("null".equals(getExpected()))
+ return StackMapTable.NULL;
+ else
+ return super.getTypeTag();
+ }
+ catch (BadBytecode e) {
+ throw new RuntimeException("fatal error: ", e);
+ }
+ }
+
+ protected int getTypeData2(ConstPool cp, String type) {
+ if ("null".equals(type))
+ return 0;
+ else
+ return super.getTypeData2(cp, type);
+ }
+
+ public String getExpected() throws BadBytecode {
+ String en = expectedName;
+ if (en == null) {
+ // ArrayList equiv = equivalences;
+ // if (equiv.size() == 1)
+ // return getName();
+ // else
+ return "java.lang.Object";
+ }
+ else
+ return en;
+ }
+ }
+
+ /**
+ * Type data for OBJECT if the type is an object type and is
+ * derived as an element type from an array type by AALOAD.
+ */
+ public static class ArrayElement extends TypeName {
+ TypeData array;
+
+ public ArrayElement(TypeData a) { // a is never null
+ array = a;
+ }
+
+ public TypeData copy() {
+ return new ArrayElement(array);
+ }
+
+ protected void setType(String typeName, ClassPool cp) throws BadBytecode {
+ super.setType(typeName, cp);
+ array.setType(getArrayType(typeName), cp);
+ }
+
+ public String getName() throws BadBytecode {
+ String name = array.getName();
+ if (name.length() > 1 && name.charAt(0) == '[') {
+ char c = name.charAt(1);
+ if (c == 'L')
+ return name.substring(2, name.length() - 1).replace('/', '.');
+ else if (c == '[')
+ return name.substring(1);
+ }
+
+ throw new BadBytecode("bad array type for AALOAD: "
+ + name);
+ }
+
+ public static String getArrayType(String elementType) {
+ if (elementType.charAt(0) == '[')
+ return "[" + elementType;
+ else
+ return "[L" + elementType.replace('.', '/') + ";";
+ }
+
+ public static String getElementType(String arrayType) {
+ char c = arrayType.charAt(1);
+ if (c == 'L')
+ return arrayType.substring(2, arrayType.length() - 1).replace('/', '.');
+ else if (c == '[')
+ return arrayType.substring(1);
+ else
+ return arrayType;
+ }
+ }
+
+ /**
+ * Type data for UNINIT.
+ */
+ public static class UninitData extends TypeData {
+ String className;
+ int offset;
+ boolean initialized;
+
+ UninitData(int offset, String className) {
+ this.className = className;
+ this.offset = offset;
+ this.initialized = false;
+ }
+
+ public void merge(TypeData neighbor) {}
+
+ public int getTypeTag() { return StackMapTable.UNINIT; }
+ public int getTypeData(ConstPool cp) { return offset; }
+
+ public boolean equals(Object obj) {
+ if (obj instanceof UninitData) {
+ UninitData ud = (UninitData)obj;
+ return offset == ud.offset && className.equals(ud.className);
+ }
+ else
+ return false;
+ }
+
+ public TypeData getSelf() {
+ if (initialized)
+ return copy();
+ else
+ return this;
+ }
+
+ public TypeData copy() {
+ return new ClassName(className);
+ }
+
+ public boolean isObjectType() { return true; }
+
+ protected void setType(String typeName, ClassPool cp) throws BadBytecode {
+ initialized = true;
+ }
+
+ public void evalExpectedType(ClassPool cp) throws BadBytecode {}
+
+ public String getName() {
+ return className;
+ }
+
+ public String getExpected() { return className; }
+
+ public String toString() { return "uninit:" + className + "@" + offset; }
+ }
+
+ public static class UninitThis extends UninitData {
+ UninitThis(String className) {
+ super(-1, className);
+ }
+
+ public int getTypeTag() { return StackMapTable.THIS; }
+ public int getTypeData(ConstPool cp) { return 0; }
+
+ public boolean equals(Object obj) {
+ return obj instanceof UninitThis;
+ }
+
+ public String toString() { return "uninit:this"; }
+ }
+}
diff --git a/src/main/javassist/bytecode/stackmap/TypeTag.java b/src/main/javassist/bytecode/stackmap/TypeTag.java
new file mode 100644
index 0000000..4172068
--- /dev/null
+++ b/src/main/javassist/bytecode/stackmap/TypeTag.java
@@ -0,0 +1,28 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode.stackmap;
+
+import javassist.bytecode.StackMapTable;
+
+public interface TypeTag {
+ TypeData TOP = null;
+ TypeData INTEGER = new TypeData.BasicType("int", StackMapTable.INTEGER);
+ TypeData FLOAT = new TypeData.BasicType("float", StackMapTable.FLOAT);
+ TypeData DOUBLE = new TypeData.BasicType("double", StackMapTable.DOUBLE);
+ TypeData LONG = new TypeData.BasicType("long", StackMapTable.LONG);
+
+ // and NULL, THIS, OBJECT, UNINIT
+}
diff --git a/src/main/javassist/bytecode/stackmap/TypedBlock.java b/src/main/javassist/bytecode/stackmap/TypedBlock.java
new file mode 100644
index 0000000..65dce97
--- /dev/null
+++ b/src/main/javassist/bytecode/stackmap/TypedBlock.java
@@ -0,0 +1,249 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.bytecode.stackmap;
+
+import javassist.bytecode.*;
+
+public class TypedBlock extends BasicBlock {
+ public int stackTop, numLocals;
+ public TypeData[] stackTypes, localsTypes;
+
+ // set by a Liveness object.
+ // inputs[i] is true if the i-th variable is used within this block.
+ public boolean[] inputs;
+
+ // working area for Liveness class.
+ public boolean updating;
+ public int status;
+ public byte[] localsUsage;
+
+ /**
+ * Divides the method body into basic blocks.
+ * The type information of the first block is initialized.
+ *
+ * @param optmize if it is true and the method does not include
+ * branches, this method returns null.
+ */
+ public static TypedBlock[] makeBlocks(MethodInfo minfo, CodeAttribute ca,
+ boolean optimize)
+ throws BadBytecode
+ {
+ TypedBlock[] blocks = (TypedBlock[])new Maker().make(minfo);
+ if (optimize && blocks.length < 2)
+ if (blocks.length == 0 || blocks[0].incoming == 0)
+ return null;
+
+ ConstPool pool = minfo.getConstPool();
+ boolean isStatic = (minfo.getAccessFlags() & AccessFlag.STATIC) != 0;
+ blocks[0].initFirstBlock(ca.getMaxStack(), ca.getMaxLocals(),
+ pool.getClassName(), minfo.getDescriptor(),
+ isStatic, minfo.isConstructor());
+ new Liveness().compute(ca.iterator(), blocks, ca.getMaxLocals(),
+ blocks[0].localsTypes);
+ return blocks;
+ }
+
+ protected TypedBlock(int pos) {
+ super(pos);
+ localsTypes = null;
+ inputs = null;
+ updating = false;
+ }
+
+ protected void toString2(StringBuffer sbuf) {
+ super.toString2(sbuf);
+ sbuf.append(",\n stack={");
+ printTypes(sbuf, stackTop, stackTypes);
+ sbuf.append("}, locals={");
+ printTypes(sbuf, numLocals, localsTypes);
+ sbuf.append("}, inputs={");
+ if (inputs != null)
+ for (int i = 0; i < inputs.length; i++)
+ sbuf.append(inputs[i] ? "1, " : "0, ");
+
+ sbuf.append('}');
+ }
+
+ private void printTypes(StringBuffer sbuf, int size,
+ TypeData[] types) {
+ if (types == null)
+ return;
+
+ for (int i = 0; i < size; i++) {
+ if (i > 0)
+ sbuf.append(", ");
+
+ TypeData td = types[i];
+ sbuf.append(td == null ? "<>" : td.toString());
+ }
+ }
+
+ public boolean alreadySet() {
+ return localsTypes != null;
+ }
+
+ public void setStackMap(int st, TypeData[] stack, int nl, TypeData[] locals)
+ throws BadBytecode
+ {
+ stackTop = st;
+ stackTypes = stack;
+ numLocals = nl;
+ localsTypes = locals;
+ }
+
+ /*
+ * Computes the correct value of numLocals.
+ */
+ public void resetNumLocals() {
+ if (localsTypes != null) {
+ int nl = localsTypes.length;
+ while (nl > 0 && localsTypes[nl - 1] == TypeTag.TOP) {
+ if (nl > 1) {
+ TypeData td = localsTypes[nl - 2];
+ if (td == TypeTag.LONG || td == TypeTag.DOUBLE)
+ break;
+ }
+
+ --nl;
+ }
+
+ numLocals = nl;
+ }
+ }
+
+ public static class Maker extends BasicBlock.Maker {
+ protected BasicBlock makeBlock(int pos) {
+ return new TypedBlock(pos);
+ }
+
+ protected BasicBlock[] makeArray(int size) {
+ return new TypedBlock[size];
+ }
+ }
+
+ /**
+ * Initializes the first block by the given method descriptor.
+ *
+ * @param block the first basic block that this method initializes.
+ * @param className a dot-separated fully qualified class name.
+ * For example, <code>javassist.bytecode.stackmap.BasicBlock</code>.
+ * @param methodDesc method descriptor.
+ * @param isStatic true if the method is a static method.
+ * @param isConstructor true if the method is a constructor.
+ */
+ void initFirstBlock(int maxStack, int maxLocals, String className,
+ String methodDesc, boolean isStatic, boolean isConstructor)
+ throws BadBytecode
+ {
+ if (methodDesc.charAt(0) != '(')
+ throw new BadBytecode("no method descriptor: " + methodDesc);
+
+ stackTop = 0;
+ stackTypes = new TypeData[maxStack];
+ TypeData[] locals = new TypeData[maxLocals];
+ if (isConstructor)
+ locals[0] = new TypeData.UninitThis(className);
+ else if (!isStatic)
+ locals[0] = new TypeData.ClassName(className);
+
+ int n = isStatic ? -1 : 0;
+ int i = 1;
+ try {
+ while ((i = descToTag(methodDesc, i, ++n, locals)) > 0)
+ if (locals[n].is2WordType())
+ locals[++n] = TypeTag.TOP;
+ }
+ catch (StringIndexOutOfBoundsException e) {
+ throw new BadBytecode("bad method descriptor: "
+ + methodDesc);
+ }
+
+ numLocals = n;
+ localsTypes = locals;
+ }
+
+ private static int descToTag(String desc, int i,
+ int n, TypeData[] types)
+ throws BadBytecode
+ {
+ int i0 = i;
+ int arrayDim = 0;
+ char c = desc.charAt(i);
+ if (c == ')')
+ return 0;
+
+ while (c == '[') {
+ ++arrayDim;
+ c = desc.charAt(++i);
+ }
+
+ if (c == 'L') {
+ int i2 = desc.indexOf(';', ++i);
+ if (arrayDim > 0)
+ types[n] = new TypeData.ClassName(desc.substring(i0, ++i2));
+ else
+ types[n] = new TypeData.ClassName(desc.substring(i0 + 1, ++i2 - 1)
+ .replace('/', '.'));
+ return i2;
+ }
+ else if (arrayDim > 0) {
+ types[n] = new TypeData.ClassName(desc.substring(i0, ++i));
+ return i;
+ }
+ else {
+ TypeData t = toPrimitiveTag(c);
+ if (t == null)
+ throw new BadBytecode("bad method descriptor: " + desc);
+
+ types[n] = t;
+ return i + 1;
+ }
+ }
+
+ private static TypeData toPrimitiveTag(char c) {
+ switch (c) {
+ case 'Z' :
+ case 'C' :
+ case 'B' :
+ case 'S' :
+ case 'I' :
+ return TypeTag.INTEGER;
+ case 'J' :
+ return TypeTag.LONG;
+ case 'F' :
+ return TypeTag.FLOAT;
+ case 'D' :
+ return TypeTag.DOUBLE;
+ case 'V' :
+ default :
+ return null;
+ }
+ }
+
+ public static String getRetType(String desc) {
+ int i = desc.indexOf(')');
+ if (i < 0)
+ return "java.lang.Object";
+
+ char c = desc.charAt(i + 1);
+ if (c == '[')
+ return desc.substring(i + 1);
+ else if (c == 'L')
+ return desc.substring(i + 2, desc.length() - 1).replace('/', '.');
+ else
+ return "java.lang.Object";
+ }
+}
diff --git a/src/main/javassist/compiler/AccessorMaker.java b/src/main/javassist/compiler/AccessorMaker.java
new file mode 100644
index 0000000..7f4f918
--- /dev/null
+++ b/src/main/javassist/compiler/AccessorMaker.java
@@ -0,0 +1,259 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.compiler;
+
+import javassist.*;
+import javassist.bytecode.*;
+import java.util.HashMap;
+
+/**
+ * AccessorMaker maintains accessors to private members of an enclosing
+ * class. It is necessary for compiling a method in an inner class.
+ */
+public class AccessorMaker {
+ private CtClass clazz;
+ private int uniqueNumber;
+ private HashMap accessors;
+
+ static final String lastParamType = "javassist.runtime.Inner";
+
+ public AccessorMaker(CtClass c) {
+ clazz = c;
+ uniqueNumber = 1;
+ accessors = new HashMap();
+ }
+
+ public String getConstructor(CtClass c, String desc, MethodInfo orig)
+ throws CompileError
+ {
+ String key = "<init>:" + desc;
+ String consDesc = (String)accessors.get(key);
+ if (consDesc != null)
+ return consDesc; // already exists.
+
+ consDesc = Descriptor.appendParameter(lastParamType, desc);
+ ClassFile cf = clazz.getClassFile(); // turn on the modified flag.
+ try {
+ ConstPool cp = cf.getConstPool();
+ ClassPool pool = clazz.getClassPool();
+ MethodInfo minfo
+ = new MethodInfo(cp, MethodInfo.nameInit, consDesc);
+ minfo.setAccessFlags(0);
+ minfo.addAttribute(new SyntheticAttribute(cp));
+ ExceptionsAttribute ea = orig.getExceptionsAttribute();
+ if (ea != null)
+ minfo.addAttribute(ea.copy(cp, null));
+
+ CtClass[] params = Descriptor.getParameterTypes(desc, pool);
+ Bytecode code = new Bytecode(cp);
+ code.addAload(0);
+ int regno = 1;
+ for (int i = 0; i < params.length; ++i)
+ regno += code.addLoad(regno, params[i]);
+ code.setMaxLocals(regno + 1); // the last parameter is added.
+ code.addInvokespecial(clazz, MethodInfo.nameInit, desc);
+
+ code.addReturn(null);
+ minfo.setCodeAttribute(code.toCodeAttribute());
+ cf.addMethod(minfo);
+ }
+ catch (CannotCompileException e) {
+ throw new CompileError(e);
+ }
+ catch (NotFoundException e) {
+ throw new CompileError(e);
+ }
+
+ accessors.put(key, consDesc);
+ return consDesc;
+ }
+
+ /**
+ * Returns the name of the method for accessing a private method.
+ *
+ * @param name the name of the private method.
+ * @param desc the descriptor of the private method.
+ * @param accDesc the descriptor of the accessor method. The first
+ * parameter type is <code>clazz</code>.
+ * If the private method is static,
+ * <code>accDesc<code> must be identical to <code>desc</code>.
+ *
+ * @param orig the method info of the private method.
+ * @return
+ */
+ public String getMethodAccessor(String name, String desc, String accDesc,
+ MethodInfo orig)
+ throws CompileError
+ {
+ String key = name + ":" + desc;
+ String accName = (String)accessors.get(key);
+ if (accName != null)
+ return accName; // already exists.
+
+ ClassFile cf = clazz.getClassFile(); // turn on the modified flag.
+ accName = findAccessorName(cf);
+ try {
+ ConstPool cp = cf.getConstPool();
+ ClassPool pool = clazz.getClassPool();
+ MethodInfo minfo
+ = new MethodInfo(cp, accName, accDesc);
+ minfo.setAccessFlags(AccessFlag.STATIC);
+ minfo.addAttribute(new SyntheticAttribute(cp));
+ ExceptionsAttribute ea = orig.getExceptionsAttribute();
+ if (ea != null)
+ minfo.addAttribute(ea.copy(cp, null));
+
+ CtClass[] params = Descriptor.getParameterTypes(accDesc, pool);
+ int regno = 0;
+ Bytecode code = new Bytecode(cp);
+ for (int i = 0; i < params.length; ++i)
+ regno += code.addLoad(regno, params[i]);
+
+ code.setMaxLocals(regno);
+ if (desc == accDesc)
+ code.addInvokestatic(clazz, name, desc);
+ else
+ code.addInvokevirtual(clazz, name, desc);
+
+ code.addReturn(Descriptor.getReturnType(desc, pool));
+ minfo.setCodeAttribute(code.toCodeAttribute());
+ cf.addMethod(minfo);
+ }
+ catch (CannotCompileException e) {
+ throw new CompileError(e);
+ }
+ catch (NotFoundException e) {
+ throw new CompileError(e);
+ }
+
+ accessors.put(key, accName);
+ return accName;
+ }
+
+ /**
+ * Returns the method_info representing the added getter.
+ */
+ public MethodInfo getFieldGetter(FieldInfo finfo, boolean is_static)
+ throws CompileError
+ {
+ String fieldName = finfo.getName();
+ String key = fieldName + ":getter";
+ Object res = accessors.get(key);
+ if (res != null)
+ return (MethodInfo)res; // already exists.
+
+ ClassFile cf = clazz.getClassFile(); // turn on the modified flag.
+ String accName = findAccessorName(cf);
+ try {
+ ConstPool cp = cf.getConstPool();
+ ClassPool pool = clazz.getClassPool();
+ String fieldType = finfo.getDescriptor();
+ String accDesc;
+ if (is_static)
+ accDesc = "()" + fieldType;
+ else
+ accDesc = "(" + Descriptor.of(clazz) + ")" + fieldType;
+
+ MethodInfo minfo = new MethodInfo(cp, accName, accDesc);
+ minfo.setAccessFlags(AccessFlag.STATIC);
+ minfo.addAttribute(new SyntheticAttribute(cp));
+ Bytecode code = new Bytecode(cp);
+ if (is_static) {
+ code.addGetstatic(Bytecode.THIS, fieldName, fieldType);
+ }
+ else {
+ code.addAload(0);
+ code.addGetfield(Bytecode.THIS, fieldName, fieldType);
+ code.setMaxLocals(1);
+ }
+
+ code.addReturn(Descriptor.toCtClass(fieldType, pool));
+ minfo.setCodeAttribute(code.toCodeAttribute());
+ cf.addMethod(minfo);
+ accessors.put(key, minfo);
+ return minfo;
+ }
+ catch (CannotCompileException e) {
+ throw new CompileError(e);
+ }
+ catch (NotFoundException e) {
+ throw new CompileError(e);
+ }
+ }
+
+ /**
+ * Returns the method_info representing the added setter.
+ */
+ public MethodInfo getFieldSetter(FieldInfo finfo, boolean is_static)
+ throws CompileError
+ {
+ String fieldName = finfo.getName();
+ String key = fieldName + ":setter";
+ Object res = accessors.get(key);
+ if (res != null)
+ return (MethodInfo)res; // already exists.
+
+ ClassFile cf = clazz.getClassFile(); // turn on the modified flag.
+ String accName = findAccessorName(cf);
+ try {
+ ConstPool cp = cf.getConstPool();
+ ClassPool pool = clazz.getClassPool();
+ String fieldType = finfo.getDescriptor();
+ String accDesc;
+ if (is_static)
+ accDesc = "(" + fieldType + ")V";
+ else
+ accDesc = "(" + Descriptor.of(clazz) + fieldType + ")V";
+
+ MethodInfo minfo = new MethodInfo(cp, accName, accDesc);
+ minfo.setAccessFlags(AccessFlag.STATIC);
+ minfo.addAttribute(new SyntheticAttribute(cp));
+ Bytecode code = new Bytecode(cp);
+ int reg;
+ if (is_static) {
+ reg = code.addLoad(0, Descriptor.toCtClass(fieldType, pool));
+ code.addPutstatic(Bytecode.THIS, fieldName, fieldType);
+ }
+ else {
+ code.addAload(0);
+ reg = code.addLoad(1, Descriptor.toCtClass(fieldType, pool))
+ + 1;
+ code.addPutfield(Bytecode.THIS, fieldName, fieldType);
+ }
+
+ code.addReturn(null);
+ code.setMaxLocals(reg);
+ minfo.setCodeAttribute(code.toCodeAttribute());
+ cf.addMethod(minfo);
+ accessors.put(key, minfo);
+ return minfo;
+ }
+ catch (CannotCompileException e) {
+ throw new CompileError(e);
+ }
+ catch (NotFoundException e) {
+ throw new CompileError(e);
+ }
+ }
+
+ private String findAccessorName(ClassFile cf) {
+ String accName;
+ do {
+ accName = "access$" + uniqueNumber++;
+ } while (cf.getMethod(accName) != null);
+ return accName;
+ }
+}
diff --git a/src/main/javassist/compiler/CodeGen.java b/src/main/javassist/compiler/CodeGen.java
new file mode 100644
index 0000000..470a976
--- /dev/null
+++ b/src/main/javassist/compiler/CodeGen.java
@@ -0,0 +1,1921 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.compiler;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import javassist.compiler.ast.*;
+import javassist.bytecode.*;
+
+/* The code generator is implemeted by three files:
+ * CodeGen.java, MemberCodeGen.java, and JvstCodeGen.
+ * I just wanted to split a big file into three smaller ones.
+ */
+
+public abstract class CodeGen extends Visitor implements Opcode, TokenId {
+ static final String javaLangObject = "java.lang.Object";
+ static final String jvmJavaLangObject = "java/lang/Object";
+
+ static final String javaLangString = "java.lang.String";
+ static final String jvmJavaLangString = "java/lang/String";
+
+ protected Bytecode bytecode;
+ private int tempVar;
+ TypeChecker typeChecker;
+
+ /**
+ * true if the last visited node is a return statement.
+ */
+ protected boolean hasReturned;
+
+ /**
+ * Must be true if compilation is for a static method.
+ */
+ public boolean inStaticMethod;
+
+ protected ArrayList breakList, continueList;
+
+ /**
+ * doit() in ReturnHook is called from atReturn().
+ */
+ protected static abstract class ReturnHook {
+ ReturnHook next;
+
+ /**
+ * Returns true if the generated code ends with return,
+ * throw, or goto.
+ */
+ protected abstract boolean doit(Bytecode b, int opcode);
+
+ protected ReturnHook(CodeGen gen) {
+ next = gen.returnHooks;
+ gen.returnHooks = this;
+ }
+
+ protected void remove(CodeGen gen) {
+ gen.returnHooks = next;
+ }
+ }
+
+ protected ReturnHook returnHooks;
+
+ /* The following fields are used by atXXX() methods
+ * for returning the type of the compiled expression.
+ */
+ protected int exprType; // VOID, NULL, CLASS, BOOLEAN, INT, ...
+ protected int arrayDim;
+ protected String className; // JVM-internal representation
+
+ public CodeGen(Bytecode b) {
+ bytecode = b;
+ tempVar = -1;
+ typeChecker = null;
+ hasReturned = false;
+ inStaticMethod = false;
+ breakList = null;
+ continueList = null;
+ returnHooks = null;
+ }
+
+ public void setTypeChecker(TypeChecker checker) {
+ typeChecker = checker;
+ }
+
+ protected static void fatal() throws CompileError {
+ throw new CompileError("fatal");
+ }
+
+ public static boolean is2word(int type, int dim) {
+ return dim == 0 && (type == DOUBLE || type == LONG);
+ }
+
+ public int getMaxLocals() { return bytecode.getMaxLocals(); }
+
+ public void setMaxLocals(int n) {
+ bytecode.setMaxLocals(n);
+ }
+
+ protected void incMaxLocals(int size) {
+ bytecode.incMaxLocals(size);
+ }
+
+ /**
+ * Returns a local variable that single or double words can be
+ * stored in.
+ */
+ protected int getTempVar() {
+ if (tempVar < 0) {
+ tempVar = getMaxLocals();
+ incMaxLocals(2);
+ }
+
+ return tempVar;
+ }
+
+ protected int getLocalVar(Declarator d) {
+ int v = d.getLocalVar();
+ if (v < 0) {
+ v = getMaxLocals(); // delayed variable allocation.
+ d.setLocalVar(v);
+ incMaxLocals(1);
+ }
+
+ return v;
+ }
+
+ /**
+ * Returns the JVM-internal representation of this class name.
+ */
+ protected abstract String getThisName();
+
+ /**
+ * Returns the JVM-internal representation of this super class name.
+ */
+ protected abstract String getSuperName() throws CompileError;
+
+ /* Converts a class name into a JVM-internal representation.
+ *
+ * It may also expand a simple class name to java.lang.*.
+ * For example, this converts Object into java/lang/Object.
+ */
+ protected abstract String resolveClassName(ASTList name)
+ throws CompileError;
+
+ /* Expands a simple class name to java.lang.*.
+ * For example, this converts Object into java/lang/Object.
+ */
+ protected abstract String resolveClassName(String jvmClassName)
+ throws CompileError;
+
+ /**
+ * @param name the JVM-internal representation.
+ * name is not exapnded to java.lang.*.
+ */
+ protected static String toJvmArrayName(String name, int dim) {
+ if (name == null)
+ return null;
+
+ if (dim == 0)
+ return name;
+ else {
+ StringBuffer sbuf = new StringBuffer();
+ int d = dim;
+ while (d-- > 0)
+ sbuf.append('[');
+
+ sbuf.append('L');
+ sbuf.append(name);
+ sbuf.append(';');
+
+ return sbuf.toString();
+ }
+ }
+
+ protected static String toJvmTypeName(int type, int dim) {
+ char c = 'I';
+ switch(type) {
+ case BOOLEAN :
+ c = 'Z';
+ break;
+ case BYTE :
+ c = 'B';
+ break;
+ case CHAR :
+ c = 'C';
+ break;
+ case SHORT :
+ c = 'S';
+ break;
+ case INT :
+ c = 'I';
+ break;
+ case LONG :
+ c = 'J';
+ break;
+ case FLOAT :
+ c = 'F';
+ break;
+ case DOUBLE :
+ c = 'D';
+ break;
+ case VOID :
+ c = 'V';
+ break;
+ }
+
+ StringBuffer sbuf = new StringBuffer();
+ while (dim-- > 0)
+ sbuf.append('[');
+
+ sbuf.append(c);
+ return sbuf.toString();
+ }
+
+ public void compileExpr(ASTree expr) throws CompileError {
+ doTypeCheck(expr);
+ expr.accept(this);
+ }
+
+ public boolean compileBooleanExpr(boolean branchIf, ASTree expr)
+ throws CompileError
+ {
+ doTypeCheck(expr);
+ return booleanExpr(branchIf, expr);
+ }
+
+ public void doTypeCheck(ASTree expr) throws CompileError {
+ if (typeChecker != null)
+ expr.accept(typeChecker);
+ }
+
+ public void atASTList(ASTList n) throws CompileError { fatal(); }
+
+ public void atPair(Pair n) throws CompileError { fatal(); }
+
+ public void atSymbol(Symbol n) throws CompileError { fatal(); }
+
+ public void atFieldDecl(FieldDecl field) throws CompileError {
+ field.getInit().accept(this);
+ }
+
+ public void atMethodDecl(MethodDecl method) throws CompileError {
+ ASTList mods = method.getModifiers();
+ setMaxLocals(1);
+ while (mods != null) {
+ Keyword k = (Keyword)mods.head();
+ mods = mods.tail();
+ if (k.get() == STATIC) {
+ setMaxLocals(0);
+ inStaticMethod = true;
+ }
+ }
+
+ ASTList params = method.getParams();
+ while (params != null) {
+ atDeclarator((Declarator)params.head());
+ params = params.tail();
+ }
+
+ Stmnt s = method.getBody();
+ atMethodBody(s, method.isConstructor(),
+ method.getReturn().getType() == VOID);
+ }
+
+ /**
+ * @param isCons true if super() must be called.
+ * false if the method is a class initializer.
+ */
+ public void atMethodBody(Stmnt s, boolean isCons, boolean isVoid)
+ throws CompileError
+ {
+ if (s == null)
+ return;
+
+ if (isCons && needsSuperCall(s))
+ insertDefaultSuperCall();
+
+ hasReturned = false;
+ s.accept(this);
+ if (!hasReturned)
+ if (isVoid) {
+ bytecode.addOpcode(Opcode.RETURN);
+ hasReturned = true;
+ }
+ else
+ throw new CompileError("no return statement");
+ }
+
+ private boolean needsSuperCall(Stmnt body) throws CompileError {
+ if (body.getOperator() == BLOCK)
+ body = (Stmnt)body.head();
+
+ if (body != null && body.getOperator() == EXPR) {
+ ASTree expr = body.head();
+ if (expr != null && expr instanceof Expr
+ && ((Expr)expr).getOperator() == CALL) {
+ ASTree target = ((Expr)expr).head();
+ if (target instanceof Keyword) {
+ int token = ((Keyword)target).get();
+ return token != THIS && token != SUPER;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ protected abstract void insertDefaultSuperCall() throws CompileError;
+
+ public void atStmnt(Stmnt st) throws CompileError {
+ if (st == null)
+ return; // empty
+
+ int op = st.getOperator();
+ if (op == EXPR) {
+ ASTree expr = st.getLeft();
+ doTypeCheck(expr);
+ if (expr instanceof AssignExpr)
+ atAssignExpr((AssignExpr)expr, false);
+ else if (isPlusPlusExpr(expr)) {
+ Expr e = (Expr)expr;
+ atPlusPlus(e.getOperator(), e.oprand1(), e, false);
+ }
+ else {
+ expr.accept(this);
+ if (is2word(exprType, arrayDim))
+ bytecode.addOpcode(POP2);
+ else if (exprType != VOID)
+ bytecode.addOpcode(POP);
+ }
+ }
+ else if (op == DECL || op == BLOCK) {
+ ASTList list = st;
+ while (list != null) {
+ ASTree h = list.head();
+ list = list.tail();
+ if (h != null)
+ h.accept(this);
+ }
+ }
+ else if (op == IF)
+ atIfStmnt(st);
+ else if (op == WHILE || op == DO)
+ atWhileStmnt(st, op == WHILE);
+ else if (op == FOR)
+ atForStmnt(st);
+ else if (op == BREAK || op == CONTINUE)
+ atBreakStmnt(st, op == BREAK);
+ else if (op == TokenId.RETURN)
+ atReturnStmnt(st);
+ else if (op == THROW)
+ atThrowStmnt(st);
+ else if (op == TRY)
+ atTryStmnt(st);
+ else if (op == SWITCH)
+ atSwitchStmnt(st);
+ else if (op == SYNCHRONIZED)
+ atSyncStmnt(st);
+ else {
+ // LABEL, SWITCH label stament might be null?.
+ hasReturned = false;
+ throw new CompileError(
+ "sorry, not supported statement: TokenId " + op);
+ }
+ }
+
+ private void atIfStmnt(Stmnt st) throws CompileError {
+ ASTree expr = st.head();
+ Stmnt thenp = (Stmnt)st.tail().head();
+ Stmnt elsep = (Stmnt)st.tail().tail().head();
+ compileBooleanExpr(false, expr);
+ int pc = bytecode.currentPc();
+ int pc2 = 0;
+ bytecode.addIndex(0); // correct later
+
+ hasReturned = false;
+ if (thenp != null)
+ thenp.accept(this);
+
+ boolean thenHasReturned = hasReturned;
+ hasReturned = false;
+
+ if (elsep != null && !thenHasReturned) {
+ bytecode.addOpcode(Opcode.GOTO);
+ pc2 = bytecode.currentPc();
+ bytecode.addIndex(0);
+ }
+
+ bytecode.write16bit(pc, bytecode.currentPc() - pc + 1);
+
+ if (elsep != null) {
+ elsep.accept(this);
+ if (!thenHasReturned)
+ bytecode.write16bit(pc2, bytecode.currentPc() - pc2 + 1);
+
+ hasReturned = thenHasReturned && hasReturned;
+ }
+ }
+
+ private void atWhileStmnt(Stmnt st, boolean notDo) throws CompileError {
+ ArrayList prevBreakList = breakList;
+ ArrayList prevContList = continueList;
+ breakList = new ArrayList();
+ continueList = new ArrayList();
+
+ ASTree expr = st.head();
+ Stmnt body = (Stmnt)st.tail();
+
+ int pc = 0;
+ if (notDo) {
+ bytecode.addOpcode(Opcode.GOTO);
+ pc = bytecode.currentPc();
+ bytecode.addIndex(0);
+ }
+
+ int pc2 = bytecode.currentPc();
+ if (body != null)
+ body.accept(this);
+
+ int pc3 = bytecode.currentPc();
+ if (notDo)
+ bytecode.write16bit(pc, pc3 - pc + 1);
+
+ boolean alwaysBranch = compileBooleanExpr(true, expr);
+ bytecode.addIndex(pc2 - bytecode.currentPc() + 1);
+
+ patchGoto(breakList, bytecode.currentPc());
+ patchGoto(continueList, pc3);
+ continueList = prevContList;
+ breakList = prevBreakList;
+ hasReturned = alwaysBranch;
+ }
+
+ protected void patchGoto(ArrayList list, int targetPc) {
+ int n = list.size();
+ for (int i = 0; i < n; ++i) {
+ int pc = ((Integer)list.get(i)).intValue();
+ bytecode.write16bit(pc, targetPc - pc + 1);
+ }
+ }
+
+ private void atForStmnt(Stmnt st) throws CompileError {
+ ArrayList prevBreakList = breakList;
+ ArrayList prevContList = continueList;
+ breakList = new ArrayList();
+ continueList = new ArrayList();
+
+ Stmnt init = (Stmnt)st.head();
+ ASTList p = st.tail();
+ ASTree expr = p.head();
+ p = p.tail();
+ Stmnt update = (Stmnt)p.head();
+ Stmnt body = (Stmnt)p.tail();
+
+ if (init != null)
+ init.accept(this);
+
+ int pc = bytecode.currentPc();
+ int pc2 = 0;
+ if (expr != null) {
+ compileBooleanExpr(false, expr);
+ pc2 = bytecode.currentPc();
+ bytecode.addIndex(0);
+ }
+
+ if (body != null)
+ body.accept(this);
+
+ int pc3 = bytecode.currentPc();
+ if (update != null)
+ update.accept(this);
+
+ bytecode.addOpcode(Opcode.GOTO);
+ bytecode.addIndex(pc - bytecode.currentPc() + 1);
+
+ int pc4 = bytecode.currentPc();
+ if (expr != null)
+ bytecode.write16bit(pc2, pc4 - pc2 + 1);
+
+ patchGoto(breakList, pc4);
+ patchGoto(continueList, pc3);
+ continueList = prevContList;
+ breakList = prevBreakList;
+ hasReturned = false;
+ }
+
+ private void atSwitchStmnt(Stmnt st) throws CompileError {
+ compileExpr(st.head());
+
+ ArrayList prevBreakList = breakList;
+ breakList = new ArrayList();
+ int opcodePc = bytecode.currentPc();
+ bytecode.addOpcode(LOOKUPSWITCH);
+ int npads = 3 - (opcodePc & 3);
+ while (npads-- > 0)
+ bytecode.add(0);
+
+ Stmnt body = (Stmnt)st.tail();
+ int npairs = 0;
+ for (ASTList list = body; list != null; list = list.tail())
+ if (((Stmnt)list.head()).getOperator() == CASE)
+ ++npairs;
+
+ // opcodePc2 is the position at which the default jump offset is.
+ int opcodePc2 = bytecode.currentPc();
+ bytecode.addGap(4);
+ bytecode.add32bit(npairs);
+ bytecode.addGap(npairs * 8);
+
+ long[] pairs = new long[npairs];
+ int ipairs = 0;
+ int defaultPc = -1;
+ for (ASTList list = body; list != null; list = list.tail()) {
+ Stmnt label = (Stmnt)list.head();
+ int op = label.getOperator();
+ if (op == DEFAULT)
+ defaultPc = bytecode.currentPc();
+ else if (op != CASE)
+ fatal();
+ else {
+ pairs[ipairs++]
+ = ((long)computeLabel(label.head()) << 32) +
+ ((long)(bytecode.currentPc() - opcodePc) & 0xffffffff);
+ }
+
+ hasReturned = false;
+ ((Stmnt)label.tail()).accept(this);
+ }
+
+ Arrays.sort(pairs);
+ int pc = opcodePc2 + 8;
+ for (int i = 0; i < npairs; ++i) {
+ bytecode.write32bit(pc, (int)(pairs[i] >>> 32));
+ bytecode.write32bit(pc + 4, (int)pairs[i]);
+ pc += 8;
+ }
+
+ if (defaultPc < 0 || breakList.size() > 0)
+ hasReturned = false;
+
+ int endPc = bytecode.currentPc();
+ if (defaultPc < 0)
+ defaultPc = endPc;
+
+ bytecode.write32bit(opcodePc2, defaultPc - opcodePc);
+
+ patchGoto(breakList, endPc);
+ breakList = prevBreakList;
+ }
+
+ private int computeLabel(ASTree expr) throws CompileError {
+ doTypeCheck(expr);
+ expr = TypeChecker.stripPlusExpr(expr);
+ if (expr instanceof IntConst)
+ return (int)((IntConst)expr).get();
+ else
+ throw new CompileError("bad case label");
+ }
+
+ private void atBreakStmnt(Stmnt st, boolean notCont)
+ throws CompileError
+ {
+ if (st.head() != null)
+ throw new CompileError(
+ "sorry, not support labeled break or continue");
+
+ bytecode.addOpcode(Opcode.GOTO);
+ Integer pc = new Integer(bytecode.currentPc());
+ bytecode.addIndex(0);
+ if (notCont)
+ breakList.add(pc);
+ else
+ continueList.add(pc);
+ }
+
+ protected void atReturnStmnt(Stmnt st) throws CompileError {
+ atReturnStmnt2(st.getLeft());
+ }
+
+ protected final void atReturnStmnt2(ASTree result) throws CompileError {
+ int op;
+ if (result == null)
+ op = Opcode.RETURN;
+ else {
+ compileExpr(result);
+ if (arrayDim > 0)
+ op = ARETURN;
+ else {
+ int type = exprType;
+ if (type == DOUBLE)
+ op = DRETURN;
+ else if (type == FLOAT)
+ op = FRETURN;
+ else if (type == LONG)
+ op = LRETURN;
+ else if (isRefType(type))
+ op = ARETURN;
+ else
+ op = IRETURN;
+ }
+ }
+
+ for (ReturnHook har = returnHooks; har != null; har = har.next)
+ if (har.doit(bytecode, op)) {
+ hasReturned = true;
+ return;
+ }
+
+ bytecode.addOpcode(op);
+ hasReturned = true;
+ }
+
+ private void atThrowStmnt(Stmnt st) throws CompileError {
+ ASTree e = st.getLeft();
+ compileExpr(e);
+ if (exprType != CLASS || arrayDim > 0)
+ throw new CompileError("bad throw statement");
+
+ bytecode.addOpcode(ATHROW);
+ hasReturned = true;
+ }
+
+ /* overridden in MemberCodeGen
+ */
+ protected void atTryStmnt(Stmnt st) throws CompileError {
+ hasReturned = false;
+ }
+
+ private void atSyncStmnt(Stmnt st) throws CompileError {
+ int nbreaks = getListSize(breakList);
+ int ncontinues = getListSize(continueList);
+
+ compileExpr(st.head());
+ if (exprType != CLASS && arrayDim == 0)
+ throw new CompileError("bad type expr for synchronized block");
+
+ Bytecode bc = bytecode;
+ final int var = bc.getMaxLocals();
+ bc.incMaxLocals(1);
+ bc.addOpcode(DUP);
+ bc.addAstore(var);
+ bc.addOpcode(MONITORENTER);
+
+ ReturnHook rh = new ReturnHook(this) {
+ protected boolean doit(Bytecode b, int opcode) {
+ b.addAload(var);
+ b.addOpcode(MONITOREXIT);
+ return false;
+ }
+ };
+
+ int pc = bc.currentPc();
+ Stmnt body = (Stmnt)st.tail();
+ if (body != null)
+ body.accept(this);
+
+ int pc2 = bc.currentPc();
+ int pc3 = 0;
+ if (!hasReturned) {
+ rh.doit(bc, 0); // the 2nd arg is ignored.
+ bc.addOpcode(Opcode.GOTO);
+ pc3 = bc.currentPc();
+ bc.addIndex(0);
+ }
+
+ if (pc < pc2) { // if the body is not empty
+ int pc4 = bc.currentPc();
+ rh.doit(bc, 0); // the 2nd arg is ignored.
+ bc.addOpcode(ATHROW);
+ bc.addExceptionHandler(pc, pc2, pc4, 0);
+ }
+
+ if (!hasReturned)
+ bc.write16bit(pc3, bc.currentPc() - pc3 + 1);
+
+ rh.remove(this);
+
+ if (getListSize(breakList) != nbreaks
+ || getListSize(continueList) != ncontinues)
+ throw new CompileError(
+ "sorry, cannot break/continue in synchronized block");
+ }
+
+ private static int getListSize(ArrayList list) {
+ return list == null ? 0 : list.size();
+ }
+
+ private static boolean isPlusPlusExpr(ASTree expr) {
+ if (expr instanceof Expr) {
+ int op = ((Expr)expr).getOperator();
+ return op == PLUSPLUS || op == MINUSMINUS;
+ }
+
+ return false;
+ }
+
+ public void atDeclarator(Declarator d) throws CompileError {
+ d.setLocalVar(getMaxLocals());
+ d.setClassName(resolveClassName(d.getClassName()));
+
+ int size;
+ if (is2word(d.getType(), d.getArrayDim()))
+ size = 2;
+ else
+ size = 1;
+
+ incMaxLocals(size);
+
+ /* NOTE: Array initializers has not been supported.
+ */
+ ASTree init = d.getInitializer();
+ if (init != null) {
+ doTypeCheck(init);
+ atVariableAssign(null, '=', null, d, init, false);
+ }
+ }
+
+ public abstract void atNewExpr(NewExpr n) throws CompileError;
+
+ public abstract void atArrayInit(ArrayInit init) throws CompileError;
+
+ public void atAssignExpr(AssignExpr expr) throws CompileError {
+ atAssignExpr(expr, true);
+ }
+
+ protected void atAssignExpr(AssignExpr expr, boolean doDup)
+ throws CompileError
+ {
+ // =, %=, &=, *=, /=, +=, -=, ^=, |=, <<=, >>=, >>>=
+ int op = expr.getOperator();
+ ASTree left = expr.oprand1();
+ ASTree right = expr.oprand2();
+ if (left instanceof Variable)
+ atVariableAssign(expr, op, (Variable)left,
+ ((Variable)left).getDeclarator(),
+ right, doDup);
+ else {
+ if (left instanceof Expr) {
+ Expr e = (Expr)left;
+ if (e.getOperator() == ARRAY) {
+ atArrayAssign(expr, op, (Expr)left, right, doDup);
+ return;
+ }
+ }
+
+ atFieldAssign(expr, op, left, right, doDup);
+ }
+ }
+
+ protected static void badAssign(Expr expr) throws CompileError {
+ String msg;
+ if (expr == null)
+ msg = "incompatible type for assignment";
+ else
+ msg = "incompatible type for " + expr.getName();
+
+ throw new CompileError(msg);
+ }
+
+ /* op is either =, %=, &=, *=, /=, +=, -=, ^=, |=, <<=, >>=, or >>>=.
+ *
+ * expr and var can be null.
+ */
+ private void atVariableAssign(Expr expr, int op, Variable var,
+ Declarator d, ASTree right,
+ boolean doDup) throws CompileError
+ {
+ int varType = d.getType();
+ int varArray = d.getArrayDim();
+ String varClass = d.getClassName();
+ int varNo = getLocalVar(d);
+
+ if (op != '=')
+ atVariable(var);
+
+ // expr is null if the caller is atDeclarator().
+ if (expr == null && right instanceof ArrayInit)
+ atArrayVariableAssign((ArrayInit)right, varType, varArray, varClass);
+ else
+ atAssignCore(expr, op, right, varType, varArray, varClass);
+
+ if (doDup)
+ if (is2word(varType, varArray))
+ bytecode.addOpcode(DUP2);
+ else
+ bytecode.addOpcode(DUP);
+
+ if (varArray > 0)
+ bytecode.addAstore(varNo);
+ else if (varType == DOUBLE)
+ bytecode.addDstore(varNo);
+ else if (varType == FLOAT)
+ bytecode.addFstore(varNo);
+ else if (varType == LONG)
+ bytecode.addLstore(varNo);
+ else if (isRefType(varType))
+ bytecode.addAstore(varNo);
+ else
+ bytecode.addIstore(varNo);
+
+ exprType = varType;
+ arrayDim = varArray;
+ className = varClass;
+ }
+
+ protected abstract void atArrayVariableAssign(ArrayInit init,
+ int varType, int varArray, String varClass) throws CompileError;
+
+ private void atArrayAssign(Expr expr, int op, Expr array,
+ ASTree right, boolean doDup) throws CompileError
+ {
+ arrayAccess(array.oprand1(), array.oprand2());
+
+ if (op != '=') {
+ bytecode.addOpcode(DUP2);
+ bytecode.addOpcode(getArrayReadOp(exprType, arrayDim));
+ }
+
+ int aType = exprType;
+ int aDim = arrayDim;
+ String cname = className;
+
+ atAssignCore(expr, op, right, aType, aDim, cname);
+
+ if (doDup)
+ if (is2word(aType, aDim))
+ bytecode.addOpcode(DUP2_X2);
+ else
+ bytecode.addOpcode(DUP_X2);
+
+ bytecode.addOpcode(getArrayWriteOp(aType, aDim));
+ exprType = aType;
+ arrayDim = aDim;
+ className = cname;
+ }
+
+ protected abstract void atFieldAssign(Expr expr, int op, ASTree left,
+ ASTree right, boolean doDup) throws CompileError;
+
+ protected void atAssignCore(Expr expr, int op, ASTree right,
+ int type, int dim, String cname)
+ throws CompileError
+ {
+ if (op == PLUS_E && dim == 0 && type == CLASS)
+ atStringPlusEq(expr, type, dim, cname, right);
+ else {
+ right.accept(this);
+ if (invalidDim(exprType, arrayDim, className, type, dim, cname,
+ false) || (op != '=' && dim > 0))
+ badAssign(expr);
+
+ if (op != '=') {
+ int token = assignOps[op - MOD_E];
+ int k = lookupBinOp(token);
+ if (k < 0)
+ fatal();
+
+ atArithBinExpr(expr, token, k, type);
+ }
+ }
+
+ if (op != '=' || (dim == 0 && !isRefType(type)))
+ atNumCastExpr(exprType, type);
+
+ // type check should be done here.
+ }
+
+ private void atStringPlusEq(Expr expr, int type, int dim, String cname,
+ ASTree right)
+ throws CompileError
+ {
+ if (!jvmJavaLangString.equals(cname))
+ badAssign(expr);
+
+ convToString(type, dim); // the value might be null.
+ right.accept(this);
+ convToString(exprType, arrayDim);
+ bytecode.addInvokevirtual(javaLangString, "concat",
+ "(Ljava/lang/String;)Ljava/lang/String;");
+ exprType = CLASS;
+ arrayDim = 0;
+ className = jvmJavaLangString;
+ }
+
+ private boolean invalidDim(int srcType, int srcDim, String srcClass,
+ int destType, int destDim, String destClass,
+ boolean isCast)
+ {
+ if (srcDim != destDim)
+ if (srcType == NULL)
+ return false;
+ else if (destDim == 0 && destType == CLASS
+ && jvmJavaLangObject.equals(destClass))
+ return false;
+ else if (isCast && srcDim == 0 && srcType == CLASS
+ && jvmJavaLangObject.equals(srcClass))
+ return false;
+ else
+ return true;
+
+ return false;
+ }
+
+ public void atCondExpr(CondExpr expr) throws CompileError {
+ booleanExpr(false, expr.condExpr());
+ int pc = bytecode.currentPc();
+ bytecode.addIndex(0); // correct later
+ expr.thenExpr().accept(this);
+ int dim1 = arrayDim;
+ bytecode.addOpcode(Opcode.GOTO);
+ int pc2 = bytecode.currentPc();
+ bytecode.addIndex(0);
+ bytecode.write16bit(pc, bytecode.currentPc() - pc + 1);
+ expr.elseExpr().accept(this);
+ if (dim1 != arrayDim)
+ throw new CompileError("type mismatch in ?:");
+
+ bytecode.write16bit(pc2, bytecode.currentPc() - pc2 + 1);
+ }
+
+ static final int[] binOp = {
+ '+', DADD, FADD, LADD, IADD,
+ '-', DSUB, FSUB, LSUB, ISUB,
+ '*', DMUL, FMUL, LMUL, IMUL,
+ '/', DDIV, FDIV, LDIV, IDIV,
+ '%', DREM, FREM, LREM, IREM,
+ '|', NOP, NOP, LOR, IOR,
+ '^', NOP, NOP, LXOR, IXOR,
+ '&', NOP, NOP, LAND, IAND,
+ LSHIFT, NOP, NOP, LSHL, ISHL,
+ RSHIFT, NOP, NOP, LSHR, ISHR,
+ ARSHIFT, NOP, NOP, LUSHR, IUSHR };
+
+ static int lookupBinOp(int token) {
+ int[] code = binOp;
+ int s = code.length;
+ for (int k = 0; k < s; k = k + 5)
+ if (code[k] == token)
+ return k;
+
+ return -1;
+ }
+
+ public void atBinExpr(BinExpr expr) throws CompileError {
+ int token = expr.getOperator();
+
+ /* arithmetic operators: +, -, *, /, %, |, ^, &, <<, >>, >>>
+ */
+ int k = lookupBinOp(token);
+ if (k >= 0) {
+ expr.oprand1().accept(this);
+ ASTree right = expr.oprand2();
+ if (right == null)
+ return; // see TypeChecker.atBinExpr().
+
+ int type1 = exprType;
+ int dim1 = arrayDim;
+ String cname1 = className;
+ right.accept(this);
+ if (dim1 != arrayDim)
+ throw new CompileError("incompatible array types");
+
+ if (token == '+' && dim1 == 0
+ && (type1 == CLASS || exprType == CLASS))
+ atStringConcatExpr(expr, type1, dim1, cname1);
+ else
+ atArithBinExpr(expr, token, k, type1);
+ }
+ else {
+ /* equation: &&, ||, ==, !=, <=, >=, <, >
+ */
+ booleanExpr(true, expr);
+ bytecode.addIndex(7);
+ bytecode.addIconst(0); // false
+ bytecode.addOpcode(Opcode.GOTO);
+ bytecode.addIndex(4);
+ bytecode.addIconst(1); // true
+ }
+ }
+
+ /* arrayDim values of the two oprands must be equal.
+ * If an oprand type is not a numeric type, this method
+ * throws an exception.
+ */
+ private void atArithBinExpr(Expr expr, int token,
+ int index, int type1) throws CompileError
+ {
+ if (arrayDim != 0)
+ badTypes(expr);
+
+ int type2 = exprType;
+ if (token == LSHIFT || token == RSHIFT || token == ARSHIFT)
+ if (type2 == INT || type2 == SHORT
+ || type2 == CHAR || type2 == BYTE)
+ exprType = type1;
+ else
+ badTypes(expr);
+ else
+ convertOprandTypes(type1, type2, expr);
+
+ int p = typePrecedence(exprType);
+ if (p >= 0) {
+ int op = binOp[index + p + 1];
+ if (op != NOP) {
+ if (p == P_INT && exprType != BOOLEAN)
+ exprType = INT; // type1 may be BYTE, ...
+
+ bytecode.addOpcode(op);
+ return;
+ }
+ }
+
+ badTypes(expr);
+ }
+
+ private void atStringConcatExpr(Expr expr, int type1, int dim1,
+ String cname1) throws CompileError
+ {
+ int type2 = exprType;
+ int dim2 = arrayDim;
+ boolean type2Is2 = is2word(type2, dim2);
+ boolean type2IsString
+ = (type2 == CLASS && jvmJavaLangString.equals(className));
+
+ if (type2Is2)
+ convToString(type2, dim2);
+
+ if (is2word(type1, dim1)) {
+ bytecode.addOpcode(DUP_X2);
+ bytecode.addOpcode(POP);
+ }
+ else
+ bytecode.addOpcode(SWAP);
+
+ // even if type1 is String, the left operand might be null.
+ convToString(type1, dim1);
+ bytecode.addOpcode(SWAP);
+
+ if (!type2Is2 && !type2IsString)
+ convToString(type2, dim2);
+
+ bytecode.addInvokevirtual(javaLangString, "concat",
+ "(Ljava/lang/String;)Ljava/lang/String;");
+ exprType = CLASS;
+ arrayDim = 0;
+ className = jvmJavaLangString;
+ }
+
+ private void convToString(int type, int dim) throws CompileError {
+ final String method = "valueOf";
+
+ if (isRefType(type) || dim > 0)
+ bytecode.addInvokestatic(javaLangString, method,
+ "(Ljava/lang/Object;)Ljava/lang/String;");
+ else if (type == DOUBLE)
+ bytecode.addInvokestatic(javaLangString, method,
+ "(D)Ljava/lang/String;");
+ else if (type == FLOAT)
+ bytecode.addInvokestatic(javaLangString, method,
+ "(F)Ljava/lang/String;");
+ else if (type == LONG)
+ bytecode.addInvokestatic(javaLangString, method,
+ "(J)Ljava/lang/String;");
+ else if (type == BOOLEAN)
+ bytecode.addInvokestatic(javaLangString, method,
+ "(Z)Ljava/lang/String;");
+ else if (type == CHAR)
+ bytecode.addInvokestatic(javaLangString, method,
+ "(C)Ljava/lang/String;");
+ else if (type == VOID)
+ throw new CompileError("void type expression");
+ else /* INT, BYTE, SHORT */
+ bytecode.addInvokestatic(javaLangString, method,
+ "(I)Ljava/lang/String;");
+ }
+
+ /* Produces the opcode to branch if the condition is true.
+ * The oprand is not produced.
+ *
+ * @return true if the compiled code is GOTO (always branch).
+ */
+ private boolean booleanExpr(boolean branchIf, ASTree expr)
+ throws CompileError
+ {
+ boolean isAndAnd;
+ int op = getCompOperator(expr);
+ if (op == EQ) { // ==, !=, ...
+ BinExpr bexpr = (BinExpr)expr;
+ int type1 = compileOprands(bexpr);
+ // here, arrayDim might represent the array dim. of the left oprand
+ // if the right oprand is NULL.
+ compareExpr(branchIf, bexpr.getOperator(), type1, bexpr);
+ }
+ else if (op == '!')
+ booleanExpr(!branchIf, ((Expr)expr).oprand1());
+ else if ((isAndAnd = (op == ANDAND)) || op == OROR) {
+ BinExpr bexpr = (BinExpr)expr;
+ booleanExpr(!isAndAnd, bexpr.oprand1());
+ int pc = bytecode.currentPc();
+ bytecode.addIndex(0); // correct later
+
+ booleanExpr(isAndAnd, bexpr.oprand2());
+ bytecode.write16bit(pc, bytecode.currentPc() - pc + 3);
+ if (branchIf != isAndAnd) {
+ bytecode.addIndex(6); // skip GOTO instruction
+ bytecode.addOpcode(Opcode.GOTO);
+ }
+ }
+ else if (isAlwaysBranch(expr, branchIf)) {
+ bytecode.addOpcode(Opcode.GOTO);
+ return true; // always branch
+ }
+ else { // others
+ expr.accept(this);
+ if (exprType != BOOLEAN || arrayDim != 0)
+ throw new CompileError("boolean expr is required");
+
+ bytecode.addOpcode(branchIf ? IFNE : IFEQ);
+ }
+
+ exprType = BOOLEAN;
+ arrayDim = 0;
+ return false;
+ }
+
+
+ private static boolean isAlwaysBranch(ASTree expr, boolean branchIf) {
+ if (expr instanceof Keyword) {
+ int t = ((Keyword)expr).get();
+ return branchIf ? t == TRUE : t == FALSE;
+ }
+
+ return false;
+ }
+
+ static int getCompOperator(ASTree expr) throws CompileError {
+ if (expr instanceof Expr) {
+ Expr bexpr = (Expr)expr;
+ int token = bexpr.getOperator();
+ if (token == '!')
+ return '!';
+ else if ((bexpr instanceof BinExpr)
+ && token != OROR && token != ANDAND
+ && token != '&' && token != '|')
+ return EQ; // ==, !=, ...
+ else
+ return token;
+ }
+
+ return ' '; // others
+ }
+
+ private int compileOprands(BinExpr expr) throws CompileError {
+ expr.oprand1().accept(this);
+ int type1 = exprType;
+ int dim1 = arrayDim;
+ expr.oprand2().accept(this);
+ if (dim1 != arrayDim)
+ if (type1 != NULL && exprType != NULL)
+ throw new CompileError("incompatible array types");
+ else if (exprType == NULL)
+ arrayDim = dim1;
+
+ if (type1 == NULL)
+ return exprType;
+ else
+ return type1;
+ }
+
+ private static final int ifOp[] = { EQ, IF_ICMPEQ, IF_ICMPNE,
+ NEQ, IF_ICMPNE, IF_ICMPEQ,
+ LE, IF_ICMPLE, IF_ICMPGT,
+ GE, IF_ICMPGE, IF_ICMPLT,
+ '<', IF_ICMPLT, IF_ICMPGE,
+ '>', IF_ICMPGT, IF_ICMPLE };
+
+ private static final int ifOp2[] = { EQ, IFEQ, IFNE,
+ NEQ, IFNE, IFEQ,
+ LE, IFLE, IFGT,
+ GE, IFGE, IFLT,
+ '<', IFLT, IFGE,
+ '>', IFGT, IFLE };
+
+ /* Produces the opcode to branch if the condition is true.
+ * The oprands are not produced.
+ *
+ * Parameter expr - compare expression ==, !=, <=, >=, <, >
+ */
+ private void compareExpr(boolean branchIf,
+ int token, int type1, BinExpr expr)
+ throws CompileError
+ {
+ if (arrayDim == 0)
+ convertOprandTypes(type1, exprType, expr);
+
+ int p = typePrecedence(exprType);
+ if (p == P_OTHER || arrayDim > 0)
+ if (token == EQ)
+ bytecode.addOpcode(branchIf ? IF_ACMPEQ : IF_ACMPNE);
+ else if (token == NEQ)
+ bytecode.addOpcode(branchIf ? IF_ACMPNE : IF_ACMPEQ);
+ else
+ badTypes(expr);
+ else
+ if (p == P_INT) {
+ int op[] = ifOp;
+ for (int i = 0; i < op.length; i += 3)
+ if (op[i] == token) {
+ bytecode.addOpcode(op[i + (branchIf ? 1 : 2)]);
+ return;
+ }
+
+ badTypes(expr);
+ }
+ else {
+ if (p == P_DOUBLE)
+ if (token == '<' || token == LE)
+ bytecode.addOpcode(DCMPG);
+ else
+ bytecode.addOpcode(DCMPL);
+ else if (p == P_FLOAT)
+ if (token == '<' || token == LE)
+ bytecode.addOpcode(FCMPG);
+ else
+ bytecode.addOpcode(FCMPL);
+ else if (p == P_LONG)
+ bytecode.addOpcode(LCMP); // 1: >, 0: =, -1: <
+ else
+ fatal();
+
+ int[] op = ifOp2;
+ for (int i = 0; i < op.length; i += 3)
+ if (op[i] == token) {
+ bytecode.addOpcode(op[i + (branchIf ? 1 : 2)]);
+ return;
+ }
+
+ badTypes(expr);
+ }
+ }
+
+ protected static void badTypes(Expr expr) throws CompileError {
+ throw new CompileError("invalid types for " + expr.getName());
+ }
+
+ private static final int P_DOUBLE = 0;
+ private static final int P_FLOAT = 1;
+ private static final int P_LONG = 2;
+ private static final int P_INT = 3;
+ private static final int P_OTHER = -1;
+
+ protected static boolean isRefType(int type) {
+ return type == CLASS || type == NULL;
+ }
+
+ private static int typePrecedence(int type) {
+ if (type == DOUBLE)
+ return P_DOUBLE;
+ else if (type == FLOAT)
+ return P_FLOAT;
+ else if (type == LONG)
+ return P_LONG;
+ else if (isRefType(type))
+ return P_OTHER;
+ else if (type == VOID)
+ return P_OTHER; // this is wrong, but ...
+ else
+ return P_INT; // BOOLEAN, BYTE, CHAR, SHORT, INT
+ }
+
+ // used in TypeChecker.
+ static boolean isP_INT(int type) {
+ return typePrecedence(type) == P_INT;
+ }
+
+ // used in TypeChecker.
+ static boolean rightIsStrong(int type1, int type2) {
+ int type1_p = typePrecedence(type1);
+ int type2_p = typePrecedence(type2);
+ return type1_p >= 0 && type2_p >= 0 && type1_p > type2_p;
+ }
+
+ private static final int[] castOp = {
+ /* D F L I */
+ /* double */ NOP, D2F, D2L, D2I,
+ /* float */ F2D, NOP, F2L, F2I,
+ /* long */ L2D, L2F, NOP, L2I,
+ /* other */ I2D, I2F, I2L, NOP };
+
+ /* do implicit type conversion.
+ * arrayDim values of the two oprands must be zero.
+ */
+ private void convertOprandTypes(int type1, int type2, Expr expr)
+ throws CompileError
+ {
+ boolean rightStrong;
+ int type1_p = typePrecedence(type1);
+ int type2_p = typePrecedence(type2);
+
+ if (type2_p < 0 && type1_p < 0) // not primitive types
+ return;
+
+ if (type2_p < 0 || type1_p < 0) // either is not a primitive type
+ badTypes(expr);
+
+ int op, result_type;
+ if (type1_p <= type2_p) {
+ rightStrong = false;
+ exprType = type1;
+ op = castOp[type2_p * 4 + type1_p];
+ result_type = type1_p;
+ }
+ else {
+ rightStrong = true;
+ op = castOp[type1_p * 4 + type2_p];
+ result_type = type2_p;
+ }
+
+ if (rightStrong) {
+ if (result_type == P_DOUBLE || result_type == P_LONG) {
+ if (type1_p == P_DOUBLE || type1_p == P_LONG)
+ bytecode.addOpcode(DUP2_X2);
+ else
+ bytecode.addOpcode(DUP2_X1);
+
+ bytecode.addOpcode(POP2);
+ bytecode.addOpcode(op);
+ bytecode.addOpcode(DUP2_X2);
+ bytecode.addOpcode(POP2);
+ }
+ else if (result_type == P_FLOAT) {
+ if (type1_p == P_LONG) {
+ bytecode.addOpcode(DUP_X2);
+ bytecode.addOpcode(POP);
+ }
+ else
+ bytecode.addOpcode(SWAP);
+
+ bytecode.addOpcode(op);
+ bytecode.addOpcode(SWAP);
+ }
+ else
+ fatal();
+ }
+ else if (op != NOP)
+ bytecode.addOpcode(op);
+ }
+
+ public void atCastExpr(CastExpr expr) throws CompileError {
+ String cname = resolveClassName(expr.getClassName());
+ String toClass = checkCastExpr(expr, cname);
+ int srcType = exprType;
+ exprType = expr.getType();
+ arrayDim = expr.getArrayDim();
+ className = cname;
+ if (toClass == null)
+ atNumCastExpr(srcType, exprType); // built-in type
+ else
+ bytecode.addCheckcast(toClass);
+ }
+
+ public void atInstanceOfExpr(InstanceOfExpr expr) throws CompileError {
+ String cname = resolveClassName(expr.getClassName());
+ String toClass = checkCastExpr(expr, cname);
+ bytecode.addInstanceof(toClass);
+ exprType = BOOLEAN;
+ arrayDim = 0;
+ }
+
+ private String checkCastExpr(CastExpr expr, String name)
+ throws CompileError
+ {
+ final String msg = "invalid cast";
+ ASTree oprand = expr.getOprand();
+ int dim = expr.getArrayDim();
+ int type = expr.getType();
+ oprand.accept(this);
+ int srcType = exprType;
+ if (invalidDim(srcType, arrayDim, className, type, dim, name, true)
+ || srcType == VOID || type == VOID)
+ throw new CompileError(msg);
+
+ if (type == CLASS) {
+ if (!isRefType(srcType))
+ throw new CompileError(msg);
+
+ return toJvmArrayName(name, dim);
+ }
+ else
+ if (dim > 0)
+ return toJvmTypeName(type, dim);
+ else
+ return null; // built-in type
+ }
+
+ void atNumCastExpr(int srcType, int destType)
+ throws CompileError
+ {
+ if (srcType == destType)
+ return;
+
+ int op, op2;
+ int stype = typePrecedence(srcType);
+ int dtype = typePrecedence(destType);
+ if (0 <= stype && stype < 3)
+ op = castOp[stype * 4 + dtype];
+ else
+ op = NOP;
+
+ if (destType == DOUBLE)
+ op2 = I2D;
+ else if (destType == FLOAT)
+ op2 = I2F;
+ else if (destType == LONG)
+ op2 = I2L;
+ else if (destType == SHORT)
+ op2 = I2S;
+ else if (destType == CHAR)
+ op2 = I2C;
+ else if (destType == BYTE)
+ op2 = I2B;
+ else
+ op2 = NOP;
+
+ if (op != NOP)
+ bytecode.addOpcode(op);
+
+ if (op == NOP || op == L2I || op == F2I || op == D2I)
+ if (op2 != NOP)
+ bytecode.addOpcode(op2);
+ }
+
+ public void atExpr(Expr expr) throws CompileError {
+ // array access, member access,
+ // (unary) +, (unary) -, ++, --, !, ~
+
+ int token = expr.getOperator();
+ ASTree oprand = expr.oprand1();
+ if (token == '.') {
+ String member = ((Symbol)expr.oprand2()).get();
+ if (member.equals("class"))
+ atClassObject(expr); // .class
+ else
+ atFieldRead(expr);
+ }
+ else if (token == MEMBER) { // field read
+ /* MEMBER ('#') is an extension by Javassist.
+ * The compiler internally uses # for compiling .class
+ * expressions such as "int.class".
+ */
+ atFieldRead(expr);
+ }
+ else if (token == ARRAY)
+ atArrayRead(oprand, expr.oprand2());
+ else if (token == PLUSPLUS || token == MINUSMINUS)
+ atPlusPlus(token, oprand, expr, true);
+ else if (token == '!') {
+ booleanExpr(false, expr);
+ bytecode.addIndex(7);
+ bytecode.addIconst(1);
+ bytecode.addOpcode(Opcode.GOTO);
+ bytecode.addIndex(4);
+ bytecode.addIconst(0);
+ }
+ else if (token == CALL) // method call
+ fatal();
+ else {
+ expr.oprand1().accept(this);
+ int type = typePrecedence(exprType);
+ if (arrayDim > 0)
+ badType(expr);
+
+ if (token == '-') {
+ if (type == P_DOUBLE)
+ bytecode.addOpcode(DNEG);
+ else if (type == P_FLOAT)
+ bytecode.addOpcode(FNEG);
+ else if (type == P_LONG)
+ bytecode.addOpcode(LNEG);
+ else if (type == P_INT) {
+ bytecode.addOpcode(INEG);
+ exprType = INT; // type may be BYTE, ...
+ }
+ else
+ badType(expr);
+ }
+ else if (token == '~') {
+ if (type == P_INT) {
+ bytecode.addIconst(-1);
+ bytecode.addOpcode(IXOR);
+ exprType = INT; // type may be BYTE. ...
+ }
+ else if (type == P_LONG) {
+ bytecode.addLconst(-1);
+ bytecode.addOpcode(LXOR);
+ }
+ else
+ badType(expr);
+
+ }
+ else if (token == '+') {
+ if (type == P_OTHER)
+ badType(expr);
+
+ // do nothing. ignore.
+ }
+ else
+ fatal();
+ }
+ }
+
+ protected static void badType(Expr expr) throws CompileError {
+ throw new CompileError("invalid type for " + expr.getName());
+ }
+
+ public abstract void atCallExpr(CallExpr expr) throws CompileError;
+
+ protected abstract void atFieldRead(ASTree expr) throws CompileError;
+
+ public void atClassObject(Expr expr) throws CompileError {
+ ASTree op1 = expr.oprand1();
+ if (!(op1 instanceof Symbol))
+ throw new CompileError("fatal error: badly parsed .class expr");
+
+ String cname = ((Symbol)op1).get();
+ if (cname.startsWith("[")) {
+ int i = cname.indexOf("[L");
+ if (i >= 0) {
+ String name = cname.substring(i + 2, cname.length() - 1);
+ String name2 = resolveClassName(name);
+ if (!name.equals(name2)) {
+ /* For example, to obtain String[].class,
+ * "[Ljava.lang.String;" (not "[Ljava/lang/String"!)
+ * must be passed to Class.forName().
+ */
+ name2 = MemberResolver.jvmToJavaName(name2);
+ StringBuffer sbuf = new StringBuffer();
+ while (i-- >= 0)
+ sbuf.append('[');
+
+ sbuf.append('L').append(name2).append(';');
+ cname = sbuf.toString();
+ }
+ }
+ }
+ else {
+ cname = resolveClassName(MemberResolver.javaToJvmName(cname));
+ cname = MemberResolver.jvmToJavaName(cname);
+ }
+
+ atClassObject2(cname);
+ exprType = CLASS;
+ arrayDim = 0;
+ className = "java/lang/Class";
+ }
+
+ /* MemberCodeGen overrides this method.
+ */
+ protected void atClassObject2(String cname) throws CompileError {
+ int start = bytecode.currentPc();
+ bytecode.addLdc(cname);
+ bytecode.addInvokestatic("java.lang.Class", "forName",
+ "(Ljava/lang/String;)Ljava/lang/Class;");
+ int end = bytecode.currentPc();
+ bytecode.addOpcode(Opcode.GOTO);
+ int pc = bytecode.currentPc();
+ bytecode.addIndex(0); // correct later
+
+ bytecode.addExceptionHandler(start, end, bytecode.currentPc(),
+ "java.lang.ClassNotFoundException");
+
+ /* -- the following code is for inlining a call to DotClass.fail().
+
+ int var = getMaxLocals();
+ incMaxLocals(1);
+ bytecode.growStack(1);
+ bytecode.addAstore(var);
+
+ bytecode.addNew("java.lang.NoClassDefFoundError");
+ bytecode.addOpcode(DUP);
+ bytecode.addAload(var);
+ bytecode.addInvokevirtual("java.lang.ClassNotFoundException",
+ "getMessage", "()Ljava/lang/String;");
+ bytecode.addInvokespecial("java.lang.NoClassDefFoundError", "<init>",
+ "(Ljava/lang/String;)V");
+ */
+
+ bytecode.growStack(1);
+ bytecode.addInvokestatic("javassist.runtime.DotClass", "fail",
+ "(Ljava/lang/ClassNotFoundException;)"
+ + "Ljava/lang/NoClassDefFoundError;");
+ bytecode.addOpcode(ATHROW);
+ bytecode.write16bit(pc, bytecode.currentPc() - pc + 1);
+ }
+
+ public void atArrayRead(ASTree array, ASTree index)
+ throws CompileError
+ {
+ arrayAccess(array, index);
+ bytecode.addOpcode(getArrayReadOp(exprType, arrayDim));
+ }
+
+ protected void arrayAccess(ASTree array, ASTree index)
+ throws CompileError
+ {
+ array.accept(this);
+ int type = exprType;
+ int dim = arrayDim;
+ if (dim == 0)
+ throw new CompileError("bad array access");
+
+ String cname = className;
+
+ index.accept(this);
+ if (typePrecedence(exprType) != P_INT || arrayDim > 0)
+ throw new CompileError("bad array index");
+
+ exprType = type;
+ arrayDim = dim - 1;
+ className = cname;
+ }
+
+ protected static int getArrayReadOp(int type, int dim) {
+ if (dim > 0)
+ return AALOAD;
+
+ switch (type) {
+ case DOUBLE :
+ return DALOAD;
+ case FLOAT :
+ return FALOAD;
+ case LONG :
+ return LALOAD;
+ case INT :
+ return IALOAD;
+ case SHORT :
+ return SALOAD;
+ case CHAR :
+ return CALOAD;
+ case BYTE :
+ case BOOLEAN :
+ return BALOAD;
+ default :
+ return AALOAD;
+ }
+ }
+
+ protected static int getArrayWriteOp(int type, int dim) {
+ if (dim > 0)
+ return AASTORE;
+
+ switch (type) {
+ case DOUBLE :
+ return DASTORE;
+ case FLOAT :
+ return FASTORE;
+ case LONG :
+ return LASTORE;
+ case INT :
+ return IASTORE;
+ case SHORT :
+ return SASTORE;
+ case CHAR :
+ return CASTORE;
+ case BYTE :
+ case BOOLEAN :
+ return BASTORE;
+ default :
+ return AASTORE;
+ }
+ }
+
+ private void atPlusPlus(int token, ASTree oprand, Expr expr,
+ boolean doDup) throws CompileError
+ {
+ boolean isPost = oprand == null; // ++i or i++?
+ if (isPost)
+ oprand = expr.oprand2();
+
+ if (oprand instanceof Variable) {
+ Declarator d = ((Variable)oprand).getDeclarator();
+ int t = exprType = d.getType();
+ arrayDim = d.getArrayDim();
+ int var = getLocalVar(d);
+ if (arrayDim > 0)
+ badType(expr);
+
+ if (t == DOUBLE) {
+ bytecode.addDload(var);
+ if (doDup && isPost)
+ bytecode.addOpcode(DUP2);
+
+ bytecode.addDconst(1.0);
+ bytecode.addOpcode(token == PLUSPLUS ? DADD : DSUB);
+ if (doDup && !isPost)
+ bytecode.addOpcode(DUP2);
+
+ bytecode.addDstore(var);
+ }
+ else if (t == LONG) {
+ bytecode.addLload(var);
+ if (doDup && isPost)
+ bytecode.addOpcode(DUP2);
+
+ bytecode.addLconst((long)1);
+ bytecode.addOpcode(token == PLUSPLUS ? LADD : LSUB);
+ if (doDup && !isPost)
+ bytecode.addOpcode(DUP2);
+
+ bytecode.addLstore(var);
+ }
+ else if (t == FLOAT) {
+ bytecode.addFload(var);
+ if (doDup && isPost)
+ bytecode.addOpcode(DUP);
+
+ bytecode.addFconst(1.0f);
+ bytecode.addOpcode(token == PLUSPLUS ? FADD : FSUB);
+ if (doDup && !isPost)
+ bytecode.addOpcode(DUP);
+
+ bytecode.addFstore(var);
+ }
+ else if (t == BYTE || t == CHAR || t == SHORT || t == INT) {
+ if (doDup && isPost)
+ bytecode.addIload(var);
+
+ int delta = token == PLUSPLUS ? 1 : -1;
+ if (var > 0xff) {
+ bytecode.addOpcode(WIDE);
+ bytecode.addOpcode(IINC);
+ bytecode.addIndex(var);
+ bytecode.addIndex(delta);
+ }
+ else {
+ bytecode.addOpcode(IINC);
+ bytecode.add(var);
+ bytecode.add(delta);
+ }
+
+ if (doDup && !isPost)
+ bytecode.addIload(var);
+ }
+ else
+ badType(expr);
+ }
+ else {
+ if (oprand instanceof Expr) {
+ Expr e = (Expr)oprand;
+ if (e.getOperator() == ARRAY) {
+ atArrayPlusPlus(token, isPost, e, doDup);
+ return;
+ }
+ }
+
+ atFieldPlusPlus(token, isPost, oprand, expr, doDup);
+ }
+ }
+
+ public void atArrayPlusPlus(int token, boolean isPost,
+ Expr expr, boolean doDup) throws CompileError
+ {
+ arrayAccess(expr.oprand1(), expr.oprand2());
+ int t = exprType;
+ int dim = arrayDim;
+ if (dim > 0)
+ badType(expr);
+
+ bytecode.addOpcode(DUP2);
+ bytecode.addOpcode(getArrayReadOp(t, arrayDim));
+ int dup_code = is2word(t, dim) ? DUP2_X2 : DUP_X2;
+ atPlusPlusCore(dup_code, doDup, token, isPost, expr);
+ bytecode.addOpcode(getArrayWriteOp(t, dim));
+ }
+
+ protected void atPlusPlusCore(int dup_code, boolean doDup,
+ int token, boolean isPost,
+ Expr expr) throws CompileError
+ {
+ int t = exprType;
+
+ if (doDup && isPost)
+ bytecode.addOpcode(dup_code);
+
+ if (t == INT || t == BYTE || t == CHAR || t == SHORT) {
+ bytecode.addIconst(1);
+ bytecode.addOpcode(token == PLUSPLUS ? IADD : ISUB);
+ exprType = INT;
+ }
+ else if (t == LONG) {
+ bytecode.addLconst((long)1);
+ bytecode.addOpcode(token == PLUSPLUS ? LADD : LSUB);
+ }
+ else if (t == FLOAT) {
+ bytecode.addFconst(1.0f);
+ bytecode.addOpcode(token == PLUSPLUS ? FADD : FSUB);
+ }
+ else if (t == DOUBLE) {
+ bytecode.addDconst(1.0);
+ bytecode.addOpcode(token == PLUSPLUS ? DADD : DSUB);
+ }
+ else
+ badType(expr);
+
+ if (doDup && !isPost)
+ bytecode.addOpcode(dup_code);
+ }
+
+ protected abstract void atFieldPlusPlus(int token, boolean isPost,
+ ASTree oprand, Expr expr, boolean doDup) throws CompileError;
+
+ public abstract void atMember(Member n) throws CompileError;
+
+ public void atVariable(Variable v) throws CompileError {
+ Declarator d = v.getDeclarator();
+ exprType = d.getType();
+ arrayDim = d.getArrayDim();
+ className = d.getClassName();
+ int var = getLocalVar(d);
+
+ if (arrayDim > 0)
+ bytecode.addAload(var);
+ else
+ switch (exprType) {
+ case CLASS :
+ bytecode.addAload(var);
+ break;
+ case LONG :
+ bytecode.addLload(var);
+ break;
+ case FLOAT :
+ bytecode.addFload(var);
+ break;
+ case DOUBLE :
+ bytecode.addDload(var);
+ break;
+ default : // BOOLEAN, BYTE, CHAR, SHORT, INT
+ bytecode.addIload(var);
+ break;
+ }
+ }
+
+ public void atKeyword(Keyword k) throws CompileError {
+ arrayDim = 0;
+ int token = k.get();
+ switch (token) {
+ case TRUE :
+ bytecode.addIconst(1);
+ exprType = BOOLEAN;
+ break;
+ case FALSE :
+ bytecode.addIconst(0);
+ exprType = BOOLEAN;
+ break;
+ case NULL :
+ bytecode.addOpcode(ACONST_NULL);
+ exprType = NULL;
+ break;
+ case THIS :
+ case SUPER :
+ if (inStaticMethod)
+ throw new CompileError("not-available: "
+ + (token == THIS ? "this" : "super"));
+
+ bytecode.addAload(0);
+ exprType = CLASS;
+ if (token == THIS)
+ className = getThisName();
+ else
+ className = getSuperName();
+ break;
+ default :
+ fatal();
+ }
+ }
+
+ public void atStringL(StringL s) throws CompileError {
+ exprType = CLASS;
+ arrayDim = 0;
+ className = jvmJavaLangString;
+ bytecode.addLdc(s.get());
+ }
+
+ public void atIntConst(IntConst i) throws CompileError {
+ arrayDim = 0;
+ long value = i.get();
+ int type = i.getType();
+ if (type == IntConstant || type == CharConstant) {
+ exprType = (type == IntConstant ? INT : CHAR);
+ bytecode.addIconst((int)value);
+ }
+ else {
+ exprType = LONG;
+ bytecode.addLconst(value);
+ }
+ }
+
+ public void atDoubleConst(DoubleConst d) throws CompileError {
+ arrayDim = 0;
+ if (d.getType() == DoubleConstant) {
+ exprType = DOUBLE;
+ bytecode.addDconst(d.get());
+ }
+ else {
+ exprType = FLOAT;
+ bytecode.addFconst((float)d.get());
+ }
+ }
+}
diff --git a/src/main/javassist/compiler/CompileError.java b/src/main/javassist/compiler/CompileError.java
new file mode 100644
index 0000000..2756d15
--- /dev/null
+++ b/src/main/javassist/compiler/CompileError.java
@@ -0,0 +1,52 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.compiler;
+
+import javassist.CannotCompileException;
+import javassist.NotFoundException;
+
+public class CompileError extends Exception {
+ private Lex lex;
+ private String reason;
+
+ public CompileError(String s, Lex l) {
+ reason = s;
+ lex = l;
+ }
+
+ public CompileError(String s) {
+ reason = s;
+ lex = null;
+ }
+
+ public CompileError(CannotCompileException e) {
+ this(e.getReason());
+ }
+
+ public CompileError(NotFoundException e) {
+ this("cannot find " + e.getMessage());
+ }
+
+ public Lex getLex() { return lex; }
+
+ public String getMessage() {
+ return reason;
+ }
+
+ public String toString() {
+ return "compile error: " + reason;
+ }
+}
diff --git a/src/main/javassist/compiler/Javac.java b/src/main/javassist/compiler/Javac.java
new file mode 100644
index 0000000..9314bbc
--- /dev/null
+++ b/src/main/javassist/compiler/Javac.java
@@ -0,0 +1,609 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.compiler;
+
+import javassist.CtClass;
+import javassist.CtPrimitiveType;
+import javassist.CtMember;
+import javassist.CtField;
+import javassist.CtBehavior;
+import javassist.CtMethod;
+import javassist.CtConstructor;
+import javassist.CannotCompileException;
+import javassist.Modifier;
+import javassist.bytecode.Bytecode;
+import javassist.bytecode.CodeAttribute;
+import javassist.bytecode.LocalVariableAttribute;
+import javassist.bytecode.BadBytecode;
+import javassist.bytecode.Opcode;
+import javassist.NotFoundException;
+
+import javassist.compiler.ast.*;
+
+public class Javac {
+ JvstCodeGen gen;
+ SymbolTable stable;
+ private Bytecode bytecode;
+
+ public static final String param0Name = "$0";
+ public static final String resultVarName = "$_";
+ public static final String proceedName = "$proceed";
+
+ /**
+ * Constructs a compiler.
+ *
+ * @param thisClass the class that a compiled method/field
+ * belongs to.
+ */
+ public Javac(CtClass thisClass) {
+ this(new Bytecode(thisClass.getClassFile2().getConstPool(), 0, 0),
+ thisClass);
+ }
+
+ /**
+ * Constructs a compiler.
+ * The produced bytecode is stored in the <code>Bytecode</code> object
+ * specified by <code>b</code>.
+ *
+ * @param thisClass the class that a compiled method/field
+ * belongs to.
+ */
+ public Javac(Bytecode b, CtClass thisClass) {
+ gen = new JvstCodeGen(b, thisClass, thisClass.getClassPool());
+ stable = new SymbolTable();
+ bytecode = b;
+ }
+
+ /**
+ * Returns the produced bytecode.
+ */
+ public Bytecode getBytecode() { return bytecode; }
+
+ /**
+ * Compiles a method, constructor, or field declaration
+ * to a class.
+ * A field declaration can declare only one field.
+ *
+ * <p>In a method or constructor body, $0, $1, ... and $_
+ * are not available.
+ *
+ * @return a <code>CtMethod</code>, <code>CtConstructor</code>,
+ * or <code>CtField</code> object.
+ * @see #recordProceed(String,String)
+ */
+ public CtMember compile(String src) throws CompileError {
+ Parser p = new Parser(new Lex(src));
+ ASTList mem = p.parseMember1(stable);
+ try {
+ if (mem instanceof FieldDecl)
+ return compileField((FieldDecl)mem);
+ else {
+ CtBehavior cb = compileMethod(p, (MethodDecl)mem);
+ CtClass decl = cb.getDeclaringClass();
+ cb.getMethodInfo2()
+ .rebuildStackMapIf6(decl.getClassPool(),
+ decl.getClassFile2());
+ return cb;
+ }
+ }
+ catch (BadBytecode bb) {
+ throw new CompileError(bb.getMessage());
+ }
+ catch (CannotCompileException e) {
+ throw new CompileError(e.getMessage());
+ }
+ }
+
+ public static class CtFieldWithInit extends CtField {
+ private ASTree init;
+
+ CtFieldWithInit(CtClass type, String name, CtClass declaring)
+ throws CannotCompileException
+ {
+ super(type, name, declaring);
+ init = null;
+ }
+
+ protected void setInit(ASTree i) { init = i; }
+
+ protected ASTree getInitAST() {
+ return init;
+ }
+ }
+
+ private CtField compileField(FieldDecl fd)
+ throws CompileError, CannotCompileException
+ {
+ CtFieldWithInit f;
+ Declarator d = fd.getDeclarator();
+ f = new CtFieldWithInit(gen.resolver.lookupClass(d),
+ d.getVariable().get(), gen.getThisClass());
+ f.setModifiers(MemberResolver.getModifiers(fd.getModifiers()));
+ if (fd.getInit() != null)
+ f.setInit(fd.getInit());
+
+ return f;
+ }
+
+ private CtBehavior compileMethod(Parser p, MethodDecl md)
+ throws CompileError
+ {
+ int mod = MemberResolver.getModifiers(md.getModifiers());
+ CtClass[] plist = gen.makeParamList(md);
+ CtClass[] tlist = gen.makeThrowsList(md);
+ recordParams(plist, Modifier.isStatic(mod));
+ md = p.parseMethod2(stable, md);
+ try {
+ if (md.isConstructor()) {
+ CtConstructor cons = new CtConstructor(plist,
+ gen.getThisClass());
+ cons.setModifiers(mod);
+ md.accept(gen);
+ cons.getMethodInfo().setCodeAttribute(
+ bytecode.toCodeAttribute());
+ cons.setExceptionTypes(tlist);
+ return cons;
+ }
+ else {
+ Declarator r = md.getReturn();
+ CtClass rtype = gen.resolver.lookupClass(r);
+ recordReturnType(rtype, false);
+ CtMethod method = new CtMethod(rtype, r.getVariable().get(),
+ plist, gen.getThisClass());
+ method.setModifiers(mod);
+ gen.setThisMethod(method);
+ md.accept(gen);
+ if (md.getBody() != null)
+ method.getMethodInfo().setCodeAttribute(
+ bytecode.toCodeAttribute());
+ else
+ method.setModifiers(mod | Modifier.ABSTRACT);
+
+ method.setExceptionTypes(tlist);
+ return method;
+ }
+ }
+ catch (NotFoundException e) {
+ throw new CompileError(e.toString());
+ }
+ }
+
+ /**
+ * Compiles a method (or constructor) body.
+ *
+ * @src a single statement or a block.
+ * If null, this method produces a body returning zero or null.
+ */
+ public Bytecode compileBody(CtBehavior method, String src)
+ throws CompileError
+ {
+ try {
+ int mod = method.getModifiers();
+ recordParams(method.getParameterTypes(), Modifier.isStatic(mod));
+
+ CtClass rtype;
+ if (method instanceof CtMethod) {
+ gen.setThisMethod((CtMethod)method);
+ rtype = ((CtMethod)method).getReturnType();
+ }
+ else
+ rtype = CtClass.voidType;
+
+ recordReturnType(rtype, false);
+ boolean isVoid = rtype == CtClass.voidType;
+
+ if (src == null)
+ makeDefaultBody(bytecode, rtype);
+ else {
+ Parser p = new Parser(new Lex(src));
+ SymbolTable stb = new SymbolTable(stable);
+ Stmnt s = p.parseStatement(stb);
+ if (p.hasMore())
+ throw new CompileError(
+ "the method/constructor body must be surrounded by {}");
+
+ boolean callSuper = false;
+ if (method instanceof CtConstructor)
+ callSuper = !((CtConstructor)method).isClassInitializer();
+
+ gen.atMethodBody(s, callSuper, isVoid);
+ }
+
+ return bytecode;
+ }
+ catch (NotFoundException e) {
+ throw new CompileError(e.toString());
+ }
+ }
+
+ private static void makeDefaultBody(Bytecode b, CtClass type) {
+ int op;
+ int value;
+ if (type instanceof CtPrimitiveType) {
+ CtPrimitiveType pt = (CtPrimitiveType)type;
+ op = pt.getReturnOp();
+ if (op == Opcode.DRETURN)
+ value = Opcode.DCONST_0;
+ else if (op == Opcode.FRETURN)
+ value = Opcode.FCONST_0;
+ else if (op == Opcode.LRETURN)
+ value = Opcode.LCONST_0;
+ else if (op == Opcode.RETURN)
+ value = Opcode.NOP;
+ else
+ value = Opcode.ICONST_0;
+ }
+ else {
+ op = Opcode.ARETURN;
+ value = Opcode.ACONST_NULL;
+ }
+
+ if (value != Opcode.NOP)
+ b.addOpcode(value);
+
+ b.addOpcode(op);
+ }
+
+ /**
+ * Records local variables available at the specified program counter.
+ * If the LocalVariableAttribute is not available, this method does not
+ * record any local variable. It only returns false.
+ *
+ * @param pc program counter (>= 0)
+ * @return false if the CodeAttribute does not include a
+ * LocalVariableAttribute.
+ */
+ public boolean recordLocalVariables(CodeAttribute ca, int pc)
+ throws CompileError
+ {
+ LocalVariableAttribute va
+ = (LocalVariableAttribute)
+ ca.getAttribute(LocalVariableAttribute.tag);
+ if (va == null)
+ return false;
+
+ int n = va.tableLength();
+ for (int i = 0; i < n; ++i) {
+ int start = va.startPc(i);
+ int len = va.codeLength(i);
+ if (start <= pc && pc < start + len)
+ gen.recordVariable(va.descriptor(i), va.variableName(i),
+ va.index(i), stable);
+ }
+
+ return true;
+ }
+
+ /**
+ * Records parameter names if the LocalVariableAttribute is available.
+ * It returns false unless the LocalVariableAttribute is available.
+ *
+ * @param numOfLocalVars the number of local variables used
+ * for storing the parameters.
+ * @return false if the CodeAttribute does not include a
+ * LocalVariableAttribute.
+ */
+ public boolean recordParamNames(CodeAttribute ca, int numOfLocalVars)
+ throws CompileError
+ {
+ LocalVariableAttribute va
+ = (LocalVariableAttribute)
+ ca.getAttribute(LocalVariableAttribute.tag);
+ if (va == null)
+ return false;
+
+ int n = va.tableLength();
+ for (int i = 0; i < n; ++i) {
+ int index = va.index(i);
+ if (index < numOfLocalVars)
+ gen.recordVariable(va.descriptor(i), va.variableName(i),
+ index, stable);
+ }
+
+ return true;
+ }
+
+
+ /**
+ * Makes variables $0 (this), $1, $2, ..., and $args represent method
+ * parameters. $args represents an array of all the parameters.
+ * It also makes $$ available as a parameter list of method call.
+ *
+ * <p>This must be called before calling <code>compileStmnt()</code> and
+ * <code>compileExpr()</code>. The correct value of
+ * <code>isStatic</code> must be recorded before compilation.
+ * <code>maxLocals</code> is updated to include $0,...
+ */
+ public int recordParams(CtClass[] params, boolean isStatic)
+ throws CompileError
+ {
+ return gen.recordParams(params, isStatic, "$", "$args", "$$", stable);
+ }
+
+ /**
+ * Makes variables $0, $1, $2, ..., and $args represent method
+ * parameters. $args represents an array of all the parameters.
+ * It also makes $$ available as a parameter list of method call.
+ * $0 can represent a local variable other than THIS (variable 0).
+ * $class is also made available.
+ *
+ * <p>This must be called before calling <code>compileStmnt()</code> and
+ * <code>compileExpr()</code>. The correct value of
+ * <code>isStatic</code> must be recorded before compilation.
+ * <code>maxLocals</code> is updated to include $0,...
+ *
+ * @paaram use0 true if $0 is used.
+ * @param varNo the register number of $0 (use0 is true)
+ * or $1 (otherwise).
+ * @param target the type of $0 (it can be null if use0 is false).
+ * It is used as the name of the type represented
+ * by $class.
+ * @param isStatic true if the method in which the compiled bytecode
+ * is embedded is static.
+ */
+ public int recordParams(String target, CtClass[] params,
+ boolean use0, int varNo, boolean isStatic)
+ throws CompileError
+ {
+ return gen.recordParams(params, isStatic, "$", "$args", "$$",
+ use0, varNo, target, stable);
+ }
+
+ /**
+ * Sets <code>maxLocals</code> to <code>max</code>.
+ * This method tells the compiler the local variables that have been
+ * allocated for the rest of the code. When the compiler needs
+ * new local variables, the local variables at the index <code>max</code>,
+ * <code>max + 1</code>, ... are assigned.
+ *
+ * <p>This method is indirectly called by <code>recordParams</code>.
+ */
+ public void setMaxLocals(int max) {
+ gen.setMaxLocals(max);
+ }
+
+ /**
+ * Prepares to use cast $r, $w, $_, and $type.
+ * $type is made to represent the specified return type.
+ * It also enables to write a return statement with a return value
+ * for void method.
+ *
+ * <p>If the return type is void, ($r) does nothing.
+ * The type of $_ is java.lang.Object.
+ *
+ * @param type the return type.
+ * @param useResultVar true if $_ is used.
+ * @return -1 or the variable index assigned to $_.
+ * @see #recordType(CtClass)
+ */
+ public int recordReturnType(CtClass type, boolean useResultVar)
+ throws CompileError
+ {
+ gen.recordType(type);
+ return gen.recordReturnType(type, "$r",
+ (useResultVar ? resultVarName : null), stable);
+ }
+
+ /**
+ * Prepares to use $type. Note that recordReturnType() overwrites
+ * the value of $type.
+ *
+ * @param t the type represented by $type.
+ */
+ public void recordType(CtClass t) {
+ gen.recordType(t);
+ }
+
+ /**
+ * Makes the given variable available.
+ *
+ * @param type variable type
+ * @param name variable name
+ */
+ public int recordVariable(CtClass type, String name)
+ throws CompileError
+ {
+ return gen.recordVariable(type, name, stable);
+ }
+
+ /**
+ * Prepares to use $proceed().
+ * If the return type of $proceed() is void, null is pushed on the
+ * stack.
+ *
+ * @param target an expression specifying the target object.
+ * if null, "this" is the target.
+ * @param method the method name.
+ */
+ public void recordProceed(String target, String method)
+ throws CompileError
+ {
+ Parser p = new Parser(new Lex(target));
+ final ASTree texpr = p.parseExpression(stable);
+ final String m = method;
+
+ ProceedHandler h = new ProceedHandler() {
+ public void doit(JvstCodeGen gen, Bytecode b, ASTList args)
+ throws CompileError
+ {
+ ASTree expr = new Member(m);
+ if (texpr != null)
+ expr = Expr.make('.', texpr, expr);
+
+ expr = CallExpr.makeCall(expr, args);
+ gen.compileExpr(expr);
+ gen.addNullIfVoid();
+ }
+
+ public void setReturnType(JvstTypeChecker check, ASTList args)
+ throws CompileError
+ {
+ ASTree expr = new Member(m);
+ if (texpr != null)
+ expr = Expr.make('.', texpr, expr);
+
+ expr = CallExpr.makeCall(expr, args);
+ expr.accept(check);
+ check.addNullIfVoid();
+ }
+ };
+
+ gen.setProceedHandler(h, proceedName);
+ }
+
+ /**
+ * Prepares to use $proceed() representing a static method.
+ * If the return type of $proceed() is void, null is pushed on the
+ * stack.
+ *
+ * @param targetClass the fully-qualified dot-separated name
+ * of the class declaring the method.
+ * @param method the method name.
+ */
+ public void recordStaticProceed(String targetClass, String method)
+ throws CompileError
+ {
+ final String c = targetClass;
+ final String m = method;
+
+ ProceedHandler h = new ProceedHandler() {
+ public void doit(JvstCodeGen gen, Bytecode b, ASTList args)
+ throws CompileError
+ {
+ Expr expr = Expr.make(TokenId.MEMBER,
+ new Symbol(c), new Member(m));
+ expr = CallExpr.makeCall(expr, args);
+ gen.compileExpr(expr);
+ gen.addNullIfVoid();
+ }
+
+ public void setReturnType(JvstTypeChecker check, ASTList args)
+ throws CompileError
+ {
+ Expr expr = Expr.make(TokenId.MEMBER,
+ new Symbol(c), new Member(m));
+ expr = CallExpr.makeCall(expr, args);
+ expr.accept(check);
+ check.addNullIfVoid();
+ }
+ };
+
+ gen.setProceedHandler(h, proceedName);
+ }
+
+ /**
+ * Prepares to use $proceed() representing a private/super's method.
+ * If the return type of $proceed() is void, null is pushed on the
+ * stack. This method is for methods invoked by INVOKESPECIAL.
+ *
+ * @param target an expression specifying the target object.
+ * if null, "this" is the target.
+ * @param classname the class name declaring the method.
+ * @param methodname the method name.
+ * @param descriptor the method descriptor.
+ */
+ public void recordSpecialProceed(String target, String classname,
+ String methodname, String descriptor)
+ throws CompileError
+ {
+ Parser p = new Parser(new Lex(target));
+ final ASTree texpr = p.parseExpression(stable);
+ final String cname = classname;
+ final String method = methodname;
+ final String desc = descriptor;
+
+ ProceedHandler h = new ProceedHandler() {
+ public void doit(JvstCodeGen gen, Bytecode b, ASTList args)
+ throws CompileError
+ {
+ gen.compileInvokeSpecial(texpr, cname, method, desc, args);
+ }
+
+ public void setReturnType(JvstTypeChecker c, ASTList args)
+ throws CompileError
+ {
+ c.compileInvokeSpecial(texpr, cname, method, desc, args);
+ }
+
+ };
+
+ gen.setProceedHandler(h, proceedName);
+ }
+
+ /**
+ * Prepares to use $proceed().
+ */
+ public void recordProceed(ProceedHandler h) {
+ gen.setProceedHandler(h, proceedName);
+ }
+
+ /**
+ * Compiles a statement (or a block).
+ * <code>recordParams()</code> must be called before invoking
+ * this method.
+ *
+ * <p>Local variables that are not declared
+ * in the compiled source text might not be accessible within that
+ * source text. Fields and method parameters ($0, $1, ..) are available.
+ */
+ public void compileStmnt(String src) throws CompileError {
+ Parser p = new Parser(new Lex(src));
+ SymbolTable stb = new SymbolTable(stable);
+ while (p.hasMore()) {
+ Stmnt s = p.parseStatement(stb);
+ if (s != null)
+ s.accept(gen);
+ }
+ }
+
+ /**
+ * Compiles an exression. <code>recordParams()</code> must be
+ * called before invoking this method.
+ *
+ * <p>Local variables are not accessible
+ * within the compiled source text. Fields and method parameters
+ * ($0, $1, ..) are available if <code>recordParams()</code>
+ * have been invoked.
+ */
+ public void compileExpr(String src) throws CompileError {
+ ASTree e = parseExpr(src, stable);
+ compileExpr(e);
+ }
+
+ /**
+ * Parsers an expression.
+ */
+ public static ASTree parseExpr(String src, SymbolTable st)
+ throws CompileError
+ {
+ Parser p = new Parser(new Lex(src));
+ return p.parseExpression(st);
+ }
+
+ /**
+ * Compiles an exression. <code>recordParams()</code> must be
+ * called before invoking this method.
+ *
+ * <p>Local variables are not accessible
+ * within the compiled source text. Fields and method parameters
+ * ($0, $1, ..) are available if <code>recordParams()</code>
+ * have been invoked.
+ */
+ public void compileExpr(ASTree e) throws CompileError {
+ if (e != null)
+ gen.compileExpr(e);
+ }
+}
diff --git a/src/main/javassist/compiler/JvstCodeGen.java b/src/main/javassist/compiler/JvstCodeGen.java
new file mode 100644
index 0000000..91b0eca
--- /dev/null
+++ b/src/main/javassist/compiler/JvstCodeGen.java
@@ -0,0 +1,709 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.compiler;
+
+import javassist.*;
+import javassist.bytecode.*;
+import javassist.compiler.ast.*;
+
+/* Code generator accepting extended Java syntax for Javassist.
+ */
+
+public class JvstCodeGen extends MemberCodeGen {
+ String paramArrayName = null;
+ String paramListName = null;
+ CtClass[] paramTypeList = null;
+ private int paramVarBase = 0; // variable index for $0 or $1.
+ private boolean useParam0 = false; // true if $0 is used.
+ private String param0Type = null; // JVM name
+ public static final String sigName = "$sig";
+ public static final String dollarTypeName = "$type";
+ public static final String clazzName = "$class";
+ private CtClass dollarType = null;
+ CtClass returnType = null;
+ String returnCastName = null;
+ private String returnVarName = null; // null if $_ is not used.
+ public static final String wrapperCastName = "$w";
+ String proceedName = null;
+ public static final String cflowName = "$cflow";
+ ProceedHandler procHandler = null; // null if not used.
+
+ public JvstCodeGen(Bytecode b, CtClass cc, ClassPool cp) {
+ super(b, cc, cp);
+ setTypeChecker(new JvstTypeChecker(cc, cp, this));
+ }
+
+ /* Index of $1.
+ */
+ private int indexOfParam1() {
+ return paramVarBase + (useParam0 ? 1 : 0);
+ }
+
+ /* Records a ProceedHandler obejct.
+ *
+ * @param name the name of the special method call.
+ * it is usually $proceed.
+ */
+ public void setProceedHandler(ProceedHandler h, String name) {
+ proceedName = name;
+ procHandler = h;
+ }
+
+ /* If the type of the expression compiled last is void,
+ * add ACONST_NULL and change exprType, arrayDim, className.
+ */
+ public void addNullIfVoid() {
+ if (exprType == VOID) {
+ bytecode.addOpcode(ACONST_NULL);
+ exprType = CLASS;
+ arrayDim = 0;
+ className = jvmJavaLangObject;
+ }
+ }
+
+ /* To support $args, $sig, and $type.
+ * $args is an array of parameter list.
+ */
+ public void atMember(Member mem) throws CompileError {
+ String name = mem.get();
+ if (name.equals(paramArrayName)) {
+ compileParameterList(bytecode, paramTypeList, indexOfParam1());
+ exprType = CLASS;
+ arrayDim = 1;
+ className = jvmJavaLangObject;
+ }
+ else if (name.equals(sigName)) {
+ bytecode.addLdc(Descriptor.ofMethod(returnType, paramTypeList));
+ bytecode.addInvokestatic("javassist/runtime/Desc", "getParams",
+ "(Ljava/lang/String;)[Ljava/lang/Class;");
+ exprType = CLASS;
+ arrayDim = 1;
+ className = "java/lang/Class";
+ }
+ else if (name.equals(dollarTypeName)) {
+ if (dollarType == null)
+ throw new CompileError(dollarTypeName + " is not available");
+
+ bytecode.addLdc(Descriptor.of(dollarType));
+ callGetType("getType");
+ }
+ else if (name.equals(clazzName)) {
+ if (param0Type == null)
+ throw new CompileError(clazzName + " is not available");
+
+ bytecode.addLdc(param0Type);
+ callGetType("getClazz");
+ }
+ else
+ super.atMember(mem);
+ }
+
+ private void callGetType(String method) {
+ bytecode.addInvokestatic("javassist/runtime/Desc", method,
+ "(Ljava/lang/String;)Ljava/lang/Class;");
+ exprType = CLASS;
+ arrayDim = 0;
+ className = "java/lang/Class";
+ }
+
+ protected void atFieldAssign(Expr expr, int op, ASTree left,
+ ASTree right, boolean doDup) throws CompileError
+ {
+ if (left instanceof Member
+ && ((Member)left).get().equals(paramArrayName)) {
+ if (op != '=')
+ throw new CompileError("bad operator for " + paramArrayName);
+
+ right.accept(this);
+ if (arrayDim != 1 || exprType != CLASS)
+ throw new CompileError("invalid type for " + paramArrayName);
+
+ atAssignParamList(paramTypeList, bytecode);
+ if (!doDup)
+ bytecode.addOpcode(POP);
+ }
+ else
+ super.atFieldAssign(expr, op, left, right, doDup);
+ }
+
+ protected void atAssignParamList(CtClass[] params, Bytecode code)
+ throws CompileError
+ {
+ if (params == null)
+ return;
+
+ int varNo = indexOfParam1();
+ int n = params.length;
+ for (int i = 0; i < n; ++i) {
+ code.addOpcode(DUP);
+ code.addIconst(i);
+ code.addOpcode(AALOAD);
+ compileUnwrapValue(params[i], code);
+ code.addStore(varNo, params[i]);
+ varNo += is2word(exprType, arrayDim) ? 2 : 1;
+ }
+ }
+
+ public void atCastExpr(CastExpr expr) throws CompileError {
+ ASTList classname = expr.getClassName();
+ if (classname != null && expr.getArrayDim() == 0) {
+ ASTree p = classname.head();
+ if (p instanceof Symbol && classname.tail() == null) {
+ String typename = ((Symbol)p).get();
+ if (typename.equals(returnCastName)) {
+ atCastToRtype(expr);
+ return;
+ }
+ else if (typename.equals(wrapperCastName)) {
+ atCastToWrapper(expr);
+ return;
+ }
+ }
+ }
+
+ super.atCastExpr(expr);
+ }
+
+ /**
+ * Inserts a cast operator to the return type.
+ * If the return type is void, this does nothing.
+ */
+ protected void atCastToRtype(CastExpr expr) throws CompileError {
+ expr.getOprand().accept(this);
+ if (exprType == VOID || isRefType(exprType) || arrayDim > 0)
+ compileUnwrapValue(returnType, bytecode);
+ else if (returnType instanceof CtPrimitiveType) {
+ CtPrimitiveType pt = (CtPrimitiveType)returnType;
+ int destType = MemberResolver.descToType(pt.getDescriptor());
+ atNumCastExpr(exprType, destType);
+ exprType = destType;
+ arrayDim = 0;
+ className = null;
+ }
+ else
+ throw new CompileError("invalid cast");
+ }
+
+ protected void atCastToWrapper(CastExpr expr) throws CompileError {
+ expr.getOprand().accept(this);
+ if (isRefType(exprType) || arrayDim > 0)
+ return; // Object type. do nothing.
+
+ CtClass clazz = resolver.lookupClass(exprType, arrayDim, className);
+ if (clazz instanceof CtPrimitiveType) {
+ CtPrimitiveType pt = (CtPrimitiveType)clazz;
+ String wrapper = pt.getWrapperName();
+ bytecode.addNew(wrapper); // new <wrapper>
+ bytecode.addOpcode(DUP); // dup
+ if (pt.getDataSize() > 1)
+ bytecode.addOpcode(DUP2_X2); // dup2_x2
+ else
+ bytecode.addOpcode(DUP2_X1); // dup2_x1
+
+ bytecode.addOpcode(POP2); // pop2
+ bytecode.addInvokespecial(wrapper, "<init>",
+ "(" + pt.getDescriptor() + ")V");
+ // invokespecial
+ exprType = CLASS;
+ arrayDim = 0;
+ className = jvmJavaLangObject;
+ }
+ }
+
+ /* Delegates to a ProcHandler object if the method call is
+ * $proceed(). It may process $cflow().
+ */
+ public void atCallExpr(CallExpr expr) throws CompileError {
+ ASTree method = expr.oprand1();
+ if (method instanceof Member) {
+ String name = ((Member)method).get();
+ if (procHandler != null && name.equals(proceedName)) {
+ procHandler.doit(this, bytecode, (ASTList)expr.oprand2());
+ return;
+ }
+ else if (name.equals(cflowName)) {
+ atCflow((ASTList)expr.oprand2());
+ return;
+ }
+ }
+
+ super.atCallExpr(expr);
+ }
+
+ /* To support $cflow().
+ */
+ protected void atCflow(ASTList cname) throws CompileError {
+ StringBuffer sbuf = new StringBuffer();
+ if (cname == null || cname.tail() != null)
+ throw new CompileError("bad " + cflowName);
+
+ makeCflowName(sbuf, cname.head());
+ String name = sbuf.toString();
+ Object[] names = resolver.getClassPool().lookupCflow(name);
+ if (names == null)
+ throw new CompileError("no such " + cflowName + ": " + name);
+
+ bytecode.addGetstatic((String)names[0], (String)names[1],
+ "Ljavassist/runtime/Cflow;");
+ bytecode.addInvokevirtual("javassist.runtime.Cflow",
+ "value", "()I");
+ exprType = INT;
+ arrayDim = 0;
+ className = null;
+ }
+
+ /* Syntax:
+ *
+ * <cflow> : $cflow '(' <cflow name> ')'
+ * <cflow name> : <identifier> ('.' <identifier>)*
+ */
+ private static void makeCflowName(StringBuffer sbuf, ASTree name)
+ throws CompileError
+ {
+ if (name instanceof Symbol) {
+ sbuf.append(((Symbol)name).get());
+ return;
+ }
+ else if (name instanceof Expr) {
+ Expr expr = (Expr)name;
+ if (expr.getOperator() == '.') {
+ makeCflowName(sbuf, expr.oprand1());
+ sbuf.append('.');
+ makeCflowName(sbuf, expr.oprand2());
+ return;
+ }
+ }
+
+ throw new CompileError("bad " + cflowName);
+ }
+
+ /* To support $$. ($$) is equivalent to ($1, ..., $n).
+ * It can be used only as a parameter list of method call.
+ */
+ public boolean isParamListName(ASTList args) {
+ if (paramTypeList != null
+ && args != null && args.tail() == null) {
+ ASTree left = args.head();
+ return (left instanceof Member
+ && ((Member)left).get().equals(paramListName));
+ }
+ else
+ return false;
+ }
+
+ /*
+ public int getMethodArgsLength(ASTList args) {
+ if (!isParamListName(args))
+ return super.getMethodArgsLength(args);
+
+ return paramTypeList.length;
+ }
+ */
+
+ public int getMethodArgsLength(ASTList args) {
+ String pname = paramListName;
+ int n = 0;
+ while (args != null) {
+ ASTree a = args.head();
+ if (a instanceof Member && ((Member)a).get().equals(pname)) {
+ if (paramTypeList != null)
+ n += paramTypeList.length;
+ }
+ else
+ ++n;
+
+ args = args.tail();
+ }
+
+ return n;
+ }
+
+ public void atMethodArgs(ASTList args, int[] types, int[] dims,
+ String[] cnames) throws CompileError {
+ CtClass[] params = paramTypeList;
+ String pname = paramListName;
+ int i = 0;
+ while (args != null) {
+ ASTree a = args.head();
+ if (a instanceof Member && ((Member)a).get().equals(pname)) {
+ if (params != null) {
+ int n = params.length;
+ int regno = indexOfParam1();
+ for (int k = 0; k < n; ++k) {
+ CtClass p = params[k];
+ regno += bytecode.addLoad(regno, p);
+ setType(p);
+ types[i] = exprType;
+ dims[i] = arrayDim;
+ cnames[i] = className;
+ ++i;
+ }
+ }
+ }
+ else {
+ a.accept(this);
+ types[i] = exprType;
+ dims[i] = arrayDim;
+ cnames[i] = className;
+ ++i;
+ }
+
+ args = args.tail();
+ }
+ }
+
+ /*
+ public void atMethodArgs(ASTList args, int[] types, int[] dims,
+ String[] cnames) throws CompileError {
+ if (!isParamListName(args)) {
+ super.atMethodArgs(args, types, dims, cnames);
+ return;
+ }
+
+ CtClass[] params = paramTypeList;
+ if (params == null)
+ return;
+
+ int n = params.length;
+ int regno = indexOfParam1();
+ for (int i = 0; i < n; ++i) {
+ CtClass p = params[i];
+ regno += bytecode.addLoad(regno, p);
+ setType(p);
+ types[i] = exprType;
+ dims[i] = arrayDim;
+ cnames[i] = className;
+ }
+ }
+ */
+
+ /* called by Javac#recordSpecialProceed().
+ */
+ void compileInvokeSpecial(ASTree target, String classname,
+ String methodname, String descriptor,
+ ASTList args)
+ throws CompileError
+ {
+ target.accept(this);
+ int nargs = getMethodArgsLength(args);
+ atMethodArgs(args, new int[nargs], new int[nargs],
+ new String[nargs]);
+ bytecode.addInvokespecial(classname, methodname, descriptor);
+ setReturnType(descriptor, false, false);
+ addNullIfVoid();
+ }
+
+ /*
+ * Makes it valid to write "return <expr>;" for a void method.
+ */
+ protected void atReturnStmnt(Stmnt st) throws CompileError {
+ ASTree result = st.getLeft();
+ if (result != null && returnType == CtClass.voidType) {
+ compileExpr(result);
+ if (is2word(exprType, arrayDim))
+ bytecode.addOpcode(POP2);
+ else if (exprType != VOID)
+ bytecode.addOpcode(POP);
+
+ result = null;
+ }
+
+ atReturnStmnt2(result);
+ }
+
+ /**
+ * Makes a cast to the return type ($r) available.
+ * It also enables $_.
+ *
+ * <p>If the return type is void, ($r) does nothing.
+ * The type of $_ is java.lang.Object.
+ *
+ * @param resultName null if $_ is not used.
+ * @return -1 or the variable index assigned to $_.
+ */
+ public int recordReturnType(CtClass type, String castName,
+ String resultName, SymbolTable tbl) throws CompileError
+ {
+ returnType = type;
+ returnCastName = castName;
+ returnVarName = resultName;
+ if (resultName == null)
+ return -1;
+ else {
+ int varNo = getMaxLocals();
+ int locals = varNo + recordVar(type, resultName, varNo, tbl);
+ setMaxLocals(locals);
+ return varNo;
+ }
+ }
+
+ /**
+ * Makes $type available.
+ */
+ public void recordType(CtClass t) {
+ dollarType = t;
+ }
+
+ /**
+ * Makes method parameters $0, $1, ..., $args, $$, and $class available.
+ * $0 is equivalent to THIS if the method is not static. Otherwise,
+ * if the method is static, then $0 is not available.
+ */
+ public int recordParams(CtClass[] params, boolean isStatic,
+ String prefix, String paramVarName,
+ String paramsName, SymbolTable tbl)
+ throws CompileError
+ {
+ return recordParams(params, isStatic, prefix, paramVarName,
+ paramsName, !isStatic, 0, getThisName(), tbl);
+ }
+
+ /**
+ * Makes method parameters $0, $1, ..., $args, $$, and $class available.
+ * $0 is available only if use0 is true. It might not be equivalent
+ * to THIS.
+ *
+ * @param params the parameter types (the types of $1, $2, ..)
+ * @param prefix it must be "$" (the first letter of $0, $1, ...)
+ * @param paramVarName it must be "$args"
+ * @param paramsName it must be "$$"
+ * @param use0 true if $0 is used.
+ * @param paramBase the register number of $0 (use0 is true)
+ * or $1 (otherwise).
+ * @param target the class of $0. If use0 is false, target
+ * can be null. The value of "target" is also used
+ * as the name of the type represented by $class.
+ * @param isStatic true if the method in which the compiled bytecode
+ * is embedded is static.
+ */
+ public int recordParams(CtClass[] params, boolean isStatic,
+ String prefix, String paramVarName,
+ String paramsName, boolean use0,
+ int paramBase, String target,
+ SymbolTable tbl)
+ throws CompileError
+ {
+ int varNo;
+
+ paramTypeList = params;
+ paramArrayName = paramVarName;
+ paramListName = paramsName;
+ paramVarBase = paramBase;
+ useParam0 = use0;
+
+ if (target != null)
+ param0Type = MemberResolver.jvmToJavaName(target);
+
+ inStaticMethod = isStatic;
+ varNo = paramBase;
+ if (use0) {
+ String varName = prefix + "0";
+ Declarator decl
+ = new Declarator(CLASS, MemberResolver.javaToJvmName(target),
+ 0, varNo++, new Symbol(varName));
+ tbl.append(varName, decl);
+ }
+
+ for (int i = 0; i < params.length; ++i)
+ varNo += recordVar(params[i], prefix + (i + 1), varNo, tbl);
+
+ if (getMaxLocals() < varNo)
+ setMaxLocals(varNo);
+
+ return varNo;
+ }
+
+ /**
+ * Makes the given variable name available.
+ *
+ * @param type variable type
+ * @param varName variable name
+ */
+ public int recordVariable(CtClass type, String varName, SymbolTable tbl)
+ throws CompileError
+ {
+ if (varName == null)
+ return -1;
+ else {
+ int varNo = getMaxLocals();
+ int locals = varNo + recordVar(type, varName, varNo, tbl);
+ setMaxLocals(locals);
+ return varNo;
+ }
+ }
+
+ private int recordVar(CtClass cc, String varName, int varNo,
+ SymbolTable tbl) throws CompileError
+ {
+ if (cc == CtClass.voidType) {
+ exprType = CLASS;
+ arrayDim = 0;
+ className = jvmJavaLangObject;
+ }
+ else
+ setType(cc);
+
+ Declarator decl
+ = new Declarator(exprType, className, arrayDim,
+ varNo, new Symbol(varName));
+ tbl.append(varName, decl);
+ return is2word(exprType, arrayDim) ? 2 : 1;
+ }
+
+ /**
+ * Makes the given variable name available.
+ *
+ * @param typeDesc the type descriptor of the variable
+ * @param varName variable name
+ * @param varNo an index into the local variable array
+ */
+ public void recordVariable(String typeDesc, String varName, int varNo,
+ SymbolTable tbl) throws CompileError
+ {
+ char c;
+ int dim = 0;
+ while ((c = typeDesc.charAt(dim)) == '[')
+ ++dim;
+
+ int type = MemberResolver.descToType(c);
+ String cname = null;
+ if (type == CLASS) {
+ if (dim == 0)
+ cname = typeDesc.substring(1, typeDesc.length() - 1);
+ else
+ cname = typeDesc.substring(dim + 1, typeDesc.length() - 1);
+ }
+
+ Declarator decl
+ = new Declarator(type, cname, dim, varNo, new Symbol(varName));
+ tbl.append(varName, decl);
+ }
+
+ /* compileParameterList() returns the stack size used
+ * by the produced code.
+ *
+ * This method correctly computes the max_stack value.
+ *
+ * @param regno the index of the local variable in which
+ * the first argument is received.
+ * (0: static method, 1: regular method.)
+ */
+ public static int compileParameterList(Bytecode code,
+ CtClass[] params, int regno) {
+ if (params == null) {
+ code.addIconst(0); // iconst_0
+ code.addAnewarray(javaLangObject); // anewarray Object
+ return 1;
+ }
+ else {
+ CtClass[] args = new CtClass[1];
+ int n = params.length;
+ code.addIconst(n); // iconst_<n>
+ code.addAnewarray(javaLangObject); // anewarray Object
+ for (int i = 0; i < n; ++i) {
+ code.addOpcode(Bytecode.DUP); // dup
+ code.addIconst(i); // iconst_<i>
+ if (params[i].isPrimitive()) {
+ CtPrimitiveType pt = (CtPrimitiveType)params[i];
+ String wrapper = pt.getWrapperName();
+ code.addNew(wrapper); // new <wrapper>
+ code.addOpcode(Bytecode.DUP); // dup
+ int s = code.addLoad(regno, pt); // ?load <regno>
+ regno += s;
+ args[0] = pt;
+ code.addInvokespecial(wrapper, "<init>",
+ Descriptor.ofMethod(CtClass.voidType, args));
+ // invokespecial
+ }
+ else {
+ code.addAload(regno); // aload <regno>
+ ++regno;
+ }
+
+ code.addOpcode(Bytecode.AASTORE); // aastore
+ }
+
+ return 8;
+ }
+ }
+
+ protected void compileUnwrapValue(CtClass type, Bytecode code)
+ throws CompileError
+ {
+ if (type == CtClass.voidType) {
+ addNullIfVoid();
+ return;
+ }
+
+ if (exprType == VOID)
+ throw new CompileError("invalid type for " + returnCastName);
+
+ if (type instanceof CtPrimitiveType) {
+ CtPrimitiveType pt = (CtPrimitiveType)type;
+ // pt is not voidType.
+ String wrapper = pt.getWrapperName();
+ code.addCheckcast(wrapper);
+ code.addInvokevirtual(wrapper, pt.getGetMethodName(),
+ pt.getGetMethodDescriptor());
+ setType(type);
+ }
+ else {
+ code.addCheckcast(type);
+ setType(type);
+ }
+ }
+
+ /* Sets exprType, arrayDim, and className;
+ * If type is void, then this method does nothing.
+ */
+ public void setType(CtClass type) throws CompileError {
+ setType(type, 0);
+ }
+
+ private void setType(CtClass type, int dim) throws CompileError {
+ if (type.isPrimitive()) {
+ CtPrimitiveType pt = (CtPrimitiveType)type;
+ exprType = MemberResolver.descToType(pt.getDescriptor());
+ arrayDim = dim;
+ className = null;
+ }
+ else if (type.isArray())
+ try {
+ setType(type.getComponentType(), dim + 1);
+ }
+ catch (NotFoundException e) {
+ throw new CompileError("undefined type: " + type.getName());
+ }
+ else {
+ exprType = CLASS;
+ arrayDim = dim;
+ className = MemberResolver.javaToJvmName(type.getName());
+ }
+ }
+
+ /* Performs implicit coercion from exprType to type.
+ */
+ public void doNumCast(CtClass type) throws CompileError {
+ if (arrayDim == 0 && !isRefType(exprType))
+ if (type instanceof CtPrimitiveType) {
+ CtPrimitiveType pt = (CtPrimitiveType)type;
+ atNumCastExpr(exprType,
+ MemberResolver.descToType(pt.getDescriptor()));
+ }
+ else
+ throw new CompileError("type mismatch");
+ }
+}
diff --git a/src/main/javassist/compiler/JvstTypeChecker.java b/src/main/javassist/compiler/JvstTypeChecker.java
new file mode 100644
index 0000000..d88909e
--- /dev/null
+++ b/src/main/javassist/compiler/JvstTypeChecker.java
@@ -0,0 +1,281 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.compiler;
+
+import javassist.*;
+import javassist.compiler.ast.*;
+
+/* Type checker accepting extended Java syntax for Javassist.
+ */
+
+public class JvstTypeChecker extends TypeChecker {
+ private JvstCodeGen codeGen;
+
+ public JvstTypeChecker(CtClass cc, ClassPool cp, JvstCodeGen gen) {
+ super(cc, cp);
+ codeGen = gen;
+ }
+
+ /* If the type of the expression compiled last is void,
+ * add ACONST_NULL and change exprType, arrayDim, className.
+ */
+ public void addNullIfVoid() {
+ if (exprType == VOID) {
+ exprType = CLASS;
+ arrayDim = 0;
+ className = jvmJavaLangObject;
+ }
+ }
+
+ /* To support $args, $sig, and $type.
+ * $args is an array of parameter list.
+ */
+ public void atMember(Member mem) throws CompileError {
+ String name = mem.get();
+ if (name.equals(codeGen.paramArrayName)) {
+ exprType = CLASS;
+ arrayDim = 1;
+ className = jvmJavaLangObject;
+ }
+ else if (name.equals(JvstCodeGen.sigName)) {
+ exprType = CLASS;
+ arrayDim = 1;
+ className = "java/lang/Class";
+ }
+ else if (name.equals(JvstCodeGen.dollarTypeName)
+ || name.equals(JvstCodeGen.clazzName)) {
+ exprType = CLASS;
+ arrayDim = 0;
+ className = "java/lang/Class";
+ }
+ else
+ super.atMember(mem);
+ }
+
+ protected void atFieldAssign(Expr expr, int op, ASTree left, ASTree right)
+ throws CompileError
+ {
+ if (left instanceof Member
+ && ((Member)left).get().equals(codeGen.paramArrayName)) {
+ right.accept(this);
+ CtClass[] params = codeGen.paramTypeList;
+ if (params == null)
+ return;
+
+ int n = params.length;
+ for (int i = 0; i < n; ++i)
+ compileUnwrapValue(params[i]);
+ }
+ else
+ super.atFieldAssign(expr, op, left, right);
+ }
+
+ public void atCastExpr(CastExpr expr) throws CompileError {
+ ASTList classname = expr.getClassName();
+ if (classname != null && expr.getArrayDim() == 0) {
+ ASTree p = classname.head();
+ if (p instanceof Symbol && classname.tail() == null) {
+ String typename = ((Symbol)p).get();
+ if (typename.equals(codeGen.returnCastName)) {
+ atCastToRtype(expr);
+ return;
+ }
+ else if (typename.equals(JvstCodeGen.wrapperCastName)) {
+ atCastToWrapper(expr);
+ return;
+ }
+ }
+ }
+
+ super.atCastExpr(expr);
+ }
+
+ /**
+ * Inserts a cast operator to the return type.
+ * If the return type is void, this does nothing.
+ */
+ protected void atCastToRtype(CastExpr expr) throws CompileError {
+ CtClass returnType = codeGen.returnType;
+ expr.getOprand().accept(this);
+ if (exprType == VOID || CodeGen.isRefType(exprType) || arrayDim > 0)
+ compileUnwrapValue(returnType);
+ else if (returnType instanceof CtPrimitiveType) {
+ CtPrimitiveType pt = (CtPrimitiveType)returnType;
+ int destType = MemberResolver.descToType(pt.getDescriptor());
+ exprType = destType;
+ arrayDim = 0;
+ className = null;
+ }
+ }
+
+ protected void atCastToWrapper(CastExpr expr) throws CompileError {
+ expr.getOprand().accept(this);
+ if (CodeGen.isRefType(exprType) || arrayDim > 0)
+ return; // Object type. do nothing.
+
+ CtClass clazz = resolver.lookupClass(exprType, arrayDim, className);
+ if (clazz instanceof CtPrimitiveType) {
+ exprType = CLASS;
+ arrayDim = 0;
+ className = jvmJavaLangObject;
+ }
+ }
+
+ /* Delegates to a ProcHandler object if the method call is
+ * $proceed(). It may process $cflow().
+ */
+ public void atCallExpr(CallExpr expr) throws CompileError {
+ ASTree method = expr.oprand1();
+ if (method instanceof Member) {
+ String name = ((Member)method).get();
+ if (codeGen.procHandler != null
+ && name.equals(codeGen.proceedName)) {
+ codeGen.procHandler.setReturnType(this,
+ (ASTList)expr.oprand2());
+ return;
+ }
+ else if (name.equals(JvstCodeGen.cflowName)) {
+ atCflow((ASTList)expr.oprand2());
+ return;
+ }
+ }
+
+ super.atCallExpr(expr);
+ }
+
+ /* To support $cflow().
+ */
+ protected void atCflow(ASTList cname) throws CompileError {
+ exprType = INT;
+ arrayDim = 0;
+ className = null;
+ }
+
+ /* To support $$. ($$) is equivalent to ($1, ..., $n).
+ * It can be used only as a parameter list of method call.
+ */
+ public boolean isParamListName(ASTList args) {
+ if (codeGen.paramTypeList != null
+ && args != null && args.tail() == null) {
+ ASTree left = args.head();
+ return (left instanceof Member
+ && ((Member)left).get().equals(codeGen.paramListName));
+ }
+ else
+ return false;
+ }
+
+ public int getMethodArgsLength(ASTList args) {
+ String pname = codeGen.paramListName;
+ int n = 0;
+ while (args != null) {
+ ASTree a = args.head();
+ if (a instanceof Member && ((Member)a).get().equals(pname)) {
+ if (codeGen.paramTypeList != null)
+ n += codeGen.paramTypeList.length;
+ }
+ else
+ ++n;
+
+ args = args.tail();
+ }
+
+ return n;
+ }
+
+ public void atMethodArgs(ASTList args, int[] types, int[] dims,
+ String[] cnames) throws CompileError {
+ CtClass[] params = codeGen.paramTypeList;
+ String pname = codeGen.paramListName;
+ int i = 0;
+ while (args != null) {
+ ASTree a = args.head();
+ if (a instanceof Member && ((Member)a).get().equals(pname)) {
+ if (params != null) {
+ int n = params.length;
+ for (int k = 0; k < n; ++k) {
+ CtClass p = params[k];
+ setType(p);
+ types[i] = exprType;
+ dims[i] = arrayDim;
+ cnames[i] = className;
+ ++i;
+ }
+ }
+ }
+ else {
+ a.accept(this);
+ types[i] = exprType;
+ dims[i] = arrayDim;
+ cnames[i] = className;
+ ++i;
+ }
+
+ args = args.tail();
+ }
+ }
+
+ /* called by Javac#recordSpecialProceed().
+ */
+ void compileInvokeSpecial(ASTree target, String classname,
+ String methodname, String descriptor,
+ ASTList args)
+ throws CompileError
+ {
+ target.accept(this);
+ int nargs = getMethodArgsLength(args);
+ atMethodArgs(args, new int[nargs], new int[nargs],
+ new String[nargs]);
+ setReturnType(descriptor);
+ addNullIfVoid();
+ }
+
+ protected void compileUnwrapValue(CtClass type) throws CompileError
+ {
+ if (type == CtClass.voidType)
+ addNullIfVoid();
+ else
+ setType(type);
+ }
+
+ /* Sets exprType, arrayDim, and className;
+ * If type is void, then this method does nothing.
+ */
+ public void setType(CtClass type) throws CompileError {
+ setType(type, 0);
+ }
+
+ private void setType(CtClass type, int dim) throws CompileError {
+ if (type.isPrimitive()) {
+ CtPrimitiveType pt = (CtPrimitiveType)type;
+ exprType = MemberResolver.descToType(pt.getDescriptor());
+ arrayDim = dim;
+ className = null;
+ }
+ else if (type.isArray())
+ try {
+ setType(type.getComponentType(), dim + 1);
+ }
+ catch (NotFoundException e) {
+ throw new CompileError("undefined type: " + type.getName());
+ }
+ else {
+ exprType = CLASS;
+ arrayDim = dim;
+ className = MemberResolver.javaToJvmName(type.getName());
+ }
+ }
+}
diff --git a/src/main/javassist/compiler/KeywordTable.java b/src/main/javassist/compiler/KeywordTable.java
new file mode 100644
index 0000000..3a22a79
--- /dev/null
+++ b/src/main/javassist/compiler/KeywordTable.java
@@ -0,0 +1,32 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.compiler;
+
+public final class KeywordTable extends java.util.HashMap {
+ public KeywordTable() { super(); }
+
+ public int lookup(String name) {
+ Object found = get(name);
+ if (found == null)
+ return -1;
+ else
+ return ((Integer)found).intValue();
+ }
+
+ public void append(String name, int t) {
+ put(name, new Integer(t));
+ }
+}
diff --git a/src/main/javassist/compiler/Lex.java b/src/main/javassist/compiler/Lex.java
new file mode 100644
index 0000000..5233a25
--- /dev/null
+++ b/src/main/javassist/compiler/Lex.java
@@ -0,0 +1,550 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.compiler;
+
+class Token {
+ public Token next = null;
+ public int tokenId;
+
+ public long longValue;
+ public double doubleValue;
+ public String textValue;
+}
+
+public class Lex implements TokenId {
+ private int lastChar;
+ private StringBuffer textBuffer;
+ private Token currentToken;
+ private Token lookAheadTokens;
+
+ private String input;
+ private int position, maxlen, lineNumber;
+
+ /**
+ * Constructs a lexical analyzer.
+ */
+ public Lex(String s) {
+ lastChar = -1;
+ textBuffer = new StringBuffer();
+ currentToken = new Token();
+ lookAheadTokens = null;
+
+ input = s;
+ position = 0;
+ maxlen = s.length();
+ lineNumber = 0;
+ }
+
+ public int get() {
+ if (lookAheadTokens == null)
+ return get(currentToken);
+ else {
+ Token t;
+ currentToken = t = lookAheadTokens;
+ lookAheadTokens = lookAheadTokens.next;
+ return t.tokenId;
+ }
+ }
+
+ /**
+ * Looks at the next token.
+ */
+ public int lookAhead() {
+ return lookAhead(0);
+ }
+
+ public int lookAhead(int i) {
+ Token tk = lookAheadTokens;
+ if (tk == null) {
+ lookAheadTokens = tk = currentToken; // reuse an object!
+ tk.next = null;
+ get(tk);
+ }
+
+ for (; i-- > 0; tk = tk.next)
+ if (tk.next == null) {
+ Token tk2;
+ tk.next = tk2 = new Token();
+ get(tk2);
+ }
+
+ currentToken = tk;
+ return tk.tokenId;
+ }
+
+ public String getString() {
+ return currentToken.textValue;
+ }
+
+ public long getLong() {
+ return currentToken.longValue;
+ }
+
+ public double getDouble() {
+ return currentToken.doubleValue;
+ }
+
+ private int get(Token token) {
+ int t;
+ do {
+ t = readLine(token);
+ } while (t == '\n');
+ token.tokenId = t;
+ return t;
+ }
+
+ private int readLine(Token token) {
+ int c = getNextNonWhiteChar();
+ if(c < 0)
+ return c;
+ else if(c == '\n') {
+ ++lineNumber;
+ return '\n';
+ }
+ else if (c == '\'')
+ return readCharConst(token);
+ else if (c == '"')
+ return readStringL(token);
+ else if ('0' <= c && c <= '9')
+ return readNumber(c, token);
+ else if(c == '.'){
+ c = getc();
+ if ('0' <= c && c <= '9') {
+ StringBuffer tbuf = textBuffer;
+ tbuf.setLength(0);
+ tbuf.append('.');
+ return readDouble(tbuf, c, token);
+ }
+ else{
+ ungetc(c);
+ return readSeparator('.');
+ }
+ }
+ else if (Character.isJavaIdentifierStart((char)c))
+ return readIdentifier(c, token);
+ else
+ return readSeparator(c);
+ }
+
+ private int getNextNonWhiteChar() {
+ int c;
+ do {
+ c = getc();
+ if (c == '/') {
+ c = getc();
+ if (c == '/')
+ do {
+ c = getc();
+ } while (c != '\n' && c != '\r' && c != -1);
+ else if (c == '*')
+ while (true) {
+ c = getc();
+ if (c == -1)
+ break;
+ else if (c == '*')
+ if ((c = getc()) == '/') {
+ c = ' ';
+ break;
+ }
+ else
+ ungetc(c);
+ }
+ else {
+ ungetc(c);
+ c = '/';
+ }
+ }
+ } while(isBlank(c));
+ return c;
+ }
+
+ private int readCharConst(Token token) {
+ int c;
+ int value = 0;
+ while ((c = getc()) != '\'')
+ if (c == '\\')
+ value = readEscapeChar();
+ else if (c < 0x20) {
+ if (c == '\n')
+ ++lineNumber;
+
+ return BadToken;
+ }
+ else
+ value = c;
+
+ token.longValue = value;
+ return CharConstant;
+ }
+
+ private int readEscapeChar() {
+ int c = getc();
+ if (c == 'n')
+ c = '\n';
+ else if (c == 't')
+ c = '\t';
+ else if (c == 'r')
+ c = '\r';
+ else if (c == 'f')
+ c = '\f';
+ else if (c == '\n')
+ ++lineNumber;
+
+ return c;
+ }
+
+ private int readStringL(Token token) {
+ int c;
+ StringBuffer tbuf = textBuffer;
+ tbuf.setLength(0);
+ for (;;) {
+ while ((c = getc()) != '"') {
+ if (c == '\\')
+ c = readEscapeChar();
+ else if (c == '\n' || c < 0) {
+ ++lineNumber;
+ return BadToken;
+ }
+
+ tbuf.append((char)c);
+ }
+
+ for (;;) {
+ c = getc();
+ if (c == '\n')
+ ++lineNumber;
+ else if (!isBlank(c))
+ break;
+ }
+
+ if (c != '"') {
+ ungetc(c);
+ break;
+ }
+ }
+
+ token.textValue = tbuf.toString();
+ return StringL;
+ }
+
+ private int readNumber(int c, Token token) {
+ long value = 0;
+ int c2 = getc();
+ if (c == '0')
+ if (c2 == 'X' || c2 == 'x')
+ for (;;) {
+ c = getc();
+ if ('0' <= c && c <= '9')
+ value = value * 16 + (long)(c - '0');
+ else if ('A' <= c && c <= 'F')
+ value = value * 16 + (long)(c - 'A' + 10);
+ else if ('a' <= c && c <= 'f')
+ value = value * 16 + (long)(c - 'a' + 10);
+ else {
+ token.longValue = value;
+ if (c == 'L' || c == 'l')
+ return LongConstant;
+ else {
+ ungetc(c);
+ return IntConstant;
+ }
+ }
+ }
+ else if ('0' <= c2 && c2 <= '7') {
+ value = c2 - '0';
+ for (;;) {
+ c = getc();
+ if ('0' <= c && c <= '7')
+ value = value * 8 + (long)(c - '0');
+ else {
+ token.longValue = value;
+ if (c == 'L' || c == 'l')
+ return LongConstant;
+ else {
+ ungetc(c);
+ return IntConstant;
+ }
+ }
+ }
+ }
+
+ value = c - '0';
+ while ('0' <= c2 && c2 <= '9') {
+ value = value * 10 + c2 - '0';
+ c2 = getc();
+ }
+
+ token.longValue = value;
+ if (c2 == 'F' || c2 == 'f') {
+ token.doubleValue = (double)value;
+ return FloatConstant;
+ }
+ else if (c2 == 'E' || c2 == 'e'
+ || c2 == 'D' || c2 == 'd' || c2 == '.') {
+ StringBuffer tbuf = textBuffer;
+ tbuf.setLength(0);
+ tbuf.append(value);
+ return readDouble(tbuf, c2, token);
+ }
+ else if (c2 == 'L' || c2 == 'l')
+ return LongConstant;
+ else {
+ ungetc(c2);
+ return IntConstant;
+ }
+ }
+
+ private int readDouble(StringBuffer sbuf, int c, Token token) {
+ if (c != 'E' && c != 'e' && c != 'D' && c != 'd') {
+ sbuf.append((char)c);
+ for (;;) {
+ c = getc();
+ if ('0' <= c && c <= '9')
+ sbuf.append((char)c);
+ else
+ break;
+ }
+ }
+
+ if (c == 'E' || c == 'e') {
+ sbuf.append((char)c);
+ c = getc();
+ if (c == '+' || c == '-') {
+ sbuf.append((char)c);
+ c = getc();
+ }
+
+ while ('0' <= c && c <= '9') {
+ sbuf.append((char)c);
+ c = getc();
+ }
+ }
+
+ try {
+ token.doubleValue = Double.parseDouble(sbuf.toString());
+ }
+ catch (NumberFormatException e) {
+ return BadToken;
+ }
+
+ if (c == 'F' || c == 'f')
+ return FloatConstant;
+ else {
+ if (c != 'D' && c != 'd')
+ ungetc(c);
+
+ return DoubleConstant;
+ }
+ }
+
+ // !"#$%&'( )*+,-./0 12345678 9:;<=>?
+ private static final int[] equalOps
+ = { NEQ, 0, 0, 0, MOD_E, AND_E, 0, 0,
+ 0, MUL_E, PLUS_E, 0, MINUS_E, 0, DIV_E, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, LE, EQ, GE, 0 };
+
+ private int readSeparator(int c) {
+ int c2, c3;
+ if ('!' <= c && c <= '?') {
+ int t = equalOps[c - '!'];
+ if (t == 0)
+ return c;
+ else {
+ c2 = getc();
+ if (c == c2)
+ switch (c) {
+ case '=' :
+ return EQ;
+ case '+' :
+ return PLUSPLUS;
+ case '-' :
+ return MINUSMINUS;
+ case '&' :
+ return ANDAND;
+ case '<' :
+ c3 = getc();
+ if (c3 == '=')
+ return LSHIFT_E;
+ else {
+ ungetc(c3);
+ return LSHIFT;
+ }
+ case '>' :
+ c3 = getc();
+ if (c3 == '=')
+ return RSHIFT_E;
+ else if (c3 == '>') {
+ c3 = getc();
+ if (c3 == '=')
+ return ARSHIFT_E;
+ else {
+ ungetc(c3);
+ return ARSHIFT;
+ }
+ }
+ else {
+ ungetc(c3);
+ return RSHIFT;
+ }
+ default :
+ break;
+ }
+ else if (c2 == '=')
+ return t;
+ }
+ }
+ else if (c == '^') {
+ c2 = getc();
+ if (c2 == '=')
+ return EXOR_E;
+ }
+ else if (c == '|') {
+ c2 = getc();
+ if (c2 == '=')
+ return OR_E;
+ else if (c2 == '|')
+ return OROR;
+ }
+ else
+ return c;
+
+ ungetc(c2);
+ return c;
+ }
+
+ private int readIdentifier(int c, Token token) {
+ StringBuffer tbuf = textBuffer;
+ tbuf.setLength(0);
+
+ do {
+ tbuf.append((char)c);
+ c = getc();
+ } while (Character.isJavaIdentifierPart((char)c));
+
+ ungetc(c);
+
+ String name = tbuf.toString();
+ int t = ktable.lookup(name);
+ if (t >= 0)
+ return t;
+ else {
+ /* tbuf.toString() is executed quickly since it does not
+ * need memory copy. Using a hand-written extensible
+ * byte-array class instead of StringBuffer is not a good idea
+ * for execution speed. Converting a byte array to a String
+ * object is very slow. Using an extensible char array
+ * might be OK.
+ */
+ token.textValue = name;
+ return Identifier;
+ }
+ }
+
+ private static final KeywordTable ktable = new KeywordTable();
+
+ static {
+ ktable.append("abstract", ABSTRACT);
+ ktable.append("boolean", BOOLEAN);
+ ktable.append("break", BREAK);
+ ktable.append("byte", BYTE);
+ ktable.append("case", CASE);
+ ktable.append("catch", CATCH);
+ ktable.append("char", CHAR);
+ ktable.append("class", CLASS);
+ ktable.append("const", CONST);
+ ktable.append("continue", CONTINUE);
+ ktable.append("default", DEFAULT);
+ ktable.append("do", DO);
+ ktable.append("double", DOUBLE);
+ ktable.append("else", ELSE);
+ ktable.append("extends", EXTENDS);
+ ktable.append("false", FALSE);
+ ktable.append("final", FINAL);
+ ktable.append("finally", FINALLY);
+ ktable.append("float", FLOAT);
+ ktable.append("for", FOR);
+ ktable.append("goto", GOTO);
+ ktable.append("if", IF);
+ ktable.append("implements", IMPLEMENTS);
+ ktable.append("import", IMPORT);
+ ktable.append("instanceof", INSTANCEOF);
+ ktable.append("int", INT);
+ ktable.append("interface", INTERFACE);
+ ktable.append("long", LONG);
+ ktable.append("native", NATIVE);
+ ktable.append("new", NEW);
+ ktable.append("null", NULL);
+ ktable.append("package", PACKAGE);
+ ktable.append("private", PRIVATE);
+ ktable.append("protected", PROTECTED);
+ ktable.append("public", PUBLIC);
+ ktable.append("return", RETURN);
+ ktable.append("short", SHORT);
+ ktable.append("static", STATIC);
+ ktable.append("strictfp", STRICT);
+ ktable.append("super", SUPER);
+ ktable.append("switch", SWITCH);
+ ktable.append("synchronized", SYNCHRONIZED);
+ ktable.append("this", THIS);
+ ktable.append("throw", THROW);
+ ktable.append("throws", THROWS);
+ ktable.append("transient", TRANSIENT);
+ ktable.append("true", TRUE);
+ ktable.append("try", TRY);
+ ktable.append("void", VOID);
+ ktable.append("volatile", VOLATILE);
+ ktable.append("while", WHILE);
+ }
+
+ private static boolean isBlank(int c) {
+ return c == ' ' || c == '\t' || c == '\f' || c == '\r'
+ || c == '\n';
+ }
+
+ private static boolean isDigit(int c) {
+ return '0' <= c && c <= '9';
+ }
+
+ private void ungetc(int c) {
+ lastChar = c;
+ }
+
+ public String getTextAround() {
+ int begin = position - 10;
+ if (begin < 0)
+ begin = 0;
+
+ int end = position + 10;
+ if (end > maxlen)
+ end = maxlen;
+
+ return input.substring(begin, end);
+ }
+
+ private int getc() {
+ if (lastChar < 0)
+ if (position < maxlen)
+ return input.charAt(position++);
+ else
+ return -1;
+ else {
+ int c = lastChar;
+ lastChar = -1;
+ return c;
+ }
+ }
+}
diff --git a/src/main/javassist/compiler/MemberCodeGen.java b/src/main/javassist/compiler/MemberCodeGen.java
new file mode 100644
index 0000000..cbff0a4
--- /dev/null
+++ b/src/main/javassist/compiler/MemberCodeGen.java
@@ -0,0 +1,1148 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.compiler;
+
+import javassist.*;
+import javassist.bytecode.*;
+import javassist.compiler.ast.*;
+
+import java.util.ArrayList;
+
+/* Code generator methods depending on javassist.* classes.
+ */
+public class MemberCodeGen extends CodeGen {
+ protected MemberResolver resolver;
+ protected CtClass thisClass;
+ protected MethodInfo thisMethod;
+
+ protected boolean resultStatic;
+
+ public MemberCodeGen(Bytecode b, CtClass cc, ClassPool cp) {
+ super(b);
+ resolver = new MemberResolver(cp);
+ thisClass = cc;
+ thisMethod = null;
+ }
+
+ /**
+ * Returns the major version of the class file
+ * targeted by this compilation.
+ */
+ public int getMajorVersion() {
+ ClassFile cf = thisClass.getClassFile2();
+ if (cf == null)
+ return ClassFile.MAJOR_VERSION; // JDK 1.3
+ else
+ return cf.getMajorVersion();
+ }
+
+ /**
+ * Records the currently compiled method.
+ */
+ public void setThisMethod(CtMethod m) {
+ thisMethod = m.getMethodInfo2();
+ if (typeChecker != null)
+ typeChecker.setThisMethod(thisMethod);
+ }
+
+ public CtClass getThisClass() { return thisClass; }
+
+ /**
+ * Returns the JVM-internal representation of this class name.
+ */
+ protected String getThisName() {
+ return MemberResolver.javaToJvmName(thisClass.getName());
+ }
+
+ /**
+ * Returns the JVM-internal representation of this super class name.
+ */
+ protected String getSuperName() throws CompileError {
+ return MemberResolver.javaToJvmName(
+ MemberResolver.getSuperclass(thisClass).getName());
+ }
+
+ protected void insertDefaultSuperCall() throws CompileError {
+ bytecode.addAload(0);
+ bytecode.addInvokespecial(MemberResolver.getSuperclass(thisClass),
+ "<init>", "()V");
+ }
+
+ static class JsrHook extends ReturnHook {
+ ArrayList jsrList;
+ CodeGen cgen;
+ int var;
+
+ JsrHook(CodeGen gen) {
+ super(gen);
+ jsrList = new ArrayList();
+ cgen = gen;
+ var = -1;
+ }
+
+ private int getVar(int size) {
+ if (var < 0) {
+ var = cgen.getMaxLocals();
+ cgen.incMaxLocals(size);
+ }
+
+ return var;
+ }
+
+ private void jsrJmp(Bytecode b) {
+ b.addOpcode(Opcode.GOTO);
+ jsrList.add(new int[] {b.currentPc(), var});
+ b.addIndex(0);
+ }
+
+ protected boolean doit(Bytecode b, int opcode) {
+ switch (opcode) {
+ case Opcode.RETURN :
+ jsrJmp(b);
+ break;
+ case ARETURN :
+ b.addAstore(getVar(1));
+ jsrJmp(b);
+ b.addAload(var);
+ break;
+ case IRETURN :
+ b.addIstore(getVar(1));
+ jsrJmp(b);
+ b.addIload(var);
+ break;
+ case LRETURN :
+ b.addLstore(getVar(2));
+ jsrJmp(b);
+ b.addLload(var);
+ break;
+ case DRETURN :
+ b.addDstore(getVar(2));
+ jsrJmp(b);
+ b.addDload(var);
+ break;
+ case FRETURN :
+ b.addFstore(getVar(1));
+ jsrJmp(b);
+ b.addFload(var);
+ break;
+ default :
+ throw new RuntimeException("fatal");
+ }
+
+ return false;
+ }
+ }
+
+ static class JsrHook2 extends ReturnHook {
+ int var;
+ int target;
+
+ JsrHook2(CodeGen gen, int[] retTarget) {
+ super(gen);
+ target = retTarget[0];
+ var = retTarget[1];
+ }
+
+ protected boolean doit(Bytecode b, int opcode) {
+ switch (opcode) {
+ case Opcode.RETURN :
+ break;
+ case ARETURN :
+ b.addAstore(var);
+ break;
+ case IRETURN :
+ b.addIstore(var);
+ break;
+ case LRETURN :
+ b.addLstore(var);
+ break;
+ case DRETURN :
+ b.addDstore(var);
+ break;
+ case FRETURN :
+ b.addFstore(var);
+ break;
+ default :
+ throw new RuntimeException("fatal");
+ }
+
+ b.addOpcode(Opcode.GOTO);
+ b.addIndex(target - b.currentPc() + 3);
+ return true;
+ }
+ }
+
+ protected void atTryStmnt(Stmnt st) throws CompileError {
+ Bytecode bc = bytecode;
+ Stmnt body = (Stmnt)st.getLeft();
+ if (body == null)
+ return;
+
+ ASTList catchList = (ASTList)st.getRight().getLeft();
+ Stmnt finallyBlock = (Stmnt)st.getRight().getRight().getLeft();
+ ArrayList gotoList = new ArrayList();
+
+ JsrHook jsrHook = null;
+ if (finallyBlock != null)
+ jsrHook = new JsrHook(this);
+
+ int start = bc.currentPc();
+ body.accept(this);
+ int end = bc.currentPc();
+ if (start == end)
+ throw new CompileError("empty try block");
+
+ boolean tryNotReturn = !hasReturned;
+ if (tryNotReturn) {
+ bc.addOpcode(Opcode.GOTO);
+ gotoList.add(new Integer(bc.currentPc()));
+ bc.addIndex(0); // correct later
+ }
+
+ int var = getMaxLocals();
+ incMaxLocals(1);
+ while (catchList != null) {
+ // catch clause
+ Pair p = (Pair)catchList.head();
+ catchList = catchList.tail();
+ Declarator decl = (Declarator)p.getLeft();
+ Stmnt block = (Stmnt)p.getRight();
+
+ decl.setLocalVar(var);
+
+ CtClass type = resolver.lookupClassByJvmName(decl.getClassName());
+ decl.setClassName(MemberResolver.javaToJvmName(type.getName()));
+ bc.addExceptionHandler(start, end, bc.currentPc(), type);
+ bc.growStack(1);
+ bc.addAstore(var);
+ hasReturned = false;
+ if (block != null)
+ block.accept(this);
+
+ if (!hasReturned) {
+ bc.addOpcode(Opcode.GOTO);
+ gotoList.add(new Integer(bc.currentPc()));
+ bc.addIndex(0); // correct later
+ tryNotReturn = true;
+ }
+ }
+
+ if (finallyBlock != null) {
+ jsrHook.remove(this);
+ // catch (any) clause
+ int pcAnyCatch = bc.currentPc();
+ bc.addExceptionHandler(start, pcAnyCatch, pcAnyCatch, 0);
+ bc.growStack(1);
+ bc.addAstore(var);
+ hasReturned = false;
+ finallyBlock.accept(this);
+ if (!hasReturned) {
+ bc.addAload(var);
+ bc.addOpcode(ATHROW);
+ }
+
+ addFinally(jsrHook.jsrList, finallyBlock);
+ }
+
+ int pcEnd = bc.currentPc();
+ patchGoto(gotoList, pcEnd);
+ hasReturned = !tryNotReturn;
+ if (finallyBlock != null) {
+ if (tryNotReturn)
+ finallyBlock.accept(this);
+ }
+ }
+
+ /**
+ * Adds a finally clause for earch return statement.
+ */
+ private void addFinally(ArrayList returnList, Stmnt finallyBlock)
+ throws CompileError
+ {
+ Bytecode bc = bytecode;
+ int n = returnList.size();
+ for (int i = 0; i < n; ++i) {
+ final int[] ret = (int[])returnList.get(i);
+ int pc = ret[0];
+ bc.write16bit(pc, bc.currentPc() - pc + 1);
+ ReturnHook hook = new JsrHook2(this, ret);
+ finallyBlock.accept(this);
+ hook.remove(this);
+ if (!hasReturned) {
+ bc.addOpcode(Opcode.GOTO);
+ bc.addIndex(pc + 3 - bc.currentPc());
+ }
+ }
+ }
+
+ public void atNewExpr(NewExpr expr) throws CompileError {
+ if (expr.isArray())
+ atNewArrayExpr(expr);
+ else {
+ CtClass clazz = resolver.lookupClassByName(expr.getClassName());
+ String cname = clazz.getName();
+ ASTList args = expr.getArguments();
+ bytecode.addNew(cname);
+ bytecode.addOpcode(DUP);
+
+ atMethodCallCore(clazz, MethodInfo.nameInit, args,
+ false, true, -1, null);
+
+ exprType = CLASS;
+ arrayDim = 0;
+ className = MemberResolver.javaToJvmName(cname);
+ }
+ }
+
+ public void atNewArrayExpr(NewExpr expr) throws CompileError {
+ int type = expr.getArrayType();
+ ASTList size = expr.getArraySize();
+ ASTList classname = expr.getClassName();
+ ArrayInit init = expr.getInitializer();
+ if (size.length() > 1) {
+ if (init != null)
+ throw new CompileError(
+ "sorry, multi-dimensional array initializer " +
+ "for new is not supported");
+
+ atMultiNewArray(type, classname, size);
+ return;
+ }
+
+ ASTree sizeExpr = size.head();
+ atNewArrayExpr2(type, sizeExpr, Declarator.astToClassName(classname, '/'), init);
+ }
+
+ private void atNewArrayExpr2(int type, ASTree sizeExpr,
+ String jvmClassname, ArrayInit init) throws CompileError {
+ if (init == null)
+ if (sizeExpr == null)
+ throw new CompileError("no array size");
+ else
+ sizeExpr.accept(this);
+ else
+ if (sizeExpr == null) {
+ int s = init.length();
+ bytecode.addIconst(s);
+ }
+ else
+ throw new CompileError("unnecessary array size specified for new");
+
+ String elementClass;
+ if (type == CLASS) {
+ elementClass = resolveClassName(jvmClassname);
+ bytecode.addAnewarray(MemberResolver.jvmToJavaName(elementClass));
+ }
+ else {
+ elementClass = null;
+ int atype = 0;
+ switch (type) {
+ case BOOLEAN :
+ atype = T_BOOLEAN;
+ break;
+ case CHAR :
+ atype = T_CHAR;
+ break;
+ case FLOAT :
+ atype = T_FLOAT;
+ break;
+ case DOUBLE :
+ atype = T_DOUBLE;
+ break;
+ case BYTE :
+ atype = T_BYTE;
+ break;
+ case SHORT :
+ atype = T_SHORT;
+ break;
+ case INT :
+ atype = T_INT;
+ break;
+ case LONG :
+ atype = T_LONG;
+ break;
+ default :
+ badNewExpr();
+ break;
+ }
+
+ bytecode.addOpcode(NEWARRAY);
+ bytecode.add(atype);
+ }
+
+ if (init != null) {
+ int s = init.length();
+ ASTList list = init;
+ for (int i = 0; i < s; i++) {
+ bytecode.addOpcode(DUP);
+ bytecode.addIconst(i);
+ list.head().accept(this);
+ if (!isRefType(type))
+ atNumCastExpr(exprType, type);
+
+ bytecode.addOpcode(getArrayWriteOp(type, 0));
+ list = list.tail();
+ }
+ }
+
+ exprType = type;
+ arrayDim = 1;
+ className = elementClass;
+ }
+
+ private static void badNewExpr() throws CompileError {
+ throw new CompileError("bad new expression");
+ }
+
+ protected void atArrayVariableAssign(ArrayInit init, int varType,
+ int varArray, String varClass) throws CompileError {
+ atNewArrayExpr2(varType, null, varClass, init);
+ }
+
+ public void atArrayInit(ArrayInit init) throws CompileError {
+ throw new CompileError("array initializer is not supported");
+ }
+
+ protected void atMultiNewArray(int type, ASTList classname, ASTList size)
+ throws CompileError
+ {
+ int count, dim;
+ dim = size.length();
+ for (count = 0; size != null; size = size.tail()) {
+ ASTree s = size.head();
+ if (s == null)
+ break; // int[][][] a = new int[3][4][];
+
+ ++count;
+ s.accept(this);
+ if (exprType != INT)
+ throw new CompileError("bad type for array size");
+ }
+
+ String desc;
+ exprType = type;
+ arrayDim = dim;
+ if (type == CLASS) {
+ className = resolveClassName(classname);
+ desc = toJvmArrayName(className, dim);
+ }
+ else
+ desc = toJvmTypeName(type, dim);
+
+ bytecode.addMultiNewarray(desc, count);
+ }
+
+ public void atCallExpr(CallExpr expr) throws CompileError {
+ String mname = null;
+ CtClass targetClass = null;
+ ASTree method = expr.oprand1();
+ ASTList args = (ASTList)expr.oprand2();
+ boolean isStatic = false;
+ boolean isSpecial = false;
+ int aload0pos = -1;
+
+ MemberResolver.Method cached = expr.getMethod();
+ if (method instanceof Member) {
+ mname = ((Member)method).get();
+ targetClass = thisClass;
+ if (inStaticMethod || (cached != null && cached.isStatic()))
+ isStatic = true; // should be static
+ else {
+ aload0pos = bytecode.currentPc();
+ bytecode.addAload(0); // this
+ }
+ }
+ else if (method instanceof Keyword) { // constructor
+ isSpecial = true;
+ mname = MethodInfo.nameInit; // <init>
+ targetClass = thisClass;
+ if (inStaticMethod)
+ throw new CompileError("a constructor cannot be static");
+ else
+ bytecode.addAload(0); // this
+
+ if (((Keyword)method).get() == SUPER)
+ targetClass = MemberResolver.getSuperclass(targetClass);
+ }
+ else if (method instanceof Expr) {
+ Expr e = (Expr)method;
+ mname = ((Symbol)e.oprand2()).get();
+ int op = e.getOperator();
+ if (op == MEMBER) { // static method
+ targetClass
+ = resolver.lookupClass(((Symbol)e.oprand1()).get(), false);
+ isStatic = true;
+ }
+ else if (op == '.') {
+ ASTree target = e.oprand1();
+ if (target instanceof Keyword)
+ if (((Keyword)target).get() == SUPER)
+ isSpecial = true;
+
+ try {
+ target.accept(this);
+ }
+ catch (NoFieldException nfe) {
+ if (nfe.getExpr() != target)
+ throw nfe;
+
+ // it should be a static method.
+ exprType = CLASS;
+ arrayDim = 0;
+ className = nfe.getField(); // JVM-internal
+ resolver.recordPackage(className);
+ isStatic = true;
+ }
+
+ if (arrayDim > 0)
+ targetClass = resolver.lookupClass(javaLangObject, true);
+ else if (exprType == CLASS /* && arrayDim == 0 */)
+ targetClass = resolver.lookupClassByJvmName(className);
+ else
+ badMethod();
+ }
+ else
+ badMethod();
+ }
+ else
+ fatal();
+
+ atMethodCallCore(targetClass, mname, args, isStatic, isSpecial,
+ aload0pos, cached);
+ }
+
+ private static void badMethod() throws CompileError {
+ throw new CompileError("bad method");
+ }
+
+ /*
+ * atMethodCallCore() is also called by doit() in NewExpr.ProceedForNew
+ *
+ * @param targetClass the class at which method lookup starts.
+ * @param found not null if the method look has been already done.
+ */
+ public void atMethodCallCore(CtClass targetClass, String mname,
+ ASTList args, boolean isStatic, boolean isSpecial,
+ int aload0pos, MemberResolver.Method found)
+ throws CompileError
+ {
+ int nargs = getMethodArgsLength(args);
+ int[] types = new int[nargs];
+ int[] dims = new int[nargs];
+ String[] cnames = new String[nargs];
+
+ if (!isStatic && found != null && found.isStatic()) {
+ bytecode.addOpcode(POP);
+ isStatic = true;
+ }
+
+ int stack = bytecode.getStackDepth();
+
+ // generate code for evaluating arguments.
+ atMethodArgs(args, types, dims, cnames);
+
+ // used by invokeinterface
+ int count = bytecode.getStackDepth() - stack + 1;
+
+ if (found == null)
+ found = resolver.lookupMethod(targetClass, thisClass, thisMethod,
+ mname, types, dims, cnames);
+
+ if (found == null) {
+ String msg;
+ if (mname.equals(MethodInfo.nameInit))
+ msg = "constructor not found";
+ else
+ msg = "Method " + mname + " not found in "
+ + targetClass.getName();
+
+ throw new CompileError(msg);
+ }
+
+ atMethodCallCore2(targetClass, mname, isStatic, isSpecial,
+ aload0pos, count, found);
+ }
+
+ private void atMethodCallCore2(CtClass targetClass, String mname,
+ boolean isStatic, boolean isSpecial,
+ int aload0pos, int count,
+ MemberResolver.Method found)
+ throws CompileError
+ {
+ CtClass declClass = found.declaring;
+ MethodInfo minfo = found.info;
+ String desc = minfo.getDescriptor();
+ int acc = minfo.getAccessFlags();
+
+ if (mname.equals(MethodInfo.nameInit)) {
+ isSpecial = true;
+ if (declClass != targetClass)
+ throw new CompileError("no such constructor");
+
+ if (declClass != thisClass && AccessFlag.isPrivate(acc)) {
+ desc = getAccessibleConstructor(desc, declClass, minfo);
+ bytecode.addOpcode(Opcode.ACONST_NULL); // the last parameter
+ }
+ }
+ else if (AccessFlag.isPrivate(acc))
+ if (declClass == thisClass)
+ isSpecial = true;
+ else {
+ isSpecial = false;
+ isStatic = true;
+ String origDesc = desc;
+ if ((acc & AccessFlag.STATIC) == 0)
+ desc = Descriptor.insertParameter(declClass.getName(),
+ origDesc);
+
+ acc = AccessFlag.setPackage(acc) | AccessFlag.STATIC;
+ mname = getAccessiblePrivate(mname, origDesc, desc,
+ minfo, declClass);
+ }
+
+ boolean popTarget = false;
+ if ((acc & AccessFlag.STATIC) != 0) {
+ if (!isStatic) {
+ /* this method is static but the target object is
+ on stack. It must be popped out. If aload0pos >= 0,
+ then the target object was pushed by aload_0. It is
+ overwritten by NOP.
+ */
+ isStatic = true;
+ if (aload0pos >= 0)
+ bytecode.write(aload0pos, NOP);
+ else
+ popTarget = true;
+ }
+
+ bytecode.addInvokestatic(declClass, mname, desc);
+ }
+ else if (isSpecial) // if (isSpecial && notStatic(acc))
+ bytecode.addInvokespecial(declClass, mname, desc);
+ else {
+ if (!Modifier.isPublic(declClass.getModifiers())
+ || declClass.isInterface() != targetClass.isInterface())
+ declClass = targetClass;
+
+ if (declClass.isInterface())
+ bytecode.addInvokeinterface(declClass, mname, desc, count);
+ else
+ if (isStatic)
+ throw new CompileError(mname + " is not static");
+ else
+ bytecode.addInvokevirtual(declClass, mname, desc);
+ }
+
+ setReturnType(desc, isStatic, popTarget);
+ }
+
+ /*
+ * Finds (or adds if necessary) a hidden accessor if the method
+ * is in an enclosing class.
+ *
+ * @param desc the descriptor of the method.
+ * @param declClass the class declaring the method.
+ */
+ protected String getAccessiblePrivate(String methodName, String desc,
+ String newDesc, MethodInfo minfo,
+ CtClass declClass)
+ throws CompileError
+ {
+ if (isEnclosing(declClass, thisClass)) {
+ AccessorMaker maker = declClass.getAccessorMaker();
+ if (maker != null)
+ return maker.getMethodAccessor(methodName, desc, newDesc,
+ minfo);
+ }
+
+ throw new CompileError("Method " + methodName
+ + " is private");
+ }
+
+ /*
+ * Finds (or adds if necessary) a hidden constructor if the given
+ * constructor is in an enclosing class.
+ *
+ * @param desc the descriptor of the constructor.
+ * @param declClass the class declaring the constructor.
+ * @param minfo the method info of the constructor.
+ * @return the descriptor of the hidden constructor.
+ */
+ protected String getAccessibleConstructor(String desc, CtClass declClass,
+ MethodInfo minfo)
+ throws CompileError
+ {
+ if (isEnclosing(declClass, thisClass)) {
+ AccessorMaker maker = declClass.getAccessorMaker();
+ if (maker != null)
+ return maker.getConstructor(declClass, desc, minfo);
+ }
+
+ throw new CompileError("the called constructor is private in "
+ + declClass.getName());
+ }
+
+ private boolean isEnclosing(CtClass outer, CtClass inner) {
+ try {
+ while (inner != null) {
+ inner = inner.getDeclaringClass();
+ if (inner == outer)
+ return true;
+ }
+ }
+ catch (NotFoundException e) {}
+ return false;
+ }
+
+ public int getMethodArgsLength(ASTList args) {
+ return ASTList.length(args);
+ }
+
+ public void atMethodArgs(ASTList args, int[] types, int[] dims,
+ String[] cnames) throws CompileError {
+ int i = 0;
+ while (args != null) {
+ ASTree a = args.head();
+ a.accept(this);
+ types[i] = exprType;
+ dims[i] = arrayDim;
+ cnames[i] = className;
+ ++i;
+ args = args.tail();
+ }
+ }
+
+ void setReturnType(String desc, boolean isStatic, boolean popTarget)
+ throws CompileError
+ {
+ int i = desc.indexOf(')');
+ if (i < 0)
+ badMethod();
+
+ char c = desc.charAt(++i);
+ int dim = 0;
+ while (c == '[') {
+ ++dim;
+ c = desc.charAt(++i);
+ }
+
+ arrayDim = dim;
+ if (c == 'L') {
+ int j = desc.indexOf(';', i + 1);
+ if (j < 0)
+ badMethod();
+
+ exprType = CLASS;
+ className = desc.substring(i + 1, j);
+ }
+ else {
+ exprType = MemberResolver.descToType(c);
+ className = null;
+ }
+
+ int etype = exprType;
+ if (isStatic) {
+ if (popTarget) {
+ if (is2word(etype, dim)) {
+ bytecode.addOpcode(DUP2_X1);
+ bytecode.addOpcode(POP2);
+ bytecode.addOpcode(POP);
+ }
+ else if (etype == VOID)
+ bytecode.addOpcode(POP);
+ else {
+ bytecode.addOpcode(SWAP);
+ bytecode.addOpcode(POP);
+ }
+ }
+ }
+ }
+
+ protected void atFieldAssign(Expr expr, int op, ASTree left,
+ ASTree right, boolean doDup) throws CompileError
+ {
+ CtField f = fieldAccess(left, false);
+ boolean is_static = resultStatic;
+ if (op != '=' && !is_static)
+ bytecode.addOpcode(DUP);
+
+ int fi;
+ if (op == '=') {
+ FieldInfo finfo = f.getFieldInfo2();
+ setFieldType(finfo);
+ AccessorMaker maker = isAccessibleField(f, finfo);
+ if (maker == null)
+ fi = addFieldrefInfo(f, finfo);
+ else
+ fi = 0;
+ }
+ else
+ fi = atFieldRead(f, is_static);
+
+ int fType = exprType;
+ int fDim = arrayDim;
+ String cname = className;
+
+ atAssignCore(expr, op, right, fType, fDim, cname);
+
+ boolean is2w = is2word(fType, fDim);
+ if (doDup) {
+ int dup_code;
+ if (is_static)
+ dup_code = (is2w ? DUP2 : DUP);
+ else
+ dup_code = (is2w ? DUP2_X1 : DUP_X1);
+
+ bytecode.addOpcode(dup_code);
+ }
+
+ atFieldAssignCore(f, is_static, fi, is2w);
+
+ exprType = fType;
+ arrayDim = fDim;
+ className = cname;
+ }
+
+ /* If fi == 0, the field must be a private field in an enclosing class.
+ */
+ private void atFieldAssignCore(CtField f, boolean is_static, int fi,
+ boolean is2byte) throws CompileError {
+ if (fi != 0) {
+ if (is_static) {
+ bytecode.add(PUTSTATIC);
+ bytecode.growStack(is2byte ? -2 : -1);
+ }
+ else {
+ bytecode.add(PUTFIELD);
+ bytecode.growStack(is2byte ? -3 : -2);
+ }
+
+ bytecode.addIndex(fi);
+ }
+ else {
+ CtClass declClass = f.getDeclaringClass();
+ AccessorMaker maker = declClass.getAccessorMaker();
+ // make should be non null.
+ FieldInfo finfo = f.getFieldInfo2();
+ MethodInfo minfo = maker.getFieldSetter(finfo, is_static);
+ bytecode.addInvokestatic(declClass, minfo.getName(),
+ minfo.getDescriptor());
+ }
+ }
+
+ /* overwritten in JvstCodeGen.
+ */
+ public void atMember(Member mem) throws CompileError {
+ atFieldRead(mem);
+ }
+
+ protected void atFieldRead(ASTree expr) throws CompileError
+ {
+ CtField f = fieldAccess(expr, true);
+ if (f == null) {
+ atArrayLength(expr);
+ return;
+ }
+
+ boolean is_static = resultStatic;
+ ASTree cexpr = TypeChecker.getConstantFieldValue(f);
+ if (cexpr == null)
+ atFieldRead(f, is_static);
+ else {
+ cexpr.accept(this);
+ setFieldType(f.getFieldInfo2());
+ }
+ }
+
+ private void atArrayLength(ASTree expr) throws CompileError {
+ if (arrayDim == 0)
+ throw new CompileError(".length applied to a non array");
+
+ bytecode.addOpcode(ARRAYLENGTH);
+ exprType = INT;
+ arrayDim = 0;
+ }
+
+ /**
+ * Generates bytecode for reading a field value.
+ * It returns a fieldref_info index or zero if the field is a private
+ * one declared in an enclosing class.
+ */
+ private int atFieldRead(CtField f, boolean isStatic) throws CompileError {
+ FieldInfo finfo = f.getFieldInfo2();
+ boolean is2byte = setFieldType(finfo);
+ AccessorMaker maker = isAccessibleField(f, finfo);
+ if (maker != null) {
+ MethodInfo minfo = maker.getFieldGetter(finfo, isStatic);
+ bytecode.addInvokestatic(f.getDeclaringClass(), minfo.getName(),
+ minfo.getDescriptor());
+ return 0;
+ }
+ else {
+ int fi = addFieldrefInfo(f, finfo);
+ if (isStatic) {
+ bytecode.add(GETSTATIC);
+ bytecode.growStack(is2byte ? 2 : 1);
+ }
+ else {
+ bytecode.add(GETFIELD);
+ bytecode.growStack(is2byte ? 1 : 0);
+ }
+
+ bytecode.addIndex(fi);
+ return fi;
+ }
+ }
+
+ /**
+ * Returns null if the field is accessible. Otherwise, it throws
+ * an exception or it returns AccessorMaker if the field is a private
+ * one declared in an enclosing class.
+ */
+ private AccessorMaker isAccessibleField(CtField f, FieldInfo finfo)
+ throws CompileError
+ {
+ if (AccessFlag.isPrivate(finfo.getAccessFlags())
+ && f.getDeclaringClass() != thisClass) {
+ CtClass declClass = f.getDeclaringClass();
+ if (isEnclosing(declClass, thisClass)) {
+ AccessorMaker maker = declClass.getAccessorMaker();
+ if (maker != null)
+ return maker;
+ else
+ throw new CompileError("fatal error. bug?");
+ }
+ else
+ throw new CompileError("Field " + f.getName() + " in "
+ + declClass.getName() + " is private.");
+ }
+
+ return null; // accessible field
+ }
+
+ /**
+ * Sets exprType, arrayDim, and className.
+ *
+ * @return true if the field type is long or double.
+ */
+ private boolean setFieldType(FieldInfo finfo) throws CompileError {
+ String type = finfo.getDescriptor();
+
+ int i = 0;
+ int dim = 0;
+ char c = type.charAt(i);
+ while (c == '[') {
+ ++dim;
+ c = type.charAt(++i);
+ }
+
+ arrayDim = dim;
+ exprType = MemberResolver.descToType(c);
+
+ if (c == 'L')
+ className = type.substring(i + 1, type.indexOf(';', i + 1));
+ else
+ className = null;
+
+ boolean is2byte = (c == 'J' || c == 'D');
+ return is2byte;
+ }
+
+ private int addFieldrefInfo(CtField f, FieldInfo finfo) {
+ ConstPool cp = bytecode.getConstPool();
+ String cname = f.getDeclaringClass().getName();
+ int ci = cp.addClassInfo(cname);
+ String name = finfo.getName();
+ String type = finfo.getDescriptor();
+ return cp.addFieldrefInfo(ci, name, type);
+ }
+
+ protected void atClassObject2(String cname) throws CompileError {
+ if (getMajorVersion() < ClassFile.JAVA_5)
+ super.atClassObject2(cname);
+ else
+ bytecode.addLdc(bytecode.getConstPool().addClassInfo(cname));
+ }
+
+ protected void atFieldPlusPlus(int token, boolean isPost,
+ ASTree oprand, Expr expr, boolean doDup)
+ throws CompileError
+ {
+ CtField f = fieldAccess(oprand, false);
+ boolean is_static = resultStatic;
+ if (!is_static)
+ bytecode.addOpcode(DUP);
+
+ int fi = atFieldRead(f, is_static);
+ int t = exprType;
+ boolean is2w = is2word(t, arrayDim);
+
+ int dup_code;
+ if (is_static)
+ dup_code = (is2w ? DUP2 : DUP);
+ else
+ dup_code = (is2w ? DUP2_X1 : DUP_X1);
+
+ atPlusPlusCore(dup_code, doDup, token, isPost, expr);
+ atFieldAssignCore(f, is_static, fi, is2w);
+ }
+
+ /* This method also returns a value in resultStatic.
+ *
+ * @param acceptLength true if array length is acceptable
+ */
+ protected CtField fieldAccess(ASTree expr, boolean acceptLength)
+ throws CompileError
+ {
+ if (expr instanceof Member) {
+ String name = ((Member)expr).get();
+ CtField f = null;
+ try {
+ f = thisClass.getField(name);
+ }
+ catch (NotFoundException e) {
+ // EXPR might be part of a static member access?
+ throw new NoFieldException(name, expr);
+ }
+
+ boolean is_static = Modifier.isStatic(f.getModifiers());
+ if (!is_static)
+ if (inStaticMethod)
+ throw new CompileError(
+ "not available in a static method: " + name);
+ else
+ bytecode.addAload(0); // this
+
+ resultStatic = is_static;
+ return f;
+ }
+ else if (expr instanceof Expr) {
+ Expr e = (Expr)expr;
+ int op = e.getOperator();
+ if (op == MEMBER) {
+ /* static member by # (extension by Javassist)
+ * For example, if int.class is parsed, the resulting tree
+ * is (# "java.lang.Integer" "TYPE").
+ */
+ CtField f = resolver.lookupField(((Symbol)e.oprand1()).get(),
+ (Symbol)e.oprand2());
+ resultStatic = true;
+ return f;
+ }
+ else if (op == '.') {
+ CtField f = null;
+ try {
+ e.oprand1().accept(this);
+ /* Don't call lookupFieldByJvmName2().
+ * The left operand of . is not a class name but
+ * a normal expression.
+ */
+ if (exprType == CLASS && arrayDim == 0)
+ f = resolver.lookupFieldByJvmName(className,
+ (Symbol)e.oprand2());
+ else if (acceptLength && arrayDim > 0
+ && ((Symbol)e.oprand2()).get().equals("length"))
+ return null; // expr is an array length.
+ else
+ badLvalue();
+
+ boolean is_static = Modifier.isStatic(f.getModifiers());
+ if (is_static)
+ bytecode.addOpcode(POP);
+
+ resultStatic = is_static;
+ return f;
+ }
+ catch (NoFieldException nfe) {
+ if (nfe.getExpr() != e.oprand1())
+ throw nfe;
+
+ /* EXPR should be a static field.
+ * If EXPR might be part of a qualified class name,
+ * lookupFieldByJvmName2() throws NoFieldException.
+ */
+ Symbol fname = (Symbol)e.oprand2();
+ String cname = nfe.getField();
+ f = resolver.lookupFieldByJvmName2(cname, fname, expr);
+ resolver.recordPackage(cname);
+ resultStatic = true;
+ return f;
+ }
+ }
+ else
+ badLvalue();
+ }
+ else
+ badLvalue();
+
+ resultStatic = false;
+ return null; // never reach
+ }
+
+ private static void badLvalue() throws CompileError {
+ throw new CompileError("bad l-value");
+ }
+
+ public CtClass[] makeParamList(MethodDecl md) throws CompileError {
+ CtClass[] params;
+ ASTList plist = md.getParams();
+ if (plist == null)
+ params = new CtClass[0];
+ else {
+ int i = 0;
+ params = new CtClass[plist.length()];
+ while (plist != null) {
+ params[i++] = resolver.lookupClass((Declarator)plist.head());
+ plist = plist.tail();
+ }
+ }
+
+ return params;
+ }
+
+ public CtClass[] makeThrowsList(MethodDecl md) throws CompileError {
+ CtClass[] clist;
+ ASTList list = md.getThrows();
+ if (list == null)
+ return null;
+ else {
+ int i = 0;
+ clist = new CtClass[list.length()];
+ while (list != null) {
+ clist[i++] = resolver.lookupClassByName((ASTList)list.head());
+ list = list.tail();
+ }
+
+ return clist;
+ }
+ }
+
+ /* Converts a class name into a JVM-internal representation.
+ *
+ * It may also expand a simple class name to java.lang.*.
+ * For example, this converts Object into java/lang/Object.
+ */
+ protected String resolveClassName(ASTList name) throws CompileError {
+ return resolver.resolveClassName(name);
+ }
+
+ /* Expands a simple class name to java.lang.*.
+ * For example, this converts Object into java/lang/Object.
+ */
+ protected String resolveClassName(String jvmName) throws CompileError {
+ return resolver.resolveJvmClassName(jvmName);
+ }
+}
diff --git a/src/main/javassist/compiler/MemberResolver.java b/src/main/javassist/compiler/MemberResolver.java
new file mode 100644
index 0000000..fb2aa9b
--- /dev/null
+++ b/src/main/javassist/compiler/MemberResolver.java
@@ -0,0 +1,583 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.compiler;
+
+import java.util.List;
+import java.util.Iterator;
+import javassist.*;
+import javassist.bytecode.*;
+import javassist.compiler.ast.*;
+
+/* Code generator methods depending on javassist.* classes.
+ */
+public class MemberResolver implements TokenId {
+ private ClassPool classPool;
+
+ public MemberResolver(ClassPool cp) {
+ classPool = cp;
+ }
+
+ public ClassPool getClassPool() { return classPool; }
+
+ private static void fatal() throws CompileError {
+ throw new CompileError("fatal");
+ }
+
+ /**
+ * @param jvmClassName a class name. Not a package name.
+ */
+ public void recordPackage(String jvmClassName) {
+ String classname = jvmToJavaName(jvmClassName);
+ for (;;) {
+ int i = classname.lastIndexOf('.');
+ if (i > 0) {
+ classname = classname.substring(0, i);
+ classPool.recordInvalidClassName(classname);
+ }
+ else
+ break;
+ }
+ }
+
+ public static class Method {
+ public CtClass declaring;
+ public MethodInfo info;
+ public int notmatch;
+
+ public Method(CtClass c, MethodInfo i, int n) {
+ declaring = c;
+ info = i;
+ notmatch = n;
+ }
+
+ /**
+ * Returns true if the invoked method is static.
+ */
+ public boolean isStatic() {
+ int acc = info.getAccessFlags();
+ return (acc & AccessFlag.STATIC) != 0;
+ }
+ }
+
+ public Method lookupMethod(CtClass clazz, CtClass currentClass, MethodInfo current,
+ String methodName,
+ int[] argTypes, int[] argDims,
+ String[] argClassNames)
+ throws CompileError
+ {
+ Method maybe = null;
+ // to enable the creation of a recursively called method
+ if (current != null && clazz == currentClass)
+ if (current.getName().equals(methodName)) {
+ int res = compareSignature(current.getDescriptor(),
+ argTypes, argDims, argClassNames);
+ if (res != NO) {
+ Method r = new Method(clazz, current, res);
+ if (res == YES)
+ return r;
+ else
+ maybe = r;
+ }
+ }
+
+ Method m = lookupMethod(clazz, methodName, argTypes, argDims,
+ argClassNames, maybe != null);
+ if (m != null)
+ return m;
+ else
+ return maybe;
+ }
+
+ private Method lookupMethod(CtClass clazz, String methodName,
+ int[] argTypes, int[] argDims,
+ String[] argClassNames, boolean onlyExact)
+ throws CompileError
+ {
+ Method maybe = null;
+ ClassFile cf = clazz.getClassFile2();
+ // If the class is an array type, the class file is null.
+ // If so, search the super class java.lang.Object for clone() etc.
+ if (cf != null) {
+ List list = cf.getMethods();
+ int n = list.size();
+ for (int i = 0; i < n; ++i) {
+ MethodInfo minfo = (MethodInfo)list.get(i);
+ if (minfo.getName().equals(methodName)) {
+ int res = compareSignature(minfo.getDescriptor(),
+ argTypes, argDims, argClassNames);
+ if (res != NO) {
+ Method r = new Method(clazz, minfo, res);
+ if (res == YES)
+ return r;
+ else if (maybe == null || maybe.notmatch > res)
+ maybe = r;
+ }
+ }
+ }
+ }
+
+ if (onlyExact)
+ maybe = null;
+ else
+ onlyExact = maybe != null;
+
+ int mod = clazz.getModifiers();
+ boolean isIntf = Modifier.isInterface(mod);
+ try {
+ // skip searching java.lang.Object if clazz is an interface type.
+ if (!isIntf) {
+ CtClass pclazz = clazz.getSuperclass();
+ if (pclazz != null) {
+ Method r = lookupMethod(pclazz, methodName, argTypes,
+ argDims, argClassNames, onlyExact);
+ if (r != null)
+ return r;
+ }
+ }
+ }
+ catch (NotFoundException e) {}
+
+ if (isIntf || Modifier.isAbstract(mod))
+ try {
+ CtClass[] ifs = clazz.getInterfaces();
+ int size = ifs.length;
+ for (int i = 0; i < size; ++i) {
+ Method r = lookupMethod(ifs[i], methodName,
+ argTypes, argDims, argClassNames,
+ onlyExact);
+ if (r != null)
+ return r;
+ }
+
+ if (isIntf) {
+ // finally search java.lang.Object.
+ CtClass pclazz = clazz.getSuperclass();
+ if (pclazz != null) {
+ Method r = lookupMethod(pclazz, methodName, argTypes,
+ argDims, argClassNames, onlyExact);
+ if (r != null)
+ return r;
+ }
+ }
+ }
+ catch (NotFoundException e) {}
+
+ return maybe;
+ }
+
+ private static final int YES = 0;
+ private static final int NO = -1;
+
+ /*
+ * Returns YES if actual parameter types matches the given signature.
+ *
+ * argTypes, argDims, and argClassNames represent actual parameters.
+ *
+ * This method does not correctly implement the Java method dispatch
+ * algorithm.
+ *
+ * If some of the parameter types exactly match but others are subtypes of
+ * the corresponding type in the signature, this method returns the number
+ * of parameter types that do not exactly match.
+ */
+ private int compareSignature(String desc, int[] argTypes,
+ int[] argDims, String[] argClassNames)
+ throws CompileError
+ {
+ int result = YES;
+ int i = 1;
+ int nArgs = argTypes.length;
+ if (nArgs != Descriptor.numOfParameters(desc))
+ return NO;
+
+ int len = desc.length();
+ for (int n = 0; i < len; ++n) {
+ char c = desc.charAt(i++);
+ if (c == ')')
+ return (n == nArgs ? result : NO);
+ else if (n >= nArgs)
+ return NO;
+
+ int dim = 0;
+ while (c == '[') {
+ ++dim;
+ c = desc.charAt(i++);
+ }
+
+ if (argTypes[n] == NULL) {
+ if (dim == 0 && c != 'L')
+ return NO;
+
+ if (c == 'L')
+ i = desc.indexOf(';', i) + 1;
+ }
+ else if (argDims[n] != dim) {
+ if (!(dim == 0 && c == 'L'
+ && desc.startsWith("java/lang/Object;", i)))
+ return NO;
+
+ // if the thread reaches here, c must be 'L'.
+ i = desc.indexOf(';', i) + 1;
+ result++;
+ if (i <= 0)
+ return NO; // invalid descriptor?
+ }
+ else if (c == 'L') { // not compare
+ int j = desc.indexOf(';', i);
+ if (j < 0 || argTypes[n] != CLASS)
+ return NO;
+
+ String cname = desc.substring(i, j);
+ if (!cname.equals(argClassNames[n])) {
+ CtClass clazz = lookupClassByJvmName(argClassNames[n]);
+ try {
+ if (clazz.subtypeOf(lookupClassByJvmName(cname)))
+ result++;
+ else
+ return NO;
+ }
+ catch (NotFoundException e) {
+ result++; // should be NO?
+ }
+ }
+
+ i = j + 1;
+ }
+ else {
+ int t = descToType(c);
+ int at = argTypes[n];
+ if (t != at)
+ if (t == INT
+ && (at == SHORT || at == BYTE || at == CHAR))
+ result++;
+ else
+ return NO;
+ }
+ }
+
+ return NO;
+ }
+
+ /**
+ * Only used by fieldAccess() in MemberCodeGen and TypeChecker.
+ *
+ * @param jvmClassName a JVM class name. e.g. java/lang/String
+ */
+ public CtField lookupFieldByJvmName2(String jvmClassName, Symbol fieldSym,
+ ASTree expr) throws NoFieldException
+ {
+ String field = fieldSym.get();
+ CtClass cc = null;
+ try {
+ cc = lookupClass(jvmToJavaName(jvmClassName), true);
+ }
+ catch (CompileError e) {
+ // EXPR might be part of a qualified class name.
+ throw new NoFieldException(jvmClassName + "/" + field, expr);
+ }
+
+ try {
+ return cc.getField(field);
+ }
+ catch (NotFoundException e) {
+ // maybe an inner class.
+ jvmClassName = javaToJvmName(cc.getName());
+ throw new NoFieldException(jvmClassName + "$" + field, expr);
+ }
+ }
+
+ /**
+ * @param jvmClassName a JVM class name. e.g. java/lang/String
+ */
+ public CtField lookupFieldByJvmName(String jvmClassName, Symbol fieldName)
+ throws CompileError
+ {
+ return lookupField(jvmToJavaName(jvmClassName), fieldName);
+ }
+
+ /**
+ * @param name a qualified class name. e.g. java.lang.String
+ */
+ public CtField lookupField(String className, Symbol fieldName)
+ throws CompileError
+ {
+ CtClass cc = lookupClass(className, false);
+ try {
+ return cc.getField(fieldName.get());
+ }
+ catch (NotFoundException e) {}
+ throw new CompileError("no such field: " + fieldName.get());
+ }
+
+ public CtClass lookupClassByName(ASTList name) throws CompileError {
+ return lookupClass(Declarator.astToClassName(name, '.'), false);
+ }
+
+ public CtClass lookupClassByJvmName(String jvmName) throws CompileError {
+ return lookupClass(jvmToJavaName(jvmName), false);
+ }
+
+ public CtClass lookupClass(Declarator decl) throws CompileError {
+ return lookupClass(decl.getType(), decl.getArrayDim(),
+ decl.getClassName());
+ }
+
+ /**
+ * @parma classname jvm class name.
+ */
+ public CtClass lookupClass(int type, int dim, String classname)
+ throws CompileError
+ {
+ String cname = "";
+ CtClass clazz;
+ if (type == CLASS) {
+ clazz = lookupClassByJvmName(classname);
+ if (dim > 0)
+ cname = clazz.getName();
+ else
+ return clazz;
+ }
+ else
+ cname = getTypeName(type);
+
+ while (dim-- > 0)
+ cname += "[]";
+
+ return lookupClass(cname, false);
+ }
+
+ /*
+ * type cannot be CLASS
+ */
+ static String getTypeName(int type) throws CompileError {
+ String cname = "";
+ switch (type) {
+ case BOOLEAN :
+ cname = "boolean";
+ break;
+ case CHAR :
+ cname = "char";
+ break;
+ case BYTE :
+ cname = "byte";
+ break;
+ case SHORT :
+ cname = "short";
+ break;
+ case INT :
+ cname = "int";
+ break;
+ case LONG :
+ cname = "long";
+ break;
+ case FLOAT :
+ cname = "float";
+ break;
+ case DOUBLE :
+ cname = "double";
+ break;
+ case VOID :
+ cname = "void";
+ break;
+ default :
+ fatal();
+ }
+
+ return cname;
+ }
+
+ /**
+ * @param name a qualified class name. e.g. java.lang.String
+ */
+ public CtClass lookupClass(String name, boolean notCheckInner)
+ throws CompileError
+ {
+ try {
+ return lookupClass0(name, notCheckInner);
+ }
+ catch (NotFoundException e) {
+ return searchImports(name);
+ }
+ }
+
+ private CtClass searchImports(String orgName)
+ throws CompileError
+ {
+ if (orgName.indexOf('.') < 0) {
+ Iterator it = classPool.getImportedPackages();
+ while (it.hasNext()) {
+ String pac = (String)it.next();
+ String fqName = pac + '.' + orgName;
+ try {
+ CtClass cc = classPool.get(fqName);
+ // if the class is found,
+ classPool.recordInvalidClassName(orgName);
+ return cc;
+ }
+ catch (NotFoundException e) {
+ classPool.recordInvalidClassName(fqName);
+ try {
+ if (pac.endsWith("." + orgName)) {
+ CtClass cc = classPool.get(pac);
+ // if the class is found,
+ classPool.recordInvalidClassName(orgName);
+ return cc;
+ }
+ }
+ catch (NotFoundException e2) {
+ classPool.recordInvalidClassName(pac);
+ }
+ }
+ }
+ }
+
+ throw new CompileError("no such class: " + orgName);
+ }
+
+ private CtClass lookupClass0(String classname, boolean notCheckInner)
+ throws NotFoundException
+ {
+ CtClass cc = null;
+ do {
+ try {
+ cc = classPool.get(classname);
+ }
+ catch (NotFoundException e) {
+ int i = classname.lastIndexOf('.');
+ if (notCheckInner || i < 0)
+ throw e;
+ else {
+ StringBuffer sbuf = new StringBuffer(classname);
+ sbuf.setCharAt(i, '$');
+ classname = sbuf.toString();
+ }
+ }
+ } while (cc == null);
+ return cc;
+ }
+
+ /* Converts a class name into a JVM-internal representation.
+ *
+ * It may also expand a simple class name to java.lang.*.
+ * For example, this converts Object into java/lang/Object.
+ */
+ public String resolveClassName(ASTList name) throws CompileError {
+ if (name == null)
+ return null;
+ else
+ return javaToJvmName(lookupClassByName(name).getName());
+ }
+
+ /* Expands a simple class name to java.lang.*.
+ * For example, this converts Object into java/lang/Object.
+ */
+ public String resolveJvmClassName(String jvmName) throws CompileError {
+ if (jvmName == null)
+ return null;
+ else
+ return javaToJvmName(lookupClassByJvmName(jvmName).getName());
+ }
+
+ public static CtClass getSuperclass(CtClass c) throws CompileError {
+ try {
+ CtClass sc = c.getSuperclass();
+ if (sc != null)
+ return sc;
+ }
+ catch (NotFoundException e) {}
+ throw new CompileError("cannot find the super class of "
+ + c.getName());
+ }
+
+ public static String javaToJvmName(String classname) {
+ return classname.replace('.', '/');
+ }
+
+ public static String jvmToJavaName(String classname) {
+ return classname.replace('/', '.');
+ }
+
+ public static int descToType(char c) throws CompileError {
+ switch (c) {
+ case 'Z' :
+ return BOOLEAN;
+ case 'C' :
+ return CHAR;
+ case 'B' :
+ return BYTE;
+ case 'S' :
+ return SHORT;
+ case 'I' :
+ return INT;
+ case 'J' :
+ return LONG;
+ case 'F' :
+ return FLOAT;
+ case 'D' :
+ return DOUBLE;
+ case 'V' :
+ return VOID;
+ case 'L' :
+ case '[' :
+ return CLASS;
+ default :
+ fatal();
+ return VOID; // never reach here
+ }
+ }
+
+ public static int getModifiers(ASTList mods) {
+ int m = 0;
+ while (mods != null) {
+ Keyword k = (Keyword)mods.head();
+ mods = mods.tail();
+ switch (k.get()) {
+ case STATIC :
+ m |= Modifier.STATIC;
+ break;
+ case FINAL :
+ m |= Modifier.FINAL;
+ break;
+ case SYNCHRONIZED :
+ m |= Modifier.SYNCHRONIZED;
+ break;
+ case ABSTRACT :
+ m |= Modifier.ABSTRACT;
+ break;
+ case PUBLIC :
+ m |= Modifier.PUBLIC;
+ break;
+ case PROTECTED :
+ m |= Modifier.PROTECTED;
+ break;
+ case PRIVATE :
+ m |= Modifier.PRIVATE;
+ break;
+ case VOLATILE :
+ m |= Modifier.VOLATILE;
+ break;
+ case TRANSIENT :
+ m |= Modifier.TRANSIENT;
+ break;
+ case STRICT :
+ m |= Modifier.STRICT;
+ break;
+ }
+ }
+
+ return m;
+ }
+}
diff --git a/src/main/javassist/compiler/NoFieldException.java b/src/main/javassist/compiler/NoFieldException.java
new file mode 100644
index 0000000..f6e114b
--- /dev/null
+++ b/src/main/javassist/compiler/NoFieldException.java
@@ -0,0 +1,39 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.compiler;
+
+import javassist.compiler.ast.ASTree;
+
+public class NoFieldException extends CompileError {
+ private String fieldName;
+ private ASTree expr;
+
+ /* NAME must be JVM-internal representation.
+ */
+ public NoFieldException(String name, ASTree e) {
+ super("no such field: " + name);
+ fieldName = name;
+ expr = e;
+ }
+
+ /* The returned name should be JVM-internal representation.
+ */
+ public String getField() { return fieldName; }
+
+ /* Returns the expression where this exception is thrown.
+ */
+ public ASTree getExpr() { return expr; }
+}
diff --git a/src/main/javassist/compiler/Parser.java b/src/main/javassist/compiler/Parser.java
new file mode 100644
index 0000000..d483814
--- /dev/null
+++ b/src/main/javassist/compiler/Parser.java
@@ -0,0 +1,1342 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.compiler;
+
+import javassist.compiler.ast.*;
+
+public final class Parser implements TokenId {
+ private Lex lex;
+
+ public Parser(Lex lex) {
+ this.lex = lex;
+ }
+
+ public boolean hasMore() { return lex.lookAhead() >= 0; }
+
+ /* member.declaration
+ * : method.declaration | field.declaration
+ */
+ public ASTList parseMember(SymbolTable tbl) throws CompileError {
+ ASTList mem = parseMember1(tbl);
+ if (mem instanceof MethodDecl)
+ return parseMethod2(tbl, (MethodDecl)mem);
+ else
+ return mem;
+ }
+
+ /* A method body is not parsed.
+ */
+ public ASTList parseMember1(SymbolTable tbl) throws CompileError {
+ ASTList mods = parseMemberMods();
+ Declarator d;
+ boolean isConstructor = false;
+ if (lex.lookAhead() == Identifier && lex.lookAhead(1) == '(') {
+ d = new Declarator(VOID, 0);
+ isConstructor = true;
+ }
+ else
+ d = parseFormalType(tbl);
+
+ if (lex.get() != Identifier)
+ throw new SyntaxError(lex);
+
+ String name;
+ if (isConstructor)
+ name = MethodDecl.initName;
+ else
+ name = lex.getString();
+
+ d.setVariable(new Symbol(name));
+ if (isConstructor || lex.lookAhead() == '(')
+ return parseMethod1(tbl, isConstructor, mods, d);
+ else
+ return parseField(tbl, mods, d);
+ }
+
+ /* field.declaration
+ * : member.modifiers
+ * formal.type Identifier
+ * [ "=" expression ] ";"
+ */
+ private FieldDecl parseField(SymbolTable tbl, ASTList mods,
+ Declarator d) throws CompileError
+ {
+ ASTree expr = null;
+ if (lex.lookAhead() == '=') {
+ lex.get();
+ expr = parseExpression(tbl);
+ }
+
+ int c = lex.get();
+ if (c == ';')
+ return new FieldDecl(mods, new ASTList(d, new ASTList(expr)));
+ else if (c == ',')
+ throw new CompileError(
+ "only one field can be declared in one declaration", lex);
+ else
+ throw new SyntaxError(lex);
+ }
+
+ /* method.declaration
+ * : member.modifiers
+ * [ formal.type ]
+ * Identifier "(" [ formal.parameter ( "," formal.parameter )* ] ")"
+ * array.dimension
+ * [ THROWS class.type ( "," class.type ) ]
+ * ( block.statement | ";" )
+ *
+ * Note that a method body is not parsed.
+ */
+ private MethodDecl parseMethod1(SymbolTable tbl, boolean isConstructor,
+ ASTList mods, Declarator d)
+ throws CompileError
+ {
+ if (lex.get() != '(')
+ throw new SyntaxError(lex);
+
+ ASTList parms = null;
+ if (lex.lookAhead() != ')')
+ while (true) {
+ parms = ASTList.append(parms, parseFormalParam(tbl));
+ int t = lex.lookAhead();
+ if (t == ',')
+ lex.get();
+ else if (t == ')')
+ break;
+ }
+
+ lex.get(); // ')'
+ d.addArrayDim(parseArrayDimension());
+ if (isConstructor && d.getArrayDim() > 0)
+ throw new SyntaxError(lex);
+
+ ASTList throwsList = null;
+ if (lex.lookAhead() == THROWS) {
+ lex.get();
+ while (true) {
+ throwsList = ASTList.append(throwsList, parseClassType(tbl));
+ if (lex.lookAhead() == ',')
+ lex.get();
+ else
+ break;
+ }
+ }
+
+ return new MethodDecl(mods, new ASTList(d,
+ ASTList.make(parms, throwsList, null)));
+ }
+
+ /* Parses a method body.
+ */
+ public MethodDecl parseMethod2(SymbolTable tbl, MethodDecl md)
+ throws CompileError
+ {
+ Stmnt body = null;
+ if (lex.lookAhead() == ';')
+ lex.get();
+ else {
+ body = parseBlock(tbl);
+ if (body == null)
+ body = new Stmnt(BLOCK);
+ }
+
+ md.sublist(4).setHead(body);
+ return md;
+ }
+
+ /* member.modifiers
+ * : ( FINAL | SYNCHRONIZED | ABSTRACT
+ * | PUBLIC | PROTECTED | PRIVATE | STATIC
+ * | VOLATILE | TRANSIENT | STRICT )*
+ */
+ private ASTList parseMemberMods() {
+ int t;
+ ASTList list = null;
+ while (true) {
+ t = lex.lookAhead();
+ if (t == ABSTRACT || t == FINAL || t == PUBLIC || t == PROTECTED
+ || t == PRIVATE || t == SYNCHRONIZED || t == STATIC
+ || t == VOLATILE || t == TRANSIENT || t == STRICT)
+ list = new ASTList(new Keyword(lex.get()), list);
+ else
+ break;
+ }
+
+ return list;
+ }
+
+ /* formal.type : ( build-in-type | class.type ) array.dimension
+ */
+ private Declarator parseFormalType(SymbolTable tbl) throws CompileError {
+ int t = lex.lookAhead();
+ if (isBuiltinType(t) || t == VOID) {
+ lex.get(); // primitive type
+ int dim = parseArrayDimension();
+ return new Declarator(t, dim);
+ }
+ else {
+ ASTList name = parseClassType(tbl);
+ int dim = parseArrayDimension();
+ return new Declarator(name, dim);
+ }
+ }
+
+ private static boolean isBuiltinType(int t) {
+ return (t == BOOLEAN || t == BYTE || t == CHAR || t == SHORT
+ || t == INT || t == LONG || t == FLOAT || t == DOUBLE);
+ }
+
+ /* formal.parameter : formal.type Identifier array.dimension
+ */
+ private Declarator parseFormalParam(SymbolTable tbl)
+ throws CompileError
+ {
+ Declarator d = parseFormalType(tbl);
+ if (lex.get() != Identifier)
+ throw new SyntaxError(lex);
+
+ String name = lex.getString();
+ d.setVariable(new Symbol(name));
+ d.addArrayDim(parseArrayDimension());
+ tbl.append(name, d);
+ return d;
+ }
+
+ /* statement : [ label ":" ]* labeled.statement
+ *
+ * labeled.statement
+ * : block.statement
+ * | if.statement
+ * | while.statement
+ * | do.statement
+ * | for.statement
+ * | switch.statement
+ * | try.statement
+ * | return.statement
+ * | thorw.statement
+ * | break.statement
+ * | continue.statement
+ * | declaration.or.expression
+ * | ";"
+ *
+ * This method may return null (empty statement).
+ */
+ public Stmnt parseStatement(SymbolTable tbl)
+ throws CompileError
+ {
+ int t = lex.lookAhead();
+ if (t == '{')
+ return parseBlock(tbl);
+ else if (t == ';') {
+ lex.get();
+ return new Stmnt(BLOCK); // empty statement
+ }
+ else if (t == Identifier && lex.lookAhead(1) == ':') {
+ lex.get(); // Identifier
+ String label = lex.getString();
+ lex.get(); // ':'
+ return Stmnt.make(LABEL, new Symbol(label), parseStatement(tbl));
+ }
+ else if (t == IF)
+ return parseIf(tbl);
+ else if (t == WHILE)
+ return parseWhile(tbl);
+ else if (t == DO)
+ return parseDo(tbl);
+ else if (t == FOR)
+ return parseFor(tbl);
+ else if (t == TRY)
+ return parseTry(tbl);
+ else if (t == SWITCH)
+ return parseSwitch(tbl);
+ else if (t == SYNCHRONIZED)
+ return parseSynchronized(tbl);
+ else if (t == RETURN)
+ return parseReturn(tbl);
+ else if (t == THROW)
+ return parseThrow(tbl);
+ else if (t == BREAK)
+ return parseBreak(tbl);
+ else if (t == CONTINUE)
+ return parseContinue(tbl);
+ else
+ return parseDeclarationOrExpression(tbl, false);
+ }
+
+ /* block.statement : "{" statement* "}"
+ */
+ private Stmnt parseBlock(SymbolTable tbl) throws CompileError {
+ if (lex.get() != '{')
+ throw new SyntaxError(lex);
+
+ Stmnt body = null;
+ SymbolTable tbl2 = new SymbolTable(tbl);
+ while (lex.lookAhead() != '}') {
+ Stmnt s = parseStatement(tbl2);
+ if (s != null)
+ body = (Stmnt)ASTList.concat(body, new Stmnt(BLOCK, s));
+ }
+
+ lex.get(); // '}'
+ if (body == null)
+ return new Stmnt(BLOCK); // empty block
+ else
+ return body;
+ }
+
+ /* if.statement : IF "(" expression ")" statement
+ * [ ELSE statement ]
+ */
+ private Stmnt parseIf(SymbolTable tbl) throws CompileError {
+ int t = lex.get(); // IF
+ ASTree expr = parseParExpression(tbl);
+ Stmnt thenp = parseStatement(tbl);
+ Stmnt elsep;
+ if (lex.lookAhead() == ELSE) {
+ lex.get();
+ elsep = parseStatement(tbl);
+ }
+ else
+ elsep = null;
+
+ return new Stmnt(t, expr, new ASTList(thenp, new ASTList(elsep)));
+ }
+
+ /* while.statement : WHILE "(" expression ")" statement
+ */
+ private Stmnt parseWhile(SymbolTable tbl)
+ throws CompileError
+ {
+ int t = lex.get(); // WHILE
+ ASTree expr = parseParExpression(tbl);
+ Stmnt body = parseStatement(tbl);
+ return new Stmnt(t, expr, body);
+ }
+
+ /* do.statement : DO statement WHILE "(" expression ")" ";"
+ */
+ private Stmnt parseDo(SymbolTable tbl) throws CompileError {
+ int t = lex.get(); // DO
+ Stmnt body = parseStatement(tbl);
+ if (lex.get() != WHILE || lex.get() != '(')
+ throw new SyntaxError(lex);
+
+ ASTree expr = parseExpression(tbl);
+ if (lex.get() != ')' || lex.get() != ';')
+ throw new SyntaxError(lex);
+
+ return new Stmnt(t, expr, body);
+ }
+
+ /* for.statement : FOR "(" decl.or.expr expression ";" expression ")"
+ * statement
+ */
+ private Stmnt parseFor(SymbolTable tbl) throws CompileError {
+ Stmnt expr1, expr3;
+ ASTree expr2;
+ int t = lex.get(); // FOR
+
+ SymbolTable tbl2 = new SymbolTable(tbl);
+
+ if (lex.get() != '(')
+ throw new SyntaxError(lex);
+
+ if (lex.lookAhead() == ';') {
+ lex.get();
+ expr1 = null;
+ }
+ else
+ expr1 = parseDeclarationOrExpression(tbl2, true);
+
+ if (lex.lookAhead() == ';')
+ expr2 = null;
+ else
+ expr2 = parseExpression(tbl2);
+
+ if (lex.get() != ';')
+ throw new CompileError("; is missing", lex);
+
+ if (lex.lookAhead() == ')')
+ expr3 = null;
+ else
+ expr3 = parseExprList(tbl2);
+
+ if (lex.get() != ')')
+ throw new CompileError(") is missing", lex);
+
+ Stmnt body = parseStatement(tbl2);
+ return new Stmnt(t, expr1, new ASTList(expr2,
+ new ASTList(expr3, body)));
+ }
+
+ /* switch.statement : SWITCH "(" expression ")" "{" switch.block "}"
+ *
+ * swtich.block : ( switch.label statement* )*
+ *
+ * swtich.label : DEFAULT ":"
+ * | CASE const.expression ":"
+ */
+ private Stmnt parseSwitch(SymbolTable tbl) throws CompileError {
+ int t = lex.get(); // SWITCH
+ ASTree expr = parseParExpression(tbl);
+ Stmnt body = parseSwitchBlock(tbl);
+ return new Stmnt(t, expr, body);
+ }
+
+ private Stmnt parseSwitchBlock(SymbolTable tbl) throws CompileError {
+ if (lex.get() != '{')
+ throw new SyntaxError(lex);
+
+ SymbolTable tbl2 = new SymbolTable(tbl);
+ Stmnt s = parseStmntOrCase(tbl2);
+ if (s == null)
+ throw new CompileError("empty switch block", lex);
+
+ int op = s.getOperator();
+ if (op != CASE && op != DEFAULT)
+ throw new CompileError("no case or default in a switch block",
+ lex);
+
+ Stmnt body = new Stmnt(BLOCK, s);
+ while (lex.lookAhead() != '}') {
+ Stmnt s2 = parseStmntOrCase(tbl2);
+ if (s2 != null) {
+ int op2 = s2.getOperator();
+ if (op2 == CASE || op2 == DEFAULT) {
+ body = (Stmnt)ASTList.concat(body, new Stmnt(BLOCK, s2));
+ s = s2;
+ }
+ else
+ s = (Stmnt)ASTList.concat(s, new Stmnt(BLOCK, s2));
+ }
+ }
+
+ lex.get(); // '}'
+ return body;
+ }
+
+ private Stmnt parseStmntOrCase(SymbolTable tbl) throws CompileError {
+ int t = lex.lookAhead();
+ if (t != CASE && t != DEFAULT)
+ return parseStatement(tbl);
+
+ lex.get();
+ Stmnt s;
+ if (t == CASE)
+ s = new Stmnt(t, parseExpression(tbl));
+ else
+ s = new Stmnt(DEFAULT);
+
+ if (lex.get() != ':')
+ throw new CompileError(": is missing", lex);
+
+ return s;
+ }
+
+ /* synchronized.statement :
+ * SYNCHRONIZED "(" expression ")" block.statement
+ */
+ private Stmnt parseSynchronized(SymbolTable tbl) throws CompileError {
+ int t = lex.get(); // SYNCHRONIZED
+ if (lex.get() != '(')
+ throw new SyntaxError(lex);
+
+ ASTree expr = parseExpression(tbl);
+ if (lex.get() != ')')
+ throw new SyntaxError(lex);
+
+ Stmnt body = parseBlock(tbl);
+ return new Stmnt(t, expr, body);
+ }
+
+ /* try.statement
+ * : TRY block.statement
+ * [ CATCH "(" class.type Identifier ")" block.statement ]*
+ * [ FINALLY block.statement ]*
+ */
+ private Stmnt parseTry(SymbolTable tbl) throws CompileError {
+ lex.get(); // TRY
+ Stmnt block = parseBlock(tbl);
+ ASTList catchList = null;
+ while (lex.lookAhead() == CATCH) {
+ lex.get(); // CATCH
+ if (lex.get() != '(')
+ throw new SyntaxError(lex);
+
+ SymbolTable tbl2 = new SymbolTable(tbl);
+ Declarator d = parseFormalParam(tbl2);
+ if (d.getArrayDim() > 0 || d.getType() != CLASS)
+ throw new SyntaxError(lex);
+
+ if (lex.get() != ')')
+ throw new SyntaxError(lex);
+
+ Stmnt b = parseBlock(tbl2);
+ catchList = ASTList.append(catchList, new Pair(d, b));
+ }
+
+ Stmnt finallyBlock = null;
+ if (lex.lookAhead() == FINALLY) {
+ lex.get(); // FINALLY
+ finallyBlock = parseBlock(tbl);
+ }
+
+ return Stmnt.make(TRY, block, catchList, finallyBlock);
+ }
+
+ /* return.statement : RETURN [ expression ] ";"
+ */
+ private Stmnt parseReturn(SymbolTable tbl) throws CompileError {
+ int t = lex.get(); // RETURN
+ Stmnt s = new Stmnt(t);
+ if (lex.lookAhead() != ';')
+ s.setLeft(parseExpression(tbl));
+
+ if (lex.get() != ';')
+ throw new CompileError("; is missing", lex);
+
+ return s;
+ }
+
+ /* throw.statement : THROW expression ";"
+ */
+ private Stmnt parseThrow(SymbolTable tbl) throws CompileError {
+ int t = lex.get(); // THROW
+ ASTree expr = parseExpression(tbl);
+ if (lex.get() != ';')
+ throw new CompileError("; is missing", lex);
+
+ return new Stmnt(t, expr);
+ }
+
+ /* break.statement : BREAK [ Identifier ] ";"
+ */
+ private Stmnt parseBreak(SymbolTable tbl)
+ throws CompileError
+ {
+ return parseContinue(tbl);
+ }
+
+ /* continue.statement : CONTINUE [ Identifier ] ";"
+ */
+ private Stmnt parseContinue(SymbolTable tbl)
+ throws CompileError
+ {
+ int t = lex.get(); // CONTINUE
+ Stmnt s = new Stmnt(t);
+ int t2 = lex.get();
+ if (t2 == Identifier) {
+ s.setLeft(new Symbol(lex.getString()));
+ t2 = lex.get();
+ }
+
+ if (t2 != ';')
+ throw new CompileError("; is missing", lex);
+
+ return s;
+ }
+
+ /* declaration.or.expression
+ * : [ FINAL ] built-in-type array.dimension declarators
+ * | [ FINAL ] class.type array.dimension declarators
+ * | expression ';'
+ * | expr.list ';' if exprList is true
+ *
+ * Note: FINAL is currently ignored. This must be fixed
+ * in future.
+ */
+ private Stmnt parseDeclarationOrExpression(SymbolTable tbl,
+ boolean exprList)
+ throws CompileError
+ {
+ int t = lex.lookAhead();
+ while (t == FINAL) {
+ lex.get();
+ t = lex.lookAhead();
+ }
+
+ if (isBuiltinType(t)) {
+ t = lex.get();
+ int dim = parseArrayDimension();
+ return parseDeclarators(tbl, new Declarator(t, dim));
+ }
+ else if (t == Identifier) {
+ int i = nextIsClassType(0);
+ if (i >= 0)
+ if (lex.lookAhead(i) == Identifier) {
+ ASTList name = parseClassType(tbl);
+ int dim = parseArrayDimension();
+ return parseDeclarators(tbl, new Declarator(name, dim));
+ }
+ }
+
+ Stmnt expr;
+ if (exprList)
+ expr = parseExprList(tbl);
+ else
+ expr = new Stmnt(EXPR, parseExpression(tbl));
+
+ if (lex.get() != ';')
+ throw new CompileError("; is missing", lex);
+
+ return expr;
+ }
+
+ /* expr.list : ( expression ',')* expression
+ */
+ private Stmnt parseExprList(SymbolTable tbl) throws CompileError {
+ Stmnt expr = null;
+ for (;;) {
+ Stmnt e = new Stmnt(EXPR, parseExpression(tbl));
+ expr = (Stmnt)ASTList.concat(expr, new Stmnt(BLOCK, e));
+ if (lex.lookAhead() == ',')
+ lex.get();
+ else
+ return expr;
+ }
+ }
+
+ /* declarators : declarator [ ',' declarator ]* ';'
+ */
+ private Stmnt parseDeclarators(SymbolTable tbl, Declarator d)
+ throws CompileError
+ {
+ Stmnt decl = null;
+ for (;;) {
+ decl = (Stmnt)ASTList.concat(decl,
+ new Stmnt(DECL, parseDeclarator(tbl, d)));
+ int t = lex.get();
+ if (t == ';')
+ return decl;
+ else if (t != ',')
+ throw new CompileError("; is missing", lex);
+ }
+ }
+
+ /* declarator : Identifier array.dimension [ '=' initializer ]
+ */
+ private Declarator parseDeclarator(SymbolTable tbl, Declarator d)
+ throws CompileError
+ {
+ if (lex.get() != Identifier || d.getType() == VOID)
+ throw new SyntaxError(lex);
+
+ String name = lex.getString();
+ Symbol symbol = new Symbol(name);
+ int dim = parseArrayDimension();
+ ASTree init = null;
+ if (lex.lookAhead() == '=') {
+ lex.get();
+ init = parseInitializer(tbl);
+ }
+
+ Declarator decl = d.make(symbol, dim, init);
+ tbl.append(name, decl);
+ return decl;
+ }
+
+ /* initializer : expression | array.initializer
+ */
+ private ASTree parseInitializer(SymbolTable tbl) throws CompileError {
+ if (lex.lookAhead() == '{')
+ return parseArrayInitializer(tbl);
+ else
+ return parseExpression(tbl);
+ }
+
+ /* array.initializer :
+ * '{' (( array.initializer | expression ) ',')* '}'
+ */
+ private ArrayInit parseArrayInitializer(SymbolTable tbl)
+ throws CompileError
+ {
+ lex.get(); // '{'
+ ASTree expr = parseExpression(tbl);
+ ArrayInit init = new ArrayInit(expr);
+ while (lex.lookAhead() == ',') {
+ lex.get();
+ expr = parseExpression(tbl);
+ ASTList.append(init, expr);
+ }
+
+ if (lex.get() != '}')
+ throw new SyntaxError(lex);
+
+ return init;
+ }
+
+ /* par.expression : '(' expression ')'
+ */
+ private ASTree parseParExpression(SymbolTable tbl) throws CompileError {
+ if (lex.get() != '(')
+ throw new SyntaxError(lex);
+
+ ASTree expr = parseExpression(tbl);
+ if (lex.get() != ')')
+ throw new SyntaxError(lex);
+
+ return expr;
+ }
+
+ /* expression : conditional.expr
+ * | conditional.expr assign.op expression (right-to-left)
+ */
+ public ASTree parseExpression(SymbolTable tbl) throws CompileError {
+ ASTree left = parseConditionalExpr(tbl);
+ if (!isAssignOp(lex.lookAhead()))
+ return left;
+
+ int t = lex.get();
+ ASTree right = parseExpression(tbl);
+ return AssignExpr.makeAssign(t, left, right);
+ }
+
+ private static boolean isAssignOp(int t) {
+ return t == '=' || t == MOD_E || t == AND_E
+ || t == MUL_E || t == PLUS_E || t == MINUS_E || t == DIV_E
+ || t == EXOR_E || t == OR_E || t == LSHIFT_E
+ || t == RSHIFT_E || t == ARSHIFT_E;
+ }
+
+ /* conditional.expr (right-to-left)
+ * : logical.or.expr [ '?' expression ':' conditional.expr ]
+ */
+ private ASTree parseConditionalExpr(SymbolTable tbl) throws CompileError {
+ ASTree cond = parseBinaryExpr(tbl);
+ if (lex.lookAhead() == '?') {
+ lex.get();
+ ASTree thenExpr = parseExpression(tbl);
+ if (lex.get() != ':')
+ throw new CompileError(": is missing", lex);
+
+ ASTree elseExpr = parseExpression(tbl);
+ return new CondExpr(cond, thenExpr, elseExpr);
+ }
+ else
+ return cond;
+ }
+
+ /* logical.or.expr 10 (operator precedence)
+ * : logical.and.expr
+ * | logical.or.expr OROR logical.and.expr left-to-right
+ *
+ * logical.and.expr 9
+ * : inclusive.or.expr
+ * | logical.and.expr ANDAND inclusive.or.expr
+ *
+ * inclusive.or.expr 8
+ * : exclusive.or.expr
+ * | inclusive.or.expr "|" exclusive.or.expr
+ *
+ * exclusive.or.expr 7
+ * : and.expr
+ * | exclusive.or.expr "^" and.expr
+ *
+ * and.expr 6
+ * : equality.expr
+ * | and.expr "&" equality.expr
+ *
+ * equality.expr 5
+ * : relational.expr
+ * | equality.expr (EQ | NEQ) relational.expr
+ *
+ * relational.expr 4
+ * : shift.expr
+ * | relational.expr (LE | GE | "<" | ">") shift.expr
+ * | relational.expr INSTANCEOF class.type ("[" "]")*
+ *
+ * shift.expr 3
+ * : additive.expr
+ * | shift.expr (LSHIFT | RSHIFT | ARSHIFT) additive.expr
+ *
+ * additive.expr 2
+ * : multiply.expr
+ * | additive.expr ("+" | "-") multiply.expr
+ *
+ * multiply.expr 1
+ * : unary.expr
+ * | multiply.expr ("*" | "/" | "%") unary.expr
+ */
+ private ASTree parseBinaryExpr(SymbolTable tbl) throws CompileError {
+ ASTree expr = parseUnaryExpr(tbl);
+ for (;;) {
+ int t = lex.lookAhead();
+ int p = getOpPrecedence(t);
+ if (p == 0)
+ return expr;
+ else
+ expr = binaryExpr2(tbl, expr, p);
+ }
+ }
+
+ private ASTree parseInstanceOf(SymbolTable tbl, ASTree expr)
+ throws CompileError
+ {
+ int t = lex.lookAhead();
+ if (isBuiltinType(t)) {
+ lex.get(); // primitive type
+ int dim = parseArrayDimension();
+ return new InstanceOfExpr(t, dim, expr);
+ }
+ else {
+ ASTList name = parseClassType(tbl);
+ int dim = parseArrayDimension();
+ return new InstanceOfExpr(name, dim, expr);
+ }
+ }
+
+ private ASTree binaryExpr2(SymbolTable tbl, ASTree expr, int prec)
+ throws CompileError
+ {
+ int t = lex.get();
+ if (t == INSTANCEOF)
+ return parseInstanceOf(tbl, expr);
+
+ ASTree expr2 = parseUnaryExpr(tbl);
+ for (;;) {
+ int t2 = lex.lookAhead();
+ int p2 = getOpPrecedence(t2);
+ if (p2 != 0 && prec > p2)
+ expr2 = binaryExpr2(tbl, expr2, p2);
+ else
+ return BinExpr.makeBin(t, expr, expr2);
+ }
+ }
+
+ // !"#$%&'( )*+,-./0 12345678 9:;<=>?
+ private static final int[] binaryOpPrecedence
+ = { 0, 0, 0, 0, 1, 6, 0, 0,
+ 0, 1, 2, 0, 2, 0, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 4, 0, 4, 0 };
+
+ private int getOpPrecedence(int c) {
+ if ('!' <= c && c <= '?')
+ return binaryOpPrecedence[c - '!'];
+ else if (c == '^')
+ return 7;
+ else if (c == '|')
+ return 8;
+ else if (c == ANDAND)
+ return 9;
+ else if (c == OROR)
+ return 10;
+ else if (c == EQ || c == NEQ)
+ return 5;
+ else if (c == LE || c == GE || c == INSTANCEOF)
+ return 4;
+ else if (c == LSHIFT || c == RSHIFT || c == ARSHIFT)
+ return 3;
+ else
+ return 0; // not a binary operator
+ }
+
+ /* unary.expr : "++"|"--" unary.expr
+ | "+"|"-" unary.expr
+ | "!"|"~" unary.expr
+ | cast.expr
+ | postfix.expr
+
+ unary.expr.not.plus.minus is a unary expression starting without
+ "+", "-", "++", or "--".
+ */
+ private ASTree parseUnaryExpr(SymbolTable tbl) throws CompileError {
+ int t;
+ switch (lex.lookAhead()) {
+ case '+' :
+ case '-' :
+ case PLUSPLUS :
+ case MINUSMINUS :
+ case '!' :
+ case '~' :
+ t = lex.get();
+ if (t == '-') {
+ int t2 = lex.lookAhead();
+ switch (t2) {
+ case LongConstant :
+ case IntConstant :
+ case CharConstant :
+ lex.get();
+ return new IntConst(-lex.getLong(), t2);
+ case DoubleConstant :
+ case FloatConstant :
+ lex.get();
+ return new DoubleConst(-lex.getDouble(), t2);
+ default :
+ break;
+ }
+ }
+
+ return Expr.make(t, parseUnaryExpr(tbl));
+ case '(' :
+ return parseCast(tbl);
+ default :
+ return parsePostfix(tbl);
+ }
+ }
+
+ /* cast.expr : "(" builtin.type ("[" "]")* ")" unary.expr
+ | "(" class.type ("[" "]")* ")" unary.expr2
+
+ unary.expr2 is a unary.expr beginning with "(", NULL, StringL,
+ Identifier, THIS, SUPER, or NEW.
+
+ Either "(int.class)" or "(String[].class)" is a not cast expression.
+ */
+ private ASTree parseCast(SymbolTable tbl) throws CompileError {
+ int t = lex.lookAhead(1);
+ if (isBuiltinType(t) && nextIsBuiltinCast()) {
+ lex.get(); // '('
+ lex.get(); // primitive type
+ int dim = parseArrayDimension();
+ if (lex.get() != ')')
+ throw new CompileError(") is missing", lex);
+
+ return new CastExpr(t, dim, parseUnaryExpr(tbl));
+ }
+ else if (t == Identifier && nextIsClassCast()) {
+ lex.get(); // '('
+ ASTList name = parseClassType(tbl);
+ int dim = parseArrayDimension();
+ if (lex.get() != ')')
+ throw new CompileError(") is missing", lex);
+
+ return new CastExpr(name, dim, parseUnaryExpr(tbl));
+ }
+ else
+ return parsePostfix(tbl);
+ }
+
+ private boolean nextIsBuiltinCast() {
+ int t;
+ int i = 2;
+ while ((t = lex.lookAhead(i++)) == '[')
+ if (lex.lookAhead(i++) != ']')
+ return false;
+
+ return lex.lookAhead(i - 1) == ')';
+ }
+
+ private boolean nextIsClassCast() {
+ int i = nextIsClassType(1);
+ if (i < 0)
+ return false;
+
+ int t = lex.lookAhead(i);
+ if (t != ')')
+ return false;
+
+ t = lex.lookAhead(i + 1);
+ return t == '(' || t == NULL || t == StringL
+ || t == Identifier || t == THIS || t == SUPER || t == NEW
+ || t == TRUE || t == FALSE || t == LongConstant
+ || t == IntConstant || t == CharConstant
+ || t == DoubleConstant || t == FloatConstant;
+ }
+
+ private int nextIsClassType(int i) {
+ int t;
+ while (lex.lookAhead(++i) == '.')
+ if (lex.lookAhead(++i) != Identifier)
+ return -1;
+
+ while ((t = lex.lookAhead(i++)) == '[')
+ if (lex.lookAhead(i++) != ']')
+ return -1;
+
+ return i - 1;
+ }
+
+ /* array.dimension : [ "[" "]" ]*
+ */
+ private int parseArrayDimension() throws CompileError {
+ int arrayDim = 0;
+ while (lex.lookAhead() == '[') {
+ ++arrayDim;
+ lex.get();
+ if (lex.get() != ']')
+ throw new CompileError("] is missing", lex);
+ }
+
+ return arrayDim;
+ }
+
+ /* class.type : Identifier ( "." Identifier )*
+ */
+ private ASTList parseClassType(SymbolTable tbl) throws CompileError {
+ ASTList list = null;
+ for (;;) {
+ if (lex.get() != Identifier)
+ throw new SyntaxError(lex);
+
+ list = ASTList.append(list, new Symbol(lex.getString()));
+ if (lex.lookAhead() == '.')
+ lex.get();
+ else
+ break;
+ }
+
+ return list;
+ }
+
+ /* postfix.expr : number.literal
+ * | primary.expr
+ * | method.expr
+ * | postfix.expr "++" | "--"
+ * | postfix.expr "[" array.size "]"
+ * | postfix.expr "." Identifier
+ * | postfix.expr ( "[" "]" )* "." CLASS
+ * | postfix.expr "#" Identifier
+ *
+ * "#" is not an operator of regular Java. It separates
+ * a class name and a member name in an expression for static member
+ * access. For example,
+ * java.lang.Integer.toString(3) in regular Java
+ * can be written like this:
+ * java.lang.Integer#toString(3) for this compiler.
+ */
+ private ASTree parsePostfix(SymbolTable tbl) throws CompileError {
+ int token = lex.lookAhead();
+ switch (token) { // see also parseUnaryExpr()
+ case LongConstant :
+ case IntConstant :
+ case CharConstant :
+ lex.get();
+ return new IntConst(lex.getLong(), token);
+ case DoubleConstant :
+ case FloatConstant :
+ lex.get();
+ return new DoubleConst(lex.getDouble(), token);
+ default :
+ break;
+ }
+
+ String str;
+ ASTree index;
+ ASTree expr = parsePrimaryExpr(tbl);
+ int t;
+ while (true) {
+ switch (lex.lookAhead()) {
+ case '(' :
+ expr = parseMethodCall(tbl, expr);
+ break;
+ case '[' :
+ if (lex.lookAhead(1) == ']') {
+ int dim = parseArrayDimension();
+ if (lex.get() != '.' || lex.get() != CLASS)
+ throw new SyntaxError(lex);
+
+ expr = parseDotClass(expr, dim);
+ }
+ else {
+ index = parseArrayIndex(tbl);
+ if (index == null)
+ throw new SyntaxError(lex);
+
+ expr = Expr.make(ARRAY, expr, index);
+ }
+ break;
+ case PLUSPLUS :
+ case MINUSMINUS :
+ t = lex.get();
+ expr = Expr.make(t, null, expr);
+ break;
+ case '.' :
+ lex.get();
+ t = lex.get();
+ if (t == CLASS) {
+ expr = parseDotClass(expr, 0);
+ }
+ else if (t == Identifier) {
+ str = lex.getString();
+ expr = Expr.make('.', expr, new Member(str));
+ }
+ else
+ throw new CompileError("missing member name", lex);
+ break;
+ case '#' :
+ lex.get();
+ t = lex.get();
+ if (t != Identifier)
+ throw new CompileError("missing static member name", lex);
+
+ str = lex.getString();
+ expr = Expr.make(MEMBER, new Symbol(toClassName(expr)),
+ new Member(str));
+ break;
+ default :
+ return expr;
+ }
+ }
+ }
+
+ /* Parse a .class expression on a class type. For example,
+ * String.class => ('.' "String" "class")
+ * String[].class => ('.' "[LString;" "class")
+ */
+ private ASTree parseDotClass(ASTree className, int dim)
+ throws CompileError
+ {
+ String cname = toClassName(className);
+ if (dim > 0) {
+ StringBuffer sbuf = new StringBuffer();
+ while (dim-- > 0)
+ sbuf.append('[');
+
+ sbuf.append('L').append(cname.replace('.', '/')).append(';');
+ cname = sbuf.toString();
+ }
+
+ return Expr.make('.', new Symbol(cname), new Member("class"));
+ }
+
+ /* Parses a .class expression on a built-in type. For example,
+ * int.class => ('#' "java.lang.Integer" "TYPE")
+ * int[].class => ('.' "[I", "class")
+ */
+ private ASTree parseDotClass(int builtinType, int dim)
+ throws CompileError
+ {
+ if (dim > 0) {
+ String cname = CodeGen.toJvmTypeName(builtinType, dim);
+ return Expr.make('.', new Symbol(cname), new Member("class"));
+ }
+ else {
+ String cname;
+ switch(builtinType) {
+ case BOOLEAN :
+ cname = "java.lang.Boolean";
+ break;
+ case BYTE :
+ cname = "java.lang.Byte";
+ break;
+ case CHAR :
+ cname = "java.lang.Character";
+ break;
+ case SHORT :
+ cname = "java.lang.Short";
+ break;
+ case INT :
+ cname = "java.lang.Integer";
+ break;
+ case LONG :
+ cname = "java.lang.Long";
+ break;
+ case FLOAT :
+ cname = "java.lang.Float";
+ break;
+ case DOUBLE :
+ cname = "java.lang.Double";
+ break;
+ case VOID :
+ cname = "java.lang.Void";
+ break;
+ default :
+ throw new CompileError("invalid builtin type: "
+ + builtinType);
+ }
+
+ return Expr.make(MEMBER, new Symbol(cname), new Member("TYPE"));
+ }
+ }
+
+ /* method.call : method.expr "(" argument.list ")"
+ * method.expr : THIS | SUPER | Identifier
+ * | postfix.expr "." Identifier
+ * | postfix.expr "#" Identifier
+ */
+ private ASTree parseMethodCall(SymbolTable tbl, ASTree expr)
+ throws CompileError
+ {
+ if (expr instanceof Keyword) {
+ int token = ((Keyword)expr).get();
+ if (token != THIS && token != SUPER)
+ throw new SyntaxError(lex);
+ }
+ else if (expr instanceof Symbol) // Identifier
+ ;
+ else if (expr instanceof Expr) {
+ int op = ((Expr)expr).getOperator();
+ if (op != '.' && op != MEMBER)
+ throw new SyntaxError(lex);
+ }
+
+ return CallExpr.makeCall(expr, parseArgumentList(tbl));
+ }
+
+ private String toClassName(ASTree name)
+ throws CompileError
+ {
+ StringBuffer sbuf = new StringBuffer();
+ toClassName(name, sbuf);
+ return sbuf.toString();
+ }
+
+ private void toClassName(ASTree name, StringBuffer sbuf)
+ throws CompileError
+ {
+ if (name instanceof Symbol) {
+ sbuf.append(((Symbol)name).get());
+ return;
+ }
+ else if (name instanceof Expr) {
+ Expr expr = (Expr)name;
+ if (expr.getOperator() == '.') {
+ toClassName(expr.oprand1(), sbuf);
+ sbuf.append('.');
+ toClassName(expr.oprand2(), sbuf);
+ return;
+ }
+ }
+
+ throw new CompileError("bad static member access", lex);
+ }
+
+ /* primary.expr : THIS | SUPER | TRUE | FALSE | NULL
+ * | StringL
+ * | Identifier
+ * | NEW new.expr
+ * | "(" expression ")"
+ * | builtin.type ( "[" "]" )* "." CLASS
+ *
+ * Identifier represents either a local variable name, a member name,
+ * or a class name.
+ */
+ private ASTree parsePrimaryExpr(SymbolTable tbl) throws CompileError {
+ int t;
+ String name;
+ Declarator decl;
+ ASTree expr;
+
+ switch (t = lex.get()) {
+ case THIS :
+ case SUPER :
+ case TRUE :
+ case FALSE :
+ case NULL :
+ return new Keyword(t);
+ case Identifier :
+ name = lex.getString();
+ decl = tbl.lookup(name);
+ if (decl == null)
+ return new Member(name); // this or static member
+ else
+ return new Variable(name, decl); // local variable
+ case StringL :
+ return new StringL(lex.getString());
+ case NEW :
+ return parseNew(tbl);
+ case '(' :
+ expr = parseExpression(tbl);
+ if (lex.get() == ')')
+ return expr;
+ else
+ throw new CompileError(") is missing", lex);
+ default :
+ if (isBuiltinType(t) || t == VOID) {
+ int dim = parseArrayDimension();
+ if (lex.get() == '.' && lex.get() == CLASS)
+ return parseDotClass(t, dim);
+ }
+
+ throw new SyntaxError(lex);
+ }
+ }
+
+ /* new.expr : class.type "(" argument.list ")"
+ * | class.type array.size [ array.initializer ]
+ * | primitive.type array.size [ array.initializer ]
+ */
+ private NewExpr parseNew(SymbolTable tbl) throws CompileError {
+ ArrayInit init = null;
+ int t = lex.lookAhead();
+ if (isBuiltinType(t)) {
+ lex.get();
+ ASTList size = parseArraySize(tbl);
+ if (lex.lookAhead() == '{')
+ init = parseArrayInitializer(tbl);
+
+ return new NewExpr(t, size, init);
+ }
+ else if (t == Identifier) {
+ ASTList name = parseClassType(tbl);
+ t = lex.lookAhead();
+ if (t == '(') {
+ ASTList args = parseArgumentList(tbl);
+ return new NewExpr(name, args);
+ }
+ else if (t == '[') {
+ ASTList size = parseArraySize(tbl);
+ if (lex.lookAhead() == '{')
+ init = parseArrayInitializer(tbl);
+
+ return NewExpr.makeObjectArray(name, size, init);
+ }
+ }
+
+ throw new SyntaxError(lex);
+ }
+
+ /* array.size : [ array.index ]*
+ */
+ private ASTList parseArraySize(SymbolTable tbl) throws CompileError {
+ ASTList list = null;
+ while (lex.lookAhead() == '[')
+ list = ASTList.append(list, parseArrayIndex(tbl));
+
+ return list;
+ }
+
+ /* array.index : "[" [ expression ] "]"
+ */
+ private ASTree parseArrayIndex(SymbolTable tbl) throws CompileError {
+ lex.get(); // '['
+ if (lex.lookAhead() == ']') {
+ lex.get();
+ return null;
+ }
+ else {
+ ASTree index = parseExpression(tbl);
+ if (lex.get() != ']')
+ throw new CompileError("] is missing", lex);
+
+ return index;
+ }
+ }
+
+ /* argument.list : "(" [ expression [ "," expression ]* ] ")"
+ */
+ private ASTList parseArgumentList(SymbolTable tbl) throws CompileError {
+ if (lex.get() != '(')
+ throw new CompileError("( is missing", lex);
+
+ ASTList list = null;
+ if (lex.lookAhead() != ')')
+ for (;;) {
+ list = ASTList.append(list, parseExpression(tbl));
+ if (lex.lookAhead() == ',')
+ lex.get();
+ else
+ break;
+ }
+
+ if (lex.get() != ')')
+ throw new CompileError(") is missing", lex);
+
+ return list;
+ }
+}
+
diff --git a/src/main/javassist/compiler/ProceedHandler.java b/src/main/javassist/compiler/ProceedHandler.java
new file mode 100644
index 0000000..fd77f0e
--- /dev/null
+++ b/src/main/javassist/compiler/ProceedHandler.java
@@ -0,0 +1,30 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.compiler;
+
+import javassist.bytecode.Bytecode;
+import javassist.compiler.ast.ASTList;
+
+/**
+ * An interface to an object for implementing $proceed().
+ *
+ * @see javassist.compiler.JvstCodeGen#setProceedHandler(ProceedHandler, String)
+ * @see javassist.compiler.JvstCodeGen#atMethodCall(Expr)
+ */
+public interface ProceedHandler {
+ void doit(JvstCodeGen gen, Bytecode b, ASTList args) throws CompileError;
+ void setReturnType(JvstTypeChecker c, ASTList args) throws CompileError;
+}
diff --git a/src/main/javassist/compiler/SymbolTable.java b/src/main/javassist/compiler/SymbolTable.java
new file mode 100644
index 0000000..8f35f36
--- /dev/null
+++ b/src/main/javassist/compiler/SymbolTable.java
@@ -0,0 +1,44 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.compiler;
+
+import java.util.HashMap;
+import javassist.compiler.ast.Declarator;
+
+public final class SymbolTable extends HashMap {
+ private SymbolTable parent;
+
+ public SymbolTable() { this(null); }
+
+ public SymbolTable(SymbolTable p) {
+ super();
+ parent = p;
+ }
+
+ public SymbolTable getParent() { return parent; }
+
+ public Declarator lookup(String name) {
+ Declarator found = (Declarator)get(name);
+ if (found == null && parent != null)
+ return parent.lookup(name);
+ else
+ return found;
+ }
+
+ public void append(String name, Declarator value) {
+ put(name, value);
+ }
+}
diff --git a/src/main/javassist/compiler/SyntaxError.java b/src/main/javassist/compiler/SyntaxError.java
new file mode 100644
index 0000000..dcbb937
--- /dev/null
+++ b/src/main/javassist/compiler/SyntaxError.java
@@ -0,0 +1,22 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.compiler;
+
+public class SyntaxError extends CompileError {
+ public SyntaxError(Lex lexer) {
+ super("syntax error near \"" + lexer.getTextAround() + "\"", lexer);
+ }
+}
diff --git a/src/main/javassist/compiler/TokenId.java b/src/main/javassist/compiler/TokenId.java
new file mode 100644
index 0000000..6a94768
--- /dev/null
+++ b/src/main/javassist/compiler/TokenId.java
@@ -0,0 +1,124 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.compiler;
+
+public interface TokenId {
+ int ABSTRACT = 300;
+ int BOOLEAN = 301;
+ int BREAK = 302;
+ int BYTE = 303;
+ int CASE = 304;
+ int CATCH = 305;
+ int CHAR = 306;
+ int CLASS = 307;
+ int CONST = 308; // reserved keyword
+ int CONTINUE = 309;
+ int DEFAULT = 310;
+ int DO = 311;
+ int DOUBLE = 312;
+ int ELSE = 313;
+ int EXTENDS = 314;
+ int FINAL = 315;
+ int FINALLY = 316;
+ int FLOAT = 317;
+ int FOR = 318;
+ int GOTO = 319; // reserved keyword
+ int IF = 320;
+ int IMPLEMENTS = 321;
+ int IMPORT = 322;
+ int INSTANCEOF = 323;
+ int INT = 324;
+ int INTERFACE = 325;
+ int LONG = 326;
+ int NATIVE = 327;
+ int NEW = 328;
+ int PACKAGE = 329;
+ int PRIVATE = 330;
+ int PROTECTED = 331;
+ int PUBLIC = 332;
+ int RETURN = 333;
+ int SHORT = 334;
+ int STATIC = 335;
+ int SUPER = 336;
+ int SWITCH = 337;
+ int SYNCHRONIZED = 338;
+ int THIS = 339;
+ int THROW = 340;
+ int THROWS = 341;
+ int TRANSIENT = 342;
+ int TRY = 343;
+ int VOID = 344;
+ int VOLATILE = 345;
+ int WHILE = 346;
+ int STRICT = 347;
+
+ int NEQ = 350; // !=
+ int MOD_E = 351; // %=
+ int AND_E = 352; // &=
+ int MUL_E = 353; // *=
+ int PLUS_E = 354; // +=
+ int MINUS_E = 355; // -=
+ int DIV_E = 356; // /=
+ int LE = 357; // <=
+ int EQ = 358; // ==
+ int GE = 359; // >=
+ int EXOR_E = 360; // ^=
+ int OR_E = 361; // |=
+ int PLUSPLUS = 362; // ++
+ int MINUSMINUS = 363; // --
+ int LSHIFT = 364; // <<
+ int LSHIFT_E = 365; // <<=
+ int RSHIFT = 366; // >>
+ int RSHIFT_E = 367; // >>=
+ int OROR = 368; // ||
+ int ANDAND = 369; // &&
+ int ARSHIFT = 370; // >>>
+ int ARSHIFT_E = 371; // >>>=
+
+ // operators from NEQ to ARSHIFT_E
+ String opNames[] = { "!=", "%=", "&=", "*=", "+=", "-=", "/=",
+ "<=", "==", ">=", "^=", "|=", "++", "--",
+ "<<", "<<=", ">>", ">>=", "||", "&&", ">>>",
+ ">>>=" };
+
+ // operators from MOD_E to ARSHIFT_E
+ int assignOps[] = { '%', '&', '*', '+', '-', '/', 0, 0, 0,
+ '^', '|', 0, 0, 0, LSHIFT, 0, RSHIFT, 0, 0, 0,
+ ARSHIFT };
+
+ int Identifier = 400;
+ int CharConstant = 401;
+ int IntConstant = 402;
+ int LongConstant = 403;
+ int FloatConstant = 404;
+ int DoubleConstant = 405;
+ int StringL = 406;
+
+ int TRUE = 410;
+ int FALSE = 411;
+ int NULL = 412;
+
+ int CALL = 'C'; // method call
+ int ARRAY = 'A'; // array access
+ int MEMBER = '#'; // static member access
+
+ int EXPR = 'E'; // expression statement
+ int LABEL = 'L'; // label statement
+ int BLOCK = 'B'; // block statement
+ int DECL = 'D'; // declaration statement
+
+ int BadToken = 500;
+}
diff --git a/src/main/javassist/compiler/TypeChecker.java b/src/main/javassist/compiler/TypeChecker.java
new file mode 100644
index 0000000..d2cb861
--- /dev/null
+++ b/src/main/javassist/compiler/TypeChecker.java
@@ -0,0 +1,1008 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.compiler;
+
+import javassist.CtClass;
+import javassist.CtField;
+import javassist.ClassPool;
+import javassist.Modifier;
+import javassist.NotFoundException;
+import javassist.compiler.ast.*;
+import javassist.bytecode.*;
+
+public class TypeChecker extends Visitor implements Opcode, TokenId {
+ static final String javaLangObject = "java.lang.Object";
+ static final String jvmJavaLangObject = "java/lang/Object";
+ static final String jvmJavaLangString = "java/lang/String";
+ static final String jvmJavaLangClass = "java/lang/Class";
+
+ /* The following fields are used by atXXX() methods
+ * for returning the type of the compiled expression.
+ */
+ protected int exprType; // VOID, NULL, CLASS, BOOLEAN, INT, ...
+ protected int arrayDim;
+ protected String className; // JVM-internal representation
+
+ protected MemberResolver resolver;
+ protected CtClass thisClass;
+ protected MethodInfo thisMethod;
+
+ public TypeChecker(CtClass cc, ClassPool cp) {
+ resolver = new MemberResolver(cp);
+ thisClass = cc;
+ thisMethod = null;
+ }
+
+ /*
+ * Converts an array of tuples of exprType, arrayDim, and className
+ * into a String object.
+ */
+ protected static String argTypesToString(int[] types, int[] dims,
+ String[] cnames) {
+ StringBuffer sbuf = new StringBuffer();
+ sbuf.append('(');
+ int n = types.length;
+ if (n > 0) {
+ int i = 0;
+ while (true) {
+ typeToString(sbuf, types[i], dims[i], cnames[i]);
+ if (++i < n)
+ sbuf.append(',');
+ else
+ break;
+ }
+ }
+
+ sbuf.append(')');
+ return sbuf.toString();
+ }
+
+ /*
+ * Converts a tuple of exprType, arrayDim, and className
+ * into a String object.
+ */
+ protected static StringBuffer typeToString(StringBuffer sbuf,
+ int type, int dim, String cname) {
+ String s;
+ if (type == CLASS)
+ s = MemberResolver.jvmToJavaName(cname);
+ else if (type == NULL)
+ s = "Object";
+ else
+ try {
+ s = MemberResolver.getTypeName(type);
+ }
+ catch (CompileError e) {
+ s = "?";
+ }
+
+ sbuf.append(s);
+ while (dim-- > 0)
+ sbuf.append("[]");
+
+ return sbuf;
+ }
+
+ /**
+ * Records the currently compiled method.
+ */
+ public void setThisMethod(MethodInfo m) {
+ thisMethod = m;
+ }
+
+ protected static void fatal() throws CompileError {
+ throw new CompileError("fatal");
+ }
+
+ /**
+ * Returns the JVM-internal representation of this class name.
+ */
+ protected String getThisName() {
+ return MemberResolver.javaToJvmName(thisClass.getName());
+ }
+
+ /**
+ * Returns the JVM-internal representation of this super class name.
+ */
+ protected String getSuperName() throws CompileError {
+ return MemberResolver.javaToJvmName(
+ MemberResolver.getSuperclass(thisClass).getName());
+ }
+
+ /* Converts a class name into a JVM-internal representation.
+ *
+ * It may also expand a simple class name to java.lang.*.
+ * For example, this converts Object into java/lang/Object.
+ */
+ protected String resolveClassName(ASTList name) throws CompileError {
+ return resolver.resolveClassName(name);
+ }
+
+ /* Expands a simple class name to java.lang.*.
+ * For example, this converts Object into java/lang/Object.
+ */
+ protected String resolveClassName(String jvmName) throws CompileError {
+ return resolver.resolveJvmClassName(jvmName);
+ }
+
+ public void atNewExpr(NewExpr expr) throws CompileError {
+ if (expr.isArray())
+ atNewArrayExpr(expr);
+ else {
+ CtClass clazz = resolver.lookupClassByName(expr.getClassName());
+ String cname = clazz.getName();
+ ASTList args = expr.getArguments();
+ atMethodCallCore(clazz, MethodInfo.nameInit, args);
+ exprType = CLASS;
+ arrayDim = 0;
+ className = MemberResolver.javaToJvmName(cname);
+ }
+ }
+
+ public void atNewArrayExpr(NewExpr expr) throws CompileError {
+ int type = expr.getArrayType();
+ ASTList size = expr.getArraySize();
+ ASTList classname = expr.getClassName();
+ ASTree init = expr.getInitializer();
+ if (init != null)
+ init.accept(this);
+
+ if (size.length() > 1)
+ atMultiNewArray(type, classname, size);
+ else {
+ ASTree sizeExpr = size.head();
+ if (sizeExpr != null)
+ sizeExpr.accept(this);
+
+ exprType = type;
+ arrayDim = 1;
+ if (type == CLASS)
+ className = resolveClassName(classname);
+ else
+ className = null;
+ }
+ }
+
+ public void atArrayInit(ArrayInit init) throws CompileError {
+ ASTList list = init;
+ while (list != null) {
+ ASTree h = list.head();
+ list = list.tail();
+ if (h != null)
+ h.accept(this);
+ }
+ }
+
+ protected void atMultiNewArray(int type, ASTList classname, ASTList size)
+ throws CompileError
+ {
+ int count, dim;
+ dim = size.length();
+ for (count = 0; size != null; size = size.tail()) {
+ ASTree s = size.head();
+ if (s == null)
+ break; // int[][][] a = new int[3][4][];
+
+ ++count;
+ s.accept(this);
+ }
+
+ exprType = type;
+ arrayDim = dim;
+ if (type == CLASS)
+ className = resolveClassName(classname);
+ else
+ className = null;
+ }
+
+ public void atAssignExpr(AssignExpr expr) throws CompileError {
+ // =, %=, &=, *=, /=, +=, -=, ^=, |=, <<=, >>=, >>>=
+ int op = expr.getOperator();
+ ASTree left = expr.oprand1();
+ ASTree right = expr.oprand2();
+ if (left instanceof Variable)
+ atVariableAssign(expr, op, (Variable)left,
+ ((Variable)left).getDeclarator(),
+ right);
+ else {
+ if (left instanceof Expr) {
+ Expr e = (Expr)left;
+ if (e.getOperator() == ARRAY) {
+ atArrayAssign(expr, op, (Expr)left, right);
+ return;
+ }
+ }
+
+ atFieldAssign(expr, op, left, right);
+ }
+ }
+
+ /* op is either =, %=, &=, *=, /=, +=, -=, ^=, |=, <<=, >>=, or >>>=.
+ *
+ * expr and var can be null.
+ */
+ private void atVariableAssign(Expr expr, int op, Variable var,
+ Declarator d, ASTree right)
+ throws CompileError
+ {
+ int varType = d.getType();
+ int varArray = d.getArrayDim();
+ String varClass = d.getClassName();
+
+ if (op != '=')
+ atVariable(var);
+
+ right.accept(this);
+ exprType = varType;
+ arrayDim = varArray;
+ className = varClass;
+ }
+
+ private void atArrayAssign(Expr expr, int op, Expr array,
+ ASTree right) throws CompileError
+ {
+ atArrayRead(array.oprand1(), array.oprand2());
+ int aType = exprType;
+ int aDim = arrayDim;
+ String cname = className;
+ right.accept(this);
+ exprType = aType;
+ arrayDim = aDim;
+ className = cname;
+ }
+
+ protected void atFieldAssign(Expr expr, int op, ASTree left, ASTree right)
+ throws CompileError
+ {
+ CtField f = fieldAccess(left);
+ atFieldRead(f);
+ int fType = exprType;
+ int fDim = arrayDim;
+ String cname = className;
+ right.accept(this);
+ exprType = fType;
+ arrayDim = fDim;
+ className = cname;
+ }
+
+ public void atCondExpr(CondExpr expr) throws CompileError {
+ booleanExpr(expr.condExpr());
+ expr.thenExpr().accept(this);
+ int type1 = exprType;
+ int dim1 = arrayDim;
+ String cname1 = className;
+ expr.elseExpr().accept(this);
+
+ if (dim1 == 0 && dim1 == arrayDim)
+ if (CodeGen.rightIsStrong(type1, exprType))
+ expr.setThen(new CastExpr(exprType, 0, expr.thenExpr()));
+ else if (CodeGen.rightIsStrong(exprType, type1)) {
+ expr.setElse(new CastExpr(type1, 0, expr.elseExpr()));
+ exprType = type1;
+ }
+ }
+
+ /*
+ * If atBinExpr() substitutes a new expression for the original
+ * binary-operator expression, it changes the operator name to '+'
+ * (if the original is not '+') and sets the new expression to the
+ * left-hand-side expression and null to the right-hand-side expression.
+ */
+ public void atBinExpr(BinExpr expr) throws CompileError {
+ int token = expr.getOperator();
+ int k = CodeGen.lookupBinOp(token);
+ if (k >= 0) {
+ /* arithmetic operators: +, -, *, /, %, |, ^, &, <<, >>, >>>
+ */
+ if (token == '+') {
+ Expr e = atPlusExpr(expr);
+ if (e != null) {
+ /* String concatenation has been translated into
+ * an expression using StringBuffer.
+ */
+ e = CallExpr.makeCall(Expr.make('.', e,
+ new Member("toString")), null);
+ expr.setOprand1(e);
+ expr.setOprand2(null); // <---- look at this!
+ className = jvmJavaLangString;
+ }
+ }
+ else {
+ ASTree left = expr.oprand1();
+ ASTree right = expr.oprand2();
+ left.accept(this);
+ int type1 = exprType;
+ right.accept(this);
+ if (!isConstant(expr, token, left, right))
+ computeBinExprType(expr, token, type1);
+ }
+ }
+ else {
+ /* equation: &&, ||, ==, !=, <=, >=, <, >
+ */
+ booleanExpr(expr);
+ }
+ }
+
+ /* EXPR must be a + expression.
+ * atPlusExpr() returns non-null if the given expression is string
+ * concatenation. The returned value is "new StringBuffer().append..".
+ */
+ private Expr atPlusExpr(BinExpr expr) throws CompileError {
+ ASTree left = expr.oprand1();
+ ASTree right = expr.oprand2();
+ if (right == null) {
+ // this expression has been already type-checked.
+ // see atBinExpr() above.
+ left.accept(this);
+ return null;
+ }
+
+ if (isPlusExpr(left)) {
+ Expr newExpr = atPlusExpr((BinExpr)left);
+ if (newExpr != null) {
+ right.accept(this);
+ exprType = CLASS;
+ arrayDim = 0;
+ className = "java/lang/StringBuffer";
+ return makeAppendCall(newExpr, right);
+ }
+ }
+ else
+ left.accept(this);
+
+ int type1 = exprType;
+ int dim1 = arrayDim;
+ String cname = className;
+ right.accept(this);
+
+ if (isConstant(expr, '+', left, right))
+ return null;
+
+ if ((type1 == CLASS && dim1 == 0 && jvmJavaLangString.equals(cname))
+ || (exprType == CLASS && arrayDim == 0
+ && jvmJavaLangString.equals(className))) {
+ ASTList sbufClass = ASTList.make(new Symbol("java"),
+ new Symbol("lang"), new Symbol("StringBuffer"));
+ ASTree e = new NewExpr(sbufClass, null);
+ exprType = CLASS;
+ arrayDim = 0;
+ className = "java/lang/StringBuffer";
+ return makeAppendCall(makeAppendCall(e, left), right);
+ }
+ else {
+ computeBinExprType(expr, '+', type1);
+ return null;
+ }
+ }
+
+ private boolean isConstant(BinExpr expr, int op, ASTree left,
+ ASTree right) throws CompileError
+ {
+ left = stripPlusExpr(left);
+ right = stripPlusExpr(right);
+ ASTree newExpr = null;
+ if (left instanceof StringL && right instanceof StringL && op == '+')
+ newExpr = new StringL(((StringL)left).get()
+ + ((StringL)right).get());
+ else if (left instanceof IntConst)
+ newExpr = ((IntConst)left).compute(op, right);
+ else if (left instanceof DoubleConst)
+ newExpr = ((DoubleConst)left).compute(op, right);
+
+ if (newExpr == null)
+ return false; // not a constant expression
+ else {
+ expr.setOperator('+');
+ expr.setOprand1(newExpr);
+ expr.setOprand2(null);
+ newExpr.accept(this); // for setting exprType, arrayDim, ...
+ return true;
+ }
+ }
+
+ /* CodeGen.atSwitchStmnt() also calls stripPlusExpr().
+ */
+ static ASTree stripPlusExpr(ASTree expr) {
+ if (expr instanceof BinExpr) {
+ BinExpr e = (BinExpr)expr;
+ if (e.getOperator() == '+' && e.oprand2() == null)
+ return e.getLeft();
+ }
+ else if (expr instanceof Expr) { // note: BinExpr extends Expr.
+ Expr e = (Expr)expr;
+ int op = e.getOperator();
+ if (op == MEMBER) {
+ ASTree cexpr = getConstantFieldValue((Member)e.oprand2());
+ if (cexpr != null)
+ return cexpr;
+ }
+ else if (op == '+' && e.getRight() == null)
+ return e.getLeft();
+ }
+ else if (expr instanceof Member) {
+ ASTree cexpr = getConstantFieldValue((Member)expr);
+ if (cexpr != null)
+ return cexpr;
+ }
+
+ return expr;
+ }
+
+ /**
+ * If MEM is a static final field, this method returns a constant
+ * expression representing the value of that field.
+ */
+ private static ASTree getConstantFieldValue(Member mem) {
+ return getConstantFieldValue(mem.getField());
+ }
+
+ public static ASTree getConstantFieldValue(CtField f) {
+ if (f == null)
+ return null;
+
+ Object value = f.getConstantValue();
+ if (value == null)
+ return null;
+
+ if (value instanceof String)
+ return new StringL((String)value);
+ else if (value instanceof Double || value instanceof Float) {
+ int token = (value instanceof Double)
+ ? DoubleConstant : FloatConstant;
+ return new DoubleConst(((Number)value).doubleValue(), token);
+ }
+ else if (value instanceof Number) {
+ int token = (value instanceof Long) ? LongConstant : IntConstant;
+ return new IntConst(((Number)value).longValue(), token);
+ }
+ else if (value instanceof Boolean)
+ return new Keyword(((Boolean)value).booleanValue()
+ ? TokenId.TRUE : TokenId.FALSE);
+ else
+ return null;
+ }
+
+ private static boolean isPlusExpr(ASTree expr) {
+ if (expr instanceof BinExpr) {
+ BinExpr bexpr = (BinExpr)expr;
+ int token = bexpr.getOperator();
+ return token == '+';
+ }
+
+ return false;
+ }
+
+ private static Expr makeAppendCall(ASTree target, ASTree arg) {
+ return CallExpr.makeCall(Expr.make('.', target, new Member("append")),
+ new ASTList(arg));
+ }
+
+ private void computeBinExprType(BinExpr expr, int token, int type1)
+ throws CompileError
+ {
+ // arrayDim should be 0.
+ int type2 = exprType;
+ if (token == LSHIFT || token == RSHIFT || token == ARSHIFT)
+ exprType = type1;
+ else
+ insertCast(expr, type1, type2);
+
+ if (CodeGen.isP_INT(exprType))
+ exprType = INT; // type1 may be BYTE, ...
+ }
+
+ private void booleanExpr(ASTree expr)
+ throws CompileError
+ {
+ int op = CodeGen.getCompOperator(expr);
+ if (op == EQ) { // ==, !=, ...
+ BinExpr bexpr = (BinExpr)expr;
+ bexpr.oprand1().accept(this);
+ int type1 = exprType;
+ int dim1 = arrayDim;
+ bexpr.oprand2().accept(this);
+ if (dim1 == 0 && arrayDim == 0)
+ insertCast(bexpr, type1, exprType);
+ }
+ else if (op == '!')
+ ((Expr)expr).oprand1().accept(this);
+ else if (op == ANDAND || op == OROR) {
+ BinExpr bexpr = (BinExpr)expr;
+ bexpr.oprand1().accept(this);
+ bexpr.oprand2().accept(this);
+ }
+ else // others
+ expr.accept(this);
+
+ exprType = BOOLEAN;
+ arrayDim = 0;
+ }
+
+ private void insertCast(BinExpr expr, int type1, int type2)
+ throws CompileError
+ {
+ if (CodeGen.rightIsStrong(type1, type2))
+ expr.setLeft(new CastExpr(type2, 0, expr.oprand1()));
+ else
+ exprType = type1;
+ }
+
+ public void atCastExpr(CastExpr expr) throws CompileError {
+ String cname = resolveClassName(expr.getClassName());
+ expr.getOprand().accept(this);
+ exprType = expr.getType();
+ arrayDim = expr.getArrayDim();
+ className = cname;
+ }
+
+ public void atInstanceOfExpr(InstanceOfExpr expr) throws CompileError {
+ expr.getOprand().accept(this);
+ exprType = BOOLEAN;
+ arrayDim = 0;
+ }
+
+ public void atExpr(Expr expr) throws CompileError {
+ // array access, member access,
+ // (unary) +, (unary) -, ++, --, !, ~
+
+ int token = expr.getOperator();
+ ASTree oprand = expr.oprand1();
+ if (token == '.') {
+ String member = ((Symbol)expr.oprand2()).get();
+ if (member.equals("length"))
+ atArrayLength(expr);
+ else if (member.equals("class"))
+ atClassObject(expr); // .class
+ else
+ atFieldRead(expr);
+ }
+ else if (token == MEMBER) { // field read
+ String member = ((Symbol)expr.oprand2()).get();
+ if (member.equals("class"))
+ atClassObject(expr); // .class
+ else
+ atFieldRead(expr);
+ }
+ else if (token == ARRAY)
+ atArrayRead(oprand, expr.oprand2());
+ else if (token == PLUSPLUS || token == MINUSMINUS)
+ atPlusPlus(token, oprand, expr);
+ else if (token == '!')
+ booleanExpr(expr);
+ else if (token == CALL) // method call
+ fatal();
+ else {
+ oprand.accept(this);
+ if (!isConstant(expr, token, oprand))
+ if (token == '-' || token == '~')
+ if (CodeGen.isP_INT(exprType))
+ exprType = INT; // type may be BYTE, ...
+ }
+ }
+
+ private boolean isConstant(Expr expr, int op, ASTree oprand) {
+ oprand = stripPlusExpr(oprand);
+ if (oprand instanceof IntConst) {
+ IntConst c = (IntConst)oprand;
+ long v = c.get();
+ if (op == '-')
+ v = -v;
+ else if (op == '~')
+ v = ~v;
+ else
+ return false;
+
+ c.set(v);
+ }
+ else if (oprand instanceof DoubleConst) {
+ DoubleConst c = (DoubleConst)oprand;
+ if (op == '-')
+ c.set(-c.get());
+ else
+ return false;
+ }
+ else
+ return false;
+
+ expr.setOperator('+');
+ return true;
+ }
+
+ public void atCallExpr(CallExpr expr) throws CompileError {
+ String mname = null;
+ CtClass targetClass = null;
+ ASTree method = expr.oprand1();
+ ASTList args = (ASTList)expr.oprand2();
+
+ if (method instanceof Member) {
+ mname = ((Member)method).get();
+ targetClass = thisClass;
+ }
+ else if (method instanceof Keyword) { // constructor
+ mname = MethodInfo.nameInit; // <init>
+ if (((Keyword)method).get() == SUPER)
+ targetClass = MemberResolver.getSuperclass(thisClass);
+ else
+ targetClass = thisClass;
+ }
+ else if (method instanceof Expr) {
+ Expr e = (Expr)method;
+ mname = ((Symbol)e.oprand2()).get();
+ int op = e.getOperator();
+ if (op == MEMBER) // static method
+ targetClass
+ = resolver.lookupClass(((Symbol)e.oprand1()).get(),
+ false);
+ else if (op == '.') {
+ ASTree target = e.oprand1();
+ try {
+ target.accept(this);
+ }
+ catch (NoFieldException nfe) {
+ if (nfe.getExpr() != target)
+ throw nfe;
+
+ // it should be a static method.
+ exprType = CLASS;
+ arrayDim = 0;
+ className = nfe.getField(); // JVM-internal
+ e.setOperator(MEMBER);
+ e.setOprand1(new Symbol(MemberResolver.jvmToJavaName(
+ className)));
+ }
+
+ if (arrayDim > 0)
+ targetClass = resolver.lookupClass(javaLangObject, true);
+ else if (exprType == CLASS /* && arrayDim == 0 */)
+ targetClass = resolver.lookupClassByJvmName(className);
+ else
+ badMethod();
+ }
+ else
+ badMethod();
+ }
+ else
+ fatal();
+
+ MemberResolver.Method minfo
+ = atMethodCallCore(targetClass, mname, args);
+ expr.setMethod(minfo);
+ }
+
+ private static void badMethod() throws CompileError {
+ throw new CompileError("bad method");
+ }
+
+ /**
+ * @return a pair of the class declaring the invoked method
+ * and the MethodInfo of that method. Never null.
+ */
+ public MemberResolver.Method atMethodCallCore(CtClass targetClass,
+ String mname, ASTList args)
+ throws CompileError
+ {
+ int nargs = getMethodArgsLength(args);
+ int[] types = new int[nargs];
+ int[] dims = new int[nargs];
+ String[] cnames = new String[nargs];
+ atMethodArgs(args, types, dims, cnames);
+
+ MemberResolver.Method found
+ = resolver.lookupMethod(targetClass, thisClass, thisMethod,
+ mname, types, dims, cnames);
+ if (found == null) {
+ String clazz = targetClass.getName();
+ String signature = argTypesToString(types, dims, cnames);
+ String msg;
+ if (mname.equals(MethodInfo.nameInit))
+ msg = "cannot find constructor " + clazz + signature;
+ else
+ msg = mname + signature + " not found in " + clazz;
+
+ throw new CompileError(msg);
+ }
+
+ String desc = found.info.getDescriptor();
+ setReturnType(desc);
+ return found;
+ }
+
+ public int getMethodArgsLength(ASTList args) {
+ return ASTList.length(args);
+ }
+
+ public void atMethodArgs(ASTList args, int[] types, int[] dims,
+ String[] cnames) throws CompileError {
+ int i = 0;
+ while (args != null) {
+ ASTree a = args.head();
+ a.accept(this);
+ types[i] = exprType;
+ dims[i] = arrayDim;
+ cnames[i] = className;
+ ++i;
+ args = args.tail();
+ }
+ }
+
+ void setReturnType(String desc) throws CompileError {
+ int i = desc.indexOf(')');
+ if (i < 0)
+ badMethod();
+
+ char c = desc.charAt(++i);
+ int dim = 0;
+ while (c == '[') {
+ ++dim;
+ c = desc.charAt(++i);
+ }
+
+ arrayDim = dim;
+ if (c == 'L') {
+ int j = desc.indexOf(';', i + 1);
+ if (j < 0)
+ badMethod();
+
+ exprType = CLASS;
+ className = desc.substring(i + 1, j);
+ }
+ else {
+ exprType = MemberResolver.descToType(c);
+ className = null;
+ }
+ }
+
+ private void atFieldRead(ASTree expr) throws CompileError {
+ atFieldRead(fieldAccess(expr));
+ }
+
+ private void atFieldRead(CtField f) throws CompileError {
+ FieldInfo finfo = f.getFieldInfo2();
+ String type = finfo.getDescriptor();
+
+ int i = 0;
+ int dim = 0;
+ char c = type.charAt(i);
+ while (c == '[') {
+ ++dim;
+ c = type.charAt(++i);
+ }
+
+ arrayDim = dim;
+ exprType = MemberResolver.descToType(c);
+
+ if (c == 'L')
+ className = type.substring(i + 1, type.indexOf(';', i + 1));
+ else
+ className = null;
+ }
+
+ /* if EXPR is to access a static field, fieldAccess() translates EXPR
+ * into an expression using '#' (MEMBER). For example, it translates
+ * java.lang.Integer.TYPE into java.lang.Integer#TYPE. This translation
+ * speeds up type resolution by MemberCodeGen.
+ */
+ protected CtField fieldAccess(ASTree expr) throws CompileError {
+ if (expr instanceof Member) {
+ Member mem = (Member)expr;
+ String name = mem.get();
+ try {
+ CtField f = thisClass.getField(name);
+ if (Modifier.isStatic(f.getModifiers()))
+ mem.setField(f);
+
+ return f;
+ }
+ catch (NotFoundException e) {
+ // EXPR might be part of a static member access?
+ throw new NoFieldException(name, expr);
+ }
+ }
+ else if (expr instanceof Expr) {
+ Expr e = (Expr)expr;
+ int op = e.getOperator();
+ if (op == MEMBER) {
+ Member mem = (Member)e.oprand2();
+ CtField f
+ = resolver.lookupField(((Symbol)e.oprand1()).get(), mem);
+ mem.setField(f);
+ return f;
+ }
+ else if (op == '.') {
+ try {
+ e.oprand1().accept(this);
+ }
+ catch (NoFieldException nfe) {
+ if (nfe.getExpr() != e.oprand1())
+ throw nfe;
+
+ /* EXPR should be a static field.
+ * If EXPR might be part of a qualified class name,
+ * lookupFieldByJvmName2() throws NoFieldException.
+ */
+ return fieldAccess2(e, nfe.getField());
+ }
+
+ CompileError err = null;
+ try {
+ if (exprType == CLASS && arrayDim == 0)
+ return resolver.lookupFieldByJvmName(className,
+ (Symbol)e.oprand2());
+ }
+ catch (CompileError ce) {
+ err = ce;
+ }
+
+ /* If a filed name is the same name as a package's,
+ * a static member of a class in that package is not
+ * visible. For example,
+ *
+ * class Foo {
+ * int javassist;
+ * }
+ *
+ * It is impossible to add the following method:
+ *
+ * String m() { return javassist.CtClass.intType.toString(); }
+ *
+ * because javassist is a field name. However, this is
+ * often inconvenient, this compiler allows it. The following
+ * code is for that.
+ */
+ ASTree oprnd1 = e.oprand1();
+ if (oprnd1 instanceof Symbol)
+ return fieldAccess2(e, ((Symbol)oprnd1).get());
+
+ if (err != null)
+ throw err;
+ }
+ }
+
+ throw new CompileError("bad filed access");
+ }
+
+ private CtField fieldAccess2(Expr e, String jvmClassName) throws CompileError {
+ Member fname = (Member)e.oprand2();
+ CtField f = resolver.lookupFieldByJvmName2(jvmClassName, fname, e);
+ e.setOperator(MEMBER);
+ e.setOprand1(new Symbol(MemberResolver.jvmToJavaName(jvmClassName)));
+ fname.setField(f);
+ return f;
+ }
+
+ public void atClassObject(Expr expr) throws CompileError {
+ exprType = CLASS;
+ arrayDim = 0;
+ className =jvmJavaLangClass;
+ }
+
+ public void atArrayLength(Expr expr) throws CompileError {
+ expr.oprand1().accept(this);
+ exprType = INT;
+ arrayDim = 0;
+ }
+
+ public void atArrayRead(ASTree array, ASTree index)
+ throws CompileError
+ {
+ array.accept(this);
+ int type = exprType;
+ int dim = arrayDim;
+ String cname = className;
+ index.accept(this);
+ exprType = type;
+ arrayDim = dim - 1;
+ className = cname;
+ }
+
+ private void atPlusPlus(int token, ASTree oprand, Expr expr)
+ throws CompileError
+ {
+ boolean isPost = oprand == null; // ++i or i++?
+ if (isPost)
+ oprand = expr.oprand2();
+
+ if (oprand instanceof Variable) {
+ Declarator d = ((Variable)oprand).getDeclarator();
+ exprType = d.getType();
+ arrayDim = d.getArrayDim();
+ }
+ else {
+ if (oprand instanceof Expr) {
+ Expr e = (Expr)oprand;
+ if (e.getOperator() == ARRAY) {
+ atArrayRead(e.oprand1(), e.oprand2());
+ // arrayDim should be 0.
+ int t = exprType;
+ if (t == INT || t == BYTE || t == CHAR || t == SHORT)
+ exprType = INT;
+
+ return;
+ }
+ }
+
+ atFieldPlusPlus(oprand);
+ }
+ }
+
+ protected void atFieldPlusPlus(ASTree oprand) throws CompileError
+ {
+ CtField f = fieldAccess(oprand);
+ atFieldRead(f);
+ int t = exprType;
+ if (t == INT || t == BYTE || t == CHAR || t == SHORT)
+ exprType = INT;
+ }
+
+ public void atMember(Member mem) throws CompileError {
+ atFieldRead(mem);
+ }
+
+ public void atVariable(Variable v) throws CompileError {
+ Declarator d = v.getDeclarator();
+ exprType = d.getType();
+ arrayDim = d.getArrayDim();
+ className = d.getClassName();
+ }
+
+ public void atKeyword(Keyword k) throws CompileError {
+ arrayDim = 0;
+ int token = k.get();
+ switch (token) {
+ case TRUE :
+ case FALSE :
+ exprType = BOOLEAN;
+ break;
+ case NULL :
+ exprType = NULL;
+ break;
+ case THIS :
+ case SUPER :
+ exprType = CLASS;
+ if (token == THIS)
+ className = getThisName();
+ else
+ className = getSuperName();
+ break;
+ default :
+ fatal();
+ }
+ }
+
+ public void atStringL(StringL s) throws CompileError {
+ exprType = CLASS;
+ arrayDim = 0;
+ className = jvmJavaLangString;
+ }
+
+ public void atIntConst(IntConst i) throws CompileError {
+ arrayDim = 0;
+ int type = i.getType();
+ if (type == IntConstant || type == CharConstant)
+ exprType = (type == IntConstant ? INT : CHAR);
+ else
+ exprType = LONG;
+ }
+
+ public void atDoubleConst(DoubleConst d) throws CompileError {
+ arrayDim = 0;
+ if (d.getType() == DoubleConstant)
+ exprType = DOUBLE;
+ else
+ exprType = FLOAT;
+ }
+}
diff --git a/src/main/javassist/compiler/ast/ASTList.java b/src/main/javassist/compiler/ast/ASTList.java
new file mode 100644
index 0000000..b486668
--- /dev/null
+++ b/src/main/javassist/compiler/ast/ASTList.java
@@ -0,0 +1,159 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.compiler.ast;
+
+import javassist.compiler.CompileError;
+
+/**
+ * A linked list.
+ * The right subtree must be an ASTList object or null.
+ */
+public class ASTList extends ASTree {
+ private ASTree left;
+ private ASTList right;
+
+ public ASTList(ASTree _head, ASTList _tail) {
+ left = _head;
+ right = _tail;
+ }
+
+ public ASTList(ASTree _head) {
+ left = _head;
+ right = null;
+ }
+
+ public static ASTList make(ASTree e1, ASTree e2, ASTree e3) {
+ return new ASTList(e1, new ASTList(e2, new ASTList(e3)));
+ }
+
+ public ASTree getLeft() { return left; }
+
+ public ASTree getRight() { return right; }
+
+ public void setLeft(ASTree _left) { left = _left; }
+
+ public void setRight(ASTree _right) {
+ right = (ASTList)_right;
+ }
+
+ /**
+ * Returns the car part of the list.
+ */
+ public ASTree head() { return left; }
+
+ public void setHead(ASTree _head) {
+ left = _head;
+ }
+
+ /**
+ * Returns the cdr part of the list.
+ */
+ public ASTList tail() { return right; }
+
+ public void setTail(ASTList _tail) {
+ right = _tail;
+ }
+
+ public void accept(Visitor v) throws CompileError { v.atASTList(this); }
+
+ public String toString() {
+ StringBuffer sbuf = new StringBuffer();
+ sbuf.append("(<");
+ sbuf.append(getTag());
+ sbuf.append('>');
+ ASTList list = this;
+ while (list != null) {
+ sbuf.append(' ');
+ ASTree a = list.left;
+ sbuf.append(a == null ? "<null>" : a.toString());
+ list = list.right;
+ }
+
+ sbuf.append(')');
+ return sbuf.toString();
+ }
+
+ /**
+ * Returns the number of the elements in this list.
+ */
+ public int length() {
+ return length(this);
+ }
+
+ public static int length(ASTList list) {
+ if (list == null)
+ return 0;
+
+ int n = 0;
+ while (list != null) {
+ list = list.right;
+ ++n;
+ }
+
+ return n;
+ }
+
+ /**
+ * Returns a sub list of the list. The sub list begins with the
+ * n-th element of the list.
+ *
+ * @param nth zero or more than zero.
+ */
+ public ASTList sublist(int nth) {
+ ASTList list = this;
+ while (nth-- > 0)
+ list = list.right;
+
+ return list;
+ }
+
+ /**
+ * Substitutes <code>newObj</code> for <code>oldObj</code> in the
+ * list.
+ */
+ public boolean subst(ASTree newObj, ASTree oldObj) {
+ for (ASTList list = this; list != null; list = list.right)
+ if (list.left == oldObj) {
+ list.left = newObj;
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Appends an object to a list.
+ */
+ public static ASTList append(ASTList a, ASTree b) {
+ return concat(a, new ASTList(b));
+ }
+
+ /**
+ * Concatenates two lists.
+ */
+ public static ASTList concat(ASTList a, ASTList b) {
+ if (a == null)
+ return b;
+ else {
+ ASTList list = a;
+ while (list.right != null)
+ list = list.right;
+
+ list.right = b;
+ return a;
+ }
+ }
+}
diff --git a/src/main/javassist/compiler/ast/ASTree.java b/src/main/javassist/compiler/ast/ASTree.java
new file mode 100644
index 0000000..a0c4330
--- /dev/null
+++ b/src/main/javassist/compiler/ast/ASTree.java
@@ -0,0 +1,58 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.compiler.ast;
+
+import java.io.Serializable;
+import javassist.compiler.CompileError;
+
+/**
+ * Abstract Syntax Tree. An ASTree object represents a node of
+ * a binary tree. If the node is a leaf node, both <code>getLeft()</code>
+ * and <code>getRight()</code> returns null.
+ */
+public abstract class ASTree implements Serializable {
+ public ASTree getLeft() { return null; }
+
+ public ASTree getRight() { return null; }
+
+ public void setLeft(ASTree _left) {}
+
+ public void setRight(ASTree _right) {}
+
+ /**
+ * Is a method for the visitor pattern. It calls
+ * <code>atXXX()</code> on the given visitor, where
+ * <code>XXX</code> is the class name of the node object.
+ */
+ public abstract void accept(Visitor v) throws CompileError;
+
+ public String toString() {
+ StringBuffer sbuf = new StringBuffer();
+ sbuf.append('<');
+ sbuf.append(getTag());
+ sbuf.append('>');
+ return sbuf.toString();
+ }
+
+ /**
+ * Returns the type of this node. This method is used by
+ * <code>toString()</code>.
+ */
+ protected String getTag() {
+ String name = getClass().getName();
+ return name.substring(name.lastIndexOf('.') + 1);
+ }
+}
diff --git a/src/main/javassist/compiler/ast/ArrayInit.java b/src/main/javassist/compiler/ast/ArrayInit.java
new file mode 100644
index 0000000..a9c6c2f
--- /dev/null
+++ b/src/main/javassist/compiler/ast/ArrayInit.java
@@ -0,0 +1,31 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.compiler.ast;
+
+import javassist.compiler.CompileError;
+
+/**
+ * Array initializer such as <code>{ 1, 2, 3 }</code>.
+ */
+public class ArrayInit extends ASTList {
+ public ArrayInit(ASTree firstElement) {
+ super(firstElement);
+ }
+
+ public void accept(Visitor v) throws CompileError { v.atArrayInit(this); }
+
+ public String getTag() { return "array"; }
+}
diff --git a/src/main/javassist/compiler/ast/AssignExpr.java b/src/main/javassist/compiler/ast/AssignExpr.java
new file mode 100644
index 0000000..9e61447
--- /dev/null
+++ b/src/main/javassist/compiler/ast/AssignExpr.java
@@ -0,0 +1,40 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.compiler.ast;
+
+import javassist.compiler.CompileError;
+
+/**
+ * Assignment expression.
+ */
+public class AssignExpr extends Expr {
+ /* operator must be either of:
+ * =, %=, &=, *=, +=, -=, /=, ^=, |=, <<=, >>=, >>>=
+ */
+
+ private AssignExpr(int op, ASTree _head, ASTList _tail) {
+ super(op, _head, _tail);
+ }
+
+ public static AssignExpr makeAssign(int op, ASTree oprand1,
+ ASTree oprand2) {
+ return new AssignExpr(op, oprand1, new ASTList(oprand2));
+ }
+
+ public void accept(Visitor v) throws CompileError {
+ v.atAssignExpr(this);
+ }
+}
diff --git a/src/main/javassist/compiler/ast/BinExpr.java b/src/main/javassist/compiler/ast/BinExpr.java
new file mode 100644
index 0000000..1fb5a6a
--- /dev/null
+++ b/src/main/javassist/compiler/ast/BinExpr.java
@@ -0,0 +1,41 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.compiler.ast;
+
+import javassist.compiler.CompileError;
+
+/**
+ * Binary expression.
+ *
+ * <p>If the operator is +, the right node might be null.
+ * See TypeChecker.atBinExpr().
+ */
+public class BinExpr extends Expr {
+ /* operator must be either of:
+ * ||, &&, |, ^, &, ==, !=, <=, >=, <, >,
+ * <<, >>, >>>, +, -, *, /, %
+ */
+
+ private BinExpr(int op, ASTree _head, ASTList _tail) {
+ super(op, _head, _tail);
+ }
+
+ public static BinExpr makeBin(int op, ASTree oprand1, ASTree oprand2) {
+ return new BinExpr(op, oprand1, new ASTList(oprand2));
+ }
+
+ public void accept(Visitor v) throws CompileError { v.atBinExpr(this); }
+}
diff --git a/src/main/javassist/compiler/ast/CallExpr.java b/src/main/javassist/compiler/ast/CallExpr.java
new file mode 100644
index 0000000..544ce24
--- /dev/null
+++ b/src/main/javassist/compiler/ast/CallExpr.java
@@ -0,0 +1,46 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.compiler.ast;
+
+import javassist.compiler.CompileError;
+import javassist.compiler.TokenId;
+import javassist.compiler.MemberResolver;
+
+/**
+ * Method call expression.
+ */
+public class CallExpr extends Expr {
+ private MemberResolver.Method method; // cached result of lookupMethod()
+
+ private CallExpr(ASTree _head, ASTList _tail) {
+ super(TokenId.CALL, _head, _tail);
+ method = null;
+ }
+
+ public void setMethod(MemberResolver.Method m) {
+ method = m;
+ }
+
+ public MemberResolver.Method getMethod() {
+ return method;
+ }
+
+ public static CallExpr makeCall(ASTree target, ASTree args) {
+ return new CallExpr(target, new ASTList(args));
+ }
+
+ public void accept(Visitor v) throws CompileError { v.atCallExpr(this); }
+}
diff --git a/src/main/javassist/compiler/ast/CastExpr.java b/src/main/javassist/compiler/ast/CastExpr.java
new file mode 100644
index 0000000..3fb5640
--- /dev/null
+++ b/src/main/javassist/compiler/ast/CastExpr.java
@@ -0,0 +1,55 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.compiler.ast;
+
+import javassist.compiler.TokenId;
+import javassist.compiler.CompileError;
+
+/**
+ * Cast expression.
+ */
+public class CastExpr extends ASTList implements TokenId {
+ protected int castType;
+ protected int arrayDim;
+
+ public CastExpr(ASTList className, int dim, ASTree expr) {
+ super(className, new ASTList(expr));
+ castType = CLASS;
+ arrayDim = dim;
+ }
+
+ public CastExpr(int type, int dim, ASTree expr) {
+ super(null, new ASTList(expr));
+ castType = type;
+ arrayDim = dim;
+ }
+
+ /* Returns CLASS, BOOLEAN, INT, or ...
+ */
+ public int getType() { return castType; }
+
+ public int getArrayDim() { return arrayDim; }
+
+ public ASTList getClassName() { return (ASTList)getLeft(); }
+
+ public ASTree getOprand() { return getRight().getLeft(); }
+
+ public void setOprand(ASTree t) { getRight().setLeft(t); }
+
+ public String getTag() { return "cast:" + castType + ":" + arrayDim; }
+
+ public void accept(Visitor v) throws CompileError { v.atCastExpr(this); }
+}
diff --git a/src/main/javassist/compiler/ast/CondExpr.java b/src/main/javassist/compiler/ast/CondExpr.java
new file mode 100644
index 0000000..2fb9603
--- /dev/null
+++ b/src/main/javassist/compiler/ast/CondExpr.java
@@ -0,0 +1,43 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.compiler.ast;
+
+import javassist.compiler.CompileError;
+
+/**
+ * Conditional expression.
+ */
+public class CondExpr extends ASTList {
+ public CondExpr(ASTree cond, ASTree thenp, ASTree elsep) {
+ super(cond, new ASTList(thenp, new ASTList(elsep)));
+ }
+
+ public ASTree condExpr() { return head(); }
+
+ public void setCond(ASTree t) { setHead(t); }
+
+ public ASTree thenExpr() { return tail().head(); }
+
+ public void setThen(ASTree t) { tail().setHead(t); }
+
+ public ASTree elseExpr() { return tail().tail().head(); }
+
+ public void setElse(ASTree t) { tail().tail().setHead(t); }
+
+ public String getTag() { return "?:"; }
+
+ public void accept(Visitor v) throws CompileError { v.atCondExpr(this); }
+}
diff --git a/src/main/javassist/compiler/ast/Declarator.java b/src/main/javassist/compiler/ast/Declarator.java
new file mode 100644
index 0000000..d3a43f0
--- /dev/null
+++ b/src/main/javassist/compiler/ast/Declarator.java
@@ -0,0 +1,127 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.compiler.ast;
+
+import javassist.compiler.TokenId;
+import javassist.compiler.CompileError;
+
+/**
+ * Variable declarator.
+ */
+public class Declarator extends ASTList implements TokenId {
+ protected int varType;
+ protected int arrayDim;
+ protected int localVar;
+ protected String qualifiedClass; // JVM-internal representation
+
+ public Declarator(int type, int dim) {
+ super(null);
+ varType = type;
+ arrayDim = dim;
+ localVar = -1;
+ qualifiedClass = null;
+ }
+
+ public Declarator(ASTList className, int dim) {
+ super(null);
+ varType = CLASS;
+ arrayDim = dim;
+ localVar = -1;
+ qualifiedClass = astToClassName(className, '/');
+ }
+
+ /* For declaring a pre-defined? local variable.
+ */
+ public Declarator(int type, String jvmClassName, int dim,
+ int var, Symbol sym) {
+ super(null);
+ varType = type;
+ arrayDim = dim;
+ localVar = var;
+ qualifiedClass = jvmClassName;
+ setLeft(sym);
+ append(this, null); // initializer
+ }
+
+ public Declarator make(Symbol sym, int dim, ASTree init) {
+ Declarator d = new Declarator(this.varType, this.arrayDim + dim);
+ d.qualifiedClass = this.qualifiedClass;
+ d.setLeft(sym);
+ append(d, init);
+ return d;
+ }
+
+ /* Returns CLASS, BOOLEAN, BYTE, CHAR, SHORT, INT, LONG, FLOAT,
+ * or DOUBLE (or VOID)
+ */
+ public int getType() { return varType; }
+
+ public int getArrayDim() { return arrayDim; }
+
+ public void addArrayDim(int d) { arrayDim += d; }
+
+ public String getClassName() { return qualifiedClass; }
+
+ public void setClassName(String s) { qualifiedClass = s; }
+
+ public Symbol getVariable() { return (Symbol)getLeft(); }
+
+ public void setVariable(Symbol sym) { setLeft(sym); }
+
+ public ASTree getInitializer() {
+ ASTList t = tail();
+ if (t != null)
+ return t.head();
+ else
+ return null;
+ }
+
+ public void setLocalVar(int n) { localVar = n; }
+
+ public int getLocalVar() { return localVar; }
+
+ public String getTag() { return "decl"; }
+
+ public void accept(Visitor v) throws CompileError {
+ v.atDeclarator(this);
+ }
+
+ public static String astToClassName(ASTList name, char sep) {
+ if (name == null)
+ return null;
+
+ StringBuffer sbuf = new StringBuffer();
+ astToClassName(sbuf, name, sep);
+ return sbuf.toString();
+ }
+
+ private static void astToClassName(StringBuffer sbuf, ASTList name,
+ char sep) {
+ for (;;) {
+ ASTree h = name.head();
+ if (h instanceof Symbol)
+ sbuf.append(((Symbol)h).get());
+ else if (h instanceof ASTList)
+ astToClassName(sbuf, (ASTList)h, sep);
+
+ name = name.tail();
+ if (name == null)
+ break;
+
+ sbuf.append(sep);
+ }
+ }
+}
diff --git a/src/main/javassist/compiler/ast/DoubleConst.java b/src/main/javassist/compiler/ast/DoubleConst.java
new file mode 100644
index 0000000..5276c2f
--- /dev/null
+++ b/src/main/javassist/compiler/ast/DoubleConst.java
@@ -0,0 +1,94 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.compiler.ast;
+
+import javassist.compiler.CompileError;
+import javassist.compiler.TokenId;
+
+/**
+ * Double constant.
+ */
+public class DoubleConst extends ASTree {
+ protected double value;
+ protected int type;
+
+ public DoubleConst(double v, int tokenId) { value = v; type = tokenId; }
+
+ public double get() { return value; }
+
+ public void set(double v) { value = v; }
+
+ /* Returns DoubleConstant or FloatConstant
+ */
+ public int getType() { return type; }
+
+ public String toString() { return Double.toString(value); }
+
+ public void accept(Visitor v) throws CompileError {
+ v.atDoubleConst(this);
+ }
+
+ public ASTree compute(int op, ASTree right) {
+ if (right instanceof IntConst)
+ return compute0(op, (IntConst)right);
+ else if (right instanceof DoubleConst)
+ return compute0(op, (DoubleConst)right);
+ else
+ return null;
+ }
+
+ private DoubleConst compute0(int op, DoubleConst right) {
+ int newType;
+ if (this.type == TokenId.DoubleConstant
+ || right.type == TokenId.DoubleConstant)
+ newType = TokenId.DoubleConstant;
+ else
+ newType = TokenId.FloatConstant;
+
+ return compute(op, this.value, right.value, newType);
+ }
+
+ private DoubleConst compute0(int op, IntConst right) {
+ return compute(op, this.value, (double)right.value, this.type);
+ }
+
+ private static DoubleConst compute(int op, double value1, double value2,
+ int newType)
+ {
+ double newValue;
+ switch (op) {
+ case '+' :
+ newValue = value1 + value2;
+ break;
+ case '-' :
+ newValue = value1 - value2;
+ break;
+ case '*' :
+ newValue = value1 * value2;
+ break;
+ case '/' :
+ newValue = value1 / value2;
+ break;
+ case '%' :
+ newValue = value1 % value2;
+ break;
+ default :
+ return null;
+ }
+
+ return new DoubleConst(newValue, newType);
+ }
+}
diff --git a/src/main/javassist/compiler/ast/Expr.java b/src/main/javassist/compiler/ast/Expr.java
new file mode 100644
index 0000000..ec11200
--- /dev/null
+++ b/src/main/javassist/compiler/ast/Expr.java
@@ -0,0 +1,84 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.compiler.ast;
+
+import javassist.compiler.TokenId;
+import javassist.compiler.CompileError;
+
+/**
+ * Expression.
+ */
+public class Expr extends ASTList implements TokenId {
+ /* operator must be either of:
+ * (unary) +, (unary) -, ++, --, !, ~,
+ * ARRAY, . (dot), MEMBER (static member access).
+ * Otherwise, the object should be an instance of a subclass.
+ */
+
+ protected int operatorId;
+
+ Expr(int op, ASTree _head, ASTList _tail) {
+ super(_head, _tail);
+ operatorId = op;
+ }
+
+ Expr(int op, ASTree _head) {
+ super(_head);
+ operatorId = op;
+ }
+
+ public static Expr make(int op, ASTree oprand1, ASTree oprand2) {
+ return new Expr(op, oprand1, new ASTList(oprand2));
+ }
+
+ public static Expr make(int op, ASTree oprand1) {
+ return new Expr(op, oprand1);
+ }
+
+ public int getOperator() { return operatorId; }
+
+ public void setOperator(int op) { operatorId = op; }
+
+ public ASTree oprand1() { return getLeft(); }
+
+ public void setOprand1(ASTree expr) {
+ setLeft(expr);
+ }
+
+ public ASTree oprand2() { return getRight().getLeft(); }
+
+ public void setOprand2(ASTree expr) {
+ getRight().setLeft(expr);
+ }
+
+ public void accept(Visitor v) throws CompileError { v.atExpr(this); }
+
+ public String getName() {
+ int id = operatorId;
+ if (id < 128)
+ return String.valueOf((char)id);
+ else if (NEQ <= id && id <= ARSHIFT_E)
+ return opNames[id - NEQ];
+ else if (id == INSTANCEOF)
+ return "instanceof";
+ else
+ return String.valueOf(id);
+ }
+
+ protected String getTag() {
+ return "op:" + getName();
+ }
+}
diff --git a/src/main/javassist/compiler/ast/FieldDecl.java b/src/main/javassist/compiler/ast/FieldDecl.java
new file mode 100644
index 0000000..ce32b87
--- /dev/null
+++ b/src/main/javassist/compiler/ast/FieldDecl.java
@@ -0,0 +1,34 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.compiler.ast;
+
+import javassist.compiler.CompileError;
+
+public class FieldDecl extends ASTList {
+ public FieldDecl(ASTree _head, ASTList _tail) {
+ super(_head, _tail);
+ }
+
+ public ASTList getModifiers() { return (ASTList)getLeft(); }
+
+ public Declarator getDeclarator() { return (Declarator)tail().head(); }
+
+ public ASTree getInit() { return (ASTree)sublist(2).head(); }
+
+ public void accept(Visitor v) throws CompileError {
+ v.atFieldDecl(this);
+ }
+}
diff --git a/src/main/javassist/compiler/ast/InstanceOfExpr.java b/src/main/javassist/compiler/ast/InstanceOfExpr.java
new file mode 100644
index 0000000..9813ce8
--- /dev/null
+++ b/src/main/javassist/compiler/ast/InstanceOfExpr.java
@@ -0,0 +1,39 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.compiler.ast;
+
+import javassist.compiler.CompileError;
+
+/**
+ * Instanceof expression.
+ */
+public class InstanceOfExpr extends CastExpr {
+ public InstanceOfExpr(ASTList className, int dim, ASTree expr) {
+ super(className, dim, expr);
+ }
+
+ public InstanceOfExpr(int type, int dim, ASTree expr) {
+ super(type, dim, expr);
+ }
+
+ public String getTag() {
+ return "instanceof:" + castType + ":" + arrayDim;
+ }
+
+ public void accept(Visitor v) throws CompileError {
+ v.atInstanceOfExpr(this);
+ }
+}
diff --git a/src/main/javassist/compiler/ast/IntConst.java b/src/main/javassist/compiler/ast/IntConst.java
new file mode 100644
index 0000000..703a0bf
--- /dev/null
+++ b/src/main/javassist/compiler/ast/IntConst.java
@@ -0,0 +1,138 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.compiler.ast;
+
+import javassist.compiler.CompileError;
+import javassist.compiler.TokenId;
+
+/**
+ * Integer constant.
+ */
+public class IntConst extends ASTree {
+ protected long value;
+ protected int type;
+
+ public IntConst(long v, int tokenId) { value = v; type = tokenId; }
+
+ public long get() { return value; }
+
+ public void set(long v) { value = v; }
+
+ /* Returns IntConstant, CharConstant, or LongConstant.
+ */
+ public int getType() { return type; }
+
+ public String toString() { return Long.toString(value); }
+
+ public void accept(Visitor v) throws CompileError {
+ v.atIntConst(this);
+ }
+
+ public ASTree compute(int op, ASTree right) {
+ if (right instanceof IntConst)
+ return compute0(op, (IntConst)right);
+ else if (right instanceof DoubleConst)
+ return compute0(op, (DoubleConst)right);
+ else
+ return null;
+ }
+
+ private IntConst compute0(int op, IntConst right) {
+ int type1 = this.type;
+ int type2 = right.type;
+ int newType;
+ if (type1 == TokenId.LongConstant || type2 == TokenId.LongConstant)
+ newType = TokenId.LongConstant;
+ else if (type1 == TokenId.CharConstant
+ && type2 == TokenId.CharConstant)
+ newType = TokenId.CharConstant;
+ else
+ newType = TokenId.IntConstant;
+
+ long value1 = this.value;
+ long value2 = right.value;
+ long newValue;
+ switch (op) {
+ case '+' :
+ newValue = value1 + value2;
+ break;
+ case '-' :
+ newValue = value1 - value2;
+ break;
+ case '*' :
+ newValue = value1 * value2;
+ break;
+ case '/' :
+ newValue = value1 / value2;
+ break;
+ case '%' :
+ newValue = value1 % value2;
+ break;
+ case '|' :
+ newValue = value1 | value2;
+ break;
+ case '^' :
+ newValue = value1 ^ value2;
+ break;
+ case '&' :
+ newValue = value1 & value2;
+ break;
+ case TokenId.LSHIFT :
+ newValue = value << (int)value2;
+ newType = type1;
+ break;
+ case TokenId.RSHIFT :
+ newValue = value >> (int)value2;
+ newType = type1;
+ break;
+ case TokenId.ARSHIFT :
+ newValue = value >>> (int)value2;
+ newType = type1;
+ break;
+ default :
+ return null;
+ }
+
+ return new IntConst(newValue, newType);
+ }
+
+ private DoubleConst compute0(int op, DoubleConst right) {
+ double value1 = (double)this.value;
+ double value2 = right.value;
+ double newValue;
+ switch (op) {
+ case '+' :
+ newValue = value1 + value2;
+ break;
+ case '-' :
+ newValue = value1 - value2;
+ break;
+ case '*' :
+ newValue = value1 * value2;
+ break;
+ case '/' :
+ newValue = value1 / value2;
+ break;
+ case '%' :
+ newValue = value1 % value2;
+ break;
+ default :
+ return null;
+ }
+
+ return new DoubleConst(newValue, right.type);
+ }
+}
diff --git a/src/main/javassist/compiler/ast/Keyword.java b/src/main/javassist/compiler/ast/Keyword.java
new file mode 100644
index 0000000..a1a9ebf
--- /dev/null
+++ b/src/main/javassist/compiler/ast/Keyword.java
@@ -0,0 +1,35 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.compiler.ast;
+
+import javassist.compiler.CompileError;
+
+/**
+ * Keyword.
+ */
+public class Keyword extends ASTree {
+ protected int tokenId;
+
+ public Keyword(int token) {
+ tokenId = token;
+ }
+
+ public int get() { return tokenId; }
+
+ public String toString() { return "id:" + tokenId; }
+
+ public void accept(Visitor v) throws CompileError { v.atKeyword(this); }
+}
diff --git a/src/main/javassist/compiler/ast/Member.java b/src/main/javassist/compiler/ast/Member.java
new file mode 100644
index 0000000..404f2b8
--- /dev/null
+++ b/src/main/javassist/compiler/ast/Member.java
@@ -0,0 +1,39 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.compiler.ast;
+
+import javassist.compiler.CompileError;
+import javassist.CtField;
+
+/**
+ * Member name.
+ */
+public class Member extends Symbol {
+ // cache maintained by fieldAccess() in TypeChecker.
+ // this is used to obtain the value of a static final field.
+ private CtField field;
+
+ public Member(String name) {
+ super(name);
+ field = null;
+ }
+
+ public void setField(CtField f) { field = f; }
+
+ public CtField getField() { return field; }
+
+ public void accept(Visitor v) throws CompileError { v.atMember(this); }
+}
diff --git a/src/main/javassist/compiler/ast/MethodDecl.java b/src/main/javassist/compiler/ast/MethodDecl.java
new file mode 100644
index 0000000..8d0661c
--- /dev/null
+++ b/src/main/javassist/compiler/ast/MethodDecl.java
@@ -0,0 +1,45 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.compiler.ast;
+
+import javassist.compiler.CompileError;
+
+public class MethodDecl extends ASTList {
+ public static final String initName = "<init>";
+
+ public MethodDecl(ASTree _head, ASTList _tail) {
+ super(_head, _tail);
+ }
+
+ public boolean isConstructor() {
+ Symbol sym = getReturn().getVariable();
+ return sym != null && initName.equals(sym.get());
+ }
+
+ public ASTList getModifiers() { return (ASTList)getLeft(); }
+
+ public Declarator getReturn() { return (Declarator)tail().head(); }
+
+ public ASTList getParams() { return (ASTList)sublist(2).head(); }
+
+ public ASTList getThrows() { return (ASTList)sublist(3).head(); }
+
+ public Stmnt getBody() { return (Stmnt)sublist(4).head(); }
+
+ public void accept(Visitor v) throws CompileError {
+ v.atMethodDecl(this);
+ }
+}
diff --git a/src/main/javassist/compiler/ast/NewExpr.java b/src/main/javassist/compiler/ast/NewExpr.java
new file mode 100644
index 0000000..db3cb51
--- /dev/null
+++ b/src/main/javassist/compiler/ast/NewExpr.java
@@ -0,0 +1,77 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.compiler.ast;
+
+import javassist.compiler.TokenId;
+import javassist.compiler.CompileError;
+
+/**
+ * New Expression.
+ */
+public class NewExpr extends ASTList implements TokenId {
+ protected boolean newArray;
+ protected int arrayType;
+
+ public NewExpr(ASTList className, ASTList args) {
+ super(className, new ASTList(args));
+ newArray = false;
+ arrayType = CLASS;
+ }
+
+ public NewExpr(int type, ASTList arraySize, ArrayInit init) {
+ super(null, new ASTList(arraySize));
+ newArray = true;
+ arrayType = type;
+ if (init != null)
+ append(this, init);
+ }
+
+ public static NewExpr makeObjectArray(ASTList className,
+ ASTList arraySize, ArrayInit init) {
+ NewExpr e = new NewExpr(className, arraySize);
+ e.newArray = true;
+ if (init != null)
+ append(e, init);
+
+ return e;
+ }
+
+ public boolean isArray() { return newArray; }
+
+ /* TokenId.CLASS, TokenId.INT, ...
+ */
+ public int getArrayType() { return arrayType; }
+
+ public ASTList getClassName() { return (ASTList)getLeft(); }
+
+ public ASTList getArguments() { return (ASTList)getRight().getLeft(); }
+
+ public ASTList getArraySize() { return getArguments(); }
+
+ public ArrayInit getInitializer() {
+ ASTree t = getRight().getRight();
+ if (t == null)
+ return null;
+ else
+ return (ArrayInit)t.getLeft();
+ }
+
+ public void accept(Visitor v) throws CompileError { v.atNewExpr(this); }
+
+ protected String getTag() {
+ return newArray ? "new[]" : "new";
+ }
+}
diff --git a/src/main/javassist/compiler/ast/Pair.java b/src/main/javassist/compiler/ast/Pair.java
new file mode 100644
index 0000000..1831a35
--- /dev/null
+++ b/src/main/javassist/compiler/ast/Pair.java
@@ -0,0 +1,51 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.compiler.ast;
+
+import javassist.compiler.CompileError;
+
+/**
+ * A node of a a binary tree. This class provides concrete methods
+ * overriding abstract methods in ASTree.
+ */
+public class Pair extends ASTree {
+ protected ASTree left, right;
+
+ public Pair(ASTree _left, ASTree _right) {
+ left = _left;
+ right = _right;
+ }
+
+ public void accept(Visitor v) throws CompileError { v.atPair(this); }
+
+ public String toString() {
+ StringBuffer sbuf = new StringBuffer();
+ sbuf.append("(<Pair> ");
+ sbuf.append(left == null ? "<null>" : left.toString());
+ sbuf.append(" . ");
+ sbuf.append(right == null ? "<null>" : right.toString());
+ sbuf.append(')');
+ return sbuf.toString();
+ }
+
+ public ASTree getLeft() { return left; }
+
+ public ASTree getRight() { return right; }
+
+ public void setLeft(ASTree _left) { left = _left; }
+
+ public void setRight(ASTree _right) { right = _right; }
+}
diff --git a/src/main/javassist/compiler/ast/Stmnt.java b/src/main/javassist/compiler/ast/Stmnt.java
new file mode 100644
index 0000000..d5c6d62
--- /dev/null
+++ b/src/main/javassist/compiler/ast/Stmnt.java
@@ -0,0 +1,59 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.compiler.ast;
+
+import javassist.compiler.TokenId;
+import javassist.compiler.CompileError;
+
+/**
+ * Statement.
+ */
+public class Stmnt extends ASTList implements TokenId {
+ protected int operatorId;
+
+ public Stmnt(int op, ASTree _head, ASTList _tail) {
+ super(_head, _tail);
+ operatorId = op;
+ }
+
+ public Stmnt(int op, ASTree _head) {
+ super(_head);
+ operatorId = op;
+ }
+
+ public Stmnt(int op) {
+ this(op, null);
+ }
+
+ public static Stmnt make(int op, ASTree oprand1, ASTree oprand2) {
+ return new Stmnt(op, oprand1, new ASTList(oprand2));
+ }
+
+ public static Stmnt make(int op, ASTree op1, ASTree op2, ASTree op3) {
+ return new Stmnt(op, op1, new ASTList(op2, new ASTList(op3)));
+ }
+
+ public void accept(Visitor v) throws CompileError { v.atStmnt(this); }
+
+ public int getOperator() { return operatorId; }
+
+ protected String getTag() {
+ if (operatorId < 128)
+ return "stmnt:" + (char)operatorId;
+ else
+ return "stmnt:" + operatorId;
+ }
+}
diff --git a/src/main/javassist/compiler/ast/StringL.java b/src/main/javassist/compiler/ast/StringL.java
new file mode 100644
index 0000000..b9e5fe8
--- /dev/null
+++ b/src/main/javassist/compiler/ast/StringL.java
@@ -0,0 +1,35 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.compiler.ast;
+
+import javassist.compiler.CompileError;
+
+/**
+ * String literal.
+ */
+public class StringL extends ASTree {
+ protected String text;
+
+ public StringL(String t) {
+ text = t;
+ }
+
+ public String get() { return text; }
+
+ public String toString() { return "\"" + text + "\""; }
+
+ public void accept(Visitor v) throws CompileError { v.atStringL(this); }
+}
diff --git a/src/main/javassist/compiler/ast/Symbol.java b/src/main/javassist/compiler/ast/Symbol.java
new file mode 100644
index 0000000..0b26e75
--- /dev/null
+++ b/src/main/javassist/compiler/ast/Symbol.java
@@ -0,0 +1,35 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.compiler.ast;
+
+import javassist.compiler.CompileError;
+
+/**
+ * Identifier.
+ */
+public class Symbol extends ASTree {
+ protected String identifier;
+
+ public Symbol(String sym) {
+ identifier = sym;
+ }
+
+ public String get() { return identifier; }
+
+ public String toString() { return identifier; }
+
+ public void accept(Visitor v) throws CompileError { v.atSymbol(this); }
+}
diff --git a/src/main/javassist/compiler/ast/Variable.java b/src/main/javassist/compiler/ast/Variable.java
new file mode 100644
index 0000000..353690e
--- /dev/null
+++ b/src/main/javassist/compiler/ast/Variable.java
@@ -0,0 +1,38 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.compiler.ast;
+
+import javassist.compiler.CompileError;
+
+/**
+ * Variable.
+ */
+public class Variable extends Symbol {
+ protected Declarator declarator;
+
+ public Variable(String sym, Declarator d) {
+ super(sym);
+ declarator = d;
+ }
+
+ public Declarator getDeclarator() { return declarator; }
+
+ public String toString() {
+ return identifier + ":" + declarator.getType();
+ }
+
+ public void accept(Visitor v) throws CompileError { v.atVariable(this); }
+}
diff --git a/src/main/javassist/compiler/ast/Visitor.java b/src/main/javassist/compiler/ast/Visitor.java
new file mode 100644
index 0000000..b4cbf34
--- /dev/null
+++ b/src/main/javassist/compiler/ast/Visitor.java
@@ -0,0 +1,51 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.compiler.ast;
+
+import javassist.compiler.CompileError;
+
+/**
+ * The visitor pattern.
+ *
+ * @see ast.ASTree#accept(Visitor)
+ */
+public class Visitor {
+ public void atASTList(ASTList n) throws CompileError {}
+ public void atPair(Pair n) throws CompileError {}
+
+ public void atFieldDecl(FieldDecl n) throws CompileError {}
+ public void atMethodDecl(MethodDecl n) throws CompileError {}
+ public void atStmnt(Stmnt n) throws CompileError {}
+ public void atDeclarator(Declarator n) throws CompileError {}
+
+ public void atAssignExpr(AssignExpr n) throws CompileError {}
+ public void atCondExpr(CondExpr n) throws CompileError {}
+ public void atBinExpr(BinExpr n) throws CompileError {}
+ public void atExpr(Expr n) throws CompileError {}
+ public void atCallExpr(CallExpr n) throws CompileError {}
+ public void atCastExpr(CastExpr n) throws CompileError {}
+ public void atInstanceOfExpr(InstanceOfExpr n) throws CompileError {}
+ public void atNewExpr(NewExpr n) throws CompileError {}
+
+ public void atSymbol(Symbol n) throws CompileError {}
+ public void atMember(Member n) throws CompileError {}
+ public void atVariable(Variable n) throws CompileError {}
+ public void atKeyword(Keyword n) throws CompileError {}
+ public void atStringL(StringL n) throws CompileError {}
+ public void atIntConst(IntConst n) throws CompileError {}
+ public void atDoubleConst(DoubleConst n) throws CompileError {}
+ public void atArrayInit(ArrayInit n) throws CompileError {}
+}
diff --git a/src/main/javassist/convert/TransformAccessArrayField.java b/src/main/javassist/convert/TransformAccessArrayField.java
new file mode 100644
index 0000000..f70148f
--- /dev/null
+++ b/src/main/javassist/convert/TransformAccessArrayField.java
@@ -0,0 +1,269 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+package javassist.convert;
+
+import javassist.CannotCompileException;
+import javassist.ClassPool;
+import javassist.CtClass;
+import javassist.NotFoundException;
+import javassist.CodeConverter.ArrayAccessReplacementMethodNames;
+import javassist.bytecode.BadBytecode;
+import javassist.bytecode.CodeIterator;
+import javassist.bytecode.ConstPool;
+import javassist.bytecode.Descriptor;
+import javassist.bytecode.MethodInfo;
+import javassist.bytecode.analysis.Analyzer;
+import javassist.bytecode.analysis.Frame;
+
+/**
+ * A transformer which replaces array access with static method invocations.
+ *
+ * @author <a href="kabir.khan@jboss.com">Kabir Khan</a>
+ * @author Jason T. Greene
+ * @version $Revision: 1.8 $
+ */
+public final class TransformAccessArrayField extends Transformer {
+ private final String methodClassname;
+ private final ArrayAccessReplacementMethodNames names;
+ private Frame[] frames;
+ private int offset;
+
+ public TransformAccessArrayField(Transformer next, String methodClassname,
+ ArrayAccessReplacementMethodNames names) throws NotFoundException {
+ super(next);
+ this.methodClassname = methodClassname;
+ this.names = names;
+
+ }
+
+ public void initialize(ConstPool cp, CtClass clazz, MethodInfo minfo) throws CannotCompileException {
+ /*
+ * This transformer must be isolated from other transformers, since some
+ * of them affect the local variable and stack maximums without updating
+ * the code attribute to reflect the changes. This screws up the
+ * data-flow analyzer, since it relies on consistent code state. Even
+ * if the attribute values were updated correctly, we would have to
+ * detect it, and redo analysis, which is not cheap. Instead, we are
+ * better off doing all changes in initialize() before everyone else has
+ * a chance to muck things up.
+ */
+ CodeIterator iterator = minfo.getCodeAttribute().iterator();
+ while (iterator.hasNext()) {
+ try {
+ int pos = iterator.next();
+ int c = iterator.byteAt(pos);
+
+ if (c == AALOAD)
+ initFrames(clazz, minfo);
+
+ if (c == AALOAD || c == BALOAD || c == CALOAD || c == DALOAD
+ || c == FALOAD || c == IALOAD || c == LALOAD
+ || c == SALOAD) {
+ pos = replace(cp, iterator, pos, c, getLoadReplacementSignature(c));
+ } else if (c == AASTORE || c == BASTORE || c == CASTORE
+ || c == DASTORE || c == FASTORE || c == IASTORE
+ || c == LASTORE || c == SASTORE) {
+ pos = replace(cp, iterator, pos, c, getStoreReplacementSignature(c));
+ }
+
+ } catch (Exception e) {
+ throw new CannotCompileException(e);
+ }
+ }
+ }
+
+ public void clean() {
+ frames = null;
+ offset = -1;
+ }
+
+ public int transform(CtClass tclazz, int pos, CodeIterator iterator,
+ ConstPool cp) throws BadBytecode {
+ // Do nothing, see above comment
+ return pos;
+ }
+
+ private Frame getFrame(int pos) throws BadBytecode {
+ return frames[pos - offset]; // Adjust pos
+ }
+
+ private void initFrames(CtClass clazz, MethodInfo minfo) throws BadBytecode {
+ if (frames == null) {
+ frames = ((new Analyzer())).analyze(clazz, minfo);
+ offset = 0; // start tracking changes
+ }
+ }
+
+ private int updatePos(int pos, int increment) {
+ if (offset > -1)
+ offset += increment;
+
+ return pos + increment;
+ }
+
+ private String getTopType(int pos) throws BadBytecode {
+ Frame frame = getFrame(pos);
+ if (frame == null)
+ return null;
+
+ CtClass clazz = frame.peek().getCtClass();
+ return clazz != null ? Descriptor.toJvmName(clazz) : null;
+ }
+
+ private int replace(ConstPool cp, CodeIterator iterator, int pos,
+ int opcode, String signature) throws BadBytecode {
+ String castType = null;
+ String methodName = getMethodName(opcode);
+ if (methodName != null) {
+ // See if the object must be cast
+ if (opcode == AALOAD) {
+ castType = getTopType(iterator.lookAhead());
+ // Do not replace an AALOAD instruction that we do not have a type for
+ // This happens when the state is guaranteed to be null (Type.UNINIT)
+ // So we don't really care about this case.
+ if (castType == null)
+ return pos;
+ if ("java/lang/Object".equals(castType))
+ castType = null;
+ }
+
+ // The gap may include extra padding
+ // Write a nop in case the padding pushes the instruction forward
+ iterator.writeByte(NOP, pos);
+ CodeIterator.Gap gap
+ = iterator.insertGapAt(pos, castType != null ? 5 : 2, false);
+ pos = gap.position;
+ int mi = cp.addClassInfo(methodClassname);
+ int methodref = cp.addMethodrefInfo(mi, methodName, signature);
+ iterator.writeByte(INVOKESTATIC, pos);
+ iterator.write16bit(methodref, pos + 1);
+
+ if (castType != null) {
+ int index = cp.addClassInfo(castType);
+ iterator.writeByte(CHECKCAST, pos + 3);
+ iterator.write16bit(index, pos + 4);
+ }
+
+ pos = updatePos(pos, gap.length);
+ }
+
+ return pos;
+ }
+
+ private String getMethodName(int opcode) {
+ String methodName = null;
+ switch (opcode) {
+ case AALOAD:
+ methodName = names.objectRead();
+ break;
+ case BALOAD:
+ methodName = names.byteOrBooleanRead();
+ break;
+ case CALOAD:
+ methodName = names.charRead();
+ break;
+ case DALOAD:
+ methodName = names.doubleRead();
+ break;
+ case FALOAD:
+ methodName = names.floatRead();
+ break;
+ case IALOAD:
+ methodName = names.intRead();
+ break;
+ case SALOAD:
+ methodName = names.shortRead();
+ break;
+ case LALOAD:
+ methodName = names.longRead();
+ break;
+ case AASTORE:
+ methodName = names.objectWrite();
+ break;
+ case BASTORE:
+ methodName = names.byteOrBooleanWrite();
+ break;
+ case CASTORE:
+ methodName = names.charWrite();
+ break;
+ case DASTORE:
+ methodName = names.doubleWrite();
+ break;
+ case FASTORE:
+ methodName = names.floatWrite();
+ break;
+ case IASTORE:
+ methodName = names.intWrite();
+ break;
+ case SASTORE:
+ methodName = names.shortWrite();
+ break;
+ case LASTORE:
+ methodName = names.longWrite();
+ break;
+ }
+
+ if (methodName.equals(""))
+ methodName = null;
+
+ return methodName;
+ }
+
+ private String getLoadReplacementSignature(int opcode) throws BadBytecode {
+ switch (opcode) {
+ case AALOAD:
+ return "(Ljava/lang/Object;I)Ljava/lang/Object;";
+ case BALOAD:
+ return "(Ljava/lang/Object;I)B";
+ case CALOAD:
+ return "(Ljava/lang/Object;I)C";
+ case DALOAD:
+ return "(Ljava/lang/Object;I)D";
+ case FALOAD:
+ return "(Ljava/lang/Object;I)F";
+ case IALOAD:
+ return "(Ljava/lang/Object;I)I";
+ case SALOAD:
+ return "(Ljava/lang/Object;I)S";
+ case LALOAD:
+ return "(Ljava/lang/Object;I)J";
+ }
+
+ throw new BadBytecode(opcode);
+ }
+
+ private String getStoreReplacementSignature(int opcode) throws BadBytecode {
+ switch (opcode) {
+ case AASTORE:
+ return "(Ljava/lang/Object;ILjava/lang/Object;)V";
+ case BASTORE:
+ return "(Ljava/lang/Object;IB)V";
+ case CASTORE:
+ return "(Ljava/lang/Object;IC)V";
+ case DASTORE:
+ return "(Ljava/lang/Object;ID)V";
+ case FASTORE:
+ return "(Ljava/lang/Object;IF)V";
+ case IASTORE:
+ return "(Ljava/lang/Object;II)V";
+ case SASTORE:
+ return "(Ljava/lang/Object;IS)V";
+ case LASTORE:
+ return "(Ljava/lang/Object;IJ)V";
+ }
+
+ throw new BadBytecode(opcode);
+ }
+}
diff --git a/src/main/javassist/convert/TransformAfter.java b/src/main/javassist/convert/TransformAfter.java
new file mode 100644
index 0000000..6015115
--- /dev/null
+++ b/src/main/javassist/convert/TransformAfter.java
@@ -0,0 +1,46 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.convert;
+
+import javassist.CtMethod;
+import javassist.NotFoundException;
+import javassist.bytecode.*;
+
+public class TransformAfter extends TransformBefore {
+ public TransformAfter(Transformer next,
+ CtMethod origMethod, CtMethod afterMethod)
+ throws NotFoundException
+ {
+ super(next, origMethod, afterMethod);
+ }
+
+ protected int match2(int pos, CodeIterator iterator) throws BadBytecode {
+ iterator.move(pos);
+ iterator.insert(saveCode);
+ iterator.insert(loadCode);
+ int p = iterator.insertGap(3);
+ iterator.setMark(p);
+ iterator.insert(loadCode);
+ pos = iterator.next();
+ p = iterator.getMark();
+ iterator.writeByte(iterator.byteAt(pos), p);
+ iterator.write16bit(iterator.u16bitAt(pos + 1), p + 1);
+ iterator.writeByte(INVOKESTATIC, pos);
+ iterator.write16bit(newIndex, pos + 1);
+ iterator.move(p);
+ return iterator.next();
+ }
+}
diff --git a/src/main/javassist/convert/TransformBefore.java b/src/main/javassist/convert/TransformBefore.java
new file mode 100644
index 0000000..2ad585f
--- /dev/null
+++ b/src/main/javassist/convert/TransformBefore.java
@@ -0,0 +1,107 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.convert;
+
+import javassist.CtClass;
+import javassist.CtMethod;
+import javassist.NotFoundException;
+import javassist.bytecode.*;
+
+public class TransformBefore extends TransformCall {
+ protected CtClass[] parameterTypes;
+ protected int locals;
+ protected int maxLocals;
+ protected byte[] saveCode, loadCode;
+
+ public TransformBefore(Transformer next,
+ CtMethod origMethod, CtMethod beforeMethod)
+ throws NotFoundException
+ {
+ super(next, origMethod, beforeMethod);
+
+ // override
+ methodDescriptor = origMethod.getMethodInfo2().getDescriptor();
+
+ parameterTypes = origMethod.getParameterTypes();
+ locals = 0;
+ maxLocals = 0;
+ saveCode = loadCode = null;
+ }
+
+ public void initialize(ConstPool cp, CodeAttribute attr) {
+ super.initialize(cp, attr);
+ locals = 0;
+ maxLocals = attr.getMaxLocals();
+ saveCode = loadCode = null;
+ }
+
+ protected int match(int c, int pos, CodeIterator iterator,
+ int typedesc, ConstPool cp) throws BadBytecode
+ {
+ if (newIndex == 0) {
+ String desc = Descriptor.ofParameters(parameterTypes) + 'V';
+ desc = Descriptor.insertParameter(classname, desc);
+ int nt = cp.addNameAndTypeInfo(newMethodname, desc);
+ int ci = cp.addClassInfo(newClassname);
+ newIndex = cp.addMethodrefInfo(ci, nt);
+ constPool = cp;
+ }
+
+ if (saveCode == null)
+ makeCode(parameterTypes, cp);
+
+ return match2(pos, iterator);
+ }
+
+ protected int match2(int pos, CodeIterator iterator) throws BadBytecode {
+ iterator.move(pos);
+ iterator.insert(saveCode);
+ iterator.insert(loadCode);
+ int p = iterator.insertGap(3);
+ iterator.writeByte(INVOKESTATIC, p);
+ iterator.write16bit(newIndex, p + 1);
+ iterator.insert(loadCode);
+ return iterator.next();
+ }
+
+ public int extraLocals() { return locals; }
+
+ protected void makeCode(CtClass[] paramTypes, ConstPool cp) {
+ Bytecode save = new Bytecode(cp, 0, 0);
+ Bytecode load = new Bytecode(cp, 0, 0);
+
+ int var = maxLocals;
+ int len = (paramTypes == null) ? 0 : paramTypes.length;
+ load.addAload(var);
+ makeCode2(save, load, 0, len, paramTypes, var + 1);
+ save.addAstore(var);
+
+ saveCode = save.get();
+ loadCode = load.get();
+ }
+
+ private void makeCode2(Bytecode save, Bytecode load,
+ int i, int n, CtClass[] paramTypes, int var)
+ {
+ if (i < n) {
+ int size = load.addLoad(var, paramTypes[i]);
+ makeCode2(save, load, i + 1, n, paramTypes, var + size);
+ save.addStore(var, paramTypes[i]);
+ }
+ else
+ locals = var - maxLocals;
+ }
+}
diff --git a/src/main/javassist/convert/TransformCall.java b/src/main/javassist/convert/TransformCall.java
new file mode 100644
index 0000000..fb8395e
--- /dev/null
+++ b/src/main/javassist/convert/TransformCall.java
@@ -0,0 +1,129 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.convert;
+
+import javassist.CtClass;
+import javassist.CtMethod;
+import javassist.ClassPool;
+import javassist.Modifier;
+import javassist.NotFoundException;
+import javassist.bytecode.*;
+
+public class TransformCall extends Transformer {
+ protected String classname, methodname, methodDescriptor;
+ protected String newClassname, newMethodname;
+ protected boolean newMethodIsPrivate;
+
+ /* cache */
+ protected int newIndex;
+ protected ConstPool constPool;
+
+ public TransformCall(Transformer next, CtMethod origMethod,
+ CtMethod substMethod)
+ {
+ this(next, origMethod.getName(), substMethod);
+ classname = origMethod.getDeclaringClass().getName();
+ }
+
+ public TransformCall(Transformer next, String oldMethodName,
+ CtMethod substMethod)
+ {
+ super(next);
+ methodname = oldMethodName;
+ methodDescriptor = substMethod.getMethodInfo2().getDescriptor();
+ classname = newClassname = substMethod.getDeclaringClass().getName();
+ newMethodname = substMethod.getName();
+ constPool = null;
+ newMethodIsPrivate = Modifier.isPrivate(substMethod.getModifiers());
+ }
+
+ public void initialize(ConstPool cp, CodeAttribute attr) {
+ if (constPool != cp)
+ newIndex = 0;
+ }
+
+ /**
+ * Modify INVOKEINTERFACE, INVOKESPECIAL, INVOKESTATIC and INVOKEVIRTUAL
+ * so that a different method is invoked. The class name in the operand
+ * of these instructions might be a subclass of the target class specified
+ * by <code>classname</code>. This method transforms the instruction
+ * in that case unless the subclass overrides the target method.
+ */
+ public int transform(CtClass clazz, int pos, CodeIterator iterator,
+ ConstPool cp) throws BadBytecode
+ {
+ int c = iterator.byteAt(pos);
+ if (c == INVOKEINTERFACE || c == INVOKESPECIAL
+ || c == INVOKESTATIC || c == INVOKEVIRTUAL) {
+ int index = iterator.u16bitAt(pos + 1);
+ String cname = cp.eqMember(methodname, methodDescriptor, index);
+ if (cname != null && matchClass(cname, clazz.getClassPool())) {
+ int ntinfo = cp.getMemberNameAndType(index);
+ pos = match(c, pos, iterator,
+ cp.getNameAndTypeDescriptor(ntinfo), cp);
+ }
+ }
+
+ return pos;
+ }
+
+ private boolean matchClass(String name, ClassPool pool) {
+ if (classname.equals(name))
+ return true;
+
+ try {
+ CtClass clazz = pool.get(name);
+ CtClass declClazz = pool.get(classname);
+ if (clazz.subtypeOf(declClazz))
+ try {
+ CtMethod m = clazz.getMethod(methodname, methodDescriptor);
+ return m.getDeclaringClass().getName().equals(classname);
+ }
+ catch (NotFoundException e) {
+ // maybe the original method has been removed.
+ return true;
+ }
+ }
+ catch (NotFoundException e) {
+ return false;
+ }
+
+ return false;
+ }
+
+ protected int match(int c, int pos, CodeIterator iterator,
+ int typedesc, ConstPool cp) throws BadBytecode
+ {
+ if (newIndex == 0) {
+ int nt = cp.addNameAndTypeInfo(cp.addUtf8Info(newMethodname),
+ typedesc);
+ int ci = cp.addClassInfo(newClassname);
+ if (c == INVOKEINTERFACE)
+ newIndex = cp.addInterfaceMethodrefInfo(ci, nt);
+ else {
+ if (newMethodIsPrivate && c == INVOKEVIRTUAL)
+ iterator.writeByte(INVOKESPECIAL, pos);
+
+ newIndex = cp.addMethodrefInfo(ci, nt);
+ }
+
+ constPool = cp;
+ }
+
+ iterator.write16bit(newIndex, pos + 1);
+ return pos;
+ }
+}
diff --git a/src/main/javassist/convert/TransformFieldAccess.java b/src/main/javassist/convert/TransformFieldAccess.java
new file mode 100644
index 0000000..d97c95d
--- /dev/null
+++ b/src/main/javassist/convert/TransformFieldAccess.java
@@ -0,0 +1,81 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.convert;
+
+import javassist.bytecode.*;
+import javassist.CtClass;
+import javassist.CtField;
+import javassist.Modifier;
+
+final public class TransformFieldAccess extends Transformer {
+ private String newClassname, newFieldname;
+ private String fieldname;
+ private CtClass fieldClass;
+ private boolean isPrivate;
+
+ /* cache */
+ private int newIndex;
+ private ConstPool constPool;
+
+ public TransformFieldAccess(Transformer next, CtField field,
+ String newClassname, String newFieldname)
+ {
+ super(next);
+ this.fieldClass = field.getDeclaringClass();
+ this.fieldname = field.getName();
+ this.isPrivate = Modifier.isPrivate(field.getModifiers());
+ this.newClassname = newClassname;
+ this.newFieldname = newFieldname;
+ this.constPool = null;
+ }
+
+ public void initialize(ConstPool cp, CodeAttribute attr) {
+ if (constPool != cp)
+ newIndex = 0;
+ }
+
+ /**
+ * Modify GETFIELD, GETSTATIC, PUTFIELD, and PUTSTATIC so that
+ * a different field is accessed. The new field must be declared
+ * in a superclass of the class in which the original field is
+ * declared.
+ */
+ public int transform(CtClass clazz, int pos,
+ CodeIterator iterator, ConstPool cp)
+ {
+ int c = iterator.byteAt(pos);
+ if (c == GETFIELD || c == GETSTATIC
+ || c == PUTFIELD || c == PUTSTATIC) {
+ int index = iterator.u16bitAt(pos + 1);
+ String typedesc
+ = TransformReadField.isField(clazz.getClassPool(), cp,
+ fieldClass, fieldname, isPrivate, index);
+ if (typedesc != null) {
+ if (newIndex == 0) {
+ int nt = cp.addNameAndTypeInfo(newFieldname,
+ typedesc);
+ newIndex = cp.addFieldrefInfo(
+ cp.addClassInfo(newClassname), nt);
+ constPool = cp;
+ }
+
+ iterator.write16bit(newIndex, pos + 1);
+ }
+ }
+
+ return pos;
+ }
+}
diff --git a/src/main/javassist/convert/TransformNew.java b/src/main/javassist/convert/TransformNew.java
new file mode 100644
index 0000000..f796489
--- /dev/null
+++ b/src/main/javassist/convert/TransformNew.java
@@ -0,0 +1,102 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.convert;
+
+import javassist.bytecode.*;
+import javassist.CtClass;
+import javassist.CannotCompileException;
+
+final public class TransformNew extends Transformer {
+ private int nested;
+ private String classname, trapClass, trapMethod;
+
+ public TransformNew(Transformer next,
+ String classname, String trapClass, String trapMethod) {
+ super(next);
+ this.classname = classname;
+ this.trapClass = trapClass;
+ this.trapMethod = trapMethod;
+ }
+
+ public void initialize(ConstPool cp, CodeAttribute attr) {
+ nested = 0;
+ }
+
+ /**
+ * Replace a sequence of
+ * NEW classname
+ * DUP
+ * ...
+ * INVOKESPECIAL
+ * with
+ * NOP
+ * NOP
+ * ...
+ * INVOKESTATIC trapMethod in trapClass
+ */
+ public int transform(CtClass clazz, int pos, CodeIterator iterator,
+ ConstPool cp) throws CannotCompileException
+ {
+ int index;
+ int c = iterator.byteAt(pos);
+ if (c == NEW) {
+ index = iterator.u16bitAt(pos + 1);
+ if (cp.getClassInfo(index).equals(classname)) {
+ if (iterator.byteAt(pos + 3) != DUP)
+ throw new CannotCompileException(
+ "NEW followed by no DUP was found");
+
+ iterator.writeByte(NOP, pos);
+ iterator.writeByte(NOP, pos + 1);
+ iterator.writeByte(NOP, pos + 2);
+ iterator.writeByte(NOP, pos + 3);
+ ++nested;
+
+ StackMapTable smt
+ = (StackMapTable)iterator.get().getAttribute(StackMapTable.tag);
+ if (smt != null)
+ smt.removeNew(pos);
+
+ StackMap sm
+ = (StackMap)iterator.get().getAttribute(StackMap.tag);
+ if (sm != null)
+ sm.removeNew(pos);
+ }
+ }
+ else if (c == INVOKESPECIAL) {
+ index = iterator.u16bitAt(pos + 1);
+ int typedesc = cp.isConstructor(classname, index);
+ if (typedesc != 0 && nested > 0) {
+ int methodref = computeMethodref(typedesc, cp);
+ iterator.writeByte(INVOKESTATIC, pos);
+ iterator.write16bit(methodref, pos + 1);
+ --nested;
+ }
+ }
+
+ return pos;
+ }
+
+ private int computeMethodref(int typedesc, ConstPool cp) {
+ int classIndex = cp.addClassInfo(trapClass);
+ int mnameIndex = cp.addUtf8Info(trapMethod);
+ typedesc = cp.addUtf8Info(
+ Descriptor.changeReturnType(classname,
+ cp.getUtf8Info(typedesc)));
+ return cp.addMethodrefInfo(classIndex,
+ cp.addNameAndTypeInfo(mnameIndex, typedesc));
+ }
+}
diff --git a/src/main/javassist/convert/TransformNewClass.java b/src/main/javassist/convert/TransformNewClass.java
new file mode 100644
index 0000000..f34ef83
--- /dev/null
+++ b/src/main/javassist/convert/TransformNewClass.java
@@ -0,0 +1,82 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.convert;
+
+import javassist.bytecode.*;
+import javassist.CtClass;
+import javassist.CannotCompileException;
+
+final public class TransformNewClass extends Transformer {
+ private int nested;
+ private String classname, newClassName;
+ private int newClassIndex, newMethodNTIndex, newMethodIndex;
+
+ public TransformNewClass(Transformer next,
+ String classname, String newClassName) {
+ super(next);
+ this.classname = classname;
+ this.newClassName = newClassName;
+ }
+
+ public void initialize(ConstPool cp, CodeAttribute attr) {
+ nested = 0;
+ newClassIndex = newMethodNTIndex = newMethodIndex = 0;
+ }
+
+ /**
+ * Modifies a sequence of
+ * NEW classname
+ * DUP
+ * ...
+ * INVOKESPECIAL classname:method
+ */
+ public int transform(CtClass clazz, int pos, CodeIterator iterator,
+ ConstPool cp) throws CannotCompileException
+ {
+ int index;
+ int c = iterator.byteAt(pos);
+ if (c == NEW) {
+ index = iterator.u16bitAt(pos + 1);
+ if (cp.getClassInfo(index).equals(classname)) {
+ if (iterator.byteAt(pos + 3) != DUP)
+ throw new CannotCompileException(
+ "NEW followed by no DUP was found");
+
+ if (newClassIndex == 0)
+ newClassIndex = cp.addClassInfo(newClassName);
+
+ iterator.write16bit(newClassIndex, pos + 1);
+ ++nested;
+ }
+ }
+ else if (c == INVOKESPECIAL) {
+ index = iterator.u16bitAt(pos + 1);
+ int typedesc = cp.isConstructor(classname, index);
+ if (typedesc != 0 && nested > 0) {
+ int nt = cp.getMethodrefNameAndType(index);
+ if (newMethodNTIndex != nt) {
+ newMethodNTIndex = nt;
+ newMethodIndex = cp.addMethodrefInfo(newClassIndex, nt);
+ }
+
+ iterator.write16bit(newMethodIndex, pos + 1);
+ --nested;
+ }
+ }
+
+ return pos;
+ }
+}
diff --git a/src/main/javassist/convert/TransformReadField.java b/src/main/javassist/convert/TransformReadField.java
new file mode 100644
index 0000000..a9e613c
--- /dev/null
+++ b/src/main/javassist/convert/TransformReadField.java
@@ -0,0 +1,95 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.convert;
+
+import javassist.bytecode.*;
+import javassist.ClassPool;
+import javassist.CtClass;
+import javassist.CtField;
+import javassist.NotFoundException;
+import javassist.Modifier;
+
+public class TransformReadField extends Transformer {
+ protected String fieldname;
+ protected CtClass fieldClass;
+ protected boolean isPrivate;
+ protected String methodClassname, methodName;
+
+ public TransformReadField(Transformer next, CtField field,
+ String methodClassname, String methodName)
+ {
+ super(next);
+ this.fieldClass = field.getDeclaringClass();
+ this.fieldname = field.getName();
+ this.methodClassname = methodClassname;
+ this.methodName = methodName;
+ this.isPrivate = Modifier.isPrivate(field.getModifiers());
+ }
+
+ static String isField(ClassPool pool, ConstPool cp, CtClass fclass,
+ String fname, boolean is_private, int index) {
+ if (!cp.getFieldrefName(index).equals(fname))
+ return null;
+
+ try {
+ CtClass c = pool.get(cp.getFieldrefClassName(index));
+ if (c == fclass || (!is_private && isFieldInSuper(c, fclass, fname)))
+ return cp.getFieldrefType(index);
+ }
+ catch (NotFoundException e) {}
+ return null;
+ }
+
+ static boolean isFieldInSuper(CtClass clazz, CtClass fclass, String fname) {
+ if (!clazz.subclassOf(fclass))
+ return false;
+
+ try {
+ CtField f = clazz.getField(fname);
+ return f.getDeclaringClass() == fclass;
+ }
+ catch (NotFoundException e) {}
+ return false;
+ }
+
+ public int transform(CtClass tclazz, int pos, CodeIterator iterator,
+ ConstPool cp) throws BadBytecode
+ {
+ int c = iterator.byteAt(pos);
+ if (c == GETFIELD || c == GETSTATIC) {
+ int index = iterator.u16bitAt(pos + 1);
+ String typedesc = isField(tclazz.getClassPool(), cp,
+ fieldClass, fieldname, isPrivate, index);
+ if (typedesc != null) {
+ if (c == GETSTATIC) {
+ iterator.move(pos);
+ pos = iterator.insertGap(1); // insertGap() may insert 4 bytes.
+ iterator.writeByte(ACONST_NULL, pos);
+ pos = iterator.next();
+ }
+
+ String type = "(Ljava/lang/Object;)" + typedesc;
+ int mi = cp.addClassInfo(methodClassname);
+ int methodref = cp.addMethodrefInfo(mi, methodName, type);
+ iterator.writeByte(INVOKESTATIC, pos);
+ iterator.write16bit(methodref, pos + 1);
+ return pos;
+ }
+ }
+
+ return pos;
+ }
+}
diff --git a/src/main/javassist/convert/TransformWriteField.java b/src/main/javassist/convert/TransformWriteField.java
new file mode 100644
index 0000000..2b6f8bb
--- /dev/null
+++ b/src/main/javassist/convert/TransformWriteField.java
@@ -0,0 +1,71 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.convert;
+
+import javassist.CtClass;
+import javassist.CtField;
+import javassist.bytecode.*;
+
+final public class TransformWriteField extends TransformReadField {
+ public TransformWriteField(Transformer next, CtField field,
+ String methodClassname, String methodName)
+ {
+ super(next, field, methodClassname, methodName);
+ }
+
+ public int transform(CtClass tclazz, int pos, CodeIterator iterator,
+ ConstPool cp) throws BadBytecode
+ {
+ int c = iterator.byteAt(pos);
+ if (c == PUTFIELD || c == PUTSTATIC) {
+ int index = iterator.u16bitAt(pos + 1);
+ String typedesc = isField(tclazz.getClassPool(), cp,
+ fieldClass, fieldname, isPrivate, index);
+ if (typedesc != null) {
+ if (c == PUTSTATIC) {
+ CodeAttribute ca = iterator.get();
+ iterator.move(pos);
+ char c0 = typedesc.charAt(0);
+ if (c0 == 'J' || c0 == 'D') { // long or double
+ // insertGap() may insert 4 bytes.
+ pos = iterator.insertGap(3);
+ iterator.writeByte(ACONST_NULL, pos);
+ iterator.writeByte(DUP_X2, pos + 1);
+ iterator.writeByte(POP, pos + 2);
+ ca.setMaxStack(ca.getMaxStack() + 2);
+ }
+ else {
+ // insertGap() may insert 4 bytes.
+ pos = iterator.insertGap(2);
+ iterator.writeByte(ACONST_NULL, pos);
+ iterator.writeByte(SWAP, pos + 1);
+ ca.setMaxStack(ca.getMaxStack() + 1);
+ }
+
+ pos = iterator.next();
+ }
+
+ int mi = cp.addClassInfo(methodClassname);
+ String type = "(Ljava/lang/Object;" + typedesc + ")V";
+ int methodref = cp.addMethodrefInfo(mi, methodName, type);
+ iterator.writeByte(INVOKESTATIC, pos);
+ iterator.write16bit(methodref, pos + 1);
+ }
+ }
+
+ return pos;
+ }
+}
diff --git a/src/main/javassist/convert/Transformer.java b/src/main/javassist/convert/Transformer.java
new file mode 100644
index 0000000..4dd3807
--- /dev/null
+++ b/src/main/javassist/convert/Transformer.java
@@ -0,0 +1,56 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.convert;
+
+import javassist.CannotCompileException;
+import javassist.CtClass;
+import javassist.bytecode.BadBytecode;
+import javassist.bytecode.CodeAttribute;
+import javassist.bytecode.CodeIterator;
+import javassist.bytecode.ConstPool;
+import javassist.bytecode.MethodInfo;
+import javassist.bytecode.Opcode;
+
+/**
+ * Transformer and its subclasses are used for executing
+ * code transformation specified by CodeConverter.
+ *
+ * @see javassist.CodeConverter
+ */
+public abstract class Transformer implements Opcode {
+ private Transformer next;
+
+ public Transformer(Transformer t) {
+ next = t;
+ }
+
+ public Transformer getNext() { return next; }
+
+ public void initialize(ConstPool cp, CodeAttribute attr) {}
+
+ public void initialize(ConstPool cp, CtClass clazz, MethodInfo minfo) throws CannotCompileException {
+ initialize(cp, minfo.getCodeAttribute());
+ }
+
+ public void clean() {}
+
+ public abstract int transform(CtClass clazz, int pos, CodeIterator it,
+ ConstPool cp) throws CannotCompileException, BadBytecode;
+
+ public int extraLocals() { return 0; }
+
+ public int extraStack() { return 0; }
+}
diff --git a/src/main/javassist/expr/Cast.java b/src/main/javassist/expr/Cast.java
new file mode 100644
index 0000000..1ef87be
--- /dev/null
+++ b/src/main/javassist/expr/Cast.java
@@ -0,0 +1,165 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.expr;
+
+import javassist.*;
+import javassist.bytecode.*;
+import javassist.compiler.*;
+import javassist.compiler.ast.ASTList;
+
+/**
+ * Explicit type cast.
+ */
+public class Cast extends Expr {
+ /**
+ * Undocumented constructor. Do not use; internal-use only.
+ */
+ protected Cast(int pos, CodeIterator i, CtClass declaring, MethodInfo m) {
+ super(pos, i, declaring, m);
+ }
+
+ /**
+ * Returns the method or constructor containing the type cast
+ * expression represented by this object.
+ */
+ public CtBehavior where() { return super.where(); }
+
+ /**
+ * Returns the line number of the source line containing the
+ * type-cast expression.
+ *
+ * @return -1 if this information is not available.
+ */
+ public int getLineNumber() {
+ return super.getLineNumber();
+ }
+
+ /**
+ * Returns the source file containing the type-cast expression.
+ *
+ * @return null if this information is not available.
+ */
+ public String getFileName() {
+ return super.getFileName();
+ }
+
+ /**
+ * Returns the <code>CtClass</code> object representing
+ * the type specified by the cast.
+ */
+ public CtClass getType() throws NotFoundException {
+ ConstPool cp = getConstPool();
+ int pos = currentPos;
+ int index = iterator.u16bitAt(pos + 1);
+ String name = cp.getClassInfo(index);
+ return thisClass.getClassPool().getCtClass(name);
+ }
+
+ /**
+ * Returns the list of exceptions that the expression may throw.
+ * This list includes both the exceptions that the try-catch statements
+ * including the expression can catch and the exceptions that
+ * the throws declaration allows the method to throw.
+ */
+ public CtClass[] mayThrow() {
+ return super.mayThrow();
+ }
+
+ /**
+ * Replaces the explicit cast operator with the bytecode derived from
+ * the given source text.
+ *
+ * <p>$0 is available but the value is <code>null</code>.
+ *
+ * @param statement a Java statement except try-catch.
+ */
+ public void replace(String statement) throws CannotCompileException {
+ thisClass.getClassFile(); // to call checkModify().
+ ConstPool constPool = getConstPool();
+ int pos = currentPos;
+ int index = iterator.u16bitAt(pos + 1);
+
+ Javac jc = new Javac(thisClass);
+ ClassPool cp = thisClass.getClassPool();
+ CodeAttribute ca = iterator.get();
+
+ try {
+ CtClass[] params
+ = new CtClass[] { cp.get(javaLangObject) };
+ CtClass retType = getType();
+
+ int paramVar = ca.getMaxLocals();
+ jc.recordParams(javaLangObject, params, true, paramVar,
+ withinStatic());
+ int retVar = jc.recordReturnType(retType, true);
+ jc.recordProceed(new ProceedForCast(index, retType));
+
+ /* Is $_ included in the source code?
+ */
+ checkResultValue(retType, statement);
+
+ Bytecode bytecode = jc.getBytecode();
+ storeStack(params, true, paramVar, bytecode);
+ jc.recordLocalVariables(ca, pos);
+
+ bytecode.addConstZero(retType);
+ bytecode.addStore(retVar, retType); // initialize $_
+
+ jc.compileStmnt(statement);
+ bytecode.addLoad(retVar, retType);
+
+ replace0(pos, bytecode, 3);
+ }
+ catch (CompileError e) { throw new CannotCompileException(e); }
+ catch (NotFoundException e) { throw new CannotCompileException(e); }
+ catch (BadBytecode e) {
+ throw new CannotCompileException("broken method");
+ }
+ }
+
+ /* <type> $proceed(Object obj)
+ */
+ static class ProceedForCast implements ProceedHandler {
+ int index;
+ CtClass retType;
+
+ ProceedForCast(int i, CtClass t) {
+ index = i;
+ retType = t;
+ }
+
+ public void doit(JvstCodeGen gen, Bytecode bytecode, ASTList args)
+ throws CompileError
+ {
+ if (gen.getMethodArgsLength(args) != 1)
+ throw new CompileError(Javac.proceedName
+ + "() cannot take more than one parameter "
+ + "for cast");
+
+ gen.atMethodArgs(args, new int[1], new int[1], new String[1]);
+ bytecode.addOpcode(Opcode.CHECKCAST);
+ bytecode.addIndex(index);
+ gen.setType(retType);
+ }
+
+ public void setReturnType(JvstTypeChecker c, ASTList args)
+ throws CompileError
+ {
+ c.atMethodArgs(args, new int[1], new int[1], new String[1]);
+ c.setType(retType);
+ }
+ }
+}
diff --git a/src/main/javassist/expr/ConstructorCall.java b/src/main/javassist/expr/ConstructorCall.java
new file mode 100644
index 0000000..3a6a02f
--- /dev/null
+++ b/src/main/javassist/expr/ConstructorCall.java
@@ -0,0 +1,69 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.expr;
+
+import javassist.CtClass;
+import javassist.CtConstructor;
+import javassist.CtMethod;
+import javassist.NotFoundException;
+import javassist.bytecode.CodeIterator;
+import javassist.bytecode.MethodInfo;
+
+/**
+ * Constructor call such as <code>this()</code> and <code>super()</code>
+ * within a constructor body.
+ *
+ * @see NewExpr
+ */
+public class ConstructorCall extends MethodCall {
+ /**
+ * Undocumented constructor. Do not use; internal-use only.
+ */
+ protected ConstructorCall(int pos, CodeIterator i, CtClass decl, MethodInfo m) {
+ super(pos, i, decl, m);
+ }
+
+ /**
+ * Returns <code>"super"</code> or "<code>"this"</code>.
+ */
+ public String getMethodName() {
+ return isSuper() ? "super" : "this";
+ }
+
+ /**
+ * Always throws a <code>NotFoundException</code>.
+ *
+ * @see #getConstructor()
+ */
+ public CtMethod getMethod() throws NotFoundException {
+ throw new NotFoundException("this is a constructor call. Call getConstructor().");
+ }
+
+ /**
+ * Returns the called constructor.
+ */
+ public CtConstructor getConstructor() throws NotFoundException {
+ return getCtClass().getConstructor(getSignature());
+ }
+
+ /**
+ * Returns true if the called constructor is not <code>this()</code>
+ * but <code>super()</code> (a constructor declared in the super class).
+ */
+ public boolean isSuper() {
+ return super.isSuper();
+ }
+}
diff --git a/src/main/javassist/expr/Expr.java b/src/main/javassist/expr/Expr.java
new file mode 100644
index 0000000..75de54a
--- /dev/null
+++ b/src/main/javassist/expr/Expr.java
@@ -0,0 +1,329 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.expr;
+
+import javassist.CannotCompileException;
+import javassist.ClassPool;
+import javassist.CtBehavior;
+import javassist.CtClass;
+import javassist.CtConstructor;
+import javassist.CtPrimitiveType;
+import javassist.NotFoundException;
+import javassist.bytecode.AccessFlag;
+import javassist.bytecode.BadBytecode;
+import javassist.bytecode.Bytecode;
+import javassist.bytecode.ClassFile;
+import javassist.bytecode.CodeAttribute;
+import javassist.bytecode.CodeIterator;
+import javassist.bytecode.ConstPool;
+import javassist.bytecode.ExceptionTable;
+import javassist.bytecode.ExceptionsAttribute;
+import javassist.bytecode.MethodInfo;
+import javassist.bytecode.Opcode;
+import javassist.compiler.Javac;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+
+/**
+ * Expression.
+ */
+public abstract class Expr implements Opcode {
+ int currentPos;
+ CodeIterator iterator;
+ CtClass thisClass;
+ MethodInfo thisMethod;
+ boolean edited;
+ int maxLocals, maxStack;
+
+ static final String javaLangObject = "java.lang.Object";
+
+ /**
+ * Undocumented constructor. Do not use; internal-use only.
+ */
+ protected Expr(int pos, CodeIterator i, CtClass declaring, MethodInfo m) {
+ currentPos = pos;
+ iterator = i;
+ thisClass = declaring;
+ thisMethod = m;
+ }
+
+ /**
+ * Returns the class that declares the method enclosing
+ * this expression.
+ *
+ * @since 3.7
+ */
+ public CtClass getEnclosingClass() { return thisClass; }
+
+ protected final ConstPool getConstPool() {
+ return thisMethod.getConstPool();
+ }
+
+ protected final boolean edited() {
+ return edited;
+ }
+
+ protected final int locals() {
+ return maxLocals;
+ }
+
+ protected final int stack() {
+ return maxStack;
+ }
+
+ /**
+ * Returns true if this method is static.
+ */
+ protected final boolean withinStatic() {
+ return (thisMethod.getAccessFlags() & AccessFlag.STATIC) != 0;
+ }
+
+ /**
+ * Returns the constructor or method containing the expression.
+ */
+ public CtBehavior where() {
+ MethodInfo mi = thisMethod;
+ CtBehavior[] cb = thisClass.getDeclaredBehaviors();
+ for (int i = cb.length - 1; i >= 0; --i)
+ if (cb[i].getMethodInfo2() == mi)
+ return cb[i];
+
+ CtConstructor init = thisClass.getClassInitializer();
+ if (init != null && init.getMethodInfo2() == mi)
+ return init;
+
+ /* getDeclaredBehaviors() returns a list of methods/constructors.
+ * Although the list is cached in a CtClass object, it might be
+ * recreated for some reason. Thus, the member name and the signature
+ * must be also checked.
+ */
+ for (int i = cb.length - 1; i >= 0; --i) {
+ if (thisMethod.getName().equals(cb[i].getMethodInfo2().getName())
+ && thisMethod.getDescriptor()
+ .equals(cb[i].getMethodInfo2().getDescriptor())) {
+ return cb[i];
+ }
+ }
+
+ throw new RuntimeException("fatal: not found");
+ }
+
+ /**
+ * Returns the list of exceptions that the expression may throw. This list
+ * includes both the exceptions that the try-catch statements including the
+ * expression can catch and the exceptions that the throws declaration
+ * allows the method to throw.
+ */
+ public CtClass[] mayThrow() {
+ ClassPool pool = thisClass.getClassPool();
+ ConstPool cp = thisMethod.getConstPool();
+ LinkedList list = new LinkedList();
+ try {
+ CodeAttribute ca = thisMethod.getCodeAttribute();
+ ExceptionTable et = ca.getExceptionTable();
+ int pos = currentPos;
+ int n = et.size();
+ for (int i = 0; i < n; ++i)
+ if (et.startPc(i) <= pos && pos < et.endPc(i)) {
+ int t = et.catchType(i);
+ if (t > 0)
+ try {
+ addClass(list, pool.get(cp.getClassInfo(t)));
+ }
+ catch (NotFoundException e) {
+ }
+ }
+ }
+ catch (NullPointerException e) {
+ }
+
+ ExceptionsAttribute ea = thisMethod.getExceptionsAttribute();
+ if (ea != null) {
+ String[] exceptions = ea.getExceptions();
+ if (exceptions != null) {
+ int n = exceptions.length;
+ for (int i = 0; i < n; ++i)
+ try {
+ addClass(list, pool.get(exceptions[i]));
+ }
+ catch (NotFoundException e) {
+ }
+ }
+ }
+
+ return (CtClass[])list.toArray(new CtClass[list.size()]);
+ }
+
+ private static void addClass(LinkedList list, CtClass c) {
+ Iterator it = list.iterator();
+ while (it.hasNext())
+ if (it.next() == c)
+ return;
+
+ list.add(c);
+ }
+
+ /**
+ * Returns the index of the bytecode corresponding to the expression. It is
+ * the index into the byte array containing the Java bytecode that
+ * implements the method.
+ */
+ public int indexOfBytecode() {
+ return currentPos;
+ }
+
+ /**
+ * Returns the line number of the source line containing the expression.
+ *
+ * @return -1 if this information is not available.
+ */
+ public int getLineNumber() {
+ return thisMethod.getLineNumber(currentPos);
+ }
+
+ /**
+ * Returns the source file containing the expression.
+ *
+ * @return null if this information is not available.
+ */
+ public String getFileName() {
+ ClassFile cf = thisClass.getClassFile2();
+ if (cf == null)
+ return null;
+ else
+ return cf.getSourceFile();
+ }
+
+ static final boolean checkResultValue(CtClass retType, String prog)
+ throws CannotCompileException {
+ /*
+ * Is $_ included in the source code?
+ */
+ boolean hasIt = (prog.indexOf(Javac.resultVarName) >= 0);
+ if (!hasIt && retType != CtClass.voidType)
+ throw new CannotCompileException(
+ "the resulting value is not stored in "
+ + Javac.resultVarName);
+
+ return hasIt;
+ }
+
+ /*
+ * If isStaticCall is true, null is assigned to $0. So $0 must be declared
+ * by calling Javac.recordParams().
+ *
+ * After executing this method, the current stack depth might be less than
+ * 0.
+ */
+ static final void storeStack(CtClass[] params, boolean isStaticCall,
+ int regno, Bytecode bytecode) {
+ storeStack0(0, params.length, params, regno + 1, bytecode);
+ if (isStaticCall)
+ bytecode.addOpcode(ACONST_NULL);
+
+ bytecode.addAstore(regno);
+ }
+
+ private static void storeStack0(int i, int n, CtClass[] params, int regno,
+ Bytecode bytecode) {
+ if (i >= n)
+ return;
+ else {
+ CtClass c = params[i];
+ int size;
+ if (c instanceof CtPrimitiveType)
+ size = ((CtPrimitiveType)c).getDataSize();
+ else
+ size = 1;
+
+ storeStack0(i + 1, n, params, regno + size, bytecode);
+ bytecode.addStore(regno, c);
+ }
+ }
+
+ // The implementation of replace() should call thisClass.checkModify()
+ // so that isModify() will return true. Otherwise, thisClass.classfile
+ // might be released during compilation and the compiler might generate
+ // bytecode with a wrong copy of ConstPool.
+
+ /**
+ * Replaces this expression with the bytecode derived from
+ * the given source text.
+ *
+ * @param statement a Java statement except try-catch.
+ */
+ public abstract void replace(String statement) throws CannotCompileException;
+
+ /**
+ * Replaces this expression with the bytecode derived from
+ * the given source text and <code>ExprEditor</code>.
+ *
+ * @param statement a Java statement except try-catch.
+ * @param recursive if not null, the substituted bytecode
+ * is recursively processed by the given
+ * <code>ExprEditor</code>.
+ * @since 3.1
+ */
+ public void replace(String statement, ExprEditor recursive)
+ throws CannotCompileException
+ {
+ replace(statement);
+ if (recursive != null)
+ runEditor(recursive, iterator);
+ }
+
+ protected void replace0(int pos, Bytecode bytecode, int size)
+ throws BadBytecode {
+ byte[] code = bytecode.get();
+ edited = true;
+ int gap = code.length - size;
+ for (int i = 0; i < size; ++i)
+ iterator.writeByte(NOP, pos + i);
+
+ if (gap > 0)
+ pos = iterator.insertGapAt(pos, gap, false).position;
+
+ iterator.write(code, pos);
+ iterator.insert(bytecode.getExceptionTable(), pos);
+ maxLocals = bytecode.getMaxLocals();
+ maxStack = bytecode.getMaxStack();
+ }
+
+ protected void runEditor(ExprEditor ed, CodeIterator oldIterator)
+ throws CannotCompileException
+ {
+ CodeAttribute codeAttr = oldIterator.get();
+ int orgLocals = codeAttr.getMaxLocals();
+ int orgStack = codeAttr.getMaxStack();
+ int newLocals = locals();
+ codeAttr.setMaxStack(stack());
+ codeAttr.setMaxLocals(newLocals);
+ ExprEditor.LoopContext context
+ = new ExprEditor.LoopContext(newLocals);
+ int size = oldIterator.getCodeLength();
+ int endPos = oldIterator.lookAhead();
+ oldIterator.move(currentPos);
+ if (ed.doit(thisClass, thisMethod, context, oldIterator, endPos))
+ edited = true;
+
+ oldIterator.move(endPos + oldIterator.getCodeLength() - size);
+ codeAttr.setMaxLocals(orgLocals);
+ codeAttr.setMaxStack(orgStack);
+ maxLocals = context.maxLocals;
+ maxStack += context.maxStack;
+ }
+}
diff --git a/src/main/javassist/expr/ExprEditor.java b/src/main/javassist/expr/ExprEditor.java
new file mode 100644
index 0000000..80ddd4b
--- /dev/null
+++ b/src/main/javassist/expr/ExprEditor.java
@@ -0,0 +1,316 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.expr;
+
+import javassist.bytecode.*;
+import javassist.CtClass;
+import javassist.CannotCompileException;
+
+/**
+ * A translator of method bodies.
+ *
+ * <p>The users can define a subclass of this class to customize how to
+ * modify a method body. The overall architecture is similar to the
+ * strategy pattern.
+ *
+ * <p>If <code>instrument()</code> is called in
+ * <code>CtMethod</code>, the method body is scanned from the beginning
+ * to the end.
+ * Whenever an expression, such as a method call and a <tt>new</tt>
+ * expression (object creation),
+ * is found, <code>edit()</code> is called in <code>ExprEdit</code>.
+ * <code>edit()</code> can inspect and modify the given expression.
+ * The modification is reflected on the original method body. If
+ * <code>edit()</code> does nothing, the original method body is not
+ * changed.
+ *
+ * <p>The following code is an example:
+ *
+ * <ul><pre>
+ * CtMethod cm = ...;
+ * cm.instrument(new ExprEditor() {
+ * public void edit(MethodCall m) throws CannotCompileException {
+ * if (m.getClassName().equals("Point")) {
+ * System.out.println(m.getMethodName() + " line: "
+ * + m.getLineNumber());
+ * }
+ * });
+ * </pre></ul>
+ *
+ * <p>This code inspects all method calls appearing in the method represented
+ * by <code>cm</code> and it prints the names and the line numbers of the
+ * methods declared in class <code>Point</code>. This code does not modify
+ * the body of the method represented by <code>cm</code>. If the method
+ * body must be modified, call <code>replace()</code>
+ * in <code>MethodCall</code>.
+ *
+ * @see javassist.CtClass#instrument(ExprEditor)
+ * @see javassist.CtMethod#instrument(ExprEditor)
+ * @see javassist.CtConstructor#instrument(ExprEditor)
+ * @see MethodCall
+ * @see NewExpr
+ * @see FieldAccess
+ *
+ * @see javassist.CodeConverter
+ */
+public class ExprEditor {
+ /**
+ * Default constructor. It does nothing.
+ */
+ public ExprEditor() {}
+
+ /**
+ * Undocumented method. Do not use; internal-use only.
+ */
+ public boolean doit(CtClass clazz, MethodInfo minfo)
+ throws CannotCompileException
+ {
+ CodeAttribute codeAttr = minfo.getCodeAttribute();
+ if (codeAttr == null)
+ return false;
+
+ CodeIterator iterator = codeAttr.iterator();
+ boolean edited = false;
+ LoopContext context = new LoopContext(codeAttr.getMaxLocals());
+
+ while (iterator.hasNext())
+ if (loopBody(iterator, clazz, minfo, context))
+ edited = true;
+
+ ExceptionTable et = codeAttr.getExceptionTable();
+ int n = et.size();
+ for (int i = 0; i < n; ++i) {
+ Handler h = new Handler(et, i, iterator, clazz, minfo);
+ edit(h);
+ if (h.edited()) {
+ edited = true;
+ context.updateMax(h.locals(), h.stack());
+ }
+ }
+
+ // codeAttr might be modified by other partiess
+ // so I check the current value of max-locals.
+ if (codeAttr.getMaxLocals() < context.maxLocals)
+ codeAttr.setMaxLocals(context.maxLocals);
+
+ codeAttr.setMaxStack(codeAttr.getMaxStack() + context.maxStack);
+ try {
+ if (edited)
+ minfo.rebuildStackMapIf6(clazz.getClassPool(),
+ clazz.getClassFile2());
+ }
+ catch (BadBytecode b) {
+ throw new CannotCompileException(b.getMessage(), b);
+ }
+
+ return edited;
+ }
+
+ /**
+ * Visits each bytecode in the given range.
+ */
+ boolean doit(CtClass clazz, MethodInfo minfo, LoopContext context,
+ CodeIterator iterator, int endPos)
+ throws CannotCompileException
+ {
+ boolean edited = false;
+ while (iterator.hasNext() && iterator.lookAhead() < endPos) {
+ int size = iterator.getCodeLength();
+ if (loopBody(iterator, clazz, minfo, context)) {
+ edited = true;
+ int size2 = iterator.getCodeLength();
+ if (size != size2) // the body was modified.
+ endPos += size2 - size;
+ }
+ }
+
+ return edited;
+ }
+
+ final static class NewOp {
+ NewOp next;
+ int pos;
+ String type;
+
+ NewOp(NewOp n, int p, String t) {
+ next = n;
+ pos = p;
+ type = t;
+ }
+ }
+
+ final static class LoopContext {
+ NewOp newList;
+ int maxLocals;
+ int maxStack;
+
+ LoopContext(int locals) {
+ maxLocals = locals;
+ maxStack = 0;
+ newList = null;
+ }
+
+ void updateMax(int locals, int stack) {
+ if (maxLocals < locals)
+ maxLocals = locals;
+
+ if (maxStack < stack)
+ maxStack = stack;
+ }
+ }
+
+ final boolean loopBody(CodeIterator iterator, CtClass clazz,
+ MethodInfo minfo, LoopContext context)
+ throws CannotCompileException
+ {
+ try {
+ Expr expr = null;
+ int pos = iterator.next();
+ int c = iterator.byteAt(pos);
+
+ if (c < Opcode.GETSTATIC) // c < 178
+ /* skip */;
+ else if (c < Opcode.NEWARRAY) { // c < 188
+ if (c == Opcode.INVOKESTATIC
+ || c == Opcode.INVOKEINTERFACE
+ || c == Opcode.INVOKEVIRTUAL) {
+ expr = new MethodCall(pos, iterator, clazz, minfo);
+ edit((MethodCall)expr);
+ }
+ else if (c == Opcode.GETFIELD || c == Opcode.GETSTATIC
+ || c == Opcode.PUTFIELD
+ || c == Opcode.PUTSTATIC) {
+ expr = new FieldAccess(pos, iterator, clazz, minfo, c);
+ edit((FieldAccess)expr);
+ }
+ else if (c == Opcode.NEW) {
+ int index = iterator.u16bitAt(pos + 1);
+ context.newList = new NewOp(context.newList, pos,
+ minfo.getConstPool().getClassInfo(index));
+ }
+ else if (c == Opcode.INVOKESPECIAL) {
+ NewOp newList = context.newList;
+ if (newList != null
+ && minfo.getConstPool().isConstructor(newList.type,
+ iterator.u16bitAt(pos + 1)) > 0) {
+ expr = new NewExpr(pos, iterator, clazz, minfo,
+ newList.type, newList.pos);
+ edit((NewExpr)expr);
+ context.newList = newList.next;
+ }
+ else {
+ MethodCall mcall = new MethodCall(pos, iterator, clazz, minfo);
+ if (mcall.getMethodName().equals(MethodInfo.nameInit)) {
+ ConstructorCall ccall = new ConstructorCall(pos, iterator, clazz, minfo);
+ expr = ccall;
+ edit(ccall);
+ }
+ else {
+ expr = mcall;
+ edit(mcall);
+ }
+ }
+ }
+ }
+ else { // c >= 188
+ if (c == Opcode.NEWARRAY || c == Opcode.ANEWARRAY
+ || c == Opcode.MULTIANEWARRAY) {
+ expr = new NewArray(pos, iterator, clazz, minfo, c);
+ edit((NewArray)expr);
+ }
+ else if (c == Opcode.INSTANCEOF) {
+ expr = new Instanceof(pos, iterator, clazz, minfo);
+ edit((Instanceof)expr);
+ }
+ else if (c == Opcode.CHECKCAST) {
+ expr = new Cast(pos, iterator, clazz, minfo);
+ edit((Cast)expr);
+ }
+ }
+
+ if (expr != null && expr.edited()) {
+ context.updateMax(expr.locals(), expr.stack());
+ return true;
+ }
+ else
+ return false;
+ }
+ catch (BadBytecode e) {
+ throw new CannotCompileException(e);
+ }
+ }
+
+ /**
+ * Edits a <tt>new</tt> expression (overridable).
+ * The default implementation performs nothing.
+ *
+ * @param e the <tt>new</tt> expression creating an object.
+ */
+ public void edit(NewExpr e) throws CannotCompileException {}
+
+ /**
+ * Edits an expression for array creation (overridable).
+ * The default implementation performs nothing.
+ *
+ * @param a the <tt>new</tt> expression for creating an array.
+ * @throws CannotCompileException
+ */
+ public void edit(NewArray a) throws CannotCompileException {}
+
+ /**
+ * Edits a method call (overridable).
+ *
+ * The default implementation performs nothing.
+ */
+ public void edit(MethodCall m) throws CannotCompileException {}
+
+ /**
+ * Edits a constructor call (overridable).
+ * The constructor call is either
+ * <code>super()</code> or <code>this()</code>
+ * included in a constructor body.
+ *
+ * The default implementation performs nothing.
+ *
+ * @see #edit(NewExpr)
+ */
+ public void edit(ConstructorCall c) throws CannotCompileException {}
+
+ /**
+ * Edits a field-access expression (overridable).
+ * Field access means both read and write.
+ * The default implementation performs nothing.
+ */
+ public void edit(FieldAccess f) throws CannotCompileException {}
+
+ /**
+ * Edits an instanceof expression (overridable).
+ * The default implementation performs nothing.
+ */
+ public void edit(Instanceof i) throws CannotCompileException {}
+
+ /**
+ * Edits an expression for explicit type casting (overridable).
+ * The default implementation performs nothing.
+ */
+ public void edit(Cast c) throws CannotCompileException {}
+
+ /**
+ * Edits a catch clause (overridable).
+ * The default implementation performs nothing.
+ */
+ public void edit(Handler h) throws CannotCompileException {}
+}
diff --git a/src/main/javassist/expr/FieldAccess.java b/src/main/javassist/expr/FieldAccess.java
new file mode 100644
index 0000000..56ead16
--- /dev/null
+++ b/src/main/javassist/expr/FieldAccess.java
@@ -0,0 +1,322 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.expr;
+
+import javassist.*;
+import javassist.bytecode.*;
+import javassist.compiler.*;
+import javassist.compiler.ast.ASTList;
+
+/**
+ * Expression for accessing a field.
+ */
+public class FieldAccess extends Expr {
+ int opcode;
+
+ protected FieldAccess(int pos, CodeIterator i, CtClass declaring,
+ MethodInfo m, int op) {
+ super(pos, i, declaring, m);
+ opcode = op;
+ }
+
+ /**
+ * Returns the method or constructor containing the field-access
+ * expression represented by this object.
+ */
+ public CtBehavior where() { return super.where(); }
+
+ /**
+ * Returns the line number of the source line containing the
+ * field access.
+ *
+ * @return -1 if this information is not available.
+ */
+ public int getLineNumber() {
+ return super.getLineNumber();
+ }
+
+ /**
+ * Returns the source file containing the field access.
+ *
+ * @return null if this information is not available.
+ */
+ public String getFileName() {
+ return super.getFileName();
+ }
+
+ /**
+ * Returns true if the field is static.
+ */
+ public boolean isStatic() {
+ return isStatic(opcode);
+ }
+
+ static boolean isStatic(int c) {
+ return c == Opcode.GETSTATIC || c == Opcode.PUTSTATIC;
+ }
+
+ /**
+ * Returns true if the field is read.
+ */
+ public boolean isReader() {
+ return opcode == Opcode.GETFIELD || opcode == Opcode.GETSTATIC;
+ }
+
+ /**
+ * Returns true if the field is written in.
+ */
+ public boolean isWriter() {
+ return opcode == Opcode.PUTFIELD || opcode == Opcode.PUTSTATIC;
+ }
+
+ /**
+ * Returns the class in which the field is declared.
+ */
+ private CtClass getCtClass() throws NotFoundException {
+ return thisClass.getClassPool().get(getClassName());
+ }
+
+ /**
+ * Returns the name of the class in which the field is declared.
+ */
+ public String getClassName() {
+ int index = iterator.u16bitAt(currentPos + 1);
+ return getConstPool().getFieldrefClassName(index);
+ }
+
+ /**
+ * Returns the name of the field.
+ */
+ public String getFieldName() {
+ int index = iterator.u16bitAt(currentPos + 1);
+ return getConstPool().getFieldrefName(index);
+ }
+
+ /**
+ * Returns the field accessed by this expression.
+ */
+ public CtField getField() throws NotFoundException {
+ CtClass cc = getCtClass();
+ return cc.getField(getFieldName());
+ }
+
+ /**
+ * Returns the list of exceptions that the expression may throw.
+ * This list includes both the exceptions that the try-catch statements
+ * including the expression can catch and the exceptions that
+ * the throws declaration allows the method to throw.
+ */
+ public CtClass[] mayThrow() {
+ return super.mayThrow();
+ }
+
+ /**
+ * Returns the signature of the field type.
+ * The signature is represented by a character string
+ * called field descriptor, which is defined in the JVM specification.
+ *
+ * @see javassist.bytecode.Descriptor#toCtClass(String, ClassPool)
+ * @since 3.1
+ */
+ public String getSignature() {
+ int index = iterator.u16bitAt(currentPos + 1);
+ return getConstPool().getFieldrefType(index);
+ }
+
+ /**
+ * Replaces the method call with the bytecode derived from
+ * the given source text.
+ *
+ * <p>$0 is available even if the called method is static.
+ * If the field access is writing, $_ is available but the value
+ * of $_ is ignored.
+ *
+ * @param statement a Java statement except try-catch.
+ */
+ public void replace(String statement) throws CannotCompileException {
+ thisClass.getClassFile(); // to call checkModify().
+ ConstPool constPool = getConstPool();
+ int pos = currentPos;
+ int index = iterator.u16bitAt(pos + 1);
+
+ Javac jc = new Javac(thisClass);
+ CodeAttribute ca = iterator.get();
+ try {
+ CtClass[] params;
+ CtClass retType;
+ CtClass fieldType
+ = Descriptor.toCtClass(constPool.getFieldrefType(index),
+ thisClass.getClassPool());
+ boolean read = isReader();
+ if (read) {
+ params = new CtClass[0];
+ retType = fieldType;
+ }
+ else {
+ params = new CtClass[1];
+ params[0] = fieldType;
+ retType = CtClass.voidType;
+ }
+
+ int paramVar = ca.getMaxLocals();
+ jc.recordParams(constPool.getFieldrefClassName(index), params,
+ true, paramVar, withinStatic());
+
+ /* Is $_ included in the source code?
+ */
+ boolean included = checkResultValue(retType, statement);
+ if (read)
+ included = true;
+
+ int retVar = jc.recordReturnType(retType, included);
+ if (read)
+ jc.recordProceed(new ProceedForRead(retType, opcode,
+ index, paramVar));
+ else {
+ // because $type is not the return type...
+ jc.recordType(fieldType);
+ jc.recordProceed(new ProceedForWrite(params[0], opcode,
+ index, paramVar));
+ }
+
+ Bytecode bytecode = jc.getBytecode();
+ storeStack(params, isStatic(), paramVar, bytecode);
+ jc.recordLocalVariables(ca, pos);
+
+ if (included)
+ if (retType == CtClass.voidType) {
+ bytecode.addOpcode(ACONST_NULL);
+ bytecode.addAstore(retVar);
+ }
+ else {
+ bytecode.addConstZero(retType);
+ bytecode.addStore(retVar, retType); // initialize $_
+ }
+
+ jc.compileStmnt(statement);
+ if (read)
+ bytecode.addLoad(retVar, retType);
+
+ replace0(pos, bytecode, 3);
+ }
+ catch (CompileError e) { throw new CannotCompileException(e); }
+ catch (NotFoundException e) { throw new CannotCompileException(e); }
+ catch (BadBytecode e) {
+ throw new CannotCompileException("broken method");
+ }
+ }
+
+ /* <field type> $proceed()
+ */
+ static class ProceedForRead implements ProceedHandler {
+ CtClass fieldType;
+ int opcode;
+ int targetVar, index;
+
+ ProceedForRead(CtClass type, int op, int i, int var) {
+ fieldType = type;
+ targetVar = var;
+ opcode = op;
+ index = i;
+ }
+
+ public void doit(JvstCodeGen gen, Bytecode bytecode, ASTList args)
+ throws CompileError
+ {
+ if (args != null && !gen.isParamListName(args))
+ throw new CompileError(Javac.proceedName
+ + "() cannot take a parameter for field reading");
+
+ int stack;
+ if (isStatic(opcode))
+ stack = 0;
+ else {
+ stack = -1;
+ bytecode.addAload(targetVar);
+ }
+
+ if (fieldType instanceof CtPrimitiveType)
+ stack += ((CtPrimitiveType)fieldType).getDataSize();
+ else
+ ++stack;
+
+ bytecode.add(opcode);
+ bytecode.addIndex(index);
+ bytecode.growStack(stack);
+ gen.setType(fieldType);
+ }
+
+ public void setReturnType(JvstTypeChecker c, ASTList args)
+ throws CompileError
+ {
+ c.setType(fieldType);
+ }
+ }
+
+ /* void $proceed(<field type>)
+ * the return type is not the field type but void.
+ */
+ static class ProceedForWrite implements ProceedHandler {
+ CtClass fieldType;
+ int opcode;
+ int targetVar, index;
+
+ ProceedForWrite(CtClass type, int op, int i, int var) {
+ fieldType = type;
+ targetVar = var;
+ opcode = op;
+ index = i;
+ }
+
+ public void doit(JvstCodeGen gen, Bytecode bytecode, ASTList args)
+ throws CompileError
+ {
+ if (gen.getMethodArgsLength(args) != 1)
+ throw new CompileError(Javac.proceedName
+ + "() cannot take more than one parameter "
+ + "for field writing");
+
+ int stack;
+ if (isStatic(opcode))
+ stack = 0;
+ else {
+ stack = -1;
+ bytecode.addAload(targetVar);
+ }
+
+ gen.atMethodArgs(args, new int[1], new int[1], new String[1]);
+ gen.doNumCast(fieldType);
+ if (fieldType instanceof CtPrimitiveType)
+ stack -= ((CtPrimitiveType)fieldType).getDataSize();
+ else
+ --stack;
+
+ bytecode.add(opcode);
+ bytecode.addIndex(index);
+ bytecode.growStack(stack);
+ gen.setType(CtClass.voidType);
+ gen.addNullIfVoid();
+ }
+
+ public void setReturnType(JvstTypeChecker c, ASTList args)
+ throws CompileError
+ {
+ c.atMethodArgs(args, new int[1], new int[1], new String[1]);
+ c.setType(CtClass.voidType);
+ c.addNullIfVoid();
+ }
+ }
+}
diff --git a/src/main/javassist/expr/Handler.java b/src/main/javassist/expr/Handler.java
new file mode 100644
index 0000000..dd7e53f
--- /dev/null
+++ b/src/main/javassist/expr/Handler.java
@@ -0,0 +1,145 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.expr;
+
+import javassist.*;
+import javassist.bytecode.*;
+import javassist.compiler.*;
+
+/**
+ * A <code>catch</code> clause or a <code>finally</code> block.
+ */
+public class Handler extends Expr {
+ private static String EXCEPTION_NAME = "$1";
+ private ExceptionTable etable;
+ private int index;
+
+ /**
+ * Undocumented constructor. Do not use; internal-use only.
+ */
+ protected Handler(ExceptionTable et, int nth,
+ CodeIterator it, CtClass declaring, MethodInfo m) {
+ super(et.handlerPc(nth), it, declaring, m);
+ etable = et;
+ index = nth;
+ }
+
+ /**
+ * Returns the method or constructor containing the catch clause.
+ */
+ public CtBehavior where() { return super.where(); }
+
+ /**
+ * Returns the source line number of the catch clause.
+ *
+ * @return -1 if this information is not available.
+ */
+ public int getLineNumber() {
+ return super.getLineNumber();
+ }
+
+ /**
+ * Returns the source file containing the catch clause.
+ *
+ * @return null if this information is not available.
+ */
+ public String getFileName() {
+ return super.getFileName();
+ }
+
+ /**
+ * Returns the list of exceptions that the catch clause may throw.
+ */
+ public CtClass[] mayThrow() {
+ return super.mayThrow();
+ }
+
+ /**
+ * Returns the type handled by the catch clause.
+ * If this is a <code>finally</code> block, <code>null</code> is returned.
+ */
+ public CtClass getType() throws NotFoundException {
+ int type = etable.catchType(index);
+ if (type == 0)
+ return null;
+ else {
+ ConstPool cp = getConstPool();
+ String name = cp.getClassInfo(type);
+ return thisClass.getClassPool().getCtClass(name);
+ }
+ }
+
+ /**
+ * Returns true if this is a <code>finally</code> block.
+ */
+ public boolean isFinally() {
+ return etable.catchType(index) == 0;
+ }
+
+ /**
+ * This method has not been implemented yet.
+ *
+ * @param statement a Java statement except try-catch.
+ */
+ public void replace(String statement) throws CannotCompileException {
+ throw new RuntimeException("not implemented yet");
+ }
+
+ /**
+ * Inserts bytecode at the beginning of the catch clause.
+ * The caught exception is stored in <code>$1</code>.
+ *
+ * @param src the source code representing the inserted bytecode.
+ * It must be a single statement or block.
+ */
+ public void insertBefore(String src) throws CannotCompileException {
+ edited = true;
+
+ ConstPool cp = getConstPool();
+ CodeAttribute ca = iterator.get();
+ Javac jv = new Javac(thisClass);
+ Bytecode b = jv.getBytecode();
+ b.setStackDepth(1);
+ b.setMaxLocals(ca.getMaxLocals());
+
+ try {
+ CtClass type = getType();
+ int var = jv.recordVariable(type, EXCEPTION_NAME);
+ jv.recordReturnType(type, false);
+ b.addAstore(var);
+ jv.compileStmnt(src);
+ b.addAload(var);
+
+ int oldHandler = etable.handlerPc(index);
+ b.addOpcode(Opcode.GOTO);
+ b.addIndex(oldHandler - iterator.getCodeLength()
+ - b.currentPc() + 1);
+
+ maxStack = b.getMaxStack();
+ maxLocals = b.getMaxLocals();
+
+ int pos = iterator.append(b.get());
+ iterator.append(b.getExceptionTable(), pos);
+ etable.setHandlerPc(index, pos);
+ }
+ catch (NotFoundException e) {
+ throw new CannotCompileException(e);
+ }
+ catch (CompileError e) {
+ throw new CannotCompileException(e);
+ }
+ }
+}
diff --git a/src/main/javassist/expr/Instanceof.java b/src/main/javassist/expr/Instanceof.java
new file mode 100644
index 0000000..1ceed13
--- /dev/null
+++ b/src/main/javassist/expr/Instanceof.java
@@ -0,0 +1,169 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.expr;
+
+import javassist.*;
+import javassist.bytecode.*;
+import javassist.compiler.*;
+import javassist.compiler.ast.ASTList;
+
+/**
+ * Instanceof operator.
+ */
+public class Instanceof extends Expr {
+ /**
+ * Undocumented constructor. Do not use; internal-use only.
+ */
+ protected Instanceof(int pos, CodeIterator i, CtClass declaring,
+ MethodInfo m) {
+ super(pos, i, declaring, m);
+ }
+
+ /**
+ * Returns the method or constructor containing the instanceof
+ * expression represented by this object.
+ */
+ public CtBehavior where() { return super.where(); }
+
+ /**
+ * Returns the line number of the source line containing the
+ * instanceof expression.
+ *
+ * @return -1 if this information is not available.
+ */
+ public int getLineNumber() {
+ return super.getLineNumber();
+ }
+
+ /**
+ * Returns the source file containing the
+ * instanceof expression.
+ *
+ * @return null if this information is not available.
+ */
+ public String getFileName() {
+ return super.getFileName();
+ }
+
+ /**
+ * Returns the <code>CtClass</code> object representing
+ * the type name on the right hand side
+ * of the instanceof operator.
+ */
+ public CtClass getType() throws NotFoundException {
+ ConstPool cp = getConstPool();
+ int pos = currentPos;
+ int index = iterator.u16bitAt(pos + 1);
+ String name = cp.getClassInfo(index);
+ return thisClass.getClassPool().getCtClass(name);
+ }
+
+ /**
+ * Returns the list of exceptions that the expression may throw.
+ * This list includes both the exceptions that the try-catch statements
+ * including the expression can catch and the exceptions that
+ * the throws declaration allows the method to throw.
+ */
+ public CtClass[] mayThrow() {
+ return super.mayThrow();
+ }
+
+ /**
+ * Replaces the instanceof operator with the bytecode derived from
+ * the given source text.
+ *
+ * <p>$0 is available but the value is <code>null</code>.
+ *
+ * @param statement a Java statement except try-catch.
+ */
+ public void replace(String statement) throws CannotCompileException {
+ thisClass.getClassFile(); // to call checkModify().
+ ConstPool constPool = getConstPool();
+ int pos = currentPos;
+ int index = iterator.u16bitAt(pos + 1);
+
+ Javac jc = new Javac(thisClass);
+ ClassPool cp = thisClass.getClassPool();
+ CodeAttribute ca = iterator.get();
+
+ try {
+ CtClass[] params
+ = new CtClass[] { cp.get(javaLangObject) };
+ CtClass retType = CtClass.booleanType;
+
+ int paramVar = ca.getMaxLocals();
+ jc.recordParams(javaLangObject, params, true, paramVar,
+ withinStatic());
+ int retVar = jc.recordReturnType(retType, true);
+ jc.recordProceed(new ProceedForInstanceof(index));
+
+ // because $type is not the return type...
+ jc.recordType(getType());
+
+ /* Is $_ included in the source code?
+ */
+ checkResultValue(retType, statement);
+
+ Bytecode bytecode = jc.getBytecode();
+ storeStack(params, true, paramVar, bytecode);
+ jc.recordLocalVariables(ca, pos);
+
+ bytecode.addConstZero(retType);
+ bytecode.addStore(retVar, retType); // initialize $_
+
+ jc.compileStmnt(statement);
+ bytecode.addLoad(retVar, retType);
+
+ replace0(pos, bytecode, 3);
+ }
+ catch (CompileError e) { throw new CannotCompileException(e); }
+ catch (NotFoundException e) { throw new CannotCompileException(e); }
+ catch (BadBytecode e) {
+ throw new CannotCompileException("broken method");
+ }
+ }
+
+ /* boolean $proceed(Object obj)
+ */
+ static class ProceedForInstanceof implements ProceedHandler {
+ int index;
+
+ ProceedForInstanceof(int i) {
+ index = i;
+ }
+
+ public void doit(JvstCodeGen gen, Bytecode bytecode, ASTList args)
+ throws CompileError
+ {
+ if (gen.getMethodArgsLength(args) != 1)
+ throw new CompileError(Javac.proceedName
+ + "() cannot take more than one parameter "
+ + "for instanceof");
+
+ gen.atMethodArgs(args, new int[1], new int[1], new String[1]);
+ bytecode.addOpcode(Opcode.INSTANCEOF);
+ bytecode.addIndex(index);
+ gen.setType(CtClass.booleanType);
+ }
+
+ public void setReturnType(JvstTypeChecker c, ASTList args)
+ throws CompileError
+ {
+ c.atMethodArgs(args, new int[1], new int[1], new String[1]);
+ c.setType(CtClass.booleanType);
+ }
+ }
+}
diff --git a/src/main/javassist/expr/MethodCall.java b/src/main/javassist/expr/MethodCall.java
new file mode 100644
index 0000000..9e9d1db
--- /dev/null
+++ b/src/main/javassist/expr/MethodCall.java
@@ -0,0 +1,246 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.expr;
+
+import javassist.*;
+import javassist.bytecode.*;
+import javassist.compiler.*;
+
+/**
+ * Method invocation (caller-side expression).
+ */
+public class MethodCall extends Expr {
+ /**
+ * Undocumented constructor. Do not use; internal-use only.
+ */
+ protected MethodCall(int pos, CodeIterator i, CtClass declaring,
+ MethodInfo m) {
+ super(pos, i, declaring, m);
+ }
+
+ private int getNameAndType(ConstPool cp) {
+ int pos = currentPos;
+ int c = iterator.byteAt(pos);
+ int index = iterator.u16bitAt(pos + 1);
+
+ if (c == INVOKEINTERFACE)
+ return cp.getInterfaceMethodrefNameAndType(index);
+ else
+ return cp.getMethodrefNameAndType(index);
+ }
+
+ /**
+ * Returns the method or constructor containing the method-call
+ * expression represented by this object.
+ */
+ public CtBehavior where() { return super.where(); }
+
+ /**
+ * Returns the line number of the source line containing the
+ * method call.
+ *
+ * @return -1 if this information is not available.
+ */
+ public int getLineNumber() {
+ return super.getLineNumber();
+ }
+
+ /**
+ * Returns the source file containing the method call.
+ *
+ * @return null if this information is not available.
+ */
+ public String getFileName() {
+ return super.getFileName();
+ }
+
+ /**
+ * Returns the class of the target object,
+ * which the method is called on.
+ */
+ protected CtClass getCtClass() throws NotFoundException {
+ return thisClass.getClassPool().get(getClassName());
+ }
+
+ /**
+ * Returns the class name of the target object,
+ * which the method is called on.
+ */
+ public String getClassName() {
+ String cname;
+
+ ConstPool cp = getConstPool();
+ int pos = currentPos;
+ int c = iterator.byteAt(pos);
+ int index = iterator.u16bitAt(pos + 1);
+
+ if (c == INVOKEINTERFACE)
+ cname = cp.getInterfaceMethodrefClassName(index);
+ else
+ cname = cp.getMethodrefClassName(index);
+
+ if (cname.charAt(0) == '[')
+ cname = Descriptor.toClassName(cname);
+
+ return cname;
+ }
+
+ /**
+ * Returns the name of the called method.
+ */
+ public String getMethodName() {
+ ConstPool cp = getConstPool();
+ int nt = getNameAndType(cp);
+ return cp.getUtf8Info(cp.getNameAndTypeName(nt));
+ }
+
+ /**
+ * Returns the called method.
+ */
+ public CtMethod getMethod() throws NotFoundException {
+ return getCtClass().getMethod(getMethodName(), getSignature());
+ }
+
+ /**
+ * Returns the method signature (the parameter types
+ * and the return type).
+ * The method signature is represented by a character string
+ * called method descriptor, which is defined in the JVM specification.
+ *
+ * @see javassist.CtBehavior#getSignature()
+ * @see javassist.bytecode.Descriptor
+ * @since 3.1
+ */
+ public String getSignature() {
+ ConstPool cp = getConstPool();
+ int nt = getNameAndType(cp);
+ return cp.getUtf8Info(cp.getNameAndTypeDescriptor(nt));
+ }
+
+ /**
+ * Returns the list of exceptions that the expression may throw.
+ * This list includes both the exceptions that the try-catch statements
+ * including the expression can catch and the exceptions that
+ * the throws declaration allows the method to throw.
+ */
+ public CtClass[] mayThrow() {
+ return super.mayThrow();
+ }
+
+ /**
+ * Returns true if the called method is of a superclass of the current
+ * class.
+ */
+ public boolean isSuper() {
+ return iterator.byteAt(currentPos) == INVOKESPECIAL
+ && !where().getDeclaringClass().getName().equals(getClassName());
+ }
+
+ /*
+ * Returns the parameter types of the called method.
+
+ public CtClass[] getParameterTypes() throws NotFoundException {
+ return Descriptor.getParameterTypes(getMethodDesc(),
+ thisClass.getClassPool());
+ }
+ */
+
+ /*
+ * Returns the return type of the called method.
+
+ public CtClass getReturnType() throws NotFoundException {
+ return Descriptor.getReturnType(getMethodDesc(),
+ thisClass.getClassPool());
+ }
+ */
+
+ /**
+ * Replaces the method call with the bytecode derived from
+ * the given source text.
+ *
+ * <p>$0 is available even if the called method is static.
+ *
+ * @param statement a Java statement except try-catch.
+ */
+ public void replace(String statement) throws CannotCompileException {
+ thisClass.getClassFile(); // to call checkModify().
+ ConstPool constPool = getConstPool();
+ int pos = currentPos;
+ int index = iterator.u16bitAt(pos + 1);
+
+ String classname, methodname, signature;
+ int opcodeSize;
+ int c = iterator.byteAt(pos);
+ if (c == INVOKEINTERFACE) {
+ opcodeSize = 5;
+ classname = constPool.getInterfaceMethodrefClassName(index);
+ methodname = constPool.getInterfaceMethodrefName(index);
+ signature = constPool.getInterfaceMethodrefType(index);
+ }
+ else if (c == INVOKESTATIC
+ || c == INVOKESPECIAL || c == INVOKEVIRTUAL) {
+ opcodeSize = 3;
+ classname = constPool.getMethodrefClassName(index);
+ methodname = constPool.getMethodrefName(index);
+ signature = constPool.getMethodrefType(index);
+ }
+ else
+ throw new CannotCompileException("not method invocation");
+
+ Javac jc = new Javac(thisClass);
+ ClassPool cp = thisClass.getClassPool();
+ CodeAttribute ca = iterator.get();
+ try {
+ CtClass[] params = Descriptor.getParameterTypes(signature, cp);
+ CtClass retType = Descriptor.getReturnType(signature, cp);
+ int paramVar = ca.getMaxLocals();
+ jc.recordParams(classname, params,
+ true, paramVar, withinStatic());
+ int retVar = jc.recordReturnType(retType, true);
+ if (c == INVOKESTATIC)
+ jc.recordStaticProceed(classname, methodname);
+ else if (c == INVOKESPECIAL)
+ jc.recordSpecialProceed(Javac.param0Name, classname,
+ methodname, signature);
+ else
+ jc.recordProceed(Javac.param0Name, methodname);
+
+ /* Is $_ included in the source code?
+ */
+ checkResultValue(retType, statement);
+
+ Bytecode bytecode = jc.getBytecode();
+ storeStack(params, c == INVOKESTATIC, paramVar, bytecode);
+ jc.recordLocalVariables(ca, pos);
+
+ if (retType != CtClass.voidType) {
+ bytecode.addConstZero(retType);
+ bytecode.addStore(retVar, retType); // initialize $_
+ }
+
+ jc.compileStmnt(statement);
+ if (retType != CtClass.voidType)
+ bytecode.addLoad(retVar, retType);
+
+ replace0(pos, bytecode, opcodeSize);
+ }
+ catch (CompileError e) { throw new CannotCompileException(e); }
+ catch (NotFoundException e) { throw new CannotCompileException(e); }
+ catch (BadBytecode e) {
+ throw new CannotCompileException("broken method");
+ }
+ }
+}
diff --git a/src/main/javassist/expr/NewArray.java b/src/main/javassist/expr/NewArray.java
new file mode 100644
index 0000000..c5ac41e
--- /dev/null
+++ b/src/main/javassist/expr/NewArray.java
@@ -0,0 +1,282 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.expr;
+
+import javassist.*;
+import javassist.bytecode.*;
+import javassist.compiler.*;
+import javassist.compiler.ast.ASTList;
+
+/**
+ * Array creation.
+ *
+ * <p>This class does not provide methods for obtaining the initial
+ * values of array elements.
+ */
+public class NewArray extends Expr {
+ int opcode;
+
+ protected NewArray(int pos, CodeIterator i, CtClass declaring,
+ MethodInfo m, int op) {
+ super(pos, i, declaring, m);
+ opcode = op;
+ }
+
+ /**
+ * Returns the method or constructor containing the array creation
+ * represented by this object.
+ */
+ public CtBehavior where() { return super.where(); }
+
+ /**
+ * Returns the line number of the source line containing the
+ * array creation.
+ *
+ * @return -1 if this information is not available.
+ */
+ public int getLineNumber() {
+ return super.getLineNumber();
+ }
+
+ /**
+ * Returns the source file containing the array creation.
+ *
+ * @return null if this information is not available.
+ */
+ public String getFileName() {
+ return super.getFileName();
+ }
+
+ /**
+ * Returns the list of exceptions that the expression may throw.
+ * This list includes both the exceptions that the try-catch statements
+ * including the expression can catch and the exceptions that
+ * the throws declaration allows the method to throw.
+ */
+ public CtClass[] mayThrow() {
+ return super.mayThrow();
+ }
+
+ /**
+ * Returns the type of array components. If the created array is
+ * a two-dimensional array of <tt>int</tt>,
+ * the type returned by this method is
+ * not <tt>int[]</tt> but <tt>int</tt>.
+ */
+ public CtClass getComponentType() throws NotFoundException {
+ if (opcode == Opcode.NEWARRAY) {
+ int atype = iterator.byteAt(currentPos + 1);
+ return getPrimitiveType(atype);
+ }
+ else if (opcode == Opcode.ANEWARRAY
+ || opcode == Opcode.MULTIANEWARRAY) {
+ int index = iterator.u16bitAt(currentPos + 1);
+ String desc = getConstPool().getClassInfo(index);
+ int dim = Descriptor.arrayDimension(desc);
+ desc = Descriptor.toArrayComponent(desc, dim);
+ return Descriptor.toCtClass(desc, thisClass.getClassPool());
+ }
+ else
+ throw new RuntimeException("bad opcode: " + opcode);
+ }
+
+ CtClass getPrimitiveType(int atype) {
+ switch (atype) {
+ case Opcode.T_BOOLEAN :
+ return CtClass.booleanType;
+ case Opcode.T_CHAR :
+ return CtClass.charType;
+ case Opcode.T_FLOAT :
+ return CtClass.floatType;
+ case Opcode.T_DOUBLE :
+ return CtClass.doubleType;
+ case Opcode.T_BYTE :
+ return CtClass.byteType;
+ case Opcode.T_SHORT :
+ return CtClass.shortType;
+ case Opcode.T_INT :
+ return CtClass.intType;
+ case Opcode.T_LONG :
+ return CtClass.longType;
+ default :
+ throw new RuntimeException("bad atype: " + atype);
+ }
+ }
+
+ /**
+ * Returns the dimension of the created array.
+ */
+ public int getDimension() {
+ if (opcode == Opcode.NEWARRAY)
+ return 1;
+ else if (opcode == Opcode.ANEWARRAY
+ || opcode == Opcode.MULTIANEWARRAY) {
+ int index = iterator.u16bitAt(currentPos + 1);
+ String desc = getConstPool().getClassInfo(index);
+ return Descriptor.arrayDimension(desc)
+ + (opcode == Opcode.ANEWARRAY ? 1 : 0);
+ }
+ else
+ throw new RuntimeException("bad opcode: " + opcode);
+ }
+
+ /**
+ * Returns the number of dimensions of arrays to be created.
+ * If the opcode is multianewarray, this method returns the second
+ * operand. Otherwise, it returns 1.
+ */
+ public int getCreatedDimensions() {
+ if (opcode == Opcode.MULTIANEWARRAY)
+ return iterator.byteAt(currentPos + 3);
+ else
+ return 1;
+ }
+
+ /**
+ * Replaces the array creation with the bytecode derived from
+ * the given source text.
+ *
+ * <p>$0 is available even if the called method is static.
+ * If the field access is writing, $_ is available but the value
+ * of $_ is ignored.
+ *
+ * @param statement a Java statement except try-catch.
+ */
+ public void replace(String statement) throws CannotCompileException {
+ try {
+ replace2(statement);
+ }
+ catch (CompileError e) { throw new CannotCompileException(e); }
+ catch (NotFoundException e) { throw new CannotCompileException(e); }
+ catch (BadBytecode e) {
+ throw new CannotCompileException("broken method");
+ }
+ }
+
+ private void replace2(String statement)
+ throws CompileError, NotFoundException, BadBytecode,
+ CannotCompileException
+ {
+ thisClass.getClassFile(); // to call checkModify().
+ ConstPool constPool = getConstPool();
+ int pos = currentPos;
+ CtClass retType;
+ int codeLength;
+ int index = 0;
+ int dim = 1;
+ String desc;
+ if (opcode == Opcode.NEWARRAY) {
+ index = iterator.byteAt(currentPos + 1); // atype
+ CtPrimitiveType cpt = (CtPrimitiveType)getPrimitiveType(index);
+ desc = "[" + cpt.getDescriptor();
+ codeLength = 2;
+ }
+ else if (opcode == Opcode.ANEWARRAY) {
+ index = iterator.u16bitAt(pos + 1);
+ desc = constPool.getClassInfo(index);
+ if (desc.startsWith("["))
+ desc = "[" + desc;
+ else
+ desc = "[L" + desc + ";";
+
+ codeLength = 3;
+ }
+ else if (opcode == Opcode.MULTIANEWARRAY) {
+ index = iterator.u16bitAt(currentPos + 1);
+ desc = constPool.getClassInfo(index);
+ dim = iterator.byteAt(currentPos + 3);
+ codeLength = 4;
+ }
+ else
+ throw new RuntimeException("bad opcode: " + opcode);
+
+ retType = Descriptor.toCtClass(desc, thisClass.getClassPool());
+
+ Javac jc = new Javac(thisClass);
+ CodeAttribute ca = iterator.get();
+
+ CtClass[] params = new CtClass[dim];
+ for (int i = 0; i < dim; ++i)
+ params[i] = CtClass.intType;
+
+ int paramVar = ca.getMaxLocals();
+ jc.recordParams(javaLangObject, params,
+ true, paramVar, withinStatic());
+
+ /* Is $_ included in the source code?
+ */
+ checkResultValue(retType, statement);
+ int retVar = jc.recordReturnType(retType, true);
+ jc.recordProceed(new ProceedForArray(retType, opcode, index, dim));
+
+ Bytecode bytecode = jc.getBytecode();
+ storeStack(params, true, paramVar, bytecode);
+ jc.recordLocalVariables(ca, pos);
+
+ bytecode.addOpcode(ACONST_NULL); // initialize $_
+ bytecode.addAstore(retVar);
+
+ jc.compileStmnt(statement);
+ bytecode.addAload(retVar);
+
+ replace0(pos, bytecode, codeLength);
+ }
+
+ /* <array type> $proceed(<dim> ..)
+ */
+ static class ProceedForArray implements ProceedHandler {
+ CtClass arrayType;
+ int opcode;
+ int index, dimension;
+
+ ProceedForArray(CtClass type, int op, int i, int dim) {
+ arrayType = type;
+ opcode = op;
+ index = i;
+ dimension = dim;
+ }
+
+ public void doit(JvstCodeGen gen, Bytecode bytecode, ASTList args)
+ throws CompileError
+ {
+ int num = gen.getMethodArgsLength(args);
+ if (num != dimension)
+ throw new CompileError(Javac.proceedName
+ + "() with a wrong number of parameters");
+
+ gen.atMethodArgs(args, new int[num],
+ new int[num], new String[num]);
+ bytecode.addOpcode(opcode);
+ if (opcode == Opcode.ANEWARRAY)
+ bytecode.addIndex(index);
+ else if (opcode == Opcode.NEWARRAY)
+ bytecode.add(index);
+ else /* if (opcode == Opcode.MULTIANEWARRAY) */ {
+ bytecode.addIndex(index);
+ bytecode.add(dimension);
+ bytecode.growStack(1 - dimension);
+ }
+
+ gen.setType(arrayType);
+ }
+
+ public void setReturnType(JvstTypeChecker c, ASTList args)
+ throws CompileError
+ {
+ c.setType(arrayType);
+ }
+ }
+}
diff --git a/src/main/javassist/expr/NewExpr.java b/src/main/javassist/expr/NewExpr.java
new file mode 100644
index 0000000..c2ab044
--- /dev/null
+++ b/src/main/javassist/expr/NewExpr.java
@@ -0,0 +1,247 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.expr;
+
+import javassist.*;
+import javassist.bytecode.*;
+import javassist.compiler.*;
+import javassist.compiler.ast.ASTList;
+
+/**
+ * Object creation (<tt>new</tt> expression).
+ */
+public class NewExpr extends Expr {
+ String newTypeName;
+ int newPos;
+
+ /**
+ * Undocumented constructor. Do not use; internal-use only.
+ */
+ protected NewExpr(int pos, CodeIterator i, CtClass declaring,
+ MethodInfo m, String type, int np) {
+ super(pos, i, declaring, m);
+ newTypeName = type;
+ newPos = np;
+ }
+
+ /*
+ * Not used
+ *
+ private int getNameAndType(ConstPool cp) {
+ int pos = currentPos;
+ int c = iterator.byteAt(pos);
+ int index = iterator.u16bitAt(pos + 1);
+
+ if (c == INVOKEINTERFACE)
+ return cp.getInterfaceMethodrefNameAndType(index);
+ else
+ return cp.getMethodrefNameAndType(index);
+ } */
+
+ /**
+ * Returns the method or constructor containing the <tt>new</tt>
+ * expression represented by this object.
+ */
+ public CtBehavior where() { return super.where(); }
+
+ /**
+ * Returns the line number of the source line containing the
+ * <tt>new</tt> expression.
+ *
+ * @return -1 if this information is not available.
+ */
+ public int getLineNumber() {
+ return super.getLineNumber();
+ }
+
+ /**
+ * Returns the source file containing the <tt>new</tt> expression.
+ *
+ * @return null if this information is not available.
+ */
+ public String getFileName() {
+ return super.getFileName();
+ }
+
+ /**
+ * Returns the class of the created object.
+ */
+ private CtClass getCtClass() throws NotFoundException {
+ return thisClass.getClassPool().get(newTypeName);
+ }
+
+ /**
+ * Returns the class name of the created object.
+ */
+ public String getClassName() {
+ return newTypeName;
+ }
+
+ /**
+ * Get the signature of the constructor
+ *
+ * The signature is represented by a character string
+ * called method descriptor, which is defined in the JVM specification.
+ *
+ * @see javassist.CtBehavior#getSignature()
+ * @see javassist.bytecode.Descriptor
+ * @return the signature
+ */
+ public String getSignature() {
+ ConstPool constPool = getConstPool();
+ int methodIndex = iterator.u16bitAt(currentPos + 1); // constructor
+ return constPool.getMethodrefType(methodIndex);
+ }
+
+ /**
+ * Returns the constructor called for creating the object.
+ */
+ public CtConstructor getConstructor() throws NotFoundException {
+ ConstPool cp = getConstPool();
+ int index = iterator.u16bitAt(currentPos + 1);
+ String desc = cp.getMethodrefType(index);
+ return getCtClass().getConstructor(desc);
+ }
+
+ /**
+ * Returns the list of exceptions that the expression may throw.
+ * This list includes both the exceptions that the try-catch statements
+ * including the expression can catch and the exceptions that
+ * the throws declaration allows the method to throw.
+ */
+ public CtClass[] mayThrow() {
+ return super.mayThrow();
+ }
+
+ /*
+ * Returns the parameter types of the constructor.
+
+ public CtClass[] getParameterTypes() throws NotFoundException {
+ ConstPool cp = getConstPool();
+ int index = iterator.u16bitAt(currentPos + 1);
+ String desc = cp.getMethodrefType(index);
+ return Descriptor.getParameterTypes(desc, thisClass.getClassPool());
+ }
+ */
+
+ private int canReplace() throws CannotCompileException {
+ int op = iterator.byteAt(newPos + 3);
+ if (op == Opcode.DUP)
+ return 4;
+ else if (op == Opcode.DUP_X1
+ && iterator.byteAt(newPos + 4) == Opcode.SWAP)
+ return 5;
+ else
+ return 3; // for Eclipse. The generated code may include no DUP.
+ // throw new CannotCompileException(
+ // "sorry, cannot edit NEW followed by no DUP");
+ }
+
+ /**
+ * Replaces the <tt>new</tt> expression with the bytecode derived from
+ * the given source text.
+ *
+ * <p>$0 is available but the value is null.
+ *
+ * @param statement a Java statement except try-catch.
+ */
+ public void replace(String statement) throws CannotCompileException {
+ thisClass.getClassFile(); // to call checkModify().
+
+ final int bytecodeSize = 3;
+ int pos = newPos;
+
+ int newIndex = iterator.u16bitAt(pos + 1);
+
+ /* delete the preceding NEW and DUP (or DUP_X1, SWAP) instructions.
+ */
+ int codeSize = canReplace();
+ int end = pos + codeSize;
+ for (int i = pos; i < end; ++i)
+ iterator.writeByte(NOP, i);
+
+ ConstPool constPool = getConstPool();
+ pos = currentPos;
+ int methodIndex = iterator.u16bitAt(pos + 1); // constructor
+
+ String signature = constPool.getMethodrefType(methodIndex);
+
+ Javac jc = new Javac(thisClass);
+ ClassPool cp = thisClass.getClassPool();
+ CodeAttribute ca = iterator.get();
+ try {
+ CtClass[] params = Descriptor.getParameterTypes(signature, cp);
+ CtClass newType = cp.get(newTypeName);
+ int paramVar = ca.getMaxLocals();
+ jc.recordParams(newTypeName, params,
+ true, paramVar, withinStatic());
+ int retVar = jc.recordReturnType(newType, true);
+ jc.recordProceed(new ProceedForNew(newType, newIndex,
+ methodIndex));
+
+ /* Is $_ included in the source code?
+ */
+ checkResultValue(newType, statement);
+
+ Bytecode bytecode = jc.getBytecode();
+ storeStack(params, true, paramVar, bytecode);
+ jc.recordLocalVariables(ca, pos);
+
+ bytecode.addConstZero(newType);
+ bytecode.addStore(retVar, newType); // initialize $_
+
+ jc.compileStmnt(statement);
+ if (codeSize > 3) // if the original code includes DUP.
+ bytecode.addAload(retVar);
+
+ replace0(pos, bytecode, bytecodeSize);
+ }
+ catch (CompileError e) { throw new CannotCompileException(e); }
+ catch (NotFoundException e) { throw new CannotCompileException(e); }
+ catch (BadBytecode e) {
+ throw new CannotCompileException("broken method");
+ }
+ }
+
+ static class ProceedForNew implements ProceedHandler {
+ CtClass newType;
+ int newIndex, methodIndex;
+
+ ProceedForNew(CtClass nt, int ni, int mi) {
+ newType = nt;
+ newIndex = ni;
+ methodIndex = mi;
+ }
+
+ public void doit(JvstCodeGen gen, Bytecode bytecode, ASTList args)
+ throws CompileError
+ {
+ bytecode.addOpcode(NEW);
+ bytecode.addIndex(newIndex);
+ bytecode.addOpcode(DUP);
+ gen.atMethodCallCore(newType, MethodInfo.nameInit, args,
+ false, true, -1, null);
+ gen.setType(newType);
+ }
+
+ public void setReturnType(JvstTypeChecker c, ASTList args)
+ throws CompileError
+ {
+ c.atMethodCallCore(newType, MethodInfo.nameInit, args);
+ c.setType(newType);
+ }
+ }
+}
diff --git a/src/main/javassist/expr/package.html b/src/main/javassist/expr/package.html
new file mode 100644
index 0000000..12a2c63
--- /dev/null
+++ b/src/main/javassist/expr/package.html
@@ -0,0 +1,8 @@
+<html>
+<body>
+
+<p>This package contains the classes for modifying a method body.
+See <code>ExprEditor</code> (expression editor) for more details.
+
+</body>
+</html>
diff --git a/src/main/javassist/package.html b/src/main/javassist/package.html
new file mode 100644
index 0000000..f5b66b9
--- /dev/null
+++ b/src/main/javassist/package.html
@@ -0,0 +1,22 @@
+<html>
+<body>
+The Javassist Core API.
+
+<p>Javassist (<i>Java</i> programming <i>assist</i>ant) makes bytecode
+engineering simple. It is a class library for editing
+bytecode in Java; it enables Java programs to define a new class at
+runtime and to modify a given class file when the JVM loads it.
+
+<p>The most significant class of this package is <code>CtClass</code>.
+See the description of this class first.
+
+<p>To know the version number of this package, type the following command:
+
+<ul><pre>
+java -jar javassist.jar
+</pre></ul>
+
+<p>It prints the version number on the console.
+
+</body>
+</html>
diff --git a/src/main/javassist/runtime/Cflow.java b/src/main/javassist/runtime/Cflow.java
new file mode 100644
index 0000000..641c63f
--- /dev/null
+++ b/src/main/javassist/runtime/Cflow.java
@@ -0,0 +1,52 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.runtime;
+
+/**
+ * A support class for implementing <code>$cflow</code>.
+ * This support class is required at runtime
+ * only if <code>$cflow</code> is used.
+ *
+ * @see javassist.CtBehavior#useCflow(String)
+ */
+public class Cflow extends ThreadLocal {
+ private static class Depth {
+ private int depth;
+ Depth() { depth = 0; }
+ int get() { return depth; }
+ void inc() { ++depth; }
+ void dec() { --depth; }
+ }
+
+ protected synchronized Object initialValue() {
+ return new Depth();
+ }
+
+ /**
+ * Increments the counter.
+ */
+ public void enter() { ((Depth)get()).inc(); }
+
+ /**
+ * Decrements the counter.
+ */
+ public void exit() { ((Depth)get()).dec(); }
+
+ /**
+ * Returns the value of the counter.
+ */
+ public int value() { return ((Depth)get()).get(); }
+}
diff --git a/src/main/javassist/runtime/Desc.java b/src/main/javassist/runtime/Desc.java
new file mode 100644
index 0000000..fa86a74
--- /dev/null
+++ b/src/main/javassist/runtime/Desc.java
@@ -0,0 +1,161 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.runtime;
+
+/**
+ * A support class for implementing <code>$sig</code> and
+ * <code>$type</code>.
+ * This support class is required at runtime
+ * only if <code>$sig</code> or <code>$type</code> is used.
+ */
+public class Desc {
+
+ /**
+ * Specifies how a <code>java.lang.Class</code> object is loaded.
+ *
+ * <p>If true, it is loaded by:
+ * <ul><pre>Thread.currentThread().getContextClassLoader().loadClass()</pre></ul>
+ * <p>If false, it is loaded by <code>Class.forName()</code>.
+ * The default value is false.
+ */
+ public static boolean useContextClassLoader = false;
+
+ private static Class getClassObject(String name)
+ throws ClassNotFoundException
+ {
+ if (useContextClassLoader)
+ return Thread.currentThread().getContextClassLoader()
+ .loadClass(name);
+ else
+ return Class.forName(name);
+ }
+
+ /**
+ * Interprets the given class name.
+ * It is used for implementing <code>$class</code>.
+ */
+ public static Class getClazz(String name) {
+ try {
+ return getClassObject(name);
+ }
+ catch (ClassNotFoundException e) {
+ throw new RuntimeException(
+ "$class: internal error, could not find class '" + name
+ + "' (Desc.useContextClassLoader: "
+ + Boolean.toString(useContextClassLoader) + ")", e);
+ }
+ }
+
+ /**
+ * Interprets the given type descriptor representing a method
+ * signature. It is used for implementing <code>$sig</code>.
+ */
+ public static Class[] getParams(String desc) {
+ if (desc.charAt(0) != '(')
+ throw new RuntimeException("$sig: internal error");
+
+ return getType(desc, desc.length(), 1, 0);
+ }
+
+ /**
+ * Interprets the given type descriptor.
+ * It is used for implementing <code>$type</code>.
+ */
+ public static Class getType(String desc) {
+ Class[] result = getType(desc, desc.length(), 0, 0);
+ if (result == null || result.length != 1)
+ throw new RuntimeException("$type: internal error");
+
+ return result[0];
+ }
+
+ private static Class[] getType(String desc, int descLen,
+ int start, int num) {
+ Class clazz;
+ if (start >= descLen)
+ return new Class[num];
+
+ char c = desc.charAt(start);
+ switch (c) {
+ case 'Z' :
+ clazz = Boolean.TYPE;
+ break;
+ case 'C' :
+ clazz = Character.TYPE;
+ break;
+ case 'B' :
+ clazz = Byte.TYPE;
+ break;
+ case 'S' :
+ clazz = Short.TYPE;
+ break;
+ case 'I' :
+ clazz = Integer.TYPE;
+ break;
+ case 'J' :
+ clazz = Long.TYPE;
+ break;
+ case 'F' :
+ clazz = Float.TYPE;
+ break;
+ case 'D' :
+ clazz = Double.TYPE;
+ break;
+ case 'V' :
+ clazz = Void.TYPE;
+ break;
+ case 'L' :
+ case '[' :
+ return getClassType(desc, descLen, start, num);
+ default :
+ return new Class[num];
+ }
+
+ Class[] result = getType(desc, descLen, start + 1, num + 1);
+ result[num] = clazz;
+ return result;
+ }
+
+ private static Class[] getClassType(String desc, int descLen,
+ int start, int num) {
+ int end = start;
+ while (desc.charAt(end) == '[')
+ ++end;
+
+ if (desc.charAt(end) == 'L') {
+ end = desc.indexOf(';', end);
+ if (end < 0)
+ throw new IndexOutOfBoundsException("bad descriptor");
+ }
+
+ String cname;
+ if (desc.charAt(start) == 'L')
+ cname = desc.substring(start + 1, end);
+ else
+ cname = desc.substring(start, end + 1);
+
+ Class[] result = getType(desc, descLen, end + 1, num + 1);
+ try {
+ result[num] = getClassObject(cname.replace('/', '.'));
+ }
+ catch (ClassNotFoundException e) {
+ // "new RuntimeException(e)" is not available in JDK 1.3.
+ throw new RuntimeException(e.getMessage());
+ }
+
+ return result;
+ }
+}
diff --git a/src/main/javassist/runtime/DotClass.java b/src/main/javassist/runtime/DotClass.java
new file mode 100644
index 0000000..29db811
--- /dev/null
+++ b/src/main/javassist/runtime/DotClass.java
@@ -0,0 +1,28 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.runtime;
+
+/**
+ * A support class for implementing <code>.class</code> notation.
+ * This is required at runtime
+ * only if <code>.class</code> notation is used in source code given
+ * to the Javassist compiler.
+ */
+public class DotClass {
+ public static NoClassDefFoundError fail(ClassNotFoundException e) {
+ return new NoClassDefFoundError(e.getMessage());
+ }
+}
diff --git a/src/main/javassist/runtime/Inner.java b/src/main/javassist/runtime/Inner.java
new file mode 100644
index 0000000..ef96c50
--- /dev/null
+++ b/src/main/javassist/runtime/Inner.java
@@ -0,0 +1,24 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.runtime;
+
+/**
+ * A support class for compiling a method declared in an inner class.
+ * This support class is required at runtime
+ * only if the method calls a private constructor in the enclosing class.
+ */
+public class Inner {
+}
diff --git a/src/main/javassist/runtime/package.html b/src/main/javassist/runtime/package.html
new file mode 100644
index 0000000..313648f
--- /dev/null
+++ b/src/main/javassist/runtime/package.html
@@ -0,0 +1,12 @@
+<html>
+<body>
+Runtime support classes required by modified bytecode.
+
+<p>This package includes support classes that may be required by
+classes modified with Javassist. Note that most of the modified
+classes do not require these support classes. See the documentation
+of every support class to know which kind of modification needs
+a support class.
+
+</body>
+</html>
diff --git a/src/main/javassist/scopedpool/ScopedClassPool.java b/src/main/javassist/scopedpool/ScopedClassPool.java
new file mode 100644
index 0000000..4833773
--- /dev/null
+++ b/src/main/javassist/scopedpool/ScopedClassPool.java
@@ -0,0 +1,308 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.scopedpool;
+
+import java.lang.ref.WeakReference;
+import java.security.ProtectionDomain;
+import java.util.Iterator;
+import java.util.Map;
+import javassist.CannotCompileException;
+import javassist.ClassPool;
+import javassist.CtClass;
+import javassist.LoaderClassPath;
+import javassist.NotFoundException;
+
+/**
+ * A scoped class pool.
+ *
+ * @author <a href="mailto:bill@jboss.org">Bill Burke</a>
+ * @author <a href="adrian@jboss.com">Adrian Brock</a>
+ * @author <a href="kabir.khan@jboss.com">Kabir Khan</a>
+ * @version $Revision: 1.8 $
+ */
+public class ScopedClassPool extends ClassPool {
+ protected ScopedClassPoolRepository repository;
+
+ protected WeakReference classLoader;
+
+ protected LoaderClassPath classPath;
+
+ protected SoftValueHashMap softcache = new SoftValueHashMap();
+
+ boolean isBootstrapCl = true;
+
+ static {
+ ClassPool.doPruning = false;
+ ClassPool.releaseUnmodifiedClassFile = false;
+ }
+
+ /**
+ * Create a new ScopedClassPool.
+ *
+ * @param cl
+ * the classloader
+ * @param src
+ * the original class pool
+ * @param repository
+ * the repository
+ *@deprecated
+ */
+ protected ScopedClassPool(ClassLoader cl, ClassPool src,
+ ScopedClassPoolRepository repository) {
+ this(cl, src, repository, false);
+ }
+
+ /**
+ * Create a new ScopedClassPool.
+ *
+ * @param cl
+ * the classloader
+ * @param src
+ * the original class pool
+ * @param repository
+ * the repository
+ * @param isTemp
+ * Whether this is a temporary pool used to resolve references
+ */
+ protected ScopedClassPool(ClassLoader cl, ClassPool src, ScopedClassPoolRepository repository, boolean isTemp)
+ {
+ super(src);
+ this.repository = repository;
+ this.classLoader = new WeakReference(cl);
+ if (cl != null) {
+ classPath = new LoaderClassPath(cl);
+ this.insertClassPath(classPath);
+ }
+ childFirstLookup = true;
+ if (!isTemp && cl == null)
+ {
+ isBootstrapCl = true;
+ }
+ }
+
+ /**
+ * Get the class loader
+ *
+ * @return the class loader
+ */
+ public ClassLoader getClassLoader() {
+ ClassLoader cl = getClassLoader0();
+ if (cl == null && !isBootstrapCl)
+ {
+ throw new IllegalStateException(
+ "ClassLoader has been garbage collected");
+ }
+ return cl;
+ }
+
+ protected ClassLoader getClassLoader0() {
+ return (ClassLoader)classLoader.get();
+ }
+
+ /**
+ * Close the class pool
+ */
+ public void close() {
+ this.removeClassPath(classPath);
+ classPath.close();
+ classes.clear();
+ softcache.clear();
+ }
+
+ /**
+ * Flush a class
+ *
+ * @param classname
+ * the class to flush
+ */
+ public synchronized void flushClass(String classname) {
+ classes.remove(classname);
+ softcache.remove(classname);
+ }
+
+ /**
+ * Soften a class
+ *
+ * @param clazz
+ * the class
+ */
+ public synchronized void soften(CtClass clazz) {
+ if (repository.isPrune())
+ clazz.prune();
+ classes.remove(clazz.getName());
+ softcache.put(clazz.getName(), clazz);
+ }
+
+ /**
+ * Whether the classloader is loader
+ *
+ * @return false always
+ */
+ public boolean isUnloadedClassLoader() {
+ return false;
+ }
+
+ /**
+ * Get the cached class
+ *
+ * @param classname
+ * the class name
+ * @return the class
+ */
+ protected CtClass getCached(String classname) {
+ CtClass clazz = getCachedLocally(classname);
+ if (clazz == null) {
+ boolean isLocal = false;
+
+ ClassLoader dcl = getClassLoader0();
+ if (dcl != null) {
+ final int lastIndex = classname.lastIndexOf('$');
+ String classResourceName = null;
+ if (lastIndex < 0) {
+ classResourceName = classname.replaceAll("[\\.]", "/")
+ + ".class";
+ }
+ else {
+ classResourceName = classname.substring(0, lastIndex)
+ .replaceAll("[\\.]", "/")
+ + classname.substring(lastIndex) + ".class";
+ }
+
+ isLocal = dcl.getResource(classResourceName) != null;
+ }
+
+ if (!isLocal) {
+ Map registeredCLs = repository.getRegisteredCLs();
+ synchronized (registeredCLs) {
+ Iterator it = registeredCLs.values().iterator();
+ while (it.hasNext()) {
+ ScopedClassPool pool = (ScopedClassPool)it.next();
+ if (pool.isUnloadedClassLoader()) {
+ repository.unregisterClassLoader(pool
+ .getClassLoader());
+ continue;
+ }
+
+ clazz = pool.getCachedLocally(classname);
+ if (clazz != null) {
+ return clazz;
+ }
+ }
+ }
+ }
+ }
+ // *NOTE* NEED TO TEST WHEN SUPERCLASS IS IN ANOTHER UCL!!!!!!
+ return clazz;
+ }
+
+ /**
+ * Cache a class
+ *
+ * @param classname
+ * the class name
+ * @param c
+ * the ctClass
+ * @param dynamic
+ * whether the class is dynamically generated
+ */
+ protected void cacheCtClass(String classname, CtClass c, boolean dynamic) {
+ if (dynamic) {
+ super.cacheCtClass(classname, c, dynamic);
+ }
+ else {
+ if (repository.isPrune())
+ c.prune();
+ softcache.put(classname, c);
+ }
+ }
+
+ /**
+ * Lock a class into the cache
+ *
+ * @param c
+ * the class
+ */
+ public void lockInCache(CtClass c) {
+ super.cacheCtClass(c.getName(), c, false);
+ }
+
+ /**
+ * Whether the class is cached in this pooled
+ *
+ * @param classname
+ * the class name
+ * @return the cached class
+ */
+ protected CtClass getCachedLocally(String classname) {
+ CtClass cached = (CtClass)classes.get(classname);
+ if (cached != null)
+ return cached;
+ synchronized (softcache) {
+ return (CtClass)softcache.get(classname);
+ }
+ }
+
+ /**
+ * Get any local copy of the class
+ *
+ * @param classname
+ * the class name
+ * @return the class
+ * @throws NotFoundException
+ * when the class is not found
+ */
+ public synchronized CtClass getLocally(String classname)
+ throws NotFoundException {
+ softcache.remove(classname);
+ CtClass clazz = (CtClass)classes.get(classname);
+ if (clazz == null) {
+ clazz = createCtClass(classname, true);
+ if (clazz == null)
+ throw new NotFoundException(classname);
+ super.cacheCtClass(classname, clazz, false);
+ }
+
+ return clazz;
+ }
+
+ /**
+ * Convert a javassist class to a java class
+ *
+ * @param ct
+ * the javassist class
+ * @param loader
+ * the loader
+ * @throws CannotCompileException
+ * for any error
+ */
+ public Class toClass(CtClass ct, ClassLoader loader, ProtectionDomain domain)
+ throws CannotCompileException {
+ // We need to pass up the classloader stored in this pool, as the
+ // default implementation uses the Thread context cl.
+ // In the case of JSP's in Tomcat,
+ // org.apache.jasper.servlet.JasperLoader will be stored here, while
+ // it's parent
+ // org.jboss.web.tomcat.tc5.WebCtxLoader$ENCLoader is used as the Thread
+ // context cl. The invocation class needs to
+ // be generated in the JasperLoader classloader since in the case of
+ // method invocations, the package name will be
+ // the same as for the class generated from the jsp, i.e.
+ // org.apache.jsp. For classes belonging to org.apache.jsp,
+ // JasperLoader does NOT delegate to its parent if it cannot find them.
+ lockInCache(ct);
+ return super.toClass(ct, getClassLoader0(), domain);
+ }
+}
diff --git a/src/main/javassist/scopedpool/ScopedClassPoolFactory.java b/src/main/javassist/scopedpool/ScopedClassPoolFactory.java
new file mode 100644
index 0000000..1a998a9
--- /dev/null
+++ b/src/main/javassist/scopedpool/ScopedClassPoolFactory.java
@@ -0,0 +1,38 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.scopedpool;
+
+import javassist.ClassPool;
+
+/**
+ * A factory interface.
+ *
+ * @author <a href="kabir.khan@jboss.com">Kabir Khan</a>
+ * @version $Revision: 1.4 $
+ */
+public interface ScopedClassPoolFactory {
+ /**
+ * Makes an instance.
+ */
+ ScopedClassPool create(ClassLoader cl, ClassPool src,
+ ScopedClassPoolRepository repository);
+
+ /**
+ * Makes an instance.
+ */
+ ScopedClassPool create(ClassPool src,
+ ScopedClassPoolRepository repository);
+}
diff --git a/src/main/javassist/scopedpool/ScopedClassPoolFactoryImpl.java b/src/main/javassist/scopedpool/ScopedClassPoolFactoryImpl.java
new file mode 100644
index 0000000..b8e66d6
--- /dev/null
+++ b/src/main/javassist/scopedpool/ScopedClassPoolFactoryImpl.java
@@ -0,0 +1,42 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.scopedpool;
+
+import javassist.ClassPool;
+
+/**
+ * An implementation of factory.
+ *
+ * @author <a href="kabir.khan@jboss.com">Kabir Khan</a>
+ * @version $Revision: 1.5 $
+ */
+public class ScopedClassPoolFactoryImpl implements ScopedClassPoolFactory {
+ /**
+ * Makes an instance.
+ */
+ public ScopedClassPool create(ClassLoader cl, ClassPool src,
+ ScopedClassPoolRepository repository) {
+ return new ScopedClassPool(cl, src, repository, false);
+ }
+
+ /**
+ * Makes an instance.
+ */
+ public ScopedClassPool create(ClassPool src,
+ ScopedClassPoolRepository repository) {
+ return new ScopedClassPool(null, src, repository, true);
+ }
+}
diff --git a/src/main/javassist/scopedpool/ScopedClassPoolRepository.java b/src/main/javassist/scopedpool/ScopedClassPoolRepository.java
new file mode 100644
index 0000000..7ebf8f8
--- /dev/null
+++ b/src/main/javassist/scopedpool/ScopedClassPoolRepository.java
@@ -0,0 +1,97 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.scopedpool;
+
+import java.util.Map;
+
+import javassist.ClassPool;
+
+/**
+ * An interface to <code>ScopedClassPoolRepositoryImpl</code>.
+ *
+ * @author <a href="kabir.khan@jboss.com">Kabir Khan</a>
+ * @version $Revision: 1.4 $
+ */
+public interface ScopedClassPoolRepository {
+ /**
+ * Records a factory.
+ */
+ void setClassPoolFactory(ScopedClassPoolFactory factory);
+
+ /**
+ * Obtains the recorded factory.
+ */
+ ScopedClassPoolFactory getClassPoolFactory();
+
+ /**
+ * Returns whether or not the class pool is pruned.
+ *
+ * @return the prune.
+ */
+ boolean isPrune();
+
+ /**
+ * Sets the prune flag.
+ *
+ * @param prune a new value.
+ */
+ void setPrune(boolean prune);
+
+ /**
+ * Create a scoped classpool.
+ *
+ * @param cl the classloader.
+ * @param src the original classpool.
+ * @return the classpool.
+ */
+ ScopedClassPool createScopedClassPool(ClassLoader cl, ClassPool src);
+
+ /**
+ * Finds a scoped classpool registered under the passed in classloader.
+ *
+ * @param cl the classloader.
+ * @return the classpool.
+ */
+ ClassPool findClassPool(ClassLoader cl);
+
+ /**
+ * Register a classloader.
+ *
+ * @param ucl the classloader.
+ * @return the classpool.
+ */
+ ClassPool registerClassLoader(ClassLoader ucl);
+
+ /**
+ * Get the registered classloaders.
+ *
+ * @return the registered classloaders.
+ */
+ Map getRegisteredCLs();
+
+ /**
+ * This method will check to see if a register classloader has been
+ * undeployed (as in JBoss).
+ */
+ void clearUnregisteredClassLoaders();
+
+ /**
+ * Unregisters a classpool and unregisters its classloader.
+ *
+ * @param cl the classloader the pool is stored under.
+ */
+ void unregisterClassLoader(ClassLoader cl);
+}
diff --git a/src/main/javassist/scopedpool/ScopedClassPoolRepositoryImpl.java b/src/main/javassist/scopedpool/ScopedClassPoolRepositoryImpl.java
new file mode 100644
index 0000000..2245c7d
--- /dev/null
+++ b/src/main/javassist/scopedpool/ScopedClassPoolRepositoryImpl.java
@@ -0,0 +1,187 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.scopedpool;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+import javassist.ClassPool;
+import javassist.LoaderClassPath;
+
+/**
+ * An implementation of <code>ScopedClassPoolRepository</code>.
+ * It is an singleton.
+ *
+ * @author <a href="kabir.khan@jboss.com">Kabir Khan</a>
+ * @version $Revision: 1.4 $
+ */
+public class ScopedClassPoolRepositoryImpl implements ScopedClassPoolRepository {
+ /** The instance */
+ private static final ScopedClassPoolRepositoryImpl instance = new ScopedClassPoolRepositoryImpl();
+
+ /** Whether to prune */
+ private boolean prune = true;
+
+ /** Whether to prune when added to the classpool's cache */
+ boolean pruneWhenCached;
+
+ /** The registered classloaders */
+ protected Map registeredCLs = Collections
+ .synchronizedMap(new WeakHashMap());
+
+ /** The default class pool */
+ protected ClassPool classpool;
+
+ /** The factory for creating class pools */
+ protected ScopedClassPoolFactory factory = new ScopedClassPoolFactoryImpl();
+
+ /**
+ * Get the instance.
+ *
+ * @return the instance.
+ */
+ public static ScopedClassPoolRepository getInstance() {
+ return instance;
+ }
+
+ /**
+ * Singleton.
+ */
+ private ScopedClassPoolRepositoryImpl() {
+ classpool = ClassPool.getDefault();
+ // FIXME This doesn't look correct
+ ClassLoader cl = Thread.currentThread().getContextClassLoader();
+ classpool.insertClassPath(new LoaderClassPath(cl));
+ }
+
+ /**
+ * Returns the value of the prune attribute.
+ *
+ * @return the prune.
+ */
+ public boolean isPrune() {
+ return prune;
+ }
+
+ /**
+ * Set the prune attribute.
+ *
+ * @param prune a new value.
+ */
+ public void setPrune(boolean prune) {
+ this.prune = prune;
+ }
+
+ /**
+ * Create a scoped classpool.
+ *
+ * @param cl the classloader.
+ * @param src the original classpool.
+ * @return the classpool
+ */
+ public ScopedClassPool createScopedClassPool(ClassLoader cl, ClassPool src) {
+ return factory.create(cl, src, this);
+ }
+
+ public ClassPool findClassPool(ClassLoader cl) {
+ if (cl == null)
+ return registerClassLoader(ClassLoader.getSystemClassLoader());
+
+ return registerClassLoader(cl);
+ }
+
+ /**
+ * Register a classloader.
+ *
+ * @param ucl the classloader.
+ * @return the classpool
+ */
+ public ClassPool registerClassLoader(ClassLoader ucl) {
+ synchronized (registeredCLs) {
+ // FIXME: Probably want to take this method out later
+ // so that AOP framework can be independent of JMX
+ // This is in here so that we can remove a UCL from the ClassPool as
+ // a
+ // ClassPool.classpath
+ if (registeredCLs.containsKey(ucl)) {
+ return (ClassPool)registeredCLs.get(ucl);
+ }
+ ScopedClassPool pool = createScopedClassPool(ucl, classpool);
+ registeredCLs.put(ucl, pool);
+ return pool;
+ }
+ }
+
+ /**
+ * Get the registered classloaders.
+ */
+ public Map getRegisteredCLs() {
+ clearUnregisteredClassLoaders();
+ return registeredCLs;
+ }
+
+ /**
+ * This method will check to see if a register classloader has been
+ * undeployed (as in JBoss)
+ */
+ public void clearUnregisteredClassLoaders() {
+ ArrayList toUnregister = null;
+ synchronized (registeredCLs) {
+ Iterator it = registeredCLs.values().iterator();
+ while (it.hasNext()) {
+ ScopedClassPool pool = (ScopedClassPool)it.next();
+ if (pool.isUnloadedClassLoader()) {
+ it.remove();
+ ClassLoader cl = pool.getClassLoader();
+ if (cl != null) {
+ if (toUnregister == null) {
+ toUnregister = new ArrayList();
+ }
+ toUnregister.add(cl);
+ }
+ }
+ }
+ if (toUnregister != null) {
+ for (int i = 0; i < toUnregister.size(); i++) {
+ unregisterClassLoader((ClassLoader)toUnregister.get(i));
+ }
+ }
+ }
+ }
+
+ public void unregisterClassLoader(ClassLoader cl) {
+ synchronized (registeredCLs) {
+ ScopedClassPool pool = (ScopedClassPool)registeredCLs.remove(cl);
+ if (pool != null)
+ pool.close();
+ }
+ }
+
+ public void insertDelegate(ScopedClassPoolRepository delegate) {
+ // Noop - this is the end
+ }
+
+ public void setClassPoolFactory(ScopedClassPoolFactory factory) {
+ this.factory = factory;
+ }
+
+ public ScopedClassPoolFactory getClassPoolFactory() {
+ return factory;
+ }
+}
diff --git a/src/main/javassist/scopedpool/SoftValueHashMap.java b/src/main/javassist/scopedpool/SoftValueHashMap.java
new file mode 100644
index 0000000..6a73d9e
--- /dev/null
+++ b/src/main/javassist/scopedpool/SoftValueHashMap.java
@@ -0,0 +1,232 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.scopedpool;
+
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.SoftReference;
+import java.util.AbstractMap;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * This Map will remove entries when the value in the map has been cleaned from
+ * garbage collection
+ *
+ * @version <tt>$Revision: 1.4 $</tt>
+ * @author <a href="mailto:bill@jboss.org">Bill Burke</a>
+ */
+public class SoftValueHashMap extends AbstractMap implements Map {
+ private static class SoftValueRef extends SoftReference {
+ public Object key;
+
+ private SoftValueRef(Object key, Object val, ReferenceQueue q) {
+ super(val, q);
+ this.key = key;
+ }
+
+ private static SoftValueRef create(Object key, Object val,
+ ReferenceQueue q) {
+ if (val == null)
+ return null;
+ else
+ return new SoftValueRef(key, val, q);
+ }
+
+ }
+
+ /**
+ * Returns a set of the mappings contained in this hash table.
+ */
+ public Set entrySet() {
+ processQueue();
+ return hash.entrySet();
+ }
+
+ /* Hash table mapping WeakKeys to values */
+ private Map hash;
+
+ /* Reference queue for cleared WeakKeys */
+ private ReferenceQueue queue = new ReferenceQueue();
+
+ /*
+ * Remove all invalidated entries from the map, that is, remove all entries
+ * whose values have been discarded.
+ */
+ private void processQueue() {
+ SoftValueRef ref;
+ while ((ref = (SoftValueRef)queue.poll()) != null) {
+ if (ref == (SoftValueRef)hash.get(ref.key)) {
+ // only remove if it is the *exact* same WeakValueRef
+ //
+ hash.remove(ref.key);
+ }
+ }
+ }
+
+ /* -- Constructors -- */
+
+ /**
+ * Constructs a new, empty <code>WeakHashMap</code> with the given initial
+ * capacity and the given load factor.
+ *
+ * @param initialCapacity
+ * The initial capacity of the <code>WeakHashMap</code>
+ *
+ * @param loadFactor
+ * The load factor of the <code>WeakHashMap</code>
+ *
+ * @throws IllegalArgumentException
+ * If the initial capacity is less than zero, or if the load
+ * factor is nonpositive
+ */
+ public SoftValueHashMap(int initialCapacity, float loadFactor) {
+ hash = new HashMap(initialCapacity, loadFactor);
+ }
+
+ /**
+ * Constructs a new, empty <code>WeakHashMap</code> with the given initial
+ * capacity and the default load factor, which is <code>0.75</code>.
+ *
+ * @param initialCapacity
+ * The initial capacity of the <code>WeakHashMap</code>
+ *
+ * @throws IllegalArgumentException
+ * If the initial capacity is less than zero
+ */
+ public SoftValueHashMap(int initialCapacity) {
+ hash = new HashMap(initialCapacity);
+ }
+
+ /**
+ * Constructs a new, empty <code>WeakHashMap</code> with the default
+ * initial capacity and the default load factor, which is <code>0.75</code>.
+ */
+ public SoftValueHashMap() {
+ hash = new HashMap();
+ }
+
+ /**
+ * Constructs a new <code>WeakHashMap</code> with the same mappings as the
+ * specified <tt>Map</tt>. The <code>WeakHashMap</code> is created with
+ * an initial capacity of twice the number of mappings in the specified map
+ * or 11 (whichever is greater), and a default load factor, which is
+ * <tt>0.75</tt>.
+ *
+ * @param t the map whose mappings are to be placed in this map.
+ */
+ public SoftValueHashMap(Map t) {
+ this(Math.max(2 * t.size(), 11), 0.75f);
+ putAll(t);
+ }
+
+ /* -- Simple queries -- */
+
+ /**
+ * Returns the number of key-value mappings in this map. <strong>Note:</strong>
+ * <em>In contrast with most implementations of the
+ * <code>Map</code> interface, the time required by this operation is
+ * linear in the size of the map.</em>
+ */
+ public int size() {
+ processQueue();
+ return hash.size();
+ }
+
+ /**
+ * Returns <code>true</code> if this map contains no key-value mappings.
+ */
+ public boolean isEmpty() {
+ processQueue();
+ return hash.isEmpty();
+ }
+
+ /**
+ * Returns <code>true</code> if this map contains a mapping for the
+ * specified key.
+ *
+ * @param key
+ * The key whose presence in this map is to be tested.
+ */
+ public boolean containsKey(Object key) {
+ processQueue();
+ return hash.containsKey(key);
+ }
+
+ /* -- Lookup and modification operations -- */
+
+ /**
+ * Returns the value to which this map maps the specified <code>key</code>.
+ * If this map does not contain a value for this key, then return
+ * <code>null</code>.
+ *
+ * @param key
+ * The key whose associated value, if any, is to be returned.
+ */
+ public Object get(Object key) {
+ processQueue();
+ SoftReference ref = (SoftReference)hash.get(key);
+ if (ref != null)
+ return ref.get();
+ return null;
+ }
+
+ /**
+ * Updates this map so that the given <code>key</code> maps to the given
+ * <code>value</code>. If the map previously contained a mapping for
+ * <code>key</code> then that mapping is replaced and the previous value
+ * is returned.
+ *
+ * @param key
+ * The key that is to be mapped to the given <code>value</code>
+ * @param value
+ * The value to which the given <code>key</code> is to be
+ * mapped
+ *
+ * @return The previous value to which this key was mapped, or
+ * <code>null</code> if if there was no mapping for the key
+ */
+ public Object put(Object key, Object value) {
+ processQueue();
+ Object rtn = hash.put(key, SoftValueRef.create(key, value, queue));
+ if (rtn != null)
+ rtn = ((SoftReference)rtn).get();
+ return rtn;
+ }
+
+ /**
+ * Removes the mapping for the given <code>key</code> from this map, if
+ * present.
+ *
+ * @param key
+ * The key whose mapping is to be removed.
+ *
+ * @return The value to which this key was mapped, or <code>null</code> if
+ * there was no mapping for the key.
+ */
+ public Object remove(Object key) {
+ processQueue();
+ return hash.remove(key);
+ }
+
+ /**
+ * Removes all mappings from this map.
+ */
+ public void clear() {
+ processQueue();
+ hash.clear();
+ }
+}
diff --git a/src/main/javassist/scopedpool/package.html b/src/main/javassist/scopedpool/package.html
new file mode 100644
index 0000000..946e5e1
--- /dev/null
+++ b/src/main/javassist/scopedpool/package.html
@@ -0,0 +1,7 @@
+<html>
+<body>
+<p>A custom class pool for several JBoss products.
+It is not part of Javassist.
+</p>
+</body>
+</html>
diff --git a/src/main/javassist/tools/Dump.java b/src/main/javassist/tools/Dump.java
new file mode 100644
index 0000000..2f064a8
--- /dev/null
+++ b/src/main/javassist/tools/Dump.java
@@ -0,0 +1,57 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.tools;
+
+import java.io.*;
+import javassist.bytecode.ClassFile;
+import javassist.bytecode.ClassFilePrinter;
+
+/**
+ * Dump is a tool for viewing the class definition in the given
+ * class file. Unlike the JDK javap tool, Dump works even if
+ * the class file is broken.
+ *
+ * <p>For example,
+ * <ul><pre>% java javassist.tools.Dump foo.class</pre></ul>
+ *
+ * <p>prints the contents of the constant pool and the list of methods
+ * and fields.
+ */
+public class Dump {
+ private Dump() {}
+
+ /**
+ * Main method.
+ *
+ * @param args <code>args[0]</code> is the class file name.
+ */
+ public static void main(String[] args) throws Exception {
+ if (args.length != 1) {
+ System.err.println("Usage: java Dump <class file name>");
+ return;
+ }
+
+ DataInputStream in = new DataInputStream(
+ new FileInputStream(args[0]));
+ ClassFile w = new ClassFile(in);
+ PrintWriter out = new PrintWriter(System.out, true);
+ out.println("*** constant pool ***");
+ w.getConstPool().print(out);
+ out.println();
+ out.println("*** members ***");
+ ClassFilePrinter.print(w, out);
+ }
+}
diff --git a/src/main/javassist/tools/framedump.java b/src/main/javassist/tools/framedump.java
new file mode 100644
index 0000000..0a30fb1
--- /dev/null
+++ b/src/main/javassist/tools/framedump.java
@@ -0,0 +1,47 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+package javassist.tools;
+
+import javassist.ClassPool;
+import javassist.CtClass;
+import javassist.bytecode.analysis.FramePrinter;
+
+/**
+ * framedump is a tool for viewing a merged combination of the instructions and frame state
+ * of all methods in a class.
+ *
+ * <p>For example,
+ * <ul><pre>% java javassist.tools.framedump foo.class</pre></ul>
+ */
+public class framedump {
+ private framedump() {}
+
+ /**
+ * Main method.
+ *
+ * @param args <code>args[0]</code> is the class file name.
+ */
+ public static void main(String[] args) throws Exception {
+ if (args.length != 1) {
+ System.err.println("Usage: java javassist.tools.framedump <class file name>");
+ return;
+ }
+
+ ClassPool pool = ClassPool.getDefault();
+ CtClass clazz = pool.get(args[0]);
+ System.out.println("Frame Dump of " + clazz.getName() + ":");
+ FramePrinter.print(clazz, System.out);
+ }
+}
diff --git a/src/main/javassist/tools/package.html b/src/main/javassist/tools/package.html
new file mode 100644
index 0000000..bee6208
--- /dev/null
+++ b/src/main/javassist/tools/package.html
@@ -0,0 +1,6 @@
+<html>
+<body>
+Covenient tools.
+
+</body>
+</html>
diff --git a/src/main/javassist/tools/reflect/CannotCreateException.java b/src/main/javassist/tools/reflect/CannotCreateException.java
new file mode 100644
index 0000000..75ffe0c
--- /dev/null
+++ b/src/main/javassist/tools/reflect/CannotCreateException.java
@@ -0,0 +1,29 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.tools.reflect;
+
+/**
+ * Signals that <code>ClassMetaobject.newInstance()</code> fails.
+ */
+public class CannotCreateException extends Exception {
+ public CannotCreateException(String s) {
+ super(s);
+ }
+
+ public CannotCreateException(Exception e) {
+ super("by " + e.toString());
+ }
+}
diff --git a/src/main/javassist/tools/reflect/CannotInvokeException.java b/src/main/javassist/tools/reflect/CannotInvokeException.java
new file mode 100644
index 0000000..8c063d7
--- /dev/null
+++ b/src/main/javassist/tools/reflect/CannotInvokeException.java
@@ -0,0 +1,68 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.tools.reflect;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.IllegalAccessException;
+
+/**
+ * Thrown when method invocation using the reflection API has thrown
+ * an exception.
+ *
+ * @see javassist.tools.reflect.Metaobject#trapMethodcall(int, Object[])
+ * @see javassist.tools.reflect.ClassMetaobject#trapMethodcall(int, Object[])
+ * @see javassist.tools.reflect.ClassMetaobject#invoke(Object, int, Object[])
+ */
+public class CannotInvokeException extends RuntimeException {
+
+ private Throwable err = null;
+
+ /**
+ * Returns the cause of this exception. It may return null.
+ */
+ public Throwable getReason() { return err; }
+
+ /**
+ * Constructs a CannotInvokeException with an error message.
+ */
+ public CannotInvokeException(String reason) {
+ super(reason);
+ }
+
+ /**
+ * Constructs a CannotInvokeException with an InvocationTargetException.
+ */
+ public CannotInvokeException(InvocationTargetException e) {
+ super("by " + e.getTargetException().toString());
+ err = e.getTargetException();
+ }
+
+ /**
+ * Constructs a CannotInvokeException with an IllegalAccessException.
+ */
+ public CannotInvokeException(IllegalAccessException e) {
+ super("by " + e.toString());
+ err = e;
+ }
+
+ /**
+ * Constructs a CannotInvokeException with an ClassNotFoundException.
+ */
+ public CannotInvokeException(ClassNotFoundException e) {
+ super("by " + e.toString());
+ err = e;
+ }
+}
diff --git a/src/main/javassist/tools/reflect/CannotReflectException.java b/src/main/javassist/tools/reflect/CannotReflectException.java
new file mode 100644
index 0000000..0af2892
--- /dev/null
+++ b/src/main/javassist/tools/reflect/CannotReflectException.java
@@ -0,0 +1,34 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.tools.reflect;
+
+import javassist.CannotCompileException;
+
+/**
+ * Thrown by <code>makeReflective()</code> in <code>Reflection</code>
+ * when there is an attempt to reflect
+ * a class that is either an interface or a subclass of
+ * either ClassMetaobject or Metaobject.
+ *
+ * @author Brett Randall
+ * @see javassist.tools.reflect.Reflection#makeReflective(CtClass,CtClass,CtClass)
+ * @see javassist.CannotCompileException
+ */
+public class CannotReflectException extends CannotCompileException {
+ public CannotReflectException(String msg) {
+ super(msg);
+ }
+}
diff --git a/src/main/javassist/tools/reflect/ClassMetaobject.java b/src/main/javassist/tools/reflect/ClassMetaobject.java
new file mode 100644
index 0000000..208d104
--- /dev/null
+++ b/src/main/javassist/tools/reflect/ClassMetaobject.java
@@ -0,0 +1,369 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.tools.reflect;
+
+import java.lang.reflect.*;
+import java.util.Arrays;
+import java.io.Serializable;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+/**
+ * A runtime class metaobject.
+ *
+ * <p>A <code>ClassMetaobject</code> is created for every
+ * class of reflective objects. It can be used to hold values
+ * shared among the reflective objects of the same class.
+ *
+ * <p>To obtain a class metaobject, calls <code>_getClass()</code>
+ * on a reflective object. For example,
+ *
+ * <ul><pre>ClassMetaobject cm = ((Metalevel)reflectiveObject)._getClass();
+ * </pre></ul>
+ *
+ * @see javassist.tools.reflect.Metaobject
+ * @see javassist.tools.reflect.Metalevel
+ */
+public class ClassMetaobject implements Serializable {
+ /**
+ * The base-level methods controlled by a metaobject
+ * are renamed so that they begin with
+ * <code>methodPrefix "_m_"</code>.
+ */
+ static final String methodPrefix = "_m_";
+ static final int methodPrefixLen = 3;
+
+ private Class javaClass;
+ private Constructor[] constructors;
+ private Method[] methods;
+
+ /**
+ * Specifies how a <code>java.lang.Class</code> object is loaded.
+ *
+ * <p>If true, it is loaded by:
+ * <ul><pre>Thread.currentThread().getContextClassLoader().loadClass()</pre></ul>
+ * <p>If false, it is loaded by <code>Class.forName()</code>.
+ * The default value is false.
+ */
+ public static boolean useContextClassLoader = false;
+
+ /**
+ * Constructs a <code>ClassMetaobject</code>.
+ *
+ * @param params <code>params[0]</code> is the name of the class
+ * of the reflective objects.
+ */
+ public ClassMetaobject(String[] params)
+ {
+ try {
+ javaClass = getClassObject(params[0]);
+ }
+ catch (ClassNotFoundException e) {
+ throw new RuntimeException("not found: " + params[0]
+ + ", useContextClassLoader: "
+ + Boolean.toString(useContextClassLoader), e);
+ }
+
+ constructors = javaClass.getConstructors();
+ methods = null;
+ }
+
+ private void writeObject(ObjectOutputStream out) throws IOException {
+ out.writeUTF(javaClass.getName());
+ }
+
+ private void readObject(ObjectInputStream in)
+ throws IOException, ClassNotFoundException
+ {
+ javaClass = getClassObject(in.readUTF());
+ constructors = javaClass.getConstructors();
+ methods = null;
+ }
+
+ private Class getClassObject(String name) throws ClassNotFoundException {
+ if (useContextClassLoader)
+ return Thread.currentThread().getContextClassLoader()
+ .loadClass(name);
+ else
+ return Class.forName(name);
+ }
+
+ /**
+ * Obtains the <code>java.lang.Class</code> representing this class.
+ */
+ public final Class getJavaClass() {
+ return javaClass;
+ }
+
+ /**
+ * Obtains the name of this class.
+ */
+ public final String getName() {
+ return javaClass.getName();
+ }
+
+ /**
+ * Returns true if <code>obj</code> is an instance of this class.
+ */
+ public final boolean isInstance(Object obj) {
+ return javaClass.isInstance(obj);
+ }
+
+ /**
+ * Creates a new instance of the class.
+ *
+ * @param args the arguments passed to the constructor.
+ */
+ public final Object newInstance(Object[] args)
+ throws CannotCreateException
+ {
+ int n = constructors.length;
+ for (int i = 0; i < n; ++i) {
+ try {
+ return constructors[i].newInstance(args);
+ }
+ catch (IllegalArgumentException e) {
+ // try again
+ }
+ catch (InstantiationException e) {
+ throw new CannotCreateException(e);
+ }
+ catch (IllegalAccessException e) {
+ throw new CannotCreateException(e);
+ }
+ catch (InvocationTargetException e) {
+ throw new CannotCreateException(e);
+ }
+ }
+
+ throw new CannotCreateException("no constructor matches");
+ }
+
+ /**
+ * Is invoked when <code>static</code> fields of the base-level
+ * class are read and the runtime system intercepts it.
+ * This method simply returns the value of the field.
+ *
+ * <p>Every subclass of this class should redefine this method.
+ */
+ public Object trapFieldRead(String name) {
+ Class jc = getJavaClass();
+ try {
+ return jc.getField(name).get(null);
+ }
+ catch (NoSuchFieldException e) {
+ throw new RuntimeException(e.toString());
+ }
+ catch (IllegalAccessException e) {
+ throw new RuntimeException(e.toString());
+ }
+ }
+
+ /**
+ * Is invoked when <code>static</code> fields of the base-level
+ * class are modified and the runtime system intercepts it.
+ * This method simply sets the field to the given value.
+ *
+ * <p>Every subclass of this class should redefine this method.
+ */
+ public void trapFieldWrite(String name, Object value) {
+ Class jc = getJavaClass();
+ try {
+ jc.getField(name).set(null, value);
+ }
+ catch (NoSuchFieldException e) {
+ throw new RuntimeException(e.toString());
+ }
+ catch (IllegalAccessException e) {
+ throw new RuntimeException(e.toString());
+ }
+ }
+
+ /**
+ * Invokes a method whose name begins with
+ * <code>methodPrefix "_m_"</code> and the identifier.
+ *
+ * @exception CannotInvokeException if the invocation fails.
+ */
+ static public Object invoke(Object target, int identifier, Object[] args)
+ throws Throwable
+ {
+ Method[] allmethods = target.getClass().getMethods();
+ int n = allmethods.length;
+ String head = methodPrefix + identifier;
+ for (int i = 0; i < n; ++i)
+ if (allmethods[i].getName().startsWith(head)) {
+ try {
+ return allmethods[i].invoke(target, args);
+ } catch (java.lang.reflect.InvocationTargetException e) {
+ throw e.getTargetException();
+ } catch (java.lang.IllegalAccessException e) {
+ throw new CannotInvokeException(e);
+ }
+ }
+
+ throw new CannotInvokeException("cannot find a method");
+ }
+
+ /**
+ * Is invoked when <code>static</code> methods of the base-level
+ * class are called and the runtime system intercepts it.
+ * This method simply executes the intercepted method invocation
+ * with the original parameters and returns the resulting value.
+ *
+ * <p>Every subclass of this class should redefine this method.
+ */
+ public Object trapMethodcall(int identifier, Object[] args)
+ throws Throwable
+ {
+ try {
+ Method[] m = getReflectiveMethods();
+ return m[identifier].invoke(null, args);
+ }
+ catch (java.lang.reflect.InvocationTargetException e) {
+ throw e.getTargetException();
+ }
+ catch (java.lang.IllegalAccessException e) {
+ throw new CannotInvokeException(e);
+ }
+ }
+
+ /**
+ * Returns an array of the methods defined on the given reflective
+ * object. This method is for the internal use only.
+ */
+ public final Method[] getReflectiveMethods() {
+ if (methods != null)
+ return methods;
+
+ Class baseclass = getJavaClass();
+ Method[] allmethods = baseclass.getDeclaredMethods();
+ int n = allmethods.length;
+ int[] index = new int[n];
+ int max = 0;
+ for (int i = 0; i < n; ++i) {
+ Method m = allmethods[i];
+ String mname = m.getName();
+ if (mname.startsWith(methodPrefix)) {
+ int k = 0;
+ for (int j = methodPrefixLen;; ++j) {
+ char c = mname.charAt(j);
+ if ('0' <= c && c <= '9')
+ k = k * 10 + c - '0';
+ else
+ break;
+ }
+
+ index[i] = ++k;
+ if (k > max)
+ max = k;
+ }
+ }
+
+ methods = new Method[max];
+ for (int i = 0; i < n; ++i)
+ if (index[i] > 0)
+ methods[index[i] - 1] = allmethods[i];
+
+ return methods;
+ }
+
+ /**
+ * Returns the <code>java.lang.reflect.Method</code> object representing
+ * the method specified by <code>identifier</code>.
+ *
+ * <p>Note that the actual method returned will be have an altered,
+ * reflective name i.e. <code>_m_2_..</code>.
+ *
+ * @param identifier the identifier index
+ * given to <code>trapMethodcall()</code> etc.
+ * @see Metaobject#trapMethodcall(int,Object[])
+ * @see #trapMethodcall(int,Object[])
+ */
+ public final Method getMethod(int identifier) {
+ return getReflectiveMethods()[identifier];
+ }
+
+ /**
+ * Returns the name of the method specified
+ * by <code>identifier</code>.
+ */
+ public final String getMethodName(int identifier) {
+ String mname = getReflectiveMethods()[identifier].getName();
+ int j = ClassMetaobject.methodPrefixLen;
+ for (;;) {
+ char c = mname.charAt(j++);
+ if (c < '0' || '9' < c)
+ break;
+ }
+
+ return mname.substring(j);
+ }
+
+ /**
+ * Returns an array of <code>Class</code> objects representing the
+ * formal parameter types of the method specified
+ * by <code>identifier</code>.
+ */
+ public final Class[] getParameterTypes(int identifier) {
+ return getReflectiveMethods()[identifier].getParameterTypes();
+ }
+
+ /**
+ * Returns a <code>Class</code> objects representing the
+ * return type of the method specified by <code>identifier</code>.
+ */
+ public final Class getReturnType(int identifier) {
+ return getReflectiveMethods()[identifier].getReturnType();
+ }
+
+ /**
+ * Returns the identifier index of the method, as identified by its
+ * original name.
+ *
+ * <p>This method is useful, in conjuction with
+ * <link>ClassMetaobject#getMethod()</link>, to obtain a quick reference
+ * to the original method in the reflected class (i.e. not the proxy
+ * method), using the original name of the method.
+ *
+ * <p>Written by Brett Randall and Shigeru Chiba.
+ *
+ * @param originalName The original name of the reflected method
+ * @param argTypes array of Class specifying the method signature
+ * @return the identifier index of the original method
+ * @throws NoSuchMethodException if the method does not exist
+ *
+ * @see ClassMetaobject#getMethod(int)
+ */
+ public final int getMethodIndex(String originalName, Class[] argTypes)
+ throws NoSuchMethodException
+ {
+ Method[] mthds = getReflectiveMethods();
+ for (int i = 0; i < mthds.length; i++) {
+ if (mthds[i] == null)
+ continue;
+
+ // check name and parameter types match
+ if (getMethodName(i).equals(originalName)
+ && Arrays.equals(argTypes, mthds[i].getParameterTypes()))
+ return i;
+ }
+
+ throw new NoSuchMethodException("Method " + originalName
+ + " not found");
+ }
+}
diff --git a/src/main/javassist/tools/reflect/Compiler.java b/src/main/javassist/tools/reflect/Compiler.java
new file mode 100644
index 0000000..5375584
--- /dev/null
+++ b/src/main/javassist/tools/reflect/Compiler.java
@@ -0,0 +1,162 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.tools.reflect;
+
+import javassist.CtClass;
+import javassist.ClassPool;
+import java.io.PrintStream;
+
+class CompiledClass {
+ public String classname;
+ public String metaobject;
+ public String classobject;
+}
+
+/**
+ * A bytecode translator for reflection.
+ *
+ * <p>This translator directly modifies class files on a local disk so that
+ * the classes represented by those class files are reflective.
+ * After the modification, the class files can be run with the standard JVM
+ * without <code>javassist.tools.reflect.Loader</code>
+ * or any other user-defined class loader.
+ *
+ * <p>The modified class files are given as the command-line parameters,
+ * which are a sequence of fully-qualified class names followed by options:
+ *
+ * <p><code>-m <i>classname</i></code> : specifies the class of the
+ * metaobjects associated with instances of the class followed by
+ * this option. The default is <code>javassit.reflect.Metaobject</code>.
+ *
+ * <p><code>-c <i>classname</i></code> : specifies the class of the
+ * class metaobjects associated with instances of the class followed by
+ * this option. The default is <code>javassit.reflect.ClassMetaobject</code>.
+ *
+ * <p>If a class name is not followed by any options, the class indicated
+ * by that class name is not reflective.
+ *
+ * <p>For example,
+ * <ul><pre>% java Compiler Dog -m MetaDog -c CMetaDog Cat -m MetaCat Cow
+ * </pre></ul>
+ *
+ * <p>modifies class files <code>Dog.class</code>, <code>Cat.class</code>,
+ * and <code>Cow.class</code>.
+ * The metaobject of a Dog object is a MetaDog object and the class
+ * metaobject is a CMetaDog object.
+ * The metaobject of a Cat object is a MetaCat object but
+ * the class metaobject is a default one.
+ * Cow objects are not reflective.
+ *
+ * <p>Note that if the super class is also made reflective, it must be done
+ * before the sub class.
+ *
+ * @see javassist.tools.reflect.Metaobject
+ * @see javassist.tools.reflect.ClassMetaobject
+ * @see javassist.tools.reflect.Reflection
+ */
+public class Compiler {
+
+ public static void main(String[] args) throws Exception {
+ if (args.length == 0) {
+ help(System.err);
+ return;
+ }
+
+ CompiledClass[] entries = new CompiledClass[args.length];
+ int n = parse(args, entries);
+
+ if (n < 1) {
+ System.err.println("bad parameter.");
+ return;
+ }
+
+ processClasses(entries, n);
+ }
+
+ private static void processClasses(CompiledClass[] entries, int n)
+ throws Exception
+ {
+ Reflection implementor = new Reflection();
+ ClassPool pool = ClassPool.getDefault();
+ implementor.start(pool);
+
+ for (int i = 0; i < n; ++i) {
+ CtClass c = pool.get(entries[i].classname);
+ if (entries[i].metaobject != null
+ || entries[i].classobject != null) {
+ String metaobj, classobj;
+
+ if (entries[i].metaobject == null)
+ metaobj = "javassist.tools.reflect.Metaobject";
+ else
+ metaobj = entries[i].metaobject;
+
+ if (entries[i].classobject == null)
+ classobj = "javassist.tools.reflect.ClassMetaobject";
+ else
+ classobj = entries[i].classobject;
+
+ if (!implementor.makeReflective(c, pool.get(metaobj),
+ pool.get(classobj)))
+ System.err.println("Warning: " + c.getName()
+ + " is reflective. It was not changed.");
+
+ System.err.println(c.getName() + ": " + metaobj + ", "
+ + classobj);
+ }
+ else
+ System.err.println(c.getName() + ": not reflective");
+ }
+
+ for (int i = 0; i < n; ++i) {
+ implementor.onLoad(pool, entries[i].classname);
+ pool.get(entries[i].classname).writeFile();
+ }
+ }
+
+ private static int parse(String[] args, CompiledClass[] result) {
+ int n = -1;
+ for (int i = 0; i < args.length; ++i) {
+ String a = args[i];
+ if (a.equals("-m"))
+ if (n < 0 || i + 1 > args.length)
+ return -1;
+ else
+ result[n].metaobject = args[++i];
+ else if (a.equals("-c"))
+ if (n < 0 || i + 1 > args.length)
+ return -1;
+ else
+ result[n].classobject = args[++i];
+ else if (a.charAt(0) == '-')
+ return -1;
+ else {
+ CompiledClass cc = new CompiledClass();
+ cc.classname = a;
+ cc.metaobject = null;
+ cc.classobject = null;
+ result[++n] = cc;
+ }
+ }
+
+ return n + 1;
+ }
+
+ private static void help(PrintStream out) {
+ out.println("Usage: java javassist.tools.reflect.Compiler");
+ out.println(" (<class> [-m <metaobject>] [-c <class metaobject>])+");
+ }
+}
diff --git a/src/main/javassist/tools/reflect/Loader.java b/src/main/javassist/tools/reflect/Loader.java
new file mode 100644
index 0000000..a9aef4b
--- /dev/null
+++ b/src/main/javassist/tools/reflect/Loader.java
@@ -0,0 +1,163 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.tools.reflect;
+
+import javassist.CannotCompileException;
+import javassist.NotFoundException;
+import javassist.ClassPool;
+
+/**
+ * A class loader for reflection.
+ *
+ * <p>To run a program, say <code>MyApp</code>,
+ * including a reflective class,
+ * you must write a start-up program as follows:
+ *
+ * <ul><pre>
+ * public class Main {
+ * public static void main(String[] args) throws Throwable {
+ * javassist.tools.reflect.Loader cl
+ * = (javassist.tools.reflect.Loader)Main.class.getClassLoader();
+ * cl.makeReflective("Person", "MyMetaobject",
+ * "javassist.tools.reflect.ClassMetaobject");
+ * cl.run("MyApp", args);
+ * }
+ * }
+ * </pre></ul>
+ *
+ * <p>Then run this program as follows:
+ *
+ * <ul><pre>% java javassist.tools.reflect.Loader Main arg1, ...</pre></ul>
+ *
+ * <p>This command runs <code>Main.main()</code> with <code>arg1</code>, ...
+ * and <code>Main.main()</code> runs <code>MyApp.main()</code> with
+ * <code>arg1</code>, ...
+ * The <code>Person</code> class is modified
+ * to be a reflective class. Method calls on a <code>Person</code>
+ * object are intercepted by an instance of <code>MyMetaobject</code>.
+ *
+ * <p>Also, you can run <code>MyApp</code> in a slightly different way:
+ *
+ * <ul><pre>
+ * public class Main2 {
+ * public static void main(String[] args) throws Throwable {
+ * javassist.tools.reflect.Loader cl = new javassist.tools.reflect.Loader();
+ * cl.makeReflective("Person", "MyMetaobject",
+ * "javassist.tools.reflect.ClassMetaobject");
+ * cl.run("MyApp", args);
+ * }
+ * }
+ * </pre></ul>
+ *
+ * <p>This program is run as follows:
+ *
+ * <ul><pre>% java Main2 arg1, ...</pre></ul>
+ *
+ * <p>The difference from the former one is that the class <code>Main</code>
+ * is loaded by <code>javassist.tools.reflect.Loader</code> whereas the class
+ * <code>Main2</code> is not. Thus, <code>Main</code> belongs
+ * to the same name space (security domain) as <code>MyApp</code>
+ * whereas <code>Main2</code> does not; <code>Main2</code> belongs
+ * to the same name space as <code>javassist.tools.reflect.Loader</code>.
+ * For more details,
+ * see the notes in the manual page of <code>javassist.Loader</code>.
+ *
+ * <p>The class <code>Main2</code> is equivalent to this class:
+ *
+ * <ul><pre>
+ * public class Main3 {
+ * public static void main(String[] args) throws Throwable {
+ * Reflection reflection = new Reflection();
+ * javassist.Loader cl
+ * = new javassist.Loader(ClassPool.getDefault(reflection));
+ * reflection.makeReflective("Person", "MyMetaobject",
+ * "javassist.tools.reflect.ClassMetaobject");
+ * cl.run("MyApp", args);
+ * }
+ * }
+ * </pre></ul>
+ *
+ * <p><b>Note:</b>
+ *
+ * <p><code>javassist.tools.reflect.Loader</code> does not make a class reflective
+ * if that class is in a <code>java.*</code> or
+ * <code>javax.*</code> pacakge because of the specifications
+ * on the class loading algorithm of Java. The JVM does not allow to
+ * load such a system class with a user class loader.
+ *
+ * <p>To avoid this limitation, those classes should be statically
+ * modified with <code>javassist.tools.reflect.Compiler</code> and the original
+ * class files should be replaced.
+ *
+ * @see javassist.tools.reflect.Reflection
+ * @see javassist.tools.reflect.Compiler
+ * @see javassist.Loader
+ */
+public class Loader extends javassist.Loader {
+ protected Reflection reflection;
+
+ /**
+ * Loads a class with an instance of <code>Loader</code>
+ * and calls <code>main()</code> in that class.
+ *
+ * @param args command line parameters.
+ * <ul>
+ * <code>args[0]</code> is the class name to be loaded.
+ * <br><code>args[1..n]</code> are parameters passed
+ * to the target <code>main()</code>.
+ * </ul>
+ */
+ public static void main(String[] args) throws Throwable {
+ Loader cl = new Loader();
+ cl.run(args);
+ }
+
+ /**
+ * Constructs a new class loader.
+ */
+ public Loader() throws CannotCompileException, NotFoundException {
+ super();
+ delegateLoadingOf("javassist.tools.reflect.Loader");
+
+ reflection = new Reflection();
+ ClassPool pool = ClassPool.getDefault();
+ addTranslator(pool, reflection);
+ }
+
+ /**
+ * Produces a reflective class.
+ * If the super class is also made reflective, it must be done
+ * before the sub class.
+ *
+ * @param clazz the reflective class.
+ * @param metaobject the class of metaobjects.
+ * It must be a subclass of
+ * <code>Metaobject</code>.
+ * @param metaclass the class of the class metaobject.
+ * It must be a subclass of
+ * <code>ClassMetaobject</code>.
+ * @return <code>false</code> if the class is already reflective.
+ *
+ * @see javassist.tools.reflect.Metaobject
+ * @see javassist.tools.reflect.ClassMetaobject
+ */
+ public boolean makeReflective(String clazz,
+ String metaobject, String metaclass)
+ throws CannotCompileException, NotFoundException
+ {
+ return reflection.makeReflective(clazz, metaobject, metaclass);
+ }
+}
diff --git a/src/main/javassist/tools/reflect/Metalevel.java b/src/main/javassist/tools/reflect/Metalevel.java
new file mode 100644
index 0000000..4361fac
--- /dev/null
+++ b/src/main/javassist/tools/reflect/Metalevel.java
@@ -0,0 +1,38 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.tools.reflect;
+
+/**
+ * An interface to access a metaobject and a class metaobject.
+ * This interface is implicitly implemented by the reflective
+ * class.
+ */
+public interface Metalevel {
+ /**
+ * Obtains the class metaobject associated with this object.
+ */
+ ClassMetaobject _getClass();
+
+ /**
+ * Obtains the metaobject associated with this object.
+ */
+ Metaobject _getMetaobject();
+
+ /**
+ * Changes the metaobject associated with this object.
+ */
+ void _setMetaobject(Metaobject m);
+}
diff --git a/src/main/javassist/tools/reflect/Metaobject.java b/src/main/javassist/tools/reflect/Metaobject.java
new file mode 100644
index 0000000..cd3a5f5
--- /dev/null
+++ b/src/main/javassist/tools/reflect/Metaobject.java
@@ -0,0 +1,236 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.tools.reflect;
+
+import java.lang.reflect.Method;
+import java.io.Serializable;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+/**
+ * A runtime metaobject.
+ *
+ * <p>A <code>Metaobject</code> is created for
+ * every object at the base level. A different reflective object is
+ * associated with a different metaobject.
+ *
+ * <p>The metaobject intercepts method calls
+ * on the reflective object at the base-level. To change the behavior
+ * of the method calls, a subclass of <code>Metaobject</code>
+ * should be defined.
+ *
+ * <p>To obtain a metaobject, calls <code>_getMetaobject()</code>
+ * on a reflective object. For example,
+ *
+ * <ul><pre>Metaobject m = ((Metalevel)reflectiveObject)._getMetaobject();
+ * </pre></ul>
+ *
+ * @see javassist.tools.reflect.ClassMetaobject
+ * @see javassist.tools.reflect.Metalevel
+ */
+public class Metaobject implements Serializable {
+ protected ClassMetaobject classmetaobject;
+ protected Metalevel baseobject;
+ protected Method[] methods;
+
+ /**
+ * Constructs a <code>Metaobject</code>. The metaobject is
+ * constructed before the constructor is called on the base-level
+ * object.
+ *
+ * @param self the object that this metaobject is associated with.
+ * @param args the parameters passed to the constructor of
+ * <code>self</code>.
+ */
+ public Metaobject(Object self, Object[] args) {
+ baseobject = (Metalevel)self;
+ classmetaobject = baseobject._getClass();
+ methods = classmetaobject.getReflectiveMethods();
+ }
+
+ /**
+ * Constructs a <code>Metaobject</code> without initialization.
+ * If calling this constructor, a subclass should be responsible
+ * for initialization.
+ */
+ protected Metaobject() {
+ baseobject = null;
+ classmetaobject = null;
+ methods = null;
+ }
+
+ private void writeObject(ObjectOutputStream out) throws IOException {
+ out.writeObject(baseobject);
+ }
+
+ private void readObject(ObjectInputStream in)
+ throws IOException, ClassNotFoundException
+ {
+ baseobject = (Metalevel)in.readObject();
+ classmetaobject = baseobject._getClass();
+ methods = classmetaobject.getReflectiveMethods();
+ }
+
+ /**
+ * Obtains the class metaobject associated with this metaobject.
+ *
+ * @see javassist.tools.reflect.ClassMetaobject
+ */
+ public final ClassMetaobject getClassMetaobject() {
+ return classmetaobject;
+ }
+
+ /**
+ * Obtains the object controlled by this metaobject.
+ */
+ public final Object getObject() {
+ return baseobject;
+ }
+
+ /**
+ * Changes the object controlled by this metaobject.
+ *
+ * @param self the object
+ */
+ public final void setObject(Object self) {
+ baseobject = (Metalevel)self;
+ classmetaobject = baseobject._getClass();
+ methods = classmetaobject.getReflectiveMethods();
+
+ // call _setMetaobject() after the metaobject is settled.
+ baseobject._setMetaobject(this);
+ }
+
+ /**
+ * Returns the name of the method specified
+ * by <code>identifier</code>.
+ */
+ public final String getMethodName(int identifier) {
+ String mname = methods[identifier].getName();
+ int j = ClassMetaobject.methodPrefixLen;
+ for (;;) {
+ char c = mname.charAt(j++);
+ if (c < '0' || '9' < c)
+ break;
+ }
+
+ return mname.substring(j);
+ }
+
+ /**
+ * Returns an array of <code>Class</code> objects representing the
+ * formal parameter types of the method specified
+ * by <code>identifier</code>.
+ */
+ public final Class[] getParameterTypes(int identifier) {
+ return methods[identifier].getParameterTypes();
+ }
+
+ /**
+ * Returns a <code>Class</code> objects representing the
+ * return type of the method specified by <code>identifier</code>.
+ */
+ public final Class getReturnType(int identifier) {
+ return methods[identifier].getReturnType();
+ }
+
+ /**
+ * Is invoked when public fields of the base-level
+ * class are read and the runtime system intercepts it.
+ * This method simply returns the value of the field.
+ *
+ * <p>Every subclass of this class should redefine this method.
+ */
+ public Object trapFieldRead(String name) {
+ Class jc = getClassMetaobject().getJavaClass();
+ try {
+ return jc.getField(name).get(getObject());
+ }
+ catch (NoSuchFieldException e) {
+ throw new RuntimeException(e.toString());
+ }
+ catch (IllegalAccessException e) {
+ throw new RuntimeException(e.toString());
+ }
+ }
+
+ /**
+ * Is invoked when public fields of the base-level
+ * class are modified and the runtime system intercepts it.
+ * This method simply sets the field to the given value.
+ *
+ * <p>Every subclass of this class should redefine this method.
+ */
+ public void trapFieldWrite(String name, Object value) {
+ Class jc = getClassMetaobject().getJavaClass();
+ try {
+ jc.getField(name).set(getObject(), value);
+ }
+ catch (NoSuchFieldException e) {
+ throw new RuntimeException(e.toString());
+ }
+ catch (IllegalAccessException e) {
+ throw new RuntimeException(e.toString());
+ }
+ }
+
+ /**
+ * Is invoked when base-level method invocation is intercepted.
+ * This method simply executes the intercepted method invocation
+ * with the original parameters and returns the resulting value.
+ *
+ * <p>Every subclass of this class should redefine this method.
+ *
+ * <p>Note: this method is not invoked if the base-level method
+ * is invoked by a constructor in the super class. For example,
+ *
+ * <ul><pre>abstract class A {
+ * abstract void initialize();
+ * A() {
+ * initialize(); // not intercepted
+ * }
+ * }
+ *
+ * class B extends A {
+ * void initialize() { System.out.println("initialize()"); }
+ * B() {
+ * super();
+ * initialize(); // intercepted
+ * }
+ * }</pre></ul>
+ *
+ * <p>if an instance of B is created,
+ * the invocation of initialize() in B is intercepted only once.
+ * The first invocation by the constructor in A is not intercepted.
+ * This is because the link between a base-level object and a
+ * metaobject is not created until the execution of a
+ * constructor of the super class finishes.
+ */
+ public Object trapMethodcall(int identifier, Object[] args)
+ throws Throwable
+ {
+ try {
+ return methods[identifier].invoke(getObject(), args);
+ }
+ catch (java.lang.reflect.InvocationTargetException e) {
+ throw e.getTargetException();
+ }
+ catch (java.lang.IllegalAccessException e) {
+ throw new CannotInvokeException(e);
+ }
+ }
+}
diff --git a/src/main/javassist/tools/reflect/Reflection.java b/src/main/javassist/tools/reflect/Reflection.java
new file mode 100644
index 0000000..cb93096
--- /dev/null
+++ b/src/main/javassist/tools/reflect/Reflection.java
@@ -0,0 +1,384 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.tools.reflect;
+
+import javassist.*;
+import javassist.CtMethod.ConstParameter;
+
+/**
+ * The class implementing the behavioral reflection mechanism.
+ *
+ * <p>If a class is reflective,
+ * then all the method invocations on every
+ * instance of that class are intercepted by the runtime
+ * metaobject controlling that instance. The methods inherited from the
+ * super classes are also intercepted except final methods. To intercept
+ * a final method in a super class, that super class must be also reflective.
+ *
+ * <p>To do this, the original class file representing a reflective class:
+ *
+ * <ul><pre>
+ * class Person {
+ * public int f(int i) { return i + 1; }
+ * public int value;
+ * }
+ * </pre></ul>
+ *
+ * <p>is modified so that it represents a class:
+ *
+ * <ul><pre>
+ * class Person implements Metalevel {
+ * public int _original_f(int i) { return i + 1; }
+ * public int f(int i) { <i>delegate to the metaobject</i> }
+ *
+ * public int value;
+ * public int _r_value() { <i>read "value"</i> }
+ * public void _w_value(int v) { <i>write "value"</i> }
+ *
+ * public ClassMetaobject _getClass() { <i>return a class metaobject</i> }
+ * public Metaobject _getMetaobject() { <i>return a metaobject</i> }
+ * public void _setMetaobject(Metaobject m) { <i>change a metaobject</i> }
+ * }
+ * </pre></ul>
+ *
+ * @see javassist.tools.reflect.ClassMetaobject
+ * @see javassist.tools.reflect.Metaobject
+ * @see javassist.tools.reflect.Loader
+ * @see javassist.tools.reflect.Compiler
+ */
+public class Reflection implements Translator {
+
+ static final String classobjectField = "_classobject";
+ static final String classobjectAccessor = "_getClass";
+ static final String metaobjectField = "_metaobject";
+ static final String metaobjectGetter = "_getMetaobject";
+ static final String metaobjectSetter = "_setMetaobject";
+ static final String readPrefix = "_r_";
+ static final String writePrefix = "_w_";
+
+ static final String metaobjectClassName = "javassist.tools.reflect.Metaobject";
+ static final String classMetaobjectClassName
+ = "javassist.tools.reflect.ClassMetaobject";
+
+ protected CtMethod trapMethod, trapStaticMethod;
+ protected CtMethod trapRead, trapWrite;
+ protected CtClass[] readParam;
+
+ protected ClassPool classPool;
+ protected CodeConverter converter;
+
+ private boolean isExcluded(String name) {
+ return name.startsWith(ClassMetaobject.methodPrefix)
+ || name.equals(classobjectAccessor)
+ || name.equals(metaobjectSetter)
+ || name.equals(metaobjectGetter)
+ || name.startsWith(readPrefix)
+ || name.startsWith(writePrefix);
+ }
+
+ /**
+ * Constructs a new <code>Reflection</code> object.
+ */
+ public Reflection() {
+ classPool = null;
+ converter = new CodeConverter();
+ }
+
+ /**
+ * Initializes the object.
+ */
+ public void start(ClassPool pool) throws NotFoundException {
+ classPool = pool;
+ final String msg
+ = "javassist.tools.reflect.Sample is not found or broken.";
+ try {
+ CtClass c = classPool.get("javassist.tools.reflect.Sample");
+ trapMethod = c.getDeclaredMethod("trap");
+ trapStaticMethod = c.getDeclaredMethod("trapStatic");
+ trapRead = c.getDeclaredMethod("trapRead");
+ trapWrite = c.getDeclaredMethod("trapWrite");
+ readParam
+ = new CtClass[] { classPool.get("java.lang.Object") };
+ }
+ catch (NotFoundException e) {
+ throw new RuntimeException(msg);
+ }
+ }
+
+ /**
+ * Inserts hooks for intercepting accesses to the fields declared
+ * in reflective classes.
+ */
+ public void onLoad(ClassPool pool, String classname)
+ throws CannotCompileException, NotFoundException
+ {
+ CtClass clazz = pool.get(classname);
+ clazz.instrument(converter);
+ }
+
+ /**
+ * Produces a reflective class.
+ * If the super class is also made reflective, it must be done
+ * before the sub class.
+ *
+ * @param classname the name of the reflective class
+ * @param metaobject the class name of metaobjects.
+ * @param metaclass the class name of the class metaobject.
+ * @return <code>false</code> if the class is already reflective.
+ *
+ * @see javassist.tools.reflect.Metaobject
+ * @see javassist.tools.reflect.ClassMetaobject
+ */
+ public boolean makeReflective(String classname,
+ String metaobject, String metaclass)
+ throws CannotCompileException, NotFoundException
+ {
+ return makeReflective(classPool.get(classname),
+ classPool.get(metaobject),
+ classPool.get(metaclass));
+ }
+
+ /**
+ * Produces a reflective class.
+ * If the super class is also made reflective, it must be done
+ * before the sub class.
+ *
+ * @param clazz the reflective class.
+ * @param metaobject the class of metaobjects.
+ * It must be a subclass of
+ * <code>Metaobject</code>.
+ * @param metaclass the class of the class metaobject.
+ * It must be a subclass of
+ * <code>ClassMetaobject</code>.
+ * @return <code>false</code> if the class is already reflective.
+ *
+ * @see javassist.tools.reflect.Metaobject
+ * @see javassist.tools.reflect.ClassMetaobject
+ */
+ public boolean makeReflective(Class clazz,
+ Class metaobject, Class metaclass)
+ throws CannotCompileException, NotFoundException
+ {
+ return makeReflective(clazz.getName(), metaobject.getName(),
+ metaclass.getName());
+ }
+
+ /**
+ * Produces a reflective class. It modifies the given
+ * <code>CtClass</code> object and makes it reflective.
+ * If the super class is also made reflective, it must be done
+ * before the sub class.
+ *
+ * @param clazz the reflective class.
+ * @param metaobject the class of metaobjects.
+ * It must be a subclass of
+ * <code>Metaobject</code>.
+ * @param metaclass the class of the class metaobject.
+ * It must be a subclass of
+ * <code>ClassMetaobject</code>.
+ * @return <code>false</code> if the class is already reflective.
+ *
+ * @see javassist.tools.reflect.Metaobject
+ * @see javassist.tools.reflect.ClassMetaobject
+ */
+ public boolean makeReflective(CtClass clazz,
+ CtClass metaobject, CtClass metaclass)
+ throws CannotCompileException, CannotReflectException,
+ NotFoundException
+ {
+ if (clazz.isInterface())
+ throw new CannotReflectException(
+ "Cannot reflect an interface: " + clazz.getName());
+
+ if (clazz.subclassOf(classPool.get(classMetaobjectClassName)))
+ throw new CannotReflectException(
+ "Cannot reflect a subclass of ClassMetaobject: "
+ + clazz.getName());
+
+ if (clazz.subclassOf(classPool.get(metaobjectClassName)))
+ throw new CannotReflectException(
+ "Cannot reflect a subclass of Metaobject: "
+ + clazz.getName());
+
+ registerReflectiveClass(clazz);
+ return modifyClassfile(clazz, metaobject, metaclass);
+ }
+
+ /**
+ * Registers a reflective class. The field accesses to the instances
+ * of this class are instrumented.
+ */
+ private void registerReflectiveClass(CtClass clazz) {
+ CtField[] fs = clazz.getDeclaredFields();
+ for (int i = 0; i < fs.length; ++i) {
+ CtField f = fs[i];
+ int mod = f.getModifiers();
+ if ((mod & Modifier.PUBLIC) != 0 && (mod & Modifier.FINAL) == 0) {
+ String name = f.getName();
+ converter.replaceFieldRead(f, clazz, readPrefix + name);
+ converter.replaceFieldWrite(f, clazz, writePrefix + name);
+ }
+ }
+ }
+
+ private boolean modifyClassfile(CtClass clazz, CtClass metaobject,
+ CtClass metaclass)
+ throws CannotCompileException, NotFoundException
+ {
+ if (clazz.getAttribute("Reflective") != null)
+ return false; // this is already reflective.
+ else
+ clazz.setAttribute("Reflective", new byte[0]);
+
+ CtClass mlevel = classPool.get("javassist.tools.reflect.Metalevel");
+ boolean addMeta = !clazz.subtypeOf(mlevel);
+ if (addMeta)
+ clazz.addInterface(mlevel);
+
+ processMethods(clazz, addMeta);
+ processFields(clazz);
+
+ CtField f;
+ if (addMeta) {
+ f = new CtField(classPool.get("javassist.tools.reflect.Metaobject"),
+ metaobjectField, clazz);
+ f.setModifiers(Modifier.PROTECTED);
+ clazz.addField(f, CtField.Initializer.byNewWithParams(metaobject));
+
+ clazz.addMethod(CtNewMethod.getter(metaobjectGetter, f));
+ clazz.addMethod(CtNewMethod.setter(metaobjectSetter, f));
+ }
+
+ f = new CtField(classPool.get("javassist.tools.reflect.ClassMetaobject"),
+ classobjectField, clazz);
+ f.setModifiers(Modifier.PRIVATE | Modifier.STATIC);
+ clazz.addField(f, CtField.Initializer.byNew(metaclass,
+ new String[] { clazz.getName() }));
+
+ clazz.addMethod(CtNewMethod.getter(classobjectAccessor, f));
+ return true;
+ }
+
+ private void processMethods(CtClass clazz, boolean dontSearch)
+ throws CannotCompileException, NotFoundException
+ {
+ CtMethod[] ms = clazz.getMethods();
+ for (int i = 0; i < ms.length; ++i) {
+ CtMethod m = ms[i];
+ int mod = m.getModifiers();
+ if (Modifier.isPublic(mod) && !Modifier.isAbstract(mod))
+ processMethods0(mod, clazz, m, i, dontSearch);
+ }
+ }
+
+ private void processMethods0(int mod, CtClass clazz,
+ CtMethod m, int identifier, boolean dontSearch)
+ throws CannotCompileException, NotFoundException
+ {
+ CtMethod body;
+ String name = m.getName();
+
+ if (isExcluded(name)) // internally-used method inherited
+ return; // from a reflective class.
+
+ CtMethod m2;
+ if (m.getDeclaringClass() == clazz) {
+ if (Modifier.isNative(mod))
+ return;
+
+ m2 = m;
+ if (Modifier.isFinal(mod)) {
+ mod &= ~Modifier.FINAL;
+ m2.setModifiers(mod);
+ }
+ }
+ else {
+ if (Modifier.isFinal(mod))
+ return;
+
+ mod &= ~Modifier.NATIVE;
+ m2 = CtNewMethod.delegator(findOriginal(m, dontSearch), clazz);
+ m2.setModifiers(mod);
+ clazz.addMethod(m2);
+ }
+
+ m2.setName(ClassMetaobject.methodPrefix + identifier
+ + "_" + name);
+
+ if (Modifier.isStatic(mod))
+ body = trapStaticMethod;
+ else
+ body = trapMethod;
+
+ CtMethod wmethod
+ = CtNewMethod.wrapped(m.getReturnType(), name,
+ m.getParameterTypes(), m.getExceptionTypes(),
+ body, ConstParameter.integer(identifier),
+ clazz);
+ wmethod.setModifiers(mod);
+ clazz.addMethod(wmethod);
+ }
+
+ private CtMethod findOriginal(CtMethod m, boolean dontSearch)
+ throws NotFoundException
+ {
+ if (dontSearch)
+ return m;
+
+ String name = m.getName();
+ CtMethod[] ms = m.getDeclaringClass().getDeclaredMethods();
+ for (int i = 0; i < ms.length; ++i) {
+ String orgName = ms[i].getName();
+ if (orgName.endsWith(name)
+ && orgName.startsWith(ClassMetaobject.methodPrefix)
+ && ms[i].getSignature().equals(m.getSignature()))
+ return ms[i];
+ }
+
+ return m;
+ }
+
+ private void processFields(CtClass clazz)
+ throws CannotCompileException, NotFoundException
+ {
+ CtField[] fs = clazz.getDeclaredFields();
+ for (int i = 0; i < fs.length; ++i) {
+ CtField f = fs[i];
+ int mod = f.getModifiers();
+ if ((mod & Modifier.PUBLIC) != 0 && (mod & Modifier.FINAL) == 0) {
+ mod |= Modifier.STATIC;
+ String name = f.getName();
+ CtClass ftype = f.getType();
+ CtMethod wmethod
+ = CtNewMethod.wrapped(ftype, readPrefix + name,
+ readParam, null, trapRead,
+ ConstParameter.string(name),
+ clazz);
+ wmethod.setModifiers(mod);
+ clazz.addMethod(wmethod);
+ CtClass[] writeParam = new CtClass[2];
+ writeParam[0] = classPool.get("java.lang.Object");
+ writeParam[1] = ftype;
+ wmethod = CtNewMethod.wrapped(CtClass.voidType,
+ writePrefix + name,
+ writeParam, null, trapWrite,
+ ConstParameter.string(name), clazz);
+ wmethod.setModifiers(mod);
+ clazz.addMethod(wmethod);
+ }
+ }
+ }
+}
diff --git a/src/main/javassist/tools/reflect/Sample.java b/src/main/javassist/tools/reflect/Sample.java
new file mode 100644
index 0000000..d76df19
--- /dev/null
+++ b/src/main/javassist/tools/reflect/Sample.java
@@ -0,0 +1,56 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.tools.reflect;
+
+/**
+ * A template used for defining a reflective class.
+ */
+public class Sample {
+ private Metaobject _metaobject;
+ private static ClassMetaobject _classobject;
+
+ public Object trap(Object[] args, int identifier) throws Throwable {
+ Metaobject mobj;
+ mobj = _metaobject;
+ if (mobj == null)
+ return ClassMetaobject.invoke(this, identifier, args);
+ else
+ return mobj.trapMethodcall(identifier, args);
+ }
+
+ public static Object trapStatic(Object[] args, int identifier)
+ throws Throwable
+ {
+ return _classobject.trapMethodcall(identifier, args);
+ }
+
+ public static Object trapRead(Object[] args, String name) {
+ if (args[0] == null)
+ return _classobject.trapFieldRead(name);
+ else
+ return ((Metalevel)args[0])._getMetaobject().trapFieldRead(name);
+ }
+
+ public static Object trapWrite(Object[] args, String name) {
+ Metalevel base = (Metalevel)args[0];
+ if (base == null)
+ _classobject.trapFieldWrite(name, args[1]);
+ else
+ base._getMetaobject().trapFieldWrite(name, args[1]);
+
+ return null;
+ }
+}
diff --git a/src/main/javassist/tools/reflect/package.html b/src/main/javassist/tools/reflect/package.html
new file mode 100644
index 0000000..10a4196
--- /dev/null
+++ b/src/main/javassist/tools/reflect/package.html
@@ -0,0 +1,35 @@
+<html>
+<body>
+Runtime Behavioral Reflection.
+
+<p>(also recently known as interceptors or AOP?)
+
+<p>This package enables a metaobject to trap method calls and field
+accesses on a regular Java object. It provides a class
+<code>Reflection</code>, which is a main module for implementing
+runtime behavioral reflection.
+It also provides
+a class <code>Loader</code> and <code>Compiler</code>
+as utilities for dynamically or statically
+translating a regular class into a reflective class.
+
+<p>An instance of the reflective class is associated with
+a runtime metaobject and a runtime class metaobject, which control
+the behavior of that instance.
+The runtime
+metaobject is created for every (base-level) instance but the
+runtime class metaobject is created for every (base-level) class.
+<code>Metaobject</code> is the root class of the runtime
+metaobject and <code>ClassMetaobject</code> is the root class
+of the runtime class metaobject.
+
+<p>This package is provided as a sample implementation of the
+reflection mechanism with Javassist. All the programs in this package
+uses only the regular Javassist API; they never call any hidden
+methods.
+
+<p>The most significant class in this package is <code>Reflection</code>.
+See the description of this class first.
+
+</body>
+</html>
diff --git a/src/main/javassist/tools/rmi/AppletServer.java b/src/main/javassist/tools/rmi/AppletServer.java
new file mode 100644
index 0000000..b2678f3
--- /dev/null
+++ b/src/main/javassist/tools/rmi/AppletServer.java
@@ -0,0 +1,250 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.tools.rmi;
+
+import java.io.*;
+
+import javassist.tools.web.*;
+import javassist.CannotCompileException;
+import javassist.NotFoundException;
+import javassist.ClassPool;
+import java.lang.reflect.Method;
+import java.util.Hashtable;
+import java.util.Vector;
+
+/**
+ * An AppletServer object is a web server that an ObjectImporter
+ * communicates with. It makes the objects specified by
+ * <code>exportObject()</code> remotely accessible from applets.
+ * If the classes of the exported objects are requested by the client-side
+ * JVM, this web server sends proxy classes for the requested classes.
+ *
+ * @see javassist.tools.rmi.ObjectImporter
+ */
+public class AppletServer extends Webserver {
+ private StubGenerator stubGen;
+ private Hashtable exportedNames;
+ private Vector exportedObjects;
+
+ private static final byte[] okHeader
+ = "HTTP/1.0 200 OK\r\n\r\n".getBytes();
+
+ /**
+ * Constructs a web server.
+ *
+ * @param port port number
+ */
+ public AppletServer(String port)
+ throws IOException, NotFoundException, CannotCompileException
+ {
+ this(Integer.parseInt(port));
+ }
+
+ /**
+ * Constructs a web server.
+ *
+ * @param port port number
+ */
+ public AppletServer(int port)
+ throws IOException, NotFoundException, CannotCompileException
+ {
+ this(ClassPool.getDefault(), new StubGenerator(), port);
+ }
+
+ /**
+ * Constructs a web server.
+ *
+ * @param port port number
+ * @param src the source of classs files.
+ */
+ public AppletServer(int port, ClassPool src)
+ throws IOException, NotFoundException, CannotCompileException
+ {
+ this(new ClassPool(src), new StubGenerator(), port);
+ }
+
+ private AppletServer(ClassPool loader, StubGenerator gen, int port)
+ throws IOException, NotFoundException, CannotCompileException
+ {
+ super(port);
+ exportedNames = new Hashtable();
+ exportedObjects = new Vector();
+ stubGen = gen;
+ addTranslator(loader, gen);
+ }
+
+ /**
+ * Begins the HTTP service.
+ */
+ public void run() {
+ super.run();
+ }
+
+ /**
+ * Exports an object.
+ * This method produces the bytecode of the proxy class used
+ * to access the exported object. A remote applet can load
+ * the proxy class and call a method on the exported object.
+ *
+ * @param name the name used for looking the object up.
+ * @param obj the exported object.
+ * @return the object identifier
+ *
+ * @see javassist.tools.rmi.ObjectImporter#lookupObject(String)
+ */
+ public synchronized int exportObject(String name, Object obj)
+ throws CannotCompileException
+ {
+ Class clazz = obj.getClass();
+ ExportedObject eo = new ExportedObject();
+ eo.object = obj;
+ eo.methods = clazz.getMethods();
+ exportedObjects.addElement(eo);
+ eo.identifier = exportedObjects.size() - 1;
+ if (name != null)
+ exportedNames.put(name, eo);
+
+ try {
+ stubGen.makeProxyClass(clazz);
+ }
+ catch (NotFoundException e) {
+ throw new CannotCompileException(e);
+ }
+
+ return eo.identifier;
+ }
+
+ /**
+ * Processes a request from a web browser (an ObjectImporter).
+ */
+ public void doReply(InputStream in, OutputStream out, String cmd)
+ throws IOException, BadHttpRequest
+ {
+ if (cmd.startsWith("POST /rmi "))
+ processRMI(in, out);
+ else if (cmd.startsWith("POST /lookup "))
+ lookupName(cmd, in, out);
+ else
+ super.doReply(in, out, cmd);
+ }
+
+ private void processRMI(InputStream ins, OutputStream outs)
+ throws IOException
+ {
+ ObjectInputStream in = new ObjectInputStream(ins);
+
+ int objectId = in.readInt();
+ int methodId = in.readInt();
+ Exception err = null;
+ Object rvalue = null;
+ try {
+ ExportedObject eo
+ = (ExportedObject)exportedObjects.elementAt(objectId);
+ Object[] args = readParameters(in);
+ rvalue = convertRvalue(eo.methods[methodId].invoke(eo.object,
+ args));
+ }
+ catch(Exception e) {
+ err = e;
+ logging2(e.toString());
+ }
+
+ outs.write(okHeader);
+ ObjectOutputStream out = new ObjectOutputStream(outs);
+ if (err != null) {
+ out.writeBoolean(false);
+ out.writeUTF(err.toString());
+ }
+ else
+ try {
+ out.writeBoolean(true);
+ out.writeObject(rvalue);
+ }
+ catch (NotSerializableException e) {
+ logging2(e.toString());
+ }
+ catch (InvalidClassException e) {
+ logging2(e.toString());
+ }
+
+ out.flush();
+ out.close();
+ in.close();
+ }
+
+ private Object[] readParameters(ObjectInputStream in)
+ throws IOException, ClassNotFoundException
+ {
+ int n = in.readInt();
+ Object[] args = new Object[n];
+ for (int i = 0; i < n; ++i) {
+ Object a = in.readObject();
+ if (a instanceof RemoteRef) {
+ RemoteRef ref = (RemoteRef)a;
+ ExportedObject eo
+ = (ExportedObject)exportedObjects.elementAt(ref.oid);
+ a = eo.object;
+ }
+
+ args[i] = a;
+ }
+
+ return args;
+ }
+
+ private Object convertRvalue(Object rvalue)
+ throws CannotCompileException
+ {
+ if (rvalue == null)
+ return null; // the return type is void.
+
+ String classname = rvalue.getClass().getName();
+ if (stubGen.isProxyClass(classname))
+ return new RemoteRef(exportObject(null, rvalue), classname);
+ else
+ return rvalue;
+ }
+
+ private void lookupName(String cmd, InputStream ins, OutputStream outs)
+ throws IOException
+ {
+ ObjectInputStream in = new ObjectInputStream(ins);
+ String name = DataInputStream.readUTF(in);
+ ExportedObject found = (ExportedObject)exportedNames.get(name);
+ outs.write(okHeader);
+ ObjectOutputStream out = new ObjectOutputStream(outs);
+ if (found == null) {
+ logging2(name + "not found.");
+ out.writeInt(-1); // error code
+ out.writeUTF("error");
+ }
+ else {
+ logging2(name);
+ out.writeInt(found.identifier);
+ out.writeUTF(found.object.getClass().getName());
+ }
+
+ out.flush();
+ out.close();
+ in.close();
+ }
+}
+
+class ExportedObject {
+ public int identifier;
+ public Object object;
+ public Method[] methods;
+}
diff --git a/src/main/javassist/tools/rmi/ObjectImporter.java b/src/main/javassist/tools/rmi/ObjectImporter.java
new file mode 100644
index 0000000..e6692ef
--- /dev/null
+++ b/src/main/javassist/tools/rmi/ObjectImporter.java
@@ -0,0 +1,298 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.tools.rmi;
+
+import java.io.*;
+import java.net.*;
+import java.applet.Applet;
+import java.lang.reflect.*;
+
+/**
+ * The object importer enables applets to call a method on a remote
+ * object running on the <code>Webserver</code> (the <b>main</b> class of this
+ * package).
+ *
+ * <p>To access the remote
+ * object, the applet first calls <code>lookupObject()</code> and
+ * obtains a proxy object, which is a reference to that object.
+ * The class name of the proxy object is identical to that of
+ * the remote object.
+ * The proxy object provides the same set of methods as the remote object.
+ * If one of the methods is invoked on the proxy object,
+ * the invocation is delegated to the remote object.
+ * From the viewpoint of the applet, therefore, the two objects are
+ * identical. The applet can access the object on the server
+ * with the regular Java syntax without concern about the actual
+ * location.
+ *
+ * <p>The methods remotely called by the applet must be <code>public</code>.
+ * This is true even if the applet's class and the remote object's classs
+ * belong to the same package.
+ *
+ * <p>If class X is a class of remote objects, a subclass of X must be
+ * also a class of remote objects. On the other hand, this restriction
+ * is not applied to the superclass of X. The class X does not have to
+ * contain a constructor taking no arguments.
+ *
+ * <p>The parameters to a remote method is passed in the <i>call-by-value</i>
+ * manner. Thus all the parameter classes must implement
+ * <code>java.io.Serializable</code>. However, if the parameter is the
+ * proxy object, the reference to the remote object instead of a copy of
+ * the object is passed to the method.
+ *
+ * <p>Because of the limitations of the current implementation,
+ * <ul>
+ * <li>The parameter objects cannot contain the proxy
+ * object as a field value.
+ * <li>If class <code>C</code> is of the remote object, then
+ * the applet cannot instantiate <code>C</code> locally or remotely.
+ * </ul>
+ *
+ * <p>All the exceptions thrown by the remote object are converted
+ * into <code>RemoteException</code>. Since this exception is a subclass
+ * of <code>RuntimeException</code>, the caller method does not need
+ * to catch the exception. However, good programs should catch
+ * the <code>RuntimeException</code>.
+ *
+ * @see javassist.tools.rmi.AppletServer
+ * @see javassist.tools.rmi.RemoteException
+ * @see javassist.tools.web.Viewer
+ */
+public class ObjectImporter implements java.io.Serializable {
+ private final byte[] endofline = { 0x0d, 0x0a };
+ private String servername, orgServername;
+ private int port, orgPort;
+
+ protected byte[] lookupCommand = "POST /lookup HTTP/1.0".getBytes();
+ protected byte[] rmiCommand = "POST /rmi HTTP/1.0".getBytes();
+
+ /**
+ * Constructs an object importer.
+ *
+ * <p>Remote objects are imported from the web server that the given
+ * applet has been loaded from.
+ *
+ * @param applet the applet loaded from the <code>Webserver</code>.
+ */
+ public ObjectImporter(Applet applet) {
+ URL codebase = applet.getCodeBase();
+ orgServername = servername = codebase.getHost();
+ orgPort = port = codebase.getPort();
+ }
+
+ /**
+ * Constructs an object importer.
+ *
+ * <p>If you run a program with <code>javassist.tools.web.Viewer</code>,
+ * you can construct an object importer as follows:
+ *
+ * <ul><pre>
+ * Viewer v = (Viewer)this.getClass().getClassLoader();
+ * ObjectImporter oi = new ObjectImporter(v.getServer(), v.getPort());
+ * </pre></ul>
+ *
+ * @see javassist.tools.web.Viewer
+ */
+ public ObjectImporter(String servername, int port) {
+ this.orgServername = this.servername = servername;
+ this.orgPort = this.port = port;
+ }
+
+ /**
+ * Finds the object exported by a server with the specified name.
+ * If the object is not found, this method returns null.
+ *
+ * @param name the name of the exported object.
+ * @return the proxy object or null.
+ */
+ public Object getObject(String name) {
+ try {
+ return lookupObject(name);
+ }
+ catch (ObjectNotFoundException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Sets an http proxy server. After this method is called, the object
+ * importer connects a server through the http proxy server.
+ */
+ public void setHttpProxy(String host, int port) {
+ String proxyHeader = "POST http://" + orgServername + ":" + orgPort;
+ String cmd = proxyHeader + "/lookup HTTP/1.0";
+ lookupCommand = cmd.getBytes();
+ cmd = proxyHeader + "/rmi HTTP/1.0";
+ rmiCommand = cmd.getBytes();
+ this.servername = host;
+ this.port = port;
+ }
+
+ /**
+ * Finds the object exported by the server with the specified name.
+ * It sends a POST request to the server (via an http proxy server
+ * if needed).
+ *
+ * @param name the name of the exported object.
+ * @return the proxy object.
+ */
+ public Object lookupObject(String name) throws ObjectNotFoundException
+ {
+ try {
+ Socket sock = new Socket(servername, port);
+ OutputStream out = sock.getOutputStream();
+ out.write(lookupCommand);
+ out.write(endofline);
+ out.write(endofline);
+
+ ObjectOutputStream dout = new ObjectOutputStream(out);
+ dout.writeUTF(name);
+ dout.flush();
+
+ InputStream in = new BufferedInputStream(sock.getInputStream());
+ skipHeader(in);
+ ObjectInputStream din = new ObjectInputStream(in);
+ int n = din.readInt();
+ String classname = din.readUTF();
+ din.close();
+ dout.close();
+ sock.close();
+
+ if (n >= 0)
+ return createProxy(n, classname);
+ }
+ catch (Exception e) {
+ e.printStackTrace();
+ throw new ObjectNotFoundException(name, e);
+ }
+
+ throw new ObjectNotFoundException(name);
+ }
+
+ private static final Class[] proxyConstructorParamTypes
+ = new Class[] { ObjectImporter.class, int.class };
+
+ private Object createProxy(int oid, String classname) throws Exception {
+ Class c = Class.forName(classname);
+ Constructor cons = c.getConstructor(proxyConstructorParamTypes);
+ return cons.newInstance(new Object[] { this, new Integer(oid) });
+ }
+
+ /**
+ * Calls a method on a remote object.
+ * It sends a POST request to the server (via an http proxy server
+ * if needed).
+ *
+ * <p>This method is called by only proxy objects.
+ */
+ public Object call(int objectid, int methodid, Object[] args)
+ throws RemoteException
+ {
+ boolean result;
+ Object rvalue;
+ String errmsg;
+
+ try {
+ /* This method establishes a raw tcp connection for sending
+ * a POST message. Thus the object cannot communicate a
+ * remote object beyond a fire wall. To avoid this problem,
+ * the connection should be established with a mechanism
+ * collaborating a proxy server. Unfortunately, java.lang.URL
+ * does not seem to provide such a mechanism.
+ *
+ * You might think that using HttpURLConnection is a better
+ * way than constructing a raw tcp connection. Unfortunately,
+ * URL.openConnection() does not return an HttpURLConnection
+ * object in Netscape's JVM. It returns a
+ * netscape.net.URLConnection object.
+ *
+ * lookupObject() has the same problem.
+ */
+ Socket sock = new Socket(servername, port);
+ OutputStream out = new BufferedOutputStream(
+ sock.getOutputStream());
+ out.write(rmiCommand);
+ out.write(endofline);
+ out.write(endofline);
+
+ ObjectOutputStream dout = new ObjectOutputStream(out);
+ dout.writeInt(objectid);
+ dout.writeInt(methodid);
+ writeParameters(dout, args);
+ dout.flush();
+
+ InputStream ins = new BufferedInputStream(sock.getInputStream());
+ skipHeader(ins);
+ ObjectInputStream din = new ObjectInputStream(ins);
+ result = din.readBoolean();
+ rvalue = null;
+ errmsg = null;
+ if (result)
+ rvalue = din.readObject();
+ else
+ errmsg = din.readUTF();
+
+ din.close();
+ dout.close();
+ sock.close();
+
+ if (rvalue instanceof RemoteRef) {
+ RemoteRef ref = (RemoteRef)rvalue;
+ rvalue = createProxy(ref.oid, ref.classname);
+ }
+ }
+ catch (ClassNotFoundException e) {
+ throw new RemoteException(e);
+ }
+ catch (IOException e) {
+ throw new RemoteException(e);
+ }
+ catch (Exception e) {
+ throw new RemoteException(e);
+ }
+
+ if (result)
+ return rvalue;
+ else
+ throw new RemoteException(errmsg);
+ }
+
+ private void skipHeader(InputStream in) throws IOException {
+ int len;
+ do {
+ int c;
+ len = 0;
+ while ((c = in.read()) >= 0 && c != 0x0d)
+ ++len;
+
+ in.read(); /* skip 0x0a (LF) */
+ } while (len > 0);
+ }
+
+ private void writeParameters(ObjectOutputStream dout, Object[] params)
+ throws IOException
+ {
+ int n = params.length;
+ dout.writeInt(n);
+ for (int i = 0; i < n; ++i)
+ if (params[i] instanceof Proxy) {
+ Proxy p = (Proxy)params[i];
+ dout.writeObject(new RemoteRef(p._getObjectId()));
+ }
+ else
+ dout.writeObject(params[i]);
+ }
+}
diff --git a/src/main/javassist/tools/rmi/ObjectNotFoundException.java b/src/main/javassist/tools/rmi/ObjectNotFoundException.java
new file mode 100644
index 0000000..8ec3a46
--- /dev/null
+++ b/src/main/javassist/tools/rmi/ObjectNotFoundException.java
@@ -0,0 +1,26 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.tools.rmi;
+
+public class ObjectNotFoundException extends Exception {
+ public ObjectNotFoundException(String name) {
+ super(name + " is not exported");
+ }
+
+ public ObjectNotFoundException(String name, Exception e) {
+ super(name + " because of " + e.toString());
+ }
+}
diff --git a/src/main/javassist/tools/rmi/Proxy.java b/src/main/javassist/tools/rmi/Proxy.java
new file mode 100644
index 0000000..5ea8a70
--- /dev/null
+++ b/src/main/javassist/tools/rmi/Proxy.java
@@ -0,0 +1,25 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.tools.rmi;
+
+/**
+ * An interface implemented by proxy classes.
+ *
+ * @see javassist.tools.rmi.StubGenerator
+ */
+public interface Proxy {
+ int _getObjectId();
+}
diff --git a/src/main/javassist/tools/rmi/RemoteException.java b/src/main/javassist/tools/rmi/RemoteException.java
new file mode 100644
index 0000000..19a107f
--- /dev/null
+++ b/src/main/javassist/tools/rmi/RemoteException.java
@@ -0,0 +1,30 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.tools.rmi;
+
+/**
+ * <code>RemoteException</code> represents any exception thrown
+ * during remote method invocation.
+ */
+public class RemoteException extends RuntimeException {
+ public RemoteException(String msg) {
+ super(msg);
+ }
+
+ public RemoteException(Exception e) {
+ super("by " + e.toString());
+ }
+}
diff --git a/src/main/javassist/tools/rmi/RemoteRef.java b/src/main/javassist/tools/rmi/RemoteRef.java
new file mode 100644
index 0000000..fb1c2e1
--- /dev/null
+++ b/src/main/javassist/tools/rmi/RemoteRef.java
@@ -0,0 +1,35 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.tools.rmi;
+
+/**
+ * Remote reference. This class is internally used for sending a remote
+ * reference through a network stream.
+ */
+public class RemoteRef implements java.io.Serializable {
+ public int oid;
+ public String classname;
+
+ public RemoteRef(int i) {
+ oid = i;
+ classname = null;
+ }
+
+ public RemoteRef(int i, String name) {
+ oid = i;
+ classname = name;
+ }
+}
diff --git a/src/main/javassist/tools/rmi/Sample.java b/src/main/javassist/tools/rmi/Sample.java
new file mode 100644
index 0000000..12c799b
--- /dev/null
+++ b/src/main/javassist/tools/rmi/Sample.java
@@ -0,0 +1,36 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.tools.rmi;
+
+/**
+ * A template used for defining a proxy class.
+ * The class file of this class is read by the <code>StubGenerator</code>
+ * class.
+ */
+public class Sample {
+ private ObjectImporter importer;
+ private int objectId;
+
+ public Object forward(Object[] args, int identifier) {
+ return importer.call(objectId, identifier, args);
+ }
+
+ public static Object forwardStatic(Object[] args, int identifier)
+ throws RemoteException
+ {
+ throw new RemoteException("cannot call a static method.");
+ }
+}
diff --git a/src/main/javassist/tools/rmi/StubGenerator.java b/src/main/javassist/tools/rmi/StubGenerator.java
new file mode 100644
index 0000000..8b6604a
--- /dev/null
+++ b/src/main/javassist/tools/rmi/StubGenerator.java
@@ -0,0 +1,255 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.tools.rmi;
+
+import javassist.*;
+import java.lang.reflect.Method;
+import java.util.Hashtable;
+import javassist.CtMethod.ConstParameter;
+
+/**
+ * A stub-code generator. It is used for producing a proxy class.
+ *
+ * <p>The proxy class for class A is as follows:
+ *
+ * <ul><pre>public class A implements Proxy, Serializable {
+ * private ObjectImporter importer;
+ * private int objectId;
+ * public int _getObjectId() { return objectId; }
+ * public A(ObjectImporter oi, int id) {
+ * importer = oi; objectId = id;
+ * }
+ *
+ * ... the same methods that the original class A declares ...
+ * }</pre></ul>
+ *
+ * <p>Instances of the proxy class is created by an
+ * <code>ObjectImporter</code> object.
+ */
+public class StubGenerator implements Translator {
+ private static final String fieldImporter = "importer";
+ private static final String fieldObjectId = "objectId";
+ private static final String accessorObjectId = "_getObjectId";
+ private static final String sampleClass = "javassist.tools.rmi.Sample";
+
+ private ClassPool classPool;
+ private Hashtable proxyClasses;
+ private CtMethod forwardMethod;
+ private CtMethod forwardStaticMethod;
+
+ private CtClass[] proxyConstructorParamTypes;
+ private CtClass[] interfacesForProxy;
+ private CtClass[] exceptionForProxy;
+
+ /**
+ * Constructs a stub-code generator.
+ */
+ public StubGenerator() {
+ proxyClasses = new Hashtable();
+ }
+
+ /**
+ * Initializes the object.
+ * This is a method declared in javassist.Translator.
+ *
+ * @see javassist.Translator#start(ClassPool)
+ */
+ public void start(ClassPool pool) throws NotFoundException {
+ classPool = pool;
+ CtClass c = pool.get(sampleClass);
+ forwardMethod = c.getDeclaredMethod("forward");
+ forwardStaticMethod = c.getDeclaredMethod("forwardStatic");
+
+ proxyConstructorParamTypes
+ = pool.get(new String[] { "javassist.tools.rmi.ObjectImporter",
+ "int" });
+ interfacesForProxy
+ = pool.get(new String[] { "java.io.Serializable",
+ "javassist.tools.rmi.Proxy" });
+ exceptionForProxy
+ = new CtClass[] { pool.get("javassist.tools.rmi.RemoteException") };
+ }
+
+ /**
+ * Does nothing.
+ * This is a method declared in javassist.Translator.
+ * @see javassist.Translator#onLoad(ClassPool,String)
+ */
+ public void onLoad(ClassPool pool, String classname) {}
+
+ /**
+ * Returns <code>true</code> if the specified class is a proxy class
+ * recorded by <code>makeProxyClass()</code>.
+ *
+ * @param name a fully-qualified class name
+ */
+ public boolean isProxyClass(String name) {
+ return proxyClasses.get(name) != null;
+ }
+
+ /**
+ * Makes a proxy class. The produced class is substituted
+ * for the original class.
+ *
+ * @param clazz the class referenced
+ * through the proxy class.
+ * @return <code>false</code> if the proxy class
+ * has been already produced.
+ */
+ public synchronized boolean makeProxyClass(Class clazz)
+ throws CannotCompileException, NotFoundException
+ {
+ String classname = clazz.getName();
+ if (proxyClasses.get(classname) != null)
+ return false;
+ else {
+ CtClass ctclazz = produceProxyClass(classPool.get(classname),
+ clazz);
+ proxyClasses.put(classname, ctclazz);
+ modifySuperclass(ctclazz);
+ return true;
+ }
+ }
+
+ private CtClass produceProxyClass(CtClass orgclass, Class orgRtClass)
+ throws CannotCompileException, NotFoundException
+ {
+ int modify = orgclass.getModifiers();
+ if (Modifier.isAbstract(modify) || Modifier.isNative(modify)
+ || !Modifier.isPublic(modify))
+ throw new CannotCompileException(orgclass.getName()
+ + " must be public, non-native, and non-abstract.");
+
+ CtClass proxy = classPool.makeClass(orgclass.getName(),
+ orgclass.getSuperclass());
+
+ proxy.setInterfaces(interfacesForProxy);
+
+ CtField f
+ = new CtField(classPool.get("javassist.tools.rmi.ObjectImporter"),
+ fieldImporter, proxy);
+ f.setModifiers(Modifier.PRIVATE);
+ proxy.addField(f, CtField.Initializer.byParameter(0));
+
+ f = new CtField(CtClass.intType, fieldObjectId, proxy);
+ f.setModifiers(Modifier.PRIVATE);
+ proxy.addField(f, CtField.Initializer.byParameter(1));
+
+ proxy.addMethod(CtNewMethod.getter(accessorObjectId, f));
+
+ proxy.addConstructor(CtNewConstructor.defaultConstructor(proxy));
+ CtConstructor cons
+ = CtNewConstructor.skeleton(proxyConstructorParamTypes,
+ null, proxy);
+ proxy.addConstructor(cons);
+
+ try {
+ addMethods(proxy, orgRtClass.getMethods());
+ return proxy;
+ }
+ catch (SecurityException e) {
+ throw new CannotCompileException(e);
+ }
+ }
+
+ private CtClass toCtClass(Class rtclass) throws NotFoundException {
+ String name;
+ if (!rtclass.isArray())
+ name = rtclass.getName();
+ else {
+ StringBuffer sbuf = new StringBuffer();
+ do {
+ sbuf.append("[]");
+ rtclass = rtclass.getComponentType();
+ } while(rtclass.isArray());
+ sbuf.insert(0, rtclass.getName());
+ name = sbuf.toString();
+ }
+
+ return classPool.get(name);
+ }
+
+ private CtClass[] toCtClass(Class[] rtclasses) throws NotFoundException {
+ int n = rtclasses.length;
+ CtClass[] ctclasses = new CtClass[n];
+ for (int i = 0; i < n; ++i)
+ ctclasses[i] = toCtClass(rtclasses[i]);
+
+ return ctclasses;
+ }
+
+ /* ms must not be an array of CtMethod. To invoke a method ms[i]
+ * on a server, a client must send i to the server.
+ */
+ private void addMethods(CtClass proxy, Method[] ms)
+ throws CannotCompileException, NotFoundException
+ {
+ CtMethod wmethod;
+ for (int i = 0; i < ms.length; ++i) {
+ Method m = ms[i];
+ int mod = m.getModifiers();
+ if (m.getDeclaringClass() != Object.class
+ && !Modifier.isFinal(mod))
+ if (Modifier.isPublic(mod)) {
+ CtMethod body;
+ if (Modifier.isStatic(mod))
+ body = forwardStaticMethod;
+ else
+ body = forwardMethod;
+
+ wmethod
+ = CtNewMethod.wrapped(toCtClass(m.getReturnType()),
+ m.getName(),
+ toCtClass(m.getParameterTypes()),
+ exceptionForProxy,
+ body,
+ ConstParameter.integer(i),
+ proxy);
+ wmethod.setModifiers(mod);
+ proxy.addMethod(wmethod);
+ }
+ else if (!Modifier.isProtected(mod)
+ && !Modifier.isPrivate(mod))
+ // if package method
+ throw new CannotCompileException(
+ "the methods must be public, protected, or private.");
+ }
+ }
+
+ /**
+ * Adds a default constructor to the super classes.
+ */
+ private void modifySuperclass(CtClass orgclass)
+ throws CannotCompileException, NotFoundException
+ {
+ CtClass superclazz;
+ for (;; orgclass = superclazz) {
+ superclazz = orgclass.getSuperclass();
+ if (superclazz == null)
+ break;
+
+ try {
+ superclazz.getDeclaredConstructor(null);
+ break; // the constructor with no arguments is found.
+ }
+ catch (NotFoundException e) {
+ }
+
+ superclazz.addConstructor(
+ CtNewConstructor.defaultConstructor(superclazz));
+ }
+ }
+}
diff --git a/src/main/javassist/tools/rmi/package.html b/src/main/javassist/tools/rmi/package.html
new file mode 100644
index 0000000..5432a94
--- /dev/null
+++ b/src/main/javassist/tools/rmi/package.html
@@ -0,0 +1,16 @@
+<html>
+<body>
+Sample implementation of remote method invocation.
+
+<p>This package enables applets to access remote objects
+running on the web server with regular Java syntax.
+It is provided as a sample implementation with Javassist.
+All the programs in this package uses only the regular
+Javassist API; they never call any hidden methods.
+
+<p>The most significant class of this package is
+<code>ObjectImporter</code>.
+See the description of this class first.
+
+</body>
+</html>
diff --git a/src/main/javassist/tools/web/BadHttpRequest.java b/src/main/javassist/tools/web/BadHttpRequest.java
new file mode 100644
index 0000000..0010203
--- /dev/null
+++ b/src/main/javassist/tools/web/BadHttpRequest.java
@@ -0,0 +1,34 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.tools.web;
+
+/**
+ * Thrown when receiving an invalid HTTP request.
+ */
+public class BadHttpRequest extends Exception {
+ private Exception e;
+
+ public BadHttpRequest() { e = null; }
+
+ public BadHttpRequest(Exception _e) { e = _e; }
+
+ public String toString() {
+ if (e == null)
+ return super.toString();
+ else
+ return e.toString();
+ }
+}
diff --git a/src/main/javassist/tools/web/Viewer.java b/src/main/javassist/tools/web/Viewer.java
new file mode 100644
index 0000000..de7afae
--- /dev/null
+++ b/src/main/javassist/tools/web/Viewer.java
@@ -0,0 +1,208 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.tools.web;
+
+import java.io.*;
+import java.net.*;
+
+/**
+ * A sample applet viewer.
+ *
+ * <p>This is a sort of applet viewer that can run any program even if
+ * the main class is not a subclass of <code>Applet</code>.
+ * This viewwer first calls <code>main()</code> in the main class.
+ *
+ * <p>To run, you should type:
+ *
+ * <ul><code>% java javassist.tools.web.Viewer <i>host port</i> Main arg1, ...</code></ul>
+ *
+ * <p>This command calls <code>Main.main()</code> with <code>arg1,...</code>
+ * All classes including <code>Main</code> are fetched from
+ * a server http://<i>host</i>:<i>port</i>.
+ * Only the class file for <code>Viewer</code> must exist
+ * on a local file system at the client side; even other
+ * <code>javassist.*</code> classes are not needed at the client side.
+ * <code>Viewer</code> uses only Java core API classes.
+ *
+ * <p>Note: since a <code>Viewer</code> object is a class loader,
+ * a program loaded by this object can call a method in <code>Viewer</code>.
+ * For example, you can write something like this:
+ *
+ * <ul><pre>
+ * Viewer v = (Viewer)this.getClass().getClassLoader();
+ * String port = v.getPort();
+ * </pre></ul>
+ *
+ */
+public class Viewer extends ClassLoader {
+ private String server;
+ private int port;
+
+ /**
+ * Starts a program.
+ */
+ public static void main(String[] args) throws Throwable {
+ if (args.length >= 3) {
+ Viewer cl = new Viewer(args[0], Integer.parseInt(args[1]));
+ String[] args2 = new String[args.length - 3];
+ System.arraycopy(args, 3, args2, 0, args.length - 3);
+ cl.run(args[2], args2);
+ }
+ else
+ System.err.println(
+ "Usage: java javassist.tools.web.Viewer <host> <port> class [args ...]");
+ }
+
+ /**
+ * Constructs a viewer.
+ *
+ * @param host server name
+ * @param p port number
+ */
+ public Viewer(String host, int p) {
+ server = host;
+ port = p;
+ }
+
+ /**
+ * Returns the server name.
+ */
+ public String getServer() { return server; }
+
+ /**
+ * Returns the port number.
+ */
+ public int getPort() { return port; }
+
+ /**
+ * Invokes main() in the class specified by <code>classname</code>.
+ *
+ * @param classname executed class
+ * @param args the arguments passed to <code>main()</code>.
+ */
+ public void run(String classname, String[] args)
+ throws Throwable
+ {
+ Class c = loadClass(classname);
+ try {
+ c.getDeclaredMethod("main", new Class[] { String[].class })
+ .invoke(null, new Object[] { args });
+ }
+ catch (java.lang.reflect.InvocationTargetException e) {
+ throw e.getTargetException();
+ }
+ }
+
+ /**
+ * Requests the class loader to load a class.
+ */
+ protected synchronized Class loadClass(String name, boolean resolve)
+ throws ClassNotFoundException
+ {
+ Class c = findLoadedClass(name);
+ if (c == null)
+ c = findClass(name);
+
+ if (c == null)
+ throw new ClassNotFoundException(name);
+
+ if (resolve)
+ resolveClass(c);
+
+ return c;
+ }
+
+ /**
+ * Finds the specified class. The implementation in this class
+ * fetches the class from the http server. If the class is
+ * either <code>java.*</code>, <code>javax.*</code>, or
+ * <code>Viewer</code>, then it is loaded by the parent class
+ * loader.
+ *
+ * <p>This method can be overridden by a subclass of
+ * <code>Viewer</code>.
+ */
+ protected Class findClass(String name) throws ClassNotFoundException {
+ Class c = null;
+ if (name.startsWith("java.") || name.startsWith("javax.")
+ || name.equals("javassist.tools.web.Viewer"))
+ c = findSystemClass(name);
+
+ if (c == null)
+ try {
+ byte[] b = fetchClass(name);
+ if (b != null)
+ c = defineClass(name, b, 0, b.length);
+ }
+ catch (Exception e) {
+ }
+
+ return c;
+ }
+
+ /**
+ * Fetches the class file of the specified class from the http
+ * server.
+ */
+ protected byte[] fetchClass(String classname) throws Exception
+ {
+ byte[] b;
+ URL url = new URL("http", server, port,
+ "/" + classname.replace('.', '/') + ".class");
+ URLConnection con = url.openConnection();
+ con.connect();
+ int size = con.getContentLength();
+ InputStream s = con.getInputStream();
+ if (size <= 0)
+ b = readStream(s);
+ else {
+ b = new byte[size];
+ int len = 0;
+ do {
+ int n = s.read(b, len, size - len);
+ if (n < 0) {
+ s.close();
+ throw new IOException("the stream was closed: "
+ + classname);
+ }
+ len += n;
+ } while (len < size);
+ }
+
+ s.close();
+ return b;
+ }
+
+ private byte[] readStream(InputStream fin) throws IOException {
+ byte[] buf = new byte[4096];
+ int size = 0;
+ int len = 0;
+ do {
+ size += len;
+ if (buf.length - size <= 0) {
+ byte[] newbuf = new byte[buf.length * 2];
+ System.arraycopy(buf, 0, newbuf, 0, size);
+ buf = newbuf;
+ }
+
+ len = fin.read(buf, size, buf.length - size);
+ } while (len >= 0);
+
+ byte[] result = new byte[size];
+ System.arraycopy(buf, 0, result, 0, size);
+ return result;
+ }
+}
diff --git a/src/main/javassist/tools/web/Webserver.java b/src/main/javassist/tools/web/Webserver.java
new file mode 100644
index 0000000..952d56d
--- /dev/null
+++ b/src/main/javassist/tools/web/Webserver.java
@@ -0,0 +1,407 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.tools.web;
+
+import java.net.*;
+import java.io.*;
+import java.util.Date;
+import javassist.*;
+
+/**
+ * A web server for running sample programs.
+ *
+ * <p>This enables a Java program to instrument class files loaded by
+ * web browsers for applets. Since the (standard) security manager
+ * does not allow an applet to create and use a class loader,
+ * instrumenting class files must be done by this web server.
+ *
+ * <p><b>Note:</b> although this class is included in the Javassist API,
+ * it is provided as a sample implementation of the web server using
+ * Javassist. Especially, there might be security flaws in this server.
+ * Please use this with YOUR OWN RISK.
+ */
+public class Webserver {
+ private ServerSocket socket;
+ private ClassPool classPool;
+ protected Translator translator;
+
+ private final static byte[] endofline = { 0x0d, 0x0a };
+
+ private final static int typeHtml = 1;
+ private final static int typeClass = 2;
+ private final static int typeGif = 3;
+ private final static int typeJpeg = 4;
+ private final static int typeText = 5;
+
+ /**
+ * If this field is not null, the class files taken from
+ * <code>ClassPool</code> are written out under the directory
+ * specified by this field. The directory name must not end
+ * with a directory separator.
+ */
+ public String debugDir = null;
+
+ /**
+ * The top directory of html (and .gif, .class, ...) files.
+ * It must end with the directory separator such as "/".
+ * (For portability, "/" should be used as the directory separator.
+ * Javassist automatically translates "/" into a platform-dependent
+ * character.)
+ * If this field is null, the top directory is the current one where
+ * the JVM is running.
+ *
+ * <p>If the given URL indicates a class file and the class file
+ * is not found under the directory specified by this variable,
+ * then <code>Class.getResourceAsStream()</code> is called
+ * for searching the Java class paths.
+ */
+ public String htmlfileBase = null;
+
+ /**
+ * Starts a web server.
+ * The port number is specified by the first argument.
+ */
+ public static void main(String[] args) throws IOException {
+ if (args.length == 1) {
+ Webserver web = new Webserver(args[0]);
+ web.run();
+ }
+ else
+ System.err.println(
+ "Usage: java javassist.tools.web.Webserver <port number>");
+ }
+
+ /**
+ * Constructs a web server.
+ *
+ * @param port port number
+ */
+ public Webserver(String port) throws IOException {
+ this(Integer.parseInt(port));
+ }
+
+ /**
+ * Constructs a web server.
+ *
+ * @param port port number
+ */
+ public Webserver(int port) throws IOException {
+ socket = new ServerSocket(port);
+ classPool = null;
+ translator = null;
+ }
+
+ /**
+ * Requests the web server to use the specified
+ * <code>ClassPool</code> object for obtaining a class file.
+ */
+ public void setClassPool(ClassPool loader) {
+ classPool = loader;
+ }
+
+ /**
+ * Adds a translator, which is called whenever a client requests
+ * a class file.
+ *
+ * @param cp the <code>ClassPool</code> object for obtaining
+ * a class file.
+ * @param t a translator.
+ */
+ public void addTranslator(ClassPool cp, Translator t)
+ throws NotFoundException, CannotCompileException
+ {
+ classPool = cp;
+ translator = t;
+ t.start(classPool);
+ }
+
+ /**
+ * Closes the socket.
+ */
+ public void end() throws IOException {
+ socket.close();
+ }
+
+ /**
+ * Prints a log message.
+ */
+ public void logging(String msg) {
+ System.out.println(msg);
+ }
+
+ /**
+ * Prints a log message.
+ */
+ public void logging(String msg1, String msg2) {
+ System.out.print(msg1);
+ System.out.print(" ");
+ System.out.println(msg2);
+ }
+
+ /**
+ * Prints a log message.
+ */
+ public void logging(String msg1, String msg2, String msg3) {
+ System.out.print(msg1);
+ System.out.print(" ");
+ System.out.print(msg2);
+ System.out.print(" ");
+ System.out.println(msg3);
+ }
+
+ /**
+ * Prints a log message with indentation.
+ */
+ public void logging2(String msg) {
+ System.out.print(" ");
+ System.out.println(msg);
+ }
+
+ /**
+ * Begins the HTTP service.
+ */
+ public void run() {
+ System.err.println("ready to service...");
+ for (;;)
+ try {
+ ServiceThread th = new ServiceThread(this, socket.accept());
+ th.start();
+ }
+ catch (IOException e) {
+ logging(e.toString());
+ }
+ }
+
+ final void process(Socket clnt) throws IOException {
+ InputStream in = new BufferedInputStream(clnt.getInputStream());
+ String cmd = readLine(in);
+ logging(clnt.getInetAddress().getHostName(),
+ new Date().toString(), cmd);
+ while (skipLine(in) > 0){
+ }
+
+ OutputStream out = new BufferedOutputStream(clnt.getOutputStream());
+ try {
+ doReply(in, out, cmd);
+ }
+ catch (BadHttpRequest e) {
+ replyError(out, e);
+ }
+
+ out.flush();
+ in.close();
+ out.close();
+ clnt.close();
+ }
+
+ private String readLine(InputStream in) throws IOException {
+ StringBuffer buf = new StringBuffer();
+ int c;
+ while ((c = in.read()) >= 0 && c != 0x0d)
+ buf.append((char)c);
+
+ in.read(); /* skip 0x0a (LF) */
+ return buf.toString();
+ }
+
+ private int skipLine(InputStream in) throws IOException {
+ int c;
+ int len = 0;
+ while ((c = in.read()) >= 0 && c != 0x0d)
+ ++len;
+
+ in.read(); /* skip 0x0a (LF) */
+ return len;
+ }
+
+ /**
+ * Proceses a HTTP request from a client.
+ *
+ * @param out the output stream to a client
+ * @param cmd the command received from a client
+ */
+ public void doReply(InputStream in, OutputStream out, String cmd)
+ throws IOException, BadHttpRequest
+ {
+ int len;
+ int fileType;
+ String filename, urlName;
+
+ if (cmd.startsWith("GET /"))
+ filename = urlName = cmd.substring(5, cmd.indexOf(' ', 5));
+ else
+ throw new BadHttpRequest();
+
+ if (filename.endsWith(".class"))
+ fileType = typeClass;
+ else if (filename.endsWith(".html") || filename.endsWith(".htm"))
+ fileType = typeHtml;
+ else if (filename.endsWith(".gif"))
+ fileType = typeGif;
+ else if (filename.endsWith(".jpg"))
+ fileType = typeJpeg;
+ else
+ fileType = typeText; // or textUnknown
+
+ len = filename.length();
+ if (fileType == typeClass
+ && letUsersSendClassfile(out, filename, len))
+ return;
+
+ checkFilename(filename, len);
+ if (htmlfileBase != null)
+ filename = htmlfileBase + filename;
+
+ if (File.separatorChar != '/')
+ filename = filename.replace('/', File.separatorChar);
+
+ File file = new File(filename);
+ if (file.canRead()) {
+ sendHeader(out, file.length(), fileType);
+ FileInputStream fin = new FileInputStream(file);
+ byte[] filebuffer = new byte[4096];
+ for (;;) {
+ len = fin.read(filebuffer);
+ if (len <= 0)
+ break;
+ else
+ out.write(filebuffer, 0, len);
+ }
+
+ fin.close();
+ return;
+ }
+
+ // If the file is not found under the html-file directory,
+ // then Class.getResourceAsStream() is tried.
+
+ if (fileType == typeClass) {
+ InputStream fin
+ = getClass().getResourceAsStream("/" + urlName);
+ if (fin != null) {
+ ByteArrayOutputStream barray = new ByteArrayOutputStream();
+ byte[] filebuffer = new byte[4096];
+ for (;;) {
+ len = fin.read(filebuffer);
+ if (len <= 0)
+ break;
+ else
+ barray.write(filebuffer, 0, len);
+ }
+
+ byte[] classfile = barray.toByteArray();
+ sendHeader(out, classfile.length, typeClass);
+ out.write(classfile);
+ fin.close();
+ return;
+ }
+ }
+
+ throw new BadHttpRequest();
+ }
+
+ private void checkFilename(String filename, int len)
+ throws BadHttpRequest
+ {
+ for (int i = 0; i < len; ++i) {
+ char c = filename.charAt(i);
+ if (!Character.isJavaIdentifierPart(c) && c != '.' && c != '/')
+ throw new BadHttpRequest();
+ }
+
+ if (filename.indexOf("..") >= 0)
+ throw new BadHttpRequest();
+ }
+
+ private boolean letUsersSendClassfile(OutputStream out,
+ String filename, int length)
+ throws IOException, BadHttpRequest
+ {
+ if (classPool == null)
+ return false;
+
+ byte[] classfile;
+ String classname
+ = filename.substring(0, length - 6).replace('/', '.');
+ try {
+ if (translator != null)
+ translator.onLoad(classPool, classname);
+
+ CtClass c = classPool.get(classname);
+ classfile = c.toBytecode();
+ if (debugDir != null)
+ c.writeFile(debugDir);
+ }
+ catch (Exception e) {
+ throw new BadHttpRequest(e);
+ }
+
+ sendHeader(out, classfile.length, typeClass);
+ out.write(classfile);
+ return true;
+ }
+
+ private void sendHeader(OutputStream out, long dataLength, int filetype)
+ throws IOException
+ {
+ out.write("HTTP/1.0 200 OK".getBytes());
+ out.write(endofline);
+ out.write("Content-Length: ".getBytes());
+ out.write(Long.toString(dataLength).getBytes());
+ out.write(endofline);
+ if (filetype == typeClass)
+ out.write("Content-Type: application/octet-stream".getBytes());
+ else if (filetype == typeHtml)
+ out.write("Content-Type: text/html".getBytes());
+ else if (filetype == typeGif)
+ out.write("Content-Type: image/gif".getBytes());
+ else if (filetype == typeJpeg)
+ out.write("Content-Type: image/jpg".getBytes());
+ else if (filetype == typeText)
+ out.write("Content-Type: text/plain".getBytes());
+
+ out.write(endofline);
+ out.write(endofline);
+ }
+
+ private void replyError(OutputStream out, BadHttpRequest e)
+ throws IOException
+ {
+ logging2("bad request: " + e.toString());
+ out.write("HTTP/1.0 400 Bad Request".getBytes());
+ out.write(endofline);
+ out.write(endofline);
+ out.write("<H1>Bad Request</H1>".getBytes());
+ }
+}
+
+class ServiceThread extends Thread {
+ Webserver web;
+ Socket sock;
+
+ public ServiceThread(Webserver w, Socket s) {
+ web = w;
+ sock = s;
+ }
+
+ public void run() {
+ try {
+ web.process(sock);
+ }
+ catch (IOException e) {
+ }
+ }
+}
diff --git a/src/main/javassist/tools/web/package.html b/src/main/javassist/tools/web/package.html
new file mode 100644
index 0000000..0c7fb45
--- /dev/null
+++ b/src/main/javassist/tools/web/package.html
@@ -0,0 +1,7 @@
+<html>
+<body>
+Simple web server for running sample code.
+
+<p>This package provides a simple web server for sample packages.
+</body>
+</html>
diff --git a/src/main/javassist/util/HotSwapper.java b/src/main/javassist/util/HotSwapper.java
new file mode 100644
index 0000000..3536adf
--- /dev/null
+++ b/src/main/javassist/util/HotSwapper.java
@@ -0,0 +1,252 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.util;
+
+import com.sun.jdi.*;
+import com.sun.jdi.connect.*;
+import com.sun.jdi.event.*;
+import com.sun.jdi.request.*;
+import java.io.*;
+import java.util.*;
+
+class Trigger {
+ void doSwap() {}
+}
+
+/**
+ * A utility class for dynamically reloading a class by
+ * the Java Platform Debugger Architecture (JPDA), or <it>HotSwap</code>.
+ * It works only with JDK 1.4 and later.
+ *
+ * <p><b>Note:</b> The new definition of the reloaded class must declare
+ * the same set of methods and fields as the original definition. The
+ * schema change between the original and new definitions is not allowed
+ * by the JPDA.
+ *
+ * <p>To use this class, the JVM must be launched with the following
+ * command line options:
+ *
+ * <ul>
+ * <p>For Java 1.4,<br>
+ * <pre>java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000</pre>
+ * <p>For Java 5,<br>
+ * <pre>java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000</pre>
+ * </ul>
+ *
+ * <p>Note that 8000 is the port number used by <code>HotSwapper</code>.
+ * Any port number can be specified. Since <code>HotSwapper</code> does not
+ * launch another JVM for running a target application, this port number
+ * is used only for inter-thread communication.
+ *
+ * <p>Furthermore, <code>JAVA_HOME/lib/tools.jar</code> must be included
+ * in the class path.
+ *
+ * <p>Using <code>HotSwapper</code> is easy. See the following example:
+ *
+ * <ul><pre>
+ * CtClass clazz = ...
+ * byte[] classFile = clazz.toBytecode();
+ * HotSwapper hs = new HostSwapper(8000); // 8000 is a port number.
+ * hs.reload("Test", classFile);
+ * </pre></ul>
+ *
+ * <p><code>reload()</code>
+ * first unload the <code>Test</code> class and load a new version of
+ * the <code>Test</code> class.
+ * <code>classFile</code> is a byte array containing the new contents of
+ * the class file for the <code>Test</code> class. The developers can
+ * repatedly call <code>reload()</code> on the same <code>HotSwapper</code>
+ * object so that they can reload a number of classes.
+ *
+ * @since 3.1
+ */
+public class HotSwapper {
+ private VirtualMachine jvm;
+ private MethodEntryRequest request;
+ private Map newClassFiles;
+
+ private Trigger trigger;
+
+ private static final String HOST_NAME = "localhost";
+ private static final String TRIGGER_NAME = Trigger.class.getName();
+
+ /**
+ * Connects to the JVM.
+ *
+ * @param port the port number used for the connection to the JVM.
+ */
+ public HotSwapper(int port)
+ throws IOException, IllegalConnectorArgumentsException
+ {
+ this(Integer.toString(port));
+ }
+
+ /**
+ * Connects to the JVM.
+ *
+ * @param port the port number used for the connection to the JVM.
+ */
+ public HotSwapper(String port)
+ throws IOException, IllegalConnectorArgumentsException
+ {
+ jvm = null;
+ request = null;
+ newClassFiles = null;
+ trigger = new Trigger();
+ AttachingConnector connector
+ = (AttachingConnector)findConnector("com.sun.jdi.SocketAttach");
+
+ Map arguments = connector.defaultArguments();
+ ((Connector.Argument)arguments.get("hostname")).setValue(HOST_NAME);
+ ((Connector.Argument)arguments.get("port")).setValue(port);
+ jvm = connector.attach(arguments);
+ EventRequestManager manager = jvm.eventRequestManager();
+ request = methodEntryRequests(manager, TRIGGER_NAME);
+ }
+
+ private Connector findConnector(String connector) throws IOException {
+ List connectors = Bootstrap.virtualMachineManager().allConnectors();
+ Iterator iter = connectors.iterator();
+ while (iter.hasNext()) {
+ Connector con = (Connector)iter.next();
+ if (con.name().equals(connector)) {
+ return con;
+ }
+ }
+
+ throw new IOException("Not found: " + connector);
+ }
+
+ private static MethodEntryRequest methodEntryRequests(
+ EventRequestManager manager,
+ String classpattern) {
+ MethodEntryRequest mereq = manager.createMethodEntryRequest();
+ mereq.addClassFilter(classpattern);
+ mereq.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
+ return mereq;
+ }
+
+ /* Stops triggering a hotswapper when reload() is called.
+ */
+ private void deleteEventRequest(EventRequestManager manager,
+ MethodEntryRequest request) {
+ manager.deleteEventRequest(request);
+ }
+
+ /**
+ * Reloads a class.
+ *
+ * @param className the fully-qualified class name.
+ * @param classFile the contents of the class file.
+ */
+ public void reload(String className, byte[] classFile) {
+ ReferenceType classtype = toRefType(className);
+ Map map = new HashMap();
+ map.put(classtype, classFile);
+ reload2(map, className);
+ }
+
+ /**
+ * Reloads a class.
+ *
+ * @param classFiles a map between fully-qualified class names
+ * and class files. The type of the class names
+ * is <code>String</code> and the type of the
+ * class files is <code>byte[]</code>.
+ */
+ public void reload(Map classFiles) {
+ Set set = classFiles.entrySet();
+ Iterator it = set.iterator();
+ Map map = new HashMap();
+ String className = null;
+ while (it.hasNext()) {
+ Map.Entry e = (Map.Entry)it.next();
+ className = (String)e.getKey();
+ map.put(toRefType(className), e.getValue());
+ }
+
+ if (className != null)
+ reload2(map, className + " etc.");
+ }
+
+ private ReferenceType toRefType(String className) {
+ List list = jvm.classesByName(className);
+ if (list == null || list.isEmpty())
+ throw new RuntimeException("no such class: " + className);
+ else
+ return (ReferenceType)list.get(0);
+ }
+
+ private void reload2(Map map, String msg) {
+ synchronized (trigger) {
+ startDaemon();
+ newClassFiles = map;
+ request.enable();
+ trigger.doSwap();
+ request.disable();
+ Map ncf = newClassFiles;
+ if (ncf != null) {
+ newClassFiles = null;
+ throw new RuntimeException("failed to reload: " + msg);
+ }
+ }
+ }
+
+ private void startDaemon() {
+ new Thread() {
+ private void errorMsg(Throwable e) {
+ System.err.print("Exception in thread \"HotSwap\" ");
+ e.printStackTrace(System.err);
+ }
+
+ public void run() {
+ EventSet events = null;
+ try {
+ events = waitEvent();
+ EventIterator iter = events.eventIterator();
+ while (iter.hasNext()) {
+ Event event = iter.nextEvent();
+ if (event instanceof MethodEntryEvent) {
+ hotswap();
+ break;
+ }
+ }
+ }
+ catch (Throwable e) {
+ errorMsg(e);
+ }
+ try {
+ if (events != null)
+ events.resume();
+ }
+ catch (Throwable e) {
+ errorMsg(e);
+ }
+ }
+ }.start();
+ }
+
+ EventSet waitEvent() throws InterruptedException {
+ EventQueue queue = jvm.eventQueue();
+ return queue.remove();
+ }
+
+ void hotswap() {
+ Map map = newClassFiles;
+ jvm.redefineClasses(map);
+ newClassFiles = null;
+ }
+}
diff --git a/src/main/javassist/util/package.html b/src/main/javassist/util/package.html
new file mode 100644
index 0000000..349d996
--- /dev/null
+++ b/src/main/javassist/util/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+Utility classes.
+</body>
+</html>
diff --git a/src/main/javassist/util/proxy/FactoryHelper.java b/src/main/javassist/util/proxy/FactoryHelper.java
new file mode 100644
index 0000000..50d1944
--- /dev/null
+++ b/src/main/javassist/util/proxy/FactoryHelper.java
@@ -0,0 +1,236 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.util.proxy;
+
+import java.lang.reflect.Method;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.security.ProtectionDomain;
+
+import javassist.CannotCompileException;
+import javassist.bytecode.ClassFile;
+
+/**
+ * A helper class for implementing <code>ProxyFactory</code>.
+ * The users of <code>ProxyFactory</code> do not have to see this class.
+ *
+ * @see ProxyFactory
+ */
+public class FactoryHelper {
+ private static java.lang.reflect.Method defineClass1, defineClass2;
+
+ static {
+ try {
+ Class cl = Class.forName("java.lang.ClassLoader");
+ defineClass1 = SecurityActions.getDeclaredMethod(
+ cl,
+ "defineClass",
+ new Class[] { String.class, byte[].class,
+ int.class, int.class });
+
+ defineClass2 = SecurityActions.getDeclaredMethod(
+ cl,
+ "defineClass",
+ new Class[] { String.class, byte[].class,
+ int.class, int.class, ProtectionDomain.class });
+ }
+ catch (Exception e) {
+ throw new RuntimeException("cannot initialize");
+ }
+ }
+
+ /**
+ * Returns an index for accessing arrays in this class.
+ *
+ * @throws RuntimeException if a given type is not a primitive type.
+ */
+ public static final int typeIndex(Class type) {
+ Class[] list = primitiveTypes;
+ int n = list.length;
+ for (int i = 0; i < n; i++)
+ if (list[i] == type)
+ return i;
+
+ throw new RuntimeException("bad type:" + type.getName());
+ }
+
+ /**
+ * <code>Class</code> objects representing primitive types.
+ */
+ public static final Class[] primitiveTypes = {
+ Boolean.TYPE, Byte.TYPE, Character.TYPE, Short.TYPE, Integer.TYPE,
+ Long.TYPE, Float.TYPE, Double.TYPE, Void.TYPE
+ };
+
+ /**
+ * The fully-qualified names of wrapper classes for primitive types.
+ */
+ public static final String[] wrapperTypes = {
+ "java.lang.Boolean", "java.lang.Byte", "java.lang.Character",
+ "java.lang.Short", "java.lang.Integer", "java.lang.Long",
+ "java.lang.Float", "java.lang.Double", "java.lang.Void"
+ };
+
+ /**
+ * The descriptors of the constructors of wrapper classes.
+ */
+ public static final String[] wrapperDesc = {
+ "(Z)V", "(B)V", "(C)V", "(S)V", "(I)V", "(J)V",
+ "(F)V", "(D)V"
+ };
+
+ /**
+ * The names of methods for obtaining a primitive value
+ * from a wrapper object. For example, <code>intValue()</code>
+ * is such a method for obtaining an integer value from a
+ * <code>java.lang.Integer</code> object.
+ */
+ public static final String[] unwarpMethods = {
+ "booleanValue", "byteValue", "charValue", "shortValue",
+ "intValue", "longValue", "floatValue", "doubleValue"
+ };
+
+ /**
+ * The descriptors of the unwrapping methods contained
+ * in <code>unwrapMethods</code>.
+ */
+ public static final String[] unwrapDesc = {
+ "()Z", "()B", "()C", "()S", "()I", "()J", "()F", "()D"
+ };
+
+ /**
+ * The data size of primitive types. <code>long</code>
+ * and <code>double</code> are 2; the others are 1.
+ */
+ public static final int[] dataSize = {
+ 1, 1, 1, 1, 1, 2, 1, 2
+ };
+
+ /**
+ * Loads a class file by a given class loader.
+ * This method uses a default protection domain for the class
+ * but it may not work with a security manager or a sigend jar file.
+ *
+ * @see #toClass(ClassFile,ClassLoader,ProtectionDomain)
+ */
+ public static Class toClass(ClassFile cf, ClassLoader loader)
+ throws CannotCompileException
+ {
+ return toClass(cf, loader, null);
+ }
+
+ /**
+ * Loads a class file by a given class loader.
+ *
+ * @param domain if it is null, a default domain is used.
+ * @since 3.3
+ */
+ public static Class toClass(ClassFile cf, ClassLoader loader, ProtectionDomain domain)
+ throws CannotCompileException
+ {
+ try {
+ byte[] b = toBytecode(cf);
+ Method method;
+ Object[] args;
+ if (domain == null) {
+ method = defineClass1;
+ args = new Object[] { cf.getName(), b, new Integer(0),
+ new Integer(b.length) };
+ }
+ else {
+ method = defineClass2;
+ args = new Object[] { cf.getName(), b, new Integer(0),
+ new Integer(b.length), domain };
+ }
+
+ return toClass2(method, loader, args);
+ }
+ catch (RuntimeException e) {
+ throw e;
+ }
+ catch (java.lang.reflect.InvocationTargetException e) {
+ throw new CannotCompileException(e.getTargetException());
+ }
+ catch (Exception e) {
+ throw new CannotCompileException(e);
+ }
+ }
+
+ private static synchronized Class toClass2(Method method,
+ ClassLoader loader, Object[] args)
+ throws Exception
+ {
+ SecurityActions.setAccessible(method, true);
+ Class clazz = (Class)method.invoke(loader, args);
+ SecurityActions.setAccessible(method, false);
+ return clazz;
+ }
+
+ private static byte[] toBytecode(ClassFile cf) throws IOException {
+ ByteArrayOutputStream barray = new ByteArrayOutputStream();
+ DataOutputStream out = new DataOutputStream(barray);
+ try {
+ cf.write(out);
+ }
+ finally {
+ out.close();
+ }
+
+ return barray.toByteArray();
+ }
+
+ /**
+ * Writes a class file.
+ */
+ public static void writeFile(ClassFile cf, String directoryName)
+ throws CannotCompileException {
+ try {
+ writeFile0(cf, directoryName);
+ }
+ catch (IOException e) {
+ throw new CannotCompileException(e);
+ }
+ }
+
+ private static void writeFile0(ClassFile cf, String directoryName)
+ throws CannotCompileException, IOException {
+ String classname = cf.getName();
+ String filename = directoryName + File.separatorChar
+ + classname.replace('.', File.separatorChar) + ".class";
+ int pos = filename.lastIndexOf(File.separatorChar);
+ if (pos > 0) {
+ String dir = filename.substring(0, pos);
+ if (!dir.equals("."))
+ new File(dir).mkdirs();
+ }
+
+ DataOutputStream out = new DataOutputStream(new BufferedOutputStream(
+ new FileOutputStream(filename)));
+ try {
+ cf.write(out);
+ }
+ catch (IOException e) {
+ throw e;
+ }
+ finally {
+ out.close();
+ }
+ }
+}
diff --git a/src/main/javassist/util/proxy/MethodFilter.java b/src/main/javassist/util/proxy/MethodFilter.java
new file mode 100644
index 0000000..76084ff
--- /dev/null
+++ b/src/main/javassist/util/proxy/MethodFilter.java
@@ -0,0 +1,30 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.util.proxy;
+
+import java.lang.reflect.Method;
+
+/**
+ * Selector of the methods implemented by a handler.
+ *
+ * @see ProxyFactory#setFilter(MethodFilter)
+ */
+public interface MethodFilter {
+ /**
+ * Returns true if the given method is implemented by a handler.
+ */
+ boolean isHandled(Method m);
+}
diff --git a/src/main/javassist/util/proxy/MethodHandler.java b/src/main/javassist/util/proxy/MethodHandler.java
new file mode 100644
index 0000000..2bb32cc
--- /dev/null
+++ b/src/main/javassist/util/proxy/MethodHandler.java
@@ -0,0 +1,48 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.util.proxy;
+
+import java.lang.reflect.Method;
+
+/**
+ * The interface implemented by the invocation handler of a proxy
+ * instance.
+ *
+ * @see ProxyFactory#setHandler(MethodHandler)
+ */
+public interface MethodHandler {
+ /**
+ * Is called when a method is invoked on a proxy instance associated
+ * with this handler. This method must process that method invocation.
+ *
+ * @param self the proxy instance.
+ * @param thisMethod the overridden method declared in the super
+ * class or interface.
+ * @param proceed the forwarder method for invoking the overridden
+ * method. It is null if the overridden mehtod is
+ * abstract or declared in the interface.
+ * @param args an array of objects containing the values of
+ * the arguments passed in the method invocation
+ * on the proxy instance. If a parameter type is
+ * a primitive type, the type of the array element
+ * is a wrapper class.
+ * @return the resulting value of the method invocation.
+ *
+ * @throws Throwable if the method invocation fails.
+ */
+ Object invoke(Object self, Method thisMethod, Method proceed,
+ Object[] args) throws Throwable;
+}
diff --git a/src/main/javassist/util/proxy/ProxyFactory.java b/src/main/javassist/util/proxy/ProxyFactory.java
new file mode 100644
index 0000000..0750950
--- /dev/null
+++ b/src/main/javassist/util/proxy/ProxyFactory.java
@@ -0,0 +1,1334 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.util.proxy;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Member;
+import java.lang.reflect.Modifier;
+import java.security.ProtectionDomain;
+import java.util.*;
+import java.lang.ref.WeakReference;
+
+import javassist.CannotCompileException;
+import javassist.bytecode.*;
+
+/*
+ * This class is implemented only with the lower-level API of Javassist.
+ * This design decision is for maximizing performance.
+ */
+
+/**
+ * Factory of dynamic proxy classes.
+ *
+ * <p>This factory generates a class that extends the given super class and implements
+ * the given interfaces. The calls of the methods inherited from the super class are
+ * forwarded and then <code>invoke()</code> is called on the method handler
+ * associated with instances of the generated class. The calls of the methods from
+ * the interfaces are also forwarded to the method handler.
+ *
+ * <p>For example, if the following code is executed,
+ *
+ * <ul><pre>
+ * ProxyFactory f = new ProxyFactory();
+ * f.setSuperclass(Foo.class);
+ * f.setFilter(new MethodFilter() {
+ * public boolean isHandled(Method m) {
+ * // ignore finalize()
+ * return !m.getName().equals("finalize");
+ * }
+ * });
+ * Class c = f.createClass();
+ * MethodHandler mi = new MethodHandler() {
+ * public Object invoke(Object self, Method m, Method proceed,
+ * Object[] args) throws Throwable {
+ * System.out.println("Name: " + m.getName());
+ * return proceed.invoke(self, args); // execute the original method.
+ * }
+ * };
+ * Foo foo = (Foo)c.newInstance();
+ * ((ProxyObject)foo).setHandler(mi);
+ * </pre></ul>
+ *
+ * <p>Then, the following method call will be forwarded to MethodHandler
+ * <code>mi</code> and prints a message before executing the originally called method
+ * <code>bar()</code> in <code>Foo</code>.
+ *
+ * <ul><pre>
+ * foo.bar();
+ * </pre></ul>
+ *
+ * <p>The last three lines of the code shown above can be replaced with a call to
+ * the helper method <code>create</code>, which generates a proxy class, instantiates
+ * it, and sets the method handler of the instance:
+ *
+ * <ul><pre>
+ * :
+ * Foo foo = (Foo)f.create(new Class[0], new Object[0], mi);
+ * </pre></ul>
+ *
+ * <p>To change the method handler during runtime,
+ * execute the following code:
+ *
+ * <ul><pre>
+ * MethodHandler mi = ... ; // alternative handler
+ * ((ProxyObject)foo).setHandler(mi);
+ * </pre></ul>
+ *
+ * <p> If setHandler is never called for a proxy instance then it will
+ * employ the default handler which proceeds by invoking the original method.
+ * The behaviour of the default handler is identical to the following
+ * handler:
+ *
+ * <ul><pre>
+ * class EmptyHandler implements MethodHandler {
+ * public Object invoke(Object self, Method m,
+ * Method proceed, Object[] args) throws Exception {
+ * return proceed.invoke(self, args);
+ * }
+ * }
+ * </pre></ul>
+ *
+ * <p>A proxy factory caches and reuses proxy classes by default. It is possible to reset
+ * this default globally by setting static field {@link ProxyFactory#useCache} to false.
+ * Caching may also be configured for a specific factory by calling instance method
+ * {@link ProxyFactory#setUseCache(boolean)}. It is strongly recommended that new clients
+ * of class ProxyFactory enable caching. Failure to do so may lead to exhaustion of
+ * the heap memory area used to store classes.
+ *
+ * <p>Caching is automatically disabled for any given proxy factory if deprecated instance
+ * method {@link ProxyFactory#setHandler(MethodHandler)} is called. This method was
+ * used to specify a default handler which newly created proxy classes should install
+ * when they create their instances. It is only retained to provide backward compatibility
+ * with previous releases of javassist. Unfortunately,this legacy behaviour makes caching
+ * and reuse of proxy classes impossible. The current programming model expects javassist
+ * clients to set the handler of a proxy instance explicitly by calling method
+ * {@link ProxyObject#setHandler(MethodHandler)} as shown in the sample code above. New
+ * clients are strongly recommended to use this model rather than calling
+ * {@link ProxyFactory#setHandler(MethodHandler)}.
+ *
+ * <p>A proxy object generated by <code>ProxyFactory</code> is serializable
+ * if its super class or any of its interfaces implement <code>java.io.Serializable</code>.
+ * However, a serialized proxy object may not be compatible with future releases.
+ * The serialization support should be used for short-term storage or RMI.
+ *
+ * <p>For compatibility with older releases serialization of proxy objects is implemented by
+ * adding a writeReplace method to the proxy class. This allows a proxy to be serialized
+ * to a conventional {@link java.io.ObjectOutputStream} and deserialized from a corresponding
+ * {@link java.io.ObjectInputStream}. However this method suffers from several problems, the most
+ * notable one being that it fails to serialize state inherited from the proxy's superclass.
+ * <p>
+ * An alternative method of serializing proxy objects is available which fixes these problems. It
+ * requires inhibiting generation of the writeReplace method and instead using instances of
+ * {@link javassist.util.proxy.ProxyObjectOutputStream} and {@link javassist.util.proxy.ProxyObjectInputStream}
+ * (which are subclasses of {@link java.io.ObjectOutputStream} and {@link java.io.ObjectInputStream})
+ * to serialize and deserialize, respectively, the proxy. These streams recognise javassist proxies and ensure
+ * that they are serialized and deserialized without the need for the proxy class to implement special methods
+ * such as writeReplace. Generation of the writeReplace method can be disabled globally by setting static field
+ * {@link ProxyFactory#useWriteReplace} to false. Alternatively, it may be
+ * configured per factory by calling instance method {@link ProxyFactory#setUseWriteReplace(boolean)}.
+ *
+ * @see MethodHandler
+ * @since 3.1
+ * @author Muga Nishizawa
+ * @author Shigeru Chiba
+ * @author Andrew Dinn
+ */
+public class ProxyFactory {
+ private Class superClass;
+ private Class[] interfaces;
+ private MethodFilter methodFilter;
+ private MethodHandler handler; // retained for legacy usage
+ private List signatureMethods;
+ private byte[] signature;
+ private String classname;
+ private String basename;
+ private String superName;
+ private Class thisClass;
+ /**
+ * per factory setting initialised from current setting for useCache but able to be reset before each create call
+ */
+ private boolean factoryUseCache;
+ /**
+ * per factory setting initialised from current setting for useWriteReplace but able to be reset before each create call
+ */
+ private boolean factoryWriteReplace;
+
+
+ /**
+ * If the value of this variable is not null, the class file of
+ * the generated proxy class is written under the directory specified
+ * by this variable. For example, if the value is
+ * <code>"."</code>, then the class file is written under the current
+ * directory. This method is for debugging.
+ *
+ * <p>The default value is null.
+ */
+ public String writeDirectory;
+
+ private static final Class OBJECT_TYPE = Object.class;
+
+ private static final String HOLDER = "_methods_";
+ private static final String HOLDER_TYPE = "[Ljava/lang/reflect/Method;";
+ private static final String FILTER_SIGNATURE_FIELD = "_filter_signature";
+ private static final String FILTER_SIGNATURE_TYPE = "[B";
+ private static final String HANDLER = "handler";
+ private static final String NULL_INTERCEPTOR_HOLDER = "javassist.util.proxy.RuntimeSupport";
+ private static final String DEFAULT_INTERCEPTOR = "default_interceptor";
+ private static final String HANDLER_TYPE
+ = 'L' + MethodHandler.class.getName().replace('.', '/') + ';';
+ private static final String HANDLER_SETTER = "setHandler";
+ private static final String HANDLER_SETTER_TYPE = "(" + HANDLER_TYPE + ")V";
+
+ private static final String HANDLER_GETTER = "getHandler";
+ private static final String HANDLER_GETTER_TYPE = "()" + HANDLER_TYPE;
+
+ private static final String SERIAL_VERSION_UID_FIELD = "serialVersionUID";
+ private static final String SERIAL_VERSION_UID_TYPE = "J";
+ private static final int SERIAL_VERSION_UID_VALUE = -1;
+
+ /**
+ * If true, a generated proxy class is cached and it will be reused
+ * when generating the proxy class with the same properties is requested.
+ * The default value is true.
+ *
+ * Note that this value merely specifies the initial setting employed by any newly created
+ * proxy factory. The factory setting may be overwritten by calling factory instance method
+ * {@link #setUseCache(boolean)}
+ *
+ * @since 3.4
+ */
+ public static volatile boolean useCache = true;
+
+ /**
+ * If true, a generated proxy class will implement method writeReplace enabling
+ * serialization of its proxies to a conventional ObjectOutputStream. this (default)
+ * setting retains the old javassist behaviour which has the advantage that it
+ * retains compatibility with older releases and requires no extra work on the part
+ * of the client performing the serialization. However, it has the disadvantage that
+ * state inherited from the superclasses of the proxy is lost during serialization.
+ * if false then serialization/deserialization of the proxy instances will preserve
+ * all fields. However, serialization must be performed via a {@link ProxyObjectOutputStream}
+ * and deserialization must be via {@link ProxyObjectInputStream}. Any attempt to serialize
+ * proxies whose class was created with useWriteReplace set to false via a normal
+ * {@link java.io.ObjectOutputStream} will fail.
+ *
+ * Note that this value merely specifies the initial setting employed by any newly created
+ * proxy factory. The factory setting may be overwritten by calling factory instance method
+ * {@link #setUseWriteReplace(boolean)}
+ *
+ * @since 3.4
+ */
+ public static volatile boolean useWriteReplace = true;
+
+ /*
+ * methods allowing individual factory settings for factoryUseCache and factoryWriteReplace to be reset
+ */
+
+ /**
+ * test whether this factory uses the proxy cache
+ * @return true if this factory uses the proxy cache otherwise false
+ */
+ public boolean isUseCache()
+ {
+ return factoryUseCache;
+ }
+
+ /**
+ * configure whether this factory should use the proxy cache
+ * @param useCache true if this factory should use the proxy cache and false if it should not use the cache
+ * @throws RuntimeException if a default interceptor has been set for the factory
+ */
+ public void setUseCache(boolean useCache)
+ {
+ // we cannot allow caching to be used if the factory is configured to install a default interceptor
+ // field into generated classes
+ if (handler != null && useCache) {
+ throw new RuntimeException("caching cannot be enabled if the factory default interceptor has been set");
+ }
+ factoryUseCache = useCache;
+ }
+
+ /**
+ * test whether this factory installs a writeReplace method in created classes
+ * @return true if this factory installs a writeReplace method in created classes otherwise false
+ */
+ public boolean isUseWriteReplace()
+ {
+ return factoryWriteReplace;
+ }
+
+ /**
+ * configure whether this factory should add a writeReplace method to created classes
+ * @param useWriteReplace true if this factory should add a writeReplace method to created classes and false if it
+ * should not add a writeReplace method
+ */
+ public void setUseWriteReplace(boolean useWriteReplace)
+ {
+ factoryWriteReplace = useWriteReplace;
+ }
+
+ private static WeakHashMap proxyCache = new WeakHashMap();
+
+ /**
+ * determine if a class is a javassist proxy class
+ * @param cl
+ * @return true if the class is a javassist proxy class otherwise false
+ */
+ public static boolean isProxyClass(Class cl)
+ {
+ // all proxies implement ProxyObject. nothing else should.
+ return (ProxyObject.class.isAssignableFrom(cl));
+ }
+
+ /**
+ * used to store details of a specific proxy class in the second tier of the proxy cache. this entry
+ * will be located in a hashmap keyed by the unique identifying name of the proxy class. the hashmap is
+ * located in a weak hashmap keyed by the classloader common to all proxy classes in the second tier map.
+ */
+ static class ProxyDetails {
+ /**
+ * the unique signature of any method filter whose behaviour will be met by this class. each bit in
+ * the byte array is set if the filter redirects the corresponding super or interface method and clear
+ * if it does not redirect it.
+ */
+ byte[] signature;
+ /**
+ * a hexadecimal string representation of the signature bit sequence. this string also forms part
+ * of the proxy class name.
+ */
+ WeakReference proxyClass;
+ /**
+ * a flag which is true this class employs writeReplace to perform serialization of its instances
+ * and false if serialization must employ of a ProxyObjectOutputStream and ProxyObjectInputStream
+ */
+ boolean isUseWriteReplace;
+
+ ProxyDetails(byte[] signature, Class proxyClass, boolean isUseWriteReplace)
+ {
+ this.signature = signature;
+ this.proxyClass = new WeakReference(proxyClass);
+ this.isUseWriteReplace = isUseWriteReplace;
+ }
+ }
+
+ /**
+ * Constructs a factory of proxy class.
+ */
+ public ProxyFactory() {
+ superClass = null;
+ interfaces = null;
+ methodFilter = null;
+ handler = null;
+ signature = null;
+ signatureMethods = null;
+ thisClass = null;
+ writeDirectory = null;
+ factoryUseCache = useCache;
+ factoryWriteReplace = useWriteReplace;
+ }
+
+ /**
+ * Sets the super class of a proxy class.
+ */
+ public void setSuperclass(Class clazz) {
+ superClass = clazz;
+ // force recompute of signature
+ signature = null;
+ }
+
+ /**
+ * Obtains the super class set by <code>setSuperclass()</code>.
+ *
+ * @since 3.4
+ */
+ public Class getSuperclass() { return superClass; }
+
+ /**
+ * Sets the interfaces of a proxy class.
+ */
+ public void setInterfaces(Class[] ifs) {
+ interfaces = ifs;
+ // force recompute of signature
+ signature = null;
+ }
+
+ /**
+ * Obtains the interfaces set by <code>setInterfaces</code>.
+ *
+ * @since 3.4
+ */
+ public Class[] getInterfaces() { return interfaces; }
+
+ /**
+ * Sets a filter that selects the methods that will be controlled by a handler.
+ */
+ public void setFilter(MethodFilter mf) {
+ methodFilter = mf;
+ // force recompute of signature
+ signature = null;
+ }
+
+ /**
+ * Generates a proxy class using the current filter.
+ */
+ public Class createClass() {
+ if (signature == null) {
+ computeSignature(methodFilter);
+ }
+ return createClass1();
+ }
+
+ /**
+ * Generates a proxy class using the supplied filter.
+ */
+ public Class createClass(MethodFilter filter) {
+ computeSignature(filter);
+ return createClass1();
+ }
+
+ /**
+ * Generates a proxy class with a specific signature.
+ * access is package local so ProxyObjectInputStream can use this
+ * @param signature
+ * @return
+ */
+ Class createClass(byte[] signature)
+ {
+ installSignature(signature);
+ return createClass1();
+ }
+
+ private Class createClass1() {
+ if (thisClass == null) {
+ ClassLoader cl = getClassLoader();
+ synchronized (proxyCache) {
+ if (factoryUseCache)
+ createClass2(cl);
+ else
+ createClass3(cl);
+ }
+ }
+
+ // don't retain any unwanted references
+ Class result = thisClass;
+ thisClass = null;
+
+ return result;
+ }
+
+ private static char[] hexDigits =
+ { '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+
+ public String getKey(Class superClass, Class[] interfaces, byte[] signature, boolean useWriteReplace)
+ {
+ StringBuffer sbuf = new StringBuffer();
+ if (superClass != null){
+ sbuf.append(superClass.getName());
+ }
+ sbuf.append(":");
+ for (int i = 0; i < interfaces.length; i++) {
+ sbuf.append(interfaces[i].getName());
+ sbuf.append(":");
+ }
+ for (int i = 0; i < signature.length; i++) {
+ byte b = signature[i];
+ int lo = b & 0xf;
+ int hi = (b >> 4) & 0xf;
+ sbuf.append(hexDigits[lo]);
+ sbuf.append(hexDigits[hi]);
+ }
+ if (useWriteReplace) {
+ sbuf.append(":w");
+ }
+
+ return sbuf.toString();
+ }
+
+ private void createClass2(ClassLoader cl) {
+ String key = getKey(superClass, interfaces, signature, factoryWriteReplace);
+ /*
+ * Excessive concurrency causes a large memory footprint and slows the
+ * execution speed down (with JDK 1.5). Thus, we use a jumbo lock for
+ * reducing concrrency.
+ */
+ // synchronized (proxyCache) {
+ HashMap cacheForTheLoader = (HashMap)proxyCache.get(cl);
+ ProxyDetails details;
+ if (cacheForTheLoader == null) {
+ cacheForTheLoader = new HashMap();
+ proxyCache.put(cl, cacheForTheLoader);
+ }
+ details = (ProxyDetails)cacheForTheLoader.get(key);
+ if (details != null) {
+ WeakReference reference = details.proxyClass;
+ thisClass = (Class)reference.get();
+ if (thisClass != null) {
+ return;
+ }
+ }
+ createClass3(cl);
+ details = new ProxyDetails(signature, thisClass, factoryWriteReplace);
+ cacheForTheLoader.put(key, details);
+ // }
+ }
+
+ private void createClass3(ClassLoader cl) {
+ // we need a new class so we need a new class name
+ allocateClassName();
+
+ try {
+ ClassFile cf = make();
+ if (writeDirectory != null)
+ FactoryHelper.writeFile(cf, writeDirectory);
+
+ thisClass = FactoryHelper.toClass(cf, cl, getDomain());
+ setField(FILTER_SIGNATURE_FIELD, signature);
+ // legacy behaviour : we only set the default interceptor static field if we are not using the cache
+ if (!factoryUseCache) {
+ setField(DEFAULT_INTERCEPTOR, handler);
+ }
+ }
+ catch (CannotCompileException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ }
+
+ }
+
+ private void setField(String fieldName, Object value) {
+ if (thisClass != null && value != null)
+ try {
+ Field f = thisClass.getField(fieldName);
+ SecurityActions.setAccessible(f, true);
+ f.set(null, value);
+ SecurityActions.setAccessible(f, false);
+ }
+ catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ static byte[] getFilterSignature(Class clazz) {
+ return (byte[])getField(clazz, FILTER_SIGNATURE_FIELD);
+ }
+
+ private static Object getField(Class clazz, String fieldName) {
+ try {
+ Field f = clazz.getField(fieldName);
+ f.setAccessible(true);
+ Object value = f.get(null);
+ f.setAccessible(false);
+ return value;
+ }
+ catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * A provider of class loaders.
+ *
+ * @see #classLoaderProvider
+ * @since 3.4
+ */
+ public static interface ClassLoaderProvider {
+ /**
+ * Returns a class loader.
+ *
+ * @param pf a proxy factory that is going to obtain a class loader.
+ */
+ public ClassLoader get(ProxyFactory pf);
+ }
+
+ /**
+ * A provider used by <code>createClass()</code> for obtaining
+ * a class loader.
+ * <code>get()</code> on this <code>ClassLoaderProvider</code> object
+ * is called to obtain a class loader.
+ *
+ * <p>The value of this field can be updated for changing the default
+ * implementation.
+ *
+ * <p>Example:
+ * <ul><pre>
+ * ProxyFactory.classLoaderProvider = new ProxyFactory.ClassLoaderProvider() {
+ * public ClassLoader get(ProxyFactory pf) {
+ * return Thread.currentThread().getContextClassLoader();
+ * }
+ * };
+ * </pre></ul>
+ *
+ * @since 3.4
+ */
+ public static ClassLoaderProvider classLoaderProvider
+ = new ClassLoaderProvider() {
+ public ClassLoader get(ProxyFactory pf) {
+ return pf.getClassLoader0();
+ }
+ };
+
+ protected ClassLoader getClassLoader() {
+ return classLoaderProvider.get(this);
+ }
+
+ protected ClassLoader getClassLoader0() {
+ ClassLoader loader = null;
+ if (superClass != null && !superClass.getName().equals("java.lang.Object"))
+ loader = superClass.getClassLoader();
+ else if (interfaces != null && interfaces.length > 0)
+ loader = interfaces[0].getClassLoader();
+
+ if (loader == null) {
+ loader = getClass().getClassLoader();
+ // In case javassist is in the endorsed dir
+ if (loader == null) {
+ loader = Thread.currentThread().getContextClassLoader();
+ if (loader == null)
+ loader = ClassLoader.getSystemClassLoader();
+ }
+ }
+
+ return loader;
+ }
+
+ protected ProtectionDomain getDomain() {
+ Class clazz;
+ if (superClass != null && !superClass.getName().equals("java.lang.Object"))
+ clazz = superClass;
+ else if (interfaces != null && interfaces.length > 0)
+ clazz = interfaces[0];
+ else
+ clazz = this.getClass();
+
+ return clazz.getProtectionDomain();
+ }
+
+ /**
+ * Creates a proxy class and returns an instance of that class.
+ *
+ * @param paramTypes parameter types for a constructor.
+ * @param args arguments passed to a constructor.
+ * @param mh the method handler for the proxy class.
+ * @since 3.4
+ */
+ public Object create(Class[] paramTypes, Object[] args, MethodHandler mh)
+ throws NoSuchMethodException, IllegalArgumentException,
+ InstantiationException, IllegalAccessException, InvocationTargetException
+ {
+ Object obj = create(paramTypes, args);
+ ((ProxyObject)obj).setHandler(mh);
+ return obj;
+ }
+
+ /**
+ * Creates a proxy class and returns an instance of that class.
+ *
+ * @param paramTypes parameter types for a constructor.
+ * @param args arguments passed to a constructor.
+ */
+ public Object create(Class[] paramTypes, Object[] args)
+ throws NoSuchMethodException, IllegalArgumentException,
+ InstantiationException, IllegalAccessException, InvocationTargetException
+ {
+ Class c = createClass();
+ Constructor cons = c.getConstructor(paramTypes);
+ return cons.newInstance(args);
+ }
+
+ /**
+ * Sets the default invocation handler. This invocation handler is shared
+ * among all the instances of a proxy class unless another is explicitly
+ * specified.
+ * @deprecated since 3.12
+ * use of this method is incompatible with proxy class caching.
+ * instead clients should call method {@link ProxyObject#setHandler(MethodHandler)} to set the handler
+ * for each newly created proxy instance.
+ * calling this method will automatically disable caching of classes created by the proxy factory.
+ */
+ public void setHandler(MethodHandler mi) {
+ // if we were using the cache and the handler is non-null then we must stop caching
+ if (factoryUseCache && mi != null) {
+ factoryUseCache = false;
+ // clear any currently held class so we don't try to reuse it or set its handler field
+ thisClass = null;
+ }
+ handler = mi;
+ // this retains the behaviour of the old code which resets any class we were holding on to
+ // this is probably not what is wanted
+ setField(DEFAULT_INTERCEPTOR, handler);
+ }
+
+ private static int counter = 0;
+
+ private static synchronized String makeProxyName(String classname) {
+ return classname + "_$$_javassist_" + counter++;
+ }
+
+ private ClassFile make() throws CannotCompileException {
+ ClassFile cf = new ClassFile(false, classname, superName);
+ cf.setAccessFlags(AccessFlag.PUBLIC);
+ setInterfaces(cf, interfaces);
+ ConstPool pool = cf.getConstPool();
+
+ // legacy: we only add the static field for the default interceptor if caching is disabled
+ if (!factoryUseCache) {
+ FieldInfo finfo = new FieldInfo(pool, DEFAULT_INTERCEPTOR, HANDLER_TYPE);
+ finfo.setAccessFlags(AccessFlag.PUBLIC | AccessFlag.STATIC);
+ cf.addField(finfo);
+ }
+
+ // handler is per instance
+ FieldInfo finfo2 = new FieldInfo(pool, HANDLER, HANDLER_TYPE);
+ finfo2.setAccessFlags(AccessFlag.PRIVATE);
+ cf.addField(finfo2);
+
+ // filter signature is per class
+ FieldInfo finfo3 = new FieldInfo(pool, FILTER_SIGNATURE_FIELD, FILTER_SIGNATURE_TYPE);
+ finfo3.setAccessFlags(AccessFlag.PUBLIC | AccessFlag.STATIC);
+ cf.addField(finfo3);
+
+ // the proxy class serial uid must always be a fixed value
+ FieldInfo finfo4 = new FieldInfo(pool, SERIAL_VERSION_UID_FIELD, SERIAL_VERSION_UID_TYPE);
+ finfo4.setAccessFlags(AccessFlag.PUBLIC | AccessFlag.STATIC| AccessFlag.FINAL);
+ cf.addField(finfo4);
+
+ // HashMap allMethods = getMethods(superClass, interfaces);
+ // int size = allMethods.size();
+ makeConstructors(classname, cf, pool, classname);
+ int s = overrideMethods(cf, pool, classname);
+ addMethodsHolder(cf, pool, classname, s);
+ addSetter(classname, cf, pool);
+ addGetter(classname, cf, pool);
+
+ if (factoryWriteReplace) {
+ try {
+ cf.addMethod(makeWriteReplace(pool));
+ }
+ catch (DuplicateMemberException e) {
+ // writeReplace() is already declared in the super class/interfaces.
+ }
+ }
+
+ thisClass = null;
+ return cf;
+ }
+
+ private void checkClassAndSuperName()
+ {
+ if (interfaces == null)
+ interfaces = new Class[0];
+
+ if (superClass == null) {
+ superClass = OBJECT_TYPE;
+ superName = superClass.getName();
+ basename = interfaces.length == 0 ? superName
+ : interfaces[0].getName();
+ } else {
+ superName = superClass.getName();
+ basename = superName;
+ }
+
+ if (Modifier.isFinal(superClass.getModifiers()))
+ throw new RuntimeException(superName + " is final");
+
+ if (basename.startsWith("java."))
+ basename = "org.javassist.tmp." + basename;
+ }
+
+ private void allocateClassName()
+ {
+ classname = makeProxyName(basename);
+ }
+
+ private static Comparator sorter = new Comparator() {
+
+ public int compare(Object o1, Object o2) {
+ Map.Entry e1 = (Map.Entry)o1;
+ Map.Entry e2 = (Map.Entry)o2;
+ String key1 = (String)e1.getKey();
+ String key2 = (String)e2.getKey();
+ return key1.compareTo(key2);
+ }
+ };
+
+ private void makeSortedMethodList()
+ {
+ checkClassAndSuperName();
+
+ HashMap allMethods = getMethods(superClass, interfaces);
+ signatureMethods = new ArrayList(allMethods.entrySet());
+ Collections.sort(signatureMethods, sorter);
+ }
+
+ private void computeSignature(MethodFilter filter) // throws CannotCompileException
+ {
+ makeSortedMethodList();
+
+ int l = signatureMethods.size();
+ int maxBytes = ((l + 7) >> 3);
+ signature = new byte[maxBytes];
+ for (int idx = 0; idx < l; idx++)
+ {
+ Map.Entry e = (Map.Entry)signatureMethods.get(idx);
+ Method m = (Method)e.getValue();
+ int mod = m.getModifiers();
+ if (!Modifier.isFinal(mod) && !Modifier.isStatic(mod)
+ && isVisible(mod, basename, m) && (filter == null || filter.isHandled(m))) {
+ setBit(signature, idx);
+ }
+ }
+ }
+
+ private void installSignature(byte[] signature) // throws CannotCompileException
+ {
+ makeSortedMethodList();
+
+ int l = signatureMethods.size();
+ int maxBytes = ((l + 7) >> 3);
+ if (signature.length != maxBytes) {
+ throw new RuntimeException("invalid filter signature length for deserialized proxy class");
+ }
+
+ this.signature = signature;
+ }
+
+ private boolean testBit(byte[] signature, int idx)
+ {
+ int byteIdx = idx >> 3;
+ if (byteIdx > signature.length) {
+ return false;
+ } else {
+ int bitIdx = idx & 0x7;
+ int mask = 0x1 << bitIdx;
+ int sigByte = signature[byteIdx];
+ return ((sigByte & mask) != 0);
+ }
+ }
+
+ private void setBit(byte[] signature, int idx)
+ {
+ int byteIdx = idx >> 3;
+ if (byteIdx < signature.length) {
+ int bitIdx = idx & 0x7;
+ int mask = 0x1 << bitIdx;
+ int sigByte = signature[byteIdx];
+ signature[byteIdx] = (byte)(sigByte | mask);
+ }
+ }
+
+ private static void setInterfaces(ClassFile cf, Class[] interfaces) {
+ String setterIntf = ProxyObject.class.getName();
+ String[] list;
+ if (interfaces == null || interfaces.length == 0)
+ list = new String[] { setterIntf };
+ else {
+ list = new String[interfaces.length + 1];
+ for (int i = 0; i < interfaces.length; i++)
+ list[i] = interfaces[i].getName();
+
+ list[interfaces.length] = setterIntf;
+ }
+
+ cf.setInterfaces(list);
+ }
+
+ private static void addMethodsHolder(ClassFile cf, ConstPool cp,
+ String classname, int size)
+ throws CannotCompileException
+ {
+ FieldInfo finfo = new FieldInfo(cp, HOLDER, HOLDER_TYPE);
+ finfo.setAccessFlags(AccessFlag.PRIVATE | AccessFlag.STATIC);
+ cf.addField(finfo);
+ MethodInfo minfo = new MethodInfo(cp, "<clinit>", "()V");
+ minfo.setAccessFlags(AccessFlag.STATIC);
+ Bytecode code = new Bytecode(cp, 0, 0);
+ code.addIconst(size * 2);
+ code.addAnewarray("java.lang.reflect.Method");
+ code.addPutstatic(classname, HOLDER, HOLDER_TYPE);
+ // also need to set serial version uid
+ code.addLconst(-1L);
+ code.addPutstatic(classname, SERIAL_VERSION_UID_FIELD, SERIAL_VERSION_UID_TYPE);
+ code.addOpcode(Bytecode.RETURN);
+ minfo.setCodeAttribute(code.toCodeAttribute());
+ cf.addMethod(minfo);
+ }
+
+ private static void addSetter(String classname, ClassFile cf, ConstPool cp)
+ throws CannotCompileException
+ {
+ MethodInfo minfo = new MethodInfo(cp, HANDLER_SETTER,
+ HANDLER_SETTER_TYPE);
+ minfo.setAccessFlags(AccessFlag.PUBLIC);
+ Bytecode code = new Bytecode(cp, 2, 2);
+ code.addAload(0);
+ code.addAload(1);
+ code.addPutfield(classname, HANDLER, HANDLER_TYPE);
+ code.addOpcode(Bytecode.RETURN);
+ minfo.setCodeAttribute(code.toCodeAttribute());
+ cf.addMethod(minfo);
+ }
+
+ private static void addGetter(String classname, ClassFile cf, ConstPool cp)
+ throws CannotCompileException
+ {
+ MethodInfo minfo = new MethodInfo(cp, HANDLER_GETTER,
+ HANDLER_GETTER_TYPE);
+ minfo.setAccessFlags(AccessFlag.PUBLIC);
+ Bytecode code = new Bytecode(cp, 1, 1);
+ code.addAload(0);
+ code.addGetfield(classname, HANDLER, HANDLER_TYPE);
+ code.addOpcode(Bytecode.ARETURN);
+ minfo.setCodeAttribute(code.toCodeAttribute());
+ cf.addMethod(minfo);
+ }
+
+ private int overrideMethods(ClassFile cf, ConstPool cp, String className)
+ throws CannotCompileException
+ {
+ String prefix = makeUniqueName("_d", signatureMethods);
+ Iterator it = signatureMethods.iterator();
+ int index = 0;
+ while (it.hasNext()) {
+ Map.Entry e = (Map.Entry)it.next();
+ String key = (String)e.getKey();
+ Method meth = (Method)e.getValue();
+ int mod = meth.getModifiers();
+ if (testBit(signature, index)) {
+ override(className, meth, prefix, index,
+ keyToDesc(key), cf, cp);
+ }
+ index++;
+ }
+
+ return index;
+ }
+
+ private void override(String thisClassname, Method meth, String prefix,
+ int index, String desc, ClassFile cf, ConstPool cp)
+ throws CannotCompileException
+ {
+ Class declClass = meth.getDeclaringClass();
+ String delegatorName = prefix + index + meth.getName();
+ if (Modifier.isAbstract(meth.getModifiers()))
+ delegatorName = null;
+ else {
+ MethodInfo delegator
+ = makeDelegator(meth, desc, cp, declClass, delegatorName);
+ // delegator is not a bridge method. See Sec. 15.12.4.5 of JLS 3rd Ed.
+ delegator.setAccessFlags(delegator.getAccessFlags() & ~AccessFlag.BRIDGE);
+ cf.addMethod(delegator);
+ }
+
+ MethodInfo forwarder
+ = makeForwarder(thisClassname, meth, desc, cp, declClass,
+ delegatorName, index);
+ cf.addMethod(forwarder);
+ }
+
+ private void makeConstructors(String thisClassName, ClassFile cf,
+ ConstPool cp, String classname) throws CannotCompileException
+ {
+ Constructor[] cons = SecurityActions.getDeclaredConstructors(superClass);
+ // legacy: if we are not caching then we need to initialise the default handler
+ boolean doHandlerInit = !factoryUseCache;
+ for (int i = 0; i < cons.length; i++) {
+ Constructor c = cons[i];
+ int mod = c.getModifiers();
+ if (!Modifier.isFinal(mod) && !Modifier.isPrivate(mod)
+ && isVisible(mod, basename, c)) {
+ MethodInfo m = makeConstructor(thisClassName, c, cp, superClass, doHandlerInit);
+ cf.addMethod(m);
+ }
+ }
+ }
+
+ private static String makeUniqueName(String name, List sortedMethods) {
+ if (makeUniqueName0(name, sortedMethods.iterator()))
+ return name;
+
+ for (int i = 100; i < 999; i++) {
+ String s = name + i;
+ if (makeUniqueName0(s, sortedMethods.iterator()))
+ return s;
+ }
+
+ throw new RuntimeException("cannot make a unique method name");
+ }
+
+ private static boolean makeUniqueName0(String name, Iterator it) {
+ while (it.hasNext()) {
+ Map.Entry e = (Map.Entry)it.next();
+ String key = (String)e.getKey();
+ if (key.startsWith(name))
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns true if the method is visible from the package.
+ *
+ * @param mod the modifiers of the method.
+ */
+ private static boolean isVisible(int mod, String from, Member meth) {
+ if ((mod & Modifier.PRIVATE) != 0)
+ return false;
+ else if ((mod & (Modifier.PUBLIC | Modifier.PROTECTED)) != 0)
+ return true;
+ else {
+ String p = getPackageName(from);
+ String q = getPackageName(meth.getDeclaringClass().getName());
+ if (p == null)
+ return q == null;
+ else
+ return p.equals(q);
+ }
+ }
+
+ private static String getPackageName(String name) {
+ int i = name.lastIndexOf('.');
+ if (i < 0)
+ return null;
+ else
+ return name.substring(0, i);
+ }
+
+ private static HashMap getMethods(Class superClass, Class[] interfaceTypes) {
+ HashMap hash = new HashMap();
+ for (int i = 0; i < interfaceTypes.length; i++)
+ getMethods(hash, interfaceTypes[i]);
+
+ getMethods(hash, superClass);
+ return hash;
+ }
+
+ private static void getMethods(HashMap hash, Class clazz) {
+ Class[] ifs = clazz.getInterfaces();
+ for (int i = 0; i < ifs.length; i++)
+ getMethods(hash, ifs[i]);
+
+ Class parent = clazz.getSuperclass();
+ if (parent != null)
+ getMethods(hash, parent);
+
+ Method[] methods = SecurityActions.getDeclaredMethods(clazz);
+ for (int i = 0; i < methods.length; i++)
+ if (!Modifier.isPrivate(methods[i].getModifiers())) {
+ Method m = methods[i];
+ String key = m.getName() + ':' + RuntimeSupport.makeDescriptor(m);
+ // JIRA JASSIST-85
+ // put the method to the cache, retrieve previous definition (if any)
+ Method oldMethod = (Method)hash.put(key, methods[i]);
+
+ // check if visibility has been reduced
+ if (null != oldMethod && Modifier.isPublic(oldMethod.getModifiers())
+ && !Modifier.isPublic(methods[i].getModifiers()) ) {
+ // we tried to overwrite a public definition with a non-public definition,
+ // use the old definition instead.
+ hash.put(key, oldMethod);
+ }
+ }
+ }
+
+ private static String keyToDesc(String key) {
+ return key.substring(key.indexOf(':') + 1);
+ }
+
+ private static MethodInfo makeConstructor(String thisClassName, Constructor cons,
+ ConstPool cp, Class superClass, boolean doHandlerInit) {
+ String desc = RuntimeSupport.makeDescriptor(cons.getParameterTypes(),
+ Void.TYPE);
+ MethodInfo minfo = new MethodInfo(cp, "<init>", desc);
+ minfo.setAccessFlags(Modifier.PUBLIC); // cons.getModifiers() & ~Modifier.NATIVE
+ setThrows(minfo, cp, cons.getExceptionTypes());
+ Bytecode code = new Bytecode(cp, 0, 0);
+
+ // legacy: if we are not using caching then we initialise the instance's handler
+ // from the class's static default interceptor and skip the next few instructions if
+ // it is non-null
+ if (doHandlerInit) {
+ code.addAload(0);
+ code.addGetstatic(thisClassName, DEFAULT_INTERCEPTOR, HANDLER_TYPE);
+ code.addPutfield(thisClassName, HANDLER, HANDLER_TYPE);
+ code.addGetstatic(thisClassName, DEFAULT_INTERCEPTOR, HANDLER_TYPE);
+ code.addOpcode(Opcode.IFNONNULL);
+ code.addIndex(10);
+ }
+ // if caching is enabled then we don't have a handler to initialise so this else branch will install
+ // the handler located in the static field of class RuntimeSupport.
+ code.addAload(0);
+ code.addGetstatic(NULL_INTERCEPTOR_HOLDER, DEFAULT_INTERCEPTOR, HANDLER_TYPE);
+ code.addPutfield(thisClassName, HANDLER, HANDLER_TYPE);
+ int pc = code.currentPc();
+
+ code.addAload(0);
+ int s = addLoadParameters(code, cons.getParameterTypes(), 1);
+ code.addInvokespecial(superClass.getName(), "<init>", desc);
+ code.addOpcode(Opcode.RETURN);
+ code.setMaxLocals(s + 1);
+ CodeAttribute ca = code.toCodeAttribute();
+ minfo.setCodeAttribute(ca);
+
+ StackMapTable.Writer writer = new StackMapTable.Writer(32);
+ writer.sameFrame(pc);
+ ca.setAttribute(writer.toStackMapTable(cp));
+ return minfo;
+ }
+
+ private static MethodInfo makeDelegator(Method meth, String desc,
+ ConstPool cp, Class declClass, String delegatorName) {
+ MethodInfo delegator = new MethodInfo(cp, delegatorName, desc);
+ delegator.setAccessFlags(Modifier.FINAL | Modifier.PUBLIC
+ | (meth.getModifiers() & ~(Modifier.PRIVATE
+ | Modifier.PROTECTED
+ | Modifier.ABSTRACT
+ | Modifier.NATIVE
+ | Modifier.SYNCHRONIZED)));
+ setThrows(delegator, cp, meth);
+ Bytecode code = new Bytecode(cp, 0, 0);
+ code.addAload(0);
+ int s = addLoadParameters(code, meth.getParameterTypes(), 1);
+ code.addInvokespecial(declClass.getName(), meth.getName(), desc);
+ addReturn(code, meth.getReturnType());
+ code.setMaxLocals(++s);
+ delegator.setCodeAttribute(code.toCodeAttribute());
+ return delegator;
+ }
+
+ /**
+ * @param delegatorName null if the original method is abstract.
+ */
+ private static MethodInfo makeForwarder(String thisClassName,
+ Method meth, String desc, ConstPool cp,
+ Class declClass, String delegatorName, int index) {
+ MethodInfo forwarder = new MethodInfo(cp, meth.getName(), desc);
+ forwarder.setAccessFlags(Modifier.FINAL
+ | (meth.getModifiers() & ~(Modifier.ABSTRACT
+ | Modifier.NATIVE
+ | Modifier.SYNCHRONIZED)));
+ setThrows(forwarder, cp, meth);
+ int args = Descriptor.paramSize(desc);
+ Bytecode code = new Bytecode(cp, 0, args + 2);
+ /*
+ * if (methods[index * 2] == null) {
+ * methods[index * 2]
+ * = RuntimeSupport.findSuperMethod(this, <overridden name>, <desc>);
+ * methods[index * 2 + 1]
+ * = RuntimeSupport.findMethod(this, <delegator name>, <desc>);
+ * or = null // the original method is abstract.
+ * }
+ * return ($r)handler.invoke(this, methods[index * 2],
+ * methods[index * 2 + 1], $args);
+ */
+ int origIndex = index * 2;
+ int delIndex = index * 2 + 1;
+ int arrayVar = args + 1;
+ code.addGetstatic(thisClassName, HOLDER, HOLDER_TYPE);
+ code.addAstore(arrayVar);
+
+ callFind2Methods(code, meth.getName(), delegatorName, origIndex, desc, arrayVar);
+
+ code.addAload(0);
+ code.addGetfield(thisClassName, HANDLER, HANDLER_TYPE);
+ code.addAload(0);
+
+ code.addAload(arrayVar);
+ code.addIconst(origIndex);
+ code.addOpcode(Opcode.AALOAD);
+
+ code.addAload(arrayVar);
+ code.addIconst(delIndex);
+ code.addOpcode(Opcode.AALOAD);
+
+ makeParameterList(code, meth.getParameterTypes());
+ code.addInvokeinterface(MethodHandler.class.getName(), "invoke",
+ "(Ljava/lang/Object;Ljava/lang/reflect/Method;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;",
+ 5);
+ Class retType = meth.getReturnType();
+ addUnwrapper(code, retType);
+ addReturn(code, retType);
+
+ CodeAttribute ca = code.toCodeAttribute();
+ forwarder.setCodeAttribute(ca);
+ return forwarder;
+ }
+
+ private static void setThrows(MethodInfo minfo, ConstPool cp, Method orig) {
+ Class[] exceptions = orig.getExceptionTypes();
+ setThrows(minfo, cp, exceptions);
+ }
+
+ private static void setThrows(MethodInfo minfo, ConstPool cp,
+ Class[] exceptions) {
+ if (exceptions.length == 0)
+ return;
+
+ String[] list = new String[exceptions.length];
+ for (int i = 0; i < exceptions.length; i++)
+ list[i] = exceptions[i].getName();
+
+ ExceptionsAttribute ea = new ExceptionsAttribute(cp);
+ ea.setExceptions(list);
+ minfo.setExceptionsAttribute(ea);
+ }
+
+ private static int addLoadParameters(Bytecode code, Class[] params,
+ int offset) {
+ int stacksize = 0;
+ int n = params.length;
+ for (int i = 0; i < n; ++i)
+ stacksize += addLoad(code, stacksize + offset, params[i]);
+
+ return stacksize;
+ }
+
+ private static int addLoad(Bytecode code, int n, Class type) {
+ if (type.isPrimitive()) {
+ if (type == Long.TYPE) {
+ code.addLload(n);
+ return 2;
+ }
+ else if (type == Float.TYPE)
+ code.addFload(n);
+ else if (type == Double.TYPE) {
+ code.addDload(n);
+ return 2;
+ }
+ else
+ code.addIload(n);
+ }
+ else
+ code.addAload(n);
+
+ return 1;
+ }
+
+ private static int addReturn(Bytecode code, Class type) {
+ if (type.isPrimitive()) {
+ if (type == Long.TYPE) {
+ code.addOpcode(Opcode.LRETURN);
+ return 2;
+ }
+ else if (type == Float.TYPE)
+ code.addOpcode(Opcode.FRETURN);
+ else if (type == Double.TYPE) {
+ code.addOpcode(Opcode.DRETURN);
+ return 2;
+ }
+ else if (type == Void.TYPE) {
+ code.addOpcode(Opcode.RETURN);
+ return 0;
+ }
+ else
+ code.addOpcode(Opcode.IRETURN);
+ }
+ else
+ code.addOpcode(Opcode.ARETURN);
+
+ return 1;
+ }
+
+ private static void makeParameterList(Bytecode code, Class[] params) {
+ int regno = 1;
+ int n = params.length;
+ code.addIconst(n);
+ code.addAnewarray("java/lang/Object");
+ for (int i = 0; i < n; i++) {
+ code.addOpcode(Opcode.DUP);
+ code.addIconst(i);
+ Class type = params[i];
+ if (type.isPrimitive())
+ regno = makeWrapper(code, type, regno);
+ else {
+ code.addAload(regno);
+ regno++;
+ }
+
+ code.addOpcode(Opcode.AASTORE);
+ }
+ }
+
+ private static int makeWrapper(Bytecode code, Class type, int regno) {
+ int index = FactoryHelper.typeIndex(type);
+ String wrapper = FactoryHelper.wrapperTypes[index];
+ code.addNew(wrapper);
+ code.addOpcode(Opcode.DUP);
+ addLoad(code, regno, type);
+ code.addInvokespecial(wrapper, "<init>",
+ FactoryHelper.wrapperDesc[index]);
+ return regno + FactoryHelper.dataSize[index];
+ }
+
+ /**
+ * @param thisMethod might be null.
+ */
+ private static void callFind2Methods(Bytecode code, String superMethod, String thisMethod,
+ int index, String desc, int arrayVar) {
+ String findClass = RuntimeSupport.class.getName();
+ String findDesc
+ = "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;[Ljava/lang/reflect/Method;)V";
+
+ code.addAload(0);
+ code.addLdc(superMethod);
+ if (thisMethod == null)
+ code.addOpcode(Opcode.ACONST_NULL);
+ else
+ code.addLdc(thisMethod);
+
+ code.addIconst(index);
+ code.addLdc(desc);
+ code.addAload(arrayVar);
+ code.addInvokestatic(findClass, "find2Methods", findDesc);
+ }
+
+ private static void addUnwrapper(Bytecode code, Class type) {
+ if (type.isPrimitive()) {
+ if (type == Void.TYPE)
+ code.addOpcode(Opcode.POP);
+ else {
+ int index = FactoryHelper.typeIndex(type);
+ String wrapper = FactoryHelper.wrapperTypes[index];
+ code.addCheckcast(wrapper);
+ code.addInvokevirtual(wrapper,
+ FactoryHelper.unwarpMethods[index],
+ FactoryHelper.unwrapDesc[index]);
+ }
+ }
+ else
+ code.addCheckcast(type.getName());
+ }
+
+ private static MethodInfo makeWriteReplace(ConstPool cp) {
+ MethodInfo minfo = new MethodInfo(cp, "writeReplace", "()Ljava/lang/Object;");
+ String[] list = new String[1];
+ list[0] = "java.io.ObjectStreamException";
+ ExceptionsAttribute ea = new ExceptionsAttribute(cp);
+ ea.setExceptions(list);
+ minfo.setExceptionsAttribute(ea);
+ Bytecode code = new Bytecode(cp, 0, 1);
+ code.addAload(0);
+ code.addInvokestatic("javassist.util.proxy.RuntimeSupport",
+ "makeSerializedProxy",
+ "(Ljava/lang/Object;)Ljavassist/util/proxy/SerializedProxy;");
+ code.addOpcode(Opcode.ARETURN);
+ minfo.setCodeAttribute(code.toCodeAttribute());
+ return minfo;
+ }
+}
diff --git a/src/main/javassist/util/proxy/ProxyObject.java b/src/main/javassist/util/proxy/ProxyObject.java
new file mode 100644
index 0000000..08febd6
--- /dev/null
+++ b/src/main/javassist/util/proxy/ProxyObject.java
@@ -0,0 +1,36 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.util.proxy;
+
+/**
+ * The interface implemented by proxy classes.
+ *
+ * @see ProxyFactory
+ */
+public interface ProxyObject {
+ /**
+ * Sets a handler. It can be used for changing handlers
+ * during runtime.
+ */
+ void setHandler(MethodHandler mi);
+
+ /**
+ * Get the handler.
+ * This can be used to access values of the underlying MethodHandler
+ * or to serialize it properly.
+ */
+ MethodHandler getHandler();
+}
diff --git a/src/main/javassist/util/proxy/ProxyObjectInputStream.java b/src/main/javassist/util/proxy/ProxyObjectInputStream.java
new file mode 100644
index 0000000..62c5eea
--- /dev/null
+++ b/src/main/javassist/util/proxy/ProxyObjectInputStream.java
@@ -0,0 +1,99 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2010 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.util.proxy;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectStreamClass;
+
+/**
+ * An input stream class which knows how to deserialize proxies created via {@link ProxyFactory} and
+ * serializedo via a {@link ProxyObjectOutputStream}. It must be used when deserialising proxies created
+ * from a proxy factory configured with {@link ProxyFactory#useWriteReplace} set to false.
+ *
+ * @author Andrew Dinn
+ */
+public class ProxyObjectInputStream extends ObjectInputStream
+{
+ /**
+ * create an input stream which can be used to deserialize an object graph which includes proxies created
+ * using class ProxyFactory. the classloader used to resolve proxy superclass and interface names
+ * read from the input stream will default to the current thread's context class loader or the system
+ * classloader if the context class loader is null.
+ * @param in
+ * @throws java.io.StreamCorruptedException whenever ObjectInputStream would also do so
+ * @throws IOException whenever ObjectInputStream would also do so
+ * @throws SecurityException whenever ObjectInputStream would also do so
+ * @throws NullPointerException if in is null
+ */
+ public ProxyObjectInputStream(InputStream in) throws IOException
+ {
+ super(in);
+ loader = Thread.currentThread().getContextClassLoader();
+ if (loader == null) {
+ loader = ClassLoader.getSystemClassLoader();
+ }
+ }
+
+ /**
+ * Reset the loader to be
+ * @param loader
+ */
+ public void setClassLoader(ClassLoader loader)
+ {
+ if (loader != null) {
+ this.loader = loader;
+ } else {
+ loader = ClassLoader.getSystemClassLoader();
+ }
+ }
+
+ protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException {
+ boolean isProxy = readBoolean();
+ if (isProxy) {
+ String name = (String)readObject();
+ Class superClass = loader.loadClass(name);
+ int length = readInt();
+ Class[] interfaces = new Class[length];
+ for (int i = 0; i < length; i++) {
+ name = (String)readObject();
+ interfaces[i] = loader.loadClass(name);
+ }
+ length = readInt();
+ byte[] signature = new byte[length];
+ read(signature);
+ ProxyFactory factory = new ProxyFactory();
+ // we must always use the cache and never use writeReplace when using
+ // ProxyObjectOutputStream and ProxyObjectInputStream
+ factory.setUseCache(true);
+ factory.setUseWriteReplace(false);
+ factory.setSuperclass(superClass);
+ factory.setInterfaces(interfaces);
+ Class proxyClass = factory.createClass(signature);
+ return ObjectStreamClass.lookup(proxyClass);
+ } else {
+ return super.readClassDescriptor();
+ }
+ }
+
+ /**
+ * the loader to use to resolve classes for proxy superclass and interface names read
+ * from the stream. defaults to the context class loader of the thread which creates
+ * the input stream or the system class loader if the context class loader is null.
+ */
+ private ClassLoader loader;
+}
\ No newline at end of file
diff --git a/src/main/javassist/util/proxy/ProxyObjectOutputStream.java b/src/main/javassist/util/proxy/ProxyObjectOutputStream.java
new file mode 100644
index 0000000..b2f8bd4
--- /dev/null
+++ b/src/main/javassist/util/proxy/ProxyObjectOutputStream.java
@@ -0,0 +1,71 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2010 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.util.proxy;
+
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamClass;
+import java.io.OutputStream;
+
+/**
+ * An input stream class which knows how to serialize proxies created via {@link ProxyFactory}. It must
+ * be used when serialising proxies created from a proxy factory configured with
+ * {@link ProxyFactory#useWriteReplace} set to false. Subsequent deserialization of the serialized data
+ * must employ a {@link ProxyObjectInputStream}
+ *
+ * @author Andrew Dinn
+ */
+public class ProxyObjectOutputStream extends ObjectOutputStream
+{
+ /**
+ * create an output stream which can be used to serialize an object graph which includes proxies created
+ * using class ProxyFactory
+ * @param out
+ * @throws IOException whenever ObjectOutputStream would also do so
+ * @throws SecurityException whenever ObjectOutputStream would also do so
+ * @throws NullPointerException if out is null
+ */
+ public ProxyObjectOutputStream(OutputStream out) throws IOException
+ {
+ super(out);
+ }
+
+ protected void writeClassDescriptor(ObjectStreamClass desc) throws IOException {
+ Class cl = desc.forClass();
+ if (ProxyFactory.isProxyClass(cl)) {
+ writeBoolean(true);
+ Class superClass = cl.getSuperclass();
+ Class[] interfaces = cl.getInterfaces();
+ byte[] signature = ProxyFactory.getFilterSignature(cl);
+ String name = superClass.getName();
+ writeObject(name);
+ // we don't write the marker interface ProxyObject
+ writeInt(interfaces.length - 1);
+ for (int i = 0; i < interfaces.length; i++) {
+ Class interfaze = interfaces[i];
+ if (interfaze != ProxyObject.class) {
+ name = interfaces[i].getName();
+ writeObject(name);
+ }
+ }
+ writeInt(signature.length);
+ write(signature);
+ } else {
+ writeBoolean(false);
+ super.writeClassDescriptor(desc);
+ }
+ }
+}
diff --git a/src/main/javassist/util/proxy/RuntimeSupport.java b/src/main/javassist/util/proxy/RuntimeSupport.java
new file mode 100644
index 0000000..817ab4c
--- /dev/null
+++ b/src/main/javassist/util/proxy/RuntimeSupport.java
@@ -0,0 +1,211 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.util.proxy;
+
+import java.lang.reflect.Method;
+import java.io.Serializable;
+
+/**
+ * Runtime support routines that the classes generated by ProxyFactory use.
+ *
+ * @see ProxyFactory
+ */
+public class RuntimeSupport {
+ /**
+ * A method handler that only executes a method.
+ */
+ public static MethodHandler default_interceptor = new DefaultMethodHandler();
+
+ static class DefaultMethodHandler implements MethodHandler, Serializable {
+ public Object invoke(Object self, Method m,
+ Method proceed, Object[] args)
+ throws Exception
+ {
+ return proceed.invoke(self, args);
+ }
+ };
+
+ /**
+ * Finds two methods specified by the parameters and stores them
+ * into the given array.
+ *
+ * @throws RuntimeException if the methods are not found.
+ * @see javassist.util.proxy.ProxyFactory
+ */
+ public static void find2Methods(Object self, String superMethod,
+ String thisMethod, int index,
+ String desc, java.lang.reflect.Method[] methods)
+ {
+ synchronized (methods) {
+ if (methods[index] == null) {
+ methods[index + 1] = thisMethod == null ? null
+ : findMethod(self, thisMethod, desc);
+ methods[index] = findSuperMethod(self, superMethod, desc);
+ }
+ }
+ }
+
+ /**
+ * Finds a method with the given name and descriptor.
+ * It searches only the class of self.
+ *
+ * @throws RuntimeException if the method is not found.
+ */
+ public static Method findMethod(Object self, String name, String desc) {
+ Method m = findMethod2(self.getClass(), name, desc);
+ if (m == null)
+ error(self, name, desc);
+
+ return m;
+ }
+
+ /**
+ * Finds a method that has the given name and descriptor and is declared
+ * in the super class.
+ *
+ * @throws RuntimeException if the method is not found.
+ */
+ public static Method findSuperMethod(Object self, String name, String desc) {
+ Class clazz = self.getClass();
+ Method m = findSuperMethod2(clazz.getSuperclass(), name, desc);
+ if (m == null)
+ m = searchInterfaces(clazz, name, desc);
+
+ if (m == null)
+ error(self, name, desc);
+
+ return m;
+ }
+
+ private static void error(Object self, String name, String desc) {
+ throw new RuntimeException("not found " + name + ":" + desc
+ + " in " + self.getClass().getName());
+ }
+
+ private static Method findSuperMethod2(Class clazz, String name, String desc) {
+ Method m = findMethod2(clazz, name, desc);
+ if (m != null)
+ return m;
+
+ Class superClass = clazz.getSuperclass();
+ if (superClass != null) {
+ m = findSuperMethod2(superClass, name, desc);
+ if (m != null)
+ return m;
+ }
+
+ return searchInterfaces(clazz, name, desc);
+ }
+
+ private static Method searchInterfaces(Class clazz, String name, String desc) {
+ Method m = null;
+ Class[] interfaces = clazz.getInterfaces();
+ for (int i = 0; i < interfaces.length; i++) {
+ m = findSuperMethod2(interfaces[i], name, desc);
+ if (m != null)
+ return m;
+ }
+
+ return m;
+ }
+
+ private static Method findMethod2(Class clazz, String name, String desc) {
+ Method[] methods = SecurityActions.getDeclaredMethods(clazz);
+ int n = methods.length;
+ for (int i = 0; i < n; i++)
+ if (methods[i].getName().equals(name)
+ && makeDescriptor(methods[i]).equals(desc))
+ return methods[i];
+
+ return null;
+ }
+
+ /**
+ * Makes a descriptor for a given method.
+ */
+ public static String makeDescriptor(Method m) {
+ Class[] params = m.getParameterTypes();
+ return makeDescriptor(params, m.getReturnType());
+ }
+
+ /**
+ * Makes a descriptor for a given method.
+ *
+ * @param params parameter types.
+ * @param retType return type.
+ */
+ public static String makeDescriptor(Class[] params, Class retType) {
+ StringBuffer sbuf = new StringBuffer();
+ sbuf.append('(');
+ for (int i = 0; i < params.length; i++)
+ makeDesc(sbuf, params[i]);
+
+ sbuf.append(')');
+ makeDesc(sbuf, retType);
+ return sbuf.toString();
+ }
+
+ private static void makeDesc(StringBuffer sbuf, Class type) {
+ if (type.isArray()) {
+ sbuf.append('[');
+ makeDesc(sbuf, type.getComponentType());
+ }
+ else if (type.isPrimitive()) {
+ if (type == Void.TYPE)
+ sbuf.append('V');
+ else if (type == Integer.TYPE)
+ sbuf.append('I');
+ else if (type == Byte.TYPE)
+ sbuf.append('B');
+ else if (type == Long.TYPE)
+ sbuf.append('J');
+ else if (type == Double.TYPE)
+ sbuf.append('D');
+ else if (type == Float.TYPE)
+ sbuf.append('F');
+ else if (type == Character.TYPE)
+ sbuf.append('C');
+ else if (type == Short.TYPE)
+ sbuf.append('S');
+ else if (type == Boolean.TYPE)
+ sbuf.append('Z');
+ else
+ throw new RuntimeException("bad type: " + type.getName());
+ }
+ else
+ sbuf.append('L').append(type.getName().replace('.', '/'))
+ .append(';');
+ }
+
+ /**
+ * Converts a proxy object to an object that is writable to an
+ * object stream. This method is called by <code>writeReplace()</code>
+ * in a proxy class.
+ *
+ * @since 3.4
+ */
+ public static SerializedProxy makeSerializedProxy(Object proxy)
+ throws java.io.InvalidClassException
+ {
+ Class clazz = proxy.getClass();
+
+ MethodHandler methodHandler = null;
+ if (proxy instanceof ProxyObject)
+ methodHandler = ((ProxyObject)proxy).getHandler();
+
+ return new SerializedProxy(clazz, ProxyFactory.getFilterSignature(clazz), methodHandler);
+ }
+}
diff --git a/src/main/javassist/util/proxy/SecurityActions.java b/src/main/javassist/util/proxy/SecurityActions.java
new file mode 100644
index 0000000..40741e4
--- /dev/null
+++ b/src/main/javassist/util/proxy/SecurityActions.java
@@ -0,0 +1,135 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+package javassist.util.proxy;
+
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+
+class SecurityActions {
+ static Method[] getDeclaredMethods(final Class clazz) {
+ if (System.getSecurityManager() == null)
+ return clazz.getDeclaredMethods();
+ else {
+ return (Method[]) AccessController
+ .doPrivileged(new PrivilegedAction() {
+ public Object run() {
+ return clazz.getDeclaredMethods();
+ }
+ });
+ }
+ }
+
+ static Constructor[] getDeclaredConstructors(final Class clazz) {
+ if (System.getSecurityManager() == null)
+ return clazz.getDeclaredConstructors();
+ else {
+ return (Constructor[]) AccessController
+ .doPrivileged(new PrivilegedAction() {
+ public Object run() {
+ return clazz.getDeclaredConstructors();
+ }
+ });
+ }
+ }
+
+ static Method getDeclaredMethod(final Class clazz, final String name,
+ final Class[] types) throws NoSuchMethodException {
+ if (System.getSecurityManager() == null)
+ return clazz.getDeclaredMethod(name, types);
+ else {
+ try {
+ return (Method) AccessController
+ .doPrivileged(new PrivilegedExceptionAction() {
+ public Object run() throws Exception {
+ return clazz.getDeclaredMethod(name, types);
+ }
+ });
+ }
+ catch (PrivilegedActionException e) {
+ if (e.getCause() instanceof NoSuchMethodException)
+ throw (NoSuchMethodException) e.getCause();
+
+ throw new RuntimeException(e.getCause());
+ }
+ }
+ }
+
+ static Constructor getDeclaredConstructor(final Class clazz,
+ final Class[] types)
+ throws NoSuchMethodException
+ {
+ if (System.getSecurityManager() == null)
+ return clazz.getDeclaredConstructor(types);
+ else {
+ try {
+ return (Constructor) AccessController
+ .doPrivileged(new PrivilegedExceptionAction() {
+ public Object run() throws Exception {
+ return clazz.getDeclaredConstructor(types);
+ }
+ });
+ }
+ catch (PrivilegedActionException e) {
+ if (e.getCause() instanceof NoSuchMethodException)
+ throw (NoSuchMethodException) e.getCause();
+
+ throw new RuntimeException(e.getCause());
+ }
+ }
+ }
+
+ static void setAccessible(final AccessibleObject ao,
+ final boolean accessible) {
+ if (System.getSecurityManager() == null)
+ ao.setAccessible(accessible);
+ else {
+ AccessController.doPrivileged(new PrivilegedAction() {
+ public Object run() {
+ ao.setAccessible(accessible);
+ return null;
+ }
+ });
+ }
+ }
+
+ static void set(final Field fld, final Object target, final Object value)
+ throws IllegalAccessException
+ {
+ if (System.getSecurityManager() == null)
+ fld.set(target, value);
+ else {
+ try {
+ AccessController.doPrivileged(new PrivilegedExceptionAction() {
+ public Object run() throws Exception {
+ fld.set(target, value);
+ return null;
+ }
+ });
+ }
+ catch (PrivilegedActionException e) {
+ if (e.getCause() instanceof NoSuchMethodException)
+ throw (IllegalAccessException) e.getCause();
+
+ throw new RuntimeException(e.getCause());
+ }
+ }
+ }
+}
diff --git a/src/main/javassist/util/proxy/SerializedProxy.java b/src/main/javassist/util/proxy/SerializedProxy.java
new file mode 100644
index 0000000..cddfab4
--- /dev/null
+++ b/src/main/javassist/util/proxy/SerializedProxy.java
@@ -0,0 +1,97 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+package javassist.util.proxy;
+
+import java.io.Serializable;
+import java.io.ObjectStreamException;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.security.ProtectionDomain;
+
+/**
+ * A proxy object is converted into an instance of this class
+ * when it is written to an output stream.
+ *
+ * @see RuntimeSupport#makeSerializedProxy(Object)
+ */
+class SerializedProxy implements Serializable {
+ private String superClass;
+ private String[] interfaces;
+ private byte[] filterSignature;
+ private MethodHandler handler;
+
+ SerializedProxy(Class proxy, byte[] sig, MethodHandler h) {
+ filterSignature = sig;
+ handler = h;
+ superClass = proxy.getSuperclass().getName();
+ Class[] infs = proxy.getInterfaces();
+ int n = infs.length;
+ interfaces = new String[n - 1];
+ String setterInf = ProxyObject.class.getName();
+ for (int i = 0; i < n; i++) {
+ String name = infs[i].getName();
+ if (!name.equals(setterInf))
+ interfaces[i] = name;
+ }
+ }
+
+ /**
+ * Load class.
+ *
+ * @param className the class name
+ * @return loaded class
+ * @throws ClassNotFoundException for any error
+ */
+ protected Class loadClass(final String className) throws ClassNotFoundException {
+ try {
+ return (Class)AccessController.doPrivileged(new PrivilegedExceptionAction(){
+ public Object run() throws Exception{
+ ClassLoader cl = Thread.currentThread().getContextClassLoader();
+ return Class.forName(className, true, cl);
+ }
+ });
+ }
+ catch (PrivilegedActionException pae) {
+ throw new RuntimeException("cannot load the class: " + className, pae.getException());
+ }
+ }
+
+ Object readResolve() throws ObjectStreamException {
+ try {
+ int n = interfaces.length;
+ Class[] infs = new Class[n];
+ for (int i = 0; i < n; i++)
+ infs[i] = loadClass(interfaces[i]);
+
+ ProxyFactory f = new ProxyFactory();
+ f.setSuperclass(loadClass(superClass));
+ f.setInterfaces(infs);
+ ProxyObject proxy = (ProxyObject)f.createClass(filterSignature).newInstance();
+ proxy.setHandler(handler);
+ return proxy;
+ }
+ catch (ClassNotFoundException e) {
+ throw new java.io.InvalidClassException(e.getMessage());
+ }
+ catch (InstantiationException e2) {
+ throw new java.io.InvalidObjectException(e2.getMessage());
+ }
+ catch (IllegalAccessException e3) {
+ throw new java.io.InvalidClassException(e3.getMessage());
+ }
+ }
+}
diff --git a/src/main/javassist/util/proxy/package.html b/src/main/javassist/util/proxy/package.html
new file mode 100644
index 0000000..6c77804
--- /dev/null
+++ b/src/main/javassist/util/proxy/package.html
@@ -0,0 +1,6 @@
+<html>
+<body>
+Dynamic proxy (similar to <code>Enhancer</code> of <a href="http://cglib.sourceforge.net/">cglib</a>).
+See <code>ProxyFactory</code> for more details.
+</body>
+</html>
diff --git a/src/test/test/javassist/bytecode/analysis/AnalyzerTest.java b/src/test/test/javassist/bytecode/analysis/AnalyzerTest.java
new file mode 100644
index 0000000..0c5a77e
--- /dev/null
+++ b/src/test/test/javassist/bytecode/analysis/AnalyzerTest.java
@@ -0,0 +1,411 @@
+package test.javassist.bytecode.analysis;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import javassist.ClassPool;
+import javassist.CtClass;
+import javassist.CtMethod;
+import javassist.bytecode.AccessFlag;
+import javassist.bytecode.BadBytecode;
+import javassist.bytecode.Bytecode;
+import javassist.bytecode.CodeIterator;
+import javassist.bytecode.MethodInfo;
+import javassist.bytecode.Opcode;
+import javassist.bytecode.analysis.Analyzer;
+import javassist.bytecode.analysis.Frame;
+import javassist.bytecode.analysis.Type;
+import junit.framework.TestCase;
+
+/**
+ * Tests Analyzer
+ *
+ * @author Jason T. Greene
+ */
+public class AnalyzerTest extends TestCase {
+
+ public void testCommonSupperArray() throws Exception {
+ ClassPool pool = ClassPool.getDefault();
+ CtClass clazz = pool.get(getClass().getName() + "$Dummy");
+ CtMethod method = clazz.getDeclaredMethod("commonSuperArray");
+ verifyArrayLoad(clazz, method, "java.lang.Number");
+ }
+
+ public void testCommonInterfaceArray() throws Exception {
+ ClassPool pool = ClassPool.getDefault();
+ CtClass clazz = pool.get(getClass().getName() + "$Dummy");
+ CtMethod method = clazz.getDeclaredMethod("commonInterfaceArray");
+ verifyArrayLoad(clazz, method, "java.io.Serializable");
+ }
+
+ public void testSharedInterfaceAndSuperClass() throws Exception {
+ CtMethod method = ClassPool.getDefault().getMethod(
+ getClass().getName() + "$Dummy", "sharedInterfaceAndSuperClass");
+ verifyReturn(method, "java.io.Serializable");
+
+ method = ClassPool.getDefault().getMethod(
+ getClass().getName() + "$Dummy", "sharedOffsetInterfaceAndSuperClass");
+ verifyReturn(method, "java.io.Serializable");
+
+ method = ClassPool.getDefault().getMethod(
+ getClass().getName() + "$Dummy", "sharedSuperWithSharedInterface");
+ verifyReturn(method, getClass().getName() + "$Dummy$A");
+ }
+
+ public void testArrayDifferentDims() throws Exception {
+ CtMethod method = ClassPool.getDefault().getMethod(
+ getClass().getName() + "$Dummy", "arrayDifferentDimensions1");
+ verifyReturn(method, "java.lang.Cloneable[]");
+
+ method = ClassPool.getDefault().getMethod(
+ getClass().getName() + "$Dummy", "arrayDifferentDimensions2");
+ verifyReturn(method, "java.lang.Object[][]");
+ }
+
+ public void testReusedLocalMerge() throws Exception {
+ CtMethod method = ClassPool.getDefault().getMethod(
+ getClass().getName() + "$Dummy", "reusedLocalMerge");
+
+ MethodInfo info = method.getMethodInfo2();
+ Analyzer analyzer = new Analyzer();
+ Frame[] frames = analyzer.analyze(method.getDeclaringClass(), info);
+ assertNotNull(frames);
+ int pos = findOpcode(info, Opcode.RETURN);
+ Frame frame = frames[pos];
+ assertEquals("java.lang.Object", frame.getLocal(2).getCtClass().getName());
+ }
+
+ private static int findOpcode(MethodInfo info, int opcode) throws BadBytecode {
+ CodeIterator iter = info.getCodeAttribute().iterator();
+
+ // find return
+ int pos = 0;
+ while (iter.hasNext()) {
+ pos = iter.next();
+ if (iter.byteAt(pos) == opcode)
+ break;
+ }
+ return pos;
+ }
+
+
+ private static void verifyReturn(CtMethod method, String expected) throws BadBytecode {
+ MethodInfo info = method.getMethodInfo2();
+ CodeIterator iter = info.getCodeAttribute().iterator();
+
+ // find areturn
+ int pos = 0;
+ while (iter.hasNext()) {
+ pos = iter.next();
+ if (iter.byteAt(pos) == Opcode.ARETURN)
+ break;
+ }
+
+ Analyzer analyzer = new Analyzer();
+ Frame[] frames = analyzer.analyze(method.getDeclaringClass(), info);
+ assertNotNull(frames);
+ Frame frame = frames[pos];
+ assertEquals(expected, frame.peek().getCtClass().getName());
+ }
+
+ private static void verifyArrayLoad(CtClass clazz, CtMethod method, String component)
+ throws BadBytecode {
+ MethodInfo info = method.getMethodInfo2();
+ CodeIterator iter = info.getCodeAttribute().iterator();
+
+ // find aaload
+ int pos = 0;
+ while (iter.hasNext()) {
+ pos = iter.next();
+ if (iter.byteAt(pos) == Opcode.AALOAD)
+ break;
+ }
+
+ Analyzer analyzer = new Analyzer();
+ Frame[] frames = analyzer.analyze(clazz, info);
+ assertNotNull(frames);
+ Frame frame = frames[pos];
+ assertNotNull(frame);
+
+ Type type = frame.getStack(frame.getTopIndex() - 1);
+ assertEquals(component + "[]", type.getCtClass().getName());
+
+ pos = iter.next();
+ frame = frames[pos];
+ assertNotNull(frame);
+
+ type = frame.getStack(frame.getTopIndex());
+ assertEquals(component, type.getCtClass().getName());
+ }
+
+ private static void addJump(Bytecode code, int opcode, int pos) {
+ int current = code.currentPc();
+ code.addOpcode(opcode);
+ code.addIndex(pos - current);
+ }
+
+ public void testDeadCode() throws Exception {
+ CtMethod method = generateDeadCode(ClassPool.getDefault());
+ Analyzer analyzer = new Analyzer();
+ Frame[] frames = analyzer.analyze(method.getDeclaringClass(), method.getMethodInfo2());
+ assertNotNull(frames);
+ assertNull(frames[4]);
+ assertNotNull(frames[5]);
+ verifyReturn(method, "java.lang.String");
+ }
+
+ public void testInvalidCode() throws Exception {
+ CtMethod method = generateInvalidCode(ClassPool.getDefault());
+ Analyzer analyzer = new Analyzer();
+ try {
+ analyzer.analyze(method.getDeclaringClass(), method.getMethodInfo2());
+ } catch (BadBytecode e) {
+ return;
+ }
+
+ fail("Invalid code should have triggered a BadBytecode exception");
+ }
+
+ public void testCodeFalloff() throws Exception {
+ CtMethod method = generateCodeFalloff(ClassPool.getDefault());
+ Analyzer analyzer = new Analyzer();
+ try {
+ analyzer.analyze(method.getDeclaringClass(), method.getMethodInfo2());
+ } catch (BadBytecode e) {
+ return;
+ }
+
+ fail("Code falloff should have triggered a BadBytecode exception");
+ }
+
+ public void testJsrMerge() throws Exception {
+ CtMethod method = generateJsrMerge(ClassPool.getDefault());
+ Analyzer analyzer = new Analyzer();
+ analyzer.analyze(method.getDeclaringClass(), method.getMethodInfo2());
+ verifyReturn(method, "java.lang.String");
+ }
+
+ public void testJsrMerge2() throws Exception {
+ CtMethod method = generateJsrMerge2(ClassPool.getDefault());
+ Analyzer analyzer = new Analyzer();
+ analyzer.analyze(method.getDeclaringClass(), method.getMethodInfo2());
+ verifyReturn(method, "java.lang.String");
+ }
+
+ private CtMethod generateDeadCode(ClassPool pool) throws Exception {
+ CtClass clazz = pool.makeClass(getClass().getName() + "$Generated0");
+ CtClass stringClass = pool.get("java.lang.String");
+ CtMethod method = new CtMethod(stringClass, "foo", new CtClass[0], clazz);
+ MethodInfo info = method.getMethodInfo2();
+ info.setAccessFlags(AccessFlag.PUBLIC | AccessFlag.STATIC);
+ Bytecode code = new Bytecode(info.getConstPool(), 1, 2);
+ /* 0 */ code.addIconst(1);
+ /* 1 */ addJump(code, Opcode.GOTO, 5);
+ /* 4 */ code.addIconst(0); // DEAD
+ /* 5 */ code.addIconst(1);
+ /* 6 */ code.addInvokestatic(stringClass, "valueOf", stringClass, new CtClass[]{CtClass.intType});
+ /* 9 */ code.addOpcode(Opcode.ARETURN);
+ info.setCodeAttribute(code.toCodeAttribute());
+ clazz.addMethod(method);
+
+ return method;
+ }
+
+ private CtMethod generateInvalidCode(ClassPool pool) throws Exception {
+ CtClass clazz = pool.makeClass(getClass().getName() + "$Generated4");
+ CtClass intClass = pool.get("java.lang.Integer");
+ CtClass stringClass = pool.get("java.lang.String");
+ CtMethod method = new CtMethod(stringClass, "foo", new CtClass[0], clazz);
+ MethodInfo info = method.getMethodInfo2();
+ info.setAccessFlags(AccessFlag.PUBLIC | AccessFlag.STATIC);
+ Bytecode code = new Bytecode(info.getConstPool(), 1, 2);
+ /* 0 */ code.addIconst(1);
+ /* 1 */ code.addInvokestatic(intClass, "valueOf", intClass, new CtClass[]{CtClass.intType});
+ /* 4 */ code.addOpcode(Opcode.ARETURN);
+ info.setCodeAttribute(code.toCodeAttribute());
+ clazz.addMethod(method);
+
+ return method;
+ }
+
+
+ private CtMethod generateCodeFalloff(ClassPool pool) throws Exception {
+ CtClass clazz = pool.makeClass(getClass().getName() + "$Generated3");
+ CtClass stringClass = pool.get("java.lang.String");
+ CtMethod method = new CtMethod(stringClass, "foo", new CtClass[0], clazz);
+ MethodInfo info = method.getMethodInfo2();
+ info.setAccessFlags(AccessFlag.PUBLIC | AccessFlag.STATIC);
+ Bytecode code = new Bytecode(info.getConstPool(), 1, 2);
+ /* 0 */ code.addIconst(1);
+ /* 1 */ code.addInvokestatic(stringClass, "valueOf", stringClass, new CtClass[]{CtClass.intType});
+ info.setCodeAttribute(code.toCodeAttribute());
+ clazz.addMethod(method);
+
+ return method;
+ }
+
+ private CtMethod generateJsrMerge(ClassPool pool) throws Exception {
+ CtClass clazz = pool.makeClass(getClass().getName() + "$Generated1");
+ CtClass stringClass = pool.get("java.lang.String");
+ CtMethod method = new CtMethod(stringClass, "foo", new CtClass[0], clazz);
+ MethodInfo info = method.getMethodInfo2();
+ info.setAccessFlags(AccessFlag.PUBLIC | AccessFlag.STATIC);
+ Bytecode code = new Bytecode(info.getConstPool(), 1, 2);
+ /* 0 */ code.addIconst(5);
+ /* 1 */ code.addIstore(0);
+ /* 2 */ addJump(code, Opcode.JSR, 7);
+ /* 5 */ code.addAload(0);
+ /* 6 */ code.addOpcode(Opcode.ARETURN);
+ /* 7 */ code.addAstore(1);
+ /* 8 */ code.addIconst(3);
+ /* 9 */ code.addInvokestatic(stringClass, "valueOf", stringClass, new CtClass[]{CtClass.intType});
+ /* 12 */ code.addAstore(0);
+ /* 12 */ code.addRet(1);
+ info.setCodeAttribute(code.toCodeAttribute());
+ clazz.addMethod(method);
+ //System.out.println(clazz.toClass().getMethod("foo", new Class[0]).invoke(null, new Object[0]));
+
+ return method;
+ }
+
+ private CtMethod generateJsrMerge2(ClassPool pool) throws Exception {
+ CtClass clazz = pool.makeClass(getClass().getName() + "$Generated2");
+ CtClass stringClass = pool.get("java.lang.String");
+ CtMethod method = new CtMethod(stringClass, "foo", new CtClass[0], clazz);
+ MethodInfo info = method.getMethodInfo2();
+ info.setAccessFlags(AccessFlag.PUBLIC | AccessFlag.STATIC);
+ Bytecode code = new Bytecode(info.getConstPool(), 1, 2);
+ /* 0 */ addJump(code, Opcode.JSR, 5);
+ /* 3 */ code.addAload(0);
+ /* 4 */ code.addOpcode(Opcode.ARETURN);
+ /* 5 */ code.addAstore(1);
+ /* 6 */ code.addIconst(4);
+ /* 7 */ code.addInvokestatic(stringClass, "valueOf", stringClass, new CtClass[]{CtClass.intType});
+ /* 10 */ code.addAstore(0);
+ /* 11 */ code.addRet(1);
+ info.setCodeAttribute(code.toCodeAttribute());
+ clazz.addMethod(method);
+
+ return method;
+ }
+
+ public static class Dummy {
+ public Serializable commonSuperArray(int x) {
+ Number[] n;
+
+ if (x > 5) {
+ n = new Long[10];
+ } else {
+ n = new Double[5];
+ }
+
+ return n[x];
+ }
+
+ public Serializable commonInterfaceArray(int x) {
+ Serializable[] n;
+
+ if (x > 5) {
+ n = new Long[10];
+ } else if (x > 3) {
+ n = new Double[5];
+ } else {
+ n = new String[3];
+ }
+
+ return n[x];
+ }
+
+
+ public static class A {};
+ public static class B1 extends A implements Serializable {};
+ public static class B2 extends A implements Serializable {};
+ public static class A2 implements Serializable, Cloneable {};
+ public static class A3 implements Serializable, Cloneable {};
+
+ public static class B3 extends A {};
+ public static class C31 extends B3 implements Serializable {};
+
+
+ public void dummy(Serializable s) {}
+
+ public Object sharedInterfaceAndSuperClass(int x) {
+ Serializable s;
+
+ if (x > 5) {
+ s = new B1();
+ } else {
+ s = new B2();
+ }
+
+ dummy(s);
+
+ return s;
+ }
+
+ public A sharedSuperWithSharedInterface(int x) {
+ A a;
+
+ if (x > 5) {
+ a = new B1();
+ } else if (x > 3) {
+ a = new B2();
+ } else {
+ a = new C31();
+ }
+
+ return a;
+ }
+
+
+ public void reusedLocalMerge() {
+ ArrayList list = new ArrayList();
+ try {
+ Iterator i = list.iterator();
+ i.hasNext();
+ } catch (Exception e) {
+ }
+ }
+
+ public Object sharedOffsetInterfaceAndSuperClass(int x) {
+ Serializable s;
+
+ if (x > 5) {
+ s = new B1();
+ } else {
+ s = new C31();
+ }
+
+ dummy(s);
+
+ return s;
+ }
+
+
+ public Object arrayDifferentDimensions1(int x) {
+ Object[] n;
+
+ if ( x > 5) {
+ n = new Number[1][1];
+ } else {
+ n = new Cloneable[1];
+ }
+
+
+ return n;
+ }
+
+ public Object arrayDifferentDimensions2(int x) {
+ Object[] n;
+
+ if ( x> 5) {
+ n = new String[1][1];
+ } else {
+ n = new Number[1][1][1][1];
+ }
+
+ return n;
+ }
+ }
+}
diff --git a/src/test/test/javassist/bytecode/analysis/ErrorFinder.java b/src/test/test/javassist/bytecode/analysis/ErrorFinder.java
new file mode 100644
index 0000000..e131ffb
--- /dev/null
+++ b/src/test/test/javassist/bytecode/analysis/ErrorFinder.java
@@ -0,0 +1,63 @@
+package test.javassist.bytecode.analysis;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+
+import javassist.ClassPool;
+import javassist.CtClass;
+import javassist.CtMethod;
+import javassist.bytecode.analysis.Analyzer;
+
+/**
+ * Simple testing tool that verifies class files can be analyzed.
+ *
+ * @author Jason T. Greene
+ */
+public class ErrorFinder {
+
+ public static void main(String[] args) throws Exception {
+ ClassPool pool = ClassPool.getDefault();
+
+ String className = args[0];
+ if (!className.equals("-file")) {
+ analyzeClass(pool, className);
+ return;
+ }
+
+ FileReader reader = new FileReader(args[1]);
+ BufferedReader lineReader = new BufferedReader(reader);
+
+
+ String line = lineReader.readLine();
+ while (line != null) {
+ analyzeClass(pool, line);
+ line = lineReader.readLine();
+ }
+ }
+
+ private static void analyzeClass(ClassPool pool, String className) {
+ try {
+
+ CtClass clazz = pool.get(className);
+ CtMethod[] methods = clazz.getDeclaredMethods();
+ for (int i = 0; i < methods.length; i++)
+ analyzeMethod(clazz, methods[i]);
+ } catch (Throwable e) {
+ System.out.println("FAIL: CLASS: " + className + " " + e.getClass() + ":" + e.getMessage());
+ }
+ }
+
+ private static void analyzeMethod(CtClass clazz, CtMethod method) {
+ String methodName = clazz.getName() + "." + method.getName() + method.getSignature();
+ System.out.println("START: " + methodName);
+ Analyzer analyzer = new Analyzer();
+
+ long time = System.currentTimeMillis();
+ try {
+ analyzer.analyze(clazz, method.getMethodInfo2());
+ System.out.println("SUCCESS: " + methodName + " - " + (System.currentTimeMillis() - time));
+ } catch (Exception e) {
+ System.out.println("FAIL: " + methodName + " - " + (e.getMessage() == null ? e.getClass().getName() : e.getMessage()));
+ }
+ }
+}
diff --git a/src/test/test/javassist/bytecode/analysis/ScannerTest.java b/src/test/test/javassist/bytecode/analysis/ScannerTest.java
new file mode 100644
index 0000000..10f2936
--- /dev/null
+++ b/src/test/test/javassist/bytecode/analysis/ScannerTest.java
@@ -0,0 +1,185 @@
+package test.javassist.bytecode.analysis;
+
+import java.io.IOException;
+
+import javassist.CannotCompileException;
+import javassist.ClassPool;
+import javassist.CtClass;
+import javassist.CtMethod;
+import javassist.NotFoundException;
+import javassist.bytecode.AccessFlag;
+import javassist.bytecode.Bytecode;
+import javassist.bytecode.MethodInfo;
+import javassist.bytecode.Opcode;
+import javassist.bytecode.analysis.Subroutine;
+import javassist.bytecode.analysis.SubroutineScanner;
+import junit.framework.TestCase;
+
+/**
+ * Tests Subroutine Scanner
+ *
+ * @author Jason T. Greene
+ */
+public class ScannerTest extends TestCase {
+
+ public void testNestedFinally() throws Exception {
+ ClassPool pool = ClassPool.getDefault();
+ generate(pool);
+ CtClass clazz = pool.get("test.ScannerTest$GeneratedTest");
+ CtMethod method = clazz.getDeclaredMethod("doit");
+
+ SubroutineScanner scanner = new SubroutineScanner();
+ Subroutine[] subs = scanner.scan(method.getMethodInfo2());
+
+ verifySubroutine(subs, 31, 31, new int[]{125, 25});
+ verifySubroutine(subs, 32, 31, new int[]{125, 25});
+ verifySubroutine(subs, 33, 31, new int[]{125, 25});
+ verifySubroutine(subs, 60, 31, new int[]{125, 25});
+ verifySubroutine(subs, 61, 31, new int[]{125, 25});
+ verifySubroutine(subs, 63, 31, new int[]{125, 25});
+ verifySubroutine(subs, 66, 31, new int[]{125, 25});
+ verifySubroutine(subs, 69, 31, new int[]{125, 25});
+ verifySubroutine(subs, 71, 31, new int[]{125, 25});
+ verifySubroutine(subs, 74, 31, new int[]{125, 25});
+ verifySubroutine(subs, 76, 31, new int[]{125, 25});
+ verifySubroutine(subs, 77, 77, new int[]{111, 71});
+ verifySubroutine(subs, 79, 77, new int[]{111, 71});
+ verifySubroutine(subs, 80, 77, new int[]{111, 71});
+ verifySubroutine(subs, 82, 77, new int[]{111, 71});
+ verifySubroutine(subs, 85, 77, new int[]{111, 71});
+ verifySubroutine(subs, 88, 77, new int[]{111, 71});
+ verifySubroutine(subs, 90, 77, new int[]{111, 71});
+ verifySubroutine(subs, 93, 77, new int[]{111, 71});
+ verifySubroutine(subs, 95, 77, new int[]{111, 71});
+ verifySubroutine(subs, 96, 96, new int[]{106, 90});
+ verifySubroutine(subs, 98, 96, new int[]{106, 90});
+ verifySubroutine(subs, 99, 96, new int[]{106, 90});
+ verifySubroutine(subs, 101, 96, new int[]{106, 90});
+ verifySubroutine(subs, 104, 96, new int[]{106, 90});
+ verifySubroutine(subs, 106, 77, new int[]{111, 71});
+ verifySubroutine(subs, 109, 77, new int[]{111, 71});
+ verifySubroutine(subs, 111, 31, new int[]{125, 25});
+ verifySubroutine(subs, 114, 31, new int[]{125, 25});
+ verifySubroutine(subs, 117, 31, new int[]{125, 25});
+ verifySubroutine(subs, 118, 31, new int[]{125, 25});
+ verifySubroutine(subs, 120, 31, new int[]{125, 25});
+ verifySubroutine(subs, 123, 31, new int[]{125, 25});
+ }
+
+ private static void verifySubroutine(Subroutine[] subs, int pos, int start,
+ int[] callers) {
+ Subroutine sub = subs[pos];
+ assertNotNull(sub);
+ assertEquals(sub.start(), start);
+ for (int i = 0; i < callers.length; i++)
+ assertTrue(sub.callers().contains(new Integer(callers[i])));
+ }
+
+ private static void generate(ClassPool pool) throws CannotCompileException, IOException, NotFoundException {
+ // Generated from eclipse JDK4 compiler:
+ // public void doit(int x) {
+ // println("null");
+ // try {
+ // println("try");
+ // } catch (RuntimeException e) {
+ // e.printStackTrace();
+ // } finally {
+ // switch (x) {
+ // default:
+ // case 15:
+ // try {
+ // println("inner-try");
+ // } finally {
+ // try {
+ // println("inner-inner-try");
+ // } finally {
+ // println("inner-finally");
+ // }
+ // }
+ // break;
+ // case 1789:
+ // println("switch -17");
+ // }
+ // }
+ //}
+
+ CtClass clazz = pool.makeClass("test.ScannerTest$GeneratedTest");
+ CtMethod method = new CtMethod(CtClass.voidType, "doit", new CtClass[] {CtClass.intType}, clazz);
+ MethodInfo info = method.getMethodInfo2();
+ info.setAccessFlags(AccessFlag.PUBLIC);
+ CtClass stringClass = pool.get("java.lang.String");
+ Bytecode code = new Bytecode(info.getConstPool(), 2, 9);
+ /* 0 */ code.addAload(0);
+ /* 1 */ code.addLdc("start");
+ /* 3 */ code.addInvokevirtual(clazz, "println", CtClass.voidType, new CtClass[] {stringClass});
+ /* 6 */ code.addAload(0);
+ /* 7 */ code.addLdc("try");
+ /* 9 */ code.addInvokevirtual(clazz, "println", CtClass.voidType, new CtClass[] {stringClass});
+ /* 12 */ addJump(code, Opcode.GOTO, 125);
+ /* 14 */ code.addAstore(2);
+ /* 16 */ code.addAload(2);
+ /* 17 */ code.addInvokevirtual("java.lang.Exception", "printStackTrace", "()V");
+ /* 20 */ addJump(code, Opcode.GOTO, 125);
+ /* 23 */ code.addAstore(4);
+ /* 25 */ addJump(code, Opcode.JSR, 31);
+ /* 28 */ code.addAload(4);
+ /* 30 */ code.addOpcode(Opcode.ATHROW);
+ /* 31 */ code.addAstore(3);
+ /* 32 */ code.addIload(1);
+ int spos = code.currentPc();
+ /* 33 */ code.addOpcode(Opcode.LOOKUPSWITCH);
+ code.addIndex(0); // 2 bytes pad - gets us to 36
+ code.add32bit(60 - spos); // default
+ code.add32bit(2); // 2 pairs
+ code.add32bit(15); code.add32bit(60 - spos);
+ code.add32bit(1789); code.add32bit(117 - spos);
+ /* 60 */ code.addAload(0);
+ /* 61 */ code.addLdc("inner-try");
+ /* 63 */ code.addInvokevirtual(clazz, "println", CtClass.voidType, new CtClass[] {stringClass});
+ /* 66 */ addJump(code, Opcode.GOTO, 111);
+ /* 69 */ code.addAstore(6);
+ /* 71 */ addJump(code, Opcode.JSR, 77);
+ /* 74 */ code.addAload(6);
+ /* 76 */ code.add(Opcode.ATHROW);
+ /* 77 */ code.addAstore(5);
+ /* 79 */ code.addAload(0);
+ /* 80 */ code.addLdc("inner-inner-try");
+ /* 82 */ code.addInvokevirtual(clazz, "println", CtClass.voidType, new CtClass[] {stringClass});
+ /* 85 */ addJump(code, Opcode.GOTO, 106);
+ /* 88 */ code.addAstore(8);
+ /* 90 */ addJump(code, Opcode.JSR, 96);
+ /* 93 */ code.addAload(8);
+ /* 95 */ code.add(Opcode.ATHROW);
+ /* 96 */ code.addAstore(7);
+ /* 98 */ code.addAload(0);
+ /* 99 */ code.addLdc("inner-finally");
+ /* 101 */ code.addInvokevirtual(clazz, "println", CtClass.voidType, new CtClass[] {stringClass});
+ /* 104 */ code.addRet(7);
+ /* 106 */ addJump(code, Opcode.JSR, 96);
+ /* 109 */ code.addRet(5);
+ /* 111 */ addJump(code, Opcode.JSR, 77);
+ /* 114 */ addJump(code, Opcode.GOTO, 123);
+ /* 117 */ code.addAload(0);
+ /* 118 */ code.addLdc("switch - 1789");
+ /* 120 */ code.addInvokevirtual(clazz, "println", CtClass.voidType, new CtClass[] {stringClass});
+ /* 123 */ code.addRet(3);
+ /* 125 */ addJump(code, Opcode.JSR, 31);
+ /* 128 */ code.addOpcode(Opcode.RETURN);
+ code.addExceptionHandler(6, 12, 15, "java.lang.RuntimeException");
+ code.addExceptionHandler(6, 20, 23, 0);
+ code.addExceptionHandler(125, 128, 23, 0);
+ code.addExceptionHandler(60, 69, 69, 0);
+ code.addExceptionHandler(111, 114, 69, 0);
+ code.addExceptionHandler(79, 88, 88, 0);
+ code.addExceptionHandler(106, 109, 88, 0);
+ info.setCodeAttribute(code.toCodeAttribute());
+ clazz.addMethod(method);
+ clazz.writeFile("/tmp");
+ }
+
+ private static void addJump(Bytecode code, int opcode, int pos) {
+ int current = code.currentPc();
+ code.addOpcode(opcode);
+ code.addIndex(pos - current);
+ }
+}
diff --git a/src/test/test/javassist/convert/ArrayAccessReplaceTest.java b/src/test/test/javassist/convert/ArrayAccessReplaceTest.java
new file mode 100644
index 0000000..09387ce
--- /dev/null
+++ b/src/test/test/javassist/convert/ArrayAccessReplaceTest.java
@@ -0,0 +1,433 @@
+package test.javassist.convert;
+
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.HashMap;
+import java.util.Map;
+
+import javassist.ClassPool;
+import javassist.CodeConverter;
+import javassist.CtClass;
+import junit.framework.TestCase;
+
+public class ArrayAccessReplaceTest extends TestCase {
+ private static SimpleInterface simple;
+
+ public void setUp() throws Exception {
+ ClassPool pool = new ClassPool(true);
+ CtClass echoClass = pool.get(ArrayAccessReplaceTest.class.getName() + "$Echo");
+ CtClass simpleClass = pool.get(ArrayAccessReplaceTest.class.getName() + "$Simple");
+ CodeConverter converter = new CodeConverter();
+ converter.replaceArrayAccess(echoClass, new CodeConverter.DefaultArrayAccessReplacementMethodNames());
+ simpleClass.instrument(converter);
+ //simpleClass.writeFile("/tmp");
+ simple = (SimpleInterface) simpleClass.toClass(new URLClassLoader(new URL[0], getClass().getClassLoader()), Class.class.getProtectionDomain()).newInstance();
+ }
+
+ public void testComplex() throws Exception {
+ ClassPool pool = new ClassPool(true);
+ CtClass clazz = pool.get(ArrayAccessReplaceTest.class.getName() + "$Complex");
+
+ CodeConverter converter = new CodeConverter();
+ converter.replaceArrayAccess(clazz, new CodeConverter.DefaultArrayAccessReplacementMethodNames());
+ clazz.instrument(converter);
+ ComplexInterface instance = (ComplexInterface) clazz.toClass(new URLClassLoader(new URL[0], getClass().getClassLoader()), Class.class.getProtectionDomain()).newInstance();
+ assertEquals(new Integer(5), instance.complexRead(4));
+ }
+
+ public void testBoolean() throws Exception {
+ for (int i = 0; i < 100; i++) {
+ boolean value = i % 5 == 0;
+ simple.setBoolean(i, value);
+ }
+
+ for (int i = 0; i < 100; i++) {
+ boolean value = i % 5 == 0;
+ assertEquals(value, simple.getBoolean(i));
+ }
+ }
+
+ public void testByte() throws Exception {
+ for (byte i = 0; i < 100; i++) {
+ simple.setByte(i, i);
+ }
+
+ for (byte i = 0; i < 100; i++) {
+ assertEquals(i, simple.getByte(i));
+ }
+ }
+
+ public void testShort() throws Exception {
+ for (short i = 0; i < 100; i++) {
+ simple.setShort(i, i);
+ }
+
+ for (short i = 0; i < 100; i++) {
+ assertEquals(i, simple.getShort(i));
+ }
+ }
+
+ public void testChar() throws Exception {
+ for (char i = 0; i < 100; i++) {
+ simple.setChar(i, i);
+ }
+
+ for (char i = 0; i < 100; i++) {
+ assertEquals(i, simple.getChar(i));
+ }
+ }
+
+ public void testInt() throws Exception {
+ for (int i = 0; i < 100; i++) {
+ simple.setInt(i, i);
+ }
+
+ for (int i = 0; i < 100; i++) {
+ assertEquals(i, simple.getInt(i));
+ }
+ }
+
+ public void testLong() throws Exception {
+ for (int i = 0; i < 100; i++) {
+ simple.setLong(i, i);
+ }
+
+ for (int i = 0; i < 100; i++) {
+ assertEquals(i, simple.getLong(i));
+ }
+ }
+
+ public void testFloat() throws Exception {
+ for (int i = 0; i < 100; i++) {
+ simple.setFloat(i, i);
+ }
+
+ for (int i = 0; i < 100; i++) {
+ assertEquals((float)i, simple.getFloat(i), 0);
+ }
+ }
+
+ public void testDouble() throws Exception {
+ for (int i = 0; i < 100; i++) {
+ simple.setDouble(i, i);
+ }
+
+ for (int i = 0; i < 100; i++) {
+ assertEquals((double)i, simple.getDouble(i), 0);
+ }
+ }
+
+ public void testObject() throws Exception {
+ for (int i = 0; i < 100; i++) {
+ simple.setObject(i, new Integer(i));
+ }
+
+ for (int i = 0; i < 100; i++) {
+ assertEquals(new Integer(i), simple.getObject(i));
+ }
+ }
+
+ public void testFoo() throws Exception {
+ for (int i = 0; i < 100; i++) {
+ simple.setFoo(i, new Foo(i));
+ }
+
+ for (int i = 0; i < 100; i++) {
+ assertEquals(new Foo(i), simple.getFoo(i));
+ }
+ }
+
+ public void testMulti() throws Exception {
+ for (int i = 2; i < 100; i++) {
+ simple.setMultiFoo(0, 1, i, new Foo(i));
+ }
+
+ for (int i = 2; i < 100; i++) {
+ assertEquals(new Foo(i), simple.getMultiFoo(0, 1, i));
+ }
+ }
+
+ public static class Echo {
+ public static Map byteMap = new HashMap();
+ public static Map charMap = new HashMap();
+ public static Map doubleMap = new HashMap();
+ public static Map floatMap = new HashMap();
+ public static Map intMap = new HashMap();
+ public static Map longMap = new HashMap();
+ public static Map objectMap = new HashMap();
+ public static Map shortMap = new HashMap();
+
+ public static Object arrayReadObject(Object array, int index) {
+ return objectMap.get(new Integer(index));
+ }
+
+ public static void arrayWriteObject(Object array, int index, Object element) {
+ objectMap.put(new Integer(index), element);
+ }
+
+ public static byte arrayReadByteOrBoolean(Object array, int index) {
+ return ((Byte)byteMap.get(new Integer(index))).byteValue();
+ }
+
+ public static void arrayWriteByteOrBoolean(Object array, int index, byte element) {
+ byteMap.put(new Integer(index), new Byte(element));
+ }
+
+ public static char arrayReadChar(Object array, int index) {
+ return ((Character)charMap.get(new Integer(index))).charValue();
+ }
+
+ public static void arrayWriteChar(Object array, int index, char element) {
+ charMap.put(new Integer(index), new Character(element));
+ }
+
+ public static double arrayReadDouble(Object array, int index) {
+ return ((Double)doubleMap.get(new Integer(index))).doubleValue();
+ }
+
+ public static void arrayWriteDouble(Object array, int index, double element) {
+ doubleMap.put(new Integer(index), new Double(element));
+ }
+
+ public static float arrayReadFloat(Object array, int index) {
+ return ((Float)floatMap.get(new Integer(index))).floatValue();
+ }
+
+ public static void arrayWriteFloat(Object array, int index, float element) {
+ floatMap.put(new Integer(index), new Float(element));
+ }
+
+ public static int arrayReadInt(Object array, int index) {
+ return ((Integer)intMap.get(new Integer(index))).intValue();
+ }
+
+ public static void arrayWriteInt(Object array, int index, int element) {
+ intMap.put(new Integer(index), new Integer(element));
+ }
+
+ public static long arrayReadLong(Object array, int index) {
+ return ((Long)longMap.get(new Integer(index))).longValue();
+ }
+
+ public static void arrayWriteLong(Object array, int index, long element) {
+ longMap.put(new Integer(index), new Long(element));
+ }
+
+ public static short arrayReadShort(Object array, int index) {
+ return ((Short)shortMap.get(new Integer(index))).shortValue();
+ }
+
+ public static void arrayWriteShort(Object array, int index, short element) {
+ shortMap.put(new Integer(index), new Short(element));
+ }
+ }
+
+ public static class Foo {
+ public int bar;
+
+ public Foo(int bar) {
+ this.bar = bar;
+ }
+
+ public int hashCode() {
+ return bar;
+ }
+
+ public boolean equals(Object o) {
+ if (! (o instanceof Foo))
+ return false;
+
+ return ((Foo)o).bar == bar;
+ }
+ }
+
+ public static interface SimpleInterface {
+ public void setBoolean(int pos, boolean value);
+ public boolean getBoolean(int pos);
+
+ public void setByte(int pos, byte value);
+ public byte getByte(int pos);
+
+ public void setShort(int pos, short value);
+ public short getShort(int pos);
+
+ public void setChar(int pos, char value);
+ public char getChar(int pos);
+
+ public void setInt(int pos, int value);
+ public int getInt(int pos);
+
+ public void setLong(int pos, long value);
+ public long getLong(int pos);
+
+ public void setFloat(int pos, float value);
+ public float getFloat(int pos);
+
+ public void setDouble(int pos, double value);
+ public double getDouble(int pos);
+
+ public void setObject(int pos, Object value);
+ public Object getObject(int pos);
+
+ public void setFoo(int pos, Foo value);
+ public Foo getFoo(int pos);
+
+ public void setMultiFoo(int one, int two, int three, Foo foo);
+ public Foo getMultiFoo(int one, int two, int three);
+ }
+
+ public static class Simple implements SimpleInterface {
+ private boolean[] booleans;
+ private byte[] bytes;
+ private short[] shorts;
+ private char[] chars;
+ private int[] ints;
+ private long[] longs;
+ private float[] floats;
+ private double[] doubles;
+ private Object[] objects;
+ private Foo[] foos;
+ private Foo[][][] multi;
+
+ public Simple() {
+ multi[0] = new Foo[0][0];
+ multi[0][1] = new Foo[0];
+ }
+
+ public boolean getBoolean(int pos) {
+ return booleans[pos];
+ }
+
+ public byte getByte(int pos) {
+ return bytes[pos];
+ }
+
+ public char getChar(int pos) {
+ return chars[pos];
+ }
+
+ public double getDouble(int pos) {
+ return doubles[pos];
+ }
+
+ public float getFloat(int pos) {
+ return floats[pos];
+ }
+
+ public Foo getFoo(int pos) {
+ return foos[pos];
+ }
+
+ public int getInt(int pos) {
+ return ints[pos];
+ }
+
+ public long getLong(int pos) {
+ return longs[pos];
+ }
+
+ public Object getObject(int pos) {
+ return objects[pos];
+ }
+
+ public short getShort(int pos) {
+ return shorts[pos];
+ }
+
+ public Foo getMultiFoo(int one, int two, int three) {
+ return multi[one][two][three];
+ }
+
+ public void setBoolean(int pos, boolean value) {
+ booleans[pos] = value;
+ }
+
+ public void setByte(int pos, byte value) {
+ bytes[pos] = value;
+ }
+
+ public void setChar(int pos, char value) {
+ chars[pos] = value;
+ }
+
+ public void setDouble(int pos, double value) {
+ doubles[pos] = value;
+ }
+
+ public void setFloat(int pos, float value) {
+ floats[pos] = value;
+ }
+
+ public void setFoo(int pos, Foo value) {
+ foos[pos] = value;
+ }
+
+ public void setInt(int pos, int value) {
+ ints[pos] = value;
+ }
+
+ public void setLong(int pos, long value) {
+ longs[pos] = value;
+ }
+
+ public void setObject(int pos, Object value) {
+ objects[pos] = value;
+ }
+
+ public void setShort(int pos, short value) {
+ shorts[pos] = value;
+ }
+
+ public void setMultiFoo(int one, int two, int three, Foo foo) {
+ multi[one][two][three] = foo;
+ }
+ }
+
+ public static interface ComplexInterface {
+ public Number complexRead(int x);
+ }
+
+ public static class Complex implements ComplexInterface {
+ private Integer[] nums;
+ private Long[] longNums;
+ private static Integer justRead;
+
+ public static Object arrayReadObject(Object array, int offset) {
+ return new Integer(justRead.intValue() + offset);
+ }
+
+ public static void arrayWriteObject(Object array, int offset, Object element) {
+ justRead = (Integer) element;
+ }
+
+ public Object getInteger(int i) {
+ return (Object) new Integer(i);
+ }
+
+ public Number complexRead(int x) {
+ Number[] ns = null;
+ Number n1, n2, n3, n4;
+ try {
+ ((Object[])ns)[1] = getInteger(x);
+ // We have to throw an error since we can't intercept
+ // a guaranteed null array read yet (likely never will be able to)
+ throw new Error("hi");
+ } catch (Error error) {
+ ns = nums;
+ } catch (Exception exception) {
+ ns = longNums;
+ } finally {
+ n1 = ns[1];
+ n2 = ns[2];
+ n3 = ns[3];
+ n4 = ns[4];
+
+ n2.intValue();
+ n3.intValue();
+ n4.intValue();
+ }
+
+ return n1;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/test/test/javassist/proxy/JASSIST113RegressionTest.java b/src/test/test/javassist/proxy/JASSIST113RegressionTest.java
new file mode 100644
index 0000000..baecdb9
--- /dev/null
+++ b/src/test/test/javassist/proxy/JASSIST113RegressionTest.java
@@ -0,0 +1,22 @@
+package test.javassist.proxy;
+
+import javassist.util.proxy.ProxyFactory;
+import junit.framework.TestCase;
+
+/**
+ * Test for regression error detailed in JASSIST-113
+ */
+public class JASSIST113RegressionTest extends TestCase
+{
+ interface Bear
+ {
+ void hibernate();
+ }
+
+ public void testProxyFactoryWithNonPublicInterface()
+ {
+ ProxyFactory proxyFactory = new ProxyFactory();
+ proxyFactory.setInterfaces(new Class[]{Bear.class});
+ proxyFactory.createClass();
+ }
+}
diff --git a/src/test/test/javassist/proxy/ProxyCacheGCTest.java b/src/test/test/javassist/proxy/ProxyCacheGCTest.java
new file mode 100644
index 0000000..379fefc
--- /dev/null
+++ b/src/test/test/javassist/proxy/ProxyCacheGCTest.java
@@ -0,0 +1,121 @@
+package test.javassist.proxy;
+
+import javassist.*;
+import javassist.util.proxy.MethodFilter;
+import javassist.util.proxy.MethodHandler;
+import javassist.util.proxy.ProxyFactory;
+import javassist.util.proxy.ProxyObject;
+import junit.framework.TestCase;
+
+/**
+ * test which checks that proxy classes are not retained after their classloader is released.
+ * this is a before and after test which validates JASSIST-104
+ */
+public class ProxyCacheGCTest extends TestCase
+{
+ /**
+ * creates a large number of proxies in separate classloaders then lets go of the classloaders and
+ * forces a GC. If we run out of heap then we know there is a problem.
+ */
+
+ public final static int REPETITION_COUNT = 10000;
+ private ClassPool basePool;
+ private CtClass baseHandler;
+ private CtClass baseFilter;
+
+ protected void setUp()
+ {
+ basePool = ClassPool.getDefault();
+ try {
+ baseHandler = basePool.get("javassist.util.proxy.MethodHandler");
+ baseFilter = basePool.get("javassist.util.proxy.MethodFilter");
+ } catch (NotFoundException e) {
+ e.printStackTrace();
+ fail("could not find class " + e);
+ }
+ }
+
+ public void testCacheGC()
+ {
+ ClassLoader oldCL = Thread.currentThread().getContextClassLoader();
+ try {
+ ProxyFactory.useCache = false;
+ for (int i = 0; i < REPETITION_COUNT; i++) {
+ ClassLoader newCL = new TestLoader();
+ try {
+ Thread.currentThread().setContextClassLoader(newCL);
+ createProxy(i);
+ } finally {
+ Thread.currentThread().setContextClassLoader(oldCL);
+ }
+ }
+ } finally {
+ ProxyFactory.useCache = true;
+ }
+ }
+
+ /**
+ * called when a specific classloader is in place on the thread to create a method handler, method filter
+ * and proxy in the current loader and then
+ */
+ public void createProxy(int counter)
+ {
+ try {
+ ClassPool classPool = new ClassPool(basePool);
+
+ // create a target class in the current class loader
+ String targetName = "test.javassist.MyTarget_" + counter;
+ String targetConstructorName = "MyTarget_" + counter;
+ CtClass ctTargetClass = classPool.makeClass(targetName);
+ CtMethod targetMethod = CtNewMethod.make("public Object test() { return this; }", ctTargetClass);
+ ctTargetClass.addMethod(targetMethod);
+ CtConstructor targetConstructor = CtNewConstructor.make("public " + targetConstructorName + "() { }", ctTargetClass);
+ ctTargetClass.addConstructor(targetConstructor);
+
+ // create a handler in the current classloader
+ String handlerName = "test.javassist.MyHandler_" + counter;
+ CtClass ctHandlerClass = classPool.makeClass(handlerName);
+ ctHandlerClass.addInterface(baseHandler);
+ CtMethod handlerInvoke = CtNewMethod.make("public Object invoke(Object self, java.lang.reflect.Method thisMethod, java.lang.reflect.Method proceed, Object[] args) throws Throwable { return proceed.invoke(self, args); }", ctHandlerClass);
+ ctHandlerClass.addMethod(handlerInvoke);
+
+ // create a filter in the current classloader
+ String filterName = "test.javassist.MyFilter" + counter;
+ CtClass ctFilterClass = classPool.makeClass(filterName);
+ ctFilterClass.addInterface(baseFilter);
+ CtMethod filterIsHandled = CtNewMethod.make("public boolean isHandled(java.lang.reflect.Method m) { return true; }", ctFilterClass);
+ ctFilterClass.addMethod(filterIsHandled);
+
+ // now create a proxyfactory and use it to create a proxy
+
+ ProxyFactory factory = new ProxyFactory();
+ Class javaTargetClass = classPool.toClass(ctTargetClass);
+ Class javaHandlerClass = classPool.toClass(ctHandlerClass);
+ Class javaFilterClass = classPool.toClass(ctFilterClass);
+
+ MethodHandler handler= (MethodHandler)javaHandlerClass.newInstance();
+ MethodFilter filter = (MethodFilter)javaFilterClass.newInstance();
+
+ // ok, now create a factory and a proxy class and proxy from that factory
+ factory.setFilter(filter);
+ factory.setSuperclass(javaTargetClass);
+ // factory.setSuperclass(Object.class);
+
+ Class proxyClass = factory.createClass();
+ Object target = proxyClass.newInstance();
+ ((ProxyObject)target).setHandler(handler);
+ } catch (Exception e) {
+ e.printStackTrace();
+ fail("cannot create proxy " + e);
+ }
+
+ }
+
+ /**
+ * a classloader which inherits from the system class loader and within which a proxy handler,
+ * filter and proxy will be located.
+ */
+ public static class TestLoader extends ClassLoader
+ {
+ }
+}
diff --git a/src/test/test/javassist/proxy/ProxyFactoryCompatibilityTest.java b/src/test/test/javassist/proxy/ProxyFactoryCompatibilityTest.java
new file mode 100644
index 0000000..5b72d82
--- /dev/null
+++ b/src/test/test/javassist/proxy/ProxyFactoryCompatibilityTest.java
@@ -0,0 +1,115 @@
+package test.javassist.proxy;
+
+import javassist.*;
+import javassist.util.proxy.MethodFilter;
+import javassist.util.proxy.MethodHandler;
+import javassist.util.proxy.ProxyFactory;
+import javassist.util.proxy.ProxyObject;
+import junit.framework.TestCase;
+
+import java.lang.reflect.Method;
+
+/**
+ * test which checks that it is still possible to use the old style proxy factory api
+ * to create proxy classes which set their own handler. it checks that caching is
+ * automatically disabled if this legacy api is used. it also exercises the new style
+ * api, ensuring that caching works correctly with this model.
+ */
+public class ProxyFactoryCompatibilityTest extends TestCase
+{
+ private ClassPool basePool;
+ MethodFilter filter;
+ MethodHandler handler;
+
+ protected void setUp()
+ {
+ basePool = ClassPool.getDefault();
+ filter = new MethodFilter() {
+ public boolean isHandled(Method m) {
+ return !m.getName().equals("finalize");
+ }
+ };
+
+ handler = new MethodHandler() {
+ public Object invoke(Object self, Method m, Method proceed,
+ Object[] args) throws Throwable {
+ System.out.println("calling: " + m.getName());
+ return proceed.invoke(self, args); // execute the original method.
+ }
+ };
+ }
+
+ public void testFactoryCompatibility() throws Exception
+ {
+ System.out.println("ProxyFactory.useCache = " + ProxyFactory.useCache);
+ // create a factory which, by default, uses caching
+ ProxyFactory factory = new ProxyFactory();
+ factory.setSuperclass(TestClass.class);
+ factory.setInterfaces(new Class[] { TestInterface.class});
+ factory.setFilter(filter);
+
+ // create the same class twice and check that it is reused
+ Class proxyClass1 = factory.createClass();
+ System.out.println("created first class " + proxyClass1.getName());
+ TestClass proxy1 = (TestClass)proxyClass1.newInstance();
+ ((ProxyObject) proxy1).setHandler(handler);
+ proxy1.testMethod();
+ assertTrue(proxy1.isTestCalled());
+
+ Class proxyClass2 = factory.createClass();
+ System.out.println("created second class " + proxyClass2.getName());
+ TestClass proxy2 = (TestClass)proxyClass2.newInstance();
+ ((ProxyObject) proxy2).setHandler(handler);
+ proxy2.testMethod();
+ assertTrue(proxy2.isTestCalled());
+
+ assertTrue(proxyClass1 == proxyClass2);
+
+ // create a factory which, by default, uses caching then set the handler so it creates
+ // classes which do not get cached.
+ ProxyFactory factory2 = new ProxyFactory();
+ factory.setSuperclass(TestClass.class);
+ factory.setInterfaces(new Class[] { TestInterface.class});
+ factory.setFilter(filter);
+ factory.setHandler(handler);
+
+ // create the same class twice and check that it is reused
+ Class proxyClass3 = factory.createClass();
+ System.out.println("created third class " + proxyClass3.getName());
+ TestClass proxy3 = (TestClass)proxyClass3.newInstance();
+ proxy3.testMethod();
+ assertTrue(proxy3.isTestCalled());
+
+ Class proxyClass4 = factory.createClass();
+ System.out.println("created fourth class " + proxyClass4.getName());
+ TestClass proxy4 = (TestClass)proxyClass4.newInstance();
+ proxy4.testMethod();
+ assertTrue(proxy4.isTestCalled());
+
+ assertTrue(proxyClass3 != proxyClass4);
+ }
+
+ /**
+ * test class used as the super for the proxy
+ */
+ public static class TestClass {
+ private boolean testCalled = false;
+ public void testMethod()
+ {
+ // record the call
+ testCalled = true;
+ }
+ public boolean isTestCalled()
+ {
+ return testCalled;
+ }
+ }
+
+ /**
+ * test interface used as an interface implemented by the proxy
+ */
+ public static interface TestInterface {
+ public void testMethod();
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/test/javassist/proxy/ProxySerializationTest.java b/src/test/test/javassist/proxy/ProxySerializationTest.java
new file mode 100644
index 0000000..28125de
--- /dev/null
+++ b/src/test/test/javassist/proxy/ProxySerializationTest.java
@@ -0,0 +1,151 @@
+package test.javassist.proxy;
+
+import javassist.util.proxy.*;
+import junit.framework.TestCase;
+
+import java.io.*;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Test to ensure that serialization and deserialization of javassist proxies via
+ * {@link javassist.util.proxy.ProxyObjectOutputStream} and @link javassist.util.proxy.ProxyObjectInputStream}
+ * reuses classes located in the proxy cache. This tests the fixes provided for JASSIST-42 and JASSIST-97.
+ */
+public class ProxySerializationTest extends TestCase
+{
+ public void testSerialization()
+ {
+ ProxyFactory factory = new ProxyFactory();
+ factory.setSuperclass(TestClass.class);
+ factory.setInterfaces(new Class[] {TestInterface.class});
+
+ factory.setUseWriteReplace(true);
+ Class proxyClass = factory.createClass(new TestFilter());
+
+ MethodHandler handler = new TestHandler();
+
+ // first try serialization using writeReplace
+
+ try {
+ String name = "proxytest_1";
+ Constructor constructor = proxyClass.getConstructor(new Class[] {String.class});
+ TestClass proxy = (TestClass)constructor.newInstance(new Object[] {name});
+ ((ProxyObject)proxy).setHandler(handler);
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ ObjectOutputStream out = new ObjectOutputStream(bos);
+ out.writeObject(proxy);
+ out.close();
+ byte[] bytes = bos.toByteArray();
+ ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
+ ObjectInputStream in = new ObjectInputStream(bis);
+ TestClass newProxy = (TestClass)in.readObject();
+ // inherited fields should not have been deserialized
+ assertTrue("new name should be null", newProxy.getName() == null);
+ // since we are reading into the same JVM the new proxy should have the same class as the old proxy
+ assertTrue("classes should be equal", newProxy.getClass() == proxy.getClass());
+ } catch (Exception e) {
+ e.printStackTrace();
+ fail();
+ }
+
+ // second try serialization using proxy object output/input streams
+
+ factory.setUseWriteReplace(false);
+ proxyClass = factory.createClass(new TestFilter());
+
+ try {
+ String name = "proxytest_2";
+ Constructor constructor = proxyClass.getConstructor(new Class[] {String.class});
+ TestClass proxy = (TestClass)constructor.newInstance(new Object[] {name});
+ ((ProxyObject)proxy).setHandler(handler);
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ ProxyObjectOutputStream out = new ProxyObjectOutputStream(bos);
+ out.writeObject(proxy);
+ out.close();
+ byte[] bytes = bos.toByteArray();
+ ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
+ ProxyObjectInputStream in = new ProxyObjectInputStream(bis);
+ TestClass newProxy = (TestClass)in.readObject();
+ // inherited fields should have been deserialized
+ assertTrue("names should be equal", proxy.getName().equals(newProxy.getName()));
+ // since we are reading into the same JVM the new proxy should have the same class as the old proxy
+ assertTrue("classes should still be equal", newProxy.getClass() == proxy.getClass());
+ } catch (Exception e) {
+ e.printStackTrace();
+ fail();
+ }
+ }
+
+ public static class TestFilter implements MethodFilter, Serializable
+ {
+ public boolean isHandled(Method m) {
+ if (m.getName().equals("getName")) {
+ return true;
+ }
+ return false;
+ }
+
+ public boolean equals(Object o)
+ {
+ if (o instanceof TestFilter) {
+ // all test filters are equal
+ return true;
+ }
+
+ return false;
+ }
+
+ public int hashCode()
+ {
+ return TestFilter.class.hashCode();
+ }
+ }
+
+ public static class TestHandler implements MethodHandler, Serializable
+ {
+ public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable
+ {
+ return proceed.invoke(self, args);
+ }
+ public boolean equals(Object o)
+ {
+ if (o instanceof TestHandler) {
+ // all test handlers are equal
+ return true;
+ }
+
+ return false;
+ }
+
+ public int hashCode()
+ {
+ return TestHandler.class.hashCode();
+ }
+ }
+
+ public static class TestClass implements Serializable
+ {
+ public String name;
+
+ public TestClass()
+ {
+ }
+
+ public TestClass(String name)
+ {
+ this.name = name;
+ }
+
+ public String getName()
+ {
+ return name;
+ }
+ }
+
+ public static interface TestInterface
+ {
+ public String getName();
+ }
+}
diff --git a/src/test/test/javassist/proxy/ProxySimpleTest.java b/src/test/test/javassist/proxy/ProxySimpleTest.java
new file mode 100644
index 0000000..f74fce4
--- /dev/null
+++ b/src/test/test/javassist/proxy/ProxySimpleTest.java
@@ -0,0 +1,64 @@
+package test.javassist.proxy;
+
+import junit.framework.TestCase;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.lang.reflect.Method;
+
+import javassist.util.proxy.ProxyFactory;
+
+public class ProxySimpleTest extends TestCase {
+ public void testReadWrite() throws Exception {
+ final String fileName = "read-write.bin";
+ ProxyFactory.ClassLoaderProvider cp = ProxyFactory.classLoaderProvider;
+ ProxyFactory.classLoaderProvider = new ProxyFactory.ClassLoaderProvider() {
+ public ClassLoader get(ProxyFactory pf) {
+ return new javassist.Loader();
+ }
+ };
+ ProxyFactory pf = new ProxyFactory();
+ pf.setSuperclass(ReadWriteData.class);
+ Object data = pf.createClass().newInstance();
+ // Object data = new ReadWriteData();
+ ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(fileName));
+ oos.writeObject(data);
+ oos.close();
+ ProxyFactory.classLoaderProvider = cp;
+
+ ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileName));
+ Object data2 = ois.readObject();
+ ois.close();
+ int i = ((ReadWriteData)data2).foo();
+ assertEquals(4, i);
+ }
+
+ public static class ReadWriteData implements Serializable {
+ public int foo() { return 4; }
+ }
+
+ public void testWriteReplace() throws Exception {
+ ProxyFactory pf = new ProxyFactory();
+ pf.setSuperclass(WriteReplace.class);
+ Object data = pf.createClass().newInstance();
+ assertEquals(data, ((WriteReplace)data).writeReplace());
+
+ ProxyFactory pf2 = new ProxyFactory();
+ pf2.setSuperclass(WriteReplace2.class);
+ Object data2 = pf2.createClass().newInstance();
+ Method meth = data2.getClass().getDeclaredMethod("writeReplace", new Class[0]);
+ assertEquals("javassist.util.proxy.SerializedProxy",
+ meth.invoke(data2, new Object[0]).getClass().getName());
+ }
+
+ public static class WriteReplace implements Serializable {
+ public Object writeReplace() { return this; }
+ }
+
+ public static class WriteReplace2 implements Serializable {
+ public Object writeReplace(int i) { return new Integer(i); }
+ }
+}
diff --git a/tutorial/brown.css b/tutorial/brown.css
new file mode 100644
index 0000000..7570549
--- /dev/null
+++ b/tutorial/brown.css
@@ -0,0 +1,19 @@
+h1,h2,h3 {
+ color:#663300;
+ padding:4px 6px 6px 10px;
+ border-width:1px 0px 1px 0px;
+ border-color:#F5DEB3;
+ border-style:solid;
+}
+
+h3 {
+ padding-left: 30px;
+}
+
+h4 {
+ color:#663300;
+}
+
+em {
+ color:#cc0000;
+}
diff --git a/tutorial/tutorial.html b/tutorial/tutorial.html
new file mode 100644
index 0000000..d62743d
--- /dev/null
+++ b/tutorial/tutorial.html
@@ -0,0 +1,1101 @@
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <title>Javassist Tutorial</title>
+ <link rel="stylesheet" type="text/css" href="brown.css">
+</head>
+<body>
+
+<b>
+<font size="+3">
+Getting Started with Javassist
+</font>
+
+<p><font size="+2">
+Shigeru Chiba
+</font>
+</b>
+
+<p><div align="right"><a href="tutorial2.html">Next page</a></div>
+
+<ul>1. <a href="#read">Reading and writing bytecode</a>
+<br>2. <a href="#pool">ClassPool</a>
+<br>3. <a href="#load">Class loader</a>
+<br>4. <a href="tutorial2.html#intro">Introspection and customization</a>
+<br>5. <a href="tutorial3.html#intro">Bytecode level API</a>
+<br>6. <a href="tutorial3.html#generics">Generics</a>
+<br>7. <a href="tutorial3.html#varargs">Varargs</a>
+<br>8. <a href="tutorial3.html#j2me">J2ME</a>
+</ul>
+
+<p><br>
+
+<a name="read">
+<h2>1. Reading and writing bytecode</h2>
+
+<p>Javassist is a class library for dealing with Java bytecode.
+Java bytecode is stored in a binary file called a class file.
+Each class file contains one Java class or interface.
+
+<p>The class <code>Javassist.CtClass</code> is an absatract
+representation of a class file. A <code>CtClass</code> (compile-time
+class) object is a handle for dealing with a class file. The
+following program is a very simple example:
+
+<ul><pre>
+ClassPool pool = ClassPool.getDefault();
+CtClass cc = pool.get("test.Rectangle");
+cc.setSuperclass(pool.get("test.Point"));
+cc.writeFile();
+</pre></ul>
+
+<p>This program first obtains a <code>ClassPool</code> object, which
+controls bytecode modification with Javassist. The
+<code>ClassPool</code> object is a container of <code>CtClass</code>
+object representing a class file. It reads a class file on demand for
+constructing a <code>CtClass</code> object and records the
+constructed object for responding later accesses.
+
+To modify the definition of a class, the users must first obtain
+from a <code>ClassPool</code> object
+a reference to a <code>CtClass</code> object representing that class.
+<code>get()</code> in <code>ClassPool</code> is used for this purpose.
+In the case of the program shown above, the
+<code>CtClass</code> object representing a class
+<code>test.Rectangle</code> is obtained from the
+<code>ClassPool</code> object and it is assigned to a variable
+<code>cc</code>.
+The <code>ClassPool</code> object returned by <code>getDfault()</code>
+searches the default system search path.
+
+<p>From the implementation viewpoint, <code>ClassPool</code> is a hash
+table of <code>CtClass</code> objects, which uses the class names as
+keys. <code>get()</code> in <code>ClassPool</code> searches this hash
+table to find a <code>CtClass</code> object associated with the
+specified key. If such a <code>CtClass</code> object is not found,
+<code>get()</code> reads a class file to construct a new
+<code>CtClass</code> object, which is recorded in the hash table and
+then returned as the resulting value of <code>get()</code>.
+
+<p>The <code>CtClass</code> object obtained from a <code>ClassPool</code>
+object can be modified
+(<a href="tutorial2.html#intro">details of how to modify
+a <code>CtClass</code></a> will be presented later).
+In the example above, it is modified so that the superclass of
+<code>test.Rectangle</code> is changed into a class
+<code>test.Point</code>. This change is reflected on the original
+class file when <code>writeFile()</code> in <code>CtClass()</code> is
+finally called.
+
+<p><code>writeFile()</code> translates the <code>CtClass</code> object
+into a class file and writes it on a local disk.
+Javassist also provides a method for directly obtaining the
+modified bytecode. To obtain the bytecode, call <code>toBytecode()</code>:
+
+<ul><pre>
+byte[] b = cc.toBytecode();
+</pre></ul>
+
+<p>You can directly load the <code>CtClass</code> as well:
+
+<ul><pre>
+Class clazz = cc.toClass();
+</pre></ul>
+
+<p><code>toClass()</code> requests the context class loader for the current
+thread to load the class file represented by the <code>CtClass</code>. It
+returns a <code>java.lang.Class</code> object representing the loaded class.
+For more details, please see <a href="#toclass">this section below</a>.
+
+<a name="def">
+<h4>Defining a new class</h4>
+
+<p>To define a new class from scratch, <code>makeClass()</code>
+must be called on a <code>ClassPool</code>.
+
+<ul><pre>
+ClassPool pool = ClassPool.getDefault();
+CtClass cc = pool.makeClass("Point");
+</pre></ul>
+
+<p>This program defines a class <code>Point</code>
+including no members.
+Member methods of <code>Point</code> can be created with
+factory methods declared in <code>CtNewMethod</code> and
+appended to <code>Point</code> with <code>addMethod()</code>
+in <code>CtClass</code>.
+
+<p><code>makeClass()</code> cannot create a new interface;
+<code>makeInterface()</code> in <code>ClassPool</code> can do.
+Member methods in an interface can be created with
+<code>abstractMethod()</code> in <code>CtNewMethod</code>.
+Note that an interface method is an abstract method.
+
+<a name="frozenclasses">
+<h4>Frozen classes</h4></a>
+
+<p>If a <code>CtClass</code> object is converted into a class file by
+<code>writeFile()</code>, <code>toClass()</code>, or
+<code>toBytecode()</code>, Javassist freezes that <code>CtClass</code>
+object. Further modifications of that <code>CtClass</code> object are
+not permitted. This is for warning the developers when they attempt
+to modify a class file that has been already loaded since the JVM does
+not allow reloading a class.
+
+<p>A frozen <code>CtClass</code> can be defrost so that
+modifications of the class definition will be permitted. For example,
+
+<ul><pre>
+CtClasss cc = ...;
+ :
+cc.writeFile();
+cc.defrost();
+cc.setSuperclass(...); // OK since the class is not frozen.
+</pre></ul>
+
+<p>After <code>defrost()</code> is called, the <code>CtClass</code>
+object can be modified again.
+
+<p>If <code>ClassPool.doPruning</code> is set to <code>true</code>,
+then Javassist prunes the data structure contained
+in a <code>CtClass</code> object
+when Javassist freezes that object.
+To reduce memory
+consumption, pruning discards unnecessary attributes
+(<code>attribute_info</code> structures) in that object.
+For example, <code>Code_attribute</code> structures (method bodies)
+are discarded.
+Thus, after a
+<code>CtClass</code> object is pruned, the bytecode of a method is not
+accessible except method names, signatures, and annotations.
+The pruned <code>CtClass</code> object cannot be defrost again.
+The default value of <code>ClassPool.doPruning</code> is <code>false</code>.
+
+<p>To disallow pruning a particular <code>CtClass</code>,
+<code>stopPruning()</code> must be called on that object in advance:
+
+<ul><pre>
+CtClasss cc = ...;
+cc.stopPruning(true);
+ :
+cc.writeFile(); // convert to a class file.
+// cc is not pruned.
+</pre></ul>
+
+<p>The <code>CtClass</code> object <code>cc</code> is not pruned.
+Thus it can be defrost after <code>writeFile()</code> is called.
+
+<ul><b>Note:</b>
+While debugging, you might want to temporarily stop pruning and freezing
+and write a modified class file to a disk drive.
+<code>debugWriteFile()</code> is a convenient method
+for that purpose. It stops pruning, writes a class file, defrosts it,
+and turns pruning on again (if it was initially on).
+</ul>
+
+
+
+<h4>Class search path</h4>
+
+<p>The default <code>ClassPool</code> returned
+by a static method <code>ClassPool.getDefault()</code>
+searches the same path that the underlying JVM (Java virtual machine) has.
+<em>If a program is running on a web application server such as JBoss and Tomcat,
+the <code>ClassPool</code> object may not be able to find user classes</em>
+since such a web application server uses multiple class loaders as well as
+the system class loader. In that case, an additional class path must be
+registered to the <code>ClassPool</code>. Suppose that <code>pool</code>
+refers to a <code>ClassPool</code> object:
+
+<ul><pre>
+pool.insertClassPath(new ClassClassPath(this.getClass()));
+</pre></ul>
+
+<p>
+This statement registers the class path that was used for loading
+the class of the object that <code>this</code> refers to.
+You can use any <code>Class</code> object as an argument instead of
+<code>this.getClass()</code>. The class path used for loading the
+class represented by that <code>Class</code> object is registered.
+
+<p>
+You can register a directory name as the class search path.
+For example, the following code adds a directory
+<code>/usr/local/javalib</code>
+to the search path:
+
+<ul><pre>
+ClassPool pool = ClassPool.getDefault();
+pool.insertClassPath("/usr/local/javalib");
+</pre></ul>
+
+<p>The search path that the users can add is not only a directory but also
+a URL:
+
+<ul><pre>
+ClassPool pool = ClassPool.getDefault();
+ClassPath cp = new URLClassPath("www.javassist.org", 80, "/java/", "org.javassist.");
+pool.insertClassPath(cp);
+</pre></ul>
+
+<p>This program adds "http://www.javassist.org:80/java/" to the class search
+path. This URL is used only for searching classes belonging to a
+package <code>org.javassist</code>. For example, to load a class
+<code>org.javassist.test.Main</code>, its class file will be obtained from:
+
+<ul><pre>http://www.javassist.org:80/java/org/javassist/test/Main.class
+</pre></ul>
+
+<p>Furthermore, you can directly give a byte array
+to a <code>ClassPool</code> object
+and construct a <code>CtClass</code> object from that array. To do this,
+use <code>ByteArrayClassPath</code>. For example,
+
+<ul><pre>
+ClassPool cp = ClassPool.getDefault();
+byte[] b = <em>a byte array</em>;
+String name = <em>class name</em>;
+cp.insertClassPath(new ByteArrayClassPath(name, b));
+CtClass cc = cp.get(name);
+</pre></ul>
+
+<p>The obtained <code>CtClass</code> object represents
+a class defined by the class file specified by <code>b</code>.
+The <code>ClassPool</code> reads a class file from the given
+<code>ByteArrayClassPath</code> if <code>get()</code> is called
+and the class name given to <code>get()</code> is equal to
+one specified by <code>name</code>.
+
+<p>If you do not know the fully-qualified name of the class, then you
+can use <code>makeClass()</code> in <code>ClassPool</code>:
+
+<ul><pre>
+ClassPool cp = ClassPool.getDefault();
+InputStream ins = <em>an input stream for reading a class file</em>;
+CtClass cc = cp.makeClass(ins);
+</pre></ul>
+
+<p><code>makeClass()</code> returns the <code>CtClass</code> object
+constructed from the given input stream. You can use
+<code>makeClass()</code> for eagerly feeding class files to
+the <code>ClassPool</code> object. This might improve performance
+if the search path includes a large jar file. Since
+a <code>ClassPool</code> object reads a class file on demand,
+it might repeatedly search the whole jar file for every class file.
+<code>makeClass()</code> can be used for optimizing this search.
+The <code>CtClass</code> constructed by <code>makeClass()</code>
+is kept in the <code>ClassPool</code> object and the class file is never
+read again.
+
+<p>The users can extend the class search path. They can define a new
+class implementing <code>ClassPath</code> interface and give an
+instance of that class to <code>insertClassPath()</code> in
+<code>ClassPool</code>. This allows a non-standard resource to be
+included in the search path.
+
+<p><br>
+
+<a name="pool">
+<h2>2. ClassPool</h2>
+
+<p>
+A <code>ClassPool</code> object is a container of <code>CtClass</code>
+objects. Once a <code>CtClass</code> object is created, it is
+recorded in a <code>ClassPool</code> for ever. This is because a
+compiler may need to access the <code>CtClass</code> object later when
+it compiles source code that refers to the class represented by that
+<code>CtClass</code>.
+
+<p>
+For example, suppose that a new method <code>getter()</code> is added
+to a <code>CtClass</code> object representing <code>Point</code>
+class. Later, the program attempts to compile source code including a
+method call to <code>getter()</code> in <code>Point</code> and use the
+compiled code as the body of a method, which will be added to another
+class <code>Line</code>. If the <code>CtClass</code> object representing
+<code>Point</code> is lost, the compiler cannot compile the method call
+to <code>getter()</code>. Note that the original class definition does
+not include <code>getter()</code>. Therefore, to correctly compile
+such a method call, the <code>ClassPool</code>
+must contain all the instances of <code>CtClass</code> all the time of
+program execution.
+
+<a name="avoidmemory">
+<h4>Avoid out of memory</h4>
+</a>
+
+<p>
+This specification of <code>ClassPool</code> may cause huge memory
+consumption if the number of <code>CtClass</code> objects becomes
+amazingly large (this rarely happens since Javassist tries to reduce
+memory consumption in <a href="#frozenclasses">various ways</a>).
+To avoid this problem, you
+can explicitly remove an unnecessary <code>CtClass</code> object from
+the <code>ClassPool</code>. If you call <code>detach()</code> on a
+<code>CtClass</code> object, then that <code>CtClass</code> object is
+removed from the <code>ClassPool</code>. For example,
+
+<ul><pre>
+CtClass cc = ... ;
+cc.writeFile();
+cc.detach();
+</pre></ul>
+
+<p>You must not call any method on that
+<code>CtClass</code> object after <code>detach()</code> is called.
+However, you can call <code>get()</code> on <code>ClassPool</code>
+to make a new instance of <code>CtClass</code> representing
+the same class. If you call <code>get()</code>, the <code>ClassPool</code>
+reads a class file again and newly creates a <code>CtClass</code>
+object, which is returned by <code>get()</code>.
+
+<p>
+Another idea is to occasionally replace a <code>ClassPool</code> with
+a new one and discard the old one. If an old <code>ClassPool</code>
+is garbage collected, the <code>CtClass</code> objects included in
+that <code>ClassPool</code> are also garbage collected.
+To create a new instance of <code>ClassPool</code>, execute the following
+code snippet:
+
+<ul><pre>
+ClassPool cp = new ClassPool(true);
+// if needed, append an extra search path by appendClassPath()
+</pre></ul>
+
+<p>This creates a <code>ClassPool</code> object that behaves as the
+default <code>ClassPool</code> returned by
+<code>ClassPool.getDefault()</code> does.
+Note that <code>ClassPool.getDefault()</code> is a singleton factory method
+provided for convenience. It creates a <code>ClassPool</code> object in
+the same way shown above although it keeps a single instance of
+<code>ClassPool</code> and reuses it.
+A <code>ClassPool</code> object returned by <code>getDefault()</code>
+does not have a special role. <code>getDefault()</code> is a convenience
+method.
+
+<p>Note that <code>new ClassPool(true)</code> is a convenient constructor,
+which constructs a <code>ClassPool</code> object and appends the system
+search path to it. Calling that constructor is
+equivalent to the following code:
+
+<ul><pre>
+ClassPool cp = new ClassPool();
+cp.appendSystemPath(); // or append another path by appendClassPath()
+</pre></ul>
+
+<h4>Cascaded ClassPools</h4>
+
+<p>
+<em>If a program is running on a web application server,</em>
+creating multiple instances of <code>ClassPool</code> might be necessary;
+an instance of <code>ClassPool</code> should be created
+for each class loader (i.e. container).
+The program should create a <code>ClassPool</code> object by not calling
+<code>getDefault()</code> but a constructor of <code>ClassPool</code>.
+
+<p>
+Multiple <code>ClassPool</code> objects can be cascaded like
+<code>java.lang.ClassLoader</code>. For example,
+
+<ul><pre>
+ClassPool parent = ClassPool.getDefault();
+ClassPool child = new ClassPool(parent);
+child.insertClassPath("./classes");
+</pre></ul>
+
+<p>
+If <code>child.get()</code> is called, the child <code>ClassPool</code>
+first delegates to the parent <code>ClassPool</code>. If the parent
+<code>ClassPool</code> fails to find a class file, then the child
+<code>ClassPool</code> attempts to find a class file
+under the <code>./classes</code> directory.
+
+<p>
+If <code>child.childFirstLookup</code> is true, the child
+<code>ClassPool</code> attempts to find a class file before delegating
+to the parent <code>ClassPool</code>. For example,
+
+<ul><pre>
+ClassPool parent = ClassPool.getDefault();
+ClassPool child = new ClassPool(parent);
+child.appendSystemPath(); // the same class path as the default one.
+child.childFirstLookup = true; // changes the behavior of the child.
+</pre></ul>
+
+<h4>Changing a class name for defining a new class</h4>
+
+<p>A new class can be defined as a copy of an existing class.
+The program below does that:
+
+<ul><pre>
+ClassPool pool = ClassPool.getDefault();
+CtClass cc = pool.get("Point");
+cc.setName("Pair");
+</pre></ul>
+
+<p>This program first obtains the <code>CtClass</code> object for
+class <code>Point</code>. Then it calls <code>setName()</code> to
+give a new name <code>Pair</code> to that <code>CtClass</code> object.
+After this call, all occurrences of the class name in the class
+definition represented by that <code>CtClass</code> object are changed
+from <code>Point</code> to <code>Pair</code>. The other part of the
+class definition does not change.
+
+<p>Note that <code>setName()</code> in <code>CtClass</code> changes a
+record in the <code>ClassPool</code> object. From the implementation
+viewpoint, a <code>ClassPool</code> object is a hash table of
+<code>CtClass</code> objects. <code>setName()</code> changes
+the key associated to the <code>CtClass</code> object in the hash
+table. The key is changed from the original class name to the new
+class name.
+
+<p>Therefore, if <code>get("Point")</code> is later called on the
+<code>ClassPool</code> object again, then it never returns the
+<code>CtClass</code> object that the variable <code>cc</code> refers to.
+The <code>ClassPool</code> object reads
+a class file
+<code>Point.class</code> again and it constructs a new <code>CtClass</code>
+object for class <code>Point</code>.
+This is because the <code>CtClass</code> object associated with the name
+<code>Point</code> does not exist any more.
+See the followings:
+
+<ul><pre>
+ClassPool pool = ClassPool.getDefault();
+CtClass cc = pool.get("Point");
+CtClass cc1 = pool.get("Point"); // cc1 is identical to cc.
+cc.setName("Pair");
+CtClass cc2 = pool.get("Pair"); // cc2 is identical to cc.
+CtClass cc3 = pool.get("Point"); // cc3 is not identical to cc.
+</pre></ul>
+
+<p><code>cc1</code> and <code>cc2</code> refer to the same instance of
+<code>CtClass</code> that <code>cc</code> does whereas
+<code>cc3</code> does not. Note that, after
+<code>cc.setName("Pair")</code> is executed, the <code>CtClass</code>
+object that <code>cc</code> and <code>cc1</code> refer to represents
+the <code>Pair</code> class.
+
+<p>The <code>ClassPool</code> object is used to maintain one-to-one
+mapping between classes and <code>CtClass</code> objects. Javassist
+never allows two distinct <code>CtClass</code> objects to represent
+the same class unless two independent <code>ClassPool</code> are created.
+This is a significant feature for consistent program
+transformation.
+
+<p>To create another copy of the default instance of
+<code>ClassPool</code>, which is returned by
+<code>ClassPool.getDefault()</code>, execute the following code
+snippet (this code was already <a href="#avoidmemory">shown above</a>):
+
+<ul><pre>
+ClassPool cp = new ClassPool(true);
+</pre></ul>
+
+<p>If you have two <code>ClassPool</code> objects, then you can
+obtain, from each <code>ClassPool</code>, a distinct
+<code>CtClass</code> object representing the same class file. You can
+differently modify these <code>CtClass</code> objects to generate
+different versions of the class.
+
+<h4>Renaming a frozen class for defining a new class</h4>
+
+<p>Once a <code>CtClass</code> object is converted into a class file
+by <code>writeFile()</code> or <code>toBytecode()</code>, Javassist
+rejects further modifications of that <code>CtClass</code> object.
+Hence, after the <code>CtClass</code> object representing <code>Point</code>
+class is converted into a class file, you cannot define <code>Pair</code>
+class as a copy of <code>Point</code> since executing <code>setName()</code>
+on <code>Point</code> is rejected.
+The following code snippet is wrong:
+
+<ul><pre>
+ClassPool pool = ClassPool.getDefault();
+CtClass cc = pool.get("Point");
+cc.writeFile();
+cc.setName("Pair"); // wrong since writeFile() has been called.
+</pre></ul>
+
+<p>To avoid this restriction, you should call <code>getAndRename()</code>
+in <code>ClassPool</code>. For example,
+
+<ul><pre>
+ClassPool pool = ClassPool.getDefault();
+CtClass cc = pool.get("Point");
+cc.writeFile();
+CtClass cc2 = pool.getAndRename("Point", "Pair");
+</pre></ul>
+
+<p>If <code>getAndRename()</code> is called, the <code>ClassPool</code>
+first reads <code>Point.class</code> for creating a new <code>CtClass</code>
+object representing <code>Point</code> class. However, it renames that
+<code>CtClass</code> object from <code>Point</code> to <code>Pair</code> before
+it records that <code>CtClass</code> object in a hash table.
+Thus <code>getAndRename()</code>
+can be executed after <code>writeFile()</code> or <code>toBytecode()</code>
+is called on the the <code>CtClass</code> object representing <code>Point</code>
+class.
+
+<p><br>
+
+<a name="load">
+<h2>3. Class loader</h2>
+
+<p>If what classes must be modified is known in advance,
+the easiest way for modifying the classes is as follows:
+
+<ul><li>1. Get a <code>CtClass</code> object by calling
+ <code>ClassPool.get()</code>,
+ <li>2. Modify it, and
+ <li>3. Call <code>writeFile()</code> or <code>toBytecode()</code>
+ on that <code>CtClass</code> object to obtain a modified class file.
+</ul>
+
+<p>If whether a class is modified or not is determined at load time,
+the users must make Javassist collaborate with a class loader.
+Javassist can be used with a class loader so that bytecode can be
+modified at load time. The users of Javassist can define their own
+version of class loader but they can also use a class loader provided
+by Javassist.
+
+
+<p><br>
+
+<a name="toclass">
+<h3>3.1 The <code>toClass</code> method in <code>CtClass</code></h3>
+</a>
+
+<p>The <code>CtClass</code> provides a convenience method
+<code>toClass()</code>, which requests the context class loader for
+the current thread to load the class represented by the <code>CtClass</code>
+object. To call this method, the caller must have appropriate permission;
+otherwise, a <code>SecurityException</code> may be thrown.
+
+<p>The following program shows how to use <code>toClass()</code>:
+
+<ul><pre>
+public class Hello {
+ public void say() {
+ System.out.println("Hello");
+ }
+}
+
+public class Test {
+ public static void main(String[] args) throws Exception {
+ ClassPool cp = ClassPool.getDefault();
+ CtClass cc = cp.get("Hello");
+ CtMethod m = cc.getDeclaredMethod("say");
+ m.insertBefore("{ System.out.println(\"Hello.say():\"); }");
+ Class c = cc.toClass();
+ Hello h = (Hello)c.newInstance();
+ h.say();
+ }
+}
+</pre></ul>
+
+<p><code>Test.main()</code> inserts a call to <code>println()</code>
+in the method body of <code>say()</code> in <code>Hello</code>. Then
+it constructs an instance of the modified <code>Hello</code> class
+and calls <code>say()</code> on that instance.
+
+<p>Note that the program above depends on the fact that the
+<code>Hello</code> class is never loaded before <code>toClass()</code>
+is invoked. If not, the JVM would load the original
+<code>Hello</code> class before <code>toClass()</code> requests to
+load the modified <code>Hello</code> class. Hence loading the
+modified <code>Hello</code> class would be failed
+(<code>LinkageError</code> is thrown). For example, if
+<code>main()</code> in <code>Test</code> is something like this:
+
+<ul><pre>
+public static void main(String[] args) throws Exception {
+ Hello orig = new Hello();
+ ClassPool cp = ClassPool.getDefault();
+ CtClass cc = cp.get("Hello");
+ :
+}
+</pre></ul>
+
+<p>then the original <code>Hello</code> class is loaded at the first
+line of <code>main</code> and the call to <code>toClass()</code>
+throws an exception since the class loader cannot load two different
+versions of the <code>Hello</code> class at the same time.
+
+<p><em>If the program is running on some application server such as
+JBoss and Tomcat,</em> the context class loader used by
+<code>toClass()</code> might be inappropriate. In this case, you
+would see an unexpected <code>ClassCastException</code>. To avoid
+this exception, you must explicitly give an appropriate class loader
+to <code>toClass()</code>. For example, if <code>bean</code> is your
+session bean object, then the following code:
+
+<ul><pre>CtClass cc = ...;
+Class c = cc.toClass(bean.getClass().getClassLoader());
+</pre></ul>
+
+<p>would work. You should give <code>toClass()</code> the class loader
+that has loaded your program (in the above example, the class of
+the <code>bean</code> object).
+
+<p><code>toClass()</code> is provided for convenience. If you need
+more complex functionality, you should write your own class loader.
+
+<p><br>
+
+<h3>3.2 Class loading in Java</h3>
+
+<p>In Java, multiple class loaders can coexist and
+each class loader creates its own name space.
+Different class loaders can load different class files with the
+same class name. The loaded two classes are regarded as different
+ones. This feature enables us to run multiple application programs
+on a single JVM even if these programs include different classes
+with the same name.
+
+<ul>
+<b>Note:</b> The JVM does not allow dynamically reloading a class.
+Once a class loader loads a class, it cannot reload a modified
+version of that class during runtime. Thus, you cannot alter
+the definition of a class after the JVM loads it.
+However, the JPDA (Java Platform Debugger Architecture) provides
+limited ability for reloading a class.
+See <a href="#hotswap">Section 3.6</a>.
+</ul>
+
+<p>If the same class file is loaded by two distinct class loaders,
+the JVM makes two distinct classes with the same name and definition.
+The two classes are regarded as different ones.
+Since the two classes are not identical, an instance of one class is
+not assignable to a variable of the other class. The cast operation
+between the two classes fails
+and throws a <em><code>ClassCastException</code></em>.
+
+<p>For example, the following code snippet throws an exception:
+
+<ul><pre>
+MyClassLoader myLoader = new MyClassLoader();
+Class clazz = myLoader.loadClass("Box");
+Object obj = clazz.newInstance();
+Box b = (Box)obj; // this always throws ClassCastException.
+</pre></ul>
+
+<p>
+The <code>Box</code> class is loaded by two class loaders.
+Suppose that a class loader CL loads a class including this code snippet.
+Since this code snippet refers to <code>MyClassLoader</code>,
+<code>Class</code>, <code>Object</code>, and <code>Box</code>,
+CL also loads these classes (unless it delegates to another class loader).
+Hence the type of the variable <code>b</code> is the <code>Box</code>
+class loaded by CL.
+On the other hand, <code>myLoader</code> also loads the <code>Box</code>
+class. The object <code>obj</code> is an instance of
+the <code>Box</code> class loaded by <code>myLoader</code>.
+Therefore, the last statement always throws a
+<code>ClassCastException</code> since the class of <code>obj</code> is
+a different verison of the <code>Box</code> class from one used as the
+type of the variable <code>b</code>.
+
+<p>Multiple class loaders form a tree structure.
+Each class loader except the bootstrap loader has a
+parent class loader, which has normally loaded the class of that child
+class loader. Since the request to load a class can be delegated along this
+hierarchy of class loaders, a class may be loaded by a class loader that
+you do not request the class loading.
+Therefore, the class loader that has been requested to load a class C
+may be different from the loader that actually loads the class C.
+For distinction, we call the former loader <em>the initiator of C</em>
+and we call the latter loader <em>the real loader of C</em>.
+
+<p>
+Furthermore, if a class loader CL requested to load a class C
+(the initiator of C) delegates
+to the parent class loader PL, then the class loader CL is never requested
+to load any classes referred to in the definition of the class C.
+CL is not the initiator of those classes.
+Instead, the parent class loader PL becomes their initiators
+and it is requested to load them.
+<em>The classes that the definition of a class C referes to are loaded by
+the real loader of C.</em>
+
+<p>To understand this behavior, let's consider the following example.
+
+<ul><pre>
+public class Point { // loaded by PL
+ private int x, y;
+ public int getX() { return x; }
+ :
+}
+
+public class Box { // the initiator is L but the real loader is PL
+ private Point upperLeft, size;
+ public int getBaseX() { return upperLeft.x; }
+ :
+}
+
+public class Window { // loaded by a class loader L
+ private Box box;
+ public int getBaseX() { return box.getBaseX(); }
+}</pre></ul>
+
+<p>Suppose that a class <code>Window</code> is loaded by a class loader L.
+Both the initiator and the real loader of <code>Window</code> are L.
+Since the definition of <code>Window</code> refers to <code>Box</code>,
+the JVM will request L to load <code>Box</code>.
+Here, suppose that L delegates this task to the parent class loader PL.
+The initiator of <code>Box</code> is L but the real loader is PL.
+In this case, the initiator of <code>Point</code> is not L but PL
+since it is the same as the real loader of <code>Box</code>.
+Thus L is never requested to load <code>Point</code>.
+
+<p>Next, let's consider a slightly modified example.
+
+<ul><pre>
+public class Point {
+ private int x, y;
+ public int getX() { return x; }
+ :
+}
+
+public class Box { // the initiator is L but the real loader is PL
+ private Point upperLeft, size;
+ public Point getSize() { return size; }
+ :
+}
+
+public class Window { // loaded by a class loader L
+ private Box box;
+ public boolean widthIs(int w) {
+ Point p = box.getSize();
+ return w == p.getX();
+ }
+}</pre></ul>
+
+<p>Now, the definition of <code>Window</code> also refers to
+<code>Point</code>. In this case, the class loader L must
+also delegate to PL if it is requested to load <code>Point</code>.
+<em>You must avoid having two class loaders doubly load the same
+class.</em> One of the two loaders must delegate to
+the other.
+
+<p>
+If L does not delegate to PL when <code>Point</code>
+is loaded, <code>widthIs()</code> would throw a ClassCastException.
+Since the real loader of <code>Box</code> is PL,
+<code>Point</code> referred to in <code>Box</code> is also loaded by PL.
+Therefore, the resulting value of <code>getSize()</code>
+is an instance of <code>Point</code> loaded by PL
+whereas the type of the variable <code>p</code> in <code>widthIs()</code>
+is <code>Point</code> loaded by L.
+The JVM regards them as distinct types and thus it throws an exception
+because of type mismatch.
+
+<p>This behavior is somewhat inconvenient but necessary.
+If the following statement:
+
+<ul><pre>
+Point p = box.getSize();
+</pre></ul>
+
+<p>did not throw an exception,
+then the programmer of <code>Window</code> could break the encapsulation
+of <code>Point</code> objects.
+For example, the field <code>x</code>
+is private in <code>Point</code> loaded by PL.
+However, the <code>Window</code> class could
+directly access the value of <code>x</code>
+if L loads <code>Point</code> with the following definition:
+
+<ul><pre>
+public class Point {
+ public int x, y; // not private
+ public int getX() { return x; }
+ :
+}
+</pre></ul>
+
+<p>
+For more details of class loaders in Java, the following paper would
+be helpful:
+
+<ul>Sheng Liang and Gilad Bracha,
+"Dynamic Class Loading in the Java Virtual Machine",
+<br><i>ACM OOPSLA'98</i>, pp.36-44, 1998.</ul>
+
+<p><br>
+
+<h3>3.3 Using <code>javassist.Loader</code></h3>
+
+<p>Javassist provides a class loader
+<code>javassist.Loader</code>. This class loader uses a
+<code>javassist.ClassPool</code> object for reading a class file.
+
+<p>For example, <code>javassist.Loader</code> can be used for loading
+a particular class modified with Javassist.
+
+<ul><pre>
+import javassist.*;
+import test.Rectangle;
+
+public class Main {
+ public static void main(String[] args) throws Throwable {
+ ClassPool pool = ClassPool.getDefault();
+ Loader cl = new Loader(pool);
+
+ CtClass ct = pool.get("test.Rectangle");
+ ct.setSuperclass(pool.get("test.Point"));
+
+ Class c = cl.loadClass("test.Rectangle");
+ Object rect = c.newInstance();
+ :
+ }
+}
+</pre></ul>
+
+<p>This program modifies a class <code>test.Rectangle</code>. The
+superclass of <code>test.Rectangle</code> is set to a
+<code>test.Point</code> class. Then this program loads the modified
+class, and creates a new instance of the
+<code>test.Rectangle</code> class.
+
+<p>If the users want to modify a class on demand when it is loaded,
+the users can add an event listener to a <code>javassist.Loader</code>.
+The added event listener is
+notified when the class loader loads a class.
+The event-listener class must implement the following interface:
+
+<ul><pre>public interface Translator {
+ public void start(ClassPool pool)
+ throws NotFoundException, CannotCompileException;
+ public void onLoad(ClassPool pool, String classname)
+ throws NotFoundException, CannotCompileException;
+}</pre></ul>
+
+<p>The method <code>start()</code> is called when this event listener
+is added to a <code>javassist.Loader</code> object by
+<code>addTranslator()</code> in <code>javassist.Loader</code>. The
+method <code>onLoad()</code> is called before
+<code>javassist.Loader</code> loads a class. <code>onLoad()</code>
+can modify the definition of the loaded class.
+
+<p>For example, the following event listener changes all classes
+to public classes just before they are loaded.
+
+<ul><pre>public class MyTranslator implements Translator {
+ void start(ClassPool pool)
+ throws NotFoundException, CannotCompileException {}
+ void onLoad(ClassPool pool, String classname)
+ throws NotFoundException, CannotCompileException
+ {
+ CtClass cc = pool.get(classname);
+ cc.setModifiers(Modifier.PUBLIC);
+ }
+}</pre></ul>
+
+<p>Note that <code>onLoad()</code> does not have to call
+<code>toBytecode()</code> or <code>writeFile()</code> since
+<code>javassist.Loader</code> calls these methods to obtain a class
+file.
+
+<p>To run an application class <code>MyApp</code> with a
+<code>MyTranslator</code> object, write a main class as following:
+
+<ul><pre>
+import javassist.*;
+
+public class Main2 {
+ public static void main(String[] args) throws Throwable {
+ Translator t = new MyTranslator();
+ ClassPool pool = ClassPool.getDefault();
+ Loader cl = new Loader();
+ cl.addTranslator(pool, t);
+ cl.run("MyApp", args);
+ }
+}
+</pre></ul>
+
+<p>To run this program, do:
+
+<ul><pre>
+% java Main2 <i>arg1</i> <i>arg2</i>...
+</pre></ul>
+
+<p>The class <code>MyApp</code> and the other application classes
+are translated by <code>MyTranslator</code>.
+
+<p>Note that <em>application</em> classes like <code>MyApp</code> cannot
+access the <em>loader</em> classes such as <code>Main2</code>,
+<code>MyTranslator</code>, and <code>ClassPool</code> because they
+are loaded by different loaders. The application classes are loaded
+by <code>javassist.Loader</code> whereas the loader classes such as
+<code>Main2</code> are by the default Java class loader.
+
+<p><code>javassist.Loader</code> searches for classes in a different
+order from <code>java.lang.ClassLoader</code>.
+<code>ClassLoader</code> first delegates the loading operations to
+the parent class loader and then attempts to load the classes
+only if the parent class loader cannot find them.
+On the other hand,
+<code>javassist.Loader</code> attempts
+to load the classes before delegating to the parent class loader.
+It delegates only if:
+
+<ul><li>the classes are not found by calling <code>get()</code> on
+a <code>ClassPool</code> object, or
+
+<p><li>the classes have been specified by using
+<code>delegateLoadingOf()</code>
+to be loaded by the parent class loader.
+</ul>
+
+<p>This search order allows loading modified classes by Javassist.
+However, it delegates to the parent class loader if it fails
+to find modified classes for some reason. Once a class is loaded by
+the parent class loader, the other classes referred to in that class will be
+also loaded by the parent class loader and thus they are never modified.
+Recall that all the classes referred to in a class C are loaded by the
+real loader of C.
+<em>If your program fails to load a modified class,</em> you should
+make sure whether all the classes using that class have been loaded by
+<code>javassist.Loader</code>.
+
+<p><br>
+
+<h3>3.4 Writing a class loader</h3>
+
+<p>A simple class loader using Javassist is as follows:
+
+<ul><pre>import javassist.*;
+
+public class SampleLoader extends ClassLoader {
+ /* Call MyApp.main().
+ */
+ public static void main(String[] args) throws Throwable {
+ SampleLoader s = new SampleLoader();
+ Class c = s.loadClass("MyApp");
+ c.getDeclaredMethod("main", new Class[] { String[].class })
+ .invoke(null, new Object[] { args });
+ }
+
+ private ClassPool pool;
+
+ public SampleLoader() throws NotFoundException {
+ pool = new ClassPool();
+ pool.insertClassPath("./class"); // <em>MyApp.class must be there.</em>
+ }
+
+ /* Finds a specified class.
+ * The bytecode for that class can be modified.
+ */
+ protected Class findClass(String name) throws ClassNotFoundException {
+ try {
+ CtClass cc = pool.get(name);
+ // <em>modify the CtClass object here</em>
+ byte[] b = cc.toBytecode();
+ return defineClass(name, b, 0, b.length);
+ } catch (NotFoundException e) {
+ throw new ClassNotFoundException();
+ } catch (IOException e) {
+ throw new ClassNotFoundException();
+ } catch (CannotCompileException e) {
+ throw new ClassNotFoundException();
+ }
+ }
+}</pre></ul>
+
+<p>The class <code>MyApp</code> is an application program.
+To execute this program, first put the class file under the
+<code>./class</code> directory, which must <em>not</em> be included
+in the class search path. Otherwise, <code>MyApp.class</code> would
+be loaded by the default system class loader, which is the parent
+loader of <code>SampleLoader</code>.
+The directory name <code>./class</code> is specified by
+<code>insertClassPath()</code> in the constructor.
+You can choose a different name instead of <code>./class</code> if you want.
+Then do as follows:
+
+<ul><code>% java SampleLoader</code></ul>
+
+<p>The class loader loads the class <code>MyApp</code>
+(<code>./class/MyApp.class</code>) and calls
+<code>MyApp.main()</code> with the command line parameters.
+
+<p>This is the simplest way of using Javassist. However, if you write
+a more complex class loader, you may need detailed knowledge of
+Java's class loading mechanism. For example, the program above puts the
+<code>MyApp</code> class in a name space separated from the name space
+that the class <code>SampleLoader</code> belongs to because the two
+classes are loaded by different class loaders.
+Hence, the
+<code>MyApp</code> class cannot directly access the class
+<code>SampleLoader</code>.
+
+<p><br>
+
+<h3>3.5 Modifying a system class</h3>
+
+<p>The system classes like <code>java.lang.String</code> cannot be
+loaded by a class loader other than the system class loader.
+Therefore, <code>SampleLoader</code> or <code>javassist.Loader</code>
+shown above cannot modify the system classes at loading time.
+
+<p>If your application needs to do that, the system classes must be
+<em>statically</em> modified. For example, the following program
+adds a new field <code>hiddenValue</code> to <code>java.lang.String</code>:
+
+<ul><pre>ClassPool pool = ClassPool.getDefault();
+CtClass cc = pool.get("java.lang.String");
+cc.addField(new CtField(CtClass.intType, "hiddenValue", cc));
+cc.writeFile(".");</pre></ul>
+
+<p>This program produces a file <code>"./java/lang/String.class"</code>.
+
+<p>To run your program <code>MyApp</code>
+with this modified <code>String</code> class, do as follows:
+
+<ul><pre>
+% java -Xbootclasspath/p:. MyApp <i>arg1</i> <i>arg2</i>...
+</pre></ul>
+
+<p>Suppose that the definition of <code>MyApp</code> is as follows:
+
+<ul><pre>public class MyApp {
+ public static void main(String[] args) throws Exception {
+ System.out.println(String.class.getField("hiddenValue").getName());
+ }
+}</pre></ul>
+
+<p>If the modified <code>String</code> class is correctly loaded,
+<code>MyApp</code> prints <code>hiddenValue</code>.
+
+<p><i>Note: Applications that use this technique for the purpose of
+overriding a system class in <code>rt.jar</code> should not be
+deployed as doing so would contravene the Java 2 Runtime Environment
+binary code license.</i>
+
+<p><br>
+
+<a name="hotswap">
+<h3>3.6 Reloading a class at runtime</h3></a>
+
+<p>If the JVM is launched with the JPDA (Java Platform Debugger
+Architecture) enabled, a class is dynamically reloadable. After the
+JVM loads a class, the old version of the class definition can be
+unloaded and a new one can be reloaded again. That is, the definition
+of that class can be dynamically modified during runtime. However,
+the new class definition must be somewhat compatible to the old one.
+<em>The JVM does not allow schema changes between the two versions.</em>
+They have the same set of methods and fields.
+
+<p>Javassist provides a convenient class for reloading a class at runtime.
+For more information, see the API documentation of
+<code>javassist.tools.HotSwapper</code>.
+
+<p><br>
+
+<a href="tutorial2.html">Next page</a>
+
+<hr>
+Java(TM) is a trademark of Sun Microsystems, Inc.<br>
+Copyright (C) 2000-2010 by Shigeru Chiba, All rights reserved.
+</body>
+</html>
diff --git a/tutorial/tutorial2.html b/tutorial/tutorial2.html
new file mode 100644
index 0000000..445fc90
--- /dev/null
+++ b/tutorial/tutorial2.html
@@ -0,0 +1,1627 @@
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <title>Javassist Tutorial</title>
+ <link rel="stylesheet" type="text/css" href="brown.css">
+</head>
+
+<body>
+
+<div align="right">Getting Started with Javassist</div>
+
+<div align="left"><a href="tutorial.html">Previous page</a></div>
+<div align="right"><a href="tutorial3.html">Next page</a></div>
+
+<p>
+<a href="#intro">4. Introspection and customization</a>
+<ul>
+<li><a href="#before">Inserting source text at the beginning/end of a method body</a>
+<br><li><a href="#alter">Altering a method body</a>
+<br><li><a href="#add">Adding a new method or field</a>
+<br><li><a href="#runtime">Runtime support classes</a>
+<br><li><a href="#annotation">Annotations</a>
+<br><li><a href="#import">Import</a>
+<br><li><a href="#limit">Limitations</a>
+</ul>
+
+<p><br>
+
+<a name="intro">
+<h2>4. Introspection and customization</h2>
+
+<p><code>CtClass</code> provides methods for introspection. The
+introspective ability of Javassist is compatible with that of
+the Java reflection API. <code>CtClass</code> provides
+<code>getName()</code>, <code>getSuperclass()</code>,
+<code>getMethods()</code>, and so on.
+<code>CtClass</code> also provides methods for modifying a class
+definition. It allows to add a new field, constructor, and method.
+Instrumenting a method body is also possible.
+
+<p>
+Methods are represented by <code>CtMethod</code> objects.
+<code>CtMethod</code> provides several methods for modifying
+the definition of the method. Note that if a method is inherited
+from a super class, then
+the same <code>CtMethod</code> object
+that represents the inherited method represents the method declared
+in that super class.
+A <code>CtMethod</code> object corresponds to every method declaration.
+
+<p>
+For example, if class <code>Point</code> declares method <code>move()</code>
+and a subclass <code>ColorPoint</code> of <code>Point</code> does
+not override <code>move()</code>, the two <code>move()</code> methods
+declared in <code>Point</code> and inherited in <code>ColorPoint</code>
+are represented by the identical <code>CtMethod</code> object.
+If the method definition represented by this
+<code>CtMethod</code> object is modified, the modification is
+reflected on both the methods.
+If you want to modify only the <code>move()</code> method in
+<code>ColorPoint</code>, you first have to add to <code>ColorPoint</code>
+a copy of the <code>CtMethod</code> object representing <code>move()</code>
+in <code>Point</code>. A copy of the the <code>CtMethod</code> object
+can be obtained by <code>CtNewMethod.copy()</code>.
+
+
+<p><hr width="40%">
+
+<ul>
+Javassist does not allow to remove a method or field, but it allows
+to change the name. So if a method is not necessary any more, it should be
+renamed and changed to be a private method by calling
+<code>setName()</code>
+and <code>setModifiers()</code> declared in <code>CtMethod</code>.
+
+<p>Javassist does not allow to add an extra parameter to an existing
+method, either. Instead of doing that, a new method receiving the
+extra parameter as well as the other parameters should be added to the
+same class. For example, if you want to add an extra <code>int</code>
+parameter <code>newZ</code> to a method:
+
+<ul><pre>void move(int newX, int newY) { x = newX; y = newY; }</pre></ul>
+
+<p>in a <code>Point</code> class, then you should add the following
+method to the <code>Point</code> class:
+
+<ul><pre>void move(int newX, int newY, int newZ) {
+ // do what you want with newZ.
+ move(newX, newY);
+}</pre></ul>
+
+</ul>
+
+<p><hr width="40%">
+
+<p>Javassist also provides low-level API for directly editing a raw
+class file. For example, <code>getClassFile()</code> in
+<code>CtClass</code> returns a <code>ClassFile</code> object
+representing a raw class file. <code>getMethodInfo()</code> in
+<code>CtMethod</code> returns a <code>MethodInfo</code> object
+representing a <code>method_info</code> structure included in a class
+file. The low-level API uses the vocabulary from the Java Virtual
+machine specification. The users must have the knowledge about class
+files and bytecode. For more details, the users should see the
+<a href="tutorial3.html#intro"><code>javassist.bytecode</code> package</a>.
+
+<p>The class files modified by Javassist requires the
+<code>javassist.runtime</code> package for runtime support
+only if some special identifiers starting with <code>$</code>
+are used. Those special identifiers are described below.
+The class files modified without those special identifiers
+do not need the <code>javassist.runtime</code> package or any
+other Javassist packages at runtime.
+For more details, see the API documentation
+of the <code>javassist.runtime</code> package.
+
+<p><br>
+
+<a name="before">
+<h3>4.1 Inserting source text at the beginning/end of a method body</h3>
+
+<p><code>CtMethod</code> and <code>CtConstructor</code> provide
+methods <code>insertBefore()</code>, <code>insertAfter()</code>, and
+<code>addCatch()</code>. They are used for inserting a code fragment
+into the body of an existing method. The users can specify those code
+fragments with <em>source text</em> written in Java.
+Javassist includes a simple Java compiler for processing source
+text. It receives source text
+written in Java and compiles it into Java bytecode, which will be
+<em>inlined</em> into a method body.
+
+<p>
+Inserting a code fragment at the position specified by a line number
+is also possible
+(if the line number table is contained in the class file).
+<code>insertAt()</code> in <code>CtMethod</code> and
+<code>CtConstructor</code> takes source text and a line number in the source
+file of the original class definition.
+It compiles the source text and inserts the compiled code at the line number.
+
+<p>The methods <code>insertBefore()</code>, <code>insertAfter()</code>,
+<code>addCatch()</code>, and <code>insertAt()</code>
+receive a <code>String</code> object representing
+a statement or a block. A statement is a single control structure like
+<code>if</code> and <code>while</code> or an expression ending with
+a semi colon (<code>;</code>). A block is a set of
+statements surrounded with braces <code>{}</code>.
+Hence each of the following lines is an example of valid statement or block:
+
+<ul><pre>System.out.println("Hello");
+{ System.out.println("Hello"); }
+if (i < 0) { i = -i; }
+</pre></ul>
+
+<p>The statement and the block can refer to fields and methods.
+They can also refer to the parameters
+to the method that they are inserted into
+if that method was compiled with the -g option
+(to include a local variable attribute in the class file).
+Otherwise, they must access the method parameters through the special
+variables <code>$0</code>, <code>$1</code>, <code>$2</code>, ... described
+below.
+<em>Accessing local variables declared in the method is not allowed</em>
+although declaring a new local variable in the block is allowed.
+However, <code>insertAt()</code> allows the statement and the block
+to access local variables
+if these variables are available at the specified line number
+and the target method was compiled with the -g option.
+
+
+<!--
+<p><center><table border=8 cellspacing=0 bordercolor="#cfcfcf">
+<tr><td bgcolor="#cfcfcf">
+<b>Tip:</b>
+<br>    Local variables are not accessible.  
+</td></tr>
+</table></center>
+-->
+
+<p>The <code>String</code> object passed to the methods
+<code>insertBefore()</code>, <code>insertAfter()</code>,
+<code>addCatch()</code>, and <code>insertAt()</code> are compiled by
+the compiler included in Javassist.
+Since the compiler supports language extensions,
+several identifiers starting with <code>$</code>
+have special meaning:
+
+<ul><table border=0>
+<tr>
+<td><code>$0</code>, <code>$1</code>, <code>$2</code>, ...    </td>
+<td><code>this</code> and actual parameters</td>
+</tr>
+
+<tr>
+<td><code>$args</code></td>
+<td>An array of parameters.
+The type of <code>$args</code> is <code>Object[]</code>.
+</td>
+</tr>
+
+<tr>
+<td><code>$$</code></td>
+<td rowspan=2>All actual parameters.<br>
+For example, <code>m($$)</code> is equivalent to
+<code>m($1,$2,</code>...<code>)</code></td>
+</tr>
+
+<tr><td> </td></tr>
+
+<tr>
+<td><code>$cflow(</code>...<code>)</code></td>
+<td><code>cflow</code> variable</td>
+</tr>
+
+<tr>
+<td><code>$r</code></td>
+<td>The result type. It is used in a cast expression.</td>
+</tr>
+
+<tr>
+<td><code>$w</code></td>
+<td>The wrapper type. It is used in a cast expression.</td>
+</tr>
+
+<tr>
+<td><code>$_</code></td>
+<td>The resulting value</td>
+</tr>
+
+<tr>
+<td><code>$sig</code></td>
+<td>An array of <code>java.lang.Class</code> objects representing
+the formal parameter types.
+</td>
+</tr>
+
+<tr>
+<td><code>$type</code></td>
+<td>A <code>java.lang.Class</code> object representing
+the formal result type.</td>
+</tr>
+
+<tr>
+<td><code>$class</code></td>
+<td>A <code>java.lang.Class</code> object representing
+the class currently edited.</td>
+</tr>
+
+</table>
+</ul>
+
+<h4>$0, $1, $2, ...</h4>
+
+<p>The parameters passed to the target method
+are accessible with
+<code>$1</code>, <code>$2</code>, ... instead of
+the original parameter names.
+<code>$1</code> represents the
+first parameter, <code>$2</code> represents the second parameter, and
+so on. The types of those variables are identical to the parameter
+types.
+<code>$0</code> is
+equivalent to <code>this</code>. If the method is static,
+<code>$0</code> is not available.
+
+<p>These variables are used as following. Suppose that a class
+<code>Point</code>:
+
+<pre><ul>class Point {
+ int x, y;
+ void move(int dx, int dy) { x += dx; y += dy; }
+}
+</ul></pre>
+
+<p>To print the values of <code>dx</code> and <code>dy</code>
+whenever the method <code>move()</code> is called, execute this
+program:
+
+<ul><pre>ClassPool pool = ClassPool.getDefault();
+CtClass cc = pool.get("Point");
+CtMethod m = cc.getDeclaredMethod("move");
+m.insertBefore("{ System.out.println($1); System.out.println($2); }");
+cc.writeFile();
+</pre></ul>
+
+<p>Note that the source text passed to <code>insertBefore()</code> is
+surrounded with braces <code>{}</code>.
+<code>insertBefore()</code> accepts only a single statement or a block
+surrounded with braces.
+
+<p>The definition of the class <code>Point</code> after the
+modification is like this:
+
+<pre><ul>class Point {
+ int x, y;
+ void move(int dx, int dy) {
+ { System.out.println(dx); System.out.println(dy); }
+ x += dx; y += dy;
+ }
+}
+</ul></pre>
+
+<p><code>$1</code> and <code>$2</code> are replaced with
+<code>dx</code> and <code>dy</code>, respectively.
+
+<p><code>$1</code>, <code>$2</code>, <code>$3</code> ... are
+updatable. If a new value is assigend to one of those variables,
+then the value of the parameter represented by that variable is
+also updated.
+
+
+<h4>$args</h4>
+
+<p>The variable <code>$args</code> represents an array of all the
+parameters. The type of that variable is an array of class
+<code>Object</code>. If a parameter type is a primitive type such as
+<code>int</code>, then the parameter value is converted into a wrapper
+object such as <code>java.lang.Integer</code> to store in
+<code>$args</code>. Thus, <code>$args[0]</code> is equivalent to
+<code>$1</code> unless the type of the first parameter is a primitive
+type. Note that <code>$args[0]</code> is not equivalent to
+<code>$0</code>; <code>$0</code> represents <code>this</code>.
+
+<p>If an array of <code>Object</code> is assigned to
+<code>$args</code>, then each element of that array is
+assigned to each parameter. If a parameter type is a primitive
+type, the type of the corresponding element must be a wrapper type.
+The value is converted from the wrapper type to the primitive type
+before it is assigned to the parameter.
+
+<h4>$$</h4>
+
+<p>The variable <code>$$</code> is abbreviation of a list of
+all the parameters separated by commas.
+For example, if the number of the parameters
+to method <code>move()</code> is three, then
+
+<ul><pre>move($$)</pre></ul>
+
+<p>is equivalent to this:
+
+<ul><pre>move($1, $2, $3)</pre></ul>
+
+<p>If <code>move()</code> does not take any parameters,
+then <code>move($$)</code> is
+equivalent to <code>move()</code>.
+
+<p><code>$$</code> can be used with another method.
+If you write an expression:
+
+<ul><pre>exMove($$, context)</pre></ul>
+
+<p>then this expression is equivalent to:
+
+<ul><pre>exMove($1, $2, $3, context)</pre></ul>
+
+<p>Note that <code>$$</code> enables generic notation of method call
+with respect to the number of parameters.
+It is typically used with <code>$proceed</code> shown later.
+
+<h4>$cflow</h4>
+
+<p><code>$cflow</code> means "control flow".
+This read-only variable returns the depth of the recursive calls
+to a specific method.
+
+<p>Suppose that the method shown below is represented by a
+<code>CtMethod</code> object <code>cm</code>:
+
+<ul><pre>int fact(int n) {
+ if (n <= 1)
+ return n;
+ else
+ return n * fact(n - 1);
+}</pre></ul>
+
+<p>To use <code>$cflow</code>, first declare that <code>$cflow</code>
+is used for monitoring calls to the method <code>fact()</code>:
+
+<ul><pre>CtMethod cm = ...;
+cm.useCflow("fact");</pre></ul>
+
+<p>The parameter to <code>useCflow()</code> is the identifier of the
+declared <code>$cflow</code> variable. Any valid Java name can be
+used as the identifier. Since the identifier can also include
+<code>.</code> (dot), for example, <code>"my.Test.fact"</code>
+is a valid identifier.
+
+<p>Then, <code>$cflow(fact)</code> represents the depth of the
+recursive calls to the method specified by <code>cm</code>. The value
+of <code>$cflow(fact)</code> is 0 (zero) when the method is
+first called whereas it is 1 when the method is recursively called
+within the method. For example,
+
+<ul><pre>
+cm.insertBefore("if ($cflow(fact) == 0)"
+ + " System.out.println(\"fact \" + $1);");
+</pre></ul>
+
+<p>translates the method <code>fact()</code> so that it shows the
+parameter. Since the value of <code>$cflow(fact)</code> is checked,
+the method <code>fact()</code> does not show the parameter if it is
+recursively called within <code>fact()</code>.
+
+<p>The value of <code>$cflow</code> is the number of stack frames
+associated with the specified method <code>cm</code>
+under the current topmost
+stack frame for the current thread. <code>$cflow</code> is also
+accessible within a method different from the specified method
+<code>cm</code>.
+
+<h4>$r</h4>
+
+<p><code>$r</code> represents the result type (return type) of the method.
+It must be used as the cast type in a cast expression.
+For example, this is a typical use:
+
+<ul><pre>Object result = ... ;
+$_ = ($r)result;</pre></ul>
+
+<p>If the result type is a primitive type, then <code>($r)</code>
+follows special semantics. First, if the operand type of the cast
+expression is a primitive type, <code>($r)</code> works as a normal
+cast operator to the result type.
+On the other hand, if the operand type is a wrapper type,
+<code>($r)</code> converts from the wrapper type to the result type.
+For example, if the result type is <code>int</code>, then
+<code>($r)</code> converts from <code>java.lang.Integer</code> to
+<code>int</code>.
+
+<p>If the result type is <code>void</code>, then
+<code>($r)</code> does not convert a type; it does nothing.
+However, if the operand is a call to a <code>void</code> method,
+then <code>($r)</code> results in <code>null</code>. For example,
+if the result type is <code>void</code> and
+<code>foo()</code> is a <code>void</code> method, then
+
+<ul><pre>$_ = ($r)foo();</pre></ul>
+
+<p>is a valid statement.
+
+<p>The cast operator <code>($r)</code> is also useful in a
+<code>return</code> statement. Even if the result type is
+<code>void</code>, the following <code>return</code> statement is valid:
+
+<ul><pre>return ($r)result;</pre></ul>
+
+<p>Here, <code>result</code> is some local variable.
+Since <code>($r)</code> is specified, the resulting value is
+discarded.
+This <code>return</code> statement is regarded as the equivalent
+of the <code>return</code> statement without a resulting value:
+
+<ul><pre>return;</pre></ul>
+
+<h4>$w</h4>
+
+<p><code>$w</code> represents a wrapper type.
+It must be used as the cast type in a cast expression.
+<code>($w)</code> converts from a primitive type to the corresponding
+wrapper type.
+
+The following code is an example:
+
+<ul><pre>Integer i = ($w)5;</pre></ul>
+
+<p>The selected wrapper type depends on the type of the expression
+following <code>($w)</code>. If the type of the expression is
+<code>double</code>, then the wrapper type is <code>java.lang.Double</code>.
+
+<p>If the type of the expression following <code>($w)</code> is not
+a primitive type, then <code>($w)</code> does nothing.
+
+<h4>$_</h4>
+
+<p><code>insertAfter()</code> in <code>CtMethod</code> and
+<code>CtConstructor</code> inserts the
+compiled code at the end of the method. In the statement given to
+<code>insertAfter()</code>, not only the variables shown above such as
+<code>$0</code>, <code>$1</code>, ... but also <code>$_</code> is
+available.
+
+<p>The variable <code>$_</code> represents the resulting value of the
+method. The type of that variable is the type of the result type (the
+return type) of the method. If the result type is <code>void</code>,
+then the type of <code>$_</code> is <code>Object</code> and the value
+of <code>$_</code> is <code>null</code>.
+
+<p>Although the compiled code inserted by <code>insertAfter()</code>
+is executed just before the control normally returns from the method,
+it can be also executed when an exception is thrown from the method.
+To execute it when an exception is thrown, the second parameter
+<code>asFinally</code> to <code>insertAfter()</code> must be
+<code>true</code>.
+
+<p>If an exception is thrown, the compiled code inserted by
+<code>insertAfter()</code> is executed as a <code>finally</code>
+clause. The value of <code>$_</code> is <code>0</code> or
+<code>null</code> in the compiled code. After the execution of the
+compiled code terminates, the exception originally thrown is re-thrown
+to the caller. Note that the value of <code>$_</code> is never thrown
+to the caller; it is rather discarded.
+
+<h4>$sig</h4>
+
+<p>The value of <code>$sig</code> is an array of
+<code>java.lang.Class</code> objects that represent the formal
+parameter types in declaration order.
+
+<h4>$type</h4>
+
+<p>The value of <code>$type</code> is an <code>java.lang.Class</code>
+object representing the formal type of the result value. This
+variable refers to <code>Void.class</code> if this is a constructor.
+
+<h4>$class</h4>
+
+<p>The value of <code>$class</code> is an <code>java.lang.Class</code>
+object representing the class in which the edited method is declared.
+This represents the type of <code>$0</code>.
+
+<h4>addCatch()</h4>
+
+<p><code>addCatch()</code> inserts a code fragment into a method body
+so that the code fragment is executed when the method body throws
+an exception and the control returns to the caller. In the source
+text representing the inserted code fragment, the exception value
+is referred to with the special variable <code>$e</code>.
+
+<p>For example, this program:
+
+<ul><pre>
+CtMethod m = ...;
+CtClass etype = ClassPool.getDefault().get("java.io.IOException");
+m.addCatch("{ System.out.println($e); throw $e; }", etype);
+</pre></ul>
+
+<p>translates the method body represented by <code>m</code> into
+something like this:
+
+<ul><pre>
+try {
+ <font face="serif"><em>the original method body</em></font>
+}
+catch (java.io.IOException e) {
+ System.out.println(e);
+ throw e;
+}
+</pre></ul>
+
+<p>Note that the inserted code fragment must end with a
+<code>throw</code> or <code>return</code> statement.
+
+<p><br>
+
+<a name="alter">
+<h3>4.2 Altering a method body</h3>
+
+<p><code>CtMethod</code> and <code>CtConstructor</code> provide
+<code>setBody()</code> for substituting a whole
+method body. They compile the given source text into Java bytecode
+and substitutes it for the original method body. If the given source
+text is <code>null</code>, the substituted body includes only a
+<code>return</code> statement, which returns zero or null unless the
+result type is <code>void</code>.
+
+<p>In the source text given to <code>setBody()</code>, the identifiers
+starting with <code>$</code> have special meaning
+
+<ul><table border=0>
+<tr>
+<td><code>$0</code>, <code>$1</code>, <code>$2</code>, ...    </td>
+<td><code>this</code> and actual parameters</td>
+</tr>
+
+<tr>
+<td><code>$args</code></td>
+<td>An array of parameters.
+The type of <code>$args</code> is <code>Object[]</code>.
+</td>
+</tr>
+
+<tr>
+<td><code>$$</code></td>
+<td>All actual parameters.<br>
+</tr>
+
+<tr>
+<td><code>$cflow(</code>...<code>)</code></td>
+<td><code>cflow</code> variable</td>
+</tr>
+
+<tr>
+<td><code>$r</code></td>
+<td>The result type. It is used in a cast expression.</td>
+</tr>
+
+<tr>
+<td><code>$w</code></td>
+<td>The wrapper type. It is used in a cast expression.</td>
+</tr>
+
+<tr>
+<td><code>$sig</code></td>
+<td>An array of <code>java.lang.Class</code> objects representing
+the formal parameter types.
+</td>
+</tr>
+
+<tr>
+<td><code>$type</code></td>
+<td>A <code>java.lang.Class</code> object representing
+the formal result type.</td>
+</tr>
+
+<tr>
+<td><code>$class</code></td>
+<td rowspan=2>A <code>java.lang.Class</code> object representing
+the class that declares the method<br>
+currently edited (the type of $0).</td>
+</tr>
+
+<tr><td> </td></tr>
+
+</table>
+</ul>
+
+Note that <code>$_</code> is not available.
+
+<h4>Substituting source text for an existing expression</h4>
+
+<p>Javassist allows modifying only an expression included in a method body.
+<code>javassist.expr.ExprEditor</code> is a class
+for replacing an expression in a method body.
+The users can define a subclass of <code>ExprEditor</code>
+to specify how an expression is modified.
+
+<p>To run an <code>ExprEditor</code> object, the users must
+call <code>instrument()</code> in <code>CtMethod</code> or
+<code>CtClass</code>.
+
+For example,
+
+<ul><pre>
+CtMethod cm = ... ;
+cm.instrument(
+ new ExprEditor() {
+ public void edit(MethodCall m)
+ throws CannotCompileException
+ {
+ if (m.getClassName().equals("Point")
+ && m.getMethodName().equals("move"))
+ m.replace("{ $1 = 0; $_ = $proceed($$); }");
+ }
+ });
+</pre></ul>
+
+<p>searches the method body represented by <code>cm</code> and
+replaces all calls to <code>move()</code> in class <code>Point</code>
+with a block:
+
+<ul><pre>{ $1 = 0; $_ = $proceed($$); }
+</pre></ul>
+
+<p>so that the first parameter to <code>move()</code> is always 0.
+Note that the substituted code is not an expression but
+a statement or a block. It cannot be or contain a try-catch statement.
+
+<p>The method <code>instrument()</code> searches a method body.
+If it finds an expression such as a method call, field access, and object
+creation, then it calls <code>edit()</code> on the given
+<code>ExprEditor</code> object. The parameter to <code>edit()</code>
+is an object representing the found expression. The <code>edit()</code>
+method can inspect and replace the expression through that object.
+
+<p>Calling <code>replace()</code> on the parameter to <code>edit()</code>
+substitutes the given statement or block for the expression. If the given
+block is an empty block, that is, if <code>replace("{}")</code>
+is executed, then the expression is removed from the method body.
+
+If you want to insert a statement (or a block) before/after the
+expression, a block like the following should be passed to
+<code>replace()</code>:
+
+<ul><pre>
+{ <em>before-statements;</em>
+ $_ = $proceed($$);
+ <em>after-statements;</em> }
+</pre></ul>
+
+<p>whichever the expression is either a method call, field access,
+object creation, or others. The second statement could be:
+
+<ul><pre>$_ = $proceed();</pre></ul>
+
+<p>if the expression is read access, or
+
+<ul><pre>$proceed($$);</pre></ul>
+
+<p>if the expression is write access.
+
+<p>Local variables available in the target expression is
+also available in the source text passed to <code>replace()</code>
+if the method searched by <code>instrument()</code> was compiled
+with the -g option (the class file includes a local variable
+attribute).
+
+<h4>javassist.expr.MethodCall</h4>
+
+<p>A <code>MethodCall</code> object represents a method call.
+The method <code>replace()</code> in
+<code>MethodCall</code> substitutes a statement or
+a block for the method call.
+It receives source text representing the substitued statement or
+block, in which the identifiers starting with <code>$</code>
+have special meaning as in the source text passed to
+<code>insertBefore()</code>.
+
+<ul><table border=0>
+<tr>
+<td><code>$0</code></td>
+<td rowspan=3>
+The target object of the method call.<br>
+This is not equivalent to <code>this</code>, which represents
+the caller-side <code>this</code> object.<br>
+<code>$0</code> is <code>null</code> if the method is static.
+</td>
+</tr>
+
+<tr><td> </td></tr>
+
+<tr><td> </td></tr>
+
+<tr>
+<td><code>$1</code>, <code>$2</code>, ...    </td>
+<td>
+The parameters of the method call.
+</td>
+</tr>
+
+<tr><td>
+<code>$_</code></td>
+<td>The resulting value of the method call.</td>
+</tr>
+
+<tr><td><code>$r</code></td>
+<td>The result type of the method call.</td>
+</tr>
+
+<tr><td><code>$class</code>    </td>
+<td>A <code>java.lang.Class</code> object representing
+the class declaring the method.
+</td>
+</tr>
+
+<tr><td><code>$sig</code>    </td>
+<td>An array of <code>java.lang.Class</code> objects representing
+the formal parameter types.</td>
+</tr>
+
+<tr><td><code>$type</code>    </td>
+<td>A <code>java.lang.Class</code> object representing
+the formal result type.</td>
+</tr>
+
+<tr><td><code>$proceed</code>    </td>
+<td>The name of the method originally called
+in the expression.</td>
+</tr>
+
+</table>
+</ul>
+
+<p>Here the method call means the one represented by the
+<code>MethodCall</code> object.
+
+<p>The other identifiers such as <code>$w</code>,
+<code>$args</code> and <code>$$</code>
+are also available.
+
+<p>Unless the result type of the method call is <code>void</code>,
+a value must be assigned to
+<code>$_</code> in the source text and the type of <code>$_</code>
+is the result type.
+If the result type is <code>void</code>, the type of <code>$_</code>
+is <code>Object</code> and the value assigned to <code>$_</code>
+is ignored.
+
+<p><code>$proceed</code> is not a <code>String</code> value but special
+syntax. It must be followed by an argument list surrounded by parentheses
+<code>( )</code>.
+
+<h4>javassist.expr.ConstructorCall</h4>
+
+<p>A <code>ConstructorCall</code> object represents a constructor call
+such as <code>this()</code> and <code>super</code> included in a constructor
+body.
+The method <code>replace()</code> in
+<code>ConstructorCall</code> substitutes a statement or
+a block for the constructor call.
+It receives source text representing the substituted statement or
+block, in which the identifiers starting with <code>$</code>
+have special meaning as in the source text passed to
+<code>insertBefore()</code>.
+
+<ul><table border=0>
+<tr>
+<td><code>$0</code></td>
+<td>
+The target object of the constructor call.
+This is equivalent to <code>this</code>.
+</td>
+</tr>
+
+<tr>
+<td><code>$1</code>, <code>$2</code>, ...    </td>
+<td>
+The parameters of the constructor call.
+</td>
+</tr>
+
+<tr><td><code>$class</code>    </td>
+<td>A <code>java.lang.Class</code> object representing
+the class declaring the constructor.
+</td>
+</tr>
+
+<tr><td><code>$sig</code>    </td>
+<td>An array of <code>java.lang.Class</code> objects representing
+the formal parameter types.</td>
+</tr>
+
+<tr><td><code>$proceed</code>    </td>
+<td>The name of the constructor originally called
+in the expression.</td>
+</tr>
+
+</table>
+</ul>
+
+<p>Here the constructor call means the one represented by the
+<code>ConstructorCall</code> object.
+
+<p>The other identifiers such as <code>$w</code>,
+<code>$args</code> and <code>$$</code>
+are also available.
+
+<p>Since any constructor must call either a constructor of the super
+class or another constructor of the same class,
+the substituted statement must include a constructor call,
+normally a call to <code>$proceed()</code>.
+
+<p><code>$proceed</code> is not a <code>String</code> value but special
+syntax. It must be followed by an argument list surrounded by parentheses
+<code>( )</code>.
+
+<h4>javassist.expr.FieldAccess</h4>
+
+<p>A <code>FieldAccess</code> object represents field access.
+The method <code>edit()</code> in <code>ExprEditor</code>
+receives this object if field access is found.
+The method <code>replace()</code> in
+<code>FieldAccess</code> receives
+source text representing the substitued statement or
+block for the field access.
+
+<p>
+In the source text, the identifiers starting with <code>$</code>
+have special meaning:
+
+<ul><table border=0>
+<tr>
+<td><code>$0</code></td>
+<td rowspan=3>
+The object containing the field accessed by the expression.
+This is not equivalent to <code>this</code>.<br>
+<code>this</code> represents the object that the method including the
+expression is invoked on.<br>
+<code>$0</code> is <code>null</code> if the field is static.
+</td>
+</tr>
+
+<tr><td> </td></tr>
+
+<tr><td> </td></tr>
+
+<tr>
+<td><code>$1</code></td>
+<td rowspan=2>
+The value that would be stored in the field
+if the expression is write access.
+<br>Otherwise, <code>$1</code> is not available.
+</td>
+</tr>
+
+<tr><td> </td></tr>
+
+<tr>
+<td><code>$_</code></td>
+<td rowspan=2>
+The resulting value of the field access
+if the expression is read access.
+<br>Otherwise, the value stored in <code>$_</code> is discarded.
+</td>
+</tr>
+
+<tr><td> </td></tr>
+<tr>
+<td><code>$r</code></td>
+<td rowspan=2>
+The type of the field if the expression is read access.
+<br>Otherwise, <code>$r</code> is <code>void</code>.
+</td>
+</tr>
+
+<tr><td> </td></tr>
+
+<tr><td><code>$class</code>    </td>
+<td>A <code>java.lang.Class</code> object representing
+the class declaring the field.
+</td></tr>
+
+<tr><td><code>$type</code></td>
+<td>A <code>java.lang.Class</code> object representing
+the field type.</td>
+</tr>
+
+<tr><td><code>$proceed</code>    </td>
+<td>The name of a virtual method executing the original
+field access.
+.</td>
+</tr>
+
+</table>
+</ul>
+
+<p>The other identifiers such as <code>$w</code>,
+<code>$args</code> and <code>$$</code>
+are also available.
+
+<p>If the expression is read access, a value must be assigned to
+<code>$_</code> in the source text. The type of <code>$_</code>
+is the type of the field.
+
+<h4>javassist.expr.NewExpr</h4>
+
+<p>A <code>NewExpr</code> object represents object creation
+with the <code>new</code> operator (not including array creation).
+The method <code>edit()</code> in <code>ExprEditor</code>
+receives this object if object creation is found.
+The method <code>replace()</code> in
+<code>NewExpr</code> receives
+source text representing the substitued statement or
+block for the object creation.
+
+<p>
+In the source text, the identifiers starting with <code>$</code>
+have special meaning:
+
+<ul><table border=0>
+
+<tr>
+<td><code>$0</code></td>
+<td>
+<code>null</code>.
+</td>
+</tr>
+
+<tr>
+<td><code>$1</code>, <code>$2</code>, ...    </td>
+<td>
+The parameters to the constructor.
+</td>
+</tr>
+
+<tr>
+<td><code>$_</code></td>
+<td rowspan=2>
+The resulting value of the object creation.
+<br>A newly created object must be stored in this variable.
+</td>
+</tr>
+
+<tr><td> </td></tr>
+
+<tr>
+<td><code>$r</code></td>
+<td>
+The type of the created object.
+</td>
+</tr>
+
+<tr><td><code>$sig</code>    </td>
+<td>An array of <code>java.lang.Class</code> objects representing
+the formal parameter types.</td>
+</tr>
+
+<tr><td><code>$type</code>    </td>
+<td>A <code>java.lang.Class</code> object representing
+the class of the created object.
+</td></tr>
+
+<tr><td><code>$proceed</code>    </td>
+<td>The name of a virtual method executing the original
+object creation.
+.</td>
+</tr>
+
+</table>
+</ul>
+
+<p>The other identifiers such as <code>$w</code>,
+<code>$args</code> and <code>$$</code>
+are also available.
+
+<h4>javassist.expr.NewArray</h4>
+
+<p>A <code>NewArray</code> object represents array creation
+with the <code>new</code> operator.
+The method <code>edit()</code> in <code>ExprEditor</code>
+receives this object if array creation is found.
+The method <code>replace()</code> in
+<code>NewArray</code> receives
+source text representing the substitued statement or
+block for the array creation.
+
+<p>
+In the source text, the identifiers starting with <code>$</code>
+have special meaning:
+
+<ul><table border=0>
+
+<tr>
+<td><code>$0</code></td>
+<td>
+<code>null</code>.
+</td>
+</tr>
+
+<tr>
+<td><code>$1</code>, <code>$2</code>, ...    </td>
+<td>
+The size of each dimension.
+</td>
+</tr>
+
+<tr>
+<td><code>$_</code></td>
+<td rowspan=2>
+The resulting value of the array creation.
+<br>A newly created array must be stored in this variable.
+</td>
+</tr>
+
+<tr><td> </td></tr>
+
+<tr>
+<td><code>$r</code></td>
+<td>
+The type of the created array.
+</td>
+</tr>
+
+<tr><td><code>$type</code>    </td>
+<td>A <code>java.lang.Class</code> object representing
+the class of the created array.
+</td></tr>
+
+<tr><td><code>$proceed</code>    </td>
+<td>The name of a virtual method executing the original
+array creation.
+.</td>
+</tr>
+
+</table>
+</ul>
+
+<p>The other identifiers such as <code>$w</code>,
+<code>$args</code> and <code>$$</code>
+are also available.
+
+<p>For example, if the array creation is the following expression,
+
+<ul><pre>
+String[][] s = new String[3][4];
+</pre></ul>
+
+then the value of $1 and $2 are 3 and 4, respectively. $3 is not available.
+
+<p>If the array creation is the following expression,
+
+<ul><pre>
+String[][] s = new String[3][];
+</pre></ul>
+
+then the value of $1 is 3 but $2 is not available.
+
+<h4>javassist.expr.Instanceof</h4>
+
+<p>A <code>Instanceof</code> object represents an <code>instanceof</code>
+expression.
+The method <code>edit()</code> in <code>ExprEditor</code>
+receives this object if an instanceof expression is found.
+The method <code>replace()</code> in
+<code>Instanceof</code> receives
+source text representing the substitued statement or
+block for the expression.
+
+<p>
+In the source text, the identifiers starting with <code>$</code>
+have special meaning:
+
+<ul><table border=0>
+
+<tr>
+<td><code>$0</code></td>
+<td>
+<code>null</code>.
+</td>
+</tr>
+
+<tr>
+<td><code>$1</code></td>
+<td>
+The value on the left hand side of the original
+<code>instanceof</code> operator.
+</td>
+</tr>
+
+<tr>
+<td><code>$_</code></td>
+<td>
+The resulting value of the expression.
+The type of <code>$_</code> is <code>boolean</code>.
+</td>
+</tr>
+
+<tr>
+<td><code>$r</code></td>
+<td>
+The type on the right hand side of the <code>instanceof</code> operator.
+</td>
+</tr>
+
+<tr><td><code>$type</code></td>
+<td>A <code>java.lang.Class</code> object representing
+the type on the right hand side of the <code>instanceof</code> operator.
+</td>
+</tr>
+
+<tr><td><code>$proceed</code>    </td>
+<td rowspan=4>The name of a virtual method executing the original
+<code>instanceof</code> expression.
+<br>It takes one parameter (the type is <code>java.lang.Object</code>)
+and returns true
+<br>if the parameter value is an instance of the type on the right
+hand side of
+<br>the original <code>instanceof</code> operator.
+Otherwise, it returns false.
+</td>
+</tr>
+
+<tr><td> </td></tr>
+<tr><td> </td></tr>
+<tr><td> </td></tr>
+
+</table>
+</ul>
+
+<p>The other identifiers such as <code>$w</code>,
+<code>$args</code> and <code>$$</code>
+are also available.
+
+<h4>javassist.expr.Cast</h4>
+
+<p>A <code>Cast</code> object represents an expression for
+explicit type casting.
+The method <code>edit()</code> in <code>ExprEditor</code>
+receives this object if explicit type casting is found.
+The method <code>replace()</code> in
+<code>Cast</code> receives
+source text representing the substitued statement or
+block for the expression.
+
+<p>
+In the source text, the identifiers starting with <code>$</code>
+have special meaning:
+
+<ul><table border=0>
+
+<tr>
+<td><code>$0</code></td>
+<td>
+<code>null</code>.
+</td>
+</tr>
+
+<tr>
+<td><code>$1</code></td>
+<td>
+The value the type of which is explicitly cast.
+</td>
+</tr>
+
+<tr>
+<td><code>$_</code></td>
+<td rowspan=2>
+The resulting value of the expression.
+The type of <code>$_</code> is the same as the type
+<br>after the explicit casting, that is, the type surrounded
+by <code>( )</code>.
+</td>
+</tr>
+
+<tr><td> </td></tr>
+
+<tr>
+<td><code>$r</code></td>
+<td>the type after the explicit casting, or the type surrounded
+by <code>( )</code>.
+</td>
+</tr>
+
+<tr><td><code>$type</code></td>
+<td>A <code>java.lang.Class</code> object representing
+the same type as <code>$r</code>.
+</td>
+</tr>
+
+<tr><td><code>$proceed</code>    </td>
+<td rowspan=3>The name of a virtual method executing the original
+type casting.
+<br>It takes one parameter of the type <code>java.lang.Object</code>
+and returns it after
+<br>the explicit type casting specified by the original expression.
+
+</td>
+</tr>
+
+<tr><td> </td></tr>
+
+<tr><td> </td></tr>
+
+</table>
+</ul>
+
+<p>The other identifiers such as <code>$w</code>,
+<code>$args</code> and <code>$$</code>
+are also available.
+
+<h4>javassist.expr.Handler</h4>
+
+<p>A <code>Handler</code> object represents a <code>catch</code>
+clause of <code>try-catch</code> statement.
+The method <code>edit()</code> in <code>ExprEditor</code>
+receives this object if a <code>catch</code> is found.
+The method <code>insertBefore()</code> in
+<code>Handler</code> compiles the received
+source text and inserts it at the beginning of the <code>catch</code> clause.
+
+<p>
+In the source text, the identifiers starting with <code>$</code>
+have meaning:
+
+<ul><table border=0>
+
+<tr>
+<td><code>$1</code></td>
+<td>
+The exception object caught by the <code>catch</code> clause.
+</td>
+</tr>
+
+<tr>
+<td><code>$r</code></td>
+<td>the type of the exception caught by the <code>catch</code> clause.
+It is used in a cast expression.
+</td>
+</tr>
+
+<tr>
+<td><code>$w</code></td>
+<td>The wrapper type. It is used in a cast expression.
+</td>
+</tr>
+
+<tr><td><code>$type</code>    </td>
+<td rowspan=2>
+A <code>java.lang.Class</code> object representing
+<br>the type of the exception caught by the <code>catch</code> clause.
+</td>
+</tr>
+
+<tr><td> </td></tr>
+
+</table>
+</ul>
+
+<p>If a new exception object is assigned to <code>$1</code>,
+it is passed to the original <code>catch</code> clause as the caught
+exception.
+
+<p><br>
+
+<a name="add">
+<h3>4.3 Adding a new method or field</h3>
+
+<h4>Adding a method</h4>
+
+<p>Javassist allows the users to create a new method and constructor
+from scratch. <code>CtNewMethod</code>
+and <code>CtNewConstructor</code> provide several factory methods,
+which are static methods for creating <code>CtMethod</code> or
+<code>CtConstructor</code> objects.
+Especially, <code>make()</code> creates
+a <code>CtMethod</code> or <code>CtConstructor</code> object
+from the given source text.
+
+<p>For example, this program:
+
+<ul><pre>
+CtClass point = ClassPool.getDefault().get("Point");
+CtMethod m = CtNewMethod.make(
+ "public int xmove(int dx) { x += dx; }",
+ point);
+point.addMethod(m);
+</pre></ul>
+
+<p>adds a public method <code>xmove()</code> to class <code>Point</code>.
+In this example, <code>x</code> is a <code>int</code> field in
+the class <code>Point</code>.
+
+<p>The source text passed to <code>make()</code> can include the
+identifiers starting with <code>$</code> except <code>$_</code>
+as in <code>setBody()</code>.
+It can also include
+<code>$proceed</code> if the target object and the target method name
+are also given to <code>make()</code>. For example,
+
+<ul><pre>
+CtClass point = ClassPool.getDefault().get("Point");
+CtMethod m = CtNewMethod.make(
+ "public int ymove(int dy) { $proceed(0, dy); }",
+ point, "this", "move");
+</pre></ul>
+
+<p>this program creates a method <code>ymove()</code> defined below:
+
+<ul><pre>
+public int ymove(int dy) { this.move(0, dy); }
+</pre></ul>
+
+<p>Note that <code>$proceed</code> has been replaced with
+<code>this.move</code>.
+
+<p>Javassist provides another way to add a new method.
+You can first create an abstract method and later give it a method body:
+
+<ul><pre>
+CtClass cc = ... ;
+CtMethod m = new CtMethod(CtClass.intType, "move",
+ new CtClass[] { CtClass.intType }, cc);
+cc.addMethod(m);
+m.setBody("{ x += $1; }");
+cc.setModifiers(cc.getModifiers() & ~Modifier.ABSTRACT);
+</pre></ul>
+
+<p>Since Javassist makes a class abstract if an abstract method is
+added to the class, you have to explicitly change the class back to a
+non-abstract one after calling <code>setBody()</code>.
+
+
+<h4>Mutual recursive methods</h4>
+
+<p>Javassist cannot compile a method if it calls another method that
+has not been added to a class. (Javassist can compile a method that
+calls itself recursively.) To add mutual recursive methods to a class,
+you need a trick shown below. Suppose that you want to add methods
+<code>m()</code> and <code>n()</code> to a class represented
+by <code>cc</code>:
+
+<ul><pre>
+CtClass cc = ... ;
+CtMethod m = CtNewMethod.make("public abstract int m(int i);", cc);
+CtMethod n = CtNewMethod.make("public abstract int n(int i);", cc);
+cc.addMethod(m);
+cc.addMethod(n);
+m.setBody("{ return ($1 <= 0) ? 1 : (n($1 - 1) * $1); }");
+n.setBody("{ return m($1); }");
+cc.setModifiers(cc.getModifiers() & ~Modifier.ABSTRACT);
+</pre></ul>
+
+<p>You must first make two abstract methods and add them to the class.
+Then you can give the method bodies to these methods even if the method
+bodies include method calls to each other. Finally you must change the
+class to a not-abstract class since <code>addMethod()</code> automatically
+changes a class into an abstract one if an abstract method is added.
+
+<h4>Adding a field</h4>
+
+<p>Javassist also allows the users to create a new field.
+
+<ul><pre>
+CtClass point = ClassPool.getDefault().get("Point");
+CtField f = new CtField(CtClass.intType, "z", point);
+point.addField(f);
+</pre></ul>
+
+<p>This program adds a field named <code>z</code> to class
+<code>Point</code>.
+
+<p>If the initial value of the added field must be specified,
+the program shown above must be modified into:
+
+<ul><pre>
+CtClass point = ClassPool.getDefault().get("Point");
+CtField f = new CtField(CtClass.intType, "z", point);
+point.addField(f, "0"); <em>// initial value is 0.</em>
+</pre></ul>
+
+<p>Now, the method <code>addField()</code> receives the second parameter,
+which is the source text representing an expression computing the initial
+value. This source text can be any Java expression if the result type
+of the expression matches the type of the field. Note that an expression
+does not end with a semi colon (<code>;</code>).
+
+<p>Furthermore, the above code can be rewritten into the following
+simple code:
+
+<ul><pre>
+CtClass point = ClassPool.getDefault().get("Point");
+CtField f = CtField.make("public int z = 0;", point);
+point.addField(f);
+</pre></ul>
+
+<h4>Removing a member</h4>
+
+<p>To remove a field or a method, call <code>removeField()</code>
+or <code>removeMethod()</code> in <code>CtClass</code>. A
+<code>CtConstructor</code> can be removed by <code>removeConstructor()</code>
+in <code>CtClass</code>.
+
+<p><br>
+
+<a name="annotation">
+<h3>4.4 Annotations</h3>
+
+<p><code>CtClass</code>, <code>CtMethod</code>, <code>CtField</code>
+and <code>CtConstructor</code> provides a convenient method
+<code>getAnnotations()</code> for reading annotations.
+It returns an annotation-type object.
+
+<p>For example, suppose the following annotation:
+
+<ul><pre>
+public @interface Author {
+ String name();
+ int year();
+}
+</pre></ul>
+
+<p>This annotation is used as the following:
+
+<ul><pre>
+@Author(name="Chiba", year=2005)
+public class Point {
+ int x, y;
+}
+</pre></ul>
+
+<p>Then, the value of the annotation can be obtained by
+<code>getAnnotations()</code>.
+It returns an array containing
+annotation-type objects.
+
+<ul><pre>
+CtClass cc = ClassPool.getDefault().get("Point");
+Object[] all = cc.getAnnotations();
+Author a = (Author)all[0];
+String name = a.name();
+int year = a.year();
+System.out.println("name: " + name + ", year: " + year);
+</pre></ul>
+
+<p>This code snippet should print:
+
+<ul><pre>
+name: Chiba, year: 2005
+</pre></ul>
+
+<p>
+Since the annoation of <code>Point</code> is only <code>@Author</code>,
+the length of the array <code>all</code> is one
+and <code>all[0]</code> is an <code>Author</code> object.
+The member values of the annotation can be obtained
+by calling <code>name()</code> and <code>year()</code>
+on the <code>Author</code> object.
+
+<p>To use <code>getAnnotations()</code>, annotation types
+such as <code>Author</code> must be included in the current
+class path. <em>They must be also accessible from a
+<code>ClassPool</code> object.</em> If the class file of an annotation
+type is not found, Javassist cannot obtain the default values
+of the members of that annotation type.
+
+<p><br>
+
+<a name="runtime">
+<h3>4.5 Runtime support classes</h3>
+
+<p>In most cases, a class modified by Javassist does not require
+Javassist to run. However, some kinds of bytecode generated by the
+Javassist compiler need runtime support classes, which are in the
+<code>javassist.runtime</code> package (for details, please read
+the API reference of that package). Note that the
+<code>javassist.runtime</code> package is the only package that
+classes modified by Javassist may need for running. The other
+Javassist classes are never used at runtime of the modified classes.
+
+<p><br>
+
+<a name="import">
+<h3>4.6 Import</h3>
+
+<p>All the class names in source code must be fully qualified
+(they must include package names).
+However, the <code>java.lang</code> package is an
+exception; for example, the Javassist compiler can
+resolve <code>Object</code> as
+well as <code>java.lang.Object</code>.
+
+<p>To tell the compiler to search other packages when resolving a
+class name, call <code>importPackage()</code> in <code>ClassPool</code>.
+For example,
+
+<ul><pre>
+ClassPool pool = ClassPool.getDefault();
+pool.importPackage("java.awt");
+CtClass cc = pool.makeClass("Test");
+CtField f = CtField.make("public Point p;", cc);
+cc.addField(f);
+</pre></ul>
+
+<p>The seconde line instructs the compiler
+to import the <code>java.awt</code> package.
+Thus, the third line will not throw an exception.
+The compiler can recognize <code>Point</code>
+as <code>java.awt.Point</code>.
+
+<p>Note that <code>importPackage()</code> <em>does not</em> affect
+the <code>get()</code> method in <code>ClassPool</code>.
+Only the compiler considers the imported packages.
+The parameter to <code>get()</code>
+must be always a fully qualified name.
+
+<p><br>
+
+<a name="limit">
+<h3>4.7 Limitations</h3>
+
+<p>In the current implementation, the Java compiler included in Javassist
+has several limitations with respect to the language that the compiler can
+accept. Those limitations are:
+
+<p><li>The new syntax introduced by J2SE 5.0 (including enums and generics)
+has not been supported. Annotations are supported only by the low level
+API of Javassist.
+See the <code>javassist.bytecode.annotation</code> package.
+
+<p><li>Array initializers, a comma-separated list of expressions
+enclosed by braces <code>{</code> and <code>}</code>, are not
+available unless the array dimension is one.
+
+<p><li>Inner classes or anonymous classes are not supported.
+
+<p><li>Labeled <code>continue</code> and <code>break</code> statements
+are not supported.
+
+<p><li>The compiler does not correctly implement the Java method dispatch
+algorithm. The compiler may confuse if methods defined in a class
+have the same name but take different parameter lists.
+
+<p>For example,
+
+<ul><pre>
+class A {}
+class B extends A {}
+class C extends B {}
+
+class X {
+ void foo(A a) { .. }
+ void foo(B b) { .. }
+}
+</pre></ul>
+
+<p>If the compiled expression is <code>x.foo(new C())</code>, where
+<code>x</code> is an instance of X, the compiler may produce a call
+to <code>foo(A)</code> although the compiler can correctly compile
+<code>foo((B)new C())</code>.
+
+<p><li>The users are recommended to use <code>#</code> as the separator
+between a class name and a static method or field name.
+For example, in regular Java,
+
+<ul><pre>javassist.CtClass.intType.getName()</pre></ul>
+
+<p>calls a method <code>getName()</code> on
+the object indicated by the static field <code>intType</code>
+in <code>javassist.CtClass</code>. In Javassist, the users can
+write the expression shown above but they are recommended to
+write:
+
+<ul><pre>javassist.CtClass#intType.getName()</pre></ul>
+
+<p>so that the compiler can quickly parse the expression.
+</ul>
+
+<p><br>
+
+<a href="tutorial.html">Previous page</a>
+ <a href="tutorial3.html">Next page</a>
+
+<hr>
+Java(TM) is a trademark of Sun Microsystems, Inc.<br>
+Copyright (C) 2000-2010 by Shigeru Chiba, All rights reserved.
+</body>
+</html>
diff --git a/tutorial/tutorial3.html b/tutorial/tutorial3.html
new file mode 100644
index 0000000..dd4fb79
--- /dev/null
+++ b/tutorial/tutorial3.html
@@ -0,0 +1,366 @@
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <title>Javassist Tutorial</title>
+ <link rel="stylesheet" type="text/css" href="brown.css">
+</head>
+
+<body>
+
+<div align="right">Getting Started with Javassist</div>
+
+<div align="left"><a href="tutorial2.html">Previous page</a></div>
+
+<p>
+<a href="#intro">5. Bytecode level API</a>
+<ul>
+<li><a href="#classfile">Obtaining a <code>ClassFile</code> object</a>
+<br><li><a href="#member">Adding and removing a member</a>
+<br><li><a href="#traverse">Traversing a method body</a>
+<br><li><a href="#bytecode">Producing a bytecode sequence</a>
+<br><li><a href="#annotation">Annotations (Meta tags)</a>
+
+</ul>
+
+<p><a href="#generics">6. Generics</a>
+
+<p><a href="#varargs">7. Varargs</a>
+
+<p><a href="#j2me">8. J2ME</a>
+
+<p><br>
+
+<a name="intro">
+<h2>5. Bytecode level API</h2>
+
+<p>
+Javassist also provides lower-level API for directly editing
+a class file. To use this level of API, you need detailed
+knowledge of the Java bytecode and the class file format
+while this level of API allows you any kind of modification
+of class files.
+
+<p>
+If you want to just produce a simple class file,
+<code>javassist.bytecode.ClassFileWriter</code> might provide
+the best API for you. It is much faster than
+<code>javassist.bytecode.ClassFile</code> although its API
+is minimum.
+
+<a name="classfile">
+<h3>5.1 Obtaining a <code>ClassFile</code> object</h3>
+
+<p>A <code>javassist.bytecode.ClassFile</code> object represents
+a class file. To obtian this object, <code>getClassFile()</code>
+in <code>CtClass</code> should be called.
+
+<p>Otherwise, you can construct a
+<code>javassist.bytecode.ClassFile</code> directly from a class file.
+For example,
+
+<ul><pre>
+BufferedInputStream fin
+ = new BufferedInputStream(new FileInputStream("Point.class"));
+ClassFile cf = new ClassFile(new DataInputStream(fin));
+</pre></ul>
+
+<p>
+This code snippet creats a <code>ClassFile</code> object from
+<code>Point.class</code>.
+
+<p>
+A <code>ClassFile</code> object can be written back to a
+class file. <code>write()</code> in <code>ClassFile</code>
+writes the contents of the class file to a given
+<code>DataOutputStream</code>.
+
+<p><br>
+
+<a name="member">
+<h3>5.2 Adding and removing a member</h3>
+
+<p>
+<code>ClassFile</code> provides <code>addField()</code> and
+<code>addMethod()</code> for adding a field or a method (note that
+a constructor is regarded as a method at the bytecode level).
+It also provides <code>addAttribute()</code> for adding an attribute
+to the class file.
+
+<p>
+Note that <code>FieldInfo</code>, <code>MethodInfo</code>, and
+<code>AttributeInfo</code> objects include a link to a
+<code>ConstPool</code> (constant pool table) object. The <code>ConstPool</code>
+object must be common to the <code>ClassFile</code> object and
+a <code>FieldInfo</code> (or <code>MethodInfo</code> etc.) object
+that is added to that <code>ClassFile</code> object.
+In other words, a <code>FieldInfo</code> (or <code>MethodInfo</code> etc.) object
+must not be shared among different <code>ClassFile</code> objects.
+
+<p>
+To remove a field or a method from a <code>ClassFile</code> object,
+you must first obtain a <code>java.util.List</code>
+object containing all the fields of the class. <code>getFields()</code>
+and <code>getMethods()</code> return the lists. A field or a method can
+be removed by calling <code>remove()</code> on the <code>List</code> object.
+An attribute can be removed in a similar way.
+Call <code>getAttributes()</code> in <code>FieldInfo</code> or
+<code>MethodInfo</code> to obtain the list of attributes,
+and remove one from the list.
+
+
+<p><br>
+
+<a name="traverse">
+<h3>5.3 Traversing a method body</h3>
+
+<p>
+To examine every bytecode instruction in a method body,
+<code>CodeIterator</code> is useful. To otbain this object,
+do as follows:
+
+<ul><pre>
+ClassFile cf = ... ;
+MethodInfo minfo = cf.getMethod("move"); // we assume move is not overloaded.
+CodeAttribute ca = minfo.getCodeAttribute();
+CodeIterator i = ca.iterator();
+</pre></ul>
+
+<p>
+A <code>CodeIterator</code> object allows you to visit every
+bytecode instruction one by one from the beginning to the end.
+The following methods are part of the methods declared in
+<code>CodeIterator</code>:
+
+<ul>
+<li><code>void begin()</code><br>
+Move to the first instruction.<br>
+<li><code>void move(int index)</code><br>
+Move to the instruction specified by the given index.<br>
+<li><code>boolean hasNext()</code><br>
+Returns true if there is more instructions.<br>
+<li><code>int next()</code><br>
+Returns the index of the next instruction.<br>
+<em>Note that it does not return the opcode of the next
+instruction.</em><br>
+<li><code>int byteAt(int index)</code><br>
+Returns the unsigned 8bit value at the index.<br>
+<li><code>int u16bitAt(int index)</code><br>
+Returns the unsigned 16bit value at the index.<br>
+<li><code>int write(byte[] code, int index)</code><br>
+Writes a byte array at the index.<br>
+<li><code>void insert(int index, byte[] code)</code><br>
+Inserts a byte array at the index.
+Branch offsets etc. are automatically adjusted.<br>
+</ul>
+
+<p>The following code snippet displays all the instructions included
+in a method body:
+
+<ul><pre>
+CodeIterator ci = ... ;
+while (ci.hasNext()) {
+ int index = ci.next();
+ int op = ci.byteAt(index);
+ System.out.println(Mnemonic.OPCODE[op]);
+}
+</pre></ul>
+
+<p><br>
+
+<a name="bytecode">
+<h3>5.4 Producing a bytecode sequence</h3>
+
+<p>
+A <code>Bytecode</code> object represents a sequence of bytecode
+instructions. It is a growable array of bytecode.
+Here is a sample code snippet:
+
+<ul><pre>
+ConstPool cp = ...; // constant pool table
+Bytecode b = new Bytecode(cp, 1, 0);
+b.addIconst(3);
+b.addReturn(CtClass.intType);
+CodeAttribute ca = b.toCodeAttribute();
+</pre></ul>
+
+<p>
+This produces the code attribute representing the following sequence:
+
+<ul><pre>
+iconst_3
+ireturn
+</pre></ul>
+
+<p>
+You can also obtain a byte array containing this sequence by
+calling <code>get()</code> in <code>Bytecode</code>. The
+obtained array can be inserted in another code attribute.
+
+<p>
+While <code>Bytecode</code> provides a number of methods for adding a
+specific instruction to the sequence, it provides
+<code>addOpcode()</code> for adding an 8bit opcode and
+<code>addIndex()</code> for adding an index.
+The 8bit value of each opcode is defined in the <code>Opcode</code>
+interface.
+
+<p>
+<code>addOpcode()</code> and other methods for adding a specific
+instruction are automatically maintain the maximum stack depth
+unless the control flow does not include a branch.
+This value can be obtained by calling <code>getMaxStack()</code>
+on the <code>Bytecode</code> object.
+It is also reflected on the <code>CodeAttribute</code> object
+constructed from the <code>Bytecode</code> object.
+To recompute the maximum stack depth of a method body,
+call <code>computeMaxStack()</code> in <code>CodeAttribute</code>.
+
+<p><br>
+
+<a name="annotation">
+<h3>5.5 Annotations (Meta tags)</h3>
+
+<p>Annotations are stored in a class file
+as runtime invisible (or visible) annotations attribute.
+These attributes can be obtained from <code>ClassFile</code>,
+<code>MethodInfo</code>, or <code>FieldInfo</code> objects.
+Call <code>getAttribute(AnnotationsAttribute.invisibleTag)</code>
+on those objects. For more details, see the javadoc manual
+of <code>javassist.bytecode.AnnotationsAttribute</code> class
+and the <code>javassist.bytecode.annotation</code> package.
+
+<p>Javassist also let you access annotations by the higher-level
+API.
+If you want to access annotations through <code>CtClass</code>,
+call <code>getAnnotations()</code> in <code>CtClass</code> or
+<code>CtBehavior</code>.
+
+<p><br>
+
+<h2><a name="generics">6. Generics</a></h2>
+
+<p>The lower-level API of Javassist fully supports generics
+introduced by Java 5. On the other hand, the higher-level
+API such as <code>CtClass</code> does not directly support
+generics. However, this is not a serious problem for bytecode
+transformation.
+
+<p>The generics of Java is implemented by the erasure technique.
+After compilation, all type parameters are dropped off. For
+example, suppose that your source code declares a parameterized
+type <code>Vector<String></code>:
+
+<ul><pre>
+Vector<String> v = new Vector<String>();
+ :
+String s = v.get(0);
+</pre></ul>
+
+<p>The compiled bytecode is equivalent to the following code:
+
+<ul><pre>
+Vector v = new Vector();
+ :
+String s = (String)v.get(0);
+</pre></ul>
+
+<p>So when you write a bytecode transformer, you can just drop
+off all type parameters. For example, if you have a class:
+
+<ul><pre>
+public class Wrapper<T> {
+ T value;
+ public Wrapper(T t) { value = t; }
+}
+</pre></ul>
+
+<p>and want to add an interface <code>Getter<T></code> to the
+class <code>Wrapper<T></code>:
+
+<ul><pre>
+public interface Getter<T> {
+ T get();
+}
+</pre></ul>
+
+<p>Then the interface you really have to add is <code>Getter</code>
+(the type parameters <code><T></code> drops off)
+and the method you also have to add to the <code>Wrapper</code>
+class is this simple one:
+
+<ul><pre>
+public Object get() { return value; }
+</pre></ul>
+
+<p>Note that no type parameters are necessary.
+
+<p><br>
+
+<h2><a name="varargs">7. Varargs</a></h2>
+
+<p>Currently, Javassist does not directly support varargs. So to make a method with varargs,
+you must explicitly set a method modifier. But this is easy.
+Suppose that now you want to make the following method:
+
+<ul><pre>
+public int length(int... args) { return args.length; }
+</pre></ul>
+
+<p>The following code using Javassist will make the method shown above:
+
+<ul><pre>
+CtClass cc = /* target class */;
+CtMethod m = CtMethod.make("public int length(int[] args) { return args.length; }", cc);
+m.setModifiers(m.getModifiers() | Modifier.VARARGS);
+cc.addMethod(m);
+<pre></ul>
+
+<p>The parameter type <code>int...</code> is changed into <code>int[]</code>
+and <code>Modifier.VARARGS</code> is added to the method modifiers.
+
+<p>To call this method, you must write:
+
+<ul><pre>
+length(new int[] { 1, 2, 3 });
+</pre></ul>
+
+<p>instead of this method call using the varargs mechanism:
+
+<ul><pre>
+length(1, 2, 3);
+</pre></ul>
+
+<p><br>
+
+<h2><a name="j2me">8. J2ME</a></h2>
+
+<p>If you modify a class file for the J2ME execution environment,
+you must perform <it>preverification</it>. Preverifying is basically
+producing stack maps, which is similar to stack map tables introduced
+into J2SE at JDK 1.6. Javassist maintains the stack maps for J2ME only if
+<code>javassist.bytecode.MethodInfo.doPreverify</code> is true.
+
+<p>You can also manually
+produce a stack map for a modified method.
+For a given method represented by a <code>CtMethod</code> object <code>m</code>,
+you can produce a stack map by calling the following methods:
+
+<ul><pre>
+m.getMethodInfo().rebuildStackMapForME(cpool);
+</pre></ul>
+
+<p>Here, <code>cpool</code> is a <code>ClassPool</code> object, which is
+available by calling <code>getClassPool()</code> on a <code>CtClass</code>
+object. A <code>ClassPool</code> object is responsible for finding
+class files from given class pathes. To obtain all the <code>CtMethod</code>
+objects, call the <code>getDeclaredMethods</code> method on a <code>CtClass</code> object.
+
+<p><br>
+
+<a href="tutorial2.html">Previous page</a>
+
+<hr>
+Java(TM) is a trademark of Sun Microsystems, Inc.<br>
+Copyright (C) 2000-2010 by Shigeru Chiba, All rights reserved.
+</body>
+</html>