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>&nbsp;</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,&nbsp; 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")&nbsp;</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>&nbsp;<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;&nbsp; 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>&nbsp;</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>&nbsp;<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&nbsp; 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&nbsp; 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)&nbsp;</B>&nbsp;&nbsp; Notwithstanding Section 2.2(b) above, no 
+    patent license is granted: 1) for any code that Contributor has deleted from 
+    the Contributor Version; 2)&nbsp; separate from the Contributor 
+    Version;&nbsp; 3)&nbsp; for infringements caused by: i) third party 
+    modifications of Contributor Version or ii)&nbsp; the combination of 
+    Modifications made by that Contributor with other software&nbsp; (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>&nbsp;</P></UL>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 
+  <B>(c)&nbsp;&nbsp;&nbsp; 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.&nbsp; 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.&nbsp; 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>.&nbsp; 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.&nbsp; 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.&nbsp; </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.&nbsp; </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")&nbsp; alleging that: 
+  <P><B>(a)&nbsp; </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)&nbsp; 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.&nbsp; 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>&nbsp; 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.&nbsp; </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>&nbsp; In the event of termination under Sections 8.1 or 8.2 
+  above,&nbsp; 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?.&nbsp; �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>&nbsp;
+  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>&lt;<i>java-home</i>&gt;<tt>/jre/lib/ext</tt>.</ul>
+
+<p>&lt;<i>java-home</i>&gt; 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>&nbsp;
+<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>&nbsp;
+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&lt;String&gt;</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>"&lt;clinit&gt;"</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 (&gt;= 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, &lt;type&gt; 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, &lt;type&gt; 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, &lt;type&gt; 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, ... };
+     * &lt;<i>type</i>&gt; cvalue = &lt;<i>constant-value</i>&gt;;
+     *  <i>... copied method body ...</i>
+     * Object result = &lt;<i>returned value</i>&gt;
+     * return (<i>&lt;returnType&gt;</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>
+ * &#64;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>
+ * &#64;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_&lt;n&gt;
+     *
+     * @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_&lt;n&gt;
+     *
+     * @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_&lt;n&gt;
+     *
+     * @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_&lt;n&gt;
+     *
+     * @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_&lt;n&gt;
+     *
+     * @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_&lt;n&gt;
+     *
+     * @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_&lt;n&gt;
+     *
+     * @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_&lt;n&gt;
+     *
+     * @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_&lt;n&gt;
+     *
+     * @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_&lt;n&gt;
+     *
+     * @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_&lt;n&gt;
+     *
+     * @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_&lt;n&gt;
+     *
+     * @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_&lt;n&gt;
+     *
+     * @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_&lt;n&gt;
+     *
+     * @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 (&gt;= 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 (&gt;= 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 (&gt;= 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 (&gt;= 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 (&gt;= 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 (&gt;= 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 (&gt;= 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 (&gt;= 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 (&gt;= 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 (&gt;= 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>&lt;init&gt</code>.
+     */
+    public static final String nameInit = "<init>";
+
+    /**
+     * The name of class initializer (static initializer):
+     * <code>&lt;clinit&gt</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 (&gt;= 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>
+ * &nbsp;@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 (&gt;= 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>&nbsp&nbsp&nbsp Local variables are not accessible.&nbsp&nbsp
+</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>, ... &nbsp &nbsp</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>&nbsp</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>, ... &nbsp &nbsp</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>&nbsp</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>&nbsp</td></tr>
+
+<tr><td>&nbsp</td></tr>
+
+<tr>
+<td><code>$1</code>, <code>$2</code>, ... &nbsp &nbsp</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> &nbsp &nbsp</td>
+<td>A <code>java.lang.Class</code> object representing
+the class declaring the method.
+</td>
+</tr>
+
+<tr><td><code>$sig</code> &nbsp &nbsp</td>
+<td>An array of <code>java.lang.Class</code> objects representing
+the formal parameter types.</td>
+</tr>
+
+<tr><td><code>$type</code> &nbsp &nbsp</td>
+<td>A <code>java.lang.Class</code> object representing
+the formal result type.</td>
+</tr>
+
+<tr><td><code>$proceed</code> &nbsp &nbsp</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>, ... &nbsp &nbsp</td>
+<td>
+The parameters of the constructor call.
+</td>
+</tr>
+
+<tr><td><code>$class</code> &nbsp &nbsp</td>
+<td>A <code>java.lang.Class</code> object representing
+the class declaring the constructor.
+</td>
+</tr>
+
+<tr><td><code>$sig</code> &nbsp &nbsp</td>
+<td>An array of <code>java.lang.Class</code> objects representing
+the formal parameter types.</td>
+</tr>
+
+<tr><td><code>$proceed</code> &nbsp &nbsp</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>&nbsp</td></tr>
+
+<tr><td>&nbsp</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>&nbsp</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>&nbsp</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>&nbsp</td></tr>
+
+<tr><td><code>$class</code> &nbsp &nbsp</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> &nbsp &nbsp</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>, ... &nbsp &nbsp</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>&nbsp</td></tr>
+
+<tr>
+<td><code>$r</code></td>
+<td>
+The type of the created object.
+</td>
+</tr>
+
+<tr><td><code>$sig</code> &nbsp &nbsp</td>
+<td>An array of <code>java.lang.Class</code> objects representing
+the formal parameter types.</td>
+</tr>
+
+<tr><td><code>$type</code> &nbsp &nbsp</td>
+<td>A <code>java.lang.Class</code> object representing
+the class of the created object.
+</td></tr>
+
+<tr><td><code>$proceed</code> &nbsp &nbsp</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>, ... &nbsp &nbsp</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>&nbsp</td></tr>
+
+<tr>
+<td><code>$r</code></td>
+<td>
+The type of the created array.
+</td>
+</tr>
+
+<tr><td><code>$type</code> &nbsp &nbsp</td>
+<td>A <code>java.lang.Class</code> object representing
+the class of the created array.
+</td></tr>
+
+<tr><td><code>$proceed</code> &nbsp &nbsp</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> &nbsp &nbsp</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>&nbsp</td></tr>
+<tr><td>&nbsp</td></tr>
+<tr><td>&nbsp</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>&nbsp</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> &nbsp &nbsp</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>&nbsp</td></tr>
+
+<tr><td>&nbsp</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> &nbsp &nbsp</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>&nbsp</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>
+&nbsp;&nbsp;&nbsp;<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&lt;String&gt;</code>:
+
+<ul><pre>
+Vector&lt;String&gt; v = new Vector&lt;String&gt();
+  :
+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&lt;T&gt; {
+  T value;
+  public Wrapper(T t) { value = t; }
+}
+</pre></ul>
+
+<p>and want to add an interface <code>Getter&lt;T&gt;</code> to the
+class <code>Wrapper&lt;T&gt;</code>:
+
+<ul><pre>
+public interface Getter&lt;T&gt; {
+  T get();
+}
+</pre></ul>
+
+<p>Then the interface you really have to add is <code>Getter</code>
+(the type parameters <code>&lt;T&gt;</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>